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)。
我正在从事一个项目,该项目从 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)。