LibVlcSharp:如何使用 RTSP(动态流媒体输入)传输动态帧序列?

LibVlcSharp: How to stream a dynamic frame sequence with RTSP (Dynamic StreamMediaInput)?

我正在从事一个项目,该项目从 RTSP-Stream 中获取单个图像并对其进行处理(绘制边界框)。这些图像应该在其他地址的单独 RTSP 流上重新流式传输(h264 编码),不应保存在本地磁盘上。

我目前的代码是:

        {
            // OpenCV VideoCapture: Sample RTSP-Stream
            var capture = new VideoCapture("rtsp://195.200.199.8/mpeg4/media.amp");
            capture.Set(VideoCaptureProperties.FourCC, FourCC.FromFourChars('M', 'P', 'G', '4'));
            var mat = new Mat();

            // LibVlcSharpStreamer
            Core.Initialize();
            var libvlc = new LibVLC();
            var player = new MediaPlayer(libvlc);
            player.Play();
            while (true)
            {
                if (capture.Grab())
                {
                    mat = capture.RetrieveMat();
                    // Do some manipulation in here
                    var media = new Media(libvlc, new StreamMediaInput(mat.ToMemoryStream(".jpg")));
                    media.AddOption(":no-audio");
                    media.AddOption(":sout=#transcode{vcodec=h264,fps=10,vb=1024,acodec=none}:rtp{mux=ts,sdp=rtsp://192.168.xxx.xxx:554/video}");
                    media.AddOption(":sout-keep");
                    player.Media = media;
                    // Display screen
                    Cv2.ImShow("image", mat);
                    Cv2.WaitKey(1);
                }
            }
        }

出于测试目的,它有点混乱,但如果我只使用给定的 RTSP-Stream 作为媒体而不是获取的图像,它就可以工作。我在将图像(作为字节)通过管道传输到 cvlc 命令行 (python get_images.py | cvlc -v --demux=rawvideo --rawvid-fps=25 --rawvid-chroma=RV24 --sout '#transcode{vcodec=h264,fps=25,vb=1024,acodec=none}:rtp{sdp="rtsp://:554/video"}') 方面取得了一些成功,但它应该集成在 c# 中。 get_images.py 只是在 while 循环中读取图像,在其上写入文本并将它们转发到 std-out

我解决这个问题的想法是,通过 StreamMediaInput-class 输入图像,如果检索到新图像,则动态更改媒体。但是不行,用VLC或者FFPlay什么都看不到。

有人遇到过类似的问题吗? StreamMediaInput-对象如何动态更改,以便正确广播新图像?

感谢您花时间阅读这篇文章post。祝你有美好的一天!

编辑:

我尝试通过修改 UpdateMemoryStream() 实现自己的 MediaInput class(非常类似于 MemoryStramMediaInput)。 MemoryStream 由每个新检索的图像更新,但 read() 不会被第二次调用(read() 每个 Medium 调用一次)。我正在尝试实现阻塞 read(),但我正在努力寻找实现它的好方法。到目前为止的代码是:

编辑 2:

我决定用 ManualResetEvent 实现阻塞,如果 Position 位于流的末尾,它会阻塞 read()。此外,读取会在一段时间内循环,以保持流中的数据更新。它仍然不起作用。到目前为止我的代码:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using LibVLCSharp.Shared;

namespace LibVlcSharpStreamer
{
    /// <summary>
    /// A <see cref="MediaInput"/> implementation that reads from a .NET stream
    /// </summary>
    public class MemoryStreamMediaInput : MediaInput
    {
        private Stream _stream;

        private ManualResetEvent manualResetEvent = new ManualResetEvent(false);
#if NET40
        private readonly byte[] _readBuffer = new byte[0x4000];
#endif
        /// <summary>
        /// Initializes a new instance of <see cref="StreamMediaInput"/>, which reads from the given .NET stream.
        /// </summary>
        /// <remarks>You are still responsible to dispose the stream you give as input.</remarks>
        /// <param name="stream">The stream to be read from.</param>
        public MemoryStreamMediaInput(Stream stream)
        {
            _stream = stream ?? throw new ArgumentNullException(nameof(stream));
            CanSeek = stream.CanSeek;
        }

        /// <summary>
        /// Initializes a new instance of <see cref="StreamMediaInput"/>, which reads from the given .NET stream.
        /// </summary>
        /// <remarks>You are still responsible to dispose the stream you give as input.</remarks>
        /// <param name="stream">The stream to be read from.</param>
        public void UpdateMemoryStream(Stream stream)
        {
            stream.CopyTo(_stream);
            _stream.Position = 0;
            manualResetEvent.Set();
            manualResetEvent.Reset();
            Console.WriteLine("released");
        }

        /// <summary>
        /// LibVLC calls this method when it wants to open the media
        /// </summary>
        /// <param name="size">This value must be filled with the length of the media (or ulong.MaxValue if unknown)</param>
        /// <returns><c>true</c> if the stream opened successfully</returns>
        public override bool Open(out ulong size)
        {
            try
            {
                try
                {
                    size = (ulong)_stream.Length;
                }
                catch (Exception)
                {
                    // byte length of the bitstream or UINT64_MAX if unknown
                    size = ulong.MaxValue;
                }

                if (_stream.CanSeek)
                {
                    _stream.Seek(0L, SeekOrigin.Begin);
                }

                return true;
            }
            catch (Exception)
            {
                size = 0UL;
                return false;
            }
        }

        /// <summary>
        /// LibVLC calls this method when it wants to read the media
        /// </summary>
        /// <param name="buf">The buffer where read data must be written</param>
        /// <param name="len">The buffer length</param>
        /// <returns>strictly positive number of bytes read, 0 on end-of-stream, or -1 on non-recoverable error</returns>
        public unsafe override int Read(IntPtr buf, uint len)
        {
            try
            {
                while (_stream.CanSeek)
                {
                    if (_stream.Position >= _stream.Length)
                    {
                        manualResetEvent.WaitOne();
                    }
                    var read = _stream.Read(new Span<byte>(buf.ToPointer(), (int)Math.Min(len, int.MaxValue)));
                    // Debug Purpose
                    Console.WriteLine(read);
                }
                return -1;
            }
            catch (Exception)
            {
                return -1;
            }
        }

        /// <summary>
        /// LibVLC calls this method when it wants to seek to a specific position in the media
        /// </summary>
        /// <param name="offset">The offset, in bytes, since the beginning of the stream</param>
        /// <returns><c>true</c> if the seek succeeded, false otherwise</returns>
        public override bool Seek(ulong offset)
        {
            try
            {
                _stream.Seek((long)offset, SeekOrigin.Begin);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// LibVLC calls this method when it wants to close the media.
        /// </summary>
        public override void Close()
        {
            try
            {
                if (_stream.CanSeek)
                    _stream.Seek(0, SeekOrigin.Begin);
            }
            catch (Exception)
            {
                // ignored
            }
        }
    }
}

我已经在代码中标记了我认为阻塞子句适合的位置。

因为您正在为每一帧制作一个新媒体,所以您将无法将其作为单个流进行流式传输。

您可以做的是创建一个 MJPEG 流:将 .jpg 图像一个接一个地放入单个流中,然后使用该流和 LibVLCSharp 对其进行流式传输。

但是,如果 LibVLCSharp 从内存流中读取数据的速度比向其写入数据的速度快,它将检测到文件末尾,并停止播放/流式传输(Read() 调用returns 没有数据被认为是文件的结尾)。为避免这种情况,关键是“阻止” Read() 调用,直到实际有数据要读取为止。这不是阻止调用的问题,因为这发生在 VLC 线程上。

默认 MemoryStream/StreamMediaInput 不会让您阻止 Read() 调用,您需要编写自己的 Stream 实现或编写自己的 MediaInput实施。

这里有一些阻止读取调用的想法:

  • 使用 BlockingCollection 将 Mat 实例推送到流输入。 BlockingCollection 有一个 Take() 方法,该方法会阻塞直到确实有一些数据要读取
  • 当数据可供读取时使用 ManualResetEvent 发出信号(它有一个 Wait() 方法)

The LibVLC discord 上讨论会更容易,欢迎加入!

如果您成功了,请将您的代码作为新的 libvlcsharp-sample 项目共享!

经过很长时间尝试实施一个好的解决方案但没有成功,我们使用了将检索到的 Mat 输送到 Vlc.exe 的方法。对于任何有同样问题的人:

使用ProcessStartInfo指定vlc的路径。可以使用 Process.StandardInput.BaseStream.Write() 将图像通过管道传输到进程中。使用循环定期捕获图像(例如使用 OpenCV)。