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

FFMBC_04: Playing Sound

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

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

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