【问题标题】:LibVlcSharp: How to stream a dynamic frame sequence with RTSP (Dynamic StreamMediaInput)?LibVlcSharp:如何使用 RTSP(动态 StreamMediaInput)流式传输动态帧序列?
【发布时间】:2021-08-30 11:47:50
【问题描述】:

我正在从事一个项目,该项目从 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-Object,以便正确广播新图像?

感谢您抽出宝贵时间阅读这篇文章。祝你有美好的一天!

编辑

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

编辑 2:

我决定使用 ManualResetEvent 来实现阻塞,如果 Position 位于 Stream 的末尾,它会阻塞 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
            }
        }
    }
}

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

【问题讨论】:

标签: c# opencv .net-core libvlc libvlcsharp


【解决方案1】:

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

您可以做的是创建一个 MJPEG 流:将 .jpg 图像一个接一个地放入一个流中,然后将该流与 LibVLCSharp 一起使用。

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

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

以下是阻止读取调用的一些想法:

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

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

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

【讨论】:

  • 感谢您的快速回答!我目前正在尝试实施您的想法。 ToMemoryStream() 创建一个新实例,所以我认为这不会对问题产生影响。我查看了一些 LibvlcSharp 的代码并制作了自己的 MediaInput 类版本(尽管它与 StreamMediaInput 非常相似)。我被卡住了,我必须阻止Read()。正如您在上面的编辑中所见,我正在动态更新流_stream,但不会阻止read() 意味着libvlcSharp 不能广播任何数据?还是独立的?
  • 我很乐意将我的代码作为一个新的示例项目分享,如果它有效的话^^
  • 我好像误会了什么...mat.ToMemoryStream(".jpg") 只会给你一张图片。如果你想要一个视频流,你需要一个循环。我编辑了我的答案,希望现在更清楚!
  • 是的,现在更清楚了,我会在接下来的几天里尝试实现这个好主意。感谢你的付出。我会保持更新,所以它会帮助任何有同样问题的人。
  • 我没有多少时间来实现这个想法了。因此,我们使用了通过ProcessProcess.Standardinput 将图像通过管道传输到VLC.exe 的方法。感谢您的努力!
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 2021-01-12
    • 2014-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-24
    • 2010-11-14
    • 2020-06-05
    • 2019-08-25
    相关资源
    最近更新 更多