简体   繁体   中英

C# EmbedIO server: Only the first request is working trying to live stream from FFMPEG

I'm trying to build an HTTP server that will stream dynamic video/audio in the TransportStream format via FFMPEG. I found EmbedIO and it looks like a lightweight yet flexible base for this.

So, I looked at the module examples and built a very basic module that doesn't yet handle the request URL at all but responds with the same stream for any request, just to see whether it's working as intended:

namespace TSserver
{
    using Unosquare.Swan;
    using Unosquare.Swan.Formatters;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;
#if NET46
    using System.Net;
#else
    using Unosquare.Net;
    using Unosquare.Labs.EmbedIO;
    using Unosquare.Labs.EmbedIO.Constants;
    using System.Diagnostics;
#endif

    /// <summary>
    /// TSserver Module
    /// </summary>
    public class TSserverModule : WebModuleBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="TSserverModule"/> class.
        /// </summary>
        /// <param name="basePath">The base path.</param>
        /// <param name="jsonPath">The json path.</param>
        public TSserverModule()
        {
            AddHandler(ModuleMap.AnyPath, HttpVerbs.Any, HandleRequest);
        }

        /// <summary>
        /// Gets the Module's name
        /// </summary>
        public override string Name => nameof(TSserverModule).Humanize();


        /// <summary>
        /// Handles the request.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="ct">The cancellation token.</param>
        /// <returns></returns>
        private Task<bool> HandleRequest(HttpListenerContext context, CancellationToken ct)
        {
            var path = context.RequestPath();
            var verb = context.RequestVerb();

            System.Net.HttpStatusCode statusCode;
            context.Response.SendChunked = true;
            //context.Response.AddHeader("Last-Modified", File.GetLastWriteTime(filename).ToString("r"));
            context.Response.ContentType = "video/mp2t";

            try
            {
                var ffmpeg = new Process
                {
                    StartInfo = new ProcessStartInfo
                    {
                        FileName = "ffmpeg.exe",
                        Arguments = "-re -loop 1 -i \"./default.png\" -i \"./jeopardy.mp3\" -c:v libx264 -tune stillimage -r 25 -vcodec mpeg2video -profile:v 4 -bf 2 -b:v 4000k -maxrate:v 5000k -acodec mp2 -ac 2 -ab 128k -ar 48000 -f mpegts -mpegts_original_network_id 1 -mpegts_transport_stream_id 1 -mpegts_service_id 1 -mpegts_pmt_start_pid 4096 -streamid 0:289 -streamid 1:337 -metadata service_provider=\"MYCALL\" -metadata service_name=\"My Station ID\" -y pipe:1",
                        UseShellExecute = false,
                        RedirectStandardOutput = true,
                        CreateNoWindow = true
                    }
                };

                ffmpeg.Start();

                FileStream baseStream = ffmpeg.StandardOutput.BaseStream as FileStream;
                int lastRead = 0;
                byte[] buffer = new byte[4096];

                do
                {
                    lastRead = baseStream.Read(buffer, 0, buffer.Length);
                    context.Response.OutputStream.Write(buffer, 0, lastRead);
                    context.Response.OutputStream.Flush();
                } while (lastRead > 0);

                statusCode = System.Net.HttpStatusCode.OK;
            }
            catch (Exception e)
            {
                statusCode = System.Net.HttpStatusCode.InternalServerError;
            }

            context.Response.StatusCode = (int)statusCode;
            context.Response.OutputStream.Flush();
            context.Response.OutputStream.Close();

            return Task.FromResult(true);
        }

    }
}

This does indeed work, when I open a connection in a browser, a TS file is offered for download, when I connect via VLC Player, I see my default.png file accompanied by the Jeopardy think music - yay! However, if I connect a second client (player or browser) it will just load endlessly and not get anything back. Even if I close the previous connection (abort the download or stop playback), no subsequent connection will result in any response. I have to stop and start the server again in order to be able to make one single connection again.

It seems to me that my code is blocking the server, despite being run inside a Task of its own. I'm coming from a PHP & JavaScript background, so I'm quite new to C# and threading. So this might be pretty obvious... But I hoped that EmbedIO would handle all the multitasking/threading stuff.

Just specifying Task<bool> as return type doesn't make method run inside it's own task. You must either run new task manually using Task.Run(() => ...) or make your method async and then use await to do processing asynchronously, as shown bellow. Also note that context.Response.StatusCode must be set before headers are sent.

private async Task<bool> HandleRequest(HttpListenerContext context, CancellationToken ct)
{
    var path = context.RequestPath();
    var verb = context.RequestVerb();
    bool headersSent = false;

    try
    {
        var ffmpeg = new Process
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = "ffmpeg.exe",
                Arguments = "-re -i \"./default.png\" -i \"./jeopardy.mp3\" -c:v libx264 -tune stillimage -r 25 -vcodec mpeg2video -profile:v 4 -bf 2 -b:v 4000k -maxrate:v 5000k -acodec mp2 -ac 2 -ab 128k -ar 48000 -f mpegts -mpegts_original_network_id 1 -mpegts_transport_stream_id 1 -mpegts_service_id 1 -mpegts_pmt_start_pid 4096 -streamid 0:289 -streamid 1:337 -metadata service_provider=\"MYCALL\" -metadata service_name=\"My Station ID\" -y pipe:1",
                UseShellExecute = false,
                RedirectStandardOutput = true,
                CreateNoWindow = true
            }
        };

        ffmpeg.Start();

        //StatusCode must be set before headers are sent
        context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK;
        headersSent = true;
        context.Response.SendChunked = true;
        //context.Response.AddHeader("Last-Modified", File.GetLastWriteTime(filename).ToString("r"));
        context.Response.ContentType = "video/mp2t";

        FileStream baseStream = ffmpeg.StandardOutput.BaseStream as FileStream;
        //Copy stream asynchronously, so we will not block current thread and another request can be processed
        await baseStream.CopyToAsync(context.Response.OutputStream, 4096, ct);

        context.Response.OutputStream.Flush();
        context.Response.OutputStream.Close();
    }
    catch (Exception)
    {
        if (!headersSent)//Without this, setting StatusCode would throw exception
        {
            context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
        }
    }

    return true;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM