0

I'm developing a .NET 6 web application where I have two types of cameras:

  1. An IP camera with all the convenience methods for retrieving MJPEG stream from it
  2. 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?

0 Answers0