Search code examples
c#opencv.net-corelibvlclibvlcsharp

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


I'm working on a project that takes individual images from an RTSP-Stream and manipulates them (drawing bounding boxes). Those images should be restreamed (h264 encoded) on a separate RTSP-stream on an other address and shouldn't be saved on the local disk.

My current code so far is:

        {
            // 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);
                }
            }
        }

It is a little bit messy, because of testing purposes, but it works if I just use the given RTSP-Stream as the Media instead of the fetched images. I have some success with piping the images (as bytes) into the cvlc command line (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"}'), but it should be integrated in c#. get_images.py just reads images in a while-loop, wirtes a text on it and forwards them into std-out.

My thoughts on solving this problem is, to input the images via the StreamMediaInput-class and to dynamically change the media, if a new image has been retrieved. But it doesn't work, nothing can be seen with VLC or FFPlay.

Does someone has faced a similar Problem? How can the StreamMediaInput-Object can be changed dynamically, such that new images are broadcastet correctly?

Thank you for taking the time to read this post. Have a nice day!

EDIT:

I tried to implement my own MediaInput class (very similar to MemoryStramMediaInput) with the modification of UpdateMemoryStream(). The MemoryStream gets updated by every new retrieved image, but the read() will not get called a second time (read() is called once per Medium). I am trying to implement the blocking read(), but I am struggeling to find a good way in implementing it. The code so far is:

EDIT 2:

I decided to implement the blocking with an ManualResetEvent, which blocks the read(), if the Position is at the end of the Stream. Futhermore the read is looped in a while to keep the data in the stream updated. It still does not work. My Code so far:

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

I have marked the spot in the code, where the blocking clause would fit well, in my opinion.


Solution

  • After a long time trying to implement a good solution with no success, we used the method with piping the retrieved Mat into Vlc.exe. To anybody having the same issue:

    Use ProcessStartInfo to specify the path of vlc. The images can be piped into the process with Process.StandardInput.BaseStream.Write(). Use a loop to capture the images periodically (e.g. with OpenCV).