/// <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 } }
/// <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 } }