Claaaudio
Claaaudio

Reputation: 93

Unable to properly turn a YUV420P image to RGBA

I've been trying to make a VideoPlayer following this tutorial http://dranger.com/ffmpeg in order to teach myself how to use libav but I've wanted to make it the using Delphi VCL and DirectSound instead of SDL.

Unfortunately I got stuck when trying to convert a YUV420P frame to a RGBA one, even though I can write the bytes on a TBitmap the color seems to be a bit off =>

BunnyFrame30

I've tried using sws_setColorspaceDetails to fix it but without success.

It doesn't seem to matter which .mp4/.mkv I use, the color is always off.

If there are people in the picture their faces are blue for example.

I got the headers from this repo => https://github.com/Laex/Delphi-FFMPEG

I used that 5 second BigBuckBunny clip that people use on the libav "Hello World"

The binaries I get from https://github.com/BtBn/FFmpeg-Builds/releases?page=4

Here's the code

unit uVideoPlayer;

interface

uses
  Winapi.Windows, Winapi.Messages,
  System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms,
  Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  libavcodec, libavdevice, libavfilter,
  libavformat, libavutil, libswscale;

type
  TfrmMain = class(TForm)
    btnPlay: TButton;
    edtFile: TEdit;
    mem: TMemo;
    img: TImage;
    procedure FormCreate(Sender: TObject);
    procedure btnPlayClick(Sender: TObject);
  end;

var
  frmMain: TfrmMain;

implementation

uses
  Uutils,
  USmoothResize,
  System.AnsiStrings;

{$R *.dfm}

function DecodePacket(pPacket: PAVPacket; pCodecCtx: PAVCodecContext;
  pSwsCtx: PSWSContext; pFrame, pFrameRGB: PAVFrame): Integer;
var
  vResponse: Integer;
  vSrcBmp, vDstBmp: TBitmap;
begin
  vResponse := avcodec_send_packet(pCodecCtx, pPacket);

  if vResponse < 0 then
    Exit(vResponse);

  while (vResponse >= 0) do
  begin
    vResponse := avcodec_receive_frame(pCodecCtx, pFrame);
    if (vResponse = AVERROR_EAGAIN) or (vResponse = AVERROR_EOF) then
      Break
    else if vResponse < 0 then
      Exit(vResponse);

    if vResponse >= 0 then
    begin
      frmMain.mem.Lines.Add(Format('Frame %d (type=%s, size=%d bytes, format=%d) pts %d key_frame %d [DTS %d]',
      [
        pCodecCtx.frame_number,
        av_get_picture_type_char(pFrame.pict_type),
        pFrame.pkt_size,
        pFrame.format,
        pFrame.pts,
        pFrame.key_frame,
        pFrame.coded_picture_number
      ]));

      if (pFrame.format <> Integer(AV_PIX_FMT_YUV420P)) then
        frmMain.mem.Lines.Add('Not a grayscale image!');

      pFrame.data[0] := pFrame.data[0] + pFrame.linesize[0] * (pCodecCtx.height - 1);
      pFrame.linesize[0] := pFrame.linesize[0] * -1;
      pFrame.data[1] := pFrame.data[1] + pFrame.linesize[1] * (pCodecCtx.height div 2 - 1);
      pFrame.linesize[1] := pFrame.linesize[1] * -1;
      pFrame.data[2] := pFrame.data[2] + pFrame.linesize[2] * (pCodecCtx.height div 2 - 1);
      pFrame.linesize[2] := pFrame.linesize[2] * -1;

      sws_scale(pSwsCtx, @pFrame^.Data, @pFrame^.Linesize, 0,
        pCodecCtx.height, @pFrameRGB.data[0], @pFrameRGB.Linesize[0]);

      vSrcBmp := TBitmap.Create;
      vDstBmp := nil;
      try
        vSrcBmp.Height := pCodecCtx.height;
        vSrcBmp.Width := pCodecCtx.width;
        vSrcBmp.PixelFormat := TPixelFormat.pf32bit;

        MoveMemory(vSrcBmp.ScanLine[vSrcBmp.Height -1], pFrameRGB.data[0],
          pFrameRGB.linesize[0] * pCodecCtx.Height);

        vDstBmp := TBitmap.Create;
        vDstBmp.Width := frmMain.img.Width;
        vDstBmp.Height := frmMain.img.Height;
        vDstBmp.PixelFormat := TPixelFormat.pf32bit;

        SmoothResize(vSrcBmp, vDstBmp);

        frmMain.img.Picture.Assign(vDstBmp);

        Application.ProcessMessages;
      finally
        FreeAndNil(vSrcBmp);
        FreeAndNil(vDstBmp);
      end;
    end;
  end;
  Exit(0);
end;

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  libavformat.av_register_all;
end;

procedure TfrmMain.btnPlayClick(Sender: TObject);
type
  TQuadInt = array [0..3] of Integer;
  PQuadInt = ^TQuadInt;
var
  vCount, vVideoStreamIndex, vDecPackResponse: Integer;
  vFileName: AnsiString;
//  vI1, vI2: PQuadInt;

  vFormatCtx: PAVFormatContext;
  vCodec, vLocalCodec: PAVCodec;
  vCodecParams, vLocalCodecParams: PAVCodecParameters;
  vCodecCtx: PAVCodecContext;
  vFrame, vFrameRGB: PAVFrame;
  vPacket: PAVPacket;
  vImgConvertCtx: PSWSContext;
begin
  vFormatCtx := avformat_alloc_context;

  if vFormatCtx <> nil then
    mem.Lines.Add('Alloc format context succesful!')
  else
    Exit;

  vFileName := AnsiString(edtFile.Text);

  if avformat_open_input(vFormatCtx, PAnsiChar(vFileName), nil, nil) = 0 then
    mem.Lines.Add('Open input success!')
  else
    Exit;

  if avformat_find_stream_info(vFormatCtx, nil) = 0 then
    mem.Lines.Add('Find stream info AVFormat success!')
  else
    Exit;

  vCodec := nil;
  vCodecParams := nil;
  vVideoStreamIndex := -1;

  for vCount := 0 to vFormatCtx.nb_streams - 1 do
  begin
    vLocalCodecParams := vFormatCtx.streams[vCount].codecpar;

    mem.Lines.Add(Format('[%d] AVStream.time_base before open codec %d/%d',
      [vCount, vFormatCtx.streams[vCount].time_base.num, vFormatCtx.streams[vCount].time_base.den]));
    mem.Lines.Add(Format('[%d] AVStream.r_frame_rate before open codec %d/%d',
      [vCount, vFormatCtx.streams[vCount].r_frame_rate.num, vFormatCtx.streams[vCount].r_frame_rate.den]));
    mem.Lines.Add(Format('[%d] AVStream.start_time %" PRId64',
      [vCount, vFormatCtx.streams[vCount].start_time]));
    mem.Lines.Add(Format('[%d] AVStream.duration %" PRId64',
      [vCount, vFormatCtx.streams[vCount].duration]));

    vLocalCodec := avcodec_find_decoder(vLocalCodecParams.codec_id);

    if vLocalCodec = nil then
      Continue;

    if vLocalCodecParams.codec_type = AVMEDIA_TYPE_VIDEO then
    begin
      vVideoStreamIndex := vCount;

      vCodec := vLocalCodec;
      vCodecParams := vLocalCodecParams;

      mem.Lines.Add(Format('[%d] Video codec params %dx%d',
        [vCount, vLocalCodecParams.width, vLocalCodecParams.height]));
    end;
    mem.Lines.Add(Format('[%d] CodecName: %s | CodecID: %d | BitRate: %d',
      [vCount, vLocalCodec.Name, Integer(vLocalCodec.ID), vLocalCodecParams.bit_rate]));
  end;

  if vVideoStreamIndex = -1 then
    Exit;

  vCodecCtx := avcodec_alloc_context3(vCodec);
  if vCodecCtx <> nil then
    mem.Lines.Add('Codec context allocation succesful!')
  else
    Exit;

  if avcodec_parameters_to_context(vCodecCtx, vCodecParams) = 0 then
    mem.Lines.Add('Params to Ctx succesful!')
  else
    Exit;

  if avcodec_open2(vCodecCtx, vCodec, nil) = 0 then
    mem.Lines.Add('AvCodec open succesful!')
  else
    Exit;

  vFrame := av_frame_alloc;
  if (vFrame = nil) then
    Exit;

  vFrameRGB := av_frame_alloc;
  if (vFrameRGB = nil) then
    Exit;

  vFrameRGB.format := Integer(AV_PIX_FMT_RGBA);
  vFrameRGB.width  := vCodecCtx.width;
  vFrameRGB.height := vCodecCtx.height;
  if (av_frame_get_buffer( vFrameRGB, 32 )) <> 0 then
    Exit;

  vImgConvertCtx := sws_alloc_context();

  av_opt_set_int(vImgConvertCtx, 'sws_flags', SWS_POINT, 0);
  av_opt_set_int(vImgConvertCtx, 'srcw', vCodecCtx.Width, 0);
  av_opt_set_int(vImgConvertCtx, 'srch', vCodecCtx.Height, 0);
  av_opt_set_int(vImgConvertCtx, 'src_format', Integer(vCodecCtx.pix_fmt), 0);
  av_opt_set_int(vImgConvertCtx, 'dstw', vCodecCtx.Width, 0);
  av_opt_set_int(vImgConvertCtx, 'dsth', vCodecCtx.Height, 0);
  av_opt_set_int(vImgConvertCtx, 'dst_format', Integer(AV_PIX_FMT_RGBA), 0);

//  vI1 := Pointer(sws_getCoefficients(Integer(0)));
//  vI2 := Pointer(sws_getCoefficients(Integer(AVCOL_SPC_BT709)));
//  sws_setColorspaceDetails(vImgConvertCtx, Pointer(@vI1), 1, Pointer(@vI2), 1,
//    0, 1 shl 16, 1 shl 16);

  sws_init_context(vImgConvertCtx, nil, nil);

  vPacket := av_packet_alloc;
  if (vPacket = nil) then
    Exit;

  while (av_read_frame(vFormatCtx, vPacket) >= 0) do
  begin
    if vPacket.stream_index = vVideoStreamIndex then
    begin
      vDecPackResponse := DecodePacket(vPacket, vCodecCtx,
        vImgConvertCtx, vFrame, vFrameRGB);

      if vDecPackResponse < 0 then
        Break;

    end;
    av_frame_unref(vFrame);
    av_packet_unref(vPacket);
  end;

  av_frame_unref(vFrameRGB);
  av_frame_free(vFrameRGB);
  avformat_close_input(vFormatCtx);

  av_packet_free(vPacket);
  av_frame_free(vFrame);
  avcodec_free_context(vCodecCtx);
  sws_freeContext(vImgConvertCtx);
end;

end.

Upvotes: 2

Views: 576

Answers (1)

Claaaudio
Claaaudio

Reputation: 93

Just needed to change the target format from AV_PIX_FMT_RGBA to AV_PIX_FMT_BGRA. Thanks @Andreas Rejbrand

Here's how the bunny looks now

BunnyFrame30Right

Upvotes: 2

Related Questions