【发布时间】:2021-05-10 20:08:34
【问题描述】:
我尝试创建一个端点来提供我的网络摄像头的实时流。我正在使用 AForge 访问相机,但我无法创建并返回实际呈现在 html 客户端的视频标签中的流。
另外:我使用的 MemoryStream 每秒都在增长。这绝对不是我想要的。
到目前为止我尝试了什么:
Cam.cs:
public class Cam
{
MemoryStream stream = new MemoryStream();
public Cam()
{
FilterInfoCollection videoCaptureDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
VideoCaptureDevice finalVideo = new VideoCaptureDevice(videoCaptureDevices[0].MonikerString);
finalVideo.NewFrame += this._streamNewFrame;
finalVideo.Start();
}
private void _streamNewFrame(object sender, AForge.Video.NewFrameEventArgs eventArgs)
{
System.Drawing.Image imgforms = (Bitmap)eventArgs.Frame.Clone();
imgforms.Save(this.stream, ImageFormat.Bmp);
this.stream.Seek(0, SeekOrigin.Begin);
}
public Stream GetStream()
{
MemoryStream stream = new MemoryStream();
this.stream.CopyTo(stream);
return stream;
}
}
CamController.cs:
[ApiController]
[Route("[controller]")]
public class CamController : ControllerBase
{
private readonly Cam cam;
public CamController(Cam cam)
{
this.cam = cam;
}
[HttpGet]
public IActionResult Get()
{
var contentType = "multipart/x-mixed-replace;boundary=myboundary";
Stream stream = this.cam.GetStream();
var result = new FileStreamResult(stream, contentType)
{
EnableRangeProcessing = true,
};
return result;
}
}
更新 1:
我取得了进步。我设法创建了一个有效的 MJPEP 流(如果知道它的外观很容易)。看那个:
CamController.cs:
using Microsoft.AspNetCore.Mvc;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace demo.Controllers
{
[ApiController]
[Route("[controller]")]
public class CamController : ControllerBase
{
private readonly Cam cam;
public CamController(Cam cam)
{
this.cam = cam;
}
[HttpGet]
[Route("cam/video")]
public async Task Get()
{
Response.ContentType = "video/webm";
// How to go on here?
}
[HttpGet]
[Route("cam/mjepg")]
public async Task Get2()
{
Response.StatusCode = 206;
Response.ContentType = "multipart/x-mixed-replace; boundary=frame";
Response.Headers.Add("Connection", "Keep-Alive");
StreamingSession session = this.cam.StreamOn(data =>
{
if (Request.HttpContext.RequestAborted.IsCancellationRequested)
{
throw new Exception();
}
Response.Body.Write(this.CreateHeader(data.Length));
Response.Body.Write(data);
Response.Body.Write(this.CreateFooter());
Response.Body.Flush();
});
await Response.StartAsync();
await session.WaitAsync();
}
/// <summary>
/// Create an appropriate header.
/// </summary>
/// <param name="length"></param>
/// <returns></returns>
private byte[] CreateHeader(int length)
{
string header =
"--frame" + "\r\n" +
"Content-Type:image/jpeg\r\n" +
"Content-Length:" + length + "\r\n\r\n";
return Encoding.ASCII.GetBytes(header);
}
private byte[] CreateFooter()
{
return Encoding.ASCII.GetBytes("\r\n");
}
}
}
Cam.cs:
using AForge.Video.DirectShow;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace demo
{
public class Cam
{
object locker = new object();
bool signaledToStop = false;
List<StreamingSession> sessions = new List<StreamingSession>();
VideoCaptureDevice finalVideo;
public Cam()
{
FilterInfoCollection videoCaptureDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
finalVideo = new VideoCaptureDevice(videoCaptureDevices[0].MonikerString);
finalVideo.VideoResolution = finalVideo.VideoCapabilities
.OrderByDescending(x => x.MaximumFrameRate)
.ThenByDescending(x=>x.FrameSize.Width)
.FirstOrDefault();
finalVideo.NewFrame += this._streamNewFrame;
}
private void _streamNewFrame(object sender, AForge.Video.NewFrameEventArgs eventArgs)
{
System.Drawing.Image imgforms = (Bitmap)eventArgs.Frame.Clone();
byte[] data = new byte[0];
using (MemoryStream stream = new MemoryStream())
{
imgforms.Save(stream, ImageFormat.Jpeg);
data = stream.ToArray();
}
lock (this.locker)
{
foreach (var session in sessions.ToList())
{
session.ProvideData(data);
}
}
}
public StreamingSession StreamOn(Action<byte[]> callback)
{
StreamingSession session = new StreamingSession(callback);
lock (this.locker)
{
this.sessions.Add(session);
if (this.signaledToStop)
{
this.finalVideo.WaitForStop();
}
if (!this.finalVideo.IsRunning)
{
this.finalVideo.Start();
this.signaledToStop = false;
}
}
session.OnSessionEnded += Session_OnSessionEnded;
return session;
}
private void Session_OnSessionEnded(object sender, EventArgs e)
{
lock(this.locker)
{
this.sessions.Remove(sender as StreamingSession);
if (!this.sessions.Any())
{
this.finalVideo.SignalToStop();
this.signaledToStop = true;
}
}
}
}
public class StreamingSession
{
public StreamingSession(Action<byte[]> Callback)
{
this.Callback = Callback;
}
private Action<byte[]> Callback;
private TaskCompletionSource Completion = new TaskCompletionSource();
public event EventHandler OnSessionEnded;
public Task WaitAsync(int? timeout = null)
{
if (timeout.HasValue)
{
return Task.WhenAny(Task.Delay(timeout.Value), this.Completion.Task);
}
return this.Completion.Task;
}
public void ProvideData(byte[] data)
{
try
{
this.Callback(data);
}
catch(Exception)
{
this.EndSession();
}
}
public void EndSession()
{
this.Completion.SetResult();
if (this.OnSessionEnded != null)
{
this.OnSessionEnded(this, null);
}
}
}
}
但是还有一个问题:“视频”(更像是运动图像)仅在 img 标签中呈现。但它应该在视频标签中呈现。我读到视频标签不支持 mjpeg - 但是如何以视频标签理解的格式对流进行编码?
我在这里上传了完整的代码:https://github.com/ChristophWieske/asp-net-core-live-stream-source
【问题讨论】:
-
“在 html 客户端的视频标签中呈现”你是什么意思?您想在将视频发送给客户端之前渲染一些文本信息吗?
-
@eocron 不,我只是想显示视频。
-
@christoph 也显示客户端。解释为什么使用
"multipart/x-mixed-replace;boundary=myboundary"内容类型 -
在您编写完整的示例之前,我们很难诊断问题。你应该做的第一件事——写一个测试。模拟与您的 cam 驱动程序相关的所有内容(因此所有内容都在接口后面,并且在那里看不到驱动程序名称空间)并提供您自己的实现,例如每 2 秒重复一次猫/狗图像。然后在上面调试您的应用程序。然后切换回驱动程序实现。这就是我在单元测试中所做的和固定的。
-
@Nkosi 我不太确定,但它似乎是 mjpeg 流的正确选择(它由多个 jpeg 图像组成 -> 所以是的 bmp 因为图像格式是错误的,我会更新)
标签: c# asp.net-core video-streaming webapi