I'm developing a .NET 6 web application where I have two types of cameras:
- An IP camera with all the convenience methods for retrieving MJPEG stream from it
- A "manual" camera without all this kind of convenience methods where I need to get every single frame manually.
Now, and I'm trying to grab from them a MJPEG stream and return it to clients by means of the same parameterized controller method, so that from client point of view there appears to be only one endpoint and I can achieve greater modularity client-side. For the ip camera there are no problem, while for the "manual" camera there are.
In the following you can find the code I'm using to return a mjpeg stream from the ip camera (working) and from the manual camera (not working).
Controller:
[ApiController]
[Route("[controller]")]
public class CameraSystemController : ControllerBase
{
private string _contentTypeStreaming = "multipart/x-mixed-replace; boundary=myboundary";
[HttpGet("GetStream_LowDefinition")]
public async Task<IActionResult> GetStream_LowQuality(int id, CancellationToken token) {
// Here I distinguish between the two different cameras
bool isIpCamera = IsIpCamera(id);
Stream stream = null;
if (isIpCamera) {
// ip camera
stream = await HttpClient.GetStreamAsync("http://192.../axis-cgi/mjpg/video.cgi", token);
} else {
// manual camera
stream = await MJPEGStream.GetStream();
}
if (stream != null) {
Response.Headers.Add("Cache-Control", "no-cache");
FileStreamResult result = new FileStreamResult(stream, _contentTypeStreaming) {
EnableRangeProcessing = true
};
return result;
} else {
return new StatusCodeResult((int)HttpStatusCode.ServiceUnavailable);
}
}
}
Class handling the MJPEG stream. Please note I use the '_lastFrameHash' trick to discover when a new frame is available.
internal class MJPEGStream
{
private readonly object _lock = new object();
private byte[] _lastFrameBytes;
private int _lastFrameHash;
private int _halfAcquisitionPeriod_ms = //...
private static readonly string _boundary = "myboundary";
private static readonly byte[] _newLine = Encoding.UTF8.GetBytes("\r\n");
// this method is called by the class handling the acquisition process
// every time a new frame is available.
internal void OnNewFrame(MemoryStream frame) {
lock (_lock){ _lastFrameBytes = frame.GetBuffer(); }
_lastFrameHash++;
}
internal async Task<Stream> GetStream() {
int lastFrameHash = _lastFrameHash;
do { await Task.Delay(_halfAcquisitionPeriod_ms); } while (lastFrameHash.Equals(_lastFrameHash));
byte[] bytes;
lock (_lock) { bytes = _lastFrameBytes.ToArray(); }
Stream stream = new MemoryStream();
string header = $"--{_boundary}\r\nContent-Type: image/jpeg\r\nContent-Length: {bytes.Length}\r\n\r\n";
byte[] headerData = Encoding.UTF8.GetBytes(header);
await stream.WriteAsync(headerData, 0, headerData.Length);
await stream.WriteAsync(bytes, 0, bytes.Length);
await stream.WriteAsync(_newLine, 0, _newLine.Length);
stream.Position = 0;
return stream;
}
}
However, if I make a get request by means of the browser, only the last frame is returned, not a stream. I can notice the same difference (between the actual behavior and the expected behavior) by means of postman.
Please note I've seen a lot of post, like this one, but none of them seems to address my problem.
Any idea how I could get this working?