VideoHlsService.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. using System.IO;
  2. using System.Linq;
  3. using MediaBrowser.Common.IO;
  4. using MediaBrowser.Common.MediaInfo;
  5. using MediaBrowser.Controller;
  6. using MediaBrowser.Controller.Library;
  7. using System;
  8. using ServiceStack.ServiceHost;
  9. namespace MediaBrowser.Api.Playback.Hls
  10. {
  11. /// <summary>
  12. /// Class GetHlsVideoStream
  13. /// </summary>
  14. [Route("/Videos/{Id}/stream.m3u8", "GET")]
  15. [Api(Description = "Gets a video stream using HTTP live streaming.")]
  16. public class GetHlsVideoStream : VideoStreamRequest
  17. {
  18. }
  19. /// <summary>
  20. /// Class GetHlsVideoSegment
  21. /// </summary>
  22. [Route("/Videos/{Id}/segments/{SegmentId}/stream.ts", "GET")]
  23. [Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
  24. public class GetHlsVideoSegment
  25. {
  26. /// <summary>
  27. /// Gets or sets the id.
  28. /// </summary>
  29. /// <value>The id.</value>
  30. public string Id { get; set; }
  31. /// <summary>
  32. /// Gets or sets the segment id.
  33. /// </summary>
  34. /// <value>The segment id.</value>
  35. public string SegmentId { get; set; }
  36. }
  37. /// <summary>
  38. /// Class VideoHlsService
  39. /// </summary>
  40. public class VideoHlsService : BaseHlsService
  41. {
  42. /// <summary>
  43. /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
  44. /// </summary>
  45. /// <param name="appPaths">The app paths.</param>
  46. /// <param name="userManager">The user manager.</param>
  47. /// <param name="libraryManager">The library manager.</param>
  48. /// <param name="isoManager">The iso manager.</param>
  49. /// <param name="mediaEncoder">The media encoder.</param>
  50. public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
  51. : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
  52. {
  53. }
  54. /// <summary>
  55. /// Gets the specified request.
  56. /// </summary>
  57. /// <param name="request">The request.</param>
  58. /// <returns>System.Object.</returns>
  59. public object Get(GetHlsVideoSegment request)
  60. {
  61. foreach (var playlist in Directory.EnumerateFiles(ApplicationPaths.EncodedMediaCachePath, "*.m3u8").ToList())
  62. {
  63. ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
  64. }
  65. var file = SegmentFilePrefix + request.SegmentId + Path.GetExtension(RequestContext.PathInfo);
  66. file = Path.Combine(ApplicationPaths.EncodedMediaCachePath, file);
  67. return ResultFactory.GetStaticFileResult(RequestContext, file);
  68. }
  69. /// <summary>
  70. /// Gets the specified request.
  71. /// </summary>
  72. /// <param name="request">The request.</param>
  73. /// <returns>System.Object.</returns>
  74. public object Get(GetHlsVideoStream request)
  75. {
  76. return ProcessRequest(request);
  77. }
  78. /// <summary>
  79. /// Gets the audio arguments.
  80. /// </summary>
  81. /// <param name="state">The state.</param>
  82. /// <returns>System.String.</returns>
  83. protected override string GetAudioArguments(StreamState state)
  84. {
  85. var codec = GetAudioCodec(state.Request);
  86. if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
  87. {
  88. return "-codec:a:0 copy";
  89. }
  90. var args = "-codec:a:0 " + codec;
  91. if (state.AudioStream != null)
  92. {
  93. if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
  94. {
  95. args += " -strict experimental";
  96. }
  97. var channels = GetNumAudioChannelsParam(state.Request, state.AudioStream);
  98. if (channels.HasValue)
  99. {
  100. args += " -ac " + channels.Value;
  101. }
  102. if (state.Request.AudioSampleRate.HasValue)
  103. {
  104. args += " -ar " + state.Request.AudioSampleRate.Value;
  105. }
  106. if (state.Request.AudioBitRate.HasValue)
  107. {
  108. args += " -ab " + state.Request.AudioBitRate.Value;
  109. }
  110. var volParam = string.Empty;
  111. // Boost volume to 200% when downsampling from 6ch to 2ch
  112. if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
  113. {
  114. volParam = ",volume=2.000000";
  115. }
  116. args += string.Format(" -af \"aresample=async=1000{0}\"", volParam);
  117. return args;
  118. }
  119. return args;
  120. }
  121. /// <summary>
  122. /// Gets the video arguments.
  123. /// </summary>
  124. /// <param name="state">The state.</param>
  125. /// <param name="performSubtitleConversion">if set to <c>true</c> [perform subtitle conversion].</param>
  126. /// <returns>System.String.</returns>
  127. protected override string GetVideoArguments(StreamState state, bool performSubtitleConversion)
  128. {
  129. var codec = GetVideoCodec(state.VideoRequest);
  130. // See if we can save come cpu cycles by avoiding encoding
  131. if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
  132. {
  133. return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf h264_mp4toannexb" : "-codec:v:0 copy";
  134. }
  135. const string keyFrameArg = " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,0),gte(t,prev_forced_t+5))";
  136. var args = "-codec:v:0 " + codec + " -preset superfast" + keyFrameArg;
  137. if (state.VideoRequest.VideoBitRate.HasValue)
  138. {
  139. // Make sure we don't request a bitrate higher than the source
  140. var currentBitrate = state.VideoStream == null ? state.VideoRequest.VideoBitRate.Value : state.VideoStream.BitRate ?? state.VideoRequest.VideoBitRate.Value;
  141. var bitrate = Math.Min(currentBitrate, state.VideoRequest.VideoBitRate.Value);
  142. args += string.Format(" -b:v {0}", bitrate);
  143. }
  144. // Add resolution params, if specified
  145. if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue)
  146. {
  147. args += GetOutputSizeParam(state, codec, performSubtitleConversion);
  148. }
  149. // Get the output framerate based on the FrameRate param
  150. var framerate = state.VideoRequest.Framerate ?? 0;
  151. // We have to supply a framerate for hls, so if it's null, account for that here
  152. if (framerate.Equals(0))
  153. {
  154. framerate = state.VideoStream.AverageFrameRate ?? 0;
  155. }
  156. if (framerate.Equals(0))
  157. {
  158. framerate = state.VideoStream.RealFrameRate ?? 0;
  159. }
  160. if (framerate.Equals(0))
  161. {
  162. framerate = 23.976;
  163. }
  164. framerate = Math.Round(framerate);
  165. args += string.Format(" -r {0}", framerate);
  166. args += " -vsync vfr";
  167. if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
  168. {
  169. args += " -profile:v " + state.VideoRequest.Profile;
  170. }
  171. if (!string.IsNullOrEmpty(state.VideoRequest.Level))
  172. {
  173. args += " -level " + state.VideoRequest.Level;
  174. }
  175. if (state.SubtitleStream != null)
  176. {
  177. // This is for internal graphical subs
  178. if (!state.SubtitleStream.IsExternal && (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 || state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1))
  179. {
  180. args += GetInternalGraphicalSubtitleParam(state, codec);
  181. }
  182. }
  183. return args;
  184. }
  185. /// <summary>
  186. /// Gets the segment file extension.
  187. /// </summary>
  188. /// <param name="state">The state.</param>
  189. /// <returns>System.String.</returns>
  190. protected override string GetSegmentFileExtension(StreamState state)
  191. {
  192. return ".ts";
  193. }
  194. }
  195. }