/// <summary> /// Calculates the next chunk to get for the given stream /// </summary> /// <param name="stream">the stream to look at</param> /// <returns>the next bitrate for this stream</returns> private ulong GetNextBitrateForStream(StreamInfo stream) { int streamIndex = stream.StreamId; if (m_heuristicsMode[streamIndex] == HeuristicsMode.Full) { // Use the full mode calculation // Get our network stats for this stream NetworkMediaInfo networkMediaInfo = m_networkMediaInfo[streamIndex]; // Find the closest bitrate ulong nextBitRate = networkMediaInfo.FindClosestBitrateByValue(networkMediaInfo.NextBitrate); NhTrace("INFO", streamIndex, "Next bit rate:{0}", nextBitRate); // Return Kilobits per second return nextBitRate; } else { // There is no other values for now, so that's: if (useHeuristicsMode[streamIndex] == HeuristicsMode.FixedRate) // Grab a bitrate from the bitrate array (currently always selects the first bitrate). return stream.Bitrates[m_fixedBitrateIndex[streamIndex]]; } }
/// <summary> /// Adds a stream to this manifest /// </summary> /// <param name="streamIndexInfo">the stream to add</param> public void AddStream(StreamInfo streamIndexInfo) { // Let's go through and fixup some of the info in the // stream and make sure that it is all valid streamIndexInfo.CalculateStartTimes(); // Now add it to our list of streams m_streams.Add(streamIndexInfo); // We can only have 1 audio and 1 video stream active, so let's keep track // of which ones are active if we have more than one if (m_activeStreams[(int)streamIndexInfo.MediaType] == null) { m_activeStreams[(int)streamIndexInfo.MediaType] = streamIndexInfo; } // Add our media stream descriptor m_streamDescriptions.Add(streamIndexInfo.Description); }
/// <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; }