update sync encoding to match streaming

This commit is contained in:
Luke Pulverenti 2016-02-04 14:31:04 -05:00
parent d28ef71d93
commit d1de9e0179
4 changed files with 287 additions and 182 deletions

View file

@ -19,38 +19,40 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
} }
protected override string GetCommandLineArguments(EncodingJob job) protected override string GetCommandLineArguments(EncodingJob state)
{ {
var audioTranscodeParams = new List<string>(); var audioTranscodeParams = new List<string>();
var bitrate = job.OutputAudioBitrate; var bitrate = state.OutputAudioBitrate;
if (bitrate.HasValue) if (bitrate.HasValue)
{ {
audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(UsCulture)); audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(UsCulture));
} }
if (job.OutputAudioChannels.HasValue) if (state.OutputAudioChannels.HasValue)
{ {
audioTranscodeParams.Add("-ac " + job.OutputAudioChannels.Value.ToString(UsCulture)); audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(UsCulture));
} }
if (job.OutputAudioSampleRate.HasValue) if (state.OutputAudioSampleRate.HasValue)
{ {
audioTranscodeParams.Add("-ar " + job.OutputAudioSampleRate.Value.ToString(UsCulture)); audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture));
} }
var threads = GetNumberOfThreads(job, false); const string vn = " -vn";
var inputModifier = GetInputModifier(job); var threads = GetNumberOfThreads(state, false);
var inputModifier = GetInputModifier(state);
return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"", return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
inputModifier, inputModifier,
GetInputArgument(job), GetInputArgument(state),
threads, threads,
" -vn", vn,
string.Join(" ", audioTranscodeParams.ToArray()), string.Join(" ", audioTranscodeParams.ToArray()),
job.OutputFilePath).Trim(); state.OutputFilePath).Trim();
} }
protected override string GetOutputFileExtension(EncodingJob state) protected override string GetOutputFileExtension(EncodingJob state)

View file

@ -303,15 +303,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
return job.Options.CpuCoreLimit ?? 0; return job.Options.CpuCoreLimit ?? 0;
} }
protected string GetInputModifier(EncodingJob job, bool genPts = true) protected string GetInputModifier(EncodingJob state, bool genPts = true)
{ {
var inputModifier = string.Empty; var inputModifier = string.Empty;
var probeSize = GetProbeSizeArgument(job); var probeSize = GetProbeSizeArgument(state);
inputModifier += " " + probeSize; inputModifier += " " + probeSize;
inputModifier = inputModifier.Trim(); inputModifier = inputModifier.Trim();
var userAgentParam = GetUserAgentParam(job); var userAgentParam = GetUserAgentParam(state);
if (!string.IsNullOrWhiteSpace(userAgentParam)) if (!string.IsNullOrWhiteSpace(userAgentParam))
{ {
@ -320,35 +320,43 @@ namespace MediaBrowser.MediaEncoding.Encoder
inputModifier = inputModifier.Trim(); inputModifier = inputModifier.Trim();
inputModifier += " " + GetFastSeekCommandLineParameter(job.Options); inputModifier += " " + GetFastSeekCommandLineParameter(state.Options);
inputModifier = inputModifier.Trim(); inputModifier = inputModifier.Trim();
if (job.IsVideoRequest && genPts) if (state.IsVideoRequest && genPts)
{ {
inputModifier += " -fflags +genpts"; inputModifier += " -fflags +genpts";
} }
if (!string.IsNullOrEmpty(job.InputAudioSync)) if (!string.IsNullOrEmpty(state.InputAudioSync))
{ {
inputModifier += " -async " + job.InputAudioSync; inputModifier += " -async " + state.InputAudioSync;
} }
if (!string.IsNullOrEmpty(job.InputVideoSync)) if (!string.IsNullOrEmpty(state.InputVideoSync))
{ {
inputModifier += " -vsync " + job.InputVideoSync; inputModifier += " -vsync " + state.InputVideoSync;
} }
if (job.ReadInputAtNativeFramerate) if (state.ReadInputAtNativeFramerate)
{ {
inputModifier += " -re"; inputModifier += " -re";
} }
var videoDecoder = GetVideoDecoder(job); var videoDecoder = GetVideoDecoder(state);
if (!string.IsNullOrWhiteSpace(videoDecoder)) if (!string.IsNullOrWhiteSpace(videoDecoder))
{ {
inputModifier += " " + videoDecoder; inputModifier += " " + videoDecoder;
} }
//if (state.IsVideoRequest)
//{
// if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase))
// {
// //inputModifier += " -noaccurate_seek";
// }
//}
return inputModifier; return inputModifier;
} }
@ -392,11 +400,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
return null; return null;
} }
private string GetUserAgentParam(EncodingJob job) private string GetUserAgentParam(EncodingJob state)
{ {
string useragent = null; string useragent = null;
job.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent); state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
if (!string.IsNullOrWhiteSpace(useragent)) if (!string.IsNullOrWhiteSpace(useragent))
{ {
@ -409,31 +417,31 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <summary> /// <summary>
/// Gets the probe size argument. /// Gets the probe size argument.
/// </summary> /// </summary>
/// <param name="job">The job.</param> /// <param name="state">The state.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
private string GetProbeSizeArgument(EncodingJob job) private string GetProbeSizeArgument(EncodingJob state)
{ {
if (job.PlayableStreamFileNames.Count > 0) if (state.PlayableStreamFileNames.Count > 0)
{ {
return MediaEncoder.GetProbeSizeArgument(job.PlayableStreamFileNames.ToArray(), job.InputProtocol); return MediaEncoder.GetProbeSizeArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol);
} }
return MediaEncoder.GetProbeSizeArgument(new[] { job.MediaPath }, job.InputProtocol); return MediaEncoder.GetProbeSizeArgument(new[] { state.MediaPath }, state.InputProtocol);
} }
/// <summary> /// <summary>
/// Gets the fast seek command line parameter. /// Gets the fast seek command line parameter.
/// </summary> /// </summary>
/// <param name="options">The options.</param> /// <param name="request">The request.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
/// <value>The fast seek command line parameter.</value> /// <value>The fast seek command line parameter.</value>
protected string GetFastSeekCommandLineParameter(EncodingJobOptions options) protected string GetFastSeekCommandLineParameter(EncodingJobOptions request)
{ {
var time = options.StartTimeTicks; var time = request.StartTimeTicks ?? 0;
if (time.HasValue && time.Value > 0) if (time > 0)
{ {
return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time.Value)); return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time));
} }
return string.Empty; return string.Empty;
@ -442,34 +450,35 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <summary> /// <summary>
/// Gets the input argument. /// Gets the input argument.
/// </summary> /// </summary>
/// <param name="job">The job.</param> /// <param name="state">The state.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
protected string GetInputArgument(EncodingJob job) protected string GetInputArgument(EncodingJob state)
{ {
var arg = "-i " + GetInputPathArgument(job); var arg = string.Format("-i {0}", GetInputPathArgument(state));
if (job.SubtitleStream != null) if (state.SubtitleStream != null)
{ {
if (job.SubtitleStream.IsExternal && !job.SubtitleStream.IsTextSubtitleStream) if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
{ {
arg += " -i \"" + job.SubtitleStream.Path + "\""; arg += " -i \"" + state.SubtitleStream.Path + "\"";
} }
} }
return arg; return arg.Trim();
} }
private string GetInputPathArgument(EncodingJob job) private string GetInputPathArgument(EncodingJob state)
{ {
var protocol = job.InputProtocol; var protocol = state.InputProtocol;
var mediaPath = state.MediaPath ?? string.Empty;
var inputPath = new[] { job.MediaPath }; var inputPath = new[] { mediaPath };
if (job.IsInputVideo) if (state.IsInputVideo)
{ {
if (!(job.VideoType == VideoType.Iso && job.IsoMount == null)) if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
{ {
inputPath = MediaEncoderHelpers.GetInputArgument(FileSystem, job.MediaPath, job.InputProtocol, job.IsoMount, job.PlayableStreamFileNames); inputPath = MediaEncoderHelpers.GetInputArgument(FileSystem, mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
} }
} }
@ -491,7 +500,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}, false, cancellationToken).ConfigureAwait(false); }, false, cancellationToken).ConfigureAwait(false);
AttachMediaStreamInfo(state, liveStreamResponse.MediaSource, state.Options); AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.Options);
if (state.IsVideoRequest) if (state.IsVideoRequest)
{ {
@ -505,11 +514,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
} }
private void AttachMediaStreamInfo(EncodingJob state, private void AttachMediaSourceInfo(EncodingJob state,
MediaSourceInfo mediaSource, MediaSourceInfo mediaSource,
EncodingJobOptions videoRequest) EncodingJobOptions videoRequest)
{ {
EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource, videoRequest); EncodingJobFactory.AttachMediaSourceInfo(state, mediaSource, videoRequest);
} }
/// <summary> /// <summary>
@ -572,7 +581,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
param = "-preset superfast"; param = "-preset superfast";
param += " -crf 28"; param += " -crf 23";
} }
else if (string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase)) else if (string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
@ -582,6 +591,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
param += " -crf 28"; param += " -crf 28";
} }
// h264 (h264_qsv)
else if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
param = "-preset 7 -look_ahead 0";
}
// h264 (libnvenc)
else if (string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
{
param = "-preset high-performance";
}
// webm // webm
else if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) else if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
{ {
@ -644,9 +666,53 @@ namespace MediaBrowser.MediaEncoding.Encoder
param += " -profile:v " + state.Options.Profile; param += " -profile:v " + state.Options.Profile;
} }
if (state.Options.Level.HasValue) var levelString = state.Options.Level.HasValue ? state.Options.Level.Value.ToString(CultureInfo.InvariantCulture) : null;
if (!string.IsNullOrEmpty(levelString))
{ {
param += " -level " + state.Options.Level.Value.ToString(UsCulture); var h264Encoder = EncodingJobFactory.GetH264Encoder(state, GetEncodingOptions());
// h264_qsv and libnvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
if (String.Equals(h264Encoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || String.Equals(h264Encoder, "libnvenc", StringComparison.OrdinalIgnoreCase))
{
switch (levelString)
{
case "30":
param += " -level 3";
break;
case "31":
param += " -level 3.1";
break;
case "32":
param += " -level 3.2";
break;
case "40":
param += " -level 4";
break;
case "41":
param += " -level 4.1";
break;
case "42":
param += " -level 4.2";
break;
case "50":
param += " -level 5";
break;
case "51":
param += " -level 5.1";
break;
case "52":
param += " -level 5.2";
break;
default:
param += " -level " + levelString;
break;
}
}
else
{
param += " -level " + levelString;
}
} }
return "-pix_fmt yuv420p " + param; return "-pix_fmt yuv420p " + param;
@ -658,15 +724,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (bitrate.HasValue) if (bitrate.HasValue)
{ {
var hasFixedResolution = state.Options.HasFixedResolution;
if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
{ {
if (hasFixedResolution)
{
return string.Format(" -minrate:v ({0}*.90) -maxrate:v ({0}*1.10) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture));
}
// With vpx when crf is used, b:v becomes a max rate // With vpx when crf is used, b:v becomes a max rate
// https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up. // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture)); return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
@ -677,20 +736,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture)); return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
} }
// H264 // h264
if (hasFixedResolution) if (isHls)
{ {
if (isHls) return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
{ bitrate.Value.ToString(UsCulture),
return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture)); (bitrate.Value * 2).ToString(UsCulture));
}
return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
} }
return string.Format(" -maxrate {0} -bufsize {1}", return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
bitrate.Value.ToString(UsCulture),
(bitrate.Value * 2).ToString(UsCulture));
} }
return string.Empty; return string.Empty;
@ -698,20 +752,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
protected double? GetFramerateParam(EncodingJob state) protected double? GetFramerateParam(EncodingJob state)
{ {
if (state.Options.Framerate.HasValue) if (state.Options != null)
{ {
return state.Options.Framerate.Value; if (state.Options.Framerate.HasValue)
}
var maxrate = state.Options.MaxFramerate;
if (maxrate.HasValue && state.VideoStream != null)
{
var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
if (contentRate.HasValue && contentRate.Value > maxrate.Value)
{ {
return maxrate; return state.Options.Framerate.Value;
}
var maxrate = state.Options.MaxFramerate;
if (maxrate.HasValue && state.VideoStream != null)
{
var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
if (contentRate.HasValue && contentRate.Value > maxrate.Value)
{
return maxrate;
}
} }
} }
@ -852,7 +909,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture); var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
filters.Add(string.Format("scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam)); filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
} }
// If a max height was requested // If a max height was requested
@ -863,6 +920,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam)); filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam));
} }
if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
if (filters.Count > 1)
{
//filters[filters.Count - 1] += ":flags=fast_bilinear";
}
}
var output = string.Empty; var output = string.Empty;
if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream) if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream)
@ -917,8 +982,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
seconds.ToString(UsCulture)); seconds.ToString(UsCulture));
} }
var mediaPath = state.MediaPath ?? string.Empty;
return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB", return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
MediaEncoder.EscapeSubtitleFilterPath(state.MediaPath), MediaEncoder.EscapeSubtitleFilterPath(mediaPath),
state.InternalSubtitleStreamOffset.ToString(UsCulture), state.InternalSubtitleStreamOffset.ToString(UsCulture),
seconds.ToString(UsCulture)); seconds.ToString(UsCulture));
} }

View file

@ -10,6 +10,7 @@ using MediaBrowser.Model.MediaInfo;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -66,38 +67,56 @@ namespace MediaBrowser.MediaEncoding.Encoder
? mediaSources.First() ? mediaSources.First()
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
AttachMediaStreamInfo(state, mediaSource, options); var videoRequest = state.Options;
state.OutputAudioBitrate = GetAudioBitrateParam(request, state.AudioStream); AttachMediaSourceInfo(state, mediaSource, videoRequest);
//var container = Path.GetExtension(state.RequestedUrl);
//if (string.IsNullOrEmpty(container))
//{
// container = request.Static ?
// state.InputContainer :
// (Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.');
//}
//state.OutputContainer = (container ?? string.Empty).TrimStart('.');
state.OutputAudioBitrate = GetAudioBitrateParam(state.Options, state.AudioStream);
state.OutputAudioSampleRate = request.AudioSampleRate; state.OutputAudioSampleRate = request.AudioSampleRate;
state.OutputAudioCodec = GetAudioCodec(request); state.OutputAudioCodec = state.Options.AudioCodec;
state.OutputAudioChannels = GetNumAudioChannelsParam(request, state.AudioStream, state.OutputAudioCodec); state.OutputAudioChannels = GetNumAudioChannelsParam(state.Options, state.AudioStream, state.OutputAudioCodec);
if (isVideoRequest) if (videoRequest != null)
{ {
state.OutputVideoCodec = GetVideoCodec(request); state.OutputVideoCodec = state.Options.VideoCodec;
state.OutputVideoBitrate = GetVideoBitrateParamValue(request, state.VideoStream); state.OutputVideoBitrate = GetVideoBitrateParamValue(state.Options, state.VideoStream);
if (state.OutputVideoBitrate.HasValue) if (state.OutputVideoBitrate.HasValue)
{ {
var resolution = ResolutionNormalizer.Normalize( var resolution = ResolutionNormalizer.Normalize(
state.VideoStream == null ? (int?)null : state.VideoStream.BitRate, state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
state.OutputVideoBitrate.Value, state.OutputVideoBitrate.Value,
state.VideoStream == null ? null : state.VideoStream.Codec, state.VideoStream == null ? null : state.VideoStream.Codec,
state.OutputVideoCodec, state.OutputVideoCodec,
request.MaxWidth, videoRequest.MaxWidth,
request.MaxHeight); videoRequest.MaxHeight);
request.MaxWidth = resolution.MaxWidth; videoRequest.MaxWidth = resolution.MaxWidth;
request.MaxHeight = resolution.MaxHeight; videoRequest.MaxHeight = resolution.MaxHeight;
} }
} }
ApplyDeviceProfileSettings(state); ApplyDeviceProfileSettings(state);
TryStreamCopy(state, request); if (videoRequest != null)
{
TryStreamCopy(state, videoRequest);
}
//state.OutputFilePath = GetOutputFilePath(state);
return state; return state;
} }
@ -119,7 +138,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
} }
internal static void AttachMediaStreamInfo(EncodingJob state, internal static void AttachMediaSourceInfo(EncodingJob state,
MediaSourceInfo mediaSource, MediaSourceInfo mediaSource,
EncodingJobOptions videoRequest) EncodingJobOptions videoRequest)
{ {
@ -131,11 +150,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.RunTimeTicks = mediaSource.RunTimeTicks; state.RunTimeTicks = mediaSource.RunTimeTicks;
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
if (mediaSource.ReadAtNativeFramerate)
{
state.ReadInputAtNativeFramerate = true;
}
if (mediaSource.VideoType.HasValue) if (mediaSource.VideoType.HasValue)
{ {
state.VideoType = mediaSource.VideoType.Value; state.VideoType = mediaSource.VideoType.Value;
@ -156,6 +170,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
state.InputBitrate = mediaSource.Bitrate; state.InputBitrate = mediaSource.Bitrate;
state.InputFileSize = mediaSource.Size; state.InputFileSize = mediaSource.Size;
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
if (state.ReadInputAtNativeFramerate || if (state.ReadInputAtNativeFramerate ||
mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)) mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
@ -165,6 +180,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.InputAudioSync = "1"; state.InputAudioSync = "1";
} }
if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase))
{
// Seeing some stuttering when transcoding wma to audio-only HLS
state.InputAudioSync = "1";
}
var mediaStreams = mediaSource.MediaStreams; var mediaStreams = mediaSource.MediaStreams;
if (videoRequest != null) if (videoRequest != null)
@ -210,19 +231,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <returns>System.Nullable{VideoCodecs}.</returns> /// <returns>System.Nullable{VideoCodecs}.</returns>
private static string InferVideoCodec(string container) private static string InferVideoCodec(string container)
{ {
if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase)) var ext = "." + (container ?? string.Empty);
if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase))
{ {
return "wmv"; return "wmv";
} }
if (string.Equals(container, "webm", StringComparison.OrdinalIgnoreCase)) if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
{ {
return "vpx"; return "vpx";
} }
if (string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase)) if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
{ {
return "theora"; return "theora";
} }
if (string.Equals(container, "m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase)) if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase))
{ {
return "h264"; return "h264";
} }
@ -232,35 +255,37 @@ namespace MediaBrowser.MediaEncoding.Encoder
private string InferAudioCodec(string container) private string InferAudioCodec(string container)
{ {
if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase)) var ext = "." + (container ?? string.Empty);
if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase))
{ {
return "mp3"; return "mp3";
} }
if (string.Equals(container, "aac", StringComparison.OrdinalIgnoreCase)) if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase))
{ {
return "aac"; return "aac";
} }
if (string.Equals(container, "wma", StringComparison.OrdinalIgnoreCase)) if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase))
{ {
return "wma"; return "wma";
} }
if (string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase)) if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase))
{ {
return "vorbis"; return "vorbis";
} }
if (string.Equals(container, "oga", StringComparison.OrdinalIgnoreCase)) if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase))
{ {
return "vorbis"; return "vorbis";
} }
if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase)) if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
{ {
return "vorbis"; return "vorbis";
} }
if (string.Equals(container, "webm", StringComparison.OrdinalIgnoreCase)) if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
{ {
return "vorbis"; return "vorbis";
} }
if (string.Equals(container, "webma", StringComparison.OrdinalIgnoreCase)) if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase))
{ {
return "vorbis"; return "vorbis";
} }
@ -398,15 +423,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (bitrate.HasValue) if (bitrate.HasValue)
{ {
var hasFixedResolution = state.Options.HasFixedResolution;
if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
{ {
if (hasFixedResolution)
{
return string.Format(" -minrate:v ({0}*.90) -maxrate:v ({0}*1.10) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture));
}
// With vpx when crf is used, b:v becomes a max rate // With vpx when crf is used, b:v becomes a max rate
// https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up. // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture)); return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
@ -417,20 +435,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture)); return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
} }
// H264 // h264
if (hasFixedResolution) if (isHls)
{ {
if (isHls) return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
{ bitrate.Value.ToString(UsCulture),
return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture)); (bitrate.Value * 2).ToString(UsCulture));
}
return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
} }
return string.Format(" -maxrate {0} -bufsize {1}", return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
bitrate.Value.ToString(UsCulture),
(bitrate.Value * 2).ToString(UsCulture));
} }
return string.Empty; return string.Empty;
@ -466,11 +479,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <summary> /// <summary>
/// Gets the name of the output audio codec /// Gets the name of the output audio codec
/// </summary> /// </summary>
/// <param name="request">The request.</param> /// <param name="state">The state.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
private string GetAudioCodec(EncodingJobOptions request) internal static string GetAudioEncoder(EncodingJob state)
{ {
var codec = request.AudioCodec; var codec = state.OutputAudioCodec;
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase)) if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
{ {
@ -489,40 +502,56 @@ namespace MediaBrowser.MediaEncoding.Encoder
return "wmav2"; return "wmav2";
} }
return (codec ?? string.Empty).ToLower(); return codec.ToLower();
} }
/// <summary> /// <summary>
/// Gets the name of the output video codec /// Gets the name of the output video codec
/// </summary> /// </summary>
/// <param name="request">The request.</param> /// <param name="state">The state.</param>
/// <param name="options">The options.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
private string GetVideoCodec(EncodingJobOptions request) internal static string GetVideoEncoder(EncodingJob state, EncodingOptions options)
{ {
var codec = request.VideoCodec; var codec = state.OutputVideoCodec;
if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) if (!string.IsNullOrEmpty(codec))
{ {
return "libx264"; if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
} {
if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) return GetH264Encoder(state, options);
{ }
return "libx265"; if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
} {
if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase)) return "libvpx";
{ }
return "libvpx"; if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
} {
if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase)) return "wmv2";
{ }
return "wmv2"; if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase))
} {
if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase)) return "libtheora";
{ }
return "libtheora";
return codec.ToLower();
} }
return (codec ?? string.Empty).ToLower(); return "copy";
}
internal static string GetH264Encoder(EncodingJob state, EncodingOptions options)
{
if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
// It's currently failing on live tv
if (state.RunTimeTicks.HasValue)
{
return "h264_qsv";
}
}
return "libx264";
} }
internal static bool CanStreamCopyVideo(EncodingJobOptions request, MediaStream videoStream) internal static bool CanStreamCopyVideo(EncodingJobOptions request, MediaStream videoStream)

View file

@ -21,14 +21,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
protected override string GetCommandLineArguments(EncodingJob state) protected override string GetCommandLineArguments(EncodingJob state)
{ {
// Get the output codec name // Get the output codec name
var videoCodec = state.OutputVideoCodec; var videoCodec = EncodingJobFactory.GetVideoEncoder(state, GetEncodingOptions());
var format = string.Empty; var format = string.Empty;
var keyFrame = string.Empty; var keyFrame = string.Empty;
if (string.Equals(Path.GetExtension(state.OutputFilePath), ".mp4", StringComparison.OrdinalIgnoreCase) && if (string.Equals(Path.GetExtension(state.OutputFilePath), ".mp4", StringComparison.OrdinalIgnoreCase))
state.Options.Context == EncodingContext.Streaming)
{ {
// Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
format = " -f mp4 -movflags frag_keyframe+empty_moov"; format = " -f mp4 -movflags frag_keyframe+empty_moov";
} }
@ -53,42 +53,49 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// Gets video arguments to pass to ffmpeg /// Gets video arguments to pass to ffmpeg
/// </summary> /// </summary>
/// <param name="state">The state.</param> /// <param name="state">The state.</param>
/// <param name="codec">The video codec.</param> /// <param name="videoCodec">The video codec.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
private string GetVideoArguments(EncodingJob state, string codec) private string GetVideoArguments(EncodingJob state, string videoCodec)
{ {
var args = "-codec:v:0 " + codec; var args = "-codec:v:0 " + videoCodec;
if (state.EnableMpegtsM2TsMode) if (state.EnableMpegtsM2TsMode)
{ {
args += " -mpegts_m2ts_mode 1"; args += " -mpegts_m2ts_mode 1";
} }
// See if we can save come cpu cycles by avoiding encoding var isOutputMkv = string.Equals(state.Options.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase);
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
if (state.RunTimeTicks.HasValue)
{ {
return state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) ? //args += " -copyts -avoid_negative_ts disabled -start_at_zero";
args + " -bsf:v h264_mp4toannexb" :
args;
} }
if (state.Options.Context == EncodingContext.Streaming) if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})", if (state.VideoStream != null && IsH264(state.VideoStream) &&
5.ToString(UsCulture)); (string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) || isOutputMkv))
{
args += " -bsf:v h264_mp4toannexb";
}
args += keyFrameArg; return args;
} }
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
5.ToString(UsCulture));
args += keyFrameArg;
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
// Add resolution params, if specified // Add resolution params, if specified
if (!hasGraphicalSubs) if (!hasGraphicalSubs)
{ {
args += GetOutputSizeParam(state, codec); args += GetOutputSizeParam(state, videoCodec);
} }
var qualityParam = GetVideoQualityParam(state, codec, false); var qualityParam = GetVideoQualityParam(state, videoCodec, false);
if (!string.IsNullOrEmpty(qualityParam)) if (!string.IsNullOrEmpty(qualityParam))
{ {
@ -98,7 +105,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// This is for internal graphical subs // This is for internal graphical subs
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
args += GetGraphicalSubtitleParam(state, codec); args += GetGraphicalSubtitleParam(state, videoCodec);
} }
return args; return args;
@ -118,11 +125,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
// Get the output codec name // Get the output codec name
var codec = state.OutputAudioCodec; var codec = EncodingJobFactory.GetAudioEncoder(state);
var args = "-codec:a:0 " + codec; var args = "-codec:a:0 " + codec;
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase)) if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
return args; return args;
} }