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

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++;
        }
    }
}

核心原因分析

  1. 渲染路径冲突:将MediaPlayer绑定到VideoView时,LibVLC会优先使用VideoView内置的渲染回调,直接覆盖了手动设置的Lock/Display回调。MP4本地文件的解码路径未触发该覆盖逻辑,但RTSP实时流的渲染流程会强制启用VideoView的内置回调。
  2. 格式设置时机错误:SetVideoFormat和SetVideoCallbacks的调用晚于_mediaPlayer.Media = media,此时媒体已完成初始化解码格式,手动指定的I420格式未被生效。
  3. 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;

验证步骤

  1. 取消注释_libVLC.Log代码,查看LibVLC日志中视频输出模块和格式协商的内容,确认自定义I420格式是否生效
  2. 确认摄像头输出分辨率与代码中设置的1280x720一致,分辨率不匹配会导致格式协商失败,触发默认渲染路径

内容的提问来源于stack exchange,提问作者Vaibhav kaul

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 05:55:55