/// <summary> /// Initializes a new instance of the MediaChunkQueue class /// </summary> /// <param name="count">the number of chunks in the queue</param> /// <param name="mediaType">the media type of the queue</param> /// <param name="streamId">the id of the stream this queue belongs to</param> public MediaChunkQueue(int count, MediaStreamType mediaType, int streamId) { m_dataQueue = new MediaChunk[count]; for (int i = 0; i < count; i++) { m_dataQueue[i] = new MediaChunk(i, mediaType, streamId); } }
/// <summary> /// This function is called whenever we have received a new chunk /// from the download manager. /// </summary> /// <param name="chunk">The media chunk to process</param> private void ProcessChunkDownload(MediaChunk chunk) { int streamIndex = chunk.StreamId; NetworkMediaInfo networkMediaInfo = m_networkMediaInfo[streamIndex]; // Added for logging purposes only int chunkId = chunk.ChunkId; // Update prevBitRate to actually be the one used last time networkMediaInfo.PreviousBitrate = networkMediaInfo.NextBitrate; // Nominal bit rate index int nomBitRateIdx = networkMediaInfo.FindBitRateIndex(chunk.Bitrate); // Track buffer fullness double bufferFullness = NetworkHeuristicsHelper.ConvertHnsToSeconds(GetTimeBufferedForStreamType(chunk.MediaType)); networkMediaInfo.BufferFullnessWindow.Add(bufferFullness); // Track download bit rate double downloadDuration = BandwidthCalculator.CalculateDownloadDurationInSeconds(chunk.DownloadStartTime, chunk.DownloadCompleteTime, streamIndex, chunk.ChunkId, chunk.Length); // The bit rate at which the chunk was downloaded double downloadSize = chunk.Length; double downloadBandwidth = downloadSize * 8 / downloadDuration; // Cap the bit rate to 1 Gbps if (downloadBandwidth > 1e9) { downloadBandwidth = 1e9; } // The first 2(PacketPairPacketCount) video chunks are treat // as packetpair packet and will not be cached. // The packetpair bandwidth is used to decide whether the chunk is reading from cache or not. // Better higher than lower. if (chunk.ChunkId < Downloader.PacketPairPacketCount && chunk.MediaType == MediaStreamType.Video) { if (chunk.ChunkId == 0) { m_packetPairBandwidth = (ulong)downloadBandwidth; } else { if ((ulong)downloadBandwidth > m_packetPairBandwidth) { m_packetPairBandwidth = (ulong)downloadBandwidth; } } m_cacheBandwidth = Configuration.Heuristics.Network.CacheBandwidthFactor * m_packetPairBandwidth; if (m_cacheBandwidth < Configuration.Heuristics.Network.CacheBandwidthMin) { m_cacheBandwidth = Configuration.Heuristics.Network.CacheBandwidthMin; } NhTrace("INFO", streamIndex, "Setting m_PacketPairBandwidth:{0} cache:{1}", m_packetPairBandwidth, m_cacheBandwidth); } // If our download bandwidth is less than the cache bandwidth, then // it has not been added to our window yet if (downloadBandwidth < m_cacheBandwidth) { // Not in cache networkMediaInfo.DownloadBandwidthWindow.Add(downloadBandwidth); } else { // If the current chunk is in cache, use average // bandwidth as current bandwidth downloadBandwidth = networkMediaInfo.DownloadBandwidthWindow.CurrentKernel; } // Track actual media content's bit rate double chunkDuration = NetworkHeuristicsHelper.ConvertHnsToSeconds(chunk.Duration); networkMediaInfo.BitratesInfo[nomBitRateIdx].EncodedBitrateWindow.Add(chunk.Length * 8 / chunkDuration); // Update how much has been downloaded networkMediaInfo.TotalStreamDownloaded += chunkDuration; // Now we are going to do our buffering state logic. // We have 2 states, Steady and Buffering. ulong currentBitRateSelected = 0; switch (networkMediaInfo.DownloadState) { case DownloadState.Buffering: // If we are Buffering, then we can jump as many steps // as we want networkMediaInfo.IsLimitBitrateSteps = false; // If our download bandwidth is really high, it could // be because we have no history on it yet if (downloadBandwidth >= sm_NetworkHeuristicsParams.AbsoluteHighBandwidth) { // Suspect cache download bandwidth networkMediaInfo.IsLimitBitrateSteps = true; } // Buffering state normal logic. If our buffer fullness is decreasing // or slowly changing, then limit our bitrate step so we don't tweak it too hard if (networkMediaInfo.IsLimitBitrateSteps == false && (networkMediaInfo.BufferFullnessWindow.IsDecreasing == true || networkMediaInfo.BufferFullnessWindow.IsSlowChanging == true)) { networkMediaInfo.IsLimitBitrateSteps = true; } // Find bit rate that satisfies the "conditions" currentBitRateSelected = GetNextBitRateUsingBandwidth(networkMediaInfo, chunkDuration); // Find closest bit rate currentBitRateSelected = networkMediaInfo.FindClosestBitrateByValue(currentBitRateSelected); // If our buffer is more than halfway full, then go steady state if (bufferFullness >= (Configuration.Heuristics.Network.LowerBufferFullness + ((Configuration.Heuristics.Network.UpperBufferFullness - Configuration.Heuristics.Network.LowerBufferFullness) / 2))) { // Before going to steady, re-set this parameter networkMediaInfo.RelativeContentDownloadSpeed = sm_NetworkHeuristicsParams.RelativeContentDownloadSpeed; // Set steady state networkMediaInfo.DownloadState = DownloadState.Steady; } break; case DownloadState.Steady: // Default to not limiting bitrate jumps networkMediaInfo.IsLimitBitrateSteps = false; // Check to see if we have enough history if (downloadBandwidth >= sm_NetworkHeuristicsParams.AbsoluteHighBandwidth) { // Suspect cache download bandwidth networkMediaInfo.IsLimitBitrateSteps = true; } // Steady state normal logic. If our buffer fullness gets too // low, then jump back to buffering if (bufferFullness < Configuration.Heuristics.Network.PanicBufferFullness) { // Usually will pick the lowest bit rate currentBitRateSelected = networkMediaInfo.FindDefaultBitrate(); // Stop trying to improve our bitrate networkMediaInfo.ResetImprovingBitRate(); // Set the buffering state networkMediaInfo.DownloadState = DownloadState.Buffering; } else if (networkMediaInfo.BufferFullnessWindow.IsSlowChanging) { // Smooth change is happening, can control by // adjusting in 1 step if (bufferFullness < Configuration.Heuristics.Network.LowerBufferFullness) { // Reduce bit rate 1 step currentBitRateSelected = GetNextBitRate(networkMediaInfo, -1); // Reset the timer to attempt improving bit rate networkMediaInfo.ResetImprovingBitRate(); } else if (bufferFullness > Configuration.Heuristics.Network.UpperBufferFullness) { // Try to go up 1 step currentBitRateSelected = AttemptImprovingBitRate(networkMediaInfo); } else { // Otherwise we are doing all right where we were currentBitRateSelected = networkMediaInfo.FindClosestBitrateByValue(networkMediaInfo.PreviousBitrate); } } else if (networkMediaInfo.BufferFullnessWindow.IsFastDecreasing) { // Buffer fullness decreasing rapidly if (bufferFullness < Configuration.Heuristics.Network.LowerBufferFullness) { // Usually will pick the lowest bit rate currentBitRateSelected = networkMediaInfo.FindDefaultBitrate(); // Reset the timer to attempt improving bit rate networkMediaInfo.ResetImprovingBitRate(); // Go to buffering state networkMediaInfo.DownloadState = DownloadState.Buffering; } else { // Let's wait until we reach the lower // buffer fullness threshold, might be a // transient condition currentBitRateSelected = networkMediaInfo.FindClosestBitrateByValue(networkMediaInfo.PreviousBitrate); } } else { // Buffer fullness is growing rapidly, so try to go to a better bitrate currentBitRateSelected = AttemptImprovingBitRate(networkMediaInfo); } break; } // Keep track of the next bitrate we are going to select networkMediaInfo.NextBitrate = currentBitRateSelected; // Log some information: // 1) time // 2) label // 3) object ID (instance) // 4) streamIndex // // 5) download size // 6) download duration // 7) chunk ID // 8) chunk bitrate // 9) chunk duration // 10) buffer fullness // Extended information that could be derived from basic info // 11) next bitrate // 12) kernel(download bandwidth) // 13) kernel(encoded bitrate) // 14) kernel(buffer fullness) // 15) buffer fullness slope // 16) download state (buffering, steady) // 17) Bit rate validation state // 18) Cache bandwidth NhTrace( "HIST", streamIndex, "{0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12} {13}", /* 5 */downloadSize, /* 6 */downloadDuration.ToString(DoubleFormat, CultureInfo.InvariantCulture), /* 7 */chunkId, /* 8 */networkMediaInfo.BitratesInfo[nomBitRateIdx].NominalBitrate, /* 9 */chunkDuration.ToString(DoubleFormat, CultureInfo.InvariantCulture), /* 10 */bufferFullness.ToString(DoubleFormat, CultureInfo.InvariantCulture), /* 11 */networkMediaInfo.NextBitrate, /* 12 */networkMediaInfo.DownloadBandwidthWindow.CurrentKernel.ToString(DoubleFormat, CultureInfo.InvariantCulture), /* 13 */networkMediaInfo.BitratesInfo[nomBitRateIdx].EncodedBitrateWindow.CurrentKernel.ToString(DoubleFormat, CultureInfo.InvariantCulture), /* 14 */networkMediaInfo.BufferFullnessWindow.CurrentKernel.ToString(DoubleFormat, CultureInfo.InvariantCulture), /* 15 */networkMediaInfo.BufferFullnessWindow.CurrentSlope.ToString(DoubleFormat, CultureInfo.InvariantCulture), /* 16 */(int)networkMediaInfo.DownloadState, /* 17 */0, /* 19 */downloadBandwidth < m_cacheBandwidth ? 0 : 1); }
/// <summary> /// Initializes a new instance of the DownloadCompleteEventArgs class /// </summary> /// <param name="chunk">the media chunk we are downloading</param> public DownloadCompleteEventArgs(MediaChunk chunk) { Chunk = chunk; }
/// <summary> /// Downloads a new chunk /// </summary> /// <param name="baseUrl">the base url of the file to download</param> /// <param name="urlGenerator">the class that will generator the url for this chunk</param> /// <param name="chunk">the chunk we are trying to download</param> /// <param name="completeCallback">the event handler to call when we are complete</param> /// <param name="instance">A unique id to group downloads. Can be used to group audio and video downloads separately</param> public static void Start( string baseUrl, IUrlGenerator urlGenerator, MediaChunk chunk, EventHandler<DownloadCompleteEventArgs> completeCallback, Guid instance) { Tracer.Assert(chunk.Bitrate > 0, String.Format(CultureInfo.InvariantCulture, "Cannot load chunk {0} with zero bitrate.", chunk.Sid)); Downloader downloader = new Downloader(baseUrl, urlGenerator, chunk, completeCallback, instance); downloader.StartDownload(); }
/// <summary> /// Initializes a new instance of the Downloader class /// </summary> /// <param name="baseUrl">the base url of the file to download</param> /// <param name="urlGenerator">the module that generates our url</param> /// <param name="mediaChunk">the chunk we are trying to download</param> /// <param name="completeCallback">the event handler to call when we are complete</param> /// <param name="instance">A unique id to group downloads. Can be used to group audio and video downloads separately</param> private Downloader( string baseUrl, IUrlGenerator urlGenerator, MediaChunk mediaChunk, EventHandler<DownloadCompleteEventArgs> completeCallback, Guid instance) { m_instanceId = instance; m_chunk = mediaChunk; m_url = urlGenerator.GenerateUrlStringForChunk(baseUrl, mediaChunk.StreamId, mediaChunk.ChunkId, mediaChunk.Bitrate, mediaChunk.StartTime, (long)mediaChunk.Duration); m_url = m_url.Replace('\\', '/'); // Assumption: ChunkId always start from 0 // Add random URL modifier for the first 2 video chunks to make sure they are not in cache. // We use the first two chunks to get reliable bandwidth estimates. if (mediaChunk.ChunkId < PacketPairPacketCount && mediaChunk.MediaType == MediaStreamType.Video) { m_url = m_url + "?packetpair=" + DateTime.Now.Ticks; } if (mediaChunk.Url != m_url) { mediaChunk.Url = m_url; } m_downloadCompleteEventHandler = completeCallback; // Add this download object to our table of objects lock (sm_allDownloads) { if (!sm_allDownloads.ContainsKey(m_instanceId)) { sm_allDownloads.Add(m_instanceId, new List<Downloader>(4)); } List<Downloader> allMyDownloads = sm_allDownloads[m_instanceId]; allMyDownloads.Add(this); } }
/// <summary> /// Do the actual ReportGetSampleCompleted work. Fires an event in case derived classes /// want to listen in. /// </summary> /// <param name="chunk">the chunk with our sample</param> /// <param name="mediaStreamSample">the sample we are reporting</param> private void DoReportGetSampleCompleted(MediaChunk chunk, MediaStreamSample mediaStreamSample) { ReportGetSampleCompleted(mediaStreamSample); GetSampleCompletedEventArgs args = new GetSampleCompletedEventArgs(); args.Sample = mediaStreamSample; args.ChunkId = (chunk != null) ? chunk.ChunkId : -1; args.Bitrate = (chunk != null) ? chunk.Bitrate : 0; args.StreamId = (chunk != null) ? chunk.StreamId : -1; if (GetSampleCompleted != null && chunk != null) { GetSampleCompleted(this, args); } }
/// <summary> /// Handle a ParseChunk command /// </summary> /// <param name="chunk">the media chunk we are parsing</param> private void DoParseChunk(MediaChunk chunk) { // Try to parse the chunk if (!chunk.ParseHeader(ChunkParserFactory)) { Tracer.Trace(TraceChannel.Error, "Media data chunk {0} ({2} kbps) has a bad format, chunk state is {1}.", chunk.Sid, chunk.State, chunk.Bitrate); } // We work with chunks inplace, but need to update queue pointers m_manifestInfo.GetStreamInfoForStreamType(chunk.MediaType).Queue.Add(chunk); }
/// <summary> /// Add a chunk to the queue /// </summary> /// <param name="chunk">the chunk to add</param> public void Add(MediaChunk chunk) { lock (m_dataQueue) { if (chunk.ChunkId >= m_dataQueue.Length) { throw new AdaptiveStreamingException(String.Format(CultureInfo.InvariantCulture, "Chunk ID is too big, ID={0}, media length is {1} chunks.", chunk.ChunkId, m_dataQueue.Length)); } else if (chunk.ChunkId < m_indexFirst || chunk.ChunkId >= m_indexFirst + MaxChunksBuffered) { // Silently ignore this chunk because we cannot buffer any more Tracer.Trace(TraceChannel.Seek, "Arrived chunk {2}{0}, while current is {2}{1}", chunk.ChunkId, m_indexFirst, chunk.MediaType == MediaStreamType.Video ? "V" : "A"); } else if (chunk.StreamId != m_dataQueue[chunk.ChunkId].StreamId) { // We switched streams and were not able to cancel this download, // don't replace audio from one stream with audio from another chunk.Reset(); } else { // Tack it to the end if (chunk.ChunkId >= m_indexLast) { m_indexLast = chunk.ChunkId + 1; } // Get the current chunk MediaChunk cur = m_dataQueue[chunk.ChunkId]; // In the current implementation this is never hit because chunks are updated in place, // i.e. chunk == cur always if (chunk != cur) { if (chunk.Bitrate > cur.Bitrate || cur.DownloadedPiece == null) { m_dataQueue[chunk.ChunkId] = chunk; cur.Reset(); } else { chunk.Reset(); } } } UpdateBufferSizes(); } }