Example #1
0
 /// <summary>
 /// This function is used to intialize a heuristics module. It must be implemented by all derived classes.
 /// </summary>
 /// <param name="manifestInfo">The manifest for the stream we are reading</param>
 /// <param name="playbackInfo">The playback info for the media element</param>
 /// <param name="adInsertPoints">Optional expected points for advertising. Can be null</param>
 public abstract void Initialize(ManifestInfo manifestInfo, PlaybackInfo playbackInfo, AdInsertionPoint[] adInsertPoints);
        /// <summary>
        /// This function reads the manfiest file from the given stream
        /// </summary>
        /// <param name="manifestStream">The manifest file to read</param>
        private void DoOpen(Stream manifestStream)
        {
            // Check for errors
            if (null == manifestStream)
            {
                RaiseError(string.Format(CultureInfo.InvariantCulture, Errors.ManifestFailureError, m_manifestSourceUrl));
                return;
            }

            // Parse the manifest file using our current parser
            m_manifestInfo = ManifestParser.ParseManifest(manifestStream, m_manifestAbsoluteUrl);
            manifestStream.Close();

            // Heuristics modules can tell us to either request a new download, or to pause
            // all of our current downloads. Either way, we need to listen for those events.
            m_heuristics.RequestDownload += new EventHandler<Heuristics.RequestDownloadEventArgs>(StartChunkDownload);
            m_heuristics.DownloadsPaused += new EventHandler<Heuristics.DownloadsPausedEventArgs>(FireDownloadsPaused);

            // Initialize our heuristics module. Convert our ad points into an array
            AdInsertionPoint[] adArray = m_adInsertionPoints.ToArray();
            m_heuristics.Initialize(m_manifestInfo, m_playbackInfo, adArray);

            // Set our state to opened
            m_state = State.Opened;

            // Report back to our media element that we have completed opening the media
            ReportOpenMediaCompleted(m_manifestInfo.MediaAttributes, m_manifestInfo.MediaStreamDescriptions);

            // Attach our playback info to the media element
            if (m_playbackInfo != null)
            {
                m_playbackInfo.Attach();
            }
        }
Example #3
0
        /// <summary>
        /// Implements Heuristics.Initialize(). Called to initialize this heuristics module with
        /// the given manifest file and optional adverting insertion points
        /// </summary>
        /// <param name="manifestInfo">The manifest of the stream we are using</param>
        /// <param name="playbackInfo">The playback info for the media element</param>
        /// <param name="adInsertPoints">Optional ad insertion points. Can be null</param>
        public override void Initialize(ManifestInfo manifestInfo, PlaybackInfo playbackInfo, AdInsertionPoint[] adInsertPoints)
        {
            Tracer.Trace(TraceChannel.MSS, "MediaSource instance {0}, sessionID: {1}", InstanceId.ToString("B"), sm_sessionId.ToString("B"));

            // Store our manifest info
            m_manifestInfo = manifestInfo;
            m_playbackInfo = playbackInfo;

            // We only support 1 audio and 1 video stream right now, so let's throw an exception
            // if someone tries to create us with more than that
            int numStreams = m_manifestInfo.NumberOfStreams;
            if (numStreams > 2)
            {
                throw new AdaptiveStreamingException("We only support 1 audio and 1 video stream at this time");
            }

            // Copy the add insertion points over
            m_adInsertPoints = new AdInsertionPoints();
            foreach (AdInsertionPoint ad in adInsertPoints)
            {
                m_adInsertPoints.Add(ad);
            }

            // Allocate some internal variables
            m_heuristicsMode = new HeuristicsMode[numStreams];
            m_fixedBitrateIndex = new int[numStreams];
            m_mediaStreamTypes = new MediaStreamType[numStreams];
            m_maxDownloadsPerStream = new int[numStreams];
            m_numDownloadsInProgress = new int[numStreams];
            m_nextChunkId = new int[numStreams];

            // Go through each stream in our manifest and do per-stream configuration
            int numVideoStreams = 0, numAudioStreams = 0;
            for (int i = 0; i < numStreams; i++)
            {
                StreamInfo streamInfo = m_manifestInfo.GetStreamInfoForStream(i);

                // Initialize some of the arrays we just created
                m_mediaStreamTypes[i] = streamInfo.MediaType;
                m_fixedBitrateIndex[i] = 0;
                m_maxDownloadsPerStream[i] = GlobalMaxDownloadsPerStream;
                m_numDownloadsInProgress[i] = 0;
                m_nextChunkId[i] = 0;

                // If we are a video stream, then we need to set our heuristics mode
                // to Full
                switch (streamInfo.MediaType)
                {
                    case MediaStreamType.Audio:
                        m_heuristicsMode[i] = HeuristicsMode.FixedRate;
                        ++numAudioStreams;
                        break;
                    case MediaStreamType.Video:
                        m_heuristicsMode[i] = HeuristicsMode.Full;
                        ++numVideoStreams;
                        break;
                    default:
                        throw new AdaptiveStreamingException(Errors.NonVideoOrAudioStreamsNotSupportedError);
                }

                // Configure this stream
                InitializeHeuristicsForStream(
                    streamInfo.StreamId,
                    (double)m_maxBufferSizeInMs / 1000,
                    m_manifestInfo.GetStreamInfoForStream(i).Bitrates.Count,
                    m_manifestInfo.GetStreamInfoForStream(i).GetBitrateArray());
            }

            // Make sure we found only 1 video and 1 audio stream
            if (numVideoStreams != 1)
            {
                throw new AdaptiveStreamingException(Errors.IncorrectNumberOfVideoStreamsError);
            }

            // Start scheduling our downloads
            ScheduleDownloads();
        }
        /// <summary>
        /// Default implementation of the ParseManifest function.
        /// </summary>
        /// <param name="manifestStream">The manifest stream to parse</param>
        /// <param name="manifestUrl">The url of the stream we are parsing</param>
        /// <returns>A ManifestInfo representing this manifest</returns>
        public override ManifestInfo ParseManifest(Stream manifestStream, Uri manifestUrl)
        {
            ManifestInfo info = new ManifestInfo();

            try
            {
                int streamId = 0;
                XmlReader manifest = XmlReader.Create(manifestStream);
                if (!manifest.Read())
                {
                    throw new AdaptiveStreamingException("Manifest does not have a single parsable element.");
                }

                // Check to make sure the first element is <MediaIndex>
                if (!manifest.IsStartElement(ManifestRootElement))
                {
                    throw new AdaptiveStreamingException("Manifest root element must be <" +ManifestRootElement + ">");
                }

                // Get manifest version, do not overwrite -1 if not present
                string major = manifest.GetAttribute(ManifestMajorVersionAttribute);
                string minor = manifest.GetAttribute(ManifestMinorVersionAttribute);
                if (major != null)
                {
                    info.ManifestMajorVersion = Convert.ToInt32(major, CultureInfo.InvariantCulture);
                }

                if (major != null)
                {
                    info.ManifestMinorVersion = Convert.ToInt32(minor, CultureInfo.InvariantCulture);
                }

                // Pull the duration out of the maifest
                string duration = manifest.GetAttribute(ManifestDurationAttribute);

                if (duration != null)
                {
                    info.MediaAttributes.Add(MediaSourceAttributesKeys.Duration, duration.Trim());
                }
                else
                {
                    throw new AdaptiveStreamingException("Manifest root must contain a duration attribute");
                }

                // Base URL of the manifest for chunks, we basically cut the manifest file name, e.g. "mbr/manifest.elive" will become "mbr/"
                string manifestBaseUrl = string.Empty;
                int cut1 = manifestUrl.AbsoluteUri.LastIndexOf('/');
                int cut2 = manifestUrl.AbsoluteUri.LastIndexOf('\\');

                if (cut1 < cut2)
                {
                    cut1 = cut2;
                }

                if (cut1 >= 0)
                {
                    manifestBaseUrl = manifestUrl.AbsoluteUri.Substring(0, cut1 + 1);
                }

                while (manifest.Read())
                {
                    if (manifest.IsStartElement(StreamIndexElement))
                    {
                        StreamInfo si = ParseStreamInfo(manifest, streamId, manifestBaseUrl, info);
                        if (si != null)
                        {
                            info.AddStream(si);
                            streamId++;
                        }
                    }

                    // We explicitly ignore content that we don't understand, as well as whitespace, comments etc.
                }

                if (streamId < 1)
                {
                    throw new AdaptiveStreamingException("At least one media stream is needed, none are declared in the manifest.");
                }

                info.Valid = true;
            }
            catch (AdaptiveStreamingException)
            {
                info.Valid = false;
                throw;
            }
            finally
            {
                manifestStream.Close();
            }

            return info;
        }
        /// <summary>
        /// Parse our text streams
        /// </summary>
        /// <param name="manifest">the xml we are parsing</param>
        /// <param name="manifestInfo">the manifest info object we are returning</param>
        private static void ParseTextStream(XmlReader manifest, ManifestInfo manifestInfo)
        {
            // Parse all of the entries
            while (manifest.Read())
            {
                if (manifest.Name.Equals("Marker"))
                {
                    string markerTime = manifest.GetAttribute("Time");
                    string markerValue = manifest.GetAttribute("Value");

                    TimelineMarker marker = new TimelineMarker();
                    marker.Type = MarkerType;
                    marker.Time = new TimeSpan((long)Convert.ToUInt64(markerTime, CultureInfo.InvariantCulture));
                    marker.Text = markerValue;
                    manifestInfo.Markers.Add(marker);
                }
                else if (manifest.Name.Equals("ScriptCommand"))
                {
                    string markerTime = manifest.GetAttribute("Time");
                    string markerType = manifest.GetAttribute("Type");
                    string markerCommand = manifest.GetAttribute("Command");

                    TimelineMarker marker = new TimelineMarker();
                    marker.Type = markerType;
                    marker.Time = new TimeSpan((long)Convert.ToUInt64(markerTime, CultureInfo.InvariantCulture));
                    marker.Text = markerCommand;
                    manifestInfo.Markers.Add(marker);
                }
                else if (manifest.Name.Equals("StreamIndex"))
                {
                    break;
                }
            }
        }
        /// <summary>
        /// Parse the stream section of the manifest
        /// </summary>
        /// <param name="manifest">The XML dom of the stream section in the manifest</param>
        /// <param name="streamId">The Id of the stream we are parsing</param>
        /// <param name="manifestBaseUrl">the url of the manifest we are parsing</param>
        /// <param name="manifestInfo">the manifest we are parsing</param>
        /// <returns>A StreamInfo describing the stream at streamId, or null if one was not found</returns>
        private static StreamInfo ParseStreamInfo(XmlReader manifest, int streamId, string manifestBaseUrl, ManifestInfo manifestInfo)
        {
            string mediaTypeStr = manifest.GetAttribute(StreamIndexTypeAttribute);
            bool bHaveFirstBitrate = false;

            // Pick out text types since we handle those separately
            if (mediaTypeStr.ToUpper(CultureInfo.InvariantCulture).Equals("TEXT"))
            {
                // Parse the text stream and return null
                ParseTextStream(manifest, manifestInfo);
                return null;
            }

            string baseUrl = manifest.GetAttribute(StreamIndexUrlAttribute);
            int numberOfChunks = Convert.ToInt32(manifest.GetAttribute(StreamIndexChunksAttribute), CultureInfo.InvariantCulture);
            if (mediaTypeStr == null || baseUrl == null || numberOfChunks < 1)
            {
                throw new AdaptiveStreamingException("Stream description in the manifest " + streamId.ToString(CultureInfo.InvariantCulture) + " is missing mandatory attributes (media type, subtype, base URL or number of chunks)");
            }

            MediaStreamType mediaType = mediaTypeStr.ToUpper(CultureInfo.InvariantCulture).Equals("VIDEO") ? MediaStreamType.Video : mediaTypeStr.ToUpper(CultureInfo.InvariantCulture).Equals("AUDIO") ? MediaStreamType.Audio : MediaStreamType.Script;
            if (mediaType == MediaStreamType.Script)
            {
                throw new AdaptiveStreamingException("Stream media type in manifest may be 'audio' or 'video' only");
            }

            if (!baseUrl.ToUpper(CultureInfo.InvariantCulture).StartsWith("HTTP://", StringComparison.OrdinalIgnoreCase))
            {
                baseUrl = manifestBaseUrl + baseUrl;
            }

            // Get the language attribute
            string language = manifest.GetAttribute(StreamIndexLanguageAttribute);

            if (language == null)
            {
                language = string.Empty;
            }

            StreamInfo info = new StreamInfo(baseUrl, language, numberOfChunks, mediaType, streamId);
            ulong maxBitrate = 0;
            int displayAspectRatioWidth = 0;
            int displayAspectRatioHeight = 0;
            int maxBitrateWidth = 0;
            int maxBitrateHeight = 0;
            bool bIsVideoStream = true;

            while (manifest.Read())
            {
                // Get the available bitrates
                if (manifest.IsStartElement(QualityLevelElement))
                {
                    // Missing or malformed attribute kbps will result in failure, which is what we want
                    ulong bitrate = Convert.ToUInt64(manifest.GetAttribute(QualityLevelBitrateAttribute), CultureInfo.InvariantCulture);
                    Dictionary<MediaStreamAttributeKeys, string> attributes = new Dictionary<MediaStreamAttributeKeys, string>(4);

                    // Get the FourCC for this quality level
                    string fourCC = manifest.GetAttribute(QualityLevelFourCCAttribute);

                    if (fourCC != null)
                    {
                        attributes.Add(MediaStreamAttributeKeys.VideoFourCC, fourCC);
                    }

                    // Get the width of this stream
                    string width = manifest.GetAttribute(QualityLevelWidthAttribute);

                    if (width != null)
                    {
                        attributes.Add(MediaStreamAttributeKeys.Width, width);
                    }

                    // Get the height of this stream
                    string height = manifest.GetAttribute(QualityLevelHeightAttribute);

                    if (height != null)
                    {
                        attributes.Add(MediaStreamAttributeKeys.Height, height);
                    }

                    // Get the video codec data
                    string codecPrivateData = manifest.GetAttribute(QualityLevelCodecPrivateDataAttribute);

                    if (codecPrivateData != null)
                    {
                        attributes.Add(MediaStreamAttributeKeys.CodecPrivateData, codecPrivateData);
                    }

                    // Get the wave format ex. Note we will only have one (codec private data) or the other
                    // (wave format ex)
                    string waveFormatEx = manifest.GetAttribute(QualityLevelWaveFormatExAttribute);

                    if (waveFormatEx != null)
                    {
                        if (codecPrivateData != null)
                        {
                            throw new AdaptiveStreamingException("Cannot have both a CodecPrivateData and a WaveFormatEx attribute in the same QualityLevel element.");
                        }
                        bIsVideoStream = false;
                        attributes.Add(MediaStreamAttributeKeys.CodecPrivateData, waveFormatEx);
                    }

                    if (!bHaveFirstBitrate)
                    {
                        bHaveFirstBitrate = true;

                        if (bIsVideoStream)
                        {
                            displayAspectRatioHeight = int.Parse(height, CultureInfo.InvariantCulture);
                            displayAspectRatioWidth = int.Parse(width, CultureInfo.InvariantCulture);
                        }
                    }

                    // Add this bitrate and these attributes to the stream info
                    info.AddBitrate(bitrate, attributes);

                    if (bitrate > maxBitrate)
                    {
                        maxBitrate = bitrate;

                        if (bIsVideoStream)
                        {
                            maxBitrateHeight = int.Parse(height, CultureInfo.InvariantCulture);
                            maxBitrateWidth = int.Parse(width, CultureInfo.InvariantCulture);
                        }
                    }
                }
                else if (manifest.IsStartElement("c"))
                {
                    // Getting chunk information
                    int id = 0;
                    try
                    {
                        // Missing or malformed attributes n or d will result in failure, which is what we want
                        id = Convert.ToInt32(manifest.GetAttribute("n"), CultureInfo.InvariantCulture);

                        // Ignore out-of-range chunk id's to simplify experimental manifest tinkering (truncation for test purposes).
                        if (id < info.NumberOfChunksInStream)
                        {
                            // Add a new media chunk to our stream info
                            ulong chunkDuration = Convert.ToUInt64(manifest.GetAttribute("d"), CultureInfo.InvariantCulture);
                            info.AddMediaChunk(id, chunkDuration);
                        }
                    }
                    catch (ArgumentOutOfRangeException e)
                    {
                        throw new AdaptiveStreamingException(String.Format(CultureInfo.InvariantCulture, "Bad manifest format: chunk ID {0} is out of range.", id), e);
                    }
                }
                else if (manifest.Name.Equals("StreamIndex"))
                {
                    break;
                }

                // We explicitly ignore content that we don't understand, as well as whitespace, comments etc.
            }

            // Let's fix up the aspect ratio of the highest bitrate stream. We need to find the
            // combination that gives us the largest buffer size.
            IDictionary<MediaStreamAttributeKeys, string> mediaAttributes = info.GetAttributesForBitrate(maxBitrate);
            if(bIsVideoStream)
            {
                // First try the width
                int testWidth = displayAspectRatioWidth * maxBitrateHeight;
                testWidth = (int)((double)(testWidth) / (double)displayAspectRatioHeight);

                // Now round it up to the nearest four
                testWidth += 3;
                testWidth -= testWidth % 4;

                // Now try the height
                int testHeight = displayAspectRatioHeight * maxBitrateWidth;
                testHeight = (int)((double)(testHeight) / (double)displayAspectRatioWidth);

                // Now round it up to the nearest four
                testHeight+= 3;
                testHeight -= testHeight % 4;

                // Calculate the buffer sizes
                int bufferSizeOriginal = maxBitrateWidth * maxBitrateHeight;
                int bufferSizeWidth = testWidth * maxBitrateHeight;
                int bufferSizeHeight = testHeight * maxBitrateWidth;

                if (bufferSizeWidth >= bufferSizeHeight && bufferSizeWidth >= bufferSizeOriginal)
                {
                    maxBitrateWidth = testWidth;
                }
                else if (bufferSizeHeight >= bufferSizeWidth && bufferSizeHeight >= bufferSizeOriginal)
                {
                    maxBitrateHeight = testHeight;
                }

                mediaAttributes.Remove(MediaStreamAttributeKeys.Width);
                mediaAttributes.Remove(MediaStreamAttributeKeys.Height);
                mediaAttributes.Add(MediaStreamAttributeKeys.Width, maxBitrateWidth.ToString(CultureInfo.InvariantCulture));
                mediaAttributes.Add(MediaStreamAttributeKeys.Height, maxBitrateHeight.ToString(CultureInfo.InvariantCulture));
            }

            // Set the description to be the highest bitrate item.
            info.Description = new MediaStreamDescription(info.MediaType, mediaAttributes);
            info.Valid = true;

            return info;
        }