program ffmpeg_sample_player;

{$APPTYPE CONSOLE}
{$POINTERMATH ON}
{$R *.res}

uses
  Winapi.Windows,
  System.SysUtils,
  System.Classes,
  SDL,
  ffmpeg_types,
  libavcodec,
  libavdevice,
  libavfilter,
  libavformat,
  libavutil,
  libpostproc,
  libswresample,
  libswscale;

var
  err: Integer;
  filename: AnsiString;
  format_context: pAVFormatContext = nil;
  video_stream: Integer;
  codec_context: pAVCodecContext;
  codec: pAVCodec;
  screen: pSDL_Surface;
  bmp: pSDL_Overlay;
  img_convert_context: pSwsContext;
  frame: pAVFrame;
  packet: AVPacket;
  frame_finished: Integer;
  pict: AVPicture;
  rect: TSDL_Rect;
  event: TSDL_Event;

const
  cResourceMedia = '..\..\resource\';
  std_filename   = cResourceMedia + '768x576.avi';

begin
  try
    if (ParamCount < 1) then
      filename := std_filename
    else
      filename := ParamStr(1);

    // Register all available file formats and codecs
    av_register_all();
    avformat_network_init();

    // Init SDL with video support
    err := SDL_Init(SDL_INIT_VIDEO);
    if (err < 0) then
    begin
      WriteLn(Format('Unable to init SDL: %s', [SDL_GetError()]));
      Halt(1);
    end;

    // Open video file
    err := avformat_open_input(format_context, PAnsiChar(filename), nil, nil);
    if (err < 0) then
    begin
      WriteLn('ffmpeg: Unable to open input file');
      Halt(1);
    end;

    // Retrieve stream information
    err := avformat_find_stream_info(format_context, nil);
    if (err < 0) then
    begin
      WriteLn('ffmpeg: Unable to find stream info');
      Halt(1);
    end;

    // Dump information about file onto standard error
    av_dump_format(format_context, 0, PAnsiChar(filename), 0);

    // Find the first video stream
    for video_stream := 0 to format_context^.nb_streams - 1 do
      if (format_context^.streams[video_stream]^.codec^.codec_type = AVMEDIA_TYPE_VIDEO) then
        break;
    if (video_stream = format_context^.nb_streams) then
    begin
      WriteLn('ffmpeg: Unable to find video stream');
      Halt(1);
    end;

    codec_context := format_context^.streams[video_stream]^.codec;
    codec := avcodec_find_decoder(codec_context^.codec_id);
    err := avcodec_open2(codec_context, codec, nil);
    if (err < 0) then
    begin
      WriteLn('ffmpeg: Unable to open codec');
      Halt(1);
    end;

    screen := SDL_SetVideoMode(codec_context^.width, codec_context^.height, 0, 0 { SDL_FULLSCREEN } );
    if (screen = nil) then
    begin
      WriteLn('Couldn''t set video mode');
      Halt(1);
    end;

    bmp := SDL_CreateYUVOverlay(codec_context^.width, codec_context^.height, SDL_YV12_OVERLAY, screen);

    img_convert_context := sws_getCachedContext( //
      nil,                                       //
      codec_context^.width,                      //
      codec_context^.height,                     //
      codec_context^.pix_fmt,                    //
      codec_context^.width,                      //
      codec_context^.height,                     //
      AV_PIX_FMT_YUV420P,                        //
      SWS_BICUBIC,                               //
      nil, nil, nil);

    if (img_convert_context = nil) then
    begin
      WriteLn('Cannot initialize the conversion context');
      Halt(1);
    end;

    frame := av_frame_alloc(); // avcodec_alloc_frame();
    while (av_read_frame(format_context, packet) >= 0) do
    begin
      if (packet.stream_index = video_stream) then
      begin
        // Video stream packet
        avcodec_decode_video2(codec_context, frame, frame_finished, @packet);

        if (frame_finished <> 0) then
        begin
          SDL_LockYUVOverlay(bmp);

          // Convert frame to YV12 pixel format for display in SDL overlay
          pByte(pict.data[0]) := pByte(bmp^.pixels[0]);
          pByte(pict.data[1]) := pByte(bmp^.pixels[2]); // it's because YV12
          pByte(pict.data[2]) := pByte(bmp^.pixels[1]);

          pict.linesize[0] := bmp^.pitches[0];
          pict.linesize[1] := bmp^.pitches[2];
          pict.linesize[2] := bmp^.pitches[1];

          sws_scale(img_convert_context, @frame^.data, @frame^.linesize, 0, codec_context^.height, @pict.data, @pict.linesize);

          SDL_UnlockYUVOverlay(bmp);

          rect.x := 0;
          rect.y := 0;
          rect.w := codec_context^.width;
          rect.h := codec_context^.height;
          SDL_DisplayYUVOverlay(bmp, @rect);
        end;
      end;

      // Free the packet that was allocated by av_read_frame
      av_free_packet(packet);

      // Handling SDL events there
      if SDL_PollEvent(@event) <> 0 then
        if (event.type_ = SDL_QUITEV) then
          break;
    end;

    sws_freeContext(img_convert_context);

    // Free the YUV frame
    av_free(frame);

    // Close the codec
    avcodec_close(codec_context);

    // Close the video file
    avformat_close_input(format_context);

    avformat_network_deinit;

    // Quit SDL
    SDL_QUIT();

  except
    on E: Exception do
      WriteLn(E.ClassName, ': ', E.Message);
  end;

end.
