C# EmbedIO 服务器:只有第一个请求正在尝试从 FFMPEG 直播
C# EmbedIO server: Only the first request is working trying to live stream from FFMPEG
我正在尝试构建一个 HTTP 服务器,它将通过 FFMPEG 以 TransportStream 格式传输动态 video/audio。我找到了 EmbedIO,它看起来像是一个轻量级但灵活的基础。
因此,我查看了模块示例并构建了一个非常基本的模块,该模块尚未处理请求 URL 但对任何请求都使用相同的流进行响应,只是为了查看它是否正常工作如预期:
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);
}
}
}
这确实有效,当我在浏览器中打开连接时,会提供一个 TS 文件供下载,当我通过 VLC 播放器连接时,我看到我的 default.png 文件伴随着 Jeopardy think 音乐 -耶!但是,如果我连接第二个客户端(播放器或浏览器),它将无休止地加载并且不会返回任何内容。即使我关闭之前的连接(中止下载或停止播放),后续连接也不会产生任何响应。我必须停止并重新启动服务器才能再次建立一个连接。
在我看来,我的代码正在阻塞服务器,尽管 运行 在它自己的任务中。我来自 PHP & JavaScript 背景,所以我对 C# 和线程还很陌生。所以这可能很明显......但我希望 EmbedIO 能够处理所有 multitasking/threading 的东西。
仅将 Task<bool>
指定为 return 类型不会在它自己的任务中创建方法 运行。您必须使用 Task.Run(() => ...)
手动 运行 新任务,或者使您的方法 async
然后使用 await
进行异步处理,如下所示。另请注意,必须在发送 headers 之前设置 context.Response.StatusCode
。
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;
}
我正在尝试构建一个 HTTP 服务器,它将通过 FFMPEG 以 TransportStream 格式传输动态 video/audio。我找到了 EmbedIO,它看起来像是一个轻量级但灵活的基础。
因此,我查看了模块示例并构建了一个非常基本的模块,该模块尚未处理请求 URL 但对任何请求都使用相同的流进行响应,只是为了查看它是否正常工作如预期:
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);
}
}
}
这确实有效,当我在浏览器中打开连接时,会提供一个 TS 文件供下载,当我通过 VLC 播放器连接时,我看到我的 default.png 文件伴随着 Jeopardy think 音乐 -耶!但是,如果我连接第二个客户端(播放器或浏览器),它将无休止地加载并且不会返回任何内容。即使我关闭之前的连接(中止下载或停止播放),后续连接也不会产生任何响应。我必须停止并重新启动服务器才能再次建立一个连接。
在我看来,我的代码正在阻塞服务器,尽管 运行 在它自己的任务中。我来自 PHP & JavaScript 背景,所以我对 C# 和线程还很陌生。所以这可能很明显......但我希望 EmbedIO 能够处理所有 multitasking/threading 的东西。
仅将 Task<bool>
指定为 return 类型不会在它自己的任务中创建方法 运行。您必须使用 Task.Run(() => ...)
手动 运行 新任务,或者使您的方法 async
然后使用 await
进行异步处理,如下所示。另请注意,必须在发送 headers 之前设置 context.Response.StatusCode
。
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;
}