(* tutorial03.c A pedagogical video player that will stream through every video frame as fast as it can and play audio (out of sync). This tutorial was written by Stephen Dranger (dranger@gmail.com). Code based on FFplay, Copyright (c) 2003 Fabrice Bellard, and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de) Conversion to Delphi by Oleksandr Nazaruk (mail@freehand.com.ua) Tested on Windows 8.1 64bit rus, compiled with Delphi XE5 Run using tutorial03 myvideofile.mpg to play the video stream on your screen. *) program tutorial03; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Classes, Winapi.Windows, VCL.Graphics, SDL2 in '../../../Injest/LIB/SDL/Pascal-SDL-2-Header/SDL2.pas', avcodec in '../FFmbc-0.7/libavcodec/avcodec.pas', avformat in '../FFmbc-0.7/libavformat/avformat.pas', avio in '../FFmbc-0.7/libavformat/avio.pas', avutil in '../FFmbc-0.7/libavutil/avutil.pas', opt in '../FFmbc-0.7/libavutil/opt.pas', rational in '../FFmbc-0.7/libavutil/rational.pas', imgutils in '../FFmbc-0.7/libavutil/imgutils.pas', fifo in '../FFmbc-0.7/libavutil/fifo.pas', file_ in '../FFmbc-0.7/libavutil/file_.pas', ctypes in '../FFmbc-0.7/ctypes.pas', swscale in '../FFmbc-0.7/libswscale/swscale.pas', avdevice in '../FFmbc-0.7/libavdevice/avdevice.pas', postprocess in '../FFmbc-0.7/libpostproc/postprocess.pas'; const SDL_AUDIO_BUFFER_SIZE = 1024; type PPacketQueue =^TPacketQueue; TPacketQueue = record first_pkt : PAVPacketList; last_pkt : PAVPacketList; nb_packets : integer; size : integer; mutex : PSDL_mutex; cond : PSDL_cond; end; var audioq : PPacketQueue = nil; quit : boolean = false; procedure packet_queue_init(var q: PPacketQueue); begin if not assigned(q) then new(q); fillchar(q^, sizeof(TPacketQueue), #0); q.mutex := SDL_CreateMutex(); q.cond := SDL_CreateCond(); end; function packet_queue_put(var q: PPacketQueue; pkt: PAVPacket): integer; var pkt1 : PAVPacketList; begin result:=-1; if not assigned(pkt) then exit; if(av_dup_packet(pkt) < 0) then begin result:=-1; exit; end; pkt1 := av_malloc(sizeof(TAVPacketList)); if not assigned(pkt1) then begin result:=-1; exit; end; pkt1^.pkt := pkt^; pkt1^.next := nil; SDL_LockMutex(q.mutex); try if not assigned(q.last_pkt) then q.first_pkt := pkt1 else q.last_pkt^.next := pkt1; q.last_pkt := pkt1; inc(q.nb_packets); q.size:=q.size+pkt1.pkt.size; SDL_CondSignal(q.cond); finally SDL_UnlockMutex(q.mutex); end; result:=0; end; function packet_queue_get(var q: PPacketQueue; var pkt: TAVPacket; block: integer): integer; var pkt1 : PAVPacketList; ret : integer; begin result:=-1; ret:=-1; SDL_LockMutex(q.mutex); try while not quit do begin pkt1 := q.first_pkt; if Assigned(pkt1) then begin q.first_pkt := pkt1^.next; if not assigned(q.first_pkt) then q.last_pkt := nil; dec(q.nb_packets); q.size := q.size-pkt1^.pkt.size; pkt := pkt1^.pkt; av_free(pkt1); ret := 1; break; end else if (block)<=0 then begin ret := 0; break; end else begin SDL_CondWait(q.cond, q.mutex); end; end; finally SDL_UnlockMutex(q.mutex); end; result:=ret; end; function audio_decode_frame(aCodecCtx: PAVCodecContext; audio_buf: pcuint8; buf_size: integer): integer; var pkt : TAVPacket; audio_pkt_data : pcuint8; audio_pkt_size : integer; len1 : integer; data_size : integer; buffer_size : integer; begin audio_pkt_size:=0; audio_pkt_data:=nil; data_size:=0; len1:=0; while True do begin while (audio_pkt_size > 0) do begin buffer_size := AVCODEC_MAX_AUDIO_FRAME_SIZE; len1 := avcodec_decode_audio3(aCodecCtx, PSmallInt(audio_buf), buffer_size, @pkt); if(len1 < 0) then begin (* if error, skip frame *) audio_pkt_size := 0; break; end; inc(audio_pkt_data,len1); dec(audio_pkt_size,len1); inc(data_size, len1); if(data_size <= 0) then begin (* No data yet, get more frames *) continue; end; (* We have data, return it and come back for more later *) result:=data_size; exit; end; if assigned(pkt.data) then av_free_packet(@pkt); if quit then begin result:=-1; exit; end; if(packet_queue_get(audioq, pkt, 1) < 0) then begin result:=-1; exit; end; audio_pkt_data := pkt.data; audio_pkt_size := pkt.size; end; end; procedure audio_callback(userdata: Pointer; stream: PUInt8; len: LongInt); cdecl; var aCodecCtx : PAVCodecContext; audio_size : integer; len1 : integer; audio_buf : array[0..AVCODEC_MAX_AUDIO_FRAME_SIZE] of cuint8; audio_buf_size : cardinal; audio_buf_index : cardinal; begin aCodecCtx:=PAVCodecContext(userdata); audio_buf_size := 0; audio_buf_index := 0; while (len > 0) do begin if(audio_buf_index >= audio_buf_size) then begin (* We have already sent all our data; get more *) audio_size := audio_decode_frame(aCodecCtx, @audio_buf[0], audio_buf_size); if(audio_size < 0) then begin (* If error, output silence *) audio_buf_size := 1024; // arbitrary? fillchar(audio_buf[0], audio_buf_size, #0); end else begin audio_buf_size := audio_size; end; audio_buf_index := 0; end; len1 := audio_buf_size - audio_buf_index; if(len1 > len) then len1 := len; move(audio_buf[audio_buf_index], stream^, len1); dec(len, len1); inc(stream,len1); inc(audio_buf_index,len1); end; end; var i : integer; videoStream : integer; audioStream : integer; src_filename : ansistring; pFormatCtx : PAVFormatContext = nil; aCodecCtx : PAVCodecContext = nil; pCodecCtx : PAVCodecContext = nil; pCodec : PAVCodec = nil; pStream : PPAVStream = nil; aCodec : PAVCodec = nil; videoOptionsDict: PAVDictionary = nil; audioOptionsDict: PAVDictionary = nil; pFrame : PAVFrame = nil; pFrameYUV420P : PAVFrame = nil; packet : TAVPacket; frameFinished : integer; sws_ctx : PSwsContext = nil; numBytes : integer; buffer : pcuint8; screen : PSDL_Window = nil; texture : PSDL_texture = nil; render : PSDL_renderer = nil; event : TSDL_Event; wanted_spec : TSDL_AudioSpec; spec : TSDL_AudioSpec; pict : TAVPicture; begin try if (ParamCount < 1) then begin writeln('Please provide a movie file'); exit; end; src_filename:=(AnsiString(ParamStr(1))); if SDL_Init(SDL_INIT_VIDEO or SDL_INIT_AUDIO or SDL_INIT_TIMER)<0 then begin writeln(format('Could not initialize SDL - %s', [SDL_GetError()])); exit; end; // Register all formats and codecs av_register_all(); // Open video file if (avformat_open_input(@pFormatCtx, PAnsiChar(src_filename), nil, nil)<>0) then begin writeln(format('Could not open source file %s', [src_filename])); exit; end; // Retrieve stream information if avformat_find_stream_info(pFormatCtx , nil) < 0 then begin writeln(format('Could not find stream information', [])); exit; end; // Dump information about file onto standard error av_dump_format(pFormatCtx, 0, PAnsiChar(src_filename), 0); // Find the first video stream videoStream:=-1; audioStream:=-1; pStream:=pFormatCtx.streams; for i:=0 to pFormatCtx.nb_streams-1 do begin if ((pStream^.codec.codec_type = AVMEDIA_TYPE_VIDEO) and (videoStream<0)) then begin videoStream := i; // Get a pointer to the codec context for the video stream pCodecCtx:=pStream^.codec; end else if ((pStream^.codec.codec_type = AVMEDIA_TYPE_AUDIO) and (audioStream < 0)) then begin audioStream := i; // Get a pointer to the codec context for the audio stream aCodecCtx:=pStream^.codec; wanted_spec.freq := aCodecCtx.sample_rate; wanted_spec.format := AUDIO_S16; wanted_spec.channels := aCodecCtx.channels; wanted_spec.silence := 0; wanted_spec.samples := SDL_AUDIO_BUFFER_SIZE; wanted_spec.callback := @audio_callback; wanted_spec.userdata := aCodecCtx; end; inc(pStream); end; if videoStream=-1 then begin writeln('Didn''t find a video stream'); exit; end; if audioStream=-1 then begin writeln('Didn''t find a audio stream'); exit; end; if(SDL_OpenAudio(@wanted_spec, @spec) <> 0) then begin writeln(format('SDL_OpenAudio: %s', [SDL_GetError()])); exit; end; aCodec := avcodec_find_decoder(aCodecCtx.codec_id); if not assigned(aCodec) then begin writeln('Unsupported codec!'); exit; end; avcodec_open2(aCodecCtx, aCodec, @audioOptionsDict); // audio_st = pFormatCtx->streams[index] packet_queue_init(audioq); SDL_PauseAudio(0); // Find the decoder for the video stream pCodec:=avcodec_find_decoder(pCodecCtx.codec_id); if not assigned(pCodec) then begin writeln('Unsupported codec!'); exit; end; // Open codec if avcodec_open2(pCodecCtx, pCodec, @videoOptionsDict)<0 then begin writeln('Could not open codec'); exit; end; // Allocate video frame pFrame:=avcodec_alloc_frame; // Allocate an AVFrame structure pFrameYUV420P:=avcodec_alloc_frame(); if not assigned(pFrameYUV420P) then begin writeln('Could not Allocate AVFrame structure'); exit; end; // Determine required buffer size and allocate buffer numBytes:=avpicture_get_size(pCodecCtx.pix_fmt, pCodecCtx.width, pCodecCtx.height); buffer:=av_malloc(numBytes*sizeof(cuint)); sws_ctx := sws_getContext ( pCodecCtx.width, pCodecCtx.height, pCodecCtx.pix_fmt, pCodecCtx.width, pCodecCtx.height, PIX_FMT_YUV420P, SWS_BILINEAR, nil, nil, nil ); // Assign appropriate parts of buffer to image planes in pFrameYUV420P // Note that pFrameRGB is an AVFrame, but AVFrame is a superset // of AVPicture avpicture_fill(PAVPicture(pFrameYUV420P), buffer, PIX_FMT_YUV420P, pCodecCtx.width, pCodecCtx.height); // Make a screen to put our video screen:=SDL_CreateWindow('Tutorial_03', SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, pCodecCtx.width, pCodecCtx.height, SDL_WINDOW_SHOWN ); if not assigned(screen) then begin writeln('SDL: could CreateWindow'); exit; end; render := SDL_CreateRenderer(screen, -1, 0); // Allocate a place to put our YUV image on that screen texture:=SDL_CreateTexture(render, SDL_PIXELFORMAT_YV12, sint32(SDL_TEXTUREACCESS_STREAMING), pCodecCtx.width, pCodecCtx.height); // Read frames and save first five frames to disk i:=0; while(av_read_frame(pFormatCtx, packet)>=0) do begin // Is this a packet from the video stream? if(packet.stream_index=videoStream) then begin // Decode video frame avcodec_decode_video2(pCodecCtx, pFrame, frameFinished, @packet); // Did we get a video frame? if frameFinished>0 then begin pict.data[0] := pFrameYUV420P.data[0]; pict.data[1] := pFrameYUV420P.data[2]; pict.data[2] := pFrameYUV420P.data[1]; pict.linesize[0] := pFrameYUV420P.linesize[0]; pict.linesize[1] := pFrameYUV420P.linesize[2]; pict.linesize[2] := pFrameYUV420P.linesize[1]; // Convert the image into YUV format that SDL uses sws_scale ( sws_ctx, @pFrame.data, @pFrame.linesize, 0, pCodecCtx.height, @pict.data, @pict.linesize ); SDL_UpdateTexture(texture, nil, buffer, pCodecCtx.width); SDL_RenderClear(Render); SDL_RenderCopy(Render, texture, nil, nil); SDL_RenderPresent(Render); // Free the packet that was allocated by av_read_frame av_free_packet(@packet); end; end else if(packet.stream_index=audioStream) then begin packet_queue_put(audioq, @packet); end else begin // Free the packet that was allocated by av_read_frame av_free_packet(@packet); end; SDL_PollEvent(@event); case event.type_ of SDL_QUITEV: begin quit:=true; SDL_Quit(); exit; end; end; end; quit:=true; // Free the pFrameYUV420P image av_free(buffer); av_free(pFrameYUV420P); // Free the YUV frame av_free(pFrame); // Close the codec avcodec_close(pCodecCtx); if assigned(texture) then begin SDL_DestroyTexture(texture); texture := nil; end; if assigned(Render) then begin SDL_DestroyRenderer(Render); Render := nil; end; if assigned(screen) then begin SDL_DestroyWindow(screen); screen := nil; end; if assigned(audioq) then dispose(audioq); // Close the video file av_close_input_file(pFormatCtx); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.
Stand:
- Windows 8.1 Enterprise 64Bit Rus
- Delphi XE5
Links:
- Dranger tutorial03 is ffmpeg and SDL Tutorial or How to Write a Video Player in Less Than 1000 Lines
Немає коментарів:
Дописати коментар