You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于FFMPEG.Autogen的C# RTSP播放器频繁花屏问题求助

问题:RTSP播放器画面频繁异常(灰屏、局部破损)

我使用C#结合FFMPEG.Autogen开发了一款RTSP播放器,希望将流解码后转换为Bitmap直接显示到PictureBox,无需编码操作。RTSP视频可正常播放,但画面频繁出现异常:有时画面变灰后恢复,有时局部破损模糊。已确认帧正常接收,推测可能是解码数据问题或异步运行导致,但无法确定具体原因,附上相关代码寻求帮助。

相关代码

private async void DecodeAllFramesToImages(AVHWDeviceType HWDevice, string rtspUrl)
{
    await Task.Run(async() =>
    {
        var url = rtspUrl;
        using var vsd = new VideoStreamDecoder(url);
        var info = vsd.GetContextInfo();
        info.ToList().ForEach(x => Console.WriteLine($"{x.Key} = {x.Value}"));

        var sourceSize = vsd.FrameSize;
        var sourcePixelFormat = HWDevice == AVHWDeviceType.AV_HWDEVICE_TYPE_NONE
            ? vsd.PixelFormat
            : GetHWPixelFormat(HWDevice);
        var destinationSize = sourceSize;
        var destinationPixelFormat = AVPixelFormat.AV_PIX_FMT_BGR24;
        Debug.WriteLine(url + " " + sourceSize);

        using var vfc = new VideoFrameConverter(sourceSize, sourcePixelFormat, destinationSize, destinationPixelFormat);

        while (vsd.TryDecodeNextFrame(out var frame))
        {
            if (stopRecord)
                break;

            var convertedFrame = vfc.Convert(frame);
            Bitmap image = null;
            // Create image without using unsafe block
            unsafe
            {
                image = new Bitmap(convertedFrame.width, convertedFrame.height, convertedFrame.linesize[0], PixelFormat.Format24bppRgb, (IntPtr)convertedFrame.data[0]);
            }
            await Task.Run(() => UpdatePictureBox(image));
        }
    });
}

public bool TryDecodeNextFrame(out AVFrame frame)
{
    ffmpeg.av_frame_unref(_pFrame);
    ffmpeg.av_frame_unref(_receivedFrame);
    int error;

    do
    {
        try
        {
            do
            {
                ffmpeg.av_packet_unref(_pPacket);
                error = ffmpeg.av_read_frame(_pFormatContext, _pPacket);

                if (error == ffmpeg.AVERROR_EOF)
                {
                    frame = *_pFrame;
                    return false;
                }

                error.ThrowExceptionIfError();
            } while (_pPacket->stream_index != _streamIndex);

            ffmpeg.avcodec_send_packet(_pCodecContext, _pPacket).ThrowExceptionIfError();
        }
        catch(Exception ex)
        {
            Debug.WriteLine("UC_MPP.VideoStreamingDecoder.TryDecodeNextFrame : " + ex.Message);
        }
        finally
        {
            ffmpeg.av_packet_unref(_pPacket);
        }

        error = ffmpeg.avcodec_receive_frame(_pCodecContext, _pFrame);
    } while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN));

    error.ThrowExceptionIfError();

    if (_pCodecContext->hw_device_ctx != null)
    {
        ffmpeg.av_hwframe_transfer_data(_receivedFrame, _pFrame, 0).ThrowExceptionIfError();
        frame = *_receivedFrame;
    }
    else
        frame = *_pFrame;

    return true;
}

public AVFrame Convert(AVFrame sourceFrame)
{
    ffmpeg.sws_scale(_pConvertContext,
        sourceFrame.data,
        sourceFrame.linesize,
        0,
        sourceFrame.height,
        _dstData,
        _dstLinesize);

    var data = new byte_ptr8();
    data.UpdateFrom(_dstData);
    var linesize = new int8();
    linesize.UpdateFrom(_dstLinesize);

    return new AVFrame
    {
        data = data,
        linesize = linesize,
        width = _destinationSize.Width,
        height = _destinationSize.Height
    };
}

问题分析与修复建议

核心问题点

  • UI更新的线程安全与帧序问题:嵌套的Task.Run会导致UI线程收到乱序的帧,且跨线程直接操作PictureBox控件不符合WinForm线程安全规则,易引发画面错乱。
  • 帧数据的生命周期冲突:Bitmap直接引用FFmpeg的缓冲区(_dstData或解码帧内存),后续帧处理会覆盖该内存,导致旧画面数据被破坏;av_frame_unref会提前释放未使用完的帧数据,引发内存访问异常。
  • 硬解码帧处理不严谨:若使用硬解码,_receivedFrame初始化或av_hwframe_transfer_data操作不规范可能导致数据拷贝不完整,出现灰屏。

具体修复步骤

  1. 修正UI更新逻辑
    去掉嵌套的Task.Run,改用Control.Invoke确保在UI线程更新,同时保证帧顺序:

    // 替换原有的await Task.Run(...)
    pictureBox1.Invoke(new Action(() => {
        UpdatePictureBox(image);
    }));
    

    另外在UpdatePictureBox中需先释放旧Bitmap,避免内存泄漏:

    private void UpdatePictureBox(Bitmap newImage)
    {
        if (pictureBox1.Image != null)
        {
            pictureBox1.Image.Dispose();
        }
        pictureBox1.Image = newImage;
    }
    
  2. 隔离Bitmap与FFmpeg缓冲区
    不要直接引用FFmpeg的帧内存,而是将数据拷贝到Bitmap的独立内存中,避免原缓冲区被覆盖:

    unsafe
    {
        image = new Bitmap(convertedFrame.width, convertedFrame.height, PixelFormat.Format24bppRgb);
        var bitmapData = image.LockBits(
            new Rectangle(0, 0, image.Width, image.Height),
            ImageLockMode.WriteOnly,
            PixelFormat.Format24bppRgb);
    
        for (int y = 0; y < convertedFrame.height; y++)
        {
            // 逐行拷贝数据
            Buffer.MemoryCopy(
                (void*)convertedFrame.data[0] + y * convertedFrame.linesize[0],
                (void*)bitmapData.Scan0 + y * bitmapData.Stride,
                bitmapData.Stride,
                convertedFrame.linesize[0]);
        }
    
        image.UnlockBits(bitmapData);
    }
    
  3. 完善FFmpeg帧的生命周期管理
    使用av_frame_clone克隆解码后的帧,确保返回的帧是独立副本,避免后续av_frame_unref释放正在使用的帧:

    if (_pCodecContext->hw_device_ctx != null)
    {
        ffmpeg.av_hwframe_transfer_data(_receivedFrame, _pFrame, 0).ThrowExceptionIfError();
        var clonedFrame = ffmpeg.av_frame_clone(_receivedFrame);
        frame = *clonedFrame;
    }
    else
    {
        var clonedFrame = ffmpeg.av_frame_clone(_pFrame);
        frame = *clonedFrame;
    }
    

    注意:克隆后的帧需在使用完后调用ffmpeg.av_frame_unref(&frame);释放,避免内存泄漏。

  4. 硬解码额外检查
    确保GetHWPixelFormat返回的格式与硬解码设备兼容,且_receivedFrame已正确初始化:

    // 初始化_receivedFrame示例
    _receivedFrame = ffmpeg.av_frame_alloc();
    _receivedFrame->format = (int)AVPixelFormat.AV_PIX_FMT_NV12; // 对应硬解码输出格式
    _receivedFrame->width = _pCodecContext->width;
    _receivedFrame->height = _pCodecContext->height;
    ffmpeg.av_frame_get_buffer(_receivedFrame, 0);
    

内容的提问来源于stack exchange,提问作者DK kim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 18:42:31