基于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操作不规范可能导致数据拷贝不完整,出现灰屏。
具体修复步骤
修正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; }隔离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); }完善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);释放,避免内存泄漏。硬解码额外检查
确保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
相关产品推荐
相关产品推荐

