вівторок, 10 червня 2014 р.

FFMBC_08: Seeking and Software Scaling

(*
    tutorial07.c
    A pedagogical video player that really works! Now with seeking features.

    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

    tutorial07 myvideofile.mpg

    to play the video stream on your screen.
*)

program tutorial07;

{$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';

type
  TAV_SYNC= (AV_SYNC_AUDIO_MASTER,
             AV_SYNC_VIDEO_MASTER,
             AV_SYNC_EXTERNAL_MASTER);

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';

  AV_SYNC_THRESHOLD             = 0.01;
  AV_NOSYNC_THRESHOLD           = 10.0;

  SAMPLE_CORRECTION_PERCENT_MAX = 10;
  AUDIO_DIFF_AVG_NB             = 20;

  FF_ALLOC_EVENT                 = (SDL_USEREVENT);
  FF_REFRESH_EVENT              = (SDL_USEREVENT + 1);
  FF_QUIT_EVENT                 = (SDL_USEREVENT + 2);

  VIDEO_PICTURE_QUEUE_SIZE      = 1;

  DEFAULT_AV_SYNC_TYPE          = AV_SYNC_VIDEO_MASTER;

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;
    pts           : double;
    buffer        : pcuint8;
    pFrameYUV420P : PAVFrame;
  end;

  PVideoState = ^TVideoState;
  TVideoState = record
    pFormatCtx        : PAVFormatContext;
    videoStream       : integer;
    audioStream       : integer;

    av_sync_type      : TAV_SYNC;
    external_clock    : double; (* external clock base *)
    external_clock_time : cint64;

    pause             : boolean;

    scale             : double;

    seek_req          : boolean;
    seek_flags        : integer;
    seek_pos          : cint64;

    audio_clock       : double;
    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;
    audio_hw_buf_size : integer;

    audio_diff_cum    : double; (* used for AV difference average computation *)
    audio_diff_avg_coef   : double;
    audio_diff_threshold  : double;
    audio_diff_avg_count  : integer;

    frame_timer       : double;
    frame_last_pts    : double;
    frame_last_delay  : double;
    video_clock       : double; ///pts of last decoded frame / predicted pts of next decoded frame


    video_current_pts : double; ///current displayed pts (different from video_clock if frame fifos are used)
    video_current_pts_time  : cint64;  ///time (av_gettime) at which we updated video_current_pts - used to have running video pts


    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;

  flush_pkt           : TAVPacket;


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((pkt<>@flush_pkt) and (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;

procedure packet_queue_flush(var q: PPacketQueue);
var
  pkt, pkt1: PAVPacketList;
begin

  SDL_LockMutex(q.mutex);
  try
    pkt := q.first_pkt;
    while( pkt <> nil) do
    begin
      pkt1 := pkt^.next;
      av_free_packet(@pkt^.pkt);
      av_freep(@pkt);
      pkt := pkt1
    end;

    q.last_pkt := nil;
    q.first_pkt := nil;
    q.nb_packets := 0;
    q.size := 0;
  finally
    SDL_UnlockMutex(q.mutex);
  end;
end;


function get_audio_clock(is_: PVideoState): double;
var
  pts           : double;
  hw_buf_size   : integer;
  bytes_per_sec : integer;
  n             : integer;
begin
  pts := is_.audio_clock; (* maintained in the audio thread *)
  hw_buf_size := is_.audio_buf_size - is_.audio_buf_index;
  bytes_per_sec := 0;
  n := is_.audio_st.codec.channels * 2;
  if assigned(is_.audio_st) then
    bytes_per_sec := is_.audio_st.codec.sample_rate * n;

  if(bytes_per_sec>0) then
    pts := pts - double(hw_buf_size / bytes_per_sec);

  result:=pts;
end;

function get_video_clock(is_ : PVideoState): double;
var
  delta : double;
begin
  delta := (av_gettime() - is_.video_current_pts_time) / 1000000.0;
  result:=is_.video_current_pts + delta;
end;

function get_external_clock(is_ : PVideoState): double;
begin
  result:=av_gettime() / 1000000.0;
end;

function get_master_clock(is_ : PVideoState): double;
begin
  if(is_.av_sync_type = AV_SYNC_VIDEO_MASTER) then
  begin
    result:=get_video_clock(is_);
  end else
  if(is_.av_sync_type = AV_SYNC_AUDIO_MASTER) then
  begin
    result:=get_audio_clock(is_);
  end else result:=get_external_clock(is_);
end;


(* Add or subtract samples to get a better sync, return new
   audio buffer size *)
function synchronize_audio(var is_ : PVideoState; samples: PSmallInt; var samples_size: integer; pts: double): integer;
var
  n             : integer;
  ref_clock     : double;
  diff          : double;
  avg_diff      : double;
  wanted_size,
  min_size,
  max_size      : integer; (*, nb_samples *)
  samples_end   : pcuint8;
  q             : pcuint8;
  nb            : integer;
begin
  n := 2 * is_.audio_st.codec.channels;

  if(is_.av_sync_type <> AV_SYNC_AUDIO_MASTER) then
  begin
    ref_clock := get_master_clock(is_);
    diff := get_audio_clock(is_) - ref_clock;

    if(diff < AV_NOSYNC_THRESHOLD) then
    begin
      // accumulate the diffs
      is_.audio_diff_cum := diff + is_.audio_diff_avg_coef	* is_.audio_diff_cum;
      if (is_.audio_diff_avg_count < AUDIO_DIFF_AVG_NB) then
      begin
	      inc(is_.audio_diff_avg_count);
      end else
      begin
        avg_diff := is_.audio_diff_cum * (1.0 - is_.audio_diff_avg_coef);
        if (abs(avg_diff) >= is_.audio_diff_threshold) then
        begin
          wanted_size := integer(samples_size + (round(diff * is_.audio_st.codec.sample_rate) * n));
          min_size := samples_size * ((100 - SAMPLE_CORRECTION_PERCENT_MAX) div 100);
          max_size := samples_size * ((100 + SAMPLE_CORRECTION_PERCENT_MAX) div 100);
          if(wanted_size < min_size) then
          begin
            wanted_size := min_size;
          end else
          if (wanted_size > max_size) then
          begin
            wanted_size := max_size;
          end;
          if(wanted_size < samples_size) then
          begin
            (* remove samples *)
            samples_size := wanted_size;
          end else
          if(wanted_size > samples_size) then
          begin
            (* add samples by copying final sample*)
            nb := (samples_size - wanted_size);
            samples_end := Pointer(byte(samples) + samples_size - n);
            q := Pointer(byte(samples_end) + n);
            while(nb > 0) do
            begin
              move(samples_end^, q^, n);
              inc(q,n);
              nb := nb-n;
            end;
            samples_size := wanted_size;
          end;
        end;
      end;
    end else
    begin
      (* difference is TOO big; reset diff stuff *)
      is_.audio_diff_avg_count := 0;
      is_.audio_diff_cum := 0;
    end;
  end;
  result:=samples_size;
end;



function audio_decode_frame(var is_: PVideoState; var pts_ptr: double): integer;
var
  pkt             : TAVPacket;    /////////// PAVPacket
  len1            : integer;
  data_size       : integer;
  buffer_size     : integer;
  pts             : double;
  n               : 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;

      pts := is_.audio_clock;
      pts_ptr := pts;
      n := 2 * is_.audio_st.codec.channels;
      is_.audio_clock :=is_.audio_clock + (data_size / (n * is_.audio_st.codec.sample_rate));

      (* 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;

    (* next packet *)
    if(packet_queue_get(is_.audioq, pkt, 1) < 0) then
    begin
      result:=-1;
      exit;
    end;

    if (pkt.data = flush_pkt.data) then
    begin
      avcodec_flush_buffers(is_.audio_st.codec);
      continue;
    end;

    is_.audio_pkt_data := pkt.data;
    is_.audio_pkt_size := pkt.size;
    (* if update, update the audio clock w/pts *)
    if(pkt.pts <> AV_NOPTS_VALUE) then
      is_.audio_clock := av_q2d(is_.audio_st.time_base)*pkt.pts;
  end;
end;

procedure audio_callback(userdata: Pointer; stream: PUInt8; len: LongInt); cdecl;
var
  is_             : PVideoState;
  audio_size      : integer;
  len1            : integer;
  pts             : double;
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_, pts);
      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
        audio_size := synchronize_audio(is_, PSmallInt(@is_.audio_buf[0]), audio_size, pts);
	      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(var 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;
  texr          : TSDL_Rect;
  scale         : double;
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);

    // Software scale
    scale:=1.0+global_video_state.scale;
    if scale<>1 then
    begin
      texr.w:= round(is_.video_st.codec.width * scale);
      texr.h:= round(is_.video_st.codec.height * scale);
      texr.x:= (is_.video_st.codec.width-texr.w) div 2;
      texr.y:= (is_.video_st.codec.height-texr.h) div 2;
      SDL_RenderCopy(Render, vp.bmp, nil, @texr);
    end else
      SDL_RenderCopy(Render, vp.bmp, nil, nil);


    SDL_RenderPresent(Render);
  end;
end;

procedure video_refresh_timer(userdata: pointer);
var
  is_             : PVideoState;
  vp              : PVideoPicture;
  actual_delay    : double;
  delay           : double;
  sync_threshold  : double;
  ref_clock       : double;
  diff            : double;
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];

      is_.video_current_pts := vp.pts;
      is_.video_current_pts_time := av_gettime();

      delay := vp.pts - is_.frame_last_pts; (* the pts from last time *)
      if ((delay <= 0) or  (delay >= 1.0)) then
      begin
	      (* if incorrect delay, use previous one *)
        delay := is_.frame_last_delay;
      end;

      (* save for next time *)
      is_.frame_last_delay := delay;
      is_.frame_last_pts := vp.pts;


      (* update delay to sync to audio if not master source *)
      if(is_.av_sync_type <> AV_SYNC_VIDEO_MASTER) then
      begin
        ref_clock := get_master_clock(is_);
        diff := vp.pts - ref_clock;


        (* Skip or repeat the frame. Take delay into account
        FFPlay still doesn't "know if this is the best guess." *)
        if (delay > AV_SYNC_THRESHOLD) then
          sync_threshold := delay
        else
          sync_threshold := AV_SYNC_THRESHOLD;


        if(abs(diff) < AV_NOSYNC_THRESHOLD) then
        begin
          if (diff <= -sync_threshold) then
            delay := 0
          else
          if(diff >= sync_threshold) then
            delay := 2 * delay;
        end;
      end;

      is_.frame_timer := is_.frame_timer + delay;
      (* computer the REAL delay *)
      actual_delay := is_.frame_timer - (av_gettime() / 1000000.0);

      if(actual_delay < 0.010) then
      begin
	      (* Really it should skip the picture instead *)
	      actual_delay := 0.010;
      end;
      schedule_refresh(is_, round(actual_delay * 1000 + 0.5));

      (* 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(var is_: PVideoState; pFrame: PAVFrame; pts: double): 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 we are skipping a frame, do we set this to null
     but still return vp->allocated = 1? *)

  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);

    vp.pts := pts;

    (* 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 synchronize_video(var is_: PVideoState; var src_frame: PAVFrame; pts_: double): double;
var
  frame_delay : double;
  pts         : double;
begin
  pts:=pts_;

  if (pts <> 0) then
  begin
    (* if we have pts, set video clock to it *)
    is_.video_clock := pts;
  end else begin
    (* if we aren't given a pts, set it to the clock *)
    pts := is_.video_clock;
  end;
  (* update the video clock *)
  frame_delay := av_q2d(is_.video_st.codec.time_base);
  (* if we are repeating a frame, adjust clock accordingly *)
  frame_delay := frame_delay  + (src_frame.repeat_pict * (frame_delay * 0.5));
  is_.video_clock := is_.video_clock + frame_delay;
  result:=pts;
end;

var
  global_video_pkt_pts :  cuint64 = AV_NOPTS_VALUE;

(* These are called whenever we allocate a frame
 * buffer. We use this to store the global_pts in
 * a frame at the time it is allocated.
 *)
function our_get_buffer(c: PAVCodecContext; pic: PAVFrame): cint; cdecl;
var
  ret : integer;
  pts : pcuint64;
begin
  ret := avcodec_default_get_buffer(c, pic);
  pts := av_malloc(sizeof(pcuint64));
  cuint64(pts^) := global_video_pkt_pts;
  pic^.opaque := pts;
  result:=ret;
end;



procedure our_release_buffer(c: PAVCodecContext; pic: PAVFrame); cdecl;
begin
  if assigned(pic) then
  begin
    //if assigned(pic.opaque) then
    //  av_freep(pic.opaque);
  end;
  avcodec_default_release_buffer(c, pic);
end;



function video_thread(arg: Pointer): integer;  cdecl;
var
  is_           : PVideoState;
  pkt1          : TAVPacket;
  packet        : TAVPacket;
  frameFinished : integer;
  pFrame        : PAVFrame;
  pts           : double;
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;

    if(packet.data = flush_pkt.data) then
    begin
      avcodec_flush_buffers(is_.video_st.codec);
      continue;
    end;

    pts:=0;
    // Save global pts to be stored in pFrame in first call
    global_video_pkt_pts := packet.pts;

    // Decode video frame
    avcodec_decode_video2(is_.video_st.codec, pFrame, frameFinished, @packet);

    if ((packet.dts = AV_NOPTS_VALUE)  and assigned(pFrame.opaque) and (cuint64(pFrame.opaque^) <> AV_NOPTS_VALUE))then
    begin
      pts := cuint64(pFrame.opaque^);
    end else
    if(packet.dts <> AV_NOPTS_VALUE) then
    begin
      pts := packet.dts
    end else pts := 0;


    pts := pts * av_q2d(is_.video_st.time_base);

    // Did we get a video frame?
    if (frameFinished)>0 then
    begin
      pts := synchronize_video(is_, pFrame, pts);
      if(queue_picture(is_, pFrame, pts) < 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;

    is_.audio_hw_buf_size := spec.size;

  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;

      (* averaging filter for audio sync *)
      is_.audio_diff_avg_coef := exp(log10(0.01 / AUDIO_DIFF_AVG_NB));
      is_.audio_diff_avg_count := 0;
      (* Correct audio only if larger error than this *)
      is_.audio_diff_threshold := 2.0 * SDL_AUDIO_BUFFER_SIZE / codecCtx.sample_rate;



      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;

      is_.frame_timer := av_gettime() / 1000000.0;
      is_.frame_last_delay := 40e-3;
      is_.video_current_pts_time := av_gettime();



      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
      );
      codecCtx.get_buffer := @our_get_buffer;
      codecCtx.release_buffer := @our_release_buffer;
    end;
  end;
  result:=0;
end;


function decode_interrupt_cb(opaque: Pointer): integer; cdecl;
begin
  Result:=Ord((assigned(@global_video_state) and global_video_state.quit));
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;
  stream_index  : integer;
  seek_target   : cint64;
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_.seek_req) then
    begin
      stream_index := -1;
      seek_target := is_.seek_pos;

      if (is_.videoStream >= 0)then
        stream_index := is_.videoStream
      else
      if(is_.audioStream >= 0) then
        stream_index := is_.audioStream;

      if(stream_index>=0)then
      begin
         pStream:=pFormatCtx.streams;
         for i:=0 to pFormatCtx.nb_streams-1 do
         begin
          if i=stream_index then
          begin
            seek_target := av_rescale_q(seek_target, AV_TIME_BASE_Q, pStream^.time_base);
            break;
          end;
            inc(pStream);
         end;
      end;

      if(av_seek_frame(is_.pFormatCtx, stream_index, seek_target, is_.seek_flags) < 0) then
      begin
	      writeln(format('%s: error while seeking', [ansistring(is_.pFormatCtx.filename)]));
      end else
      begin
        if(is_.audioStream >= 0) then
        begin
          packet_queue_flush(is_.audioq);
          packet_queue_put(is_.audioq, @flush_pkt);
        end;
        if(is_.videoStream >= 0) then
        begin
          packet_queue_flush(is_.videoq);
          packet_queue_put(is_.videoq, @flush_pkt);
        end;
      end;
      is_.seek_req := false;
    end;

    // 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;


procedure stream_seek(var is_: PVideoState; pos: cint64; rel: double);
begin
  if (not is_.seek_req) then
  begin
    is_.seek_pos := pos;
    if rel<0 then
      is_.seek_flags:=AVSEEK_FLAG_BACKWARD
    else
      is_.seek_flags:=0;
    is_.seek_req := true;
  end;
end;


procedure doSeek(const incr : double);
var
  pos : double;
begin
  if ((incr<>0) and assigned(global_video_state)) then
  begin
    pos := get_master_clock(global_video_state);
    pos := pos+incr;
    stream_seek(global_video_state, round(pos * AV_TIME_BASE), incr);
  end;
end;

procedure doScale(incr : double);
begin
  if ((incr<>0) and assigned(global_video_state)) then
  begin
    global_video_state.scale:=global_video_state.scale+incr;
  end;
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_07', 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_.av_sync_type := DEFAULT_AV_SYNC_TYPE;
    is_.parse_tid := SDL_CreateThread(@decode_thread, nil, is_);
    if not assigned(is_.parse_tid) then
    begin
      av_free(is_);
      exit;
    end;

    av_init_packet(flush_pkt);
    //flush_pkt.data := (unsigned char *)"FLUSH";

  while(SDL_WaitEvent(@event)=1)do
    begin
      case event.type_ of
        SDL_KEYDOWN:
        begin
          case event.key.keysym.sym of
            ord(SDLK_SPACE):
            begin
              global_video_state.pause:= not global_video_state.pause;
            end;
            SDLK_LEFT:
            begin
              doSeek(-1);
            end;
            SDLK_RIGHT:
            begin
              doSeek(1);
            end;
            SDLK_UP:
            begin
              doScale(0.1);
            end;
            SDLK_DOWN:
            begin
              doScale(-0.1);
            end;
          end;
        end;
        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 tutorial07 is ffmpeg and SDL Tutorial or How to Write a Video Player in Less Than 1000 Lines

Немає коментарів:

Дописати коментар