LibVlcSharp播放IP摄像头流时SetVideoCallbacks未触发求助
问题:LibVLCSharp自定义视频回调(Lock/Display)在RTSP流中不触发,MP4播放正常
现象说明
- 播放本地MP4文件时,通过
SetVideoCallbacks设置的Lock和Display方法能正常触发,帧处理逻辑工作正常 - 播放IP摄像头RTSP流时,
VideoView可以正常显示画面,但Lock/Display方法完全无响应 - 已尝试切换
EnableHardwareDecoding开关,任务管理器显示GPU使用率极低(排除GPU解码绕开回调的可能),CPU占用约40% - 摄像头支持H.264/H.265编码,怀疑RTSP流解码格式未匹配指定的I420,同时"能显示画面但回调不触发"的行为不符合预期逻辑
关联代码片段
public partial class MainWindow : Window { private Camera _camera; private LibVLC _libVLC; private LibVLCSharp.Shared.MediaPlayer _mediaPlayer; public const uint width = 1280; public const uint height = 720; public const uint pitch = width*2;//because of I420 which uses 12 bits per pixel public MemoryMappedFile? CurrentMappedFile; public MemoryMappedViewAccessor? CurrentMappedViewAccessor; public ConcurrentQueue<(MemoryMappedFile? file, MemoryMappedViewAccessor? accessor)> FramesToProcess = new ConcurrentQueue<(MemoryMappedFile? file, MemoryMappedViewAccessor? accessor)>(); public int FrameCounter = 0; public CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); public MainWindow() { try { InitializeComponent(); Core.Initialize(); _camera = new Camera("Camera", "192.168.1.188", 554, "admin", "123456", "stream"); _libVLC = new LibVLC("--verbose=2"); _mediaPlayer = new LibVLCSharp.Shared.MediaPlayer(_libVLC) { EnableHardwareDecoding = true}; //_libVLC.Log += (sender, e) => Debug.WriteLine($"[{e.Level}] {e.Module}:{e.Message}"); var media = new Media(_libVLC, _camera.GetStreamUrl(), FromType.FromLocation); media.AddOption(":no-audio"); _mediaPlayer.Media = media; _mediaPlayer.SetVideoFormat("I420", width, height, pitch); _mediaPlayer.SetVideoCallbacks(Lock, null, Display); _mediaPlayer.Stopped += (s, e) => cancellationTokenSource.CancelAfter(1); VideoView.MediaPlayer = _mediaPlayer; VideoView.MediaPlayer.Play(); this.Closed += MainWindow_Closed; Task.Run(() => ProcessFrames(cancellationTokenSource.Token), cancellationTokenSource.Token); } catch(Exception ex) { Application.Current.Dispatcher.Invoke(() => MessageBox.Show(ex.Message)); Debug.WriteLine(ex.Message); } } private IntPtr Lock(IntPtr opaque, IntPtr planes) { //Debug.WriteLine("Lock is called"); Dispatcher.Invoke(() => { lockTextBox.Text = $"Lock method called {FrameCounter}"; }); CurrentMappedFile = MemoryMappedFile.CreateNew(null, pitch * height); CurrentMappedViewAccessor = CurrentMappedFile.CreateViewAccessor(); Marshal.WriteIntPtr(planes, CurrentMappedViewAccessor.SafeMemoryMappedViewHandle.DangerousGetHandle()); return IntPtr.Zero; } private void Display(IntPtr opaque, IntPtr picture) { //Debug.WriteLine($"Debug is called with framecounter= {FrameCounter}"); Dispatcher.Invoke(() => { displayTextBox.Text = $"Display method called {FrameCounter}"; }); if (FrameCounter == 5) { //Enqueued frames to be disposed in the processing thread FramesToProcess.Enqueue((CurrentMappedFile, CurrentMappedViewAccessor)); CurrentMappedFile = null; CurrentMappedViewAccessor = null; FrameCounter = 0;//reset counter } else { if (CurrentMappedViewAccessor != null && CurrentMappedFile != null) { CurrentMappedViewAccessor.Dispose(); CurrentMappedFile.Dispose(); } CurrentMappedFile = null; CurrentMappedViewAccessor = null; FrameCounter++; } } }
核心原因分析
- 渲染路径冲突:将
MediaPlayer绑定到VideoView时,LibVLC会优先使用VideoView内置的渲染回调,直接覆盖了手动设置的Lock/Display回调。MP4本地文件的解码路径未触发该覆盖逻辑,但RTSP实时流的渲染流程会强制启用VideoView的内置回调。 - 格式设置时机错误:
SetVideoFormat和SetVideoCallbacks的调用晚于_mediaPlayer.Media = media,此时媒体已完成初始化解码格式,手动指定的I420格式未被生效。 - I420格式参数错误:代码中
pitch计算错误,I420格式的Y平面pitch应为宽度值(1280),U/V平面pitch为宽度的一半(640),总内存大小应为width*height*3/2,而非width*2*height。
解决方案
方案1:解绑VideoView,完全使用自定义回调
如果不需要VideoView显示画面,仅需获取帧数据,直接移除VideoView.MediaPlayer的绑定:
// 删除该行:VideoView.MediaPlayer = _mediaPlayer; // 所有帧数据将通过自定义Lock/Display回调处理
方案2:调整设置顺序,禁用默认渲染输出
若需要同时显示画面和获取帧,初始化LibVLC时禁用默认视频输出,并调整格式与回调的设置顺序:
// 初始化LibVLC时添加参数禁用默认视频输出 _libVLC = new LibVLC("--verbose=2", "--vout=vdummy"); // 先设置视频格式和回调,再绑定媒体 _mediaPlayer.SetVideoFormat("I420", width, height, width); // Y平面pitch设为width,U/V平面会自动计算 _mediaPlayer.SetVideoCallbacks(Lock, null, Display); // 再初始化并绑定媒体 var media = new Media(_libVLC, _camera.GetStreamUrl(), FromType.FromLocation); media.AddOption(":no-audio"); _mediaPlayer.Media = media; // 如需显示画面,在Display回调中将帧数据手动绘制到WPF控件(如Image)
方案3:使用FrameReady事件(更适合WPF的实现)
LibVLCSharp提供了更适配WPF的FrameReady事件,无需手动处理底层Lock/Display回调,避免路径冲突:
// 移除SetVideoFormat和SetVideoCallbacks的代码,替换为FrameReady事件绑定 _mediaPlayer.FrameReady += MediaPlayer_FrameReady; private void MediaPlayer_FrameReady(object? sender, FrameReadyEventArgs e) { // 将I420格式帧转换为WPF BitmapSource var bitmap = BitmapSource.Create( (int)e.Frame.Width, (int)e.Frame.Height, 96, 96, PixelFormats.Yuv420P, null, e.Frame.Data, (int)e.Frame.Pitches[0] * (int)e.Frame.Height + (int)e.Frame.Pitches[1] * (int)e.Frame.Height/2 * 2, (int)e.Frame.Pitches[0] ); // 将帧数据入队处理 FramesToProcess.Enqueue(/* 处理后的帧数据或原始数据 */); // 如需显示画面,更新Image控件 Dispatcher.Invoke(() => ImageControl.Source = bitmap); }
参数修正
修正I420格式的内存计算:
// I420格式总内存大小 = Y(宽*高) + U(宽/2*高/2) + V(宽/2*高/2) = 宽*高*3/2 public const uint totalSize = width * height * 3 / 2; // Y平面pitch为宽度,U/V平面pitch为宽度的一半 public const uint pitchY = width; public const uint pitchUV = width / 2;
验证步骤
- 取消注释
_libVLC.Log代码,查看LibVLC日志中视频输出模块和格式协商的内容,确认自定义I420格式是否生效 - 确认摄像头输出分辨率与代码中设置的1280x720一致,分辨率不匹配会导致格式协商失败,触发默认渲染路径
内容的提问来源于stack exchange,提问作者Vaibhav kaul
相关产品推荐
相关产品推荐

