| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/filters/source_buffer_stream.h" |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/trace_event/trace_event.h" |
| #include "media/base/demuxer_memory_limit.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/timestamp_constants.h" |
| #if defined(STARBOARD) |
| #include "starboard/media.h" |
| #endif // defined(STARBOARD) |
| |
| namespace media { |
| |
| namespace { |
| |
| // The minimum interbuffer decode timestamp delta (or buffer duration) for use |
| // in fudge room for range membership, adjacency and coalescing. |
| const int kMinimumInterbufferDistanceInMs = 1; |
| |
| // Limit the number of MEDIA_LOG() logs for track buffer time gaps. |
| const int kMaxTrackBufferGapWarningLogs = 20; |
| |
| // Limit the number of MEDIA_LOG() logs for MSE GC algorithm warnings. |
| const int kMaxGarbageCollectAlgorithmWarningLogs = 20; |
| |
| // Limit the number of MEDIA_LOG() logs for splice overlap trimming. |
| const int kMaxAudioSpliceLogs = 20; |
| |
| // Helper method that returns true if |ranges| is sorted in increasing order, |
| // false otherwise. |
| bool IsRangeListSorted(const SourceBufferStream::RangeList& ranges) { |
| base::TimeDelta prev = kNoTimestamp; |
| for (const auto& range_ptr : ranges) { |
| if (prev != kNoTimestamp && prev >= range_ptr->GetStartTimestamp()) |
| return false; |
| prev = range_ptr->GetBufferedEndTimestamp(); |
| } |
| return true; |
| } |
| |
| // Returns an estimate of how far from the beginning or end of a range a buffer |
| // can be to still be considered in the range, given the |approximate_duration| |
| // of a buffer in the stream. |
| // TODO(wolenetz): Once all stream parsers emit accurate frame durations, use |
| // logic like FrameProcessor (2*last_frame_duration + last_decode_timestamp) |
| // instead of an overall maximum interbuffer delta for range discontinuity |
| // detection. |
| // See http://crbug.com/351489 and http://crbug.com/351166. |
| base::TimeDelta ComputeFudgeRoom(base::TimeDelta approximate_duration) { |
| // Because we do not know exactly when is the next timestamp, any buffer |
| // that starts within 2x the approximate duration of a buffer is considered |
| // within this range. |
| return 2 * approximate_duration; |
| } |
| |
| // The amount of time the beginning of the buffered data can differ from the |
| // start time in order to still be considered the start of stream. |
| base::TimeDelta kSeekToStartFudgeRoom() { |
| return base::Milliseconds(1000); |
| } |
| |
| // Helper method for logging. |
| std::string StatusToString(const SourceBufferStreamStatus& status) { |
| switch (status) { |
| case SourceBufferStreamStatus::kSuccess: |
| return "kSuccess"; |
| case SourceBufferStreamStatus::kNeedBuffer: |
| return "kNeedBuffer"; |
| case SourceBufferStreamStatus::kConfigChange: |
| return "kConfigChange"; |
| case SourceBufferStreamStatus::kEndOfStream: |
| return "kEndOfStream"; |
| } |
| NOTREACHED(); |
| return ""; |
| } |
| |
| // Helper method for logging, converts a range into a readable string. |
| std::string RangeToString(const SourceBufferRange& range) { |
| if (range.size_in_bytes() == 0) { |
| return "[]"; |
| } |
| std::stringstream ss; |
| ss << "[" << range.GetStartTimestamp().InMicroseconds() << "us;" |
| << range.GetEndTimestamp().InMicroseconds() << "us(" |
| << range.GetBufferedEndTimestamp().InMicroseconds() << "us)]"; |
| return ss.str(); |
| } |
| |
| // Helper method for logging, converts a set of ranges into a readable string. |
| std::string RangesToString(const SourceBufferStream::RangeList& ranges) { |
| if (ranges.empty()) |
| return "<EMPTY>"; |
| |
| std::stringstream ss; |
| for (const auto& range_ptr : ranges) { |
| if (range_ptr != ranges.front()) |
| ss << " "; |
| ss << RangeToString(*range_ptr); |
| } |
| return ss.str(); |
| } |
| |
| std::string BufferQueueBuffersToLogString( |
| const SourceBufferStream::BufferQueue& buffers) { |
| std::stringstream result; |
| |
| result << "Buffers:\n"; |
| for (const auto& buf : buffers) { |
| result << "\tdts=" << buf->GetDecodeTimestamp().InMicroseconds() << " " |
| << buf->AsHumanReadableString() |
| << ", is_duration_estimated=" << buf->is_duration_estimated() |
| << "\n"; |
| } |
| |
| return result.str(); |
| } |
| |
| std::string BufferQueueMetadataToLogString( |
| const SourceBufferStream::BufferQueue& buffers) { |
| std::stringstream result; |
| base::TimeDelta pts_interval_start; |
| base::TimeDelta pts_interval_end; |
| SourceBufferStream::GetTimestampInterval(buffers, &pts_interval_start, |
| &pts_interval_end); |
| |
| result << "dts=[" << buffers.front()->GetDecodeTimestamp().InMicroseconds() |
| << "us;" << buffers.back()->GetDecodeTimestamp().InMicroseconds() |
| << "us(last frame dur=" << buffers.back()->duration().InMicroseconds() |
| << "us)], pts interval=[" << pts_interval_start.InMicroseconds() |
| << "us," << pts_interval_end.InMicroseconds() << "us)"; |
| return result.str(); |
| } |
| |
| SourceBufferRange::GapPolicy TypeToGapPolicy(SourceBufferStreamType type) { |
| switch (type) { |
| case SourceBufferStreamType::kAudio: |
| case SourceBufferStreamType::kVideo: |
| return SourceBufferRange::NO_GAPS_ALLOWED; |
| case SourceBufferStreamType::kText: |
| return SourceBufferRange::ALLOW_GAPS; |
| } |
| |
| NOTREACHED(); |
| return SourceBufferRange::NO_GAPS_ALLOWED; |
| } |
| |
| } // namespace |
| |
| #if defined(STARBOARD) |
| SourceBufferStream::SourceBufferStream(const std::string& mime_type, |
| const AudioDecoderConfig& audio_config, |
| MediaLog* media_log) |
| : mime_type_(mime_type), |
| media_log_(media_log), |
| #else // defined (STARBOARD) |
| SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config, |
| MediaLog* media_log) |
| : media_log_(media_log), |
| #endif // defined (STARBOARD) |
| seek_buffer_timestamp_(kNoTimestamp), |
| coded_frame_group_start_pts_(kNoTimestamp), |
| range_for_next_append_(ranges_.end()), |
| highest_output_buffer_timestamp_(kNoTimestamp), |
| max_interbuffer_distance_( |
| base::Milliseconds(kMinimumInterbufferDistanceInMs)), |
| memory_limit_(GetDemuxerStreamAudioMemoryLimit(&audio_config)) { |
| DCHECK(audio_config.IsValidConfig()); |
| audio_configs_.push_back(audio_config); |
| DVLOG(2) << __func__ << ": audio_buffer_size= " << memory_limit_; |
| } |
| |
| #if defined(STARBOARD) |
| SourceBufferStream::SourceBufferStream(const std::string& mime_type, |
| const VideoDecoderConfig& video_config, |
| MediaLog* media_log) |
| : mime_type_(mime_type), |
| media_log_(media_log), |
| #else // defined (STARBOARD) |
| SourceBufferStream::SourceBufferStream(const VideoDecoderConfig& video_config, |
| MediaLog* media_log) |
| : media_log_(media_log), |
| #endif // defined (STARBOARD) |
| seek_buffer_timestamp_(kNoTimestamp), |
| coded_frame_group_start_pts_(kNoTimestamp), |
| range_for_next_append_(ranges_.end()), |
| highest_output_buffer_timestamp_(kNoTimestamp), |
| max_interbuffer_distance_( |
| base::Milliseconds(kMinimumInterbufferDistanceInMs)), |
| memory_limit_( |
| GetDemuxerStreamVideoMemoryLimit(Demuxer::DemuxerTypes::kChunkDemuxer, |
| &video_config, |
| mime_type_)) { |
| DCHECK(video_config.IsValidConfig()); |
| video_configs_.push_back(video_config); |
| DVLOG(2) << __func__ << ": video_buffer_size= " << memory_limit_; |
| } |
| |
| SourceBufferStream::SourceBufferStream(const TextTrackConfig& text_config, |
| MediaLog* media_log) |
| : media_log_(media_log), |
| text_track_config_(text_config), |
| seek_buffer_timestamp_(kNoTimestamp), |
| coded_frame_group_start_pts_(kNoTimestamp), |
| range_for_next_append_(ranges_.end()), |
| highest_output_buffer_timestamp_(kNoTimestamp), |
| max_interbuffer_distance_( |
| base::Milliseconds(kMinimumInterbufferDistanceInMs)), |
| memory_limit_( |
| GetDemuxerStreamAudioMemoryLimit(nullptr /*audio_config*/)) {} |
| |
| SourceBufferStream::~SourceBufferStream() = default; |
| |
| void SourceBufferStream::OnStartOfCodedFrameGroup( |
| base::TimeDelta coded_frame_group_start_pts) { |
| DVLOG(1) << __func__ << " " << GetStreamTypeName() << " (pts " |
| << coded_frame_group_start_pts.InMicroseconds() << "us)"; |
| DCHECK(!end_of_stream_); |
| coded_frame_group_start_pts_ = coded_frame_group_start_pts; |
| new_coded_frame_group_ = true; |
| |
| auto last_range = range_for_next_append_; |
| range_for_next_append_ = FindExistingRangeFor(coded_frame_group_start_pts_); |
| |
| // Only reset |last_appended_buffer_timestamp_| if this new coded frame group |
| // is not adjacent to the previous coded frame group appended to the stream. |
| if (range_for_next_append_ == ranges_.end() || |
| !IsNextGopAdjacentToEndOfCurrentAppendSequence( |
| coded_frame_group_start_pts_)) { |
| ResetLastAppendedState(); |
| DVLOG(3) << __func__ << " next appended buffers will " |
| << (range_for_next_append_ == ranges_.end() |
| ? "be in a new range" |
| : "overlap an existing range"); |
| |
| if (range_for_next_append_ != ranges_.end()) { |
| // If this new coded frame group overlaps an existing range, preserve |
| // continuity from that range to the new group by moving the start time |
| // earlier (but not at or beyond the most recent buffered frame's time |
| // before |coded_frame_group_start_pts_| in the range, and not beyond the |
| // range's start time. This update helps prevent discontinuity from being |
| // introduced by the ::RemoveInternal processing during the next ::Append |
| // call. |
| base::TimeDelta adjusted_start_time = |
| (*range_for_next_append_) |
| ->FindHighestBufferedTimestampAtOrBefore( |
| coded_frame_group_start_pts_); |
| if (adjusted_start_time < coded_frame_group_start_pts_) { |
| // Exclude removal of that earlier frame during later Append |
| // processing by adjusting the removal range slightly forward. |
| coded_frame_group_start_pts_ = |
| adjusted_start_time + base::Microseconds(1); |
| } |
| } |
| } else if (last_range != ranges_.end()) { |
| DCHECK(last_range == range_for_next_append_); |
| DVLOG(3) << __func__ << " next appended buffers will continue range unless " |
| << "intervening remove makes discontinuity"; |
| } |
| } |
| |
| void SourceBufferStream::Append(const BufferQueue& buffers) { |
| TRACE_EVENT2("media", "SourceBufferStream::Append", |
| "stream type", GetStreamTypeName(), |
| "buffers to append", buffers.size()); |
| |
| DCHECK(!buffers.empty()); |
| DCHECK(coded_frame_group_start_pts_ != kNoTimestamp); |
| DCHECK(!end_of_stream_); |
| |
| DVLOG(1) << __func__ << " " << GetStreamTypeName() << ": buffers " |
| << BufferQueueMetadataToLogString(buffers); |
| DVLOG(4) << BufferQueueBuffersToLogString(buffers); |
| |
| DCHECK(!buffers.front()->is_key_frame() || |
| coded_frame_group_start_pts_ <= buffers.front()->timestamp()); |
| DVLOG_IF(2, coded_frame_group_start_pts_ > buffers.front()->timestamp()) |
| << __func__ |
| << " Suspected SAP-Type-2 occurrence: coded_frame_group_start_pts_=" |
| << coded_frame_group_start_pts_.InMicroseconds() |
| << "us, first new buffer has timestamp=" |
| << buffers.front()->timestamp().InMicroseconds() << "us"; |
| |
| // New coded frame groups emitted by the coded frame processor must begin with |
| // a keyframe. Avoid propagating with escalating impact if this assumption is |
| // broken. |
| CHECK(!new_coded_frame_group_ || buffers.front()->is_key_frame()); |
| |
| // Buffers within each GOP in a coded frame group must be monotonically |
| // increasing in DTS order. |
| DCHECK(IsDtsMonotonicallyIncreasing(buffers)); |
| |
| // Both of these checks enforce what should be guaranteed by how |
| // FrameProcessor signals OnStartOfCodedFrameGroup and the buffers it tells us |
| // to Append. |
| DCHECK(coded_frame_group_start_pts_ >= base::TimeDelta()); |
| DCHECK(buffers.front()->timestamp() >= base::TimeDelta()); |
| |
| if (UpdateMaxInterbufferDtsDistance(buffers)) { |
| // Coalesce |ranges_| using the new fudge room. This helps keep |ranges_| |
| // sorted in complex scenarios. See https://crbug.com/793247. |
| MergeAllAdjacentRanges(); |
| } |
| |
| SetConfigIds(buffers); |
| |
| // Save a snapshot of stream state before range modifications are made. |
| base::TimeDelta next_buffer_timestamp = GetNextBufferTimestamp(); |
| BufferQueue deleted_buffers; |
| |
| PrepareRangesForNextAppend(buffers, &deleted_buffers); |
| |
| // If there's a range for |buffers|, insert |buffers| accordingly. Otherwise, |
| // create a new range with |buffers|. |
| if (range_for_next_append_ != ranges_.end()) { |
| if (new_coded_frame_group_) { |
| // If the first append to this stream in a new coded frame group continues |
| // a previous range, use the new group's start time instead of the first |
| // new buffer's timestamp as the proof of adjacency to the existing range. |
| // A large gap (larger than our normal buffer adjacency test) can occur in |
| // a muxed set of streams (which share a common coded frame group start |
| // time) with a significantly jagged start across the streams. |
| (*range_for_next_append_) |
| ->AppendBuffersToEnd(buffers, coded_frame_group_start_pts_); |
| } else { |
| // Otherwise, use the first new buffer as proof of adjacency. |
| (*range_for_next_append_)->AppendBuffersToEnd(buffers, kNoTimestamp); |
| } |
| |
| last_appended_buffer_timestamp_ = buffers.back()->timestamp(); |
| last_appended_buffer_duration_ = buffers.back()->duration(); |
| last_appended_buffer_is_keyframe_ = buffers.back()->is_key_frame(); |
| last_appended_buffer_decode_timestamp_ = |
| buffers.back()->GetDecodeTimestamp(); |
| highest_timestamp_in_append_sequence_ = |
| (*range_for_next_append_)->GetEndTimestamp(); |
| highest_buffered_end_time_in_append_sequence_ = |
| (*range_for_next_append_)->GetBufferedEndTimestamp(); |
| } else { |
| base::TimeDelta new_range_start_time = |
| std::min(coded_frame_group_start_pts_, buffers.front()->timestamp()); |
| |
| const BufferQueue* buffers_for_new_range = &buffers; |
| BufferQueue trimmed_buffers; |
| |
| // If the new range is not being created because of a new coded frame group, |
| // then we must make sure that we start with a key frame. This can happen |
| // if the GOP in the previous append gets destroyed by a Remove() call. |
| if (!new_coded_frame_group_) { |
| BufferQueue::const_iterator itr = buffers.begin(); |
| |
| // Scan past all the non-key-frames. |
| while (itr != buffers.end() && !(*itr)->is_key_frame()) { |
| ++itr; |
| } |
| |
| // If we didn't find a key frame, then update the last appended |
| // buffer state and return. |
| if (itr == buffers.end()) { |
| last_appended_buffer_timestamp_ = buffers.back()->timestamp(); |
| last_appended_buffer_duration_ = buffers.back()->duration(); |
| last_appended_buffer_is_keyframe_ = buffers.back()->is_key_frame(); |
| last_appended_buffer_decode_timestamp_ = |
| buffers.back()->GetDecodeTimestamp(); |
| // Since we didn't buffer anything, don't update |
| // |highest_timestamp_in_append_sequence_|. |
| DVLOG(1) << __func__ << " " << GetStreamTypeName() |
| << ": new buffers in the middle of coded frame group depend on" |
| " keyframe that has been removed, and contain no keyframes." |
| " Skipping further processing."; |
| DVLOG(1) << __func__ << " " << GetStreamTypeName() |
| << ": done. ranges_=" << RangesToString(ranges_); |
| return; |
| } else if (itr != buffers.begin()) { |
| // Copy the first key frame and everything after it into |
| // |trimmed_buffers|. |
| trimmed_buffers.assign(itr, buffers.end()); |
| buffers_for_new_range = &trimmed_buffers; |
| } |
| |
| new_range_start_time = buffers_for_new_range->front()->timestamp(); |
| } |
| |
| range_for_next_append_ = AddToRanges(std::make_unique<SourceBufferRange>( |
| TypeToGapPolicy(GetType()), *buffers_for_new_range, |
| new_range_start_time, |
| base::BindRepeating(&SourceBufferStream::GetMaxInterbufferDistance, |
| base::Unretained(this)))); |
| |
| last_appended_buffer_timestamp_ = |
| buffers_for_new_range->back()->timestamp(); |
| last_appended_buffer_duration_ = buffers_for_new_range->back()->duration(); |
| last_appended_buffer_is_keyframe_ = |
| buffers_for_new_range->back()->is_key_frame(); |
| last_appended_buffer_decode_timestamp_ = |
| buffers_for_new_range->back()->GetDecodeTimestamp(); |
| highest_timestamp_in_append_sequence_ = |
| (*range_for_next_append_)->GetEndTimestamp(); |
| highest_buffered_end_time_in_append_sequence_ = |
| (*range_for_next_append_)->GetBufferedEndTimestamp(); |
| } |
| |
| new_coded_frame_group_ = false; |
| |
| MergeWithNextRangeIfNecessary(range_for_next_append_); |
| |
| // Some SAP-Type-2 append sequences require that we coalesce |
| // |range_for_next_append_| with the range that is *before* it. |
| if (range_for_next_append_ != ranges_.begin()) { |
| auto prior_range = range_for_next_append_; |
| prior_range--; |
| MergeWithNextRangeIfNecessary(prior_range); |
| } |
| |
| // Seek to try to fulfill a previous call to Seek(). |
| if (seek_pending_) { |
| DCHECK(!selected_range_); |
| DCHECK(deleted_buffers.empty()); |
| Seek(seek_buffer_timestamp_); |
| } |
| |
| if (!deleted_buffers.empty()) { |
| track_buffer_.insert(track_buffer_.end(), deleted_buffers.begin(), |
| deleted_buffers.end()); |
| DVLOG(3) << __func__ << " " << GetStreamTypeName() << " Added " |
| << deleted_buffers.size() |
| << " buffers to track buffer. TB size is now " |
| << track_buffer_.size(); |
| } else { |
| DVLOG(3) << __func__ << " " << GetStreamTypeName() |
| << " No deleted buffers for track buffer"; |
| } |
| |
| // Prune any extra buffers in |track_buffer_| if new keyframes |
| // are appended to the range covered by |track_buffer_|. |
| if (!track_buffer_.empty()) { |
| base::TimeDelta keyframe_timestamp = |
| FindKeyframeAfterTimestamp(track_buffer_.front()->timestamp()); |
| if (keyframe_timestamp != kNoTimestamp) |
| PruneTrackBuffer(keyframe_timestamp); |
| } |
| |
| SetSelectedRangeIfNeeded(next_buffer_timestamp); |
| |
| DVLOG(1) << __func__ << " " << GetStreamTypeName() |
| << ": done. ranges_=" << RangesToString(ranges_); |
| DCHECK(IsRangeListSorted(ranges_)); |
| DCHECK(OnlySelectedRangeIsSeeked()); |
| } |
| |
| void SourceBufferStream::Remove(base::TimeDelta start, |
| base::TimeDelta end, |
| base::TimeDelta duration) { |
| DVLOG(1) << __func__ << " " << GetStreamTypeName() << " (" |
| << start.InMicroseconds() << "us, " << end.InMicroseconds() << "us, " |
| << duration.InMicroseconds() << "us)"; |
| DCHECK(start >= base::TimeDelta()) << start.InMicroseconds() << "us"; |
| DCHECK(start < end) << "start " << start.InMicroseconds() << "us, end " |
| << end.InMicroseconds() << "us"; |
| DCHECK(duration != kNoTimestamp); |
| |
| base::TimeDelta remove_end_timestamp = duration; |
| base::TimeDelta keyframe_timestamp = FindKeyframeAfterTimestamp(end); |
| if (keyframe_timestamp != kNoTimestamp) { |
| remove_end_timestamp = keyframe_timestamp; |
| } else if (end < remove_end_timestamp) { |
| remove_end_timestamp = end; |
| } |
| |
| BufferQueue deleted_buffers; |
| RemoveInternal(start, remove_end_timestamp, false, &deleted_buffers); |
| |
| if (!deleted_buffers.empty()) { |
| // Buffers for the current position have been removed. |
| SetSelectedRangeIfNeeded(deleted_buffers.front()->timestamp()); |
| if (highest_output_buffer_timestamp_ == kNoTimestamp) { |
| // We just removed buffers for the current playback position for this |
| // stream, yet we also had output no buffer since the last Seek. |
| // Re-seek to prevent stall. |
| DVLOG(1) << __func__ << " " << GetStreamTypeName() << ": re-seeking to " |
| << seek_buffer_timestamp_ |
| << " to prevent stall if this time becomes buffered again"; |
| Seek(seek_buffer_timestamp_); |
| } |
| } |
| |
| DCHECK(OnlySelectedRangeIsSeeked()); |
| DCHECK(IsRangeListSorted(ranges_)); |
| } |
| |
| base::TimeDelta SourceBufferStream::PotentialNextAppendTimestamp() const { |
| // The next potential append will either be in a GOP adjacent to |
| // |highest_timestamp_in_append_sequence_| (if known), or if unknown and we |
| // are still at the beginning of a new coded frame group, then will be into |
| // the range (if any) to which |coded_frame_group_start_pts_| belongs. |
| if (highest_timestamp_in_append_sequence_ != kNoTimestamp) |
| return highest_timestamp_in_append_sequence_; |
| |
| if (new_coded_frame_group_) |
| return coded_frame_group_start_pts_; |
| |
| // If we still don't know a potential next append timestamp, then we have |
| // removed the range to which it previously belonged and have not completed a |
| // subsequent append or received a subsequent OnStartOfCodedFrameGroup() |
| // signal. |
| return kNoTimestamp; |
| } |
| |
| void SourceBufferStream::UpdateLastAppendStateForRemove( |
| base::TimeDelta remove_start, |
| base::TimeDelta remove_end, |
| bool exclude_start) { |
| // TODO(chcunningham): change exclude_start to include_start in this class and |
| // SourceBufferRange. Negatives are hard to reason about. |
| bool include_start = !exclude_start; |
| |
| // No need to check previous append's GOP if starting a new CFG. New CFG is |
| // already required to begin with a key frame. |
| if (new_coded_frame_group_) |
| return; |
| |
| if (range_for_next_append_ != ranges_.end()) { |
| if (last_appended_buffer_timestamp_ != kNoTimestamp) { |
| // Note start and end of last appended GOP. |
| base::TimeDelta gop_end = highest_timestamp_in_append_sequence_; |
| base::TimeDelta gop_start = |
| (*range_for_next_append_)->KeyframeBeforeTimestamp(gop_end); |
| |
| // If last append is about to be disrupted, reset associated state so we |
| // know to create a new range for future appends and require an initial |
| // key frame. |
| if (((include_start && remove_start == gop_end) || |
| remove_start < gop_end) && |
| remove_end > gop_start) { |
| DVLOG(2) << __func__ << " " << GetStreamTypeName() |
| << " Resetting next append state for remove (" |
| << remove_start.InMicroseconds() << "us, " |
| << remove_end.InMicroseconds() << "us, " << exclude_start |
| << ")"; |
| range_for_next_append_ = ranges_.end(); |
| ResetLastAppendedState(); |
| } |
| } else { |
| NOTREACHED() << __func__ << " " << GetStreamTypeName() |
| << " range_for_next_append_ set, but not tracking last" |
| << " append nor new coded frame group."; |
| } |
| } |
| } |
| |
| void SourceBufferStream::RemoveInternal(base::TimeDelta start, |
| base::TimeDelta end, |
| bool exclude_start, |
| BufferQueue* deleted_buffers) { |
| DVLOG(2) << __func__ << " " << GetStreamTypeName() << " (" |
| << start.InMicroseconds() << "us, " << end.InMicroseconds() << "us, " |
| << exclude_start << ")"; |
| DVLOG(3) << __func__ << " " << GetStreamTypeName() |
| << ": before remove ranges_=" << RangesToString(ranges_); |
| |
| DCHECK(start >= base::TimeDelta()); |
| DCHECK(start < end) << "start " << start.InMicroseconds() << "us, end " |
| << end.InMicroseconds() << "us"; |
| DCHECK(deleted_buffers); |
| |
| // Doing this up-front simplifies decisions about |range_for_next_append_| |
| // below. |
| UpdateLastAppendStateForRemove(start, end, exclude_start); |
| |
| auto itr = ranges_.begin(); |
| while (itr != ranges_.end()) { |
| SourceBufferRange* range = itr->get(); |
| if (range->GetStartTimestamp() >= end) |
| break; |
| |
| // Split off any remaining GOPs starting at or after |end| and add it to |
| // |ranges_|. |
| std::unique_ptr<SourceBufferRange> new_range = range->SplitRange(end); |
| if (new_range) { |
| itr = ranges_.insert(++itr, std::move(new_range)); |
| |
| // Update |range_for_next_append_| if it was previously |range| and should |
| // be the new range (that |itr| is at) now. |
| if (range_for_next_append_ != ranges_.end() && |
| range_for_next_append_->get() == range) { |
| base::TimeDelta potential_next_append_timestamp = |
| PotentialNextAppendTimestamp(); |
| if (potential_next_append_timestamp != kNoTimestamp && |
| (*itr)->BelongsToRange(potential_next_append_timestamp)) { |
| range_for_next_append_ = itr; |
| } |
| } |
| |
| // Update the selected range if the next buffer position was transferred |
| // to the newly inserted range (that |itr| is at now). |
| if ((*itr)->HasNextBufferPosition()) |
| SetSelectedRange(itr->get()); |
| |
| --itr; |
| } |
| |
| // Truncate the current range so that it only contains data before |
| // the removal range. |
| BufferQueue saved_buffers; |
| bool delete_range = range->TruncateAt(start, &saved_buffers, exclude_start); |
| |
| // Check to see if the current playback position was removed and update the |
| // selected range appropriately. |
| if (!saved_buffers.empty()) { |
| DCHECK(!range->HasNextBufferPosition()); |
| DCHECK(deleted_buffers->empty()); |
| |
| *deleted_buffers = saved_buffers; |
| } |
| |
| if (range == selected_range_ && !range->HasNextBufferPosition()) |
| SetSelectedRange(NULL); |
| |
| // If the current range now is completely covered by the removal |
| // range then delete it and move on. |
| if (delete_range) { |
| DeleteAndRemoveRange(&itr); |
| continue; |
| } |
| |
| // Clear |range_for_next_append_| if we determine that the removal |
| // operation makes it impossible for the next append to be added |
| // to the current range. |
| if (range_for_next_append_ != ranges_.end() && |
| range_for_next_append_->get() == range) { |
| base::TimeDelta potential_next_append_timestamp = |
| PotentialNextAppendTimestamp(); |
| |
| if (!range->BelongsToRange(potential_next_append_timestamp)) { |
| DVLOG(1) << "Resetting range_for_next_append_ since the next append" |
| << " can't add to the current range."; |
| range_for_next_append_ = |
| FindExistingRangeFor(potential_next_append_timestamp); |
| } |
| } |
| |
| // Move on to the next range. |
| ++itr; |
| } |
| |
| DVLOG(3) << __func__ << " " << GetStreamTypeName() |
| << ": after remove ranges_=" << RangesToString(ranges_); |
| |
| DCHECK(OnlySelectedRangeIsSeeked()); |
| } |
| |
| void SourceBufferStream::ResetSeekState() { |
| SetSelectedRange(NULL); |
| track_buffer_.clear(); |
| config_change_pending_ = false; |
| highest_output_buffer_timestamp_ = kNoTimestamp; |
| just_exhausted_track_buffer_ = false; |
| pending_buffer_.reset(); |
| pending_buffers_complete_ = false; |
| } |
| |
| void SourceBufferStream::ResetLastAppendedState() { |
| last_appended_buffer_timestamp_ = kNoTimestamp; |
| last_appended_buffer_duration_ = kNoTimestamp; |
| last_appended_buffer_is_keyframe_ = false; |
| last_appended_buffer_decode_timestamp_ = kNoDecodeTimestamp(); |
| highest_timestamp_in_append_sequence_ = kNoTimestamp; |
| highest_buffered_end_time_in_append_sequence_ = kNoTimestamp; |
| } |
| |
| bool SourceBufferStream::ShouldSeekToStartOfBuffered( |
| base::TimeDelta seek_timestamp) const { |
| if (ranges_.empty()) |
| return false; |
| base::TimeDelta beginning_of_buffered = ranges_.front()->GetStartTimestamp(); |
| return (seek_timestamp <= beginning_of_buffered && |
| beginning_of_buffered < kSeekToStartFudgeRoom()); |
| } |
| |
| bool SourceBufferStream::IsDtsMonotonicallyIncreasing( |
| const BufferQueue& buffers) { |
| DCHECK(!buffers.empty()); |
| DecodeTimestamp prev_dts = last_appended_buffer_decode_timestamp_; |
| for (BufferQueue::const_iterator itr = buffers.begin(); |
| itr != buffers.end(); ++itr) { |
| DecodeTimestamp current_dts = (*itr)->GetDecodeTimestamp(); |
| bool current_is_keyframe = (*itr)->is_key_frame(); |
| DCHECK(current_dts != kNoDecodeTimestamp()); |
| DCHECK((*itr)->duration() >= base::TimeDelta()) |
| << "Packet with invalid duration." |
| << " pts " << (*itr)->timestamp().InMicroseconds() << "us dts " |
| << (*itr)->GetDecodeTimestamp().InMicroseconds() << "us dur " |
| << (*itr)->duration().InMicroseconds() << "us"; |
| |
| // Only verify DTS monotonicity within the current GOP (since the last |
| // keyframe). FrameProcessor should have enforced that all audio frames are |
| // keyframes already, or are nonkeyframes with monotonically increasing PTS |
| // since the last keyframe for those types of audio for which nonkeyframes |
| // may be involved, e.g. xHE-AAC. Video nonkeyframes are not restricted to |
| // being in-order by PTS, but both audio and video nonkeyframes must be in |
| // decode sequence since the last keyframe. |
| if (current_is_keyframe) { |
| // Reset prev_dts tracking since a new GOP is starting. |
| prev_dts = kNoDecodeTimestamp(); |
| } |
| |
| if (prev_dts != kNoDecodeTimestamp()) { |
| if (current_dts < prev_dts) { |
| MEDIA_LOG(ERROR, media_log_) |
| << "Buffers did not monotonically increase."; |
| return false; |
| } |
| } |
| |
| prev_dts = current_dts; |
| } |
| return true; |
| } |
| |
| bool SourceBufferStream::OnlySelectedRangeIsSeeked() const { |
| for (auto itr = ranges_.begin(); itr != ranges_.end(); ++itr) { |
| if ((*itr)->HasNextBufferPosition() && itr->get() != selected_range_) |
| return false; |
| } |
| return !selected_range_ || selected_range_->HasNextBufferPosition(); |
| } |
| |
| bool SourceBufferStream::UpdateMaxInterbufferDtsDistance( |
| const BufferQueue& buffers) { |
| DCHECK(!buffers.empty()); |
| base::TimeDelta old_distance = max_interbuffer_distance_; |
| DecodeTimestamp prev_dts = last_appended_buffer_decode_timestamp_; |
| for (BufferQueue::const_iterator itr = buffers.begin(); |
| itr != buffers.end(); ++itr) { |
| DecodeTimestamp current_dts = (*itr)->GetDecodeTimestamp(); |
| DCHECK(current_dts != kNoDecodeTimestamp()); |
| |
| base::TimeDelta interbuffer_distance = (*itr)->duration(); |
| DCHECK(interbuffer_distance >= base::TimeDelta()); |
| |
| if (prev_dts != kNoDecodeTimestamp()) { |
| interbuffer_distance = |
| std::max(current_dts - prev_dts, interbuffer_distance); |
| } |
| |
| DCHECK(max_interbuffer_distance_ >= |
| base::Milliseconds(kMinimumInterbufferDistanceInMs)); |
| max_interbuffer_distance_ = |
| std::max(max_interbuffer_distance_, interbuffer_distance); |
| prev_dts = current_dts; |
| } |
| bool changed_max = max_interbuffer_distance_ != old_distance; |
| DVLOG_IF(2, changed_max) << __func__ << " " << GetStreamTypeName() |
| << " Changed max interbuffer DTS distance from " |
| << old_distance.InMicroseconds() << "us to " |
| << max_interbuffer_distance_.InMicroseconds() |
| << "us"; |
| return changed_max; |
| } |
| |
| void SourceBufferStream::SetConfigIds(const BufferQueue& buffers) { |
| for (BufferQueue::const_iterator itr = buffers.begin(); |
| itr != buffers.end(); ++itr) { |
| (*itr)->SetConfigId(append_config_index_); |
| } |
| } |
| |
| void SourceBufferStream::OnMemoryPressure( |
| base::TimeDelta media_time, |
| base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level, |
| bool force_instant_gc) { |
| DVLOG(4) << __func__ << " level=" << memory_pressure_level; |
| // TODO(sebmarchand): Check if MEMORY_PRESSURE_LEVEL_MODERATE should also be |
| // ignored. |
| if (memory_pressure_level == |
| base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) { |
| return; |
| } |
| |
| memory_pressure_level_ = memory_pressure_level; |
| |
| if (force_instant_gc) |
| GarbageCollectIfNeeded(media_time, 0); |
| } |
| |
| bool SourceBufferStream::GarbageCollectIfNeeded(base::TimeDelta media_time, |
| size_t newDataSize) { |
| DCHECK(media_time != kNoTimestamp); |
| // Garbage collection should only happen before/during appending new data, |
| // which should not happen in end-of-stream state. Unless we also allow GC to |
| // happen on memory pressure notifications, which might happen even in EOS |
| // state. |
| #if defined(STARBOARD) |
| // TODO: Check if we should enable `kEnableMemoryPressureBasedSourceBufferGC`. |
| constexpr bool kEnableMemoryPressureBasedSourceBufferGC = true; |
| if (!kEnableMemoryPressureBasedSourceBufferGC) { |
| DCHECK(!end_of_stream_); |
| } |
| #else // defined(STARBOARD) |
| if (!base::FeatureList::IsEnabled(kMemoryPressureBasedSourceBufferGC)) |
| DCHECK(!end_of_stream_); |
| #endif // defined(STARBOARD) |
| // Compute size of |ranges_|. |
| size_t ranges_size = GetBufferedSize(); |
| |
| // Sanity and overflow checks |
| if ((newDataSize > memory_limit_) || |
| (ranges_size + newDataSize < ranges_size)) { |
| LIMITED_MEDIA_LOG(DEBUG, media_log_, num_garbage_collect_algorithm_logs_, |
| kMaxGarbageCollectAlgorithmWarningLogs) |
| << GetStreamTypeName() << " stream: " |
| << "new append of newDataSize=" << newDataSize |
| << " bytes exceeds memory_limit_=" << memory_limit_ |
| << ", currently buffered ranges_size=" << ranges_size; |
| return false; |
| } |
| |
| size_t effective_memory_limit = memory_limit_; |
| #if defined(STARBOARD) |
| if (kEnableMemoryPressureBasedSourceBufferGC) { |
| #else // defined(STARBOARD) |
| if (base::FeatureList::IsEnabled(kMemoryPressureBasedSourceBufferGC)) { |
| #endif // defined(STARBOARD) |
| switch (memory_pressure_level_) { |
| case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE: |
| effective_memory_limit = memory_limit_ / 2; |
| break; |
| case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL: |
| effective_memory_limit = 0; |
| break; |
| case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE: |
| break; |
| } |
| } |
| |
| #if defined(STARBOARD) |
| // Address duration based GC. |
| base::TimeDelta duration = GetBufferedDurationForGarbageCollection(); |
| const SbTime duration_gc_threadold = |
| SbMediaGetBufferGarbageCollectionDurationThreshold(); |
| if (duration.ToSbTime() > duration_gc_threadold) { |
| effective_memory_limit = ranges_size * duration_gc_threadold / |
| duration.ToSbTime(); |
| } |
| #endif // defined(STARBOARD) |
| |
| // Return if we're under or at the memory limit. |
| if (ranges_size + newDataSize <= effective_memory_limit) |
| return true; |
| |
| size_t bytes_over_hard_memory_limit = 0; |
| if (ranges_size + newDataSize > memory_limit_) |
| bytes_over_hard_memory_limit = ranges_size + newDataSize - memory_limit_; |
| |
| size_t bytes_to_free = ranges_size + newDataSize - effective_memory_limit; |
| |
| DVLOG(2) << __func__ << " " << GetStreamTypeName() |
| << ": Before GC media_time=" << media_time.InMicroseconds() |
| << "us ranges_=" << RangesToString(ranges_) |
| << " seek_pending_=" << seek_pending_ |
| << " ranges_size=" << ranges_size << " newDataSize=" << newDataSize |
| << " memory_limit_=" << memory_limit_ |
| << " effective_memory_limit=" << effective_memory_limit |
| << " last_appended_buffer_timestamp_=" |
| << last_appended_buffer_timestamp_.InMicroseconds() |
| << "us highest_timestamp_in_append_sequence_=" |
| << highest_timestamp_in_append_sequence_.InMicroseconds() |
| << "us highest_buffered_end_time_in_append_sequence_=" |
| << highest_buffered_end_time_in_append_sequence_.InMicroseconds() |
| << "us"; |
| |
| if (selected_range_ && !seek_pending_ && |
| media_time > selected_range_->GetBufferedEndTimestamp()) { |
| // Strictly speaking |media_time| (taken from HTMLMediaElement::currentTime) |
| // should always be in the buffered ranges, but media::Pipeline uses audio |
| // stream as the main time source, when audio is present. |
| // In cases when audio and video streams have different buffered ranges, the |
| // |media_time| value might be slightly outside of the video stream buffered |
| // range. In those cases we need to clamp |media_time| value to the current |
| // stream buffered ranges, to ensure the MSE garbage collection algorithm |
| // works correctly (see crbug.com/563292 for details). |
| base::TimeDelta selected_buffered_end = |
| selected_range_->GetBufferedEndTimestamp(); |
| |
| DVLOG(2) << __func__ << " media_time " << media_time.InMicroseconds() |
| << "us is outside of selected_range_=[" |
| << selected_range_->GetStartTimestamp().InMicroseconds() << "us;" |
| << selected_buffered_end.InMicroseconds() |
| << "us] clamping media_time to be " |
| << selected_buffered_end.InMicroseconds() << "us"; |
| media_time = selected_buffered_end; |
| } |
| |
| size_t bytes_freed = 0; |
| |
| // If last appended buffer position was earlier than the current playback time |
| // then try deleting data between last append and current media_time. |
| if (last_appended_buffer_timestamp_ != kNoTimestamp && |
| last_appended_buffer_duration_ != kNoTimestamp && |
| highest_buffered_end_time_in_append_sequence_ != kNoTimestamp && |
| media_time > highest_buffered_end_time_in_append_sequence_) { |
| size_t between = FreeBuffersAfterLastAppended(bytes_to_free, media_time); |
| DVLOG(3) << __func__ << " FreeBuffersAfterLastAppended " |
| << " released " << between << " bytes" |
| << " ranges_=" << RangesToString(ranges_); |
| bytes_freed += between; |
| |
| // Some players start appending data at the new seek target position before |
| // actually initiating the seek operation (i.e. they try to improve seek |
| // performance by prebuffering some data at the seek target position and |
| // initiating seek once enough data is pre-buffered. In those cases we'll |
| // see that data is being appended at some new position, but there is no |
| // pending seek reported yet. In this situation we need to try preserving |
| // the most recently appended data, i.e. data belonging to the same buffered |
| // range as the most recent append. |
| if (range_for_next_append_ != ranges_.end()) { |
| DCHECK((*range_for_next_append_)->GetStartTimestamp() <= media_time); |
| media_time = (*range_for_next_append_)->GetStartTimestamp(); |
| DVLOG(3) << __func__ << " media_time adjusted to " |
| << media_time.InMicroseconds() << "us"; |
| } |
| } |
| |
| // If there is an unsatisfied pending seek, we can safely remove all data that |
| // is earlier than seek target, then remove from the back until we reach the |
| // most recently appended GOP and then remove from the front if we still don't |
| // have enough space for the upcoming append. |
| if (bytes_freed < bytes_to_free && seek_pending_) { |
| DCHECK(!ranges_.empty()); |
| // All data earlier than the seek target |media_time| can be removed safely |
| size_t front = FreeBuffers(bytes_to_free - bytes_freed, media_time, false); |
| DVLOG(3) << __func__ << " Removed " << front |
| << " bytes from the front. ranges_=" << RangesToString(ranges_); |
| bytes_freed += front; |
| |
| // If removing data earlier than |media_time| didn't free up enough space, |
| // then try deleting from the back until we reach most recently appended GOP |
| if (bytes_freed < bytes_to_free) { |
| size_t back = FreeBuffers(bytes_to_free - bytes_freed, media_time, true); |
| DVLOG(3) << __func__ << " Removed " << back |
| << " bytes from the back. ranges_=" << RangesToString(ranges_); |
| bytes_freed += back; |
| } |
| |
| // If even that wasn't enough, then try greedily deleting from the front, |
| // that should allow us to remove as much data as necessary to succeed. |
| if (bytes_freed < bytes_to_free) { |
| size_t front2 = FreeBuffers(bytes_to_free - bytes_freed, |
| ranges_.back()->GetEndTimestamp(), false); |
| DVLOG(3) << __func__ << " Removed " << front2 |
| << " bytes from the front. ranges_=" << RangesToString(ranges_); |
| bytes_freed += front2; |
| } |
| DCHECK(bytes_freed >= bytes_to_free); |
| } |
| |
| // Try removing data from the front of the SourceBuffer up to |media_time| |
| // position. |
| if (bytes_freed < bytes_to_free) { |
| size_t front = FreeBuffers(bytes_to_free - bytes_freed, media_time, false); |
| DVLOG(3) << __func__ << " Removed " << front |
| << " bytes from the front. ranges_=" << RangesToString(ranges_); |
| bytes_freed += front; |
| } |
| |
| // Try removing data from the back of the SourceBuffer, until we reach the |
| // most recent append position. |
| if (bytes_freed < bytes_to_free) { |
| size_t back = FreeBuffers(bytes_to_free - bytes_freed, media_time, true); |
| DVLOG(3) << __func__ << " Removed " << back |
| << " bytes from the back. ranges_=" << RangesToString(ranges_); |
| bytes_freed += back; |
| } |
| |
| DVLOG(2) << __func__ << " " << GetStreamTypeName() |
| << ": After GC bytes_to_free=" << bytes_to_free |
| << " bytes_freed=" << bytes_freed |
| << " bytes_over_hard_memory_limit=" << bytes_over_hard_memory_limit |
| << " ranges_=" << RangesToString(ranges_); |
| |
| return bytes_freed >= bytes_over_hard_memory_limit; |
| } |
| |
| size_t SourceBufferStream::FreeBuffersAfterLastAppended( |
| size_t total_bytes_to_free, |
| base::TimeDelta media_time) { |
| DVLOG(4) << __func__ << " highest_buffered_end_time_in_append_sequence_=" |
| << highest_buffered_end_time_in_append_sequence_.InMicroseconds() |
| << "us media_time=" << media_time.InMicroseconds() << "us"; |
| |
| base::TimeDelta remove_range_start = |
| highest_buffered_end_time_in_append_sequence_; |
| if (last_appended_buffer_is_keyframe_) |
| remove_range_start += GetMaxInterbufferDistance(); |
| |
| base::TimeDelta remove_range_start_keyframe = |
| FindKeyframeAfterTimestamp(remove_range_start); |
| if (remove_range_start_keyframe != kNoTimestamp) |
| remove_range_start = remove_range_start_keyframe; |
| if (remove_range_start >= media_time) |
| return 0; |
| |
| base::TimeDelta remove_range_end; |
| size_t bytes_freed = GetRemovalRange(remove_range_start, |
| media_time, |
| total_bytes_to_free, |
| &remove_range_end); |
| if (bytes_freed > 0) { |
| DVLOG(4) << __func__ << " removing [" << remove_range_start.InMicroseconds() |
| << "us;" << remove_range_end.InMicroseconds() << "us]"; |
| Remove(remove_range_start, remove_range_end, media_time); |
| } |
| |
| return bytes_freed; |
| } |
| |
| size_t SourceBufferStream::GetRemovalRange( |
| base::TimeDelta start_timestamp, |
| base::TimeDelta end_timestamp, |
| size_t total_bytes_to_free, |
| base::TimeDelta* removal_end_timestamp) { |
| DCHECK(start_timestamp >= base::TimeDelta()) |
| << start_timestamp.InMicroseconds() << "us"; |
| DCHECK(start_timestamp < end_timestamp) |
| << "start " << start_timestamp.InMicroseconds() << "us, end " |
| << end_timestamp.InMicroseconds() << "us"; |
| |
| size_t bytes_freed = 0; |
| |
| for (auto itr = ranges_.begin(); |
| itr != ranges_.end() && bytes_freed < total_bytes_to_free; ++itr) { |
| SourceBufferRange* range = itr->get(); |
| if (range->GetStartTimestamp() >= end_timestamp) |
| break; |
| if (range->GetEndTimestamp() < start_timestamp) |
| continue; |
| |
| size_t bytes_to_free = total_bytes_to_free - bytes_freed; |
| size_t bytes_removed = range->GetRemovalGOP( |
| start_timestamp, end_timestamp, bytes_to_free, removal_end_timestamp); |
| bytes_freed += bytes_removed; |
| } |
| return bytes_freed; |
| } |
| |
| size_t SourceBufferStream::FreeBuffers(size_t total_bytes_to_free, |
| base::TimeDelta media_time, |
| bool reverse_direction) { |
| TRACE_EVENT2("media", "SourceBufferStream::FreeBuffers", |
| "total bytes to free", total_bytes_to_free, |
| "reverse direction", reverse_direction); |
| |
| DCHECK_GT(total_bytes_to_free, 0u); |
| size_t bytes_freed = 0; |
| |
| // This range will save the last GOP appended to |range_for_next_append_| |
| // if the buffers surrounding it get deleted during garbage collection. |
| std::unique_ptr<SourceBufferRange> new_range_for_append; |
| |
| while (!ranges_.empty() && bytes_freed < total_bytes_to_free) { |
| SourceBufferRange* current_range = NULL; |
| BufferQueue buffers; |
| size_t bytes_deleted = 0; |
| |
| if (reverse_direction) { |
| current_range = ranges_.back().get(); |
| DVLOG(5) << "current_range=" << RangeToString(*current_range); |
| if (current_range->LastGOPContainsNextBufferPosition()) { |
| DCHECK_EQ(current_range, selected_range_); |
| DVLOG(5) << "current_range contains next read position, stopping GC"; |
| break; |
| } |
| DVLOG(5) << "Deleting GOP from back: " << RangeToString(*current_range); |
| bytes_deleted = current_range->DeleteGOPFromBack(&buffers); |
| } else { |
| current_range = ranges_.front().get(); |
| DVLOG(5) << "current_range=" << RangeToString(*current_range); |
| |
| // FirstGOPEarlierThanMediaTime() is useful here especially if |
| // |seek_pending_| (such that no range contains next buffer |
| // position). |
| // FirstGOPContainsNextBufferPosition() is useful here especially if |
| // |!seek_pending_| to protect against DeleteGOPFromFront() if |
| // FirstGOPEarlierThanMediaTime() was insufficient alone. |
| if (!current_range->FirstGOPEarlierThanMediaTime(media_time) || |
| current_range->FirstGOPContainsNextBufferPosition()) { |
| // We have removed all data up to the GOP that contains current playback |
| // position, we can't delete any further. |
| DVLOG(5) << "current_range contains playback position, stopping GC"; |
| break; |
| } |
| DVLOG(4) << "Deleting GOP from front: " << RangeToString(*current_range) |
| << ", media_time: " << media_time.InMicroseconds() |
| << ", current_range->HasNextBufferPosition(): " |
| << current_range->HasNextBufferPosition(); |
| bytes_deleted = current_range->DeleteGOPFromFront(&buffers); |
| } |
| |
| // Check to see if we've just deleted the GOP that was last appended. |
| base::TimeDelta end_timestamp = buffers.back()->timestamp(); |
| if (end_timestamp == last_appended_buffer_timestamp_) { |
| DCHECK(last_appended_buffer_timestamp_ != kNoTimestamp); |
| DCHECK(!new_range_for_append); |
| |
| // Create a new range containing these buffers. |
| new_range_for_append = std::make_unique<SourceBufferRange>( |
| TypeToGapPolicy(GetType()), buffers, kNoTimestamp, |
| base::BindRepeating(&SourceBufferStream::GetMaxInterbufferDistance, |
| base::Unretained(this))); |
| |
| range_for_next_append_ = ranges_.end(); |
| } else { |
| bytes_freed += bytes_deleted; |
| } |
| |
| if (current_range->size_in_bytes() == 0) { |
| DCHECK_NE(current_range, selected_range_); |
| DCHECK(range_for_next_append_ == ranges_.end() || |
| range_for_next_append_->get() != current_range); |
| |
| // Delete |current_range| by popping it out of |ranges_|. |
| reverse_direction ? ranges_.pop_back() : ranges_.pop_front(); |
| } |
| |
| if (reverse_direction && new_range_for_append) { |
| // We don't want to delete any further, or we'll be creating gaps |
| break; |
| } |
| } |
| |
| // Insert |new_range_for_append| into |ranges_|, if applicable. |
| if (new_range_for_append) { |
| range_for_next_append_ = AddToRanges(std::move(new_range_for_append)); |
| DCHECK(range_for_next_append_ != ranges_.end()); |
| |
| // Check to see if we need to merge the just added range that was in |
| // |new_range_for_append| with the range before or after it. That added |
| // range is created whenever the last GOP appended is encountered, |
| // regardless of whether any buffers after it are ultimately deleted. |
| // Merging is necessary if there were no buffers (or very few buffers) |
| // deleted after creating that added range. |
| if (range_for_next_append_ != ranges_.begin()) { |
| auto range_before_next = range_for_next_append_; |
| --range_before_next; |
| MergeWithNextRangeIfNecessary(range_before_next); |
| } |
| MergeWithNextRangeIfNecessary(range_for_next_append_); |
| } |
| return bytes_freed; |
| } |
| |
| void SourceBufferStream::TrimSpliceOverlap(const BufferQueue& new_buffers) { |
| DCHECK(!new_buffers.empty()); |
| DCHECK_EQ(SourceBufferStreamType::kAudio, GetType()); |
| |
| const base::TimeDelta splice_timestamp = new_buffers.front()->timestamp(); |
| |
| // Since some audio formats may have nonkeyframes (in PTS order since last |
| // keyframe), if the front of the new buffers is one of those, it cannot be |
| // used to begin a decode following an overlap. Here, we bail in this case, |
| // since such a splice could not be coherently decoded. |
| if (!new_buffers.front()->is_key_frame()) { |
| DVLOG(3) << __func__ |
| << " No splice trimming. Front of |new_buffers| is not a " |
| "keyframe, at time " |
| << splice_timestamp.InMicroseconds(); |
| return; |
| } |
| |
| // Find the overlapped range (if any). |
| auto range_itr = FindExistingRangeFor(splice_timestamp); |
| if (range_itr == ranges_.end()) { |
| DVLOG(3) << __func__ << " No splice trimming. No range overlap at time " |
| << splice_timestamp.InMicroseconds(); |
| return; |
| } |
| |
| // Search for overlapped buffer needs exclusive end value. Choosing smallest |
| // possible value. |
| const base::TimeDelta end_pts = splice_timestamp + base::Microseconds(1); |
| |
| // Find if new buffer's start would overlap an existing buffer. Note that |
| // overlapped audio buffers might be nonkeyframes, but if so, FrameProcessor |
| // ensures they are in PTS order since the previous keyframe. |
| BufferQueue overlapped_buffers; |
| if (!(*range_itr) |
| ->GetBuffersInRange(splice_timestamp, end_pts, |
| &overlapped_buffers)) { |
| // Bail if no overlapped buffers found. |
| DVLOG(3) << __func__ << " No splice trimming. No buffer overlap at time " |
| << splice_timestamp.InMicroseconds(); |
| return; |
| } |
| |
| // At most one buffer should exist containing the time of the newly appended |
| // buffer's start. It may happen that bad content appends buffers with |
| // durations that cause nonsensical overlap. Trimming should not be performed |
| // in these cases, as the content is already in a bad state. |
| if (overlapped_buffers.size() != 1U) { |
| DVLOG(3) << __func__ |
| << " No splice trimming. Found more than one overlapped buffer" |
| " (bad content) at time " |
| << splice_timestamp.InMicroseconds(); |
| |
| MEDIA_LOG(WARNING, media_log_) |
| << "Media is badly muxed. Detected " << overlapped_buffers.size() |
| << " overlapping audio buffers at time " |
| << splice_timestamp.InMicroseconds(); |
| return; |
| } |
| StreamParserBuffer* overlapped_buffer = overlapped_buffers.front().get(); |
| |
| if (overlapped_buffer->timestamp() == splice_timestamp) { |
| // Ignore buffers with the same start time. They will be completely removed |
| // in PrepareRangesForNextAppend(). |
| DVLOG(3) << __func__ << " No splice trimming at time " |
| << splice_timestamp.InMicroseconds() |
| << ". Overlapped buffer will be completely removed."; |
| return; |
| } |
| |
| // Trimming a buffer with estimated duration is too risky. Estimates are rough |
| // and what appears to be overlap may really just be a bad estimate. Imprecise |
| // trimming may lead to loss of AV sync. |
| if (overlapped_buffer->is_duration_estimated()) { |
| DVLOG(3) << __func__ << " Skipping audio splice trimming at PTS=" |
| << splice_timestamp.InMicroseconds() << ". Overlapped buffer has " |
| << "estimated duration."; |
| return; |
| } |
| |
| // Determine the duration of overlap. |
| base::TimeDelta overlapped_end_time = |
| overlapped_buffer->timestamp() + overlapped_buffer->duration(); |
| base::TimeDelta overlap_duration = overlapped_end_time - splice_timestamp; |
| |
| // At this point overlap should be non-empty (ruled out same-timestamp above). |
| DCHECK_GT(overlap_duration, base::TimeDelta()); |
| |
| // Don't trim for overlaps of less than one millisecond (which is frequently |
| // the extent of timestamp resolution for poorly encoded media). |
| if (overlap_duration < base::Milliseconds(1)) { |
| std::stringstream log_string; |
| log_string << "Skipping audio splice trimming at PTS=" |
| << splice_timestamp.InMicroseconds() << "us. Found only " |
| << overlap_duration.InMicroseconds() |
| << "us of overlap, need at least 1000us. Multiple occurrences " |
| << "may result in loss of A/V sync."; |
| LIMITED_MEDIA_LOG(DEBUG, media_log_, num_splice_logs_, kMaxAudioSpliceLogs) |
| << log_string.str(); |
| DVLOG(1) << __func__ << log_string.str(); |
| return; |
| } |
| |
| // Trim overlap from the existing buffer. |
| DecoderBuffer::DiscardPadding discard_padding = |
| overlapped_buffer->discard_padding(); |
| discard_padding.second += overlap_duration; |
| overlapped_buffer->set_discard_padding(discard_padding); |
| overlapped_buffer->set_duration(overlapped_buffer->duration() - |
| overlap_duration); |
| |
| // Note that the range's end time tracking shouldn't need explicit updating |
| // here due to the overlapped buffer's truncation because the range tracks |
| // that end time using a pointer to the buffer (which should be |
| // |overlapped_buffer| if the overlap occurred at the end of the range). |
| // Every audio frame is either a keyframe, or if a nonkeyframe is in PTS order |
| // since the last keyframe, so there is no out-of-order PTS vs DTS sequencing |
| // to overcome. If the overlap occurs in the middle of the range, the caller |
| // invokes methods on the range which internally update the end time(s) of the |
| // resulting range(s) involved in the append. |
| |
| std::stringstream log_string; |
| log_string << "Audio buffer splice at PTS=" |
| << splice_timestamp.InMicroseconds() |
| << "us. Trimmed tail of overlapped buffer (PTS=" |
| << overlapped_buffer->timestamp().InMicroseconds() << "us) by " |
| << overlap_duration.InMicroseconds() << "us."; |
| LIMITED_MEDIA_LOG(DEBUG, media_log_, num_splice_logs_, kMaxAudioSpliceLogs) |
| << log_string.str(); |
| DVLOG(1) << __func__ << log_string.str(); |
| } |
| |
| void SourceBufferStream::PrepareRangesForNextAppend( |
| const BufferQueue& new_buffers, |
| BufferQueue* deleted_buffers) { |
| DCHECK(deleted_buffers); |
| |
| if (GetType() == SourceBufferStreamType::kAudio) |
| TrimSpliceOverlap(new_buffers); |
| |
| base::TimeDelta buffers_start_timestamp = kNoTimestamp; |
| base::TimeDelta buffers_end_timestamp = kNoTimestamp; |
| GetTimestampInterval(new_buffers, &buffers_start_timestamp, |
| &buffers_end_timestamp); |
| DCHECK(buffers_start_timestamp != kNoTimestamp); |
| DCHECK(buffers_end_timestamp != kNoTimestamp); |
| |
| // 1. Clean up the old buffers between the last appended buffers and the |
| // beginning of |new_buffers|. |
| if (highest_timestamp_in_append_sequence_ != kNoTimestamp && |
| highest_timestamp_in_append_sequence_ < buffers_start_timestamp) { |
| RemoveInternal(highest_timestamp_in_append_sequence_, |
| buffers_start_timestamp, true, deleted_buffers); |
| } |
| |
| // 2. Delete the buffers that |new_buffers| overlaps. |
| // There may be buffers in |new_buffers| with timestamp before |
| // |highest_timestamp_in_append_sequence_| that shouldn't trigger removal of |
| // stuff before |highest_timestamp_in_append_sequence_|. |
| if (highest_timestamp_in_append_sequence_ != kNoTimestamp && |
| buffers_start_timestamp < highest_timestamp_in_append_sequence_) { |
| DCHECK(highest_timestamp_in_append_sequence_ <= |
| highest_buffered_end_time_in_append_sequence_); |
| buffers_start_timestamp = highest_buffered_end_time_in_append_sequence_; |
| } |
| |
| if (new_coded_frame_group_) { |
| // Extend the deletion range earlier to the coded frame group start time if |
| // this is the first append in a new coded frame group. |
| DCHECK(coded_frame_group_start_pts_ != kNoTimestamp); |
| buffers_start_timestamp = |
| std::min(coded_frame_group_start_pts_, buffers_start_timestamp); |
| } |
| |
| // Return early if no further overlap removal is needed. First check if |
| // |buffers_start_timestamp| is in the middle of the range; we could be |
| // overlap-appending the middle of a previous coded frame sequence's range |
| // with non-keyframes prior to |highest_timestamp_in_append_sequence_|, so we |
| // need to split that range appropriately here and then return early. If we |
| // don't return early here, overlap removal (including any necessary range |
| // splitting) will occur. |
| if (buffers_start_timestamp >= buffers_end_timestamp) { |
| DCHECK(highest_timestamp_in_append_sequence_ != kNoTimestamp); |
| DCHECK(range_for_next_append_ != ranges_.end()); |
| DCHECK((*range_for_next_append_)->BelongsToRange(buffers_start_timestamp)); |
| |
| // Split the range at |buffers_start_timestamp|, if necessary, then return |
| // early. |
| std::unique_ptr<SourceBufferRange> new_range = |
| (*range_for_next_append_)->SplitRange(buffers_start_timestamp); |
| if (!new_range) |
| return; |
| |
| range_for_next_append_ = |
| ranges_.insert(++range_for_next_append_, std::move(new_range)); |
| |
| // Update the selected range if the next buffer position was transferred |
| // to the newly inserted range. |
| if ((*range_for_next_append_)->HasNextBufferPosition()) |
| SetSelectedRange(range_for_next_append_->get()); |
| |
| --range_for_next_append_; |
| return; |
| } |
| |
| // Exclude the start from removal to avoid deleting the highest appended |
| // buffer in cases where the first buffer in |new_buffers| has same timestamp |
| // as the highest appended buffer (even in out-of-order DTS vs PTS sequence). |
| // Only do this when : |
| // A. Type is video. This may occur in cases of VP9 alt-ref frames or frames |
| // with incorrect timestamps. Removing a frame may break decode |
| // dependencies and there are no downsides to just keeping it (other than |
| // some throw-away decoder work). |
| // B. Type is text. TODO(chcunningham): Implement text splicing. See |
| // http://crbug.com/661408 |
| // C. Type is audio and overlapped duration is 0. We've encountered Vorbis |
| // streams containing zero-duration buffers (i.e. no real overlap). For |
| // non-zero duration removing overlapped frames is important to preserve |
| // A/V sync (see AudioClock). |
| const bool exclude_start = |
| highest_timestamp_in_append_sequence_ == |
| new_buffers.front()->timestamp() && |
| (GetType() == SourceBufferStreamType::kVideo || |
| GetType() == SourceBufferStreamType::kText || |
| last_appended_buffer_duration_ == base::TimeDelta()); |
| |
| // Finally do the deletion of overlap. |
| RemoveInternal(buffers_start_timestamp, buffers_end_timestamp, exclude_start, |
| deleted_buffers); |
| } |
| |
| // static |
| void SourceBufferStream::GetTimestampInterval(const BufferQueue& buffers, |
| base::TimeDelta* start, |
| base::TimeDelta* end) { |
| base::TimeDelta start_pts = buffers.front()->timestamp(); |
| base::TimeDelta end_pts = start_pts; |
| |
| for (const auto& buffer : buffers) { |
| base::TimeDelta timestamp = buffer->timestamp(); |
| start_pts = std::min(timestamp, start_pts); |
| base::TimeDelta duration = buffer->duration(); |
| |
| // FrameProcessor should protect against unknown buffer durations. |
| DCHECK_NE(duration, kNoTimestamp); |
| |
| if (duration > base::TimeDelta() && !buffer->is_duration_estimated()) { |
| timestamp += duration; |
| } else { |
| // TODO(chcunningham): Emit warning when 0ms durations are not expected. |
| // http://crbug.com/312836 |
| timestamp += base::Microseconds(1); |
| } |
| end_pts = std::max(timestamp, end_pts); |
| } |
| *start = start_pts; |
| *end = end_pts; |
| } |
| |
| bool SourceBufferStream::IsNextGopAdjacentToEndOfCurrentAppendSequence( |
| base::TimeDelta next_gop_timestamp) const { |
| base::TimeDelta upper_bound = highest_timestamp_in_append_sequence_ + |
| ComputeFudgeRoom(GetMaxInterbufferDistance()); |
| DVLOG(4) << __func__ << " " << GetStreamTypeName() |
| << " next_gop_timestamp=" << next_gop_timestamp.InMicroseconds() |
| << "us, highest_timestamp_in_append_sequence_=" |
| << highest_timestamp_in_append_sequence_.InMicroseconds() |
| << "us, upper_bound=" << upper_bound.InMicroseconds() << "us"; |
| return highest_timestamp_in_append_sequence_ < next_gop_timestamp && |
| next_gop_timestamp <= upper_bound; |
| } |
| |
| void SourceBufferStream::PruneTrackBuffer(const base::TimeDelta timestamp) { |
| DCHECK(timestamp != kNoTimestamp); |
| |
| // Scan forward until we find a buffer with timestamp at or beyond the limit. |
| // Then remove all those at and beyond that point. |
| size_t goal_size = 0; // The number of buffers we will keep in the result. |
| for (const auto& buf : track_buffer_) { |
| if (buf->timestamp() >= timestamp) |
| break; |
| goal_size++; |
| } |
| |
| while (track_buffer_.size() > goal_size) { |
| track_buffer_.pop_back(); |
| } |
| |
| DVLOG(3) << __func__ << " " << GetStreamTypeName() |
| << " Removed all buffers in track buffer sequence starting with the " |
| "first at timestamp >= " |
| << timestamp.InMicroseconds() |
| << "us. New track buffer size:" << track_buffer_.size(); |
| } |
| |
| void SourceBufferStream::MergeWithNextRangeIfNecessary( |
| const RangeList::iterator& range_with_new_buffers_itr) { |
| DCHECK(range_with_new_buffers_itr != ranges_.end()); |
| |
| SourceBufferRange* range_with_new_buffers = range_with_new_buffers_itr->get(); |
| RangeList::iterator next_range_itr = range_with_new_buffers_itr; |
| ++next_range_itr; |
| |
| if (next_range_itr == ranges_.end() || |
| !range_with_new_buffers->CanAppendRangeToEnd(**next_range_itr)) { |
| return; |
| } |
| |
| bool transfer_current_position = selected_range_ == next_range_itr->get(); |
| DVLOG(3) << __func__ << " " << GetStreamTypeName() << " merging " |
| << RangeToString(*range_with_new_buffers) << " into " |
| << RangeToString(**next_range_itr); |
| range_with_new_buffers->AppendRangeToEnd(**next_range_itr, |
| transfer_current_position); |
| // Update |selected_range_| pointer if |range| has become selected after |
| // merges. |
| if (transfer_current_position) |
| SetSelectedRange(range_with_new_buffers); |
| |
| if (next_range_itr == range_for_next_append_) |
| range_for_next_append_ = range_with_new_buffers_itr; |
| |
| DeleteAndRemoveRange(&next_range_itr); |
| } |
| |
| void SourceBufferStream::MergeAllAdjacentRanges() { |
| DVLOG(1) << __func__ << " " << GetStreamTypeName() |
| << ": Before: ranges_=" << RangesToString(ranges_); |
| |
| auto range_itr = ranges_.begin(); |
| |
| while (range_itr != ranges_.end()) { |
| const size_t old_ranges_size = ranges_.size(); |
| MergeWithNextRangeIfNecessary(range_itr); |
| |
| // Only proceed to the next range if the current range didn't merge with it. |
| if (old_ranges_size == ranges_.size()) |
| range_itr++; |
| } |
| |
| DVLOG(1) << __func__ << " " << GetStreamTypeName() |
| << ": After: ranges_=" << RangesToString(ranges_); |
| } |
| |
| void SourceBufferStream::Seek(base::TimeDelta timestamp) { |
| DCHECK(timestamp >= base::TimeDelta()); |
| DVLOG(1) << __func__ << " " << GetStreamTypeName() << " (" |
| << timestamp.InMicroseconds() << "us)"; |
| ResetSeekState(); |
| |
| seek_buffer_timestamp_ = timestamp; |
| seek_pending_ = true; |
| |
| if (ShouldSeekToStartOfBuffered(timestamp)) { |
| ranges_.front()->SeekToStart(); |
| SetSelectedRange(ranges_.front().get()); |
| seek_pending_ = false; |
| return; |
| } |
| |
| auto itr = ranges_.end(); |
| for (itr = ranges_.begin(); itr != ranges_.end(); ++itr) { |
| if ((*itr)->CanSeekTo(timestamp)) |
| break; |
| } |
| |
| if (itr == ranges_.end()) |
| return; |
| |
| if (!audio_configs_.empty()) { |
| // Adjust |timestamp| for an Opus stream backward up to the config's seek |
| // preroll, but not further than the range start time, and not at all if |
| // there is a config change in the middle of that preroll interval. If |
| // |timestamp| is already before the range start time, as can happen due to |
| // fudge room, do not adjust it. |
| const auto& config = audio_configs_[(*itr)->GetConfigIdAtTime(timestamp)]; |
| if (config.codec() == AudioCodec::kOpus && |
| timestamp > (*itr)->GetStartTimestamp()) { |
| base::TimeDelta preroll_timestamp = std::max( |
| timestamp - config.seek_preroll(), (*itr)->GetStartTimestamp()); |
| if ((*itr)->CanSeekTo(preroll_timestamp) && |
| (*itr)->SameConfigThruRange(preroll_timestamp, timestamp)) { |
| timestamp = preroll_timestamp; |
| } |
| } |
| } |
| |
| SeekAndSetSelectedRange(itr->get(), timestamp); |
| seek_pending_ = false; |
| } |
| |
| bool SourceBufferStream::IsSeekPending() const { |
| return seek_pending_ && !IsEndOfStreamReached(); |
| } |
| |
| // TODO(wolenetz): Disallow duration changes that truncate buffered media. See |
| // https://crbug.com/623729. |
| void SourceBufferStream::OnSetDuration(base::TimeDelta duration) { |
| DVLOG(1) << __func__ << " " << GetStreamTypeName() << " (" |
| << duration.InMicroseconds() << "us)"; |
| DCHECK(!end_of_stream_); |
| |
| if (ranges_.empty()) |
| return; |
| |
| base::TimeDelta start = duration; |
| base::TimeDelta end = ranges_.back()->GetBufferedEndTimestamp(); |
| |
| // Trim the end if it exceeds the new duration. |
| if (start < end) { |
| BufferQueue deleted_buffers; |
| RemoveInternal(start, end, false, &deleted_buffers); |
| |
| if (!deleted_buffers.empty()) { |
| // Truncation removed current position. Clear selected range. |
| SetSelectedRange(NULL); |
| } |
| } |
| } |
| |
| SourceBufferStreamStatus SourceBufferStream::GetNextBuffer( |
| scoped_refptr<StreamParserBuffer>* out_buffer) { |
| DVLOG(2) << __func__ << " " << GetStreamTypeName(); |
| if (!pending_buffer_.get()) { |
| const SourceBufferStreamStatus status = GetNextBufferInternal(out_buffer); |
| if (status != SourceBufferStreamStatus::kSuccess || |
| !SetPendingBuffer(out_buffer)) { |
| DVLOG(2) << __func__ << " " << GetStreamTypeName() |
| << ": no pending buffer, returning status " |
| << StatusToString(status); |
| return status; |
| } |
| } |
| |
| DCHECK(pending_buffer_->preroll_buffer().get()); |
| |
| const SourceBufferStreamStatus status = |
| HandleNextBufferWithPreroll(out_buffer); |
| DVLOG(2) << __func__ << " " << GetStreamTypeName() |
| << ": handled next buffer with preroll, returning status " |
| << StatusToString(status); |
| return status; |
| } |
| |
| SourceBufferStreamStatus SourceBufferStream::HandleNextBufferWithPreroll( |
| scoped_refptr<StreamParserBuffer>* out_buffer) { |
| // Any config change should have already been handled. |
| DCHECK_EQ(current_config_index_, pending_buffer_->GetConfigId()); |
| |
| // Check if the preroll buffer has already been handed out. |
| if (!pending_buffers_complete_) { |
| pending_buffers_complete_ = true; |
| *out_buffer = pending_buffer_->preroll_buffer(); |
| return SourceBufferStreamStatus::kSuccess; |
| } |
| |
| // Preroll complete, hand out the final buffer. |
| *out_buffer = std::move(pending_buffer_); |
| return SourceBufferStreamStatus::kSuccess; |
| } |
| |
| SourceBufferStreamStatus SourceBufferStream::GetNextBufferInternal( |
| scoped_refptr<StreamParserBuffer>* out_buffer) { |
| CHECK(!config_change_pending_); |
| |
| if (!track_buffer_.empty()) { |
| DCHECK(!selected_range_); |
| |
| if (track_buffer_.front()->GetConfigId() != current_config_index_) { |
| config_change_pending_ = true; |
| DVLOG(1) << "Config change (track buffer config ID does not match)."; |
| return SourceBufferStreamStatus::kConfigChange; |
| } |
| |
| DVLOG(3) << __func__ << " Next buffer coming from track_buffer_"; |
| *out_buffer = std::move(track_buffer_.front()); |
| track_buffer_.pop_front(); |
| WarnIfTrackBufferExhaustionSkipsForward(*out_buffer); |
| highest_output_buffer_timestamp_ = |
| std::max(highest_output_buffer_timestamp_, (*out_buffer)->timestamp()); |
| |
| // If the track buffer becomes empty, then try to set the selected range |
| // based on the timestamp of this buffer being returned. |
| if (track_buffer_.empty()) { |
| just_exhausted_track_buffer_ = true; |
| SetSelectedRangeIfNeeded(highest_output_buffer_timestamp_); |
| } |
| |
| return SourceBufferStreamStatus::kSuccess; |
| } |
| |
| DCHECK(track_buffer_.empty()); |
| if (!selected_range_ || !selected_range_->HasNextBuffer()) { |
| if (IsEndOfStreamReached()) { |
| return SourceBufferStreamStatus::kEndOfStream; |
| } |
| DVLOG(3) << __func__ << " " << GetStreamTypeName() |
| << ": returning kNeedBuffer " |
| << (selected_range_ ? "(selected range has no next buffer)" |
| : "(no selected range)"); |
| return SourceBufferStreamStatus::kNeedBuffer; |
| } |
| |
| if (selected_range_->GetNextConfigId() != current_config_index_) { |
| config_change_pending_ = true; |
| DVLOG(1) << "Config change (selected range config ID does not match)."; |
| return SourceBufferStreamStatus::kConfigChange; |
| } |
| |
| CHECK(selected_range_->GetNextBuffer(out_buffer)); |
| WarnIfTrackBufferExhaustionSkipsForward(*out_buffer); |
| highest_output_buffer_timestamp_ = |
| std::max(highest_output_buffer_timestamp_, (*out_buffer)->timestamp()); |
| return SourceBufferStreamStatus::kSuccess; |
| } |
| |
| void SourceBufferStream::WarnIfTrackBufferExhaustionSkipsForward( |
| scoped_refptr<StreamParserBuffer> next_buffer) { |
| if (!just_exhausted_track_buffer_) |
| return; |
| |
| just_exhausted_track_buffer_ = false; |
| DCHECK(next_buffer->is_key_frame()); |
| base::TimeDelta next_output_buffer_timestamp = next_buffer->timestamp(); |
| base::TimeDelta delta = |
| next_output_buffer_timestamp - highest_output_buffer_timestamp_; |
| if (delta > GetMaxInterbufferDistance()) { |
| LIMITED_MEDIA_LOG(DEBUG, media_log_, num_track_buffer_gap_warning_logs_, |
| kMaxTrackBufferGapWarningLogs) |
| << "Media append that overlapped current playback position may cause " |
| "time gap in playing " |
| << GetStreamTypeName() << " stream because the next keyframe is " |
| << delta.InMilliseconds() |
| << "ms beyond last overlapped frame. Media may appear temporarily " |
| "frozen."; |
| } |
| } |
| |
| base::TimeDelta SourceBufferStream::GetNextBufferTimestamp() { |
| if (!track_buffer_.empty()) |
| return track_buffer_.front()->timestamp(); |
| |
| if (!selected_range_) |
| return kNoTimestamp; |
| |
| DCHECK(selected_range_->HasNextBufferPosition()); |
| return selected_range_->GetNextTimestamp(); |
| } |
| |
| SourceBufferStream::RangeList::iterator |
| SourceBufferStream::FindExistingRangeFor(base::TimeDelta start_timestamp) { |
| for (auto itr = ranges_.begin(); itr != ranges_.end(); ++itr) { |
| if ((*itr)->BelongsToRange(start_timestamp)) |
| return itr; |
| } |
| return ranges_.end(); |
| } |
| |
| SourceBufferStream::RangeList::iterator SourceBufferStream::AddToRanges( |
| std::unique_ptr<SourceBufferRange> new_range) { |
| base::TimeDelta start_timestamp = new_range->GetStartTimestamp(); |
| auto itr = ranges_.end(); |
| for (itr = ranges_.begin(); itr != ranges_.end(); ++itr) { |
| if ((*itr)->GetStartTimestamp() > start_timestamp) |
| break; |
| } |
| return ranges_.insert(itr, std::move(new_range)); |
| } |
| |
| void SourceBufferStream::SeekAndSetSelectedRange( |
| SourceBufferRange* range, |
| base::TimeDelta seek_timestamp) { |
| if (range) |
| range->Seek(seek_timestamp); |
| SetSelectedRange(range); |
| } |
| |
| void SourceBufferStream::SetSelectedRange(SourceBufferRange* range) { |
| DVLOG(1) << __func__ << " " << GetStreamTypeName() << ": " << selected_range_ |
| << " " << (selected_range_ ? RangeToString(*selected_range_) : "") |
| << " -> " << range << " " << (range ? RangeToString(*range) : ""); |
| if (selected_range_) |
| selected_range_->ResetNextBufferPosition(); |
| DCHECK(!range || range->HasNextBufferPosition()); |
| selected_range_ = range; |
| } |
| |
| Ranges<base::TimeDelta> SourceBufferStream::GetBufferedTime() const { |
| Ranges<base::TimeDelta> ranges; |
| for (auto itr = ranges_.begin(); itr != ranges_.end(); ++itr) { |
| ranges.Add((*itr)->GetStartTimestamp(), (*itr)->GetBufferedEndTimestamp()); |
| } |
| return ranges; |
| } |
| |
| base::TimeDelta SourceBufferStream::GetHighestPresentationTimestamp() const { |
| if (ranges_.empty()) |
| return base::TimeDelta(); |
| |
| return ranges_.back()->GetEndTimestamp(); |
| } |
| |
| base::TimeDelta SourceBufferStream::GetBufferedDuration() const { |
| if (ranges_.empty()) |
| return base::TimeDelta(); |
| |
| return ranges_.back()->GetBufferedEndTimestamp(); |
| } |
| |
| size_t SourceBufferStream::GetBufferedSize() const { |
| size_t ranges_size = 0; |
| for (const auto& range_ptr : ranges_) |
| ranges_size += range_ptr->size_in_bytes(); |
| return ranges_size; |
| } |
| |
| void SourceBufferStream::MarkEndOfStream() { |
| DCHECK(!end_of_stream_); |
| end_of_stream_ = true; |
| } |
| |
| void SourceBufferStream::UnmarkEndOfStream() { |
| DCHECK(end_of_stream_); |
| end_of_stream_ = false; |
| } |
| |
| bool SourceBufferStream::IsEndOfStreamReached() const { |
| if (!end_of_stream_ || !track_buffer_.empty()) |
| return false; |
| |
| if (ranges_.empty()) |
| return true; |
| |
| if (seek_pending_) { |
| base::TimeDelta last_range_end_time = |
| ranges_.back()->GetBufferedEndTimestamp(); |
| return seek_buffer_timestamp_ >= last_range_end_time; |
| } |
| |
| if (!selected_range_) |
| return true; |
| |
| return selected_range_ == ranges_.back().get(); |
| } |
| |
| const AudioDecoderConfig& SourceBufferStream::GetCurrentAudioDecoderConfig() { |
| if (config_change_pending_) |
| CompleteConfigChange(); |
| // Trying to track down crash. http://crbug.com/715761 |
| CHECK(current_config_index_ >= 0 && |
| static_cast<size_t>(current_config_index_) < audio_configs_.size()); |
| return audio_configs_[current_config_index_]; |
| } |
| |
| const VideoDecoderConfig& SourceBufferStream::GetCurrentVideoDecoderConfig() { |
| if (config_change_pending_) |
| CompleteConfigChange(); |
| // Trying to track down crash. http://crbug.com/715761 |
| CHECK(current_config_index_ >= 0 && |
| static_cast<size_t>(current_config_index_) < video_configs_.size()); |
| return video_configs_[current_config_index_]; |
| } |
| |
| const TextTrackConfig& SourceBufferStream::GetCurrentTextTrackConfig() { |
| return text_track_config_; |
| } |
| |
| base::TimeDelta SourceBufferStream::GetMaxInterbufferDistance() const { |
| return max_interbuffer_distance_; |
| } |
| |
| bool SourceBufferStream::UpdateAudioConfig(const AudioDecoderConfig& config, |
| bool allow_codec_change) { |
| DCHECK(!audio_configs_.empty()); |
| DCHECK(video_configs_.empty()); |
| DVLOG(3) << "UpdateAudioConfig."; |
| |
| if (!allow_codec_change && |
| audio_configs_[append_config_index_].codec() != config.codec()) { |
| // TODO(wolenetz): When we relax addSourceBuffer() and changeType() codec |
| // strictness, codec changes should be allowed even without changing the |
| // bytestream. |
| // TODO(wolenetz): Remove "experimental" from this error message when |
| // changeType() ships without needing experimental blink flag. |
| MEDIA_LOG(ERROR, media_log_) << "Audio codec changes not allowed unless " |
| "using experimental changeType()."; |
| return false; |
| } |
| |
| // Check to see if the new config matches an existing one. |
| for (size_t i = 0; i < audio_configs_.size(); ++i) { |
| if (config.Matches(audio_configs_[i])) { |
| append_config_index_ = i; |
| return true; |
| } |
| } |
| |
| // No matches found so let's add this one to the list. |
| append_config_index_ = audio_configs_.size(); |
| DVLOG(2) << "New audio config - index: " << append_config_index_; |
| audio_configs_.resize(audio_configs_.size() + 1); |
| audio_configs_[append_config_index_] = config; |
| return true; |
| } |
| |
| bool SourceBufferStream::UpdateVideoConfig(const VideoDecoderConfig& config, |
| bool allow_codec_change) { |
| DCHECK(!video_configs_.empty()); |
| DCHECK(audio_configs_.empty()); |
| DVLOG(3) << "UpdateVideoConfig."; |
| |
| if (!allow_codec_change && |
| video_configs_[append_config_index_].codec() != config.codec()) { |
| // TODO(wolenetz): When we relax addSourceBuffer() and changeType() codec |
| // strictness, codec changes should be allowed even without changing the |
| // bytestream. |
| // TODO(wolenetz): Remove "experimental" from this error message when |
| // changeType() ships without needing experimental blink flag. |
| MEDIA_LOG(ERROR, media_log_) << "Video codec changes not allowed unless " |
| "using experimental changeType()"; |
| return false; |
| } |
| |
| // Check to see if the new config matches an existing one. |
| for (size_t i = 0; i < video_configs_.size(); ++i) { |
| if (config.Matches(video_configs_[i])) { |
| append_config_index_ = i; |
| return true; |
| } |
| } |
| |
| // No matches found so let's add this one to the list. |
| append_config_index_ = video_configs_.size(); |
| DVLOG(2) << "New video config - index: " << append_config_index_; |
| video_configs_.resize(video_configs_.size() + 1); |
| video_configs_[append_config_index_] = config; |
| |
| #if defined(STARBOARD) |
| // Dynamically increase |memory_limit_| when video resolution goes up. |
| memory_limit_ = std::max( |
| memory_limit_, |
| GetDemuxerStreamVideoMemoryLimit(Demuxer::DemuxerTypes::kChunkDemuxer, |
| &config, mime_type_)); |
| #endif // defined(STARBOARD) |
| return true; |
| } |
| |
| void SourceBufferStream::CompleteConfigChange() { |
| config_change_pending_ = false; |
| |
| if (!track_buffer_.empty()) { |
| current_config_index_ = track_buffer_.front()->GetConfigId(); |
| return; |
| } |
| |
| if (selected_range_ && selected_range_->HasNextBuffer()) |
| current_config_index_ = selected_range_->GetNextConfigId(); |
| } |
| |
| void SourceBufferStream::SetSelectedRangeIfNeeded( |
| const base::TimeDelta timestamp) { |
| DVLOG(2) << __func__ << " " << GetStreamTypeName() << "(" |
| << timestamp.InMicroseconds() << "us)"; |
| |
| if (selected_range_) { |
| DCHECK(track_buffer_.empty()); |
| return; |
| } |
| |
| if (!track_buffer_.empty()) { |
| DCHECK(!selected_range_); |
| return; |
| } |
| |
| base::TimeDelta start_timestamp = timestamp; |
| |
| // If the next buffer timestamp is not known then use a timestamp just after |
| // the timestamp on the last buffer returned by GetNextBuffer(). |
| if (start_timestamp == kNoTimestamp) { |
| if (highest_output_buffer_timestamp_ == kNoTimestamp) { |
| DVLOG(2) << __func__ << " " << GetStreamTypeName() |
| << " no previous output timestamp"; |
| return; |
| } |
| |
| start_timestamp = highest_output_buffer_timestamp_ + base::Microseconds(1); |
| } |
| |
| base::TimeDelta seek_timestamp = |
| FindNewSelectedRangeSeekTimestamp(start_timestamp); |
| |
| // If we don't have buffered data to seek to, then return. |
| if (seek_timestamp == kNoTimestamp) { |
| DVLOG(2) << __func__ << " " << GetStreamTypeName() |
| << " couldn't find new selected range seek timestamp"; |
| return; |
| } |
| |
| DCHECK(track_buffer_.empty()); |
| SeekAndSetSelectedRange(FindExistingRangeFor(seek_timestamp)->get(), |
| seek_timestamp); |
| } |
| |
| base::TimeDelta SourceBufferStream::FindNewSelectedRangeSeekTimestamp( |
| const base::TimeDelta start_timestamp) { |
| DCHECK(start_timestamp != kNoTimestamp); |
| DCHECK(start_timestamp >= base::TimeDelta()); |
| |
| auto itr = ranges_.begin(); |
| |
| // When checking a range to see if it has or begins soon enough after |
| // |start_timestamp|, use the fudge room to determine "soon enough". |
| base::TimeDelta start_timestamp_plus_fudge = |
| start_timestamp + ComputeFudgeRoom(GetMaxInterbufferDistance()); |
| |
| // Multiple ranges could be within the fudge room, because the fudge room is |
| // dynamic based on max inter-buffer distance seen so far. Optimistically |
| // check the earliest ones first. |
| for (; itr != ranges_.end(); ++itr) { |
| base::TimeDelta range_start = (*itr)->GetStartTimestamp(); |
| if (range_start >= start_timestamp_plus_fudge) |
| break; |
| if ((*itr)->GetEndTimestamp() < start_timestamp) |
| continue; |
| base::TimeDelta search_timestamp = start_timestamp; |
| if (start_timestamp < range_start && |
| start_timestamp_plus_fudge >= range_start) { |
| search_timestamp = range_start; |
| } |
| base::TimeDelta keyframe_timestamp = |
| (*itr)->NextKeyframeTimestamp(search_timestamp); |
| if (keyframe_timestamp != kNoTimestamp) |
| return keyframe_timestamp; |
| } |
| |
| DVLOG(2) << __func__ << " " << GetStreamTypeName() |
| << " no buffered data for pts=" << start_timestamp.InMicroseconds() |
| << "us"; |
| return kNoTimestamp; |
| } |
| |
| base::TimeDelta SourceBufferStream::FindKeyframeAfterTimestamp( |
| const base::TimeDelta timestamp) { |
| DCHECK(timestamp != kNoTimestamp); |
| |
| auto itr = FindExistingRangeFor(timestamp); |
| |
| if (itr == ranges_.end()) |
| return kNoTimestamp; |
| |
| // First check for a keyframe timestamp >= |timestamp| |
| // in the current range. |
| return (*itr)->NextKeyframeTimestamp(timestamp); |
| } |
| |
| std::string SourceBufferStream::GetStreamTypeName() const { |
| switch (GetType()) { |
| case SourceBufferStreamType::kAudio: |
| return "AUDIO"; |
| case SourceBufferStreamType::kVideo: |
| return "VIDEO"; |
| case SourceBufferStreamType::kText: |
| return "TEXT"; |
| } |
| NOTREACHED(); |
| return ""; |
| } |
| |
| SourceBufferStreamType SourceBufferStream::GetType() const { |
| if (!audio_configs_.empty()) |
| return SourceBufferStreamType::kAudio; |
| if (!video_configs_.empty()) |
| return SourceBufferStreamType::kVideo; |
| DCHECK_NE(text_track_config_.kind(), kTextNone); |
| return SourceBufferStreamType::kText; |
| } |
| |
| void SourceBufferStream::DeleteAndRemoveRange(RangeList::iterator* itr) { |
| DVLOG(1) << __func__; |
| |
| DCHECK(*itr != ranges_.end()); |
| if ((*itr)->get() == selected_range_) { |
| DVLOG(1) << __func__ << " deleting selected range."; |
| SetSelectedRange(NULL); |
| } |
| |
| if (*itr == range_for_next_append_) { |
| DVLOG(1) << __func__ << " deleting range_for_next_append_."; |
| range_for_next_append_ = ranges_.end(); |
| ResetLastAppendedState(); |
| } |
| |
| *itr = ranges_.erase(*itr); |
| } |
| |
| bool SourceBufferStream::SetPendingBuffer( |
| scoped_refptr<StreamParserBuffer>* out_buffer) { |
| DCHECK(out_buffer->get()); |
| DCHECK(!pending_buffer_.get()); |
| |
| const bool have_preroll_buffer = !!(*out_buffer)->preroll_buffer().get(); |
| |
| if (!have_preroll_buffer) |
| return false; |
| |
| pending_buffer_.swap(*out_buffer); |
| pending_buffers_complete_ = false; |
| return true; |
| } |
| |
| #if defined(STARBOARD) |
| |
| base::TimeDelta SourceBufferStream::GetBufferedDurationForGarbageCollection() |
| const { |
| base::TimeDelta duration; |
| for (auto&& range : ranges_) { |
| duration += range->GetEndTimestamp() - range->GetStartTimestamp(); |
| } |
| return duration; |
| } |
| |
| #endif // defined (STARBOARD) |
| |
| } // namespace media |