(* tutorial04.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 tutorial04 myvideofile.mpg to play the video stream on your screen. *) program tutorial04; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Classes, math, 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; MAX_AUDIO_FRAME_SIZE = 192000; MAX_AUDIOQ_SIZE = (5 * 16 * 1024); MAX_VIDEOQ_SIZE = (5 * 256 * 1024); USE_AUDIO_DRIVER = 'directsound'; FF_ALLOC_EVENT = (SDL_USEREVENT); FF_REFRESH_EVENT = (SDL_USEREVENT + 1); FF_QUIT_EVENT = (SDL_USEREVENT + 2); VIDEO_PICTURE_QUEUE_SIZE = 1; type PPacketQueue =^TPacketQueue; TPacketQueue = record first_pkt : PAVPacketList; last_pkt : PAVPacketList; nb_packets : integer; size : integer; mutex : PSDL_mutex; cond : PSDL_cond; end; PVideoPicture = ^TVideoPicture; TVideoPicture = record bmp : PSDL_texture; width, height : integer; (* source height & width *) allocated : integer; buffer : pcuint8; pFrameYUV420P : PAVFrame; end; PVideoState = ^TVideoState; TVideoState = record pFormatCtx : PAVFormatContext; videoStream : integer; audioStream : integer; audio_st : PAVStream; audioq : PPacketQueue; audio_buf : array[0..((AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) div 2)] of cuint8; audio_buf_size : cardinal; audio_buf_index : cardinal; audio_frame : TAVFrame; audio_pkt : TAVPacket; audio_pkt_data : pcuint8; audio_pkt_size : integer; video_st : PAVStream; videoq : PPacketQueue; pictq : array[0..VIDEO_PICTURE_QUEUE_SIZE] of TVideoPicture; pictq_size : integer; pictq_rindex : integer; pictq_windex : integer; pictq_mutex : PSDL_mutex; pictq_cond : PSDL_cond; parse_tid : PSDL_Thread; video_tid : PSDL_Thread; filename : array[0..1024] of ansichar; quit : boolean; io_context : PAVIOContext; sws_ctx : PSwsContext; end; var screen : PSDL_Window = nil; render : PSDL_renderer = nil; (* Since we only have one decoding thread, the Big Struct can be global in case we need it. *) global_video_state : PVideoState; 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 true do begin if global_video_state.quit then begin result:=-1; break; end; 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(is_: PVideoState): integer; var pkt : TAVPacket; /////////// PAVPacket len1 : integer; data_size : integer; buffer_size : integer; begin data_size:=0; len1:=0; pkt:=is_.audio_pkt; while True do begin while (is_.audio_pkt_size > 0) do begin buffer_size := ((AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) div 2); len1 := avcodec_decode_audio3(is_.audio_st.codec, PSmallInt(@is_.audio_buf[0]), buffer_size, @pkt); if(len1 <= 0) then begin (* if error, skip frame *) is_.audio_pkt_size := 0; break; end; inc(is_.audio_pkt_data,len1); dec(is_.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 is_.quit then begin result:=-1; exit; end; if(packet_queue_get(is_.audioq, pkt, 1) < 0) then begin result:=-1; exit; end; is_.audio_pkt_data := pkt.data; is_.audio_pkt_size := pkt.size; end; end; procedure audio_callback(userdata: Pointer; stream: PUInt8; len: LongInt); cdecl; var is_ : PVideoState; audio_size : integer; len1 : integer; begin is_:=PVideoState(userdata); while (len > 0) do begin if(is_.audio_buf_index >= is_.audio_buf_size) then begin (* We have already sent all our data; get more *) audio_size := audio_decode_frame(is_); if(audio_size < 0) then begin (* If error, output silence *) is_.audio_buf_size := 1024; // arbitrary? fillchar(is_.audio_buf[0], is_.audio_buf_size, #0); end else begin is_.audio_buf_size := audio_size; end; is_.audio_buf_index := 0; end; len1 := is_.audio_buf_size - is_.audio_buf_index; if(len1 > len) then len1 := len; move(is_.audio_buf[is_.audio_buf_index], stream^, len1); dec(len, len1); inc(stream,len1); inc(is_.audio_buf_index,len1); end; end; function sdl_refresh_timer_cb(interval: cardinal; opaque : Pointer): cardinal; cdecl; var event : TSDL_Event; begin event.type_ := FF_REFRESH_EVENT; event.user.data1 := opaque; SDL_PushEvent(@event); result:=0; (* 0 means stop timer *) end; (* schedule a video refresh in 'delay' ms *) procedure schedule_refresh(is_: PVideoState; delay: integer); begin SDL_AddTimer(delay, @sdl_refresh_timer_cb, is_); end; procedure video_display(is_: PVideoState); var vp : PVideoPicture; aspect_ratio : float; w, h, x, y : integer; begin vp := @is_.pictq[is_.pictq_rindex]; if assigned(vp.bmp) then begin if(is_.video_st.codec.sample_aspect_ratio.num = 0) then begin aspect_ratio := 0; end else begin aspect_ratio := av_q2d(is_.video_st.codec.sample_aspect_ratio) * is_.video_st.codec.width / is_.video_st.codec.height; end; if (aspect_ratio <= 0.0) then begin aspect_ratio := is_.video_st.codec.width /is_.video_st.codec.height; end; h := screen.h; w := (round(h * aspect_ratio)) and -3; if(w > screen.w) then begin w := screen.w; h := (round(w / aspect_ratio)) and -3; end; x := (screen.w - w) div 2; y := (screen.h - h) div 2; SDL_RenderClear(Render); SDL_RenderCopy(Render, vp.bmp, nil, nil); SDL_RenderPresent(Render); end; end; procedure video_refresh_timer(userdata: pointer); var is_ : PVideoState; begin is_ := PVideoState(userdata); // vp is used in later tutorials for synchronization //VideoPicture *vp; if assigned(is_.video_st) then begin if (is_.pictq_size = 0) then begin schedule_refresh(is_, 1) end else begin //vp = &is->pictq[is->pictq_rindex]; (* Now, normally here goes a ton of code about timing, etc. we're just going to guess at a delay for now. You can increase and decrease this value and hard code the timing - but I don't suggest that ;) We'll learn how to do it for real later. *) schedule_refresh(is_, 80); (* show the picture! *) video_display(is_); (* update queue for next picture! *) inc(is_.pictq_rindex); if (is_.pictq_rindex = VIDEO_PICTURE_QUEUE_SIZE) then begin is_.pictq_rindex := 0; end; SDL_LockMutex(is_.pictq_mutex); dec(is_.pictq_size); SDL_CondSignal(is_.pictq_cond); SDL_UnlockMutex(is_.pictq_mutex); end; end else begin schedule_refresh(is_, 100); end; end; procedure alloc_picture(userdata: pointer); var is_ : PVideoState; vp : PVideoPicture; numBytes : integer; begin is_ := PVideoState(userdata); vp := @is_.pictq[is_.pictq_windex]; if assigned(vp.bmp) then begin // we already have one make another, bigger/smaller SDL_DestroyTexture(vp.bmp); end; if assigned(vp.pFrameYUV420P) then begin av_free(vp.pFrameYUV420P); end; if assigned(vp.buffer) then begin av_free(vp.buffer); end; // Allocate an AVFrame structure vp.pFrameYUV420P:=avcodec_alloc_frame(); if not assigned(vp.pFrameYUV420P) then begin writeln('Could not Allocate AVFrame structure'); exit; end; // Determine required buffer size and allocate buffer numBytes:=avpicture_get_size(is_.video_st.codec.pix_fmt, is_.video_st.codec.width, is_.video_st.codec.height); vp.buffer:=av_malloc(numBytes*sizeof(cuint)); // 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(vp.pFrameYUV420P), vp.buffer, PIX_FMT_YUV420P, is_.video_st.codec.width, is_.video_st.codec.height); // Allocate a place to put our YUV image on that screen vp.bmp:=SDL_CreateTexture(render, SDL_PIXELFORMAT_YV12, sint32(SDL_TEXTUREACCESS_STREAMING), is_.video_st.codec.width, is_.video_st.codec.height); vp.width := is_.video_st.codec.width; vp.height := is_.video_st.codec.height; SDL_LockMutex(is_.pictq_mutex); vp.allocated := 1; SDL_CondSignal(is_.pictq_cond); SDL_UnlockMutex(is_.pictq_mutex); end; function queue_picture(is_: PVideoState; pFrame: PAVFrame): integer; var vp : PVideoPicture; pict : TAVPicture; event : TSDL_Event; begin (* wait until we have space for a new pic *) SDL_LockMutex(is_.pictq_mutex); while ((is_.pictq_size >= VIDEO_PICTURE_QUEUE_SIZE) and (not is_.quit)) do begin SDL_CondWait(is_.pictq_cond, is_.pictq_mutex); end; SDL_UnlockMutex(is_.pictq_mutex); if is_.quit then begin result:=-1; exit; end; // windex is set to 0 initially vp := @is_.pictq[is_.pictq_windex]; (* allocate or resize the buffer! *) if ((not assigned(vp.bmp)) or (vp.width <> is_.video_st.codec.width) or (vp.height <> is_.video_st.codec.height)) then begin vp.allocated := 0; (* we have to do it in the main thread *) event.type_ := FF_ALLOC_EVENT; event.user.data1 := is_; SDL_PushEvent(@event); (* wait until we have a picture allocated *) SDL_LockMutex(is_.pictq_mutex); while ((vp.allocated=0) and (not is_.quit)) do begin SDL_CondWait(is_.pictq_cond, is_.pictq_mutex); end; SDL_UnlockMutex(is_.pictq_mutex); if is_.quit then begin result:=-1; exit; end; end; (* We have a place to put our picture on the queue *) if assigned(vp.bmp) then begin pict.data[0] := vp.pFrameYUV420P.data[0]; pict.data[1] := vp.pFrameYUV420P.data[2]; pict.data[2] := vp.pFrameYUV420P.data[1]; pict.linesize[0] := vp.pFrameYUV420P.linesize[0]; pict.linesize[1] := vp.pFrameYUV420P.linesize[2]; pict.linesize[2] := vp.pFrameYUV420P.linesize[1]; // Convert the image into YUV format that SDL uses sws_scale ( is_.sws_ctx, @pFrame.data, @pFrame.linesize, 0, is_.video_st.codec.height, @pict.data, @pict.linesize ); SDL_UpdateTexture(vp.bmp, nil, vp.buffer, is_.video_st.codec.width); (* now we inform our display thread that we have a pic ready *) inc(is_.pictq_windex); if(is_.pictq_windex = VIDEO_PICTURE_QUEUE_SIZE) then is_.pictq_windex := 0; SDL_LockMutex(is_.pictq_mutex); inc(is_.pictq_size); SDL_UnlockMutex(is_.pictq_mutex); end; result:=0; end; function video_thread(arg: Pointer): integer; cdecl; var is_ : PVideoState; pkt1 : TAVPacket; packet : TAVPacket; frameFinished : integer; pFrame : PAVFrame; begin is_:= PVideoState(arg); packet:= pkt1; //////////PAVPAcket pFrame := avcodec_alloc_frame(); while true do begin if(packet_queue_get(is_.videoq, packet, 1) < 0) then // means we quit getting packets break; // Decode video frame avcodec_decode_video2(is_.video_st.codec, pFrame, frameFinished, @packet); // Did we get a video frame? if (frameFinished)>0 then begin if(queue_picture(is_, pFrame) < 0) then break; end; av_free_packet(@packet); end; av_free(pFrame); result:=0; end; function stream_component_open(var is_: PVideoState; stream_index: integer): integer; var pFormatCtx : PAVFormatContext; codecCtx : PAVCodecContext; codec : PAVCodec; optionsDict : PAVDictionary; wanted_spec : TSDL_AudioSpec; spec : TSDL_AudioSpec; pStreams : PPAVStream; pStream : PAVStream; i : integer; begin result:=-1; pStream := nil; pFormatCtx := is_.pFormatCtx; codecCtx := nil; codec := nil; optionsDict:= nil; if((stream_index < 0) or (stream_index >= pFormatCtx.nb_streams)) then begin result:=-1; exit; end; // Get a pointer to the codec context for the video stream pStreams:=pFormatCtx.streams; for i:=0 to pFormatCtx.nb_streams-1 do begin if i=stream_index then begin pStream:=pStreams^; codecCtx:=pStream.codec; end; inc(pStreams); end; if(codecCtx^.codec_type = AVMEDIA_TYPE_AUDIO) then begin // Set audio settings from codec info wanted_spec.freq := codecCtx^.sample_rate; wanted_spec.format := AUDIO_S16; wanted_spec.channels := codecCtx^.channels; wanted_spec.silence := 0; wanted_spec.samples := SDL_AUDIO_BUFFER_SIZE; wanted_spec.callback := @audio_callback; wanted_spec.userdata := is_; if(SDL_OpenAudio(@wanted_spec, @spec) <> 0) then begin writeln(format('SDL_OpenAudio: %s', [SDL_GetError()])); //exit; end; end; codec := avcodec_find_decoder(codecCtx.codec_id); if ((not assigned(codec)) or (avcodec_open2(codecCtx, codec, @optionsDict)<0)) then begin writeln('Unsupported codec!'); exit; end; case codecCtx.codec_type of AVMEDIA_TYPE_AUDIO: begin is_.audioStream := stream_index; is_.audio_st := pStream; is_.audio_buf_size := 0; is_.audio_buf_index := 0; fillchar(is_.audio_pkt, sizeof(is_.audio_pkt), #0); packet_queue_init(is_.audioq); SDL_PauseAudio(0); end; AVMEDIA_TYPE_VIDEO: begin is_.videoStream := stream_index; is_.video_st := pStream; packet_queue_init(is_.videoq); is_.video_tid := SDL_CreateThread(@video_thread, nil, is_); is_.sws_ctx := sws_getContext ( is_.video_st.codec.width, is_.video_st.codec.height, is_.video_st.codec.pix_fmt, is_.video_st.codec.width, is_.video_st.codec.height, PIX_FMT_YUV420P, SWS_BILINEAR, nil, nil, nil ); end; end; result:=0; end; function decode_interrupt_cb(opaque: Pointer): integer; begin if (assigned(global_video_state) and (global_video_state.quit)) then result:=1 else result:=0; end; function decode_thread(arg: pointer): LongInt; cdecl; label fail; var is_ : PVideoState; pFormatCtx : PAVFormatContext; packet : PAVPacket; pkt1 : TAVPacket; video_index : integer; audio_index : integer; i : integer; io_dict : PAVDictionary; pStream : PPAVStream; event : TSDL_Event; begin is_ := PVideoState(arg); pFormatCtx:= nil; packet := @pkt1; video_index := -1; audio_index := -1; io_dict := nil; is_.videoStream := -1; is_.audioStream := -1; global_video_state := is_; if avio_open(is_.io_context, @is_.filename[0], 0)<0 then begin writeln(format('Unable to open I/O for %s', [ansistring(is_.filename)])); result:=-1; exit; end; // Open video file if(avformat_open_input(@pFormatCtx, @is_.filename[0], nil, nil)<>0) then begin writeln(format('Could not open source file %s', [ansistring(is_.filename)])); result:=-1; exit; end; is_.pFormatCtx := pFormatCtx; // Retrieve stream information if avformat_find_stream_info(pFormatCtx , nil) < 0 then begin writeln(format('Could not find stream information', [])); result:=-1; exit; end; // Dump information about file onto standard error av_dump_format(pFormatCtx, 0, @is_.filename[0], 0); // Find the first video stream pStream:=pFormatCtx.streams; for i:=0 to pFormatCtx.nb_streams-1 do begin if ((pStream^.codec.codec_type = AVMEDIA_TYPE_VIDEO) and (video_index<0)) then begin video_index := i; end else if ((pStream^.codec.codec_type = AVMEDIA_TYPE_AUDIO) and (audio_index < 0)) then begin audio_index := i; end; inc(pStream); end; if (audio_index >= 0) then stream_component_open(is_, audio_index); if(video_index >= 0) then stream_component_open(is_, video_index); if ((is_.videoStream < 0) or (is_.audioStream < 0)) then begin writeln(format('%s: could not open codecs', [ansistring(is_.filename)])); goto fail; end; // main decode loop while True do begin if is_.quit then break; // seek stuff goes here if((is_.audioq.size > MAX_AUDIOQ_SIZE) or (is_.videoq.size > MAX_VIDEOQ_SIZE)) then begin SDL_Delay(10); continue; end; if(av_read_frame(is_.pFormatCtx, packet^) < 0) then begin if(is_.pFormatCtx.pb.error = 0) then begin SDL_Delay(100); (* no error; wait for user input *) continue; end else begin break; end; end; // Is this a packet from the video stream? if(packet.stream_index = is_.videoStream) then packet_queue_put(is_.videoq, packet) else if(packet.stream_index = is_.audioStream) then packet_queue_put(is_.audioq, packet) else av_free_packet(@packet); end; (* all done - wait for it *) while (not is_.quit) do SDL_Delay(100); fail: begin event.type_ := FF_QUIT_EVENT; event.user.data1 := is_; SDL_PushEvent(@event); end; result:=0; end; var event : TSDL_event; is_ : PVideoState; src_filename : ansistring; FAudioDriver : ansistring; begin try new(is_); 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(); StrPCopy(is_.filename, src_filename); // List audio driver //for I := 0 to SDL_GetNumAudioDrivers-1 do // FAudioDriver:=SDL_GetAudioDriver(i); // Set audio driver FAudioDriver:=USE_AUDIO_DRIVER; SDL_AudioInit(PAnsiChar(FAudioDriver)); // Make a screen to put our video screen:=SDL_CreateWindow('Tutorial_04', SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 720, 576, SDL_WINDOW_SHOWN ); if not assigned(screen) then begin writeln('SDL: could CreateWindow'); exit; end; render := SDL_CreateRenderer(screen, -1, 0); is_.pictq_mutex := SDL_CreateMutex(); is_.pictq_cond := SDL_CreateCond(); schedule_refresh(is_, 40); is_.parse_tid := SDL_CreateThread(@decode_thread, nil, is_); if not assigned(is_.parse_tid) then begin av_free(is_); exit; end; while True do begin SDL_WaitEvent(@event); case event.type_ of FF_QUIT_EVENT: begin break; end; SDL_QUITEV: begin is_.quit := true; (* * If the video has finished playing, then both the picture and * audio queues are waiting for more data. Make them stop * waiting and terminate normally. *) SDL_CondSignal(is_.audioq.cond); SDL_CondSignal(is_.videoq.cond); SDL_Quit(); break; end; FF_ALLOC_EVENT: begin alloc_picture(event.user.data1); end; FF_REFRESH_EVENT: begin video_refresh_timer(event.user.data1); end; end; end; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.
Stand:
- Windows 8.1 Enterprise 64Bit Rus
- Delphi XE5
Links:
- Dranger tutorial04 is ffmpeg and SDL Tutorial or How to Write a Video Player in Less Than 1000 Lines
Coin Casino: No Deposit Bonus & Free Spins › Coin › coin-casino › no-deposit-casino
ВідповістиВидалитиThe Coin Casino is a new and gioco digitale innovative online casino created in 2017. Established in 2017, this online casino is open for real players from all over the world. 코인카지노 With great odds sbobet ทางเข้า and