0

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.

apaderno
  • 28,547
  • 16
  • 75
  • 90
cl200008
  • 1
  • 1
  • 5
  • Can you help me see how to solve this? https://stackoverflow.com/questions/69210874/libvlcsharp-will-get-stuck-playing-specific-videos-on-any-platform – bbhxwl Sep 16 '21 at 15:56

2 Answers2

0

Because you are doing a new Media for each frame, you won't be able to stream it as a single stream.

What you could do is create a MJPEG stream : Put .jpg images one after one in a single stream, and use that stream with LibVLCSharp to stream it.

However, if LibVLCSharp is faster to read the data from your memory stream than you are writing data to it, it will detect the end of the file, and will stop the playback / streaming (A Read() call that returns no data is considered as the end of the file). To avoid that, the key is to "block" the Read() call until there is actually data to read. This is not a problem to block the call as this happen on the VLC thread.

The default MemoryStream/StreamMediaInput won't let you block the Read() call, and you would need to write your own Stream implementation or write your own MediaInput implementation.

Here are a few ideas to block the Read call:

  • Use a BlockingCollection to push Mat instances to the stream input. BlockingCollection has a Take() method that blocks until there is actually some data to read
  • Use a ManualResetEvent to signal when data is available to be read (it has a Wait() method)

It would be easier to talk about that on The LibVLC discord, feel free to join !

If you manage to do that, please share your code as a new libvlcsharp-sample project!

cube45
  • 3,429
  • 2
  • 24
  • 35
  • Thanks you for your quick answer! I am currently trying to implement your idea. `ToMemoryStream()` creates a new Instance, so I don't think that has an impact on the problem. I looked a bit into the code of LibvlcSharp and made an own version of the the MediaInput Class (although it is very similar to the StreamMediaInput). I got stuck to the point, in which I have to block the `Read()`. I am dynamically updating the Stream `_stream` as you can see in the EDIT above, but wouldn't blocking the `read()` entails, that libvlcSharp can't boradcast any data? Or is it independent? – cl200008 Aug 31 '21 at 12:19
  • I would be happy to share my code as a new sample-project, if it works^^ – cl200008 Aug 31 '21 at 12:21
  • It seems I misunderstood something... `mat.ToMemoryStream(".jpg")` will only give you one image. If you want a video stream, you need a loop somehow. I edited my answer, hope it is clearer now ! – cube45 Aug 31 '21 at 13:24
  • Yes it is clearer now, I will try to implement this good idea in the following days. Thank you for your effort. I will keep this updated, so it will help anyone having the same issue. – cl200008 Sep 01 '21 at 18:57
  • I hadn't much time left to implement the idea. So we used the method with piping the images into the VLC.exe via `Process` and `Process.Standardinput`. Thanks for your effort! – cl200008 Sep 13 '21 at 08:14
0

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).

cl200008
  • 1
  • 1
  • 5