2
0

BaseStreamingService.cs 109 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855
  1. using MediaBrowser.Common.Extensions;
  2. using MediaBrowser.Controller.Configuration;
  3. using MediaBrowser.Controller.Devices;
  4. using MediaBrowser.Controller.Dlna;
  5. using MediaBrowser.Controller.Library;
  6. using MediaBrowser.Controller.MediaEncoding;
  7. using MediaBrowser.Model.Dlna;
  8. using MediaBrowser.Model.Dto;
  9. using MediaBrowser.Model.Entities;
  10. using MediaBrowser.Model.Extensions;
  11. using MediaBrowser.Model.IO;
  12. using MediaBrowser.Model.MediaInfo;
  13. using MediaBrowser.Model.Serialization;
  14. using System;
  15. using System.Collections.Generic;
  16. using System.Globalization;
  17. using System.IO;
  18. using System.Linq;
  19. using System.Text;
  20. using System.Threading;
  21. using System.Threading.Tasks;
  22. using MediaBrowser.Common.Net;
  23. using MediaBrowser.Controller;
  24. using MediaBrowser.Controller.Net;
  25. using MediaBrowser.Model.Diagnostics;
  26. namespace MediaBrowser.Api.Playback
  27. {
  28. /// <summary>
  29. /// Class BaseStreamingService
  30. /// </summary>
  31. public abstract class BaseStreamingService : BaseApiService
  32. {
  33. /// <summary>
  34. /// Gets or sets the application paths.
  35. /// </summary>
  36. /// <value>The application paths.</value>
  37. protected IServerConfigurationManager ServerConfigurationManager { get; private set; }
  38. /// <summary>
  39. /// Gets or sets the user manager.
  40. /// </summary>
  41. /// <value>The user manager.</value>
  42. protected IUserManager UserManager { get; private set; }
  43. /// <summary>
  44. /// Gets or sets the library manager.
  45. /// </summary>
  46. /// <value>The library manager.</value>
  47. protected ILibraryManager LibraryManager { get; private set; }
  48. /// <summary>
  49. /// Gets or sets the iso manager.
  50. /// </summary>
  51. /// <value>The iso manager.</value>
  52. protected IIsoManager IsoManager { get; private set; }
  53. /// <summary>
  54. /// Gets or sets the media encoder.
  55. /// </summary>
  56. /// <value>The media encoder.</value>
  57. protected IMediaEncoder MediaEncoder { get; private set; }
  58. protected IFileSystem FileSystem { get; private set; }
  59. protected IDlnaManager DlnaManager { get; private set; }
  60. protected IDeviceManager DeviceManager { get; private set; }
  61. protected ISubtitleEncoder SubtitleEncoder { get; private set; }
  62. protected IMediaSourceManager MediaSourceManager { get; private set; }
  63. protected IZipClient ZipClient { get; private set; }
  64. protected IJsonSerializer JsonSerializer { get; private set; }
  65. public static IServerApplicationHost AppHost;
  66. public static IHttpClient HttpClient;
  67. protected IAuthorizationContext AuthorizationContext { get; private set; }
  68. /// <summary>
  69. /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
  70. /// </summary>
  71. protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext)
  72. {
  73. JsonSerializer = jsonSerializer;
  74. AuthorizationContext = authorizationContext;
  75. ZipClient = zipClient;
  76. MediaSourceManager = mediaSourceManager;
  77. DeviceManager = deviceManager;
  78. SubtitleEncoder = subtitleEncoder;
  79. DlnaManager = dlnaManager;
  80. FileSystem = fileSystem;
  81. ServerConfigurationManager = serverConfig;
  82. UserManager = userManager;
  83. LibraryManager = libraryManager;
  84. IsoManager = isoManager;
  85. MediaEncoder = mediaEncoder;
  86. }
  87. /// <summary>
  88. /// Gets the command line arguments.
  89. /// </summary>
  90. /// <param name="outputPath">The output path.</param>
  91. /// <param name="state">The state.</param>
  92. /// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
  93. /// <returns>System.String.</returns>
  94. protected abstract string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding);
  95. /// <summary>
  96. /// Gets the type of the transcoding job.
  97. /// </summary>
  98. /// <value>The type of the transcoding job.</value>
  99. protected abstract TranscodingJobType TranscodingJobType { get; }
  100. /// <summary>
  101. /// Gets the output file extension.
  102. /// </summary>
  103. /// <param name="state">The state.</param>
  104. /// <returns>System.String.</returns>
  105. protected virtual string GetOutputFileExtension(StreamState state)
  106. {
  107. return Path.GetExtension(state.RequestedUrl);
  108. }
  109. /// <summary>
  110. /// Gets the output file path.
  111. /// </summary>
  112. /// <param name="state">The state.</param>
  113. /// <returns>System.String.</returns>
  114. private string GetOutputFilePath(StreamState state)
  115. {
  116. var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
  117. var outputFileExtension = GetOutputFileExtension(state);
  118. var data = GetCommandLineArguments("dummy\\dummy", state, false);
  119. data += "-" + (state.Request.DeviceId ?? string.Empty);
  120. data += "-" + (state.Request.PlaySessionId ?? string.Empty);
  121. var dataHash = data.GetMD5().ToString("N");
  122. if (EnableOutputInSubFolder)
  123. {
  124. return Path.Combine(folder, dataHash, dataHash + (outputFileExtension ?? string.Empty).ToLower());
  125. }
  126. return Path.Combine(folder, dataHash + (outputFileExtension ?? string.Empty).ToLower());
  127. }
  128. protected virtual bool EnableOutputInSubFolder
  129. {
  130. get { return false; }
  131. }
  132. protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
  133. /// <summary>
  134. /// Gets the fast seek command line parameter.
  135. /// </summary>
  136. /// <param name="request">The request.</param>
  137. /// <returns>System.String.</returns>
  138. /// <value>The fast seek command line parameter.</value>
  139. protected string GetFastSeekCommandLineParameter(StreamRequest request)
  140. {
  141. var time = request.StartTimeTicks ?? 0;
  142. if (time > 0)
  143. {
  144. return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time));
  145. }
  146. return string.Empty;
  147. }
  148. /// <summary>
  149. /// Gets the map args.
  150. /// </summary>
  151. /// <param name="state">The state.</param>
  152. /// <returns>System.String.</returns>
  153. protected virtual string GetMapArgs(StreamState state)
  154. {
  155. // If we don't have known media info
  156. // If input is video, use -sn to drop subtitles
  157. // Otherwise just return empty
  158. if (state.VideoStream == null && state.AudioStream == null)
  159. {
  160. return state.IsInputVideo ? "-sn" : string.Empty;
  161. }
  162. // We have media info, but we don't know the stream indexes
  163. if (state.VideoStream != null && state.VideoStream.Index == -1)
  164. {
  165. return "-sn";
  166. }
  167. // We have media info, but we don't know the stream indexes
  168. if (state.AudioStream != null && state.AudioStream.Index == -1)
  169. {
  170. return state.IsInputVideo ? "-sn" : string.Empty;
  171. }
  172. var args = string.Empty;
  173. if (state.VideoStream != null)
  174. {
  175. args += string.Format("-map 0:{0}", state.VideoStream.Index);
  176. }
  177. else
  178. {
  179. // No known video stream
  180. args += "-vn";
  181. }
  182. if (state.AudioStream != null)
  183. {
  184. args += string.Format(" -map 0:{0}", state.AudioStream.Index);
  185. }
  186. else
  187. {
  188. args += " -map -0:a";
  189. }
  190. if (state.SubtitleStream == null || state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Hls)
  191. {
  192. args += " -map -0:s";
  193. }
  194. else if (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed)
  195. {
  196. args += string.Format(" -map 0:{0}", state.SubtitleStream.Index);
  197. }
  198. else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
  199. {
  200. args += " -map 1:0 -sn";
  201. }
  202. return args;
  203. }
  204. /// <summary>
  205. /// Determines which stream will be used for playback
  206. /// </summary>
  207. /// <param name="allStream">All stream.</param>
  208. /// <param name="desiredIndex">Index of the desired.</param>
  209. /// <param name="type">The type.</param>
  210. /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
  211. /// <returns>MediaStream.</returns>
  212. private MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, bool returnFirstIfNoIndex = true)
  213. {
  214. var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
  215. if (desiredIndex.HasValue)
  216. {
  217. var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
  218. if (stream != null)
  219. {
  220. return stream;
  221. }
  222. }
  223. if (type == MediaStreamType.Video)
  224. {
  225. streams = streams.Where(i => !string.Equals(i.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)).ToList();
  226. }
  227. if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
  228. {
  229. return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
  230. streams.FirstOrDefault();
  231. }
  232. // Just return the first one
  233. return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
  234. }
  235. /// <summary>
  236. /// Gets the number of threads.
  237. /// </summary>
  238. /// <returns>System.Int32.</returns>
  239. protected int GetNumberOfThreads(StreamState state, bool isWebm)
  240. {
  241. var threads = ApiEntryPoint.Instance.GetEncodingOptions().EncodingThreadCount;
  242. if (isWebm)
  243. {
  244. // Recommended per docs
  245. return Math.Max(Environment.ProcessorCount - 1, 2);
  246. }
  247. // Automatic
  248. if (threads == -1)
  249. {
  250. return 0;
  251. }
  252. return threads;
  253. }
  254. protected string GetH264Encoder(StreamState state)
  255. {
  256. var defaultEncoder = "libx264";
  257. // Only use alternative encoders for video files.
  258. // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
  259. // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
  260. if (state.VideoType == VideoType.VideoFile)
  261. {
  262. var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
  263. var hwType = encodingOptions.HardwareAccelerationType;
  264. if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) ||
  265. string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
  266. {
  267. return GetAvailableEncoder("h264_qsv", defaultEncoder);
  268. }
  269. if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase))
  270. {
  271. return GetAvailableEncoder("h264_nvenc", defaultEncoder);
  272. }
  273. if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase))
  274. {
  275. return GetAvailableEncoder("h264_omx", defaultEncoder);
  276. }
  277. if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(encodingOptions.VaapiDevice))
  278. {
  279. if (IsVaapiSupported(state))
  280. {
  281. return GetAvailableEncoder("h264_vaapi", defaultEncoder);
  282. }
  283. }
  284. }
  285. return defaultEncoder;
  286. }
  287. private bool IsVaapiSupported(StreamState state)
  288. {
  289. var videoStream = state.VideoStream;
  290. if (videoStream != null)
  291. {
  292. // vaapi will throw an error with this input
  293. // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99.
  294. if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
  295. {
  296. if (videoStream.Level == -99 || videoStream.Level == 15)
  297. {
  298. return false;
  299. }
  300. }
  301. }
  302. return true;
  303. }
  304. private string GetAvailableEncoder(string preferredEncoder, string defaultEncoder)
  305. {
  306. if (MediaEncoder.SupportsEncoder(preferredEncoder))
  307. {
  308. return preferredEncoder;
  309. }
  310. return defaultEncoder;
  311. }
  312. protected virtual string GetDefaultH264Preset()
  313. {
  314. return "superfast";
  315. }
  316. /// <summary>
  317. /// Gets the video bitrate to specify on the command line
  318. /// </summary>
  319. /// <param name="state">The state.</param>
  320. /// <param name="videoEncoder">The video codec.</param>
  321. /// <returns>System.String.</returns>
  322. protected string GetVideoQualityParam(StreamState state, string videoEncoder)
  323. {
  324. var param = string.Empty;
  325. var isVc1 = state.VideoStream != null &&
  326. string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
  327. var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
  328. if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
  329. {
  330. if (!string.IsNullOrWhiteSpace(encodingOptions.H264Preset))
  331. {
  332. param += "-preset " + encodingOptions.H264Preset;
  333. }
  334. else
  335. {
  336. param += "-preset " + GetDefaultH264Preset();
  337. }
  338. if (encodingOptions.H264Crf >= 0 && encodingOptions.H264Crf <= 51)
  339. {
  340. param += " -crf " + encodingOptions.H264Crf.ToString(CultureInfo.InvariantCulture);
  341. }
  342. else
  343. {
  344. param += " -crf 23";
  345. }
  346. }
  347. else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
  348. {
  349. param += "-preset fast";
  350. param += " -crf 28";
  351. }
  352. // h264 (h264_qsv)
  353. else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
  354. {
  355. param += "-preset 7 -look_ahead 0";
  356. }
  357. // h264 (h264_nvenc)
  358. else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
  359. {
  360. param += "-preset default";
  361. }
  362. // webm
  363. else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase))
  364. {
  365. // Values 0-3, 0 being highest quality but slower
  366. var profileScore = 0;
  367. string crf;
  368. var qmin = "0";
  369. var qmax = "50";
  370. crf = "10";
  371. if (isVc1)
  372. {
  373. profileScore++;
  374. }
  375. // Max of 2
  376. profileScore = Math.Min(profileScore, 2);
  377. // http://www.webmproject.org/docs/encoder-parameters/
  378. param += string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
  379. profileScore.ToString(UsCulture),
  380. crf,
  381. qmin,
  382. qmax);
  383. }
  384. else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase))
  385. {
  386. param += "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
  387. }
  388. // asf/wmv
  389. else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase))
  390. {
  391. param += "-qmin 2";
  392. }
  393. else if (string.Equals(videoEncoder, "msmpeg4", StringComparison.OrdinalIgnoreCase))
  394. {
  395. param += "-mbd 2";
  396. }
  397. param += GetVideoBitrateParam(state, videoEncoder);
  398. var framerate = GetFramerateParam(state);
  399. if (framerate.HasValue)
  400. {
  401. param += string.Format(" -r {0}", framerate.Value.ToString(UsCulture));
  402. }
  403. if (!string.IsNullOrEmpty(state.OutputVideoSync))
  404. {
  405. param += " -vsync " + state.OutputVideoSync;
  406. }
  407. if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
  408. {
  409. if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
  410. !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
  411. {
  412. // not supported by h264_omx
  413. param += " -profile:v " + state.VideoRequest.Profile;
  414. }
  415. }
  416. if (!string.IsNullOrEmpty(state.VideoRequest.Level))
  417. {
  418. var level = NormalizeTranscodingLevel(state.OutputVideoCodec, state.VideoRequest.Level);
  419. // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
  420. // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307
  421. if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
  422. string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) ||
  423. string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
  424. {
  425. switch (level)
  426. {
  427. case "30":
  428. param += " -level 3.0";
  429. break;
  430. case "31":
  431. param += " -level 3.1";
  432. break;
  433. case "32":
  434. param += " -level 3.2";
  435. break;
  436. case "40":
  437. param += " -level 4.0";
  438. break;
  439. case "41":
  440. param += " -level 4.1";
  441. break;
  442. case "42":
  443. param += " -level 4.2";
  444. break;
  445. case "50":
  446. param += " -level 5.0";
  447. break;
  448. case "51":
  449. param += " -level 5.1";
  450. break;
  451. case "52":
  452. param += " -level 5.2";
  453. break;
  454. default:
  455. param += " -level " + level;
  456. break;
  457. }
  458. }
  459. else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase))
  460. {
  461. param += " -level " + level;
  462. }
  463. }
  464. if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
  465. {
  466. param += " -x264opts:0 subme=0:rc_lookahead=10:me_range=4:me=dia:no_chroma_me:8x8dct=0:partitions=none";
  467. }
  468. if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
  469. !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
  470. !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
  471. {
  472. param = "-pix_fmt yuv420p " + param;
  473. }
  474. return param;
  475. }
  476. private string NormalizeTranscodingLevel(string videoCodec, string level)
  477. {
  478. double requestLevel;
  479. // Clients may direct play higher than level 41, but there's no reason to transcode higher
  480. if (double.TryParse(level, NumberStyles.Any, UsCulture, out requestLevel))
  481. {
  482. if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
  483. {
  484. if (requestLevel > 41)
  485. {
  486. return "41";
  487. }
  488. }
  489. }
  490. return level;
  491. }
  492. protected string GetAudioFilterParam(StreamState state, bool isHls)
  493. {
  494. var volParam = string.Empty;
  495. var audioSampleRate = string.Empty;
  496. var channels = state.OutputAudioChannels;
  497. // Boost volume to 200% when downsampling from 6ch to 2ch
  498. if (channels.HasValue && channels.Value <= 2)
  499. {
  500. if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5 && !ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.Equals(1))
  501. {
  502. volParam = ",volume=" + ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture);
  503. }
  504. }
  505. if (state.OutputAudioSampleRate.HasValue)
  506. {
  507. audioSampleRate = state.OutputAudioSampleRate.Value + ":";
  508. }
  509. var adelay = isHls ? "adelay=1," : string.Empty;
  510. var pts = string.Empty;
  511. if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode && !state.VideoRequest.CopyTimestamps)
  512. {
  513. var seconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds;
  514. pts = string.Format(",asetpts=PTS-{0}/TB", Math.Round(seconds).ToString(UsCulture));
  515. }
  516. return string.Format("-af \"{0}aresample={1}async={4}{2}{3}\"",
  517. adelay,
  518. audioSampleRate,
  519. volParam,
  520. pts,
  521. state.OutputAudioSync);
  522. }
  523. /// <summary>
  524. /// If we're going to put a fixed size on the command line, this will calculate it
  525. /// </summary>
  526. /// <param name="state">The state.</param>
  527. /// <param name="outputVideoCodec">The output video codec.</param>
  528. /// <param name="allowTimeStampCopy">if set to <c>true</c> [allow time stamp copy].</param>
  529. /// <returns>System.String.</returns>
  530. protected string GetOutputSizeParam(StreamState state,
  531. string outputVideoCodec,
  532. bool allowTimeStampCopy = true)
  533. {
  534. // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
  535. var request = state.VideoRequest;
  536. var filters = new List<string>();
  537. if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
  538. {
  539. filters.Add("format=nv12|vaapi");
  540. filters.Add("hwupload");
  541. }
  542. else if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
  543. {
  544. filters.Add("yadif=0:-1:0");
  545. }
  546. if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
  547. {
  548. // Work around vaapi's reduced scaling features
  549. var scaler = "scale_vaapi";
  550. // Given the input dimensions (inputWidth, inputHeight), determine the output dimensions
  551. // (outputWidth, outputHeight). The user may request precise output dimensions or maximum
  552. // output dimensions. Output dimensions are guaranteed to be even.
  553. decimal inputWidth = Convert.ToDecimal(state.VideoStream.Width);
  554. decimal inputHeight = Convert.ToDecimal(state.VideoStream.Height);
  555. decimal outputWidth = request.Width.HasValue ? Convert.ToDecimal(request.Width.Value) : inputWidth;
  556. decimal outputHeight = request.Height.HasValue ? Convert.ToDecimal(request.Height.Value) : inputHeight;
  557. decimal maximumWidth = request.MaxWidth.HasValue ? Convert.ToDecimal(request.MaxWidth.Value) : outputWidth;
  558. decimal maximumHeight = request.MaxHeight.HasValue ? Convert.ToDecimal(request.MaxHeight.Value) : outputHeight;
  559. if (outputWidth > maximumWidth || outputHeight > maximumHeight)
  560. {
  561. var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight);
  562. outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale));
  563. outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale));
  564. }
  565. outputWidth = 2 * Math.Truncate(outputWidth / 2);
  566. outputHeight = 2 * Math.Truncate(outputHeight / 2);
  567. if (outputWidth != inputWidth || outputHeight != inputHeight)
  568. {
  569. filters.Add(string.Format("{0}=w={1}:h={2}", scaler, outputWidth.ToString(UsCulture), outputHeight.ToString(UsCulture)));
  570. }
  571. }
  572. else
  573. {
  574. // If fixed dimensions were supplied
  575. if (request.Width.HasValue && request.Height.HasValue)
  576. {
  577. var widthParam = request.Width.Value.ToString(UsCulture);
  578. var heightParam = request.Height.Value.ToString(UsCulture);
  579. filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam));
  580. }
  581. // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
  582. else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
  583. {
  584. var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
  585. var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
  586. filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam));
  587. }
  588. // If a fixed width was requested
  589. else if (request.Width.HasValue)
  590. {
  591. var widthParam = request.Width.Value.ToString(UsCulture);
  592. filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam));
  593. }
  594. // If a fixed height was requested
  595. else if (request.Height.HasValue)
  596. {
  597. var heightParam = request.Height.Value.ToString(UsCulture);
  598. filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam));
  599. }
  600. // If a max width was requested
  601. else if (request.MaxWidth.HasValue)
  602. {
  603. var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
  604. filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
  605. }
  606. // If a max height was requested
  607. else if (request.MaxHeight.HasValue)
  608. {
  609. var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
  610. filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam));
  611. }
  612. }
  613. var output = string.Empty;
  614. if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode)
  615. {
  616. var subParam = GetTextSubtitleParam(state);
  617. filters.Add(subParam);
  618. if (allowTimeStampCopy)
  619. {
  620. output += " -copyts";
  621. }
  622. }
  623. if (filters.Count > 0)
  624. {
  625. output += string.Format(" -vf \"{0}\"", string.Join(",", filters.ToArray()));
  626. }
  627. return output;
  628. }
  629. /// <summary>
  630. /// Gets the text subtitle param.
  631. /// </summary>
  632. /// <param name="state">The state.</param>
  633. /// <returns>System.String.</returns>
  634. protected string GetTextSubtitleParam(StreamState state)
  635. {
  636. var seconds = Math.Round(TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds);
  637. var setPtsParam = state.VideoRequest.CopyTimestamps
  638. ? string.Empty
  639. : string.Format(",setpts=PTS -{0}/TB", seconds.ToString(UsCulture));
  640. if (state.SubtitleStream.IsExternal)
  641. {
  642. var subtitlePath = state.SubtitleStream.Path;
  643. var charsetParam = string.Empty;
  644. if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
  645. {
  646. var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result;
  647. if (!string.IsNullOrEmpty(charenc))
  648. {
  649. charsetParam = ":charenc=" + charenc;
  650. }
  651. }
  652. // TODO: Perhaps also use original_size=1920x800 ??
  653. return string.Format("subtitles=filename='{0}'{1}{2}",
  654. MediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
  655. charsetParam,
  656. setPtsParam);
  657. }
  658. var mediaPath = state.MediaPath ?? string.Empty;
  659. return string.Format("subtitles='{0}:si={1}'{2}",
  660. MediaEncoder.EscapeSubtitleFilterPath(mediaPath),
  661. state.InternalSubtitleStreamOffset.ToString(UsCulture),
  662. setPtsParam);
  663. }
  664. /// <summary>
  665. /// Gets the internal graphical subtitle param.
  666. /// </summary>
  667. /// <param name="state">The state.</param>
  668. /// <param name="outputVideoCodec">The output video codec.</param>
  669. /// <returns>System.String.</returns>
  670. protected string GetGraphicalSubtitleParam(StreamState state, string outputVideoCodec)
  671. {
  672. var outputSizeParam = string.Empty;
  673. var request = state.VideoRequest;
  674. // Add resolution params, if specified
  675. if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
  676. {
  677. outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).TrimEnd('"');
  678. if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
  679. {
  680. outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase));
  681. }
  682. else
  683. {
  684. outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
  685. }
  686. }
  687. if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && outputSizeParam.Length == 0)
  688. {
  689. outputSizeParam = ",format=nv12|vaapi,hwupload";
  690. }
  691. var videoSizeParam = string.Empty;
  692. if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
  693. {
  694. videoSizeParam = string.Format("scale={0}:{1}", state.VideoStream.Width.Value.ToString(UsCulture), state.VideoStream.Height.Value.ToString(UsCulture));
  695. }
  696. var mapPrefix = state.SubtitleStream.IsExternal ?
  697. 1 :
  698. 0;
  699. var subtitleStreamIndex = state.SubtitleStream.IsExternal
  700. ? 0
  701. : state.SubtitleStream.Index;
  702. return string.Format(" -filter_complex \"[{0}:{1}]{4}[sub] ; [0:{2}] [sub] overlay{3}\"",
  703. mapPrefix.ToString(UsCulture),
  704. subtitleStreamIndex.ToString(UsCulture),
  705. state.VideoStream.Index.ToString(UsCulture),
  706. outputSizeParam,
  707. videoSizeParam);
  708. }
  709. /// <summary>
  710. /// Gets the probe size argument.
  711. /// </summary>
  712. /// <param name="state">The state.</param>
  713. /// <returns>System.String.</returns>
  714. private string GetProbeSizeArgument(StreamState state)
  715. {
  716. if (state.PlayableStreamFileNames.Count > 0)
  717. {
  718. return MediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol);
  719. }
  720. return MediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(new[] { state.MediaPath }, state.InputProtocol);
  721. }
  722. /// <summary>
  723. /// Gets the number of audio channels to specify on the command line
  724. /// </summary>
  725. /// <param name="request">The request.</param>
  726. /// <param name="audioStream">The audio stream.</param>
  727. /// <param name="outputAudioCodec">The output audio codec.</param>
  728. /// <returns>System.Nullable{System.Int32}.</returns>
  729. private int? GetNumAudioChannelsParam(StreamRequest request, MediaStream audioStream, string outputAudioCodec)
  730. {
  731. var inputChannels = audioStream == null
  732. ? null
  733. : audioStream.Channels;
  734. if (inputChannels <= 0)
  735. {
  736. inputChannels = null;
  737. }
  738. int? transcoderChannelLimit = null;
  739. var codec = outputAudioCodec ?? string.Empty;
  740. if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
  741. {
  742. // wmav2 currently only supports two channel output
  743. transcoderChannelLimit = 2;
  744. }
  745. else if (codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1)
  746. {
  747. // libmp3lame currently only supports two channel output
  748. transcoderChannelLimit = 2;
  749. }
  750. else
  751. {
  752. // If we don't have any media info then limit it to 6 to prevent encoding errors due to asking for too many channels
  753. transcoderChannelLimit = 6;
  754. }
  755. var isTranscodingAudio = !string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
  756. int? resultChannels = null;
  757. if (isTranscodingAudio)
  758. {
  759. resultChannels = request.TranscodingMaxAudioChannels;
  760. }
  761. resultChannels = resultChannels ?? request.MaxAudioChannels ?? request.AudioChannels;
  762. if (inputChannels.HasValue)
  763. {
  764. resultChannels = resultChannels.HasValue
  765. ? Math.Min(resultChannels.Value, inputChannels.Value)
  766. : inputChannels.Value;
  767. }
  768. if (isTranscodingAudio && transcoderChannelLimit.HasValue)
  769. {
  770. resultChannels = resultChannels.HasValue
  771. ? Math.Min(resultChannels.Value, transcoderChannelLimit.Value)
  772. : transcoderChannelLimit.Value;
  773. }
  774. return resultChannels ?? request.AudioChannels;
  775. }
  776. /// <summary>
  777. /// Determines whether the specified stream is H264.
  778. /// </summary>
  779. /// <param name="stream">The stream.</param>
  780. /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
  781. protected bool IsH264(MediaStream stream)
  782. {
  783. var codec = stream.Codec ?? string.Empty;
  784. return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 ||
  785. codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1;
  786. }
  787. /// <summary>
  788. /// Gets the audio encoder.
  789. /// </summary>
  790. /// <param name="state">The state.</param>
  791. /// <returns>System.String.</returns>
  792. protected string GetAudioEncoder(StreamState state)
  793. {
  794. var codec = state.OutputAudioCodec;
  795. if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
  796. {
  797. return "aac -strict experimental";
  798. }
  799. if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
  800. {
  801. return "libmp3lame";
  802. }
  803. if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase))
  804. {
  805. return "libvorbis";
  806. }
  807. if (string.Equals(codec, "wma", StringComparison.OrdinalIgnoreCase))
  808. {
  809. return "wmav2";
  810. }
  811. return codec.ToLower();
  812. }
  813. /// <summary>
  814. /// Gets the name of the output video codec
  815. /// </summary>
  816. /// <param name="state">The state.</param>
  817. /// <returns>System.String.</returns>
  818. protected string GetVideoEncoder(StreamState state)
  819. {
  820. var codec = state.OutputVideoCodec;
  821. if (!string.IsNullOrEmpty(codec))
  822. {
  823. if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
  824. {
  825. return GetH264Encoder(state);
  826. }
  827. if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
  828. {
  829. return "libvpx";
  830. }
  831. if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
  832. {
  833. return "wmv2";
  834. }
  835. if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase))
  836. {
  837. return "libtheora";
  838. }
  839. return codec.ToLower();
  840. }
  841. return "copy";
  842. }
  843. /// <summary>
  844. /// Gets the name of the output video codec
  845. /// </summary>
  846. /// <param name="state">The state.</param>
  847. /// <returns>System.String.</returns>
  848. protected string GetVideoDecoder(StreamState state)
  849. {
  850. if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  851. {
  852. return null;
  853. }
  854. // Only use alternative encoders for video files.
  855. // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
  856. // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
  857. if (state.VideoType != VideoType.VideoFile)
  858. {
  859. return null;
  860. }
  861. if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
  862. {
  863. if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
  864. {
  865. switch (state.MediaSource.VideoStream.Codec.ToLower())
  866. {
  867. case "avc":
  868. case "h264":
  869. if (MediaEncoder.SupportsDecoder("h264_qsv"))
  870. {
  871. // Seeing stalls and failures with decoding. Not worth it compared to encoding.
  872. return "-c:v h264_qsv ";
  873. }
  874. break;
  875. case "mpeg2video":
  876. if (MediaEncoder.SupportsDecoder("mpeg2_qsv"))
  877. {
  878. return "-c:v mpeg2_qsv ";
  879. }
  880. break;
  881. case "vc1":
  882. if (MediaEncoder.SupportsDecoder("vc1_qsv"))
  883. {
  884. return "-c:v vc1_qsv ";
  885. }
  886. break;
  887. }
  888. }
  889. }
  890. // leave blank so ffmpeg will decide
  891. return null;
  892. }
  893. /// <summary>
  894. /// Gets the input argument.
  895. /// </summary>
  896. /// <param name="state">The state.</param>
  897. /// <returns>System.String.</returns>
  898. protected string GetInputArgument(StreamState state)
  899. {
  900. var arg = string.Format("-i {0}", GetInputPathArgument(state));
  901. if (state.SubtitleStream != null && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode)
  902. {
  903. if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
  904. {
  905. if (state.VideoStream != null && state.VideoStream.Width.HasValue)
  906. {
  907. // This is hacky but not sure how to get the exact subtitle resolution
  908. double height = state.VideoStream.Width.Value;
  909. height /= 16;
  910. height *= 9;
  911. arg += string.Format(" -canvas_size {0}:{1}", state.VideoStream.Width.Value.ToString(CultureInfo.InvariantCulture), Convert.ToInt32(height).ToString(CultureInfo.InvariantCulture));
  912. }
  913. var subtitlePath = state.SubtitleStream.Path;
  914. if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase))
  915. {
  916. var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
  917. if (FileSystem.FileExists(idxFile))
  918. {
  919. subtitlePath = idxFile;
  920. }
  921. }
  922. arg += " -i \"" + subtitlePath + "\"";
  923. }
  924. }
  925. if (state.VideoRequest != null)
  926. {
  927. var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
  928. if (GetVideoEncoder(state).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
  929. {
  930. var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
  931. var hwOutputFormat = "vaapi";
  932. if (hasGraphicalSubs)
  933. {
  934. hwOutputFormat = "yuv420p";
  935. }
  936. arg = "-hwaccel vaapi -hwaccel_output_format " + hwOutputFormat + " -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
  937. }
  938. }
  939. return arg.Trim();
  940. }
  941. private string GetInputPathArgument(StreamState state)
  942. {
  943. var protocol = state.InputProtocol;
  944. var mediaPath = state.MediaPath ?? string.Empty;
  945. var inputPath = new[] { mediaPath };
  946. if (state.IsInputVideo)
  947. {
  948. if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
  949. {
  950. inputPath = MediaEncoderHelpers.GetInputArgument(FileSystem, mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
  951. }
  952. }
  953. return MediaEncoder.GetInputArgument(inputPath, protocol);
  954. }
  955. private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
  956. {
  957. if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
  958. {
  959. state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
  960. }
  961. if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId))
  962. {
  963. var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
  964. {
  965. OpenToken = state.MediaSource.OpenToken
  966. }, false, cancellationTokenSource.Token).ConfigureAwait(false);
  967. AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.VideoRequest, state.RequestedUrl);
  968. if (state.VideoRequest != null)
  969. {
  970. TryStreamCopy(state, state.VideoRequest);
  971. }
  972. }
  973. if (state.MediaSource.BufferMs.HasValue)
  974. {
  975. await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false);
  976. }
  977. }
  978. /// <summary>
  979. /// Starts the FFMPEG.
  980. /// </summary>
  981. /// <param name="state">The state.</param>
  982. /// <param name="outputPath">The output path.</param>
  983. /// <param name="cancellationTokenSource">The cancellation token source.</param>
  984. /// <param name="workingDirectory">The working directory.</param>
  985. /// <returns>Task.</returns>
  986. protected async Task<TranscodingJob> StartFfMpeg(StreamState state,
  987. string outputPath,
  988. CancellationTokenSource cancellationTokenSource,
  989. string workingDirectory = null)
  990. {
  991. FileSystem.CreateDirectory(Path.GetDirectoryName(outputPath));
  992. await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
  993. if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  994. {
  995. var auth = AuthorizationContext.GetAuthorizationInfo(Request);
  996. if (!string.IsNullOrWhiteSpace(auth.UserId))
  997. {
  998. var user = UserManager.GetUserById(auth.UserId);
  999. if (!user.Policy.EnableVideoPlaybackTranscoding)
  1000. {
  1001. ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
  1002. throw new ArgumentException("User does not have access to video transcoding");
  1003. }
  1004. }
  1005. }
  1006. var transcodingId = Guid.NewGuid().ToString("N");
  1007. var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
  1008. var process = ApiEntryPoint.Instance.ProcessFactory.Create(new ProcessOptions
  1009. {
  1010. CreateNoWindow = true,
  1011. UseShellExecute = false,
  1012. // Must consume both stdout and stderr or deadlocks may occur
  1013. //RedirectStandardOutput = true,
  1014. RedirectStandardError = true,
  1015. RedirectStandardInput = true,
  1016. FileName = MediaEncoder.EncoderPath,
  1017. Arguments = commandLineArgs,
  1018. IsHidden = true,
  1019. ErrorDialog = false,
  1020. EnableRaisingEvents = true,
  1021. WorkingDirectory = !string.IsNullOrWhiteSpace(workingDirectory) ? workingDirectory : null
  1022. });
  1023. var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
  1024. state.Request.PlaySessionId,
  1025. state.MediaSource.LiveStreamId,
  1026. transcodingId,
  1027. TranscodingJobType,
  1028. process,
  1029. state.Request.DeviceId,
  1030. state,
  1031. cancellationTokenSource);
  1032. var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
  1033. Logger.Info(commandLineLogMessage);
  1034. var logFilePrefix = "ffmpeg-transcode";
  1035. if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
  1036. {
  1037. logFilePrefix = "ffmpeg-directstream";
  1038. }
  1039. else if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  1040. {
  1041. logFilePrefix = "ffmpeg-remux";
  1042. }
  1043. var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
  1044. FileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
  1045. // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
  1046. state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
  1047. var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(Request.AbsoluteUri + Environment.NewLine + Environment.NewLine + JsonSerializer.SerializeToString(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
  1048. await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
  1049. process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state);
  1050. try
  1051. {
  1052. process.Start();
  1053. }
  1054. catch (Exception ex)
  1055. {
  1056. Logger.ErrorException("Error starting ffmpeg", ex);
  1057. ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
  1058. throw;
  1059. }
  1060. // MUST read both stdout and stderr asynchronously or a deadlock may occurr
  1061. //process.BeginOutputReadLine();
  1062. // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
  1063. var task = Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream));
  1064. // Wait for the file to exist before proceeeding
  1065. while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
  1066. {
  1067. await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
  1068. }
  1069. if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited)
  1070. {
  1071. await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false);
  1072. if (state.ReadInputAtNativeFramerate && !transcodingJob.HasExited)
  1073. {
  1074. await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
  1075. }
  1076. }
  1077. if (!transcodingJob.HasExited)
  1078. {
  1079. StartThrottler(state, transcodingJob);
  1080. }
  1081. ReportUsage(state);
  1082. return transcodingJob;
  1083. }
  1084. private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
  1085. {
  1086. if (EnableThrottling(state))
  1087. {
  1088. transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager, ApiEntryPoint.Instance.TimerFactory, FileSystem);
  1089. state.TranscodingThrottler.Start();
  1090. }
  1091. }
  1092. private bool EnableThrottling(StreamState state)
  1093. {
  1094. return false;
  1095. // do not use throttling with hardware encoders
  1096. return state.InputProtocol == MediaProtocol.File &&
  1097. state.RunTimeTicks.HasValue &&
  1098. state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
  1099. state.IsInputVideo &&
  1100. state.VideoType == VideoType.VideoFile &&
  1101. !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) &&
  1102. string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase);
  1103. }
  1104. private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
  1105. {
  1106. try
  1107. {
  1108. using (var reader = new StreamReader(source))
  1109. {
  1110. while (!reader.EndOfStream)
  1111. {
  1112. var line = await reader.ReadLineAsync().ConfigureAwait(false);
  1113. ParseLogLine(line, transcodingJob, state);
  1114. var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
  1115. await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
  1116. await target.FlushAsync().ConfigureAwait(false);
  1117. }
  1118. }
  1119. }
  1120. catch (ObjectDisposedException)
  1121. {
  1122. // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
  1123. }
  1124. catch (Exception ex)
  1125. {
  1126. Logger.ErrorException("Error reading ffmpeg log", ex);
  1127. }
  1128. }
  1129. private void ParseLogLine(string line, TranscodingJob transcodingJob, StreamState state)
  1130. {
  1131. float? framerate = null;
  1132. double? percent = null;
  1133. TimeSpan? transcodingPosition = null;
  1134. long? bytesTranscoded = null;
  1135. int? bitRate = null;
  1136. var parts = line.Split(' ');
  1137. var totalMs = state.RunTimeTicks.HasValue
  1138. ? TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds
  1139. : 0;
  1140. var startMs = state.Request.StartTimeTicks.HasValue
  1141. ? TimeSpan.FromTicks(state.Request.StartTimeTicks.Value).TotalMilliseconds
  1142. : 0;
  1143. for (var i = 0; i < parts.Length; i++)
  1144. {
  1145. var part = parts[i];
  1146. if (string.Equals(part, "fps=", StringComparison.OrdinalIgnoreCase) &&
  1147. (i + 1 < parts.Length))
  1148. {
  1149. var rate = parts[i + 1];
  1150. float val;
  1151. if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val))
  1152. {
  1153. framerate = val;
  1154. }
  1155. }
  1156. else if (state.RunTimeTicks.HasValue &&
  1157. part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
  1158. {
  1159. var time = part.Split(new[] { '=' }, 2).Last();
  1160. TimeSpan val;
  1161. if (TimeSpan.TryParse(time, UsCulture, out val))
  1162. {
  1163. var currentMs = startMs + val.TotalMilliseconds;
  1164. var percentVal = currentMs / totalMs;
  1165. percent = 100 * percentVal;
  1166. transcodingPosition = val;
  1167. }
  1168. }
  1169. else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))
  1170. {
  1171. var size = part.Split(new[] { '=' }, 2).Last();
  1172. int? scale = null;
  1173. if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1)
  1174. {
  1175. scale = 1024;
  1176. size = size.Replace("kb", string.Empty, StringComparison.OrdinalIgnoreCase);
  1177. }
  1178. if (scale.HasValue)
  1179. {
  1180. long val;
  1181. if (long.TryParse(size, NumberStyles.Any, UsCulture, out val))
  1182. {
  1183. bytesTranscoded = val * scale.Value;
  1184. }
  1185. }
  1186. }
  1187. else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase))
  1188. {
  1189. var rate = part.Split(new[] { '=' }, 2).Last();
  1190. int? scale = null;
  1191. if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
  1192. {
  1193. scale = 1024;
  1194. rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase);
  1195. }
  1196. if (scale.HasValue)
  1197. {
  1198. float val;
  1199. if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val))
  1200. {
  1201. bitRate = (int)Math.Ceiling(val * scale.Value);
  1202. }
  1203. }
  1204. }
  1205. }
  1206. if (framerate.HasValue || percent.HasValue)
  1207. {
  1208. ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded, bitRate);
  1209. }
  1210. }
  1211. private int? GetVideoBitrateParamValue(VideoStreamRequest request, MediaStream videoStream, string outputVideoCodec)
  1212. {
  1213. var bitrate = request.VideoBitRate;
  1214. if (videoStream != null)
  1215. {
  1216. var isUpscaling = request.Height.HasValue && videoStream.Height.HasValue &&
  1217. request.Height.Value > videoStream.Height.Value;
  1218. if (request.Width.HasValue && videoStream.Width.HasValue &&
  1219. request.Width.Value > videoStream.Width.Value)
  1220. {
  1221. isUpscaling = true;
  1222. }
  1223. // Don't allow bitrate increases unless upscaling
  1224. if (!isUpscaling)
  1225. {
  1226. if (bitrate.HasValue && videoStream.BitRate.HasValue)
  1227. {
  1228. bitrate = Math.Min(bitrate.Value, videoStream.BitRate.Value);
  1229. }
  1230. }
  1231. }
  1232. if (bitrate.HasValue)
  1233. {
  1234. var inputVideoCodec = videoStream == null ? null : videoStream.Codec;
  1235. bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
  1236. // If a max bitrate was requested, don't let the scaled bitrate exceed it
  1237. if (request.VideoBitRate.HasValue)
  1238. {
  1239. bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
  1240. }
  1241. }
  1242. return bitrate;
  1243. }
  1244. protected string GetVideoBitrateParam(StreamState state, string videoCodec)
  1245. {
  1246. var bitrate = state.OutputVideoBitrate;
  1247. if (bitrate.HasValue)
  1248. {
  1249. if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
  1250. {
  1251. // With vpx when crf is used, b:v becomes a max rate
  1252. // 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.
  1253. return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
  1254. }
  1255. if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
  1256. {
  1257. return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
  1258. }
  1259. if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
  1260. {
  1261. // h264
  1262. return string.Format(" -maxrate {0} -bufsize {1}",
  1263. bitrate.Value.ToString(UsCulture),
  1264. (bitrate.Value * 2).ToString(UsCulture));
  1265. }
  1266. // h264
  1267. return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
  1268. bitrate.Value.ToString(UsCulture),
  1269. (bitrate.Value * 2).ToString(UsCulture));
  1270. }
  1271. return string.Empty;
  1272. }
  1273. private int? GetAudioBitrateParam(StreamRequest request, MediaStream audioStream)
  1274. {
  1275. if (request.AudioBitRate.HasValue)
  1276. {
  1277. // Make sure we don't request a bitrate higher than the source
  1278. var currentBitrate = audioStream == null ? request.AudioBitRate.Value : audioStream.BitRate ?? request.AudioBitRate.Value;
  1279. // Don't encode any higher than this
  1280. return Math.Min(384000, request.AudioBitRate.Value);
  1281. //return Math.Min(currentBitrate, request.AudioBitRate.Value);
  1282. }
  1283. return null;
  1284. }
  1285. /// <summary>
  1286. /// Gets the user agent param.
  1287. /// </summary>
  1288. /// <param name="state">The state.</param>
  1289. /// <returns>System.String.</returns>
  1290. private string GetUserAgentParam(StreamState state)
  1291. {
  1292. string useragent = null;
  1293. state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
  1294. if (!string.IsNullOrWhiteSpace(useragent))
  1295. {
  1296. return "-user-agent \"" + useragent + "\"";
  1297. }
  1298. return string.Empty;
  1299. }
  1300. /// <summary>
  1301. /// Processes the exited.
  1302. /// </summary>
  1303. /// <param name="process">The process.</param>
  1304. /// <param name="job">The job.</param>
  1305. /// <param name="state">The state.</param>
  1306. private void OnFfMpegProcessExited(IProcess process, TranscodingJob job, StreamState state)
  1307. {
  1308. if (job != null)
  1309. {
  1310. job.HasExited = true;
  1311. }
  1312. Logger.Debug("Disposing stream resources");
  1313. state.Dispose();
  1314. try
  1315. {
  1316. Logger.Info("FFMpeg exited with code {0}", process.ExitCode);
  1317. }
  1318. catch
  1319. {
  1320. Logger.Error("FFMpeg exited with an error.");
  1321. }
  1322. // This causes on exited to be called twice:
  1323. //try
  1324. //{
  1325. // // Dispose the process
  1326. // process.Dispose();
  1327. //}
  1328. //catch (Exception ex)
  1329. //{
  1330. // Logger.ErrorException("Error disposing ffmpeg.", ex);
  1331. //}
  1332. }
  1333. protected double? GetFramerateParam(StreamState state)
  1334. {
  1335. if (state.VideoRequest != null)
  1336. {
  1337. if (state.VideoRequest.Framerate.HasValue)
  1338. {
  1339. return state.VideoRequest.Framerate.Value;
  1340. }
  1341. var maxrate = state.VideoRequest.MaxFramerate;
  1342. if (maxrate.HasValue && state.VideoStream != null)
  1343. {
  1344. var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
  1345. if (contentRate.HasValue && contentRate.Value > maxrate.Value)
  1346. {
  1347. return maxrate;
  1348. }
  1349. }
  1350. }
  1351. return null;
  1352. }
  1353. /// <summary>
  1354. /// Parses the parameters.
  1355. /// </summary>
  1356. /// <param name="request">The request.</param>
  1357. private void ParseParams(StreamRequest request)
  1358. {
  1359. var vals = request.Params.Split(';');
  1360. var videoRequest = request as VideoStreamRequest;
  1361. for (var i = 0; i < vals.Length; i++)
  1362. {
  1363. var val = vals[i];
  1364. if (string.IsNullOrWhiteSpace(val))
  1365. {
  1366. continue;
  1367. }
  1368. if (i == 0)
  1369. {
  1370. request.DeviceProfileId = val;
  1371. }
  1372. else if (i == 1)
  1373. {
  1374. request.DeviceId = val;
  1375. }
  1376. else if (i == 2)
  1377. {
  1378. request.MediaSourceId = val;
  1379. }
  1380. else if (i == 3)
  1381. {
  1382. request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
  1383. }
  1384. else if (i == 4)
  1385. {
  1386. if (videoRequest != null)
  1387. {
  1388. videoRequest.VideoCodec = val;
  1389. }
  1390. }
  1391. else if (i == 5)
  1392. {
  1393. request.AudioCodec = val;
  1394. }
  1395. else if (i == 6)
  1396. {
  1397. if (videoRequest != null)
  1398. {
  1399. videoRequest.AudioStreamIndex = int.Parse(val, UsCulture);
  1400. }
  1401. }
  1402. else if (i == 7)
  1403. {
  1404. if (videoRequest != null)
  1405. {
  1406. videoRequest.SubtitleStreamIndex = int.Parse(val, UsCulture);
  1407. }
  1408. }
  1409. else if (i == 8)
  1410. {
  1411. if (videoRequest != null)
  1412. {
  1413. videoRequest.VideoBitRate = int.Parse(val, UsCulture);
  1414. }
  1415. }
  1416. else if (i == 9)
  1417. {
  1418. request.AudioBitRate = int.Parse(val, UsCulture);
  1419. }
  1420. else if (i == 10)
  1421. {
  1422. request.MaxAudioChannels = int.Parse(val, UsCulture);
  1423. }
  1424. else if (i == 11)
  1425. {
  1426. if (videoRequest != null)
  1427. {
  1428. videoRequest.MaxFramerate = float.Parse(val, UsCulture);
  1429. }
  1430. }
  1431. else if (i == 12)
  1432. {
  1433. if (videoRequest != null)
  1434. {
  1435. videoRequest.MaxWidth = int.Parse(val, UsCulture);
  1436. }
  1437. }
  1438. else if (i == 13)
  1439. {
  1440. if (videoRequest != null)
  1441. {
  1442. videoRequest.MaxHeight = int.Parse(val, UsCulture);
  1443. }
  1444. }
  1445. else if (i == 14)
  1446. {
  1447. request.StartTimeTicks = long.Parse(val, UsCulture);
  1448. }
  1449. else if (i == 15)
  1450. {
  1451. if (videoRequest != null)
  1452. {
  1453. videoRequest.Level = val;
  1454. }
  1455. }
  1456. else if (i == 16)
  1457. {
  1458. if (videoRequest != null)
  1459. {
  1460. videoRequest.MaxRefFrames = int.Parse(val, UsCulture);
  1461. }
  1462. }
  1463. else if (i == 17)
  1464. {
  1465. if (videoRequest != null)
  1466. {
  1467. videoRequest.MaxVideoBitDepth = int.Parse(val, UsCulture);
  1468. }
  1469. }
  1470. else if (i == 18)
  1471. {
  1472. if (videoRequest != null)
  1473. {
  1474. videoRequest.Profile = val;
  1475. }
  1476. }
  1477. else if (i == 19)
  1478. {
  1479. // cabac no longer used
  1480. }
  1481. else if (i == 20)
  1482. {
  1483. request.PlaySessionId = val;
  1484. }
  1485. else if (i == 21)
  1486. {
  1487. // api_key
  1488. }
  1489. else if (i == 22)
  1490. {
  1491. request.LiveStreamId = val;
  1492. }
  1493. else if (i == 23)
  1494. {
  1495. // Duplicating ItemId because of MediaMonkey
  1496. }
  1497. else if (i == 24)
  1498. {
  1499. if (videoRequest != null)
  1500. {
  1501. videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
  1502. }
  1503. }
  1504. else if (i == 25)
  1505. {
  1506. if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
  1507. {
  1508. SubtitleDeliveryMethod method;
  1509. if (Enum.TryParse(val, out method))
  1510. {
  1511. videoRequest.SubtitleMethod = method;
  1512. }
  1513. }
  1514. }
  1515. else if (i == 26)
  1516. {
  1517. request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
  1518. }
  1519. else if (i == 27)
  1520. {
  1521. if (videoRequest != null)
  1522. {
  1523. videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
  1524. }
  1525. }
  1526. else if (i == 28)
  1527. {
  1528. request.Tag = val;
  1529. }
  1530. else if (i == 29)
  1531. {
  1532. if (videoRequest != null)
  1533. {
  1534. videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
  1535. }
  1536. }
  1537. else if (i == 30)
  1538. {
  1539. request.SubtitleCodec = val;
  1540. }
  1541. }
  1542. }
  1543. /// <summary>
  1544. /// Parses the dlna headers.
  1545. /// </summary>
  1546. /// <param name="request">The request.</param>
  1547. private void ParseDlnaHeaders(StreamRequest request)
  1548. {
  1549. if (!request.StartTimeTicks.HasValue)
  1550. {
  1551. var timeSeek = GetHeader("TimeSeekRange.dlna.org");
  1552. request.StartTimeTicks = ParseTimeSeekHeader(timeSeek);
  1553. }
  1554. }
  1555. /// <summary>
  1556. /// Parses the time seek header.
  1557. /// </summary>
  1558. private long? ParseTimeSeekHeader(string value)
  1559. {
  1560. if (string.IsNullOrWhiteSpace(value))
  1561. {
  1562. return null;
  1563. }
  1564. if (value.IndexOf("npt=", StringComparison.OrdinalIgnoreCase) != 0)
  1565. {
  1566. throw new ArgumentException("Invalid timeseek header");
  1567. }
  1568. value = value.Substring(4).Split(new[] { '-' }, 2)[0];
  1569. if (value.IndexOf(':') == -1)
  1570. {
  1571. // Parses npt times in the format of '417.33'
  1572. double seconds;
  1573. if (double.TryParse(value, NumberStyles.Any, UsCulture, out seconds))
  1574. {
  1575. return TimeSpan.FromSeconds(seconds).Ticks;
  1576. }
  1577. throw new ArgumentException("Invalid timeseek header");
  1578. }
  1579. // Parses npt times in the format of '10:19:25.7'
  1580. var tokens = value.Split(new[] { ':' }, 3);
  1581. double secondsSum = 0;
  1582. var timeFactor = 3600;
  1583. foreach (var time in tokens)
  1584. {
  1585. double digit;
  1586. if (double.TryParse(time, NumberStyles.Any, UsCulture, out digit))
  1587. {
  1588. secondsSum += digit * timeFactor;
  1589. }
  1590. else
  1591. {
  1592. throw new ArgumentException("Invalid timeseek header");
  1593. }
  1594. timeFactor /= 60;
  1595. }
  1596. return TimeSpan.FromSeconds(secondsSum).Ticks;
  1597. }
  1598. /// <summary>
  1599. /// Gets the state.
  1600. /// </summary>
  1601. /// <param name="request">The request.</param>
  1602. /// <param name="cancellationToken">The cancellation token.</param>
  1603. /// <returns>StreamState.</returns>
  1604. protected async Task<StreamState> GetState(StreamRequest request, CancellationToken cancellationToken)
  1605. {
  1606. ParseDlnaHeaders(request);
  1607. if (!string.IsNullOrWhiteSpace(request.Params))
  1608. {
  1609. ParseParams(request);
  1610. }
  1611. var url = Request.PathInfo;
  1612. if (string.IsNullOrEmpty(request.AudioCodec))
  1613. {
  1614. request.AudioCodec = InferAudioCodec(url);
  1615. }
  1616. var state = new StreamState(MediaSourceManager, Logger, TranscodingJobType)
  1617. {
  1618. Request = request,
  1619. RequestedUrl = url,
  1620. UserAgent = Request.UserAgent
  1621. };
  1622. //if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
  1623. // (Request.UserAgent ?? string.Empty).IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 ||
  1624. // (Request.UserAgent ?? string.Empty).IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
  1625. //{
  1626. // state.SegmentLength = 6;
  1627. //}
  1628. if (state.VideoRequest != null)
  1629. {
  1630. if (!string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec))
  1631. {
  1632. state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
  1633. state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
  1634. }
  1635. }
  1636. if (!string.IsNullOrWhiteSpace(request.AudioCodec))
  1637. {
  1638. state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
  1639. state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToAudioCodec(i))
  1640. ?? state.SupportedAudioCodecs.FirstOrDefault();
  1641. }
  1642. if (!string.IsNullOrWhiteSpace(request.SubtitleCodec))
  1643. {
  1644. state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
  1645. state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToSubtitleCodec(i))
  1646. ?? state.SupportedSubtitleCodecs.FirstOrDefault();
  1647. }
  1648. var item = LibraryManager.GetItemById(request.Id);
  1649. state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
  1650. MediaSourceInfo mediaSource = null;
  1651. if (string.IsNullOrWhiteSpace(request.LiveStreamId))
  1652. {
  1653. TranscodingJob currentJob = !string.IsNullOrWhiteSpace(request.PlaySessionId) ?
  1654. ApiEntryPoint.Instance.GetTranscodingJob(request.PlaySessionId)
  1655. : null;
  1656. if (currentJob != null)
  1657. {
  1658. mediaSource = currentJob.MediaSource;
  1659. }
  1660. if (mediaSource == null)
  1661. {
  1662. var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(request.Id, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false)).ToList();
  1663. mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
  1664. ? mediaSources.First()
  1665. : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId));
  1666. if (mediaSource == null && string.Equals(request.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase))
  1667. {
  1668. mediaSource = mediaSources.First();
  1669. }
  1670. }
  1671. }
  1672. else
  1673. {
  1674. var liveStreamInfo = await MediaSourceManager.GetLiveStreamWithDirectStreamProvider(request.LiveStreamId, cancellationToken).ConfigureAwait(false);
  1675. mediaSource = liveStreamInfo.Item1;
  1676. state.DirectStreamProvider = liveStreamInfo.Item2;
  1677. }
  1678. var videoRequest = request as VideoStreamRequest;
  1679. AttachMediaSourceInfo(state, mediaSource, videoRequest, url);
  1680. var container = Path.GetExtension(state.RequestedUrl);
  1681. if (string.IsNullOrEmpty(container))
  1682. {
  1683. container = request.Static ?
  1684. state.InputContainer :
  1685. (Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.');
  1686. }
  1687. state.OutputContainer = (container ?? string.Empty).TrimStart('.');
  1688. state.OutputAudioBitrate = GetAudioBitrateParam(state.Request, state.AudioStream);
  1689. state.OutputAudioSampleRate = request.AudioSampleRate;
  1690. state.OutputAudioCodec = state.Request.AudioCodec;
  1691. state.OutputAudioChannels = GetNumAudioChannelsParam(state.Request, state.AudioStream, state.OutputAudioCodec);
  1692. if (videoRequest != null)
  1693. {
  1694. state.OutputVideoCodec = state.VideoRequest.VideoCodec;
  1695. state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
  1696. if (videoRequest != null)
  1697. {
  1698. TryStreamCopy(state, videoRequest);
  1699. }
  1700. if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
  1701. {
  1702. var resolution = ResolutionNormalizer.Normalize(
  1703. state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
  1704. state.OutputVideoBitrate.Value,
  1705. state.VideoStream == null ? null : state.VideoStream.Codec,
  1706. state.OutputVideoCodec,
  1707. videoRequest.MaxWidth,
  1708. videoRequest.MaxHeight);
  1709. videoRequest.MaxWidth = resolution.MaxWidth;
  1710. videoRequest.MaxHeight = resolution.MaxHeight;
  1711. }
  1712. ApplyDeviceProfileSettings(state);
  1713. }
  1714. else
  1715. {
  1716. ApplyDeviceProfileSettings(state);
  1717. }
  1718. state.OutputFilePath = GetOutputFilePath(state);
  1719. return state;
  1720. }
  1721. private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
  1722. {
  1723. if (state.VideoStream != null && CanStreamCopyVideo(state))
  1724. {
  1725. state.OutputVideoCodec = "copy";
  1726. }
  1727. else
  1728. {
  1729. // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
  1730. var auth = AuthorizationContext.GetAuthorizationInfo(Request);
  1731. if (!string.IsNullOrWhiteSpace(auth.UserId))
  1732. {
  1733. var user = UserManager.GetUserById(auth.UserId);
  1734. if (!user.Policy.EnableVideoPlaybackTranscoding)
  1735. {
  1736. state.OutputVideoCodec = "copy";
  1737. }
  1738. }
  1739. }
  1740. if (state.AudioStream != null && CanStreamCopyAudio(state, state.SupportedAudioCodecs))
  1741. {
  1742. state.OutputAudioCodec = "copy";
  1743. }
  1744. else
  1745. {
  1746. // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
  1747. var auth = AuthorizationContext.GetAuthorizationInfo(Request);
  1748. if (!string.IsNullOrWhiteSpace(auth.UserId))
  1749. {
  1750. var user = UserManager.GetUserById(auth.UserId);
  1751. if (!user.Policy.EnableAudioPlaybackTranscoding)
  1752. {
  1753. state.OutputAudioCodec = "copy";
  1754. }
  1755. }
  1756. }
  1757. }
  1758. private void AttachMediaSourceInfo(StreamState state,
  1759. MediaSourceInfo mediaSource,
  1760. VideoStreamRequest videoRequest,
  1761. string requestedUrl)
  1762. {
  1763. state.MediaPath = mediaSource.Path;
  1764. state.InputProtocol = mediaSource.Protocol;
  1765. state.InputContainer = mediaSource.Container;
  1766. state.InputFileSize = mediaSource.Size;
  1767. state.InputBitrate = mediaSource.Bitrate;
  1768. state.RunTimeTicks = mediaSource.RunTimeTicks;
  1769. state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
  1770. if (mediaSource.VideoType.HasValue)
  1771. {
  1772. state.VideoType = mediaSource.VideoType.Value;
  1773. }
  1774. state.IsoType = mediaSource.IsoType;
  1775. state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
  1776. if (mediaSource.Timestamp.HasValue)
  1777. {
  1778. state.InputTimestamp = mediaSource.Timestamp.Value;
  1779. }
  1780. state.InputProtocol = mediaSource.Protocol;
  1781. state.MediaPath = mediaSource.Path;
  1782. state.RunTimeTicks = mediaSource.RunTimeTicks;
  1783. state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
  1784. state.InputBitrate = mediaSource.Bitrate;
  1785. state.InputFileSize = mediaSource.Size;
  1786. state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
  1787. if (state.ReadInputAtNativeFramerate ||
  1788. mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
  1789. {
  1790. state.OutputAudioSync = "1000";
  1791. state.InputVideoSync = "-1";
  1792. state.InputAudioSync = "1";
  1793. }
  1794. if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase))
  1795. {
  1796. // Seeing some stuttering when transcoding wma to audio-only HLS
  1797. state.InputAudioSync = "1";
  1798. }
  1799. var mediaStreams = mediaSource.MediaStreams;
  1800. if (videoRequest != null)
  1801. {
  1802. if (string.IsNullOrEmpty(videoRequest.VideoCodec))
  1803. {
  1804. videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
  1805. }
  1806. state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
  1807. state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
  1808. state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
  1809. state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
  1810. if (state.SubtitleStream != null && !state.SubtitleStream.IsExternal)
  1811. {
  1812. state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal).ToList().IndexOf(state.SubtitleStream);
  1813. }
  1814. if (state.VideoStream != null && state.VideoStream.IsInterlaced)
  1815. {
  1816. state.DeInterlace = true;
  1817. }
  1818. EnforceResolutionLimit(state, videoRequest);
  1819. }
  1820. else
  1821. {
  1822. state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
  1823. }
  1824. state.MediaSource = mediaSource;
  1825. }
  1826. protected bool CanStreamCopyVideo(StreamState state)
  1827. {
  1828. var request = state.VideoRequest;
  1829. var videoStream = state.VideoStream;
  1830. if (videoStream.IsInterlaced)
  1831. {
  1832. return false;
  1833. }
  1834. if (videoStream.IsAnamorphic ?? false)
  1835. {
  1836. return false;
  1837. }
  1838. // Can't stream copy if we're burning in subtitles
  1839. if (request.SubtitleStreamIndex.HasValue)
  1840. {
  1841. if (request.SubtitleMethod == SubtitleDeliveryMethod.Encode)
  1842. {
  1843. return false;
  1844. }
  1845. }
  1846. if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
  1847. {
  1848. if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value && request.RequireAvc)
  1849. {
  1850. Logger.Debug("Cannot stream copy video. Stream is marked as not AVC");
  1851. return false;
  1852. }
  1853. }
  1854. // Source and target codecs must match
  1855. if (string.IsNullOrEmpty(videoStream.Codec) || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparer.OrdinalIgnoreCase))
  1856. {
  1857. return false;
  1858. }
  1859. // If client is requesting a specific video profile, it must match the source
  1860. if (!string.IsNullOrEmpty(request.Profile))
  1861. {
  1862. if (string.IsNullOrEmpty(videoStream.Profile))
  1863. {
  1864. //return false;
  1865. }
  1866. if (!string.IsNullOrEmpty(videoStream.Profile) && !string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
  1867. {
  1868. var currentScore = GetVideoProfileScore(videoStream.Profile);
  1869. var requestedScore = GetVideoProfileScore(request.Profile);
  1870. if (currentScore == -1 || currentScore > requestedScore)
  1871. {
  1872. return false;
  1873. }
  1874. }
  1875. }
  1876. // Video width must fall within requested value
  1877. if (request.MaxWidth.HasValue)
  1878. {
  1879. if (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value)
  1880. {
  1881. return false;
  1882. }
  1883. }
  1884. // Video height must fall within requested value
  1885. if (request.MaxHeight.HasValue)
  1886. {
  1887. if (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value)
  1888. {
  1889. return false;
  1890. }
  1891. }
  1892. // Video framerate must fall within requested value
  1893. var requestedFramerate = request.MaxFramerate ?? request.Framerate;
  1894. if (requestedFramerate.HasValue)
  1895. {
  1896. var videoFrameRate = videoStream.AverageFrameRate ?? videoStream.RealFrameRate;
  1897. if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value)
  1898. {
  1899. return false;
  1900. }
  1901. }
  1902. // Video bitrate must fall within requested value
  1903. if (request.VideoBitRate.HasValue)
  1904. {
  1905. if (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value)
  1906. {
  1907. return false;
  1908. }
  1909. }
  1910. if (request.MaxVideoBitDepth.HasValue)
  1911. {
  1912. if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > request.MaxVideoBitDepth.Value)
  1913. {
  1914. return false;
  1915. }
  1916. }
  1917. if (request.MaxRefFrames.HasValue)
  1918. {
  1919. if (videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > request.MaxRefFrames.Value)
  1920. {
  1921. return false;
  1922. }
  1923. }
  1924. // If a specific level was requested, the source must match or be less than
  1925. if (!string.IsNullOrEmpty(request.Level))
  1926. {
  1927. double requestLevel;
  1928. if (double.TryParse(request.Level, NumberStyles.Any, UsCulture, out requestLevel))
  1929. {
  1930. if (!videoStream.Level.HasValue)
  1931. {
  1932. //return false;
  1933. }
  1934. if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
  1935. {
  1936. return false;
  1937. }
  1938. }
  1939. }
  1940. return request.EnableAutoStreamCopy;
  1941. }
  1942. private int GetVideoProfileScore(string profile)
  1943. {
  1944. var list = new List<string>
  1945. {
  1946. "Constrained Baseline",
  1947. "Baseline",
  1948. "Extended",
  1949. "Main",
  1950. "High",
  1951. "Progressive High",
  1952. "Constrained High"
  1953. };
  1954. return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
  1955. }
  1956. protected virtual bool CanStreamCopyAudio(StreamState state, List<string> supportedAudioCodecs)
  1957. {
  1958. var request = state.VideoRequest;
  1959. var audioStream = state.AudioStream;
  1960. // Source and target codecs must match
  1961. if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase))
  1962. {
  1963. return false;
  1964. }
  1965. // Video bitrate must fall within requested value
  1966. if (request.AudioBitRate.HasValue)
  1967. {
  1968. if (!audioStream.BitRate.HasValue || audioStream.BitRate.Value <= 0)
  1969. {
  1970. return false;
  1971. }
  1972. if (audioStream.BitRate.Value > request.AudioBitRate.Value)
  1973. {
  1974. return false;
  1975. }
  1976. }
  1977. // Channels must fall within requested value
  1978. var channels = request.AudioChannels ?? request.MaxAudioChannels;
  1979. if (channels.HasValue)
  1980. {
  1981. if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
  1982. {
  1983. return false;
  1984. }
  1985. if (audioStream.Channels.Value > channels.Value)
  1986. {
  1987. return false;
  1988. }
  1989. }
  1990. // Sample rate must fall within requested value
  1991. if (request.AudioSampleRate.HasValue)
  1992. {
  1993. if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
  1994. {
  1995. return false;
  1996. }
  1997. if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
  1998. {
  1999. return false;
  2000. }
  2001. }
  2002. return request.EnableAutoStreamCopy;
  2003. }
  2004. private void ApplyDeviceProfileSettings(StreamState state)
  2005. {
  2006. var headers = Request.Headers.ToDictionary();
  2007. if (!string.IsNullOrWhiteSpace(state.Request.DeviceProfileId))
  2008. {
  2009. state.DeviceProfile = DlnaManager.GetProfile(state.Request.DeviceProfileId);
  2010. }
  2011. else
  2012. {
  2013. if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
  2014. {
  2015. var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
  2016. if (caps != null)
  2017. {
  2018. state.DeviceProfile = caps.DeviceProfile;
  2019. }
  2020. else
  2021. {
  2022. state.DeviceProfile = DlnaManager.GetProfile(headers);
  2023. }
  2024. }
  2025. }
  2026. var profile = state.DeviceProfile;
  2027. if (profile == null)
  2028. {
  2029. // Don't use settings from the default profile.
  2030. // Only use a specific profile if it was requested.
  2031. return;
  2032. }
  2033. var audioCodec = state.ActualOutputAudioCodec;
  2034. var videoCodec = state.ActualOutputVideoCodec;
  2035. var mediaProfile = state.VideoRequest == null ?
  2036. profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate) :
  2037. profile.GetVideoMediaProfile(state.OutputContainer,
  2038. audioCodec,
  2039. videoCodec,
  2040. state.OutputWidth,
  2041. state.OutputHeight,
  2042. state.TargetVideoBitDepth,
  2043. state.OutputVideoBitrate,
  2044. state.TargetVideoProfile,
  2045. state.TargetVideoLevel,
  2046. state.TargetFramerate,
  2047. state.TargetPacketLength,
  2048. state.TargetTimestamp,
  2049. state.IsTargetAnamorphic,
  2050. state.TargetRefFrames,
  2051. state.TargetVideoStreamCount,
  2052. state.TargetAudioStreamCount,
  2053. state.TargetVideoCodecTag,
  2054. state.IsTargetAVC);
  2055. if (mediaProfile != null)
  2056. {
  2057. state.MimeType = mediaProfile.MimeType;
  2058. }
  2059. if (!state.Request.Static)
  2060. {
  2061. var transcodingProfile = state.VideoRequest == null ?
  2062. profile.GetAudioTranscodingProfile(state.OutputContainer, audioCodec) :
  2063. profile.GetVideoTranscodingProfile(state.OutputContainer, audioCodec, videoCodec);
  2064. if (transcodingProfile != null)
  2065. {
  2066. state.EstimateContentLength = transcodingProfile.EstimateContentLength;
  2067. state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
  2068. state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
  2069. if (state.VideoRequest != null)
  2070. {
  2071. state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
  2072. state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
  2073. }
  2074. }
  2075. }
  2076. }
  2077. private async void ReportUsage(StreamState state)
  2078. {
  2079. try
  2080. {
  2081. await ReportUsageInternal(state).ConfigureAwait(false);
  2082. }
  2083. catch
  2084. {
  2085. }
  2086. }
  2087. private Task ReportUsageInternal(StreamState state)
  2088. {
  2089. if (!ServerConfigurationManager.Configuration.EnableAnonymousUsageReporting)
  2090. {
  2091. return Task.FromResult(true);
  2092. }
  2093. if (!MediaEncoder.IsDefaultEncoderPath)
  2094. {
  2095. return Task.FromResult(true);
  2096. }
  2097. return Task.FromResult(true);
  2098. //var dict = new Dictionary<string, string>();
  2099. //var outputAudio = GetAudioEncoder(state);
  2100. //if (!string.IsNullOrWhiteSpace(outputAudio))
  2101. //{
  2102. // dict["outputAudio"] = outputAudio;
  2103. //}
  2104. //var outputVideo = GetVideoEncoder(state);
  2105. //if (!string.IsNullOrWhiteSpace(outputVideo))
  2106. //{
  2107. // dict["outputVideo"] = outputVideo;
  2108. //}
  2109. //if (ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputAudio ?? string.Empty, StringComparer.OrdinalIgnoreCase) &&
  2110. // ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputVideo ?? string.Empty, StringComparer.OrdinalIgnoreCase))
  2111. //{
  2112. // return Task.FromResult(true);
  2113. //}
  2114. //dict["id"] = AppHost.SystemId;
  2115. //dict["type"] = state.VideoRequest == null ? "Audio" : "Video";
  2116. //var audioStream = state.AudioStream;
  2117. //if (audioStream != null && !string.IsNullOrWhiteSpace(audioStream.Codec))
  2118. //{
  2119. // dict["inputAudio"] = audioStream.Codec;
  2120. //}
  2121. //var videoStream = state.VideoStream;
  2122. //if (videoStream != null && !string.IsNullOrWhiteSpace(videoStream.Codec))
  2123. //{
  2124. // dict["inputVideo"] = videoStream.Codec;
  2125. //}
  2126. //var cert = GetType().Assembly.GetModules().First().GetSignerCertificate();
  2127. //if (cert != null)
  2128. //{
  2129. // dict["assemblySig"] = cert.GetCertHashString();
  2130. // dict["certSubject"] = cert.Subject ?? string.Empty;
  2131. // dict["certIssuer"] = cert.Issuer ?? string.Empty;
  2132. //}
  2133. //else
  2134. //{
  2135. // return Task.FromResult(true);
  2136. //}
  2137. //if (state.SupportedAudioCodecs.Count > 0)
  2138. //{
  2139. // dict["supportedAudioCodecs"] = string.Join(",", state.SupportedAudioCodecs.ToArray());
  2140. //}
  2141. //var auth = AuthorizationContext.GetAuthorizationInfo(Request);
  2142. //dict["appName"] = auth.Client ?? string.Empty;
  2143. //dict["appVersion"] = auth.Version ?? string.Empty;
  2144. //dict["device"] = auth.Device ?? string.Empty;
  2145. //dict["deviceId"] = auth.DeviceId ?? string.Empty;
  2146. //dict["context"] = "streaming";
  2147. ////Logger.Info(JsonSerializer.SerializeToString(dict));
  2148. //if (!ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputAudio ?? string.Empty, StringComparer.OrdinalIgnoreCase))
  2149. //{
  2150. // var list = ServerConfigurationManager.Configuration.CodecsUsed.ToList();
  2151. // list.Add(outputAudio);
  2152. // ServerConfigurationManager.Configuration.CodecsUsed = list.ToArray();
  2153. //}
  2154. //if (!ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputVideo ?? string.Empty, StringComparer.OrdinalIgnoreCase))
  2155. //{
  2156. // var list = ServerConfigurationManager.Configuration.CodecsUsed.ToList();
  2157. // list.Add(outputVideo);
  2158. // ServerConfigurationManager.Configuration.CodecsUsed = list.ToArray();
  2159. //}
  2160. //ServerConfigurationManager.SaveConfiguration();
  2161. ////Logger.Info(JsonSerializer.SerializeToString(dict));
  2162. //var options = new HttpRequestOptions()
  2163. //{
  2164. // Url = "https://mb3admin.com/admin/service/transcoding/report",
  2165. // CancellationToken = CancellationToken.None,
  2166. // LogRequest = false,
  2167. // LogErrors = false,
  2168. // BufferContent = false
  2169. //};
  2170. //options.RequestContent = JsonSerializer.SerializeToString(dict);
  2171. //options.RequestContentType = "application/json";
  2172. //return HttpClient.Post(options);
  2173. }
  2174. /// <summary>
  2175. /// Adds the dlna headers.
  2176. /// </summary>
  2177. /// <param name="state">The state.</param>
  2178. /// <param name="responseHeaders">The response headers.</param>
  2179. /// <param name="isStaticallyStreamed">if set to <c>true</c> [is statically streamed].</param>
  2180. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  2181. protected void AddDlnaHeaders(StreamState state, IDictionary<string, string> responseHeaders, bool isStaticallyStreamed)
  2182. {
  2183. var profile = state.DeviceProfile;
  2184. var transferMode = GetHeader("transferMode.dlna.org");
  2185. responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
  2186. responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*";
  2187. if (string.Equals(GetHeader("getMediaInfo.sec"), "1", StringComparison.OrdinalIgnoreCase))
  2188. {
  2189. if (state.RunTimeTicks.HasValue)
  2190. {
  2191. var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds;
  2192. responseHeaders["MediaInfo.sec"] = string.Format("SEC_Duration={0};", Convert.ToInt32(ms).ToString(CultureInfo.InvariantCulture));
  2193. }
  2194. }
  2195. if (state.RunTimeTicks.HasValue && !isStaticallyStreamed && profile != null)
  2196. {
  2197. AddTimeSeekResponseHeaders(state, responseHeaders);
  2198. }
  2199. if (profile == null)
  2200. {
  2201. profile = DlnaManager.GetDefaultProfile();
  2202. }
  2203. var audioCodec = state.ActualOutputAudioCodec;
  2204. if (state.VideoRequest == null)
  2205. {
  2206. responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile)
  2207. .BuildAudioHeader(
  2208. state.OutputContainer,
  2209. audioCodec,
  2210. state.OutputAudioBitrate,
  2211. state.OutputAudioSampleRate,
  2212. state.OutputAudioChannels,
  2213. isStaticallyStreamed,
  2214. state.RunTimeTicks,
  2215. state.TranscodeSeekInfo
  2216. );
  2217. }
  2218. else
  2219. {
  2220. var videoCodec = state.ActualOutputVideoCodec;
  2221. responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile)
  2222. .BuildVideoHeader(
  2223. state.OutputContainer,
  2224. videoCodec,
  2225. audioCodec,
  2226. state.OutputWidth,
  2227. state.OutputHeight,
  2228. state.TargetVideoBitDepth,
  2229. state.OutputVideoBitrate,
  2230. state.TargetTimestamp,
  2231. isStaticallyStreamed,
  2232. state.RunTimeTicks,
  2233. state.TargetVideoProfile,
  2234. state.TargetVideoLevel,
  2235. state.TargetFramerate,
  2236. state.TargetPacketLength,
  2237. state.TranscodeSeekInfo,
  2238. state.IsTargetAnamorphic,
  2239. state.TargetRefFrames,
  2240. state.TargetVideoStreamCount,
  2241. state.TargetAudioStreamCount,
  2242. state.TargetVideoCodecTag,
  2243. state.IsTargetAVC
  2244. ).FirstOrDefault() ?? string.Empty;
  2245. }
  2246. foreach (var item in responseHeaders)
  2247. {
  2248. Request.Response.AddHeader(item.Key, item.Value);
  2249. }
  2250. }
  2251. private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)
  2252. {
  2253. var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture);
  2254. var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(UsCulture);
  2255. responseHeaders["TimeSeekRange.dlna.org"] = string.Format("npt={0}-{1}/{1}", startSeconds, runtimeSeconds);
  2256. responseHeaders["X-AvailableSeekRange"] = string.Format("1 npt={0}-{1}", startSeconds, runtimeSeconds);
  2257. }
  2258. /// <summary>
  2259. /// Enforces the resolution limit.
  2260. /// </summary>
  2261. /// <param name="state">The state.</param>
  2262. /// <param name="videoRequest">The video request.</param>
  2263. private void EnforceResolutionLimit(StreamState state, VideoStreamRequest videoRequest)
  2264. {
  2265. // Switch the incoming params to be ceilings rather than fixed values
  2266. videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
  2267. videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
  2268. videoRequest.Width = null;
  2269. videoRequest.Height = null;
  2270. }
  2271. protected string GetInputModifier(StreamState state, bool genPts = true)
  2272. {
  2273. var inputModifier = string.Empty;
  2274. var probeSize = GetProbeSizeArgument(state);
  2275. inputModifier += " " + probeSize;
  2276. inputModifier = inputModifier.Trim();
  2277. var userAgentParam = GetUserAgentParam(state);
  2278. if (!string.IsNullOrWhiteSpace(userAgentParam))
  2279. {
  2280. inputModifier += " " + userAgentParam;
  2281. }
  2282. inputModifier = inputModifier.Trim();
  2283. inputModifier += " " + GetFastSeekCommandLineParameter(state.Request);
  2284. inputModifier = inputModifier.Trim();
  2285. //inputModifier += " -fflags +genpts+ignidx+igndts";
  2286. if (state.VideoRequest != null && genPts)
  2287. {
  2288. inputModifier += " -fflags +genpts";
  2289. }
  2290. if (!string.IsNullOrEmpty(state.InputAudioSync))
  2291. {
  2292. inputModifier += " -async " + state.InputAudioSync;
  2293. }
  2294. if (!string.IsNullOrEmpty(state.InputVideoSync))
  2295. {
  2296. inputModifier += " -vsync " + state.InputVideoSync;
  2297. }
  2298. if (state.ReadInputAtNativeFramerate)
  2299. {
  2300. inputModifier += " -re";
  2301. }
  2302. var videoDecoder = GetVideoDecoder(state);
  2303. if (!string.IsNullOrWhiteSpace(videoDecoder))
  2304. {
  2305. inputModifier += " " + videoDecoder;
  2306. }
  2307. if (state.VideoRequest != null)
  2308. {
  2309. // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
  2310. if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase) && state.VideoRequest.CopyTimestamps)
  2311. {
  2312. //inputModifier += " -noaccurate_seek";
  2313. }
  2314. if (!string.IsNullOrWhiteSpace(state.InputContainer))
  2315. {
  2316. var inputFormat = GetInputFormat(state.InputContainer);
  2317. if (!string.IsNullOrWhiteSpace(inputFormat))
  2318. {
  2319. inputModifier += " -f " + inputFormat;
  2320. }
  2321. }
  2322. if (state.RunTimeTicks.HasValue)
  2323. {
  2324. foreach (var stream in state.MediaSource.MediaStreams)
  2325. {
  2326. if (!stream.IsExternal && stream.Type != MediaStreamType.Subtitle)
  2327. {
  2328. if (!string.IsNullOrWhiteSpace(stream.Codec) && stream.Index != -1)
  2329. {
  2330. var decoder = GetDecoderFromCodec(stream.Codec);
  2331. if (!string.IsNullOrWhiteSpace(decoder))
  2332. {
  2333. inputModifier += " -codec:" + stream.Index.ToString(UsCulture) + " " + decoder;
  2334. }
  2335. }
  2336. }
  2337. }
  2338. }
  2339. }
  2340. return inputModifier;
  2341. }
  2342. private string GetInputFormat(string container)
  2343. {
  2344. if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase))
  2345. {
  2346. return "matroska";
  2347. }
  2348. return container;
  2349. }
  2350. private string GetDecoderFromCodec(string codec)
  2351. {
  2352. return null;
  2353. //if (string.Equals(codec, "mp2", StringComparison.OrdinalIgnoreCase))
  2354. //{
  2355. // return null;
  2356. //}
  2357. //if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase))
  2358. //{
  2359. // return null;
  2360. //}
  2361. //return codec;
  2362. }
  2363. /// <summary>
  2364. /// Infers the audio codec based on the url
  2365. /// </summary>
  2366. /// <param name="url">The URL.</param>
  2367. /// <returns>System.Nullable{AudioCodecs}.</returns>
  2368. private string InferAudioCodec(string url)
  2369. {
  2370. var ext = Path.GetExtension(url);
  2371. if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase))
  2372. {
  2373. return "mp3";
  2374. }
  2375. if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase))
  2376. {
  2377. return "aac";
  2378. }
  2379. if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase))
  2380. {
  2381. return "wma";
  2382. }
  2383. if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase))
  2384. {
  2385. return "vorbis";
  2386. }
  2387. if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase))
  2388. {
  2389. return "vorbis";
  2390. }
  2391. if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
  2392. {
  2393. return "vorbis";
  2394. }
  2395. if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
  2396. {
  2397. return "vorbis";
  2398. }
  2399. if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase))
  2400. {
  2401. return "vorbis";
  2402. }
  2403. return "copy";
  2404. }
  2405. /// <summary>
  2406. /// Infers the video codec.
  2407. /// </summary>
  2408. /// <param name="url">The URL.</param>
  2409. /// <returns>System.Nullable{VideoCodecs}.</returns>
  2410. private string InferVideoCodec(string url)
  2411. {
  2412. var ext = Path.GetExtension(url);
  2413. if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase))
  2414. {
  2415. return "wmv";
  2416. }
  2417. if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
  2418. {
  2419. return "vpx";
  2420. }
  2421. if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
  2422. {
  2423. return "theora";
  2424. }
  2425. if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase))
  2426. {
  2427. return "h264";
  2428. }
  2429. return "copy";
  2430. }
  2431. }
  2432. }