无法正确转换YUV420P至RGBA的技术求助(Delphi+FFmpeg)
问题:YUV420P转RGBA后画面颜色失真(面部呈蓝色)
基于Delphi VCL和DirectSound开发视频播放器,跟随libav教程实现YUV420P帧转RGBA帧时,画面颜色严重失真(如人物面部呈现蓝色)。尝试使用sws_setColorspaceDetails修复未成功,更换不同格式视频文件(.mp4/.mkv)后问题依然存在。使用的libav头文件、二进制文件均为常用开源版本,测试视频为标准BigBuckBunny片段。
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.
解决方案
1. 修正像素格式字节顺序
Delphi的TPixelFormat.pf32bit采用BGRA字节顺序(蓝-绿-红-透明度),而你当前使用的AV_PIX_FMT_RGBA是红-绿-蓝-透明度,通道顺序错位直接导致颜色失真(面部蓝色就是R和B通道颠倒的典型表现)。
修改步骤:
- 将
vFrameRGB.format改为Integer(AV_PIX_FMT_BGRA) - 调整SwsContext的目标格式为
AV_PIX_FMT_BGRA
2. 简化SwsContext创建(避免手动配置错误)
放弃sws_alloc_context+av_opt_set_int的手动配置方式,改用sws_getContext自动生成上下文,该函数会自动处理色彩空间、尺寸等参数,可靠性更高:
替换原有的SwsContext创建代码:
// 移除以下代码块 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); sws_init_context(vImgConvertCtx, nil, nil); // 替换为: vImgConvertCtx := sws_getContext( vCodecCtx.Width, vCodecCtx.Height, vCodecCtx.pix_fmt, vCodecCtx.Width, vCodecCtx.Height, AV_PIX_FMT_BGRA, SWS_BILINEAR, nil, nil, nil ); if not Assigned(vImgConvertCtx) then begin mem.Lines.Add('Failed to create SwsContext'); Exit; end;
3. 避免修改原始Frame数据指针
你当前代码中修改了pFrame.data和pFrame.linesize的正负值来翻转画面,这种操作会破坏原始帧的结构,可能导致sws_scale处理异常。建议保留原始帧方向,在Bitmap层面处理翻转:
移除DecodePacket中的以下代码块:
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;
改为在Bitmap赋值时处理翻转:
// 替换原MoveMemory代码为逐行复制 var i: Integer; SrcPtr, DstPtr: PByte; begin for i := 0 to vSrcBmp.Height - 1 do begin SrcPtr := pFrameRGB.data[0] + pFrameRGB.linesize[0] * (vSrcBmp.Height - 1 - i); DstPtr := vSrcBmp.ScanLine[i]; MoveMemory(DstPtr, SrcPtr, vSrcBmp.Width * 4); // 4字节 per BGRA pixel end;
4. 确保色彩空间正确转换(可选)
如果仍存在色彩偏差,需明确指定源视频的色彩空间参数。可以从解码后的帧中获取色彩空间,传递给sws_context:
var srcColorSpace: Integer; begin srcColorSpace := av_frame_get_colorspace(pFrame); sws_setColorspaceDetails(vImgConvertCtx, sws_getCoefficients(srcColorSpace), av_frame_get_color_range(pFrame), sws_getCoefficients(AVCOL_SPC_BT709), // 目标色彩空间(匹配显示器) AVCOL_RANGE_JPEG, 0, 1 shl 16, 1 shl 16 );
内容的提问来源于stack exchange,提问作者Claaaudio
相关产品推荐
相关产品推荐

