我一直想实现某些视频格式(例如“.mkv”、“.wmv”、“.mov”等)的按需转码,以便使用 ASP.NET 在媒体管理服务器上提供它们核心 6.0、C# 和 ffmpeg。
我决定使用的方法是提供动态生成的 .m3u8 文件,该文件是使用选择的段持续时间(例如10s 和已知的视频时长。我是这样做的。请注意,该决议目前尚未实施并被废弃:
public string GenerateVideoOnDemandPlaylist(double duration, int segment)
{
double interval = (double)segment;
var content = new StringBuilder();
content.AppendLine("#EXTM3U");
content.AppendLine("#EXT-X-VERSION:6");
content.AppendLine(String.Format("#EXT-X-TARGETDURATION:{0}", segment));
content.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
content.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
content.AppendLine("#EXT-X-INDEPENDENT-SEGMENTS");
for (double index = 0; (index * interval) < duration; index++)
{
content.AppendLine(String.Format("#EXTINF:{0:#.000000},", ((duration - (index * interval)) > interval) ? interval : ((duration - (index * interval)))));
content.AppendLine(String.Format("{0:00000}.ts", index));
}
content.AppendLine("#EXT-X-ENDLIST");
return content.ToString();
}
[HttpGet]
[Route("stream/{id}/{resolution}.m3u8")]
public IActionResult Stream(string id, string resolution)
{
double duration = RetrieveVideoLengthInSeconds();
return Content(GenerateVideoOnDemandPlaylist(duration, 10), "application/x-mpegURL", Encoding.UTF8);
}
以下是 .m3u8 文件的示例:
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-INDEPENDENT-SEGMENTS
#EXTINF:10.000000,
00000.ts
#EXTINF:3.386667,
00001.ts
#EXT-X-ENDLIST
因此玩家会要求00000.ts、00001.ts等,下一步是按需生成它们:
public byte[] GenerateVideoOnDemandSegment(int index, int duration, string path)
{
int timeout = 30000;
int totalWaitTime = 0;
int waitInterval = 100;
byte[] output = Array.Empty<byte>();
string executable = "/opt/homebrew/bin/ffmpeg";
DirectoryInfo temp = Directory.CreateDirectory(System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName()));
string format = System.IO.Path.Combine(temp.FullName, "output-%05d.ts");
using (Process ffmpeg = new())
{
ffmpeg.StartInfo.FileName = executable;
ffmpeg.StartInfo.Arguments = String.Format("-ss {0} ", index * duration);
ffmpeg.StartInfo.Arguments += String.Format("-y -t {0} ", duration);
ffmpeg.StartInfo.Arguments += String.Format("-i \"{0}\" ", path);
ffmpeg.StartInfo.Arguments += String.Format("-c:v libx264 -c:a aac ");
ffmpeg.StartInfo.Arguments += String.Format("-segment_time {0} -reset_timestamps 1 -break_non_keyframes 1 -map 0 ", duration);
ffmpeg.StartInfo.Arguments += String.Format("-initial_offset {0} ", index * duration);
ffmpeg.StartInfo.Arguments += String.Format("-f segment -segment_format mpegts {0}", format);
ffmpeg.StartInfo.CreateNoWindow = true;
ffmpeg.StartInfo.UseShellExecute = false;
ffmpeg.StartInfo.RedirectStandardError = false;
ffmpeg.StartInfo.RedirectStandardOutput = false;
ffmpeg.Start();
do
{
Thread.Sleep(waitInterval);
totalWaitTime += waitInterval;
}
while ((!ffmpeg.HasExited) && (totalWaitTime < timeout));
if (ffmpeg.HasExited)
{
string filename = System.IO.Path.Combine(temp.FullName, "output-00000.ts");
if (!File.Exists(filename))
{
throw new FileNotFoundException("Unable to find the generated segment: " + filename);
}
output = File.ReadAllBytes(filename);
}
else
{
// It's been too long. Kill it!
ffmpeg.Kill();
}
}
// Remove the temporary directory and all its contents.
temp.Delete(true);
return output;
}
[HttpGet]
[Route("stream/{id}/{index}.ts")]
public IActionResult Segment(string id, int index)
{
string path = RetrieveVideoPath(id);
return File(GenerateVideoOnDemandSegment(index, 10, path), "application/x-mpegURL", true);
}
正如您所看到的,这是我用来生成每个段的命令,为每个段将 -ss 和 -initial_offset 递增 10:
ffmpeg -ss 0 -y -t 10 -i "video.mov" -c:v libx264 -c:a aac -segment_time 10 -reset_timestamps 1 -break_non_keyframes 1 -map 0 -initial_offset 0 -f segment -segment_format mpegts /var/folders/8h/3xdhhky96b5bk2w2br6bt8n00000gn/T/4ynrwu0q.z24/output-%05d.ts
功能层面上一切正常,但是片段之间的过渡有点问题,尤其是音频在每 10 秒标记处有非常短的中断。如何确保各部分无缝衔接?在这个过程中我可以改进什么?
由于您使用的是分段复用器,因此您的输入持续时间应该是您需要的分段的总和。
对于段 00002.ts 至 00004.ts,
ffmpeg -ss 20 -t 30 -copyts -i "video.mov" -map 0 -c:v libx264 -c:a aac -f segment -segment_time 10 -reset_timestamps 0 -break_non_keyframes 1 -segment_format mpegts output-%05d.ts -y
(您只需为每一组连续的输出段运行一个命令)