From 53f4e849fd13d815ea50b18647a1abe44be07afb Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 15 Dec 2013 14:39:21 -0500 Subject: [PATCH] #642 - Add server-side encoding settings --- .../Playback/BaseStreamingService.cs | 85 +++++++++++++++++-- .../Playback/Hls/AudioHlsService.cs | 6 +- .../Playback/Hls/BaseHlsService.cs | 16 ++-- .../Playback/Hls/VideoHlsService.cs | 8 +- .../Playback/Progressive/AudioService.cs | 15 ++-- .../BaseProgressiveStreamingService.cs | 8 +- .../Playback/Progressive/VideoService.cs | 80 +++++------------ .../Configuration/ServerConfiguration.cs | 14 +++ 8 files changed, 146 insertions(+), 86 deletions(-) diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 5bdf39eef8..486221bc6a 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -2,12 +2,14 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -32,7 +34,7 @@ namespace MediaBrowser.Api.Playback /// Gets or sets the application paths. /// /// The application paths. - protected IServerApplicationPaths ApplicationPaths { get; private set; } + protected IServerConfigurationManager ServerConfigurationManager { get; private set; } /// /// Gets or sets the user manager. @@ -66,17 +68,20 @@ namespace MediaBrowser.Api.Playback /// /// Initializes a new instance of the class. /// - /// The app paths. + /// The server configuration. /// The user manager. /// The library manager. /// The iso manager. /// The media encoder. - protected BaseStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository) + /// The dto service. + /// The file system. + /// The item repository. + protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository) { ItemRepository = itemRepository; FileSystem = fileSystem; DtoService = dtoService; - ApplicationPaths = appPaths; + ServerConfigurationManager = serverConfig; UserManager = userManager; LibraryManager = libraryManager; IsoManager = isoManager; @@ -115,7 +120,7 @@ namespace MediaBrowser.Api.Playback /// System.String. protected virtual string GetOutputFilePath(StreamState state) { - var folder = ApplicationPaths.EncodedMediaCachePath; + var folder = ServerConfigurationManager.ApplicationPaths.EncodedMediaCachePath; var outputFileExtension = GetOutputFileExtension(state); @@ -246,6 +251,74 @@ namespace MediaBrowser.Api.Playback return returnFirstIfNoIndex ? streams.FirstOrDefault() : null; } + /// + /// Gets the number of threads. + /// + /// System.Int32. + /// Unrecognized EncodingQuality value. + protected int GetNumberOfThreads() + { + var quality = ServerConfigurationManager.Configuration.EncodingQuality; + + if (quality == EncodingQuality.Auto) + { + var cpuCount = Environment.ProcessorCount; + + if (cpuCount >= 4) + { + return 0; + } + + return cpuCount; + } + + switch (quality) + { + case EncodingQuality.HighSpeed: + return 2; + case EncodingQuality.HighQuality: + return 2; + case EncodingQuality.MaxQuality: + return 0; + default: + throw new Exception("Unrecognized EncodingQuality value."); + } + } + + /// + /// Gets the video bitrate to specify on the command line + /// + /// The state. + /// The video codec. + /// System.String. + protected string GetVideoQualityParam(StreamState state, string videoCodec) + { + var args = string.Empty; + + // webm + if (videoCodec.Equals("libvpx", StringComparison.OrdinalIgnoreCase)) + { + args = "-speed 16 -quality good -profile:v 0 -slices 8"; + } + + // asf/wmv + else if (videoCodec.Equals("wmv2", StringComparison.OrdinalIgnoreCase)) + { + args = "-g 100 -qmax 15"; + } + + else if (videoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase)) + { + args = "-preset superfast"; + } + else if (videoCodec.Equals("mpeg4", StringComparison.OrdinalIgnoreCase)) + { + args = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2"; + } + + return args.Trim(); + } + /// /// If we're going to put a fixed size on the command line, this will calculate it /// @@ -656,7 +729,7 @@ namespace MediaBrowser.Api.Playback Logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments); - var logFilePath = Path.Combine(ApplicationPaths.LogDirectoryPath, "ffmpeg-" + Guid.NewGuid() + ".txt"); + var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, "ffmpeg-" + Guid.NewGuid() + ".txt"); // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true); diff --git a/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs b/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs index efcc3f07ab..fc36d6ea83 100644 --- a/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs @@ -1,6 +1,6 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; -using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; @@ -26,8 +26,8 @@ namespace MediaBrowser.Api.Playback.Hls /// public class AudioHlsService : BaseHlsService { - public AudioHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository) - : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository) + public AudioHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository) + : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository) { } diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 1e5e8b82d9..abe82ab776 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -2,7 +2,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; using MediaBrowser.Common.Net; -using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -22,14 +22,14 @@ namespace MediaBrowser.Api.Playback.Hls /// public abstract class BaseHlsService : BaseStreamingService { - protected BaseHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository) - : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository) + protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository) + : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository) { } protected override string GetOutputFilePath(StreamState state) { - var folder = ApplicationPaths.EncodedMediaCachePath; + var folder = ServerConfigurationManager.ApplicationPaths.EncodedMediaCachePath; var outputFileExtension = GetOutputFileExtension(state); @@ -257,13 +257,16 @@ namespace MediaBrowser.Api.Playback.Hls var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds); - var args = string.Format("{0}{1} {2} {3} -i {4}{5} -threads 0 {6} {7} -sc_threshold 0 {8} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{9}\"", + var threads = GetNumberOfThreads(); + + var args = string.Format("{0}{1} {2} {3} -i {4}{5} -threads {6} {7} {8} -sc_threshold 0 {9} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{10}\"", itsOffset, probeSize, GetUserAgentParam(state.Item), GetFastSeekCommandLineParameter(state.Request), GetInputArgument(state.Item, state.IsoMount), GetSlowSeekCommandLineParameter(state.Request), + threads, GetMapArgs(state), GetVideoArguments(state, performSubtitleConversions), GetAudioArguments(state), @@ -278,7 +281,8 @@ namespace MediaBrowser.Api.Playback.Hls var bitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? 64000; - var lowBitrateParams = string.Format(" -threads 0 -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{0}\"", + var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {2} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{1}\"", + threads, lowBitratePath, bitrate / 2); diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 9d335d2d3b..daefe9bc9f 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -1,6 +1,6 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; -using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; @@ -32,8 +32,8 @@ namespace MediaBrowser.Api.Playback.Hls /// public class VideoHlsService : BaseHlsService { - public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository) - : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository) + public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository) + : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository) { } @@ -123,7 +123,7 @@ namespace MediaBrowser.Api.Playback.Hls (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 || state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1); - var args = "-codec:v:0 " + codec + " -preset superfast" + keyFrameArg; + var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264") + keyFrameArg; var bitrate = GetVideoBitrateParam(state); diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 86ab498f65..dcd0bc036d 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -1,6 +1,6 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; -using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; @@ -41,8 +41,8 @@ namespace MediaBrowser.Api.Playback.Progressive /// public class AudioService : BaseProgressiveStreamingService { - public AudioService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem) - : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, itemRepo, dtoService, imageProcessor, fileSystem) + public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, IImageProcessor imageProcessor) + : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, imageProcessor) { } @@ -101,13 +101,16 @@ namespace MediaBrowser.Api.Playback.Progressive const string vn = " -vn"; - return string.Format("{0} -i {1}{2} -threads 0{5} {3} -id3v2_version 3 -write_id3v1 1 \"{4}\"", + var threads = GetNumberOfThreads(); + + return string.Format("{0} -i {1}{2} -threads {3}{4} {5} -id3v2_version 3 -write_id3v1 1 \"{6}\"", GetFastSeekCommandLineParameter(request), GetInputArgument(state.Item, state.IsoMount), GetSlowSeekCommandLineParameter(request), + threads, + vn, string.Join(" ", audioTranscodeParams.ToArray()), - outputPath, - vn).Trim(); + outputPath).Trim(); } } } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 1fea322195..46836a04ce 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -2,7 +2,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; using MediaBrowser.Common.Net; -using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -26,8 +26,8 @@ namespace MediaBrowser.Api.Playback.Progressive { protected readonly IImageProcessor ImageProcessor; - protected BaseProgressiveStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepository, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem) : - base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository) + protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, IImageProcessor imageProcessor) + : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository) { ImageProcessor = imageProcessor; } @@ -304,7 +304,7 @@ namespace MediaBrowser.Api.Playback.Progressive } } - return new ImageService(UserManager, LibraryManager, ApplicationPaths, null, ItemRepository, DtoService, ImageProcessor, null) + return new ImageService(UserManager, LibraryManager, ServerConfigurationManager.ApplicationPaths, null, ItemRepository, DtoService, ImageProcessor, null) { Logger = Logger, Request = Request, diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 40c7492ffb..27af9e1820 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -1,6 +1,6 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; -using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -55,8 +55,8 @@ namespace MediaBrowser.Api.Playback.Progressive /// public class VideoService : BaseProgressiveStreamingService { - public VideoService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem) - : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, itemRepo, dtoService, imageProcessor, fileSystem) + public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, IImageProcessor imageProcessor) + : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, imageProcessor) { } @@ -104,7 +104,7 @@ namespace MediaBrowser.Api.Playback.Progressive format = " -f mp4 -movflags frag_keyframe+empty_moov"; } - var threads = string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase) ? 2 : 0; + var threads = string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase) ? 2 : GetNumberOfThreads(); return string.Format("{0} {1} {2} -i {3}{4}{5} {6} {7} -threads {8} {9}{10} \"{11}\"", probeSize, @@ -165,9 +165,16 @@ namespace MediaBrowser.Api.Playback.Progressive var qualityParam = GetVideoQualityParam(state, codec); + var bitrate = GetVideoBitrateParam(state); + + if (bitrate.HasValue) + { + qualityParam += string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture)); + } + if (!string.IsNullOrEmpty(qualityParam)) { - args += " " + qualityParam; + args += " " + qualityParam.Trim(); } args += " -vsync vfr"; @@ -213,9 +220,9 @@ namespace MediaBrowser.Api.Playback.Progressive { return "-acodec copy"; } - + var args = "-acodec " + codec; - + // Add the number of audio channels var channels = GetNumAudioChannelsParam(request, state.AudioStream); @@ -231,64 +238,23 @@ namespace MediaBrowser.Api.Playback.Progressive args += " -ab " + bitrate.Value.ToString(UsCulture); } - var volParam = string.Empty; - var AudioSampleRate = string.Empty; + var volParam = string.Empty; + var AudioSampleRate = string.Empty; - // Boost volume to 200% when downsampling from 6ch to 2ch - if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5) - { - volParam = ",volume=2.000000"; - } - - if (state.Request.AudioSampleRate.HasValue) - { - AudioSampleRate= state.Request.AudioSampleRate.Value + ":"; - } - - args += string.Format(" -af \"aresample={0}async=1000{1}\"",AudioSampleRate, volParam); - - return args; - } - - /// - /// Gets the video bitrate to specify on the command line - /// - /// The state. - /// The video codec. - /// System.String. - private string GetVideoQualityParam(StreamState state, string videoCodec) - { - var args = string.Empty; - - // webm - if (videoCodec.Equals("libvpx", StringComparison.OrdinalIgnoreCase)) - { - args = "-speed 16 -quality good -profile:v 0 -slices 8"; - } - - // asf/wmv - else if (videoCodec.Equals("wmv2", StringComparison.OrdinalIgnoreCase)) + // Boost volume to 200% when downsampling from 6ch to 2ch + if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5) { - args = "-g 100 -qmax 15"; + volParam = ",volume=2.000000"; } - else if (videoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase)) + if (state.Request.AudioSampleRate.HasValue) { - args = "-preset superfast"; + AudioSampleRate = state.Request.AudioSampleRate.Value + ":"; } - else if (videoCodec.Equals("mpeg4", StringComparison.OrdinalIgnoreCase)) - { - args = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2"; - } - - var bitrate = GetVideoBitrateParam(state); - if (bitrate.HasValue) - { - args += string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture)); - } + args += string.Format(" -af \"aresample={0}async=1000{1}\"", AudioSampleRate, volParam); - return args.Trim(); + return args; } } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 1f75969be7..45d5fa01cb 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -234,6 +234,12 @@ namespace MediaBrowser.Model.Configuration /// true if [enable people prefix sub folders]; otherwise, false. public bool EnablePeoplePrefixSubFolders { get; set; } + /// + /// Gets or sets the encoding quality. + /// + /// The encoding quality. + public EncodingQuality EncodingQuality { get; set; } + /// /// Initializes a new instance of the class. /// @@ -293,4 +299,12 @@ namespace MediaBrowser.Model.Configuration Legacy, Compatible } + + public enum EncodingQuality + { + Auto, + HighSpeed, + HighQuality, + MaxQuality + } }