/// <summary> /// Internals the seek input. /// </summary> /// <param name="renderTime">The render time.</param> private void InternalSeekInput(decimal renderTime) { if (IsLiveStream) { if (LeadingFramesCache.IsEmpty == false) { RealtimeClock.Seek(LeadingFramesCache.FirstFrameTime); } return; } #if DEBUG var seekStopwatch = new System.Diagnostics.Stopwatch(); seekStopwatch.Start(); #endif if (renderTime < StartTime) { renderTime = StartTime; } RealtimeClock.Seek(renderTime); var allowedThreshold = Constants.SeekThresholdSeconds; var seekOffsetLength = Constants.SeekOffsetSeconds; var seekTime = renderTime - seekOffsetLength; var maxSeekStartTime = seekTime - allowedThreshold; var bufferedLeadingFrames = new FFmpegMediaFrameCache(LeadingFramesCache); var bufferedLaggingFrames = new FFmpegMediaFrameCache(LaggingFramesCache); var outerLoopCount = 0; var innerLoopCount = 0; var frameReleaseCount = 0; var doSeekInStream = true; var doSeekByPullingFrames = true; var seekFlag = 0; var seekFrameResult = 0; var startTime = DateTime.UtcNow; var lastFailedTimestamp = long.MinValue; var seekToLastFrame = false; var seekTimeBase = LeadingFramesCache.Type == MediaFrameType.Video ? InputVideoStream->time_base : InputAudioStream->time_base; var seekStreamIndex = LeadingFramesCache.Type == MediaFrameType.Video ? InputVideoStream->index : InputAudioStream->index; var leadingFrameIndex = -1; try { while (doSeekInStream) { outerLoopCount++; if (seekTime < StartTime) { seekTime = StartTime; } if (lastFailedTimestamp == StartDts) { if (LeadingFramesCache.IsEmpty == false) { RealtimeClock.Seek(LeadingFramesCache.FirstFrameTime); } ErrorOccurredCallback(this, new MediaPlaybackException(MediaPlaybackErrorSources.InternalSeekInput, MediaPlaybackErrorCode.SeekFailedCritical, string.Format("Target Postion @ {0:0.000}s has already failed to seek. First DTS {1} also failed and will not retry.", seekTime, StartDts))); return; } var targetTimestamp = Helper.SecondsToTimestamp(seekTime, seekTimeBase); if (lastFailedTimestamp == targetTimestamp) { ErrorOccurredCallback(this, new MediaPlaybackException(MediaPlaybackErrorSources.InternalSeekInput, MediaPlaybackErrorCode.SeekFailedWillRetry, string.Format("Target Postion @ {0:0.000}s has already failed to seek. Target timestamp will now be First DTS {1}.", seekTime, StartDts))); targetTimestamp = StartDts; } seekFlag = (seekTime < renderTime || seekTime <= StartTime ? (int)ffmpeg.AVSEEK_FLAG_BACKWARD : 0) | 0; // FFmpegInvoke.AVSEEK_FLAG_ANY; //seekFlag = FFmpegInvoke.AVSEEK_FLAG_BACKWARD; // | FFmpegInvoke.AVSEEK_FLAG_ANY; seekFrameResult = ffmpeg.av_seek_frame(InputFormatContext, seekStreamIndex, targetTimestamp, seekFlag); // significantly faster than seek_file //seekFrameResult = FFmpegInvoke.avformat_seek_file(InputFormatContext, streamIndex, long.MinValue, targetTimestamp, long.MaxValue, seekFlag); if (seekFrameResult < Constants.SuccessCode) { if (LeadingFramesCache.IsEmpty == false) { RealtimeClock.Seek(LeadingFramesCache.FirstFrameTime); } var errorMessage = Helper.GetFFmpegErrorMessage(seekFrameResult); ErrorOccurredCallback(this, new MediaPlaybackException(MediaPlaybackErrorSources.InternalSeekInput, MediaPlaybackErrorCode.SeekFailedFFmpeg, string.Format("FFmpeg av_seek_frame @ {1:0.000}: Failed with error code {0}. {2}", seekFrameResult, seekTime, errorMessage))); return; } else { if (VideoCodecContext != null) { ffmpeg.avcodec_flush_buffers(VideoCodecContext); } if (AudioCodecContext != null) { ffmpeg.avcodec_flush_buffers(AudioCodecContext); } } leadingFrameIndex = -1; bufferedLeadingFrames.Clear(); bufferedLaggingFrames.Clear(); doSeekInStream = false; doSeekByPullingFrames = true; while (doSeekByPullingFrames) { innerLoopCount++; var frame = this.PullMediaFrame(); if (frame != null) { if (frame.StartTime < maxSeekStartTime) { frame.EnqueueRelease(); frameReleaseCount++; continue; } if (frame.Type == bufferedLeadingFrames.Type) { leadingFrameIndex++; if (leadingFrameIndex == 0 && frame.Type == bufferedLeadingFrames.Type && frame.StartTime - frame.Duration > maxSeekStartTime && maxSeekStartTime > 0M) { seekTime -= seekOffsetLength; frame.EnqueueRelease(); doSeekInStream = true; lastFailedTimestamp = targetTimestamp; break; } // We are Full minus 1 at this point. We'll stop buffering if (bufferedLeadingFrames.Count >= bufferedLeadingFrames.Capacity - 1) { doSeekByPullingFrames = false; } bufferedLeadingFrames.Add(frame); } else if (frame.Type == bufferedLaggingFrames.Type) { // add the lagging frame no matter what if (bufferedLaggingFrames.IsFull) { bufferedLaggingFrames.RemoveFirst(); } bufferedLaggingFrames.Add(frame); } // Find out if we have the frame var seekFrameIndex = bufferedLeadingFrames.IndexOf(renderTime, true); var minimumFrameCount = (seekFrameIndex - 1) * 2; // if we have more than enough frames in the buffer or we have reached a full or end condition, stop buffering frames if (seekFrameIndex > 0) { if (bufferedLeadingFrames.Count >= minimumFrameCount || bufferedLeadingFrames.IsFull || IsAtEndOfStream) { doSeekByPullingFrames = false; } } } // We're already padt the end of the stream. Natural duration was wron for the leading frames cache. if (IsAtEndOfStream && bufferedLeadingFrames.Count <= 0) { doSeekInStream = true; seekTime -= seekOffsetLength; maxSeekStartTime = seekTime - allowedThreshold; seekToLastFrame = true; } if (doSeekInStream) { break; } if (doSeekByPullingFrames == false || IsAtEndOfStream) { LeadingFramesCache.Replace(bufferedLeadingFrames); LaggingFramesCache.Replace(bufferedLaggingFrames); if (seekToLastFrame && LeadingFramesCache.Count > 0) { RealtimeClock.Seek(LeadingFramesCache.LastFrameTime); } return; } } } } finally { #if DEBUG seekStopwatch.Stop(); SeekTimes.Add(seekStopwatch.ElapsedMilliseconds); InnerLoopCounts.Add(innerLoopCount); System.Diagnostics.Debug.WriteLine("Seek @ {6:0.000} = Long: {0:00}\t Short: {1:000}\t Short (AVG): {2:0.000}\t Waste Count: {3:000}\t Elapsed: {4}\tElapsed (AVG): {5:0.000}", outerLoopCount, innerLoopCount, InnerLoopCounts.Average(), frameReleaseCount, seekStopwatch.ElapsedMilliseconds, SeekTimes.Average(), renderTime); #endif } }
private void InternalLoadFrames(decimal renderTime) { if (renderTime < StartTime) { renderTime = StartTime; } if (IsAtEndOfStream && LeadingFramesCache.Count > 0 && renderTime >= LeadingFramesCache.EndTime) { renderTime = LeadingFramesCache.EndTime; } // The very first thing we do is fill the buffer if it is empty if (this.LeadingFramesCache.IsEmpty) { this.InternalFillFramesCache(Constants.FrameExtractorFillTimeout); if (LeadingFramesCache.Count > 0) { if (FirstLeadingFrameTime == null) { FirstLeadingFrameTime = LeadingFramesCache.FirstFrameTime; } if (IsLiveStream) { RealtimeClock.Seek(LeadingFramesCache.FirstFrameTime); RealtimeClock.Play(); return; } } } var renderFrame = LeadingFramesCache.GetFrame(renderTime, CheckFrameBounds); var renderFrameIndex = LeadingFramesCache.IndexOf(renderFrame); var renderFrameFound = renderFrameIndex >= 0; // if we can't find the frame . . . if (renderFrameFound == false) { // Perform the seek operation this.InternalSeekInput(renderTime); renderTime = RealtimeClock.PositionSeconds; // try to find the frame now that we have stuff var seekFrame = LeadingFramesCache.GetFrame(renderTime, CheckFrameBounds); if (seekFrame != null) { // seek is successful at this point RealtimeClock.Seek(renderTime); PcmAudioProvider.Clear(); } else { // got some frames but not the ones we asked for if (this.LeadingFramesCache.Count > 0) { if (IsAtEndOfStream) { RealtimeClock.Seek(this.LeadingFramesCache.MiddleFrameTime); } else { if (InternalGetIsInFirstTimeSegment(renderTime)) { RealtimeClock.Seek(renderTime); ErrorOccurredCallback(this, new MediaPlaybackException(MediaPlaybackErrorSources.InternalLoadFrames, MediaPlaybackErrorCode.LoadFramesFailedInFirstSegment, string.Format("Could not find frames at {0:0.000} (On first time segment). First Leading Frame occurs at {1:0.000}", renderTime, LeadingFramesCache.FirstFrameTime))); } else { RealtimeClock.Seek(this.LeadingFramesCache.LastFrameTime); ErrorOccurredCallback(this, new MediaPlaybackException(MediaPlaybackErrorSources.InternalLoadFrames, MediaPlaybackErrorCode.LoadFramesFailedForCurrentPosition, string.Format("Could not find frames at {0:0.000} (NOT on first segment). Last Leading Frame occurs at {1:0.000} - This should not have occurred.", renderTime, LeadingFramesCache.LastFrameTime))); } } } else { ErrorOccurredCallback(this, new MediaPlaybackException(MediaPlaybackErrorSources.InternalLoadFrames, MediaPlaybackErrorCode.LoadFramesFailedCritical, string.Format("Could not find frames at {0:0.000} and no Leading Frames exist in the cache - Critical Error.", renderTime))); } } return; } else { var isInLastTimeSegment = InternalGetIsInLastTimeSegment(renderTime); var isInFirstTimeSegment = InternalGetIsInFirstTimeSegment(renderTime); // frward lookup if (renderFrameIndex > LeadingFramesCache.MiddleIndex) { if (isInLastTimeSegment == false) { if (LeadingFramesCache.IsFull) { var removalCount = 1; if (SpeedRatio >= Constants.DefaultSpeedRatio) { removalCount = (int)Math.Ceiling(SpeedRatio); } removalCount = Math.Min(LeadingFramesCache.Count / 4, removalCount); for (var i = 1; i <= removalCount; i++) { LeadingFramesCache.RemoveFirst(); } } this.InternalFillFramesCache(Constants.FrameExtractorFillTimeout); return; } else { if (renderFrameIndex >= LeadingFramesCache.Count - 1) { // All input has been processed up to the last frame now. RealtimeClock.Seek(LeadingFramesCache.EndTime); return; } } } // backward lookup if (renderFrameIndex <= 1 && isInFirstTimeSegment == false && IsLiveStream == false) { InternalSeekInput(LeadingFramesCache.StartTime); var frame = LeadingFramesCache.GetFrame(renderTime, CheckFrameBounds); if (frame != null) { RealtimeClock.Seek(renderTime); } else { RealtimeClock.Seek(LeadingFramesCache.MiddleFrame != null ? LeadingFramesCache.MiddleFrame.StartTime : 0M); } return; } } }
/// <summary> /// Internals the seek input. /// </summary> /// <param name="renderTime">The render time.</param> private void InternalSeekInput(decimal renderTime) { if (IsLiveStream) { if (PrimaryFramesCache.IsEmpty == false) RealtimeClock.Seek(PrimaryFramesCache.FirstFrameTime); return; } #if DEBUG var seekStopwatch = new System.Diagnostics.Stopwatch(); seekStopwatch.Start(); #endif if (renderTime < StartTime) renderTime = StartTime; RealtimeClock.Seek(renderTime); var allowedThreshold = Constants.SeekThresholdSeconds; var seekOffsetLength = Constants.SeekOffsetSeconds; var seekTime = renderTime - seekOffsetLength; var maxSeekStartTime = seekTime - allowedThreshold; var bufferedLeadingFrames = new FFmpegMediaFrameCache(PrimaryFramesCache); var bufferedLaggingFrames = new FFmpegMediaFrameCache(SecondaryFramesCache); var outerLoopCount = 0; var innerLoopCount = 0; var frameReleaseCount = 0; var doSeekInStream = true; var doSeekByPullingFrames = true; var seekFlag = 0; var seekFrameResult = 0; var startTime = DateTime.UtcNow; var lastFailedTimestamp = long.MinValue; var seekToLastFrame = false; var seekTimeBase = PrimaryFramesCache.Type == MediaFrameType.Video ? InputVideoStream->time_base : InputAudioStream->time_base; var seekStreamIndex = PrimaryFramesCache.Type == MediaFrameType.Video ? InputVideoStream->index : InputAudioStream->index; var leadingFrameIndex = -1; try { while (doSeekInStream) { outerLoopCount++; if (seekTime < StartTime) seekTime = StartTime; if (lastFailedTimestamp == StartDts) { if (PrimaryFramesCache.IsEmpty == false) RealtimeClock.Seek(PrimaryFramesCache.FirstFrameTime); ErrorOccurredCallback(this, new MediaPlaybackException(MediaPlaybackErrorSources.InternalSeekInput, MediaPlaybackErrorCode.SeekFailedCritical, string.Format("Target Postion @ {0:0.000}s has already failed to seek. First DTS {1} also failed and will not retry.", seekTime, StartDts))); return; } var targetTimestamp = Helper.SecondsToTimestamp(seekTime, seekTimeBase); if (lastFailedTimestamp == targetTimestamp) { ErrorOccurredCallback(this, new MediaPlaybackException(MediaPlaybackErrorSources.InternalSeekInput, MediaPlaybackErrorCode.SeekFailedWillRetry, string.Format("Target Postion @ {0:0.000}s has already failed to seek. Target timestamp will now be First DTS {1}.", seekTime, StartDts))); targetTimestamp = StartDts; } seekFlag = (seekTime < renderTime || seekTime <= StartTime ? (int)ffmpeg.AVSEEK_FLAG_BACKWARD : 0) | 0; // FFmpegInvoke.AVSEEK_FLAG_ANY; //seekFlag = ffmpeg.AVSEEK_FLAG_BACKWARD; // | ffmpeg.AVSEEK_FLAG_ANY; seekFrameResult = ffmpeg.av_seek_frame(InputFormatContext, seekStreamIndex, targetTimestamp, seekFlag); // significantly faster than seek_file //seekFrameResult = ffmpeg.avformat_seek_file(InputFormatContext, seekStreamIndex, targetTimestamp - 2, targetTimestamp, targetTimestamp + 2, seekFlag); if (seekFrameResult < Constants.SuccessCode) { if (PrimaryFramesCache.IsEmpty == false) RealtimeClock.Seek(PrimaryFramesCache.FirstFrameTime); var errorMessage = Helper.GetFFmpegErrorMessage(seekFrameResult); ErrorOccurredCallback(this, new MediaPlaybackException(MediaPlaybackErrorSources.InternalSeekInput, MediaPlaybackErrorCode.SeekFailedFFmpeg, string.Format("FFmpeg av_seek_frame @ {1:0.000}: Failed with error code {0}. {2}", seekFrameResult, seekTime, errorMessage))); return; } else { if (VideoCodecContext != null) ffmpeg.avcodec_flush_buffers(VideoCodecContext); if (AudioCodecContext != null) ffmpeg.avcodec_flush_buffers(AudioCodecContext); } leadingFrameIndex = -1; bufferedLeadingFrames.Clear(); bufferedLaggingFrames.Clear(); doSeekInStream = false; doSeekByPullingFrames = true; while (doSeekByPullingFrames) { innerLoopCount++; var frame = this.PullMediaFrame(); if (frame != null) { if (frame.StartTime < maxSeekStartTime) { frame.EnqueueRelease(); frameReleaseCount++; continue; } if (frame.Type == bufferedLeadingFrames.Type) { leadingFrameIndex++; if (leadingFrameIndex == 0 && frame.Type == bufferedLeadingFrames.Type && frame.StartTime - frame.Duration > maxSeekStartTime && maxSeekStartTime > 0M) { seekTime -= seekOffsetLength; frame.EnqueueRelease(); doSeekInStream = true; lastFailedTimestamp = targetTimestamp; break; } // We are Full minus 1 at this point. We'll stop buffering if (bufferedLeadingFrames.Count >= bufferedLeadingFrames.Capacity - 1) doSeekByPullingFrames = false; bufferedLeadingFrames.Add(frame); } else if (frame.Type == bufferedLaggingFrames.Type) { // add the lagging frame no matter what if (bufferedLaggingFrames.IsFull) bufferedLaggingFrames.RemoveFirst(); bufferedLaggingFrames.Add(frame); } // Find out if we have the frame var seekFrameIndex = bufferedLeadingFrames.IndexOf(renderTime, true); var minimumFrameCount = (seekFrameIndex - 1) * 2; // if we have more than enough frames in the buffer or we have reached a full or end condition, stop buffering frames if (seekFrameIndex > 0) if (bufferedLeadingFrames.Count >= minimumFrameCount || bufferedLeadingFrames.IsFull || IsAtEndOfStream) doSeekByPullingFrames = false; } // We're already padt the end of the stream. Natural duration was wron for the leading frames cache. if (IsAtEndOfStream && bufferedLeadingFrames.Count <= 0) { doSeekInStream = true; seekTime -= seekOffsetLength; maxSeekStartTime = seekTime - allowedThreshold; seekToLastFrame = true; } if (doSeekInStream) break; if (doSeekByPullingFrames == false || IsAtEndOfStream) { PrimaryFramesCache.Replace(bufferedLeadingFrames); SecondaryFramesCache.Replace(bufferedLaggingFrames); if (seekToLastFrame && PrimaryFramesCache.Count > 0) RealtimeClock.Seek(PrimaryFramesCache.LastFrameTime); return; } } } } finally { #if DEBUG seekStopwatch.Stop(); SeekTimes.Add(seekStopwatch.ElapsedMilliseconds); InnerLoopCounts.Add(innerLoopCount); System.Diagnostics.Debug.WriteLine("Seek @ {6:0.000} = Long: {0:00}\t Short: {1:000}\t Short (AVG): {2:0.000}\t Waste Count: {3:000}\t Elapsed: {4}\tElapsed (AVG): {5:0.000}", outerLoopCount, innerLoopCount, InnerLoopCounts.Average(), frameReleaseCount, seekStopwatch.ElapsedMilliseconds, SeekTimes.Average(), renderTime); #endif } }