我正在尝试开发一个应用程序,该应用程序可以流式传输来自 IP 摄像机的视频,并处理来自这些 IP 摄像机的一些帧。我已经使用 SetVideoCallbacks(Lock, null, Display) 设置了视频回调,但 Lock 和 Display 方法没有被调用。
我尝试用 mp4 视频执行此操作,并且工作正常。但是,当我尝试使用 IP 摄像头执行此操作时,我可以在 VideoView 中看到流,但不会调用 Lock 和 Display 方法。早些时候,我认为这可能是因为帧是使用 GPU 进行解码的,但任务管理器显示,当我运行应用程序时,GPU 使用率可以忽略不计,但 CPU 使用率相当高(~40%)。我还尝试将 EnableHardwareDecoding 设置为 true 和 false。我相信存在一些格式错误。也许相机 rtsp 流未解码为 I420 格式。我的摄像机流是 H.265 编解码器,但也支持 H.264 编解码器。 我还知道,我可以看到视频源这一事实本身就很可疑,因为如果调用 Lock 函数,那么我就看不到视频(除非我复制帧)。 请帮助我解决这个问题或指导我是否有更好的方法。 谢谢。
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++;
}
}
}
在当前的 libvlc API 中,你不能同时使用 VideoView 和视频回调,所以我认为那里存在冲突。
您的选择是有限的:要么打开两个流,要么在回调中自己进行显示(比直接在 VideoView 中显示更慢、更困难且更占用 CPU 资源)。LibVLC 4 将有一个新的回调来直接获取 directX 纹理,但它仍然在预览)