Exemplo n.º 1
0
 /// <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);
     }
 }
Exemplo n.º 2
0
        /// <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);
        }
Exemplo n.º 3
0
 /// <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;
 }
Exemplo n.º 4
0
 /// <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();
 }
Exemplo n.º 5
0
        /// <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);
        }
Exemplo n.º 8
0
        /// <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();
            }
        }