MpegDashService.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. using System.Security;
  2. using MediaBrowser.Common.IO;
  3. using MediaBrowser.Common.Net;
  4. using MediaBrowser.Controller.Channels;
  5. using MediaBrowser.Controller.Configuration;
  6. using MediaBrowser.Controller.Dlna;
  7. using MediaBrowser.Controller.Library;
  8. using MediaBrowser.Controller.LiveTv;
  9. using MediaBrowser.Controller.MediaEncoding;
  10. using MediaBrowser.Model.IO;
  11. using ServiceStack;
  12. using System;
  13. using System.Collections.Generic;
  14. using System.Globalization;
  15. using System.Text;
  16. using System.Threading;
  17. using System.Threading.Tasks;
  18. namespace MediaBrowser.Api.Playback.Hls
  19. {
  20. /// <summary>
  21. /// Options is needed for chromecast. Threw Head in there since it's related
  22. /// </summary>
  23. [Route("/Videos/{Id}/master.mpd", "GET", Summary = "Gets a video stream using Mpeg dash.")]
  24. [Route("/Videos/{Id}/master.mpd", "HEAD", Summary = "Gets a video stream using Mpeg dash.")]
  25. public class GetMasterManifest : VideoStreamRequest
  26. {
  27. public bool EnableAdaptiveBitrateStreaming { get; set; }
  28. public GetMasterManifest()
  29. {
  30. EnableAdaptiveBitrateStreaming = true;
  31. }
  32. }
  33. [Route("/Videos/{Id}/dash/{SegmentId}.ts", "GET")]
  34. public class GetDashSegment : VideoStreamRequest
  35. {
  36. /// <summary>
  37. /// Gets or sets the segment id.
  38. /// </summary>
  39. /// <value>The segment id.</value>
  40. public string SegmentId { get; set; }
  41. }
  42. public class MpegDashService : BaseHlsService
  43. {
  44. protected INetworkManager NetworkManager { get; private set; }
  45. public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, INetworkManager networkManager)
  46. : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder)
  47. {
  48. NetworkManager = networkManager;
  49. }
  50. public object Get(GetMasterManifest request)
  51. {
  52. var result = GetAsync(request, "GET").Result;
  53. return result;
  54. }
  55. public object Head(GetMasterManifest request)
  56. {
  57. var result = GetAsync(request, "HEAD").Result;
  58. return result;
  59. }
  60. private async Task<object> GetAsync(GetMasterManifest request, string method)
  61. {
  62. if (string.Equals(request.AudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
  63. {
  64. throw new ArgumentException("Audio codec copy is not allowed here.");
  65. }
  66. if (string.Equals(request.VideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  67. {
  68. throw new ArgumentException("Video codec copy is not allowed here.");
  69. }
  70. if (string.IsNullOrEmpty(request.MediaSourceId))
  71. {
  72. throw new ArgumentException("MediaSourceId is required");
  73. }
  74. var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
  75. var playlistText = string.Empty;
  76. if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase))
  77. {
  78. playlistText = GetManifestText(state);
  79. }
  80. return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.mpd"), new Dictionary<string, string>());
  81. }
  82. private string GetManifestText(StreamState state)
  83. {
  84. var audioBitrate = state.OutputAudioBitrate ?? 0;
  85. var videoBitrate = state.OutputVideoBitrate ?? 0;
  86. var builder = new StringBuilder();
  87. var duration = "PT0H02M11.00S";
  88. builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  89. builder.AppendFormat(
  90. "<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:mpeg:dash:schema:mpd:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" minBufferTime=\"PT2.00S\" mediaPresentationDuration=\"{0}\" maxSegmentDuration=\"PT{1}S\" type=\"static\" profiles=\"urn:mpeg:dash:profile:mp2t-simple:2011\">",
  91. duration,
  92. state.SegmentLength.ToString(CultureInfo.InvariantCulture));
  93. builder.Append("<ProgramInformation moreInformationURL=\"http://gpac.sourceforge.net\">");
  94. builder.Append("</ProgramInformation>");
  95. builder.AppendFormat("<Period start=\"PT0S\" duration=\"{0}\">", duration);
  96. builder.Append("<AdaptationSet segmentAlignment=\"true\">");
  97. builder.Append("<ContentComponent id=\"1\" contentType=\"video\"/>");
  98. var lang = state.AudioStream != null ? state.AudioStream.Language : null;
  99. if (string.IsNullOrWhiteSpace(lang)) lang = "und";
  100. builder.AppendFormat("<ContentComponent id=\"2\" contentType=\"audio\" lang=\"{0}\"/>", lang);
  101. builder.Append(GetRepresentationOpenElement(state, lang));
  102. AppendSegmentList(state, builder);
  103. builder.Append("</Representation>");
  104. builder.Append("</AdaptationSet>");
  105. builder.Append("</Period>");
  106. builder.Append("</MPD>");
  107. return builder.ToString();
  108. }
  109. private string GetRepresentationOpenElement(StreamState state, string language)
  110. {
  111. var codecs = GetVideoCodecDescriptor(state) + "," + GetAudioCodecDescriptor(state);
  112. var xml = "<Representation id=\"1\" mimeType=\"video/mp2t\" startWithSAP=\"1\" codecs=\"" + codecs + "\"";
  113. if (state.OutputWidth.HasValue)
  114. {
  115. xml += " width=\"" + state.OutputWidth.Value.ToString(UsCulture) + "\"";
  116. }
  117. if (state.OutputHeight.HasValue)
  118. {
  119. xml += " height=\"" + state.OutputHeight.Value.ToString(UsCulture) + "\"";
  120. }
  121. if (state.OutputAudioSampleRate.HasValue)
  122. {
  123. xml += " sampleRate=\"" + state.OutputAudioSampleRate.Value.ToString(UsCulture) + "\"";
  124. }
  125. if (state.TotalOutputBitrate.HasValue)
  126. {
  127. xml += " bandwidth=\"" + state.TotalOutputBitrate.Value.ToString(UsCulture) + "\"";
  128. }
  129. xml += ">";
  130. return xml;
  131. }
  132. private string GetVideoCodecDescriptor(StreamState state)
  133. {
  134. // https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
  135. var level = state.TargetVideoLevel ?? 0;
  136. var profile = state.TargetVideoProfile ?? string.Empty;
  137. if (profile.IndexOf("high", StringComparison.OrdinalIgnoreCase) != -1)
  138. {
  139. if (level >= 4.1)
  140. {
  141. return "avc1.640028";
  142. }
  143. if (level >= 4)
  144. {
  145. return "avc1.640028";
  146. }
  147. return "avc1.64001f";
  148. }
  149. if (profile.IndexOf("main", StringComparison.OrdinalIgnoreCase) != -1)
  150. {
  151. if (level >= 4)
  152. {
  153. return "avc1.4d0028";
  154. }
  155. if (level >= 3.1)
  156. {
  157. return "avc1.4d001f";
  158. }
  159. return "avc1.4d001e";
  160. }
  161. if (level >= 3.1)
  162. {
  163. return "avc1.42001f";
  164. }
  165. return "avc1.42001e";
  166. }
  167. private string GetAudioCodecDescriptor(StreamState state)
  168. {
  169. // https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
  170. if (string.Equals(state.OutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
  171. {
  172. return "mp4a.40.34";
  173. }
  174. // AAC 5ch
  175. if (state.OutputAudioChannels.HasValue && state.OutputAudioChannels.Value >= 5)
  176. {
  177. return "mp4a.40.5";
  178. }
  179. // AAC 2ch
  180. return "mp4a.40.2";
  181. }
  182. public object Get(GetDashSegment request)
  183. {
  184. return null;
  185. }
  186. private void AppendSegmentList(StreamState state, StringBuilder builder)
  187. {
  188. var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds;
  189. builder.Append("<SegmentList timescale=\"1000\" duration=\"10000\">");
  190. var queryStringIndex = Request.RawUrl.IndexOf('?');
  191. var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
  192. var index = 0;
  193. while (seconds > 0)
  194. {
  195. builder.AppendFormat("<SegmentURL media=\"{0}.ts{1}\"/>", index.ToString(UsCulture), SecurityElement.Escape(queryString));
  196. seconds -= state.SegmentLength;
  197. index++;
  198. }
  199. builder.Append("</SegmentList>");
  200. }
  201. protected override string GetAudioArguments(StreamState state)
  202. {
  203. var codec = state.OutputAudioCodec;
  204. if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
  205. {
  206. return "-codec:a:0 copy";
  207. }
  208. var args = "-codec:a:0 " + codec;
  209. var channels = state.OutputAudioChannels;
  210. if (channels.HasValue)
  211. {
  212. args += " -ac " + channels.Value;
  213. }
  214. var bitrate = state.OutputAudioBitrate;
  215. if (bitrate.HasValue)
  216. {
  217. args += " -ab " + bitrate.Value.ToString(UsCulture);
  218. }
  219. args += " " + GetAudioFilterParam(state, true);
  220. return args;
  221. }
  222. protected override string GetVideoArguments(StreamState state)
  223. {
  224. var codec = state.OutputVideoCodec;
  225. // See if we can save come cpu cycles by avoiding encoding
  226. if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
  227. {
  228. return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf:v h264_mp4toannexb" : "-codec:v:0 copy";
  229. }
  230. var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
  231. state.SegmentLength.ToString(UsCulture));
  232. var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
  233. var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264", true) + keyFrameArg;
  234. // Add resolution params, if specified
  235. if (!hasGraphicalSubs)
  236. {
  237. args += GetOutputSizeParam(state, codec, false);
  238. }
  239. // This is for internal graphical subs
  240. if (hasGraphicalSubs)
  241. {
  242. args += GetGraphicalSubtitleParam(state, codec);
  243. }
  244. return args;
  245. }
  246. protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
  247. {
  248. var threads = GetNumberOfThreads(state, false);
  249. var inputModifier = GetInputModifier(state);
  250. // If isEncoding is true we're actually starting ffmpeg
  251. var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0";
  252. var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
  253. inputModifier,
  254. GetInputArgument(transcodingJobId, state),
  255. threads,
  256. GetMapArgs(state),
  257. GetVideoArguments(state),
  258. GetAudioArguments(state),
  259. state.SegmentLength.ToString(UsCulture),
  260. startNumberParam,
  261. state.HlsListSize.ToString(UsCulture),
  262. outputPath
  263. ).Trim();
  264. return args;
  265. }
  266. /// <summary>
  267. /// Gets the segment file extension.
  268. /// </summary>
  269. /// <param name="state">The state.</param>
  270. /// <returns>System.String.</returns>
  271. protected override string GetSegmentFileExtension(StreamState state)
  272. {
  273. return ".ts";
  274. }
  275. protected override TranscodingJobType TranscodingJobType
  276. {
  277. get
  278. {
  279. return TranscodingJobType.Dash;
  280. }
  281. }
  282. }
  283. }