He estado intentando configurar una secuencia MJPEG en ASP.NET. Quiero recuperar una secuencia MJEPG de una URL y enviar cada fotograma que obtengo a cada cliente conectado. Los ejemplos que he podido encontrar solo se leen desde un archivo establecido, en lugar de una secuencia continua desde la URL, y envían el archivo completo a través de MultiStreamContent . Dado que recupero cuadro por cuadro, no puedo hacer esto. Me gustaría saber si es posible hacer lo que quiero con ASP.NET MVC. Actualmente estoy usando un video de AForge para recuperar la transmisión MJPEG de un enlace. Mi código para la clase de controlador:

using System.Net.Http;
using System.Web.Http;
using AForge.Video;

namespace VideoPrototypeMVC.Controllers
{
    public class CameraController : ApiController
    {
        int framecounter = 0;
        MJPEGStream stream = new MJPEGStream();

        [HttpGet]
        public void GetVideoContent()
        {   
            stream.Source = @"http://127.0.0.1:5002/stream";
            stream.NewFrame += new NewFrameEventHandler(showFrame);
            stream.Start();
            MultipartContent content = new MultipartContent();
            while (stream.IsRunning)
            {
            //Continues streaming should be here?
            }
        }

        //Can be used to display of a frame is available
        private void showFrame(object sender, NewFrameEventArgs eventArgs)
        {
            framecounter++;
            System.Diagnostics.Debug.WriteLine("New frame event: " + framecounter);

        }

        //Should be called at the end of the stream
        private void stopStream(object sender, ReasonToFinishPlaying reason)
        {
            System.Diagnostics.Debug.WriteLine("Stop stream");
            stream.Stop();
            framecounter = 0;
        }
    }
}

Este código no es definitivo, pero solo necesito obtener la transmisión continua. Encontré ejemplos que usan servidores Socket, pero me gustaría mantenerme en MVC ya que me permite configurar el resto del servidor más fácilmente.

5
Arastelion 13 nov. 2017 a las 11:18

2 respuestas

La mejor respuesta

Para asegurarse de que otras personas también se las arreglen con esto. Logré combinar lo que dijo @Evk (Gracias una vez más), junto con la información que encontré aquí: creando mi propia secuencia MJPEG.

TENGA EN CUENTA: ¡El código a continuación es solo un prototipo / prueba de concepto! Cuando ejecuto esto, mi procesador dispara al 100% debido al bucle while sin fin en StartStream . Trabajará para hacer esto más basado en eventos, pero pensé que el código a continuación se explica más fácilmente.

using System;
using System.IO;
using System.Net;
using System.Web;
using System.Net.Http;
using System.Web.Http;
using AForge.Video;
using System.Drawing;
using System.Text;
using System.Drawing.Imaging;
using System.Threading;

namespace VideoPrototypeMVC.Controllers
{
    public class CameraController : ApiController
    {
        private MJPEGStream mjpegStream = new MJPEGStream();
        private bool frameAvailable = false;
        private Bitmap frame = null;
        private string BOUNDARY = "frame";

        /// <summary>
        /// Initializer for the MJPEGstream
        /// </summary>
        CameraController()
        {
            mjpegStream.Source = @"{{INSERT STREAM URL}}";
            mjpegStream.NewFrame += new NewFrameEventHandler(showFrameEvent);
        }

        [HttpGet]
        public HttpResponseMessage GetVideoContent()
        {   
            mjpegStream.Start();
            var response = Request.CreateResponse();
            response.Content = new PushStreamContent((Action<Stream, HttpContent, TransportContext>)StartStream);
            response.Content.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("multipart/x-mixed-replace; boundary=" + BOUNDARY);
            return response;
        }

        /// <summary>
        /// Craete an appropriate header.
        /// </summary>
        /// <param name="length"></param>
        /// <returns></returns>
        private byte[] CreateHeader(int length)
        {
            string header =
                "--" + BOUNDARY + "\r\n" +
                "Content-Type:image/jpeg\r\n" +
                "Content-Length:" + length + "\r\n\r\n";

            return Encoding.ASCII.GetBytes(header);
        }

        public byte[] CreateFooter()
        {
            return Encoding.ASCII.GetBytes("\r\n");
        }

        /// <summary>
        /// Write the given frame to the stream
        /// </summary>
        /// <param name="stream">Stream</param>
        /// <param name="frame">Bitmap format frame</param>
        private void WriteFrame(Stream stream, Bitmap frame)
        {
            // prepare image data
            byte[] imageData = null;

            // this is to make sure memory stream is disposed after using
            using (MemoryStream ms = new MemoryStream())
            {
                frame.Save(ms, ImageFormat.Jpeg);
                imageData = ms.ToArray();
            }

            // prepare header
            byte[] header = CreateHeader(imageData.Length);
            // prepare footer
            byte[] footer = CreateFooter();

            // Start writing data
            stream.Write(header, 0, header.Length);
            stream.Write(imageData, 0, imageData.Length);
            stream.Write(footer, 0, footer.Length);
        }

        /// <summary>
        /// While the MJPEGStream is running and clients are connected,
        /// continue sending frames.
        /// </summary>
        /// <param name="stream">Stream to write to.</param>
        /// <param name="httpContent">The content information</param>
        /// <param name="transportContext"></param>
        private void StartStream(Stream stream, HttpContent httpContent, TransportContext transportContext)
        {
            while (mjpegStream.IsRunning && HttpContext.Current.Response.IsClientConnected)
            {
                if (frameAvailable)
                {
                    try
                    {
                        WriteFrame(stream, frame);
                        frameAvailable = false;
                    } catch (Exception e)
                    {
                        System.Diagnostics.Debug.WriteLine(e);
                    }
                }else
                {
                    Thread.Sleep(30);
                }
            }
            stopStream();
        }

        /// <summary>
        /// This event is thrown when a new frame is detected by the MJPEGStream
        /// </summary>
        /// <param name="sender">Object that is sending the event</param>
        /// <param name="eventArgs">Data from the event, including the frame</param>
        private void showFrameEvent(object sender, NewFrameEventArgs eventArgs)
        {
            frame = new Bitmap(eventArgs.Frame);
            frameAvailable = true;
        }

        /// <summary>
        /// Stop the stream.
        /// </summary>
        private void stopStream()
        {
            System.Diagnostics.Debug.WriteLine("Stop stream");
            mjpegStream.Stop();
        }
    }
}
2
Arastelion 17 nov. 2017 a las 14:54

Gran respuesta de Arastelion, sin embargo, me di cuenta de que si abandona la aplicación, todavía hay una solicitud en segundo plano que puede ser una fuente de recursos apareciendo en stream.FlushAsync (); stream.Close (); stream.Dispose (); después de que stopStream parece resolver eso.

using System;
using System.IO;
using System.Net;
using System.Web;
using System.Net.Http;
using System.Web.Http;
using AForge.Video;
using System.Drawing;
using System.Text;
using System.Drawing.Imaging;
using System.Threading;

namespace VideoPrototypeMVC.Controllers
{
    public class CameraController : ApiController
    {
        private MJPEGStream mjpegStream = new MJPEGStream();
        private bool frameAvailable = false;
        private Bitmap frame = null;
        private string BOUNDARY = "frame";

        /// <summary>
        /// Initializer for the MJPEGstream
        /// </summary>
        CameraController()
        {
            mjpegStream.Source = @"{{INSERT STREAM URL}}";
            mjpegStream.NewFrame += new NewFrameEventHandler(showFrameEvent);
        }

        [HttpGet]
        public HttpResponseMessage GetVideoContent()
        {   
            mjpegStream.Start();
            var response = Request.CreateResponse();
            response.Content = new PushStreamContent((Action<Stream, HttpContent, TransportContext>)StartStream);
            response.Content.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("multipart/x-mixed-replace; boundary=" + BOUNDARY);
            return response;
        }

        /// <summary>
        /// Craete an appropriate header.
        /// </summary>
        /// <param name="length"></param>
        /// <returns></returns>
        private byte[] CreateHeader(int length)
        {
            string header =
                "--" + BOUNDARY + "\r\n" +
                "Content-Type:image/jpeg\r\n" +
                "Content-Length:" + length + "\r\n\r\n";

            return Encoding.ASCII.GetBytes(header);
        }

        public byte[] CreateFooter()
        {
            return Encoding.ASCII.GetBytes("\r\n");
        }

        /// <summary>
        /// Write the given frame to the stream
        /// </summary>
        /// <param name="stream">Stream</param>
        /// <param name="frame">Bitmap format frame</param>
        private void WriteFrame(Stream stream, Bitmap frame)
        {
            // prepare image data
            byte[] imageData = null;

            // this is to make sure memory stream is disposed after using
            using (MemoryStream ms = new MemoryStream())
            {
                frame.Save(ms, ImageFormat.Jpeg);
                imageData = ms.ToArray();
            }

            // prepare header
            byte[] header = CreateHeader(imageData.Length);
            // prepare footer
            byte[] footer = CreateFooter();

            // Start writing data
            stream.Write(header, 0, header.Length);
            stream.Write(imageData, 0, imageData.Length);
            stream.Write(footer, 0, footer.Length);
        }

        /// <summary>
        /// While the MJPEGStream is running and clients are connected,
        /// continue sending frames.
        /// </summary>
        /// <param name="stream">Stream to write to.</param>
        /// <param name="httpContent">The content information</param>
        /// <param name="transportContext"></param>
        private void StartStream(Stream stream, HttpContent httpContent, TransportContext transportContext)
        {
            while (mjpegStream.IsRunning && HttpContext.Current.Response.IsClientConnected)
            {
                if (frameAvailable)
                {
                    try
                    {
                        WriteFrame(stream, frame);
                        frameAvailable = false;
                    } catch (Exception e)
                    {
                        System.Diagnostics.Debug.WriteLine(e);
                    }
                }else
                {
                    Thread.Sleep(30);
                }
            }
            stopStream();
            stream.FlushAsync();
            stream.Close();
            stream.Dispose();
        }

        /// <summary>
        /// This event is thrown when a new frame is detected by the MJPEGStream
        /// </summary>
        /// <param name="sender">Object that is sending the event</param>
        /// <param name="eventArgs">Data from the event, including the frame</param>
        private void showFrameEvent(object sender, NewFrameEventArgs eventArgs)
        {
            frame = new Bitmap(eventArgs.Frame);
            frameAvailable = true;
        }

        /// <summary>
        /// Stop the stream.
        /// </summary>
        private void stopStream()
        {
            System.Diagnostics.Debug.WriteLine("Stop stream");
            mjpegStream.Stop();
        }

         protected override void Dispose(bool disposing)
         {
            if (disposing)
             {
                 stopStream();
             }

             base.Dispose(disposing);
           } 


    }
}
0
Richard Housham 20 mar. 2020 a las 09:42