Reputation: 93
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 =>
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
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
Upvotes: 2