Browse Source

produce valid mpeg dash manifest

Luke Pulverenti 10 years ago
parent
commit
52776df012

+ 33 - 13
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -825,6 +825,23 @@ namespace MediaBrowser.Api.Playback
             return MediaEncoder.GetInputArgument(inputPath, protocol);
             return MediaEncoder.GetInputArgument(inputPath, protocol);
         }
         }
 
 
+        private MediaProtocol GetProtocol(string path)
+        {
+            if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
+            {
+                return MediaProtocol.Http;
+            }
+            if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase))
+            {
+                return MediaProtocol.Rtsp;
+            }
+            if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase))
+            {
+                return MediaProtocol.Rtmp;
+            }
+            return MediaProtocol.File;
+        }
+
         private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
         private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
         {
         {
             if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
             if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
@@ -845,16 +862,15 @@ namespace MediaBrowser.Api.Playback
                     if (!string.IsNullOrEmpty(streamInfo.Path))
                     if (!string.IsNullOrEmpty(streamInfo.Path))
                     {
                     {
                         state.MediaPath = streamInfo.Path;
                         state.MediaPath = streamInfo.Path;
-                        state.InputProtocol = MediaProtocol.File;
-
-                        await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
                     }
                     }
                     else if (!string.IsNullOrEmpty(streamInfo.Url))
                     else if (!string.IsNullOrEmpty(streamInfo.Url))
                     {
                     {
                         state.MediaPath = streamInfo.Url;
                         state.MediaPath = streamInfo.Url;
-                        state.InputProtocol = MediaProtocol.Http;
                     }
                     }
 
 
+                    await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
+                    
+                    state.InputProtocol = GetProtocol(state.MediaPath);
                     AttachMediaStreamInfo(state, streamInfo.MediaStreams, state.VideoRequest, state.RequestedUrl);
                     AttachMediaStreamInfo(state, streamInfo.MediaStreams, state.VideoRequest, state.RequestedUrl);
                     checkCodecs = true;
                     checkCodecs = true;
                 }
                 }
@@ -869,16 +885,15 @@ namespace MediaBrowser.Api.Playback
                     if (!string.IsNullOrEmpty(streamInfo.Path))
                     if (!string.IsNullOrEmpty(streamInfo.Path))
                     {
                     {
                         state.MediaPath = streamInfo.Path;
                         state.MediaPath = streamInfo.Path;
-                        state.InputProtocol = MediaProtocol.File;
-
-                        await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
                     }
                     }
                     else if (!string.IsNullOrEmpty(streamInfo.Url))
                     else if (!string.IsNullOrEmpty(streamInfo.Url))
                     {
                     {
                         state.MediaPath = streamInfo.Url;
                         state.MediaPath = streamInfo.Url;
-                        state.InputProtocol = MediaProtocol.Http;
                     }
                     }
 
 
+                    await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
+                    
+                    state.InputProtocol = GetProtocol(state.MediaPath);
                     AttachMediaStreamInfo(state, streamInfo.MediaStreams, state.VideoRequest, state.RequestedUrl);
                     AttachMediaStreamInfo(state, streamInfo.MediaStreams, state.VideoRequest, state.RequestedUrl);
                     checkCodecs = true;
                     checkCodecs = true;
                 }
                 }
@@ -991,6 +1006,16 @@ namespace MediaBrowser.Api.Playback
                 await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
                 await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
             }
             }
 
 
+            if (state.IsInputVideo && transcodingJob.Type == Api.TranscodingJobType.Progressive)
+            {
+                await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false);
+
+                if (state.ReadInputAtNativeFramerate)
+                {
+                    await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false);
+                }
+            }
+
             return transcodingJob;
             return transcodingJob;
         }
         }
 
 
@@ -1610,11 +1635,6 @@ namespace MediaBrowser.Api.Playback
                     {
                     {
                         state.InputTimestamp = mediaSource.Timestamp.Value;
                         state.InputTimestamp = mediaSource.Timestamp.Value;
                     }
                     }
-
-                    if (video.IsShortcut)
-                    {
-                        state.MediaPath = File.ReadAllText(video.Path);
-                    }
                 }
                 }
 
 
                 state.RunTimeTicks = mediaSource.RunTimeTicks;
                 state.RunTimeTicks = mediaSource.RunTimeTicks;

+ 103 - 10
MediaBrowser.Api/Playback/Hls/MpegDashService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using System.Security;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
@@ -41,7 +42,7 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <value>The segment id.</value>
         /// <value>The segment id.</value>
         public string SegmentId { get; set; }
         public string SegmentId { get; set; }
     }
     }
-    
+
     public class MpegDashService : BaseHlsService
     public class MpegDashService : BaseHlsService
     {
     {
         protected INetworkManager NetworkManager { get; private set; }
         protected INetworkManager NetworkManager { get; private set; }
@@ -104,8 +105,9 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             var duration = "PT0H02M11.00S";
             var duration = "PT0H02M11.00S";
 
 
+            builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
             builder.AppendFormat(
             builder.AppendFormat(
-                "<MPD type=\"static\" minBufferTime=\"PT2S\" mediaPresentationDuration=\"{0}\" profiles=\"urn:mpeg:dash:profile:mp2t-simple:2011\" xmlns=\"urn:mpeg:DASH:schema:MPD:2011\" maxSegmentDuration=\"PT{1}S\">",
+                "<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:mpeg:dash:schema:mpd:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" minBufferTime=\"PT2.00S\" mediaPresentationDuration=\"{0}\" maxSegmentDuration=\"PT{1}S\" type=\"static\" profiles=\"urn:mpeg:dash:profile:mp2t-simple:2011\">",
                 duration,
                 duration,
                 state.SegmentLength.ToString(CultureInfo.InvariantCulture));
                 state.SegmentLength.ToString(CultureInfo.InvariantCulture));
 
 
@@ -116,9 +118,13 @@ namespace MediaBrowser.Api.Playback.Hls
             builder.Append("<AdaptationSet segmentAlignment=\"true\">");
             builder.Append("<AdaptationSet segmentAlignment=\"true\">");
 
 
             builder.Append("<ContentComponent id=\"1\" contentType=\"video\"/>");
             builder.Append("<ContentComponent id=\"1\" contentType=\"video\"/>");
-            builder.Append("<ContentComponent id=\"2\" contentType=\"audio\" lang=\"eng\"/>");
 
 
-            builder.Append(GetRepresentationOpenElement(state));
+            var lang = state.AudioStream != null ? state.AudioStream.Language : null;
+            if (string.IsNullOrWhiteSpace(lang)) lang = "und";
+
+            builder.AppendFormat("<ContentComponent id=\"2\" contentType=\"audio\" lang=\"{0}\"/>", lang);
+
+            builder.Append(GetRepresentationOpenElement(state, lang));
 
 
             AppendSegmentList(state, builder);
             AppendSegmentList(state, builder);
 
 
@@ -131,10 +137,97 @@ namespace MediaBrowser.Api.Playback.Hls
             return builder.ToString();
             return builder.ToString();
         }
         }
 
 
-        private string GetRepresentationOpenElement(StreamState state)
+        private string GetRepresentationOpenElement(StreamState state, string language)
         {
         {
-            return
-                "<Representation id=\"1\" mimeType=\"video/mp2t\" codecs=\"avc1.640028,mp4a.40.02\" width=\"1280\" height=\"1024\" sampleRate=\"44100\" numChannels=\"2\" lang=\"und\" startWithSAP=\"1\" bandwidth=\"317599\">";
+            var codecs = GetVideoCodecDescriptor(state) + "," + GetAudioCodecDescriptor(state);
+
+            var xml = "<Representation id=\"1\" mimeType=\"video/mp2t\" startWithSAP=\"1\" codecs=\"" + codecs + "\"";
+
+            if (state.OutputWidth.HasValue)
+            {
+                xml += " width=\"" + state.OutputWidth.Value.ToString(UsCulture) + "\"";
+            }
+            if (state.OutputHeight.HasValue)
+            {
+                xml += " height=\"" + state.OutputHeight.Value.ToString(UsCulture) + "\"";
+            }
+            if (state.OutputAudioSampleRate.HasValue)
+            {
+                xml += " sampleRate=\"" + state.OutputAudioSampleRate.Value.ToString(UsCulture) + "\"";
+            }
+
+            if (state.TotalOutputBitrate.HasValue)
+            {
+                xml += " bandwidth=\"" + state.TotalOutputBitrate.Value.ToString(UsCulture) + "\"";
+            }
+
+            xml += ">";
+
+            return xml;
+        }
+
+        private string GetVideoCodecDescriptor(StreamState state)
+        {
+            // https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
+
+            var level = state.TargetVideoLevel ?? 0;
+            var profile = state.TargetVideoProfile ?? string.Empty;
+
+            if (profile.IndexOf("high", StringComparison.OrdinalIgnoreCase) != -1)
+            {
+                if (level >= 4.1)
+                {
+                    return "avc1.640028";
+                }
+
+                if (level >= 4)
+                {
+                    return "avc1.640028";
+                }
+
+                return "avc1.64001f";
+            }
+
+            if (profile.IndexOf("main", StringComparison.OrdinalIgnoreCase) != -1)
+            {
+                if (level >= 4)
+                {
+                    return "avc1.4d0028";
+                }
+
+                if (level >= 3.1)
+                {
+                    return "avc1.4d001f";
+                }
+
+                return "avc1.4d001e";
+            }
+
+            if (level >= 3.1)
+            {
+                return "avc1.42001f";
+            }
+
+            return "avc1.42001e";
+        }
+
+        private string GetAudioCodecDescriptor(StreamState state)
+        {
+            // https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
+
+            if (string.Equals(state.OutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
+            {
+                return "mp4a.40.34";
+            }
+
+            // AAC 5ch
+            if (state.OutputAudioChannels.HasValue && state.OutputAudioChannels.Value >= 5)
+            {
+                return "mp4a.40.5";
+            }
+
+            // AAC 2ch
+            return "mp4a.40.2";
         }
         }
 
 
         public object Get(GetDashSegment request)
         public object Get(GetDashSegment request)
@@ -147,7 +240,7 @@ namespace MediaBrowser.Api.Playback.Hls
             var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds;
             var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds;
 
 
             builder.Append("<SegmentList timescale=\"1000\" duration=\"10000\">");
             builder.Append("<SegmentList timescale=\"1000\" duration=\"10000\">");
-            
+
             var queryStringIndex = Request.RawUrl.IndexOf('?');
             var queryStringIndex = Request.RawUrl.IndexOf('?');
             var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
             var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
 
 
@@ -155,7 +248,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             while (seconds > 0)
             while (seconds > 0)
             {
             {
-                builder.AppendFormat("<SegmentURL media=\"{0}.ts{1}\"/>", index.ToString(UsCulture), queryString);
+                builder.AppendFormat("<SegmentURL media=\"{0}.ts{1}\"/>", index.ToString(UsCulture), SecurityElement.Escape(queryString));
 
 
                 seconds -= state.SegmentLength;
                 seconds -= state.SegmentLength;
                 index++;
                 index++;