| // 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 "cobalt/media/filters/chunk_demuxer.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <utility> |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/debug/trace_event.h" |
| #include "base/location.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/metrics/histogram.h" |
| #include "base/string_number_conversions.h" |
| #include "cobalt/media/base/audio_decoder_config.h" |
| #include "cobalt/media/base/bind_to_current_loop.h" |
| #include "cobalt/media/base/media_tracks.h" |
| #include "cobalt/media/base/mime_util.h" |
| #include "cobalt/media/base/stream_parser_buffer.h" |
| #include "cobalt/media/base/timestamp_constants.h" |
| #include "cobalt/media/base/video_codecs.h" |
| #include "cobalt/media/base/video_decoder_config.h" |
| #include "cobalt/media/filters/frame_processor.h" |
| #include "cobalt/media/filters/stream_parser_factory.h" |
| #include "starboard/configuration.h" |
| |
| using base::TimeDelta; |
| |
| namespace cobalt { |
| namespace media { |
| |
| ChunkDemuxerStream::ChunkDemuxerStream(Type type, bool splice_frames_enabled, |
| MediaTrack::Id media_track_id) |
| : type_(type), |
| liveness_(DemuxerStream::LIVENESS_UNKNOWN), |
| media_track_id_(media_track_id), |
| state_(UNINITIALIZED), |
| splice_frames_enabled_(splice_frames_enabled), |
| partial_append_window_trimming_enabled_(false), |
| is_enabled_(true) {} |
| |
| void ChunkDemuxerStream::StartReturningData() { |
| DVLOG(1) << "ChunkDemuxerStream::StartReturningData()"; |
| base::AutoLock auto_lock(lock_); |
| DCHECK(read_cb_.is_null()); |
| ChangeState_Locked(RETURNING_DATA_FOR_READS); |
| } |
| |
| void ChunkDemuxerStream::AbortReads() { |
| DVLOG(1) << "ChunkDemuxerStream::AbortReads()"; |
| base::AutoLock auto_lock(lock_); |
| ChangeState_Locked(RETURNING_ABORT_FOR_READS); |
| if (!read_cb_.is_null()) base::ResetAndReturn(&read_cb_).Run(kAborted, NULL); |
| } |
| |
| void ChunkDemuxerStream::CompletePendingReadIfPossible() { |
| base::AutoLock auto_lock(lock_); |
| if (read_cb_.is_null()) return; |
| |
| CompletePendingReadIfPossible_Locked(); |
| } |
| |
| void ChunkDemuxerStream::Shutdown() { |
| DVLOG(1) << "ChunkDemuxerStream::Shutdown()"; |
| base::AutoLock auto_lock(lock_); |
| ChangeState_Locked(SHUTDOWN); |
| |
| // Pass an end of stream buffer to the pending callback to signal that no more |
| // data will be sent. |
| if (!read_cb_.is_null()) { |
| base::ResetAndReturn(&read_cb_) |
| .Run(DemuxerStream::kOk, StreamParserBuffer::CreateEOSBuffer()); |
| } |
| } |
| |
| bool ChunkDemuxerStream::IsSeekWaitingForData() const { |
| base::AutoLock auto_lock(lock_); |
| |
| // This method should not be called for text tracks. See the note in |
| // SourceBufferState::IsSeekWaitingForData(). |
| DCHECK_NE(type_, DemuxerStream::TEXT); |
| |
| return stream_->IsSeekPending(); |
| } |
| |
| base::TimeDelta ChunkDemuxerStream::GetSeekKeyframeTimestamp() const { |
| base::AutoLock auto_lock(lock_); |
| return stream_->GetSeekKeyframeTimestamp(); |
| } |
| |
| void ChunkDemuxerStream::Seek(TimeDelta time) { |
| DVLOG(1) << "ChunkDemuxerStream::Seek(" << time.InSecondsF() << ")"; |
| base::AutoLock auto_lock(lock_); |
| DCHECK(read_cb_.is_null()); |
| DCHECK(state_ == UNINITIALIZED || state_ == RETURNING_ABORT_FOR_READS) |
| << state_; |
| |
| stream_->Seek(time); |
| } |
| |
| bool ChunkDemuxerStream::Append(const StreamParser::BufferQueue& buffers) { |
| if (buffers.empty()) return false; |
| |
| base::AutoLock auto_lock(lock_); |
| DCHECK_NE(state_, SHUTDOWN); |
| if (!stream_->Append(buffers)) { |
| DVLOG(1) << "ChunkDemuxerStream::Append() : stream append failed"; |
| return false; |
| } |
| |
| if (!read_cb_.is_null()) CompletePendingReadIfPossible_Locked(); |
| |
| return true; |
| } |
| |
| void ChunkDemuxerStream::Remove(TimeDelta start, TimeDelta end, |
| TimeDelta duration) { |
| base::AutoLock auto_lock(lock_); |
| stream_->Remove(start, end, duration); |
| } |
| |
| bool ChunkDemuxerStream::EvictCodedFrames(DecodeTimestamp media_time, |
| size_t newDataSize) { |
| base::AutoLock auto_lock(lock_); |
| return stream_->GarbageCollectIfNeeded(media_time, newDataSize); |
| } |
| |
| void ChunkDemuxerStream::OnSetDuration(TimeDelta duration) { |
| base::AutoLock auto_lock(lock_); |
| stream_->OnSetDuration(duration); |
| } |
| |
| Ranges<TimeDelta> ChunkDemuxerStream::GetBufferedRanges( |
| TimeDelta duration) const { |
| base::AutoLock auto_lock(lock_); |
| |
| if (type_ == TEXT) { |
| // Since text tracks are discontinuous and the lack of cues should not block |
| // playback, report the buffered range for text tracks as [0, |duration|) so |
| // that intesections with audio & video tracks are computed correctly when |
| // no cues are present. |
| Ranges<TimeDelta> text_range; |
| text_range.Add(TimeDelta(), duration); |
| return text_range; |
| } |
| |
| Ranges<TimeDelta> range = stream_->GetBufferedTime(); |
| |
| if (range.size() == 0u) return range; |
| |
| // Clamp the end of the stream's buffered ranges to fit within the duration. |
| // This can be done by intersecting the stream's range with the valid time |
| // range. |
| Ranges<TimeDelta> valid_time_range; |
| valid_time_range.Add(range.start(0), duration); |
| return range.IntersectionWith(valid_time_range); |
| } |
| |
| TimeDelta ChunkDemuxerStream::GetHighestPresentationTimestamp() const { |
| return stream_->GetHighestPresentationTimestamp(); |
| } |
| |
| TimeDelta ChunkDemuxerStream::GetBufferedDuration() const { |
| return stream_->GetBufferedDuration(); |
| } |
| |
| size_t ChunkDemuxerStream::GetBufferedSize() const { |
| return stream_->GetBufferedSize(); |
| } |
| |
| void ChunkDemuxerStream::OnStartOfCodedFrameGroup( |
| DecodeTimestamp start_timestamp) { |
| DVLOG(2) << "ChunkDemuxerStream::OnStartOfCodedFrameGroup(" |
| << start_timestamp.InSecondsF() << ")"; |
| base::AutoLock auto_lock(lock_); |
| stream_->OnStartOfCodedFrameGroup(start_timestamp); |
| } |
| |
| bool ChunkDemuxerStream::UpdateAudioConfig( |
| const AudioDecoderConfig& config, |
| const scoped_refptr<MediaLog>& media_log) { |
| DCHECK(config.IsValidConfig()); |
| DCHECK_EQ(type_, AUDIO); |
| base::AutoLock auto_lock(lock_); |
| if (!stream_) { |
| DCHECK_EQ(state_, UNINITIALIZED); |
| |
| // On platforms which support splice frames, enable splice frames and |
| // partial append window support for most codecs (notably: not opus). |
| const bool codec_supported = config.codec() == kCodecMP3 || |
| config.codec() == kCodecAAC || |
| config.codec() == kCodecVorbis; |
| splice_frames_enabled_ = splice_frames_enabled_ && codec_supported; |
| partial_append_window_trimming_enabled_ = |
| splice_frames_enabled_ && codec_supported; |
| |
| stream_.reset( |
| new SourceBufferStream(config, media_log, splice_frames_enabled_)); |
| return true; |
| } |
| |
| return stream_->UpdateAudioConfig(config); |
| } |
| |
| bool ChunkDemuxerStream::UpdateVideoConfig( |
| const VideoDecoderConfig& config, |
| const scoped_refptr<MediaLog>& media_log) { |
| DCHECK(config.IsValidConfig()); |
| DCHECK_EQ(type_, VIDEO); |
| base::AutoLock auto_lock(lock_); |
| |
| if (!stream_) { |
| DCHECK_EQ(state_, UNINITIALIZED); |
| stream_.reset( |
| new SourceBufferStream(config, media_log, splice_frames_enabled_)); |
| return true; |
| } |
| |
| return stream_->UpdateVideoConfig(config); |
| } |
| |
| void ChunkDemuxerStream::UpdateTextConfig( |
| const TextTrackConfig& config, const scoped_refptr<MediaLog>& media_log) { |
| DCHECK_EQ(type_, TEXT); |
| base::AutoLock auto_lock(lock_); |
| DCHECK(!stream_); |
| DCHECK_EQ(state_, UNINITIALIZED); |
| stream_.reset( |
| new SourceBufferStream(config, media_log, splice_frames_enabled_)); |
| } |
| |
| void ChunkDemuxerStream::MarkEndOfStream() { |
| base::AutoLock auto_lock(lock_); |
| stream_->MarkEndOfStream(); |
| } |
| |
| void ChunkDemuxerStream::UnmarkEndOfStream() { |
| base::AutoLock auto_lock(lock_); |
| stream_->UnmarkEndOfStream(); |
| } |
| |
| // DemuxerStream methods. |
| void ChunkDemuxerStream::Read(const ReadCB& read_cb) { |
| base::AutoLock auto_lock(lock_); |
| DCHECK_NE(state_, UNINITIALIZED); |
| DCHECK(read_cb_.is_null()); |
| |
| read_cb_ = BindToCurrentLoop(read_cb); |
| |
| if (!is_enabled_) { |
| DVLOG(1) << "Read from disabled stream, returning EOS"; |
| base::ResetAndReturn(&read_cb_) |
| .Run(kOk, StreamParserBuffer::CreateEOSBuffer()); |
| return; |
| } |
| |
| CompletePendingReadIfPossible_Locked(); |
| } |
| |
| DemuxerStream::Type ChunkDemuxerStream::type() const { return type_; } |
| |
| DemuxerStream::Liveness ChunkDemuxerStream::liveness() const { |
| base::AutoLock auto_lock(lock_); |
| return liveness_; |
| } |
| |
| AudioDecoderConfig ChunkDemuxerStream::audio_decoder_config() { |
| CHECK_EQ(type_, AUDIO); |
| base::AutoLock auto_lock(lock_); |
| return stream_->GetCurrentAudioDecoderConfig(); |
| } |
| |
| VideoDecoderConfig ChunkDemuxerStream::video_decoder_config() { |
| CHECK_EQ(type_, VIDEO); |
| base::AutoLock auto_lock(lock_); |
| return stream_->GetCurrentVideoDecoderConfig(); |
| } |
| |
| bool ChunkDemuxerStream::SupportsConfigChanges() { return true; } |
| |
| VideoRotation ChunkDemuxerStream::video_rotation() { return VIDEO_ROTATION_0; } |
| |
| bool ChunkDemuxerStream::enabled() const { |
| base::AutoLock auto_lock(lock_); |
| return is_enabled_; |
| } |
| |
| void ChunkDemuxerStream::set_enabled(bool enabled, base::TimeDelta timestamp) { |
| base::AutoLock auto_lock(lock_); |
| |
| if (enabled == is_enabled_) return; |
| |
| is_enabled_ = enabled; |
| if (enabled) { |
| DCHECK(stream_); |
| stream_->Seek(timestamp); |
| } else if (!read_cb_.is_null()) { |
| DVLOG(1) << "Read from disabled stream, returning EOS"; |
| base::ResetAndReturn(&read_cb_) |
| .Run(kOk, StreamParserBuffer::CreateEOSBuffer()); |
| } |
| if (!stream_status_change_cb_.is_null()) |
| stream_status_change_cb_.Run(is_enabled_, timestamp); |
| } |
| |
| void ChunkDemuxerStream::SetStreamStatusChangeCB( |
| const StreamStatusChangeCB& cb) { |
| DCHECK(!cb.is_null()); |
| stream_status_change_cb_ = BindToCurrentLoop(cb); |
| } |
| |
| TextTrackConfig ChunkDemuxerStream::text_track_config() { |
| CHECK_EQ(type_, TEXT); |
| base::AutoLock auto_lock(lock_); |
| return stream_->GetCurrentTextTrackConfig(); |
| } |
| |
| void ChunkDemuxerStream::SetStreamMemoryLimit(size_t memory_limit) { |
| stream_->set_memory_limit(memory_limit); |
| } |
| |
| void ChunkDemuxerStream::SetLiveness(Liveness liveness) { |
| base::AutoLock auto_lock(lock_); |
| liveness_ = liveness; |
| } |
| |
| void ChunkDemuxerStream::ChangeState_Locked(State state) { |
| lock_.AssertAcquired(); |
| DVLOG(1) << "ChunkDemuxerStream::ChangeState_Locked() : " |
| << "type " << type_ << " - " << state_ << " -> " << state; |
| state_ = state; |
| } |
| |
| ChunkDemuxerStream::~ChunkDemuxerStream() {} |
| |
| void ChunkDemuxerStream::CompletePendingReadIfPossible_Locked() { |
| lock_.AssertAcquired(); |
| DCHECK(!read_cb_.is_null()); |
| |
| DemuxerStream::Status status = DemuxerStream::kAborted; |
| scoped_refptr<StreamParserBuffer> buffer; |
| |
| switch (state_) { |
| case UNINITIALIZED: |
| NOTREACHED(); |
| return; |
| case RETURNING_DATA_FOR_READS: |
| switch (stream_->GetNextBuffer(&buffer)) { |
| case SourceBufferStream::kSuccess: |
| status = DemuxerStream::kOk; |
| DVLOG(2) << __func__ << ": returning kOk, type " << type_ << ", dts " |
| << buffer->GetDecodeTimestamp().InSecondsF() << ", pts " |
| << buffer->timestamp().InSecondsF() << ", dur " |
| << buffer->duration().InSecondsF() << ", key " |
| << buffer->is_key_frame(); |
| break; |
| case SourceBufferStream::kNeedBuffer: |
| // Return early without calling |read_cb_| since we don't have |
| // any data to return yet. |
| DVLOG(2) << __func__ << ": returning kNeedBuffer, type " << type_; |
| return; |
| case SourceBufferStream::kEndOfStream: |
| status = DemuxerStream::kOk; |
| buffer = StreamParserBuffer::CreateEOSBuffer(); |
| DVLOG(2) << __func__ << ": returning kOk with EOS buffer, type " |
| << type_; |
| break; |
| case SourceBufferStream::kConfigChange: |
| status = kConfigChanged; |
| buffer = NULL; |
| DVLOG(2) << __func__ << ": returning kConfigChange, type " << type_; |
| break; |
| } |
| break; |
| case RETURNING_ABORT_FOR_READS: |
| // Null buffers should be returned in this state since we are waiting |
| // for a seek. Any buffers in the SourceBuffer should NOT be returned |
| // because they are associated with the seek. |
| status = DemuxerStream::kAborted; |
| buffer = NULL; |
| DVLOG(2) << __func__ << ": returning kAborted, type " << type_; |
| break; |
| case SHUTDOWN: |
| status = DemuxerStream::kOk; |
| buffer = StreamParserBuffer::CreateEOSBuffer(); |
| DVLOG(2) << __func__ << ": returning kOk with EOS buffer, type " << type_; |
| break; |
| } |
| |
| base::ResetAndReturn(&read_cb_).Run(status, buffer); |
| } |
| |
| ChunkDemuxer::ChunkDemuxer( |
| DecoderBuffer::Allocator* buffer_allocator, const base::Closure& open_cb, |
| const EncryptedMediaInitDataCB& encrypted_media_init_data_cb, |
| const scoped_refptr<MediaLog>& media_log, bool splice_frames_enabled) |
| : buffer_allocator_(buffer_allocator), |
| state_(WAITING_FOR_INIT), |
| cancel_next_seek_(false), |
| host_(NULL), |
| open_cb_(open_cb), |
| encrypted_media_init_data_cb_(encrypted_media_init_data_cb), |
| enable_text_(false), |
| media_log_(media_log), |
| duration_(kNoTimestamp), |
| user_specified_duration_(-1), |
| liveness_(DemuxerStream::LIVENESS_UNKNOWN), |
| splice_frames_enabled_(splice_frames_enabled), |
| detected_audio_track_count_(0), |
| detected_video_track_count_(0), |
| detected_text_track_count_(0) { |
| DCHECK(buffer_allocator_); |
| DCHECK(!open_cb_.is_null()); |
| DCHECK(!encrypted_media_init_data_cb_.is_null()); |
| } |
| |
| std::string ChunkDemuxer::GetDisplayName() const { return "ChunkDemuxer"; } |
| |
| void ChunkDemuxer::Initialize(DemuxerHost* host, |
| const PipelineStatusCB& init_cb, |
| bool enable_text_tracks) { |
| TRACE_EVENT0("cobalt::media", "ChunkDemuxer::Initialize"); |
| DLOG(INFO) << "This is an ASYNC MEDIA SOURCE playback."; |
| DVLOG(1) << "Init()"; |
| |
| base::AutoLock auto_lock(lock_); |
| if (state_ == SHUTDOWN) { |
| // Init cb must only be run after this method returns, so post. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(init_cb, DEMUXER_ERROR_COULD_NOT_OPEN)); |
| return; |
| } |
| |
| DCHECK_EQ(state_, WAITING_FOR_INIT); |
| host_ = host; |
| // Do not post init_cb once this function returns because if there is an |
| // error after initialization, the error might be reported before init_cb |
| // has a chance to run. This is because ChunkDemuxer::ReportError_Locked |
| // directly calls DemuxerHost::OnDemuxerError: crbug.com/633016. |
| init_cb_ = init_cb; |
| enable_text_ = enable_text_tracks; |
| |
| ChangeState_Locked(INITIALIZING); |
| |
| base::ResetAndReturn(&open_cb_).Run(); |
| } |
| |
| void ChunkDemuxer::Stop() { |
| DVLOG(1) << "Stop()"; |
| Shutdown(); |
| } |
| |
| void ChunkDemuxer::Seek(TimeDelta time, const PipelineStatusCB& cb) { |
| DVLOG(1) << "Seek(" << time.InSecondsF() << ")"; |
| DCHECK(time >= TimeDelta()); |
| |
| base::AutoLock auto_lock(lock_); |
| DCHECK(seek_cb_.is_null()); |
| |
| seek_cb_ = BindToCurrentLoop(cb); |
| if (state_ != INITIALIZED && state_ != ENDED) { |
| base::ResetAndReturn(&seek_cb_).Run(PIPELINE_ERROR_INVALID_STATE); |
| return; |
| } |
| |
| if (cancel_next_seek_) { |
| cancel_next_seek_ = false; |
| base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); |
| return; |
| } |
| |
| SeekAllSources(time); |
| |
| #if SB_HAS_QUIRK(SEEK_TO_KEYFRAME) |
| const bool kReturningDataUntilSeekFinished = true; |
| #else // SB_HAS_QUIRK(SEEK_TO_KEYFRAME) |
| const bool kReturningDataUntilSeekFinished = false; |
| #endif // SB_HAS_QUIRK(SEEK_TO_KEYFRAME) |
| |
| // Usually all encoded audio frames are key frames while not all encoded video |
| // frames are key frames. |
| // |
| // After a seek to |time|, the audio ChunkDemuxerStream will return encoded |
| // audio frames right before |time|. The audio decoder can decode them |
| // without knowledge of prior encoded audio frames. However, the video |
| // ChunkDemuxerStream has to return encoded video frames from the key frame |
| // whose timestamp is at or before |time|. Depending on the structure of the |
| // video stream, the gap between the timestamp of the key frame and |time| can |
| // be several seconds or more. So there is a difference between the |
| // timestamps of the first audio frame and the first video frame returned |
| // after a seek. Most modern platforms can play the audio data as is and |
| // quickly decode the video frames whose timestamp is earlier than the first |
| // audio frame without displaying them. This allows the video playback to |
| // "catch up" with the audio playback. |
| // |
| // SB_HAS_QUIRK(SEEK_TO_KEYFRAME) is enabled on platforms that don't support |
| // such "catch up" behaviour, where the audio ChunkDemuxerStream should start |
| // to return audio frame whose timestamp is earlier than the video key frame, |
| // instead of |time|. So StartReturningData() should be delayed until all |
| // ChunkDemuxerStreams are seeked. |
| if (!kReturningDataUntilSeekFinished) { |
| StartReturningData(); |
| } |
| |
| if (IsSeekWaitingForData_Locked()) { |
| DVLOG(1) << "Seek() : waiting for more data to arrive."; |
| return; |
| } |
| |
| if (kReturningDataUntilSeekFinished) { |
| AdjustSeekOnAudioSource(); |
| StartReturningData(); |
| } |
| |
| base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); |
| } |
| |
| // Demuxer implementation. |
| base::Time ChunkDemuxer::GetTimelineOffset() const { return timeline_offset_; } |
| |
| DemuxerStream* ChunkDemuxer::GetStream(DemuxerStream::Type type) { |
| DCHECK_NE(type, DemuxerStream::TEXT); |
| base::AutoLock auto_lock(lock_); |
| |
| // TODO: For now return only the first enabled audio/video stream, since this |
| // GetStream method is part of the implementation of the DemuxerStreamProvider |
| // interface that is used in many places and can't be changed easily. It will |
| // be fixed later, when we add support for multiple streams/tracks in |
| // DemuxerStreamProvider, tracked by crbug.com/646669 |
| if (type == DemuxerStream::AUDIO) { |
| for (size_t i = 0; i < audio_streams_.size(); ++i) { |
| if (audio_streams_[i]->enabled()) return audio_streams_[i]; |
| } |
| } |
| |
| if (type == DemuxerStream::VIDEO) { |
| for (size_t i = 0; i < video_streams_.size(); ++i) { |
| if (video_streams_[i]->enabled()) return video_streams_[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| TimeDelta ChunkDemuxer::GetStartTime() const { return TimeDelta(); } |
| |
| int64_t ChunkDemuxer::GetMemoryUsage() const { |
| base::AutoLock auto_lock(lock_); |
| int64_t mem = 0; |
| for (size_t i = 0; i < audio_streams_.size(); ++i) |
| mem += audio_streams_[i]->GetBufferedSize(); |
| for (size_t i = 0; i < video_streams_.size(); ++i) |
| mem += video_streams_[i]->GetBufferedSize(); |
| return mem; |
| } |
| |
| void ChunkDemuxer::AbortPendingReads() { |
| base::AutoLock auto_lock(lock_); |
| DCHECK(state_ == INITIALIZED || state_ == ENDED || state_ == SHUTDOWN || |
| state_ == PARSE_ERROR) |
| << state_; |
| |
| if (state_ == SHUTDOWN || state_ == PARSE_ERROR) return; |
| |
| AbortPendingReads_Locked(); |
| } |
| |
| void ChunkDemuxer::StartWaitingForSeek(TimeDelta seek_time) { |
| DVLOG(1) << "StartWaitingForSeek()"; |
| base::AutoLock auto_lock(lock_); |
| DCHECK(state_ == INITIALIZED || state_ == ENDED || state_ == SHUTDOWN || |
| state_ == PARSE_ERROR) |
| << state_; |
| DCHECK(seek_cb_.is_null()); |
| |
| if (state_ == SHUTDOWN || state_ == PARSE_ERROR) return; |
| |
| AbortPendingReads_Locked(); |
| SeekAllSources(seek_time); |
| |
| // Cancel state set in CancelPendingSeek() since we want to |
| // accept the next Seek(). |
| cancel_next_seek_ = false; |
| } |
| |
| void ChunkDemuxer::CancelPendingSeek(TimeDelta seek_time) { |
| base::AutoLock auto_lock(lock_); |
| DCHECK_NE(state_, INITIALIZING); |
| DCHECK(seek_cb_.is_null() || IsSeekWaitingForData_Locked()); |
| |
| if (cancel_next_seek_) return; |
| |
| AbortPendingReads_Locked(); |
| SeekAllSources(seek_time); |
| |
| if (seek_cb_.is_null()) { |
| cancel_next_seek_ = true; |
| return; |
| } |
| |
| base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); |
| } |
| |
| ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id, |
| const std::string& type, |
| const std::string& codecs) { |
| DVLOG(1) << __func__ << " id=" << id << " mime_type=" << type |
| << " codecs=" << codecs; |
| base::AutoLock auto_lock(lock_); |
| |
| if ((state_ != WAITING_FOR_INIT && state_ != INITIALIZING) || IsValidId(id)) |
| return kReachedIdLimit; |
| |
| std::vector<std::string> parsed_codec_ids; |
| media::ParseCodecString(codecs, &parsed_codec_ids, false); |
| |
| scoped_ptr<media::StreamParser> stream_parser(StreamParserFactory::Create( |
| buffer_allocator_, type, parsed_codec_ids, media_log_)); |
| |
| if (!stream_parser) return ChunkDemuxer::kNotSupported; |
| |
| scoped_ptr<FrameProcessor> frame_processor( |
| new FrameProcessor(base::Bind(&ChunkDemuxer::IncreaseDurationIfNecessary, |
| base::Unretained(this)), |
| media_log_)); |
| |
| scoped_ptr<SourceBufferState> source_state( |
| new SourceBufferState(stream_parser.Pass(), frame_processor.Pass(), |
| base::Bind(&ChunkDemuxer::CreateDemuxerStream, |
| base::Unretained(this), id), |
| media_log_, buffer_allocator_)); |
| |
| SourceBufferState::NewTextTrackCB new_text_track_cb; |
| |
| if (enable_text_) { |
| new_text_track_cb = |
| base::Bind(&ChunkDemuxer::OnNewTextTrack, base::Unretained(this)); |
| } |
| |
| pending_source_init_ids_.insert(id); |
| |
| std::string expected_sbs_codecs = codecs; |
| if (codecs == "" && type == "audio/aac") expected_sbs_codecs = "aac"; |
| if (codecs == "" && (type == "audio/mpeg" || type == "audio/mp3")) |
| expected_sbs_codecs = "mp3"; |
| |
| source_state->Init( |
| base::Bind(&ChunkDemuxer::OnSourceInitDone, base::Unretained(this), id), |
| expected_sbs_codecs, encrypted_media_init_data_cb_, new_text_track_cb); |
| |
| source_state_map_[id] = source_state.release(); |
| return kOk; |
| } |
| |
| void ChunkDemuxer::SetTracksWatcher( |
| const std::string& id, const MediaTracksUpdatedCB& tracks_updated_cb) { |
| base::AutoLock auto_lock(lock_); |
| CHECK(IsValidId(id)); |
| source_state_map_[id]->SetTracksWatcher(tracks_updated_cb); |
| } |
| |
| void ChunkDemuxer::RemoveId(const std::string& id) { |
| DVLOG(1) << __func__ << " id=" << id; |
| base::AutoLock auto_lock(lock_); |
| CHECK(IsValidId(id)); |
| |
| delete source_state_map_[id]; |
| source_state_map_.erase(id); |
| pending_source_init_ids_.erase(id); |
| // Remove demuxer streams created for this id. |
| std::vector<ChunkDemuxerStream*>& streams = id_to_streams_map_[id]; |
| for (std::vector<ChunkDemuxerStream*>::const_iterator iter = streams.begin(); |
| iter != streams.end(); ++iter) { |
| bool stream_found = false; |
| for (size_t i = 0; i < audio_streams_.size(); ++i) { |
| if (audio_streams_[i] == *iter) { |
| stream_found = true; |
| removed_streams_.push_back(audio_streams_[i]); |
| audio_streams_.weak_erase(audio_streams_.begin() + i); |
| break; |
| } |
| } |
| if (stream_found) continue; |
| for (size_t i = 0; i < video_streams_.size(); ++i) { |
| if (video_streams_[i] == *iter) { |
| stream_found = true; |
| removed_streams_.push_back(video_streams_[i]); |
| video_streams_.weak_erase(video_streams_.begin() + i); |
| break; |
| } |
| } |
| CHECK(stream_found); |
| } |
| id_to_streams_map_.erase(id); |
| } |
| |
| Ranges<TimeDelta> ChunkDemuxer::GetBufferedRanges(const std::string& id) const { |
| base::AutoLock auto_lock(lock_); |
| DCHECK(!id.empty()); |
| |
| SourceBufferStateMap::const_iterator itr = source_state_map_.find(id); |
| |
| DCHECK(itr != source_state_map_.end()); |
| return itr->second->GetBufferedRanges(duration_, state_ == ENDED); |
| } |
| |
| base::TimeDelta ChunkDemuxer::GetHighestPresentationTimestamp( |
| const std::string& id) const { |
| base::AutoLock auto_lock(lock_); |
| DCHECK(!id.empty()); |
| |
| SourceBufferStateMap::const_iterator itr = source_state_map_.find(id); |
| |
| DCHECK(itr != source_state_map_.end()); |
| return itr->second->GetHighestPresentationTimestamp(); |
| } |
| |
| void ChunkDemuxer::OnEnabledAudioTracksChanged( |
| const std::vector<MediaTrack::Id>& track_ids, base::TimeDelta currTime) { |
| base::AutoLock auto_lock(lock_); |
| std::set<DemuxerStream*> enabled_streams; |
| for (size_t i = 0; i < track_ids.size(); ++i) { |
| DemuxerStream* stream = track_id_to_demux_stream_map_[track_ids[i]]; |
| DCHECK(stream); |
| DCHECK_EQ(DemuxerStream::AUDIO, stream->type()); |
| enabled_streams.insert(stream); |
| } |
| |
| // First disable all streams that need to be disabled and then enable streams |
| // that are enabled. |
| for (size_t i = 0; i < audio_streams_.size(); ++i) { |
| if (enabled_streams.find(audio_streams_[i]) == enabled_streams.end()) { |
| DVLOG(1) << __func__ << ": disabling stream " << audio_streams_[i]; |
| audio_streams_[i]->set_enabled(false, currTime); |
| } |
| } |
| for (std::set<DemuxerStream*>::iterator iter = enabled_streams.begin(); |
| iter != enabled_streams.end(); ++iter) { |
| DVLOG(1) << __func__ << ": enabling stream " << *iter; |
| (*iter)->set_enabled(true, currTime); |
| } |
| } |
| |
| void ChunkDemuxer::OnSelectedVideoTrackChanged( |
| const std::vector<MediaTrack::Id>& track_ids, base::TimeDelta currTime) { |
| DCHECK_LE(track_ids.size(), 1u); |
| |
| base::AutoLock auto_lock(lock_); |
| DemuxerStream* selected_stream = NULL; |
| if (!track_ids.empty()) { |
| selected_stream = track_id_to_demux_stream_map_[track_ids[0]]; |
| DCHECK(selected_stream); |
| DCHECK_EQ(DemuxerStream::VIDEO, selected_stream->type()); |
| } |
| |
| // First disable all streams that need to be disabled and then enable the |
| // stream that needs to be enabled (if any). |
| for (size_t i = 0; i < video_streams_.size(); ++i) { |
| if (video_streams_[i] != selected_stream) { |
| DVLOG(1) << __func__ << ": disabling stream " << video_streams_[i]; |
| DCHECK_EQ(DemuxerStream::VIDEO, video_streams_[i]->type()); |
| video_streams_[i]->set_enabled(false, currTime); |
| } |
| } |
| if (selected_stream) { |
| DVLOG(1) << __func__ << ": enabling stream " << selected_stream; |
| selected_stream->set_enabled(true, currTime); |
| } |
| } |
| |
| bool ChunkDemuxer::EvictCodedFrames(const std::string& id, |
| base::TimeDelta currentMediaTime, |
| size_t newDataSize) { |
| DVLOG(1) << __func__ << "(" << id << ")" |
| << " media_time=" << currentMediaTime.InSecondsF() |
| << " newDataSize=" << newDataSize; |
| base::AutoLock auto_lock(lock_); |
| |
| // Note: The direct conversion from PTS to DTS is safe here, since we don't |
| // need to know currentTime precisely for GC. GC only needs to know which GOP |
| // currentTime points to. |
| DecodeTimestamp media_time_dts = |
| DecodeTimestamp::FromPresentationTime(currentMediaTime); |
| |
| DCHECK(!id.empty()); |
| SourceBufferStateMap::iterator itr = source_state_map_.find(id); |
| if (itr == source_state_map_.end()) { |
| LOG(WARNING) << __func__ << " stream " << id << " not found"; |
| return false; |
| } |
| return itr->second->EvictCodedFrames(media_time_dts, newDataSize); |
| } |
| |
| bool ChunkDemuxer::AppendData(const std::string& id, const uint8_t* data, |
| size_t length, TimeDelta append_window_start, |
| TimeDelta append_window_end, |
| TimeDelta* timestamp_offset) { |
| DVLOG(1) << "AppendData(" << id << ", " << length << ")"; |
| |
| DCHECK(!id.empty()); |
| DCHECK(timestamp_offset); |
| |
| Ranges<TimeDelta> ranges; |
| |
| { |
| base::AutoLock auto_lock(lock_); |
| DCHECK_NE(state_, ENDED); |
| |
| // Capture if any of the SourceBuffers are waiting for data before we start |
| // parsing. |
| bool old_waiting_for_data = IsSeekWaitingForData_Locked(); |
| |
| if (length == 0u) return true; |
| |
| DCHECK(data); |
| |
| switch (state_) { |
| case INITIALIZING: |
| case INITIALIZED: |
| DCHECK(IsValidId(id)); |
| if (!source_state_map_[id]->Append(data, length, append_window_start, |
| append_window_end, |
| timestamp_offset)) { |
| ReportError_Locked(CHUNK_DEMUXER_ERROR_APPEND_FAILED); |
| return false; |
| } |
| break; |
| |
| case PARSE_ERROR: |
| case WAITING_FOR_INIT: |
| case ENDED: |
| case SHUTDOWN: |
| DVLOG(1) << "AppendData(): called in unexpected state " << state_; |
| return false; |
| } |
| |
| // Check to see if data was appended at the pending seek point. This |
| // indicates we have parsed enough data to complete the seek. |
| if (old_waiting_for_data && !IsSeekWaitingForData_Locked() && |
| !seek_cb_.is_null()) { |
| #if SB_HAS_QUIRK(SEEK_TO_KEYFRAME) |
| AdjustSeekOnAudioSource(); |
| StartReturningData(); |
| #endif // SB_HAS_QUIRK(SEEK_TO_KEYFRAME) |
| base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); |
| } |
| |
| ranges = GetBufferedRanges_Locked(); |
| } |
| |
| host_->OnBufferedTimeRangesChanged(ranges); |
| return true; |
| } |
| |
| void ChunkDemuxer::ResetParserState(const std::string& id, |
| TimeDelta append_window_start, |
| TimeDelta append_window_end, |
| TimeDelta* timestamp_offset) { |
| DVLOG(1) << "ResetParserState(" << id << ")"; |
| base::AutoLock auto_lock(lock_); |
| DCHECK(!id.empty()); |
| CHECK(IsValidId(id)); |
| bool old_waiting_for_data = IsSeekWaitingForData_Locked(); |
| source_state_map_[id]->ResetParserState(append_window_start, |
| append_window_end, timestamp_offset); |
| // ResetParserState can possibly emit some buffers. |
| // Need to check whether seeking can be completed. |
| if (old_waiting_for_data && !IsSeekWaitingForData_Locked() && |
| !seek_cb_.is_null()) { |
| base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); |
| } |
| } |
| |
| void ChunkDemuxer::Remove(const std::string& id, TimeDelta start, |
| TimeDelta end) { |
| DVLOG(1) << "Remove(" << id << ", " << start.InSecondsF() << ", " |
| << end.InSecondsF() << ")"; |
| base::AutoLock auto_lock(lock_); |
| |
| DCHECK(!id.empty()); |
| CHECK(IsValidId(id)); |
| DCHECK(start >= base::TimeDelta()) << start.InSecondsF(); |
| DCHECK(start < end) << "start " << start.InSecondsF() << " end " |
| << end.InSecondsF(); |
| DCHECK(duration_ != kNoTimestamp); |
| DCHECK(start <= duration_) << "start " << start.InSecondsF() << " duration " |
| << duration_.InSecondsF(); |
| |
| if (start == duration_) return; |
| |
| source_state_map_[id]->Remove(start, end, duration_); |
| host_->OnBufferedTimeRangesChanged(GetBufferedRanges_Locked()); |
| } |
| |
| double ChunkDemuxer::GetDuration() { |
| base::AutoLock auto_lock(lock_); |
| return GetDuration_Locked(); |
| } |
| |
| double ChunkDemuxer::GetDuration_Locked() { |
| lock_.AssertAcquired(); |
| if (duration_ == kNoTimestamp) |
| return std::numeric_limits<double>::quiet_NaN(); |
| |
| // Return positive infinity if the resource is unbounded. |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#dom-media-duration |
| if (duration_ == kInfiniteDuration) |
| return std::numeric_limits<double>::infinity(); |
| |
| if (user_specified_duration_ >= 0) return user_specified_duration_; |
| |
| return duration_.InSecondsF(); |
| } |
| |
| void ChunkDemuxer::SetDuration(double duration) { |
| base::AutoLock auto_lock(lock_); |
| DVLOG(1) << "SetDuration(" << duration << ")"; |
| DCHECK_GE(duration, 0); |
| |
| if (duration == GetDuration_Locked()) return; |
| |
| // Compute & bounds check the TimeDelta representation of duration. |
| // This can be different if the value of |duration| doesn't fit the range or |
| // precision of TimeDelta. |
| TimeDelta min_duration = TimeDelta::FromInternalValue(1); |
| // Don't use TimeDelta::Max() here, as we want the largest finite time delta. |
| TimeDelta max_duration = |
| TimeDelta::FromInternalValue(std::numeric_limits<int64_t>::max() - 1); |
| double min_duration_in_seconds = min_duration.InSecondsF(); |
| double max_duration_in_seconds = max_duration.InSecondsF(); |
| |
| TimeDelta duration_td; |
| if (duration == std::numeric_limits<double>::infinity()) { |
| duration_td = media::kInfiniteDuration; |
| } else if (duration < min_duration_in_seconds) { |
| duration_td = min_duration; |
| } else if (duration > max_duration_in_seconds) { |
| duration_td = max_duration; |
| } else { |
| duration_td = TimeDelta::FromMicroseconds( |
| duration * base::Time::kMicrosecondsPerSecond); |
| } |
| |
| DCHECK(duration_td > TimeDelta()); |
| |
| user_specified_duration_ = duration; |
| duration_ = duration_td; |
| host_->SetDuration(duration_); |
| |
| for (SourceBufferStateMap::iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| itr->second->OnSetDuration(duration_); |
| } |
| } |
| |
| bool ChunkDemuxer::IsParsingMediaSegment(const std::string& id) { |
| base::AutoLock auto_lock(lock_); |
| DVLOG(1) << "IsParsingMediaSegment(" << id << ")"; |
| CHECK(IsValidId(id)); |
| |
| return source_state_map_[id]->parsing_media_segment(); |
| } |
| |
| void ChunkDemuxer::SetSequenceMode(const std::string& id, bool sequence_mode) { |
| base::AutoLock auto_lock(lock_); |
| DVLOG(1) << "SetSequenceMode(" << id << ", " << sequence_mode << ")"; |
| CHECK(IsValidId(id)); |
| DCHECK_NE(state_, ENDED); |
| |
| source_state_map_[id]->SetSequenceMode(sequence_mode); |
| } |
| |
| void ChunkDemuxer::SetGroupStartTimestampIfInSequenceMode( |
| const std::string& id, base::TimeDelta timestamp_offset) { |
| base::AutoLock auto_lock(lock_); |
| DVLOG(1) << "SetGroupStartTimestampIfInSequenceMode(" << id << ", " |
| << timestamp_offset.InSecondsF() << ")"; |
| CHECK(IsValidId(id)); |
| DCHECK_NE(state_, ENDED); |
| |
| source_state_map_[id]->SetGroupStartTimestampIfInSequenceMode( |
| timestamp_offset); |
| } |
| |
| void ChunkDemuxer::MarkEndOfStream(PipelineStatus status) { |
| DVLOG(1) << "MarkEndOfStream(" << status << ")"; |
| base::AutoLock auto_lock(lock_); |
| DCHECK_NE(state_, WAITING_FOR_INIT); |
| DCHECK_NE(state_, ENDED); |
| |
| if (state_ == SHUTDOWN || state_ == PARSE_ERROR) return; |
| |
| if (state_ == INITIALIZING) { |
| ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN); |
| return; |
| } |
| |
| bool old_waiting_for_data = IsSeekWaitingForData_Locked(); |
| for (SourceBufferStateMap::iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| itr->second->MarkEndOfStream(); |
| } |
| |
| CompletePendingReadsIfPossible(); |
| |
| // Give a chance to resume the pending seek process. |
| if (status != PIPELINE_OK) { |
| DCHECK(status == CHUNK_DEMUXER_ERROR_EOS_STATUS_DECODE_ERROR || |
| status == CHUNK_DEMUXER_ERROR_EOS_STATUS_NETWORK_ERROR); |
| ReportError_Locked(status); |
| return; |
| } |
| |
| ChangeState_Locked(ENDED); |
| DecreaseDurationIfNecessary(); |
| |
| if (old_waiting_for_data && !IsSeekWaitingForData_Locked() && |
| !seek_cb_.is_null()) { |
| base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); |
| } |
| } |
| |
| void ChunkDemuxer::UnmarkEndOfStream() { |
| DVLOG(1) << "UnmarkEndOfStream()"; |
| base::AutoLock auto_lock(lock_); |
| // When we exceed the max buffer capacity we have |state_| PARSE_ERROR. |
| // Returning early causes a playback error. |
| if (state_ == PARSE_ERROR) { |
| return; |
| } |
| DCHECK_EQ(state_, ENDED); |
| |
| ChangeState_Locked(INITIALIZED); |
| |
| for (SourceBufferStateMap::iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| itr->second->UnmarkEndOfStream(); |
| } |
| } |
| |
| void ChunkDemuxer::Shutdown() { |
| DVLOG(1) << "Shutdown()"; |
| base::AutoLock auto_lock(lock_); |
| |
| if (state_ == SHUTDOWN) return; |
| |
| ShutdownAllStreams(); |
| |
| ChangeState_Locked(SHUTDOWN); |
| |
| if (!seek_cb_.is_null()) |
| base::ResetAndReturn(&seek_cb_).Run(PIPELINE_ERROR_ABORT); |
| } |
| |
| void ChunkDemuxer::SetMemoryLimitsForTest(DemuxerStream::Type type, |
| size_t memory_limit) { |
| for (SourceBufferStateMap::iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| itr->second->SetMemoryLimits(type, memory_limit); |
| } |
| } |
| |
| void ChunkDemuxer::ChangeState_Locked(State new_state) { |
| lock_.AssertAcquired(); |
| DVLOG(1) << "ChunkDemuxer::ChangeState_Locked() : " << state_ << " -> " |
| << new_state; |
| state_ = new_state; |
| } |
| |
| ChunkDemuxer::~ChunkDemuxer() { |
| DCHECK_NE(state_, INITIALIZED); |
| |
| while (!source_state_map_.empty()) { |
| delete source_state_map_.begin()->second; |
| source_state_map_.erase(source_state_map_.begin()); |
| } |
| } |
| |
| void ChunkDemuxer::ReportError_Locked(PipelineStatus error) { |
| DVLOG(1) << "ReportError_Locked(" << error << ")"; |
| lock_.AssertAcquired(); |
| DCHECK_NE(error, PIPELINE_OK); |
| |
| ChangeState_Locked(PARSE_ERROR); |
| |
| PipelineStatusCB cb; |
| |
| if (!init_cb_.is_null()) { |
| std::swap(cb, init_cb_); |
| } else { |
| if (!seek_cb_.is_null()) std::swap(cb, seek_cb_); |
| |
| ShutdownAllStreams(); |
| } |
| |
| if (!cb.is_null()) { |
| cb.Run(error); |
| return; |
| } |
| |
| base::AutoUnlock auto_unlock(lock_); |
| host_->OnDemuxerError(error); |
| } |
| |
| bool ChunkDemuxer::IsSeekWaitingForData_Locked() const { |
| lock_.AssertAcquired(); |
| for (SourceBufferStateMap::const_iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| if (itr->second->IsSeekWaitingForData()) return true; |
| } |
| |
| return false; |
| } |
| |
| void ChunkDemuxer::OnSourceInitDone( |
| const std::string& source_id, const StreamParser::InitParameters& params) { |
| TRACE_EVENT0("cobalt::media", "ChunkDemuxer::OnSourceInitDone"); |
| DVLOG(1) << "OnSourceInitDone source_id=" << source_id |
| << " duration=" << params.duration.InSecondsF(); |
| lock_.AssertAcquired(); |
| DCHECK_EQ(state_, INITIALIZING); |
| DCHECK(pending_source_init_ids_.find(source_id) != |
| pending_source_init_ids_.end()); |
| if (audio_streams_.empty() && video_streams_.empty()) { |
| ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN); |
| return; |
| } |
| |
| if (!params.duration.is_zero() && duration_ == kNoTimestamp) |
| UpdateDuration(params.duration); |
| |
| if (!params.timeline_offset.is_null()) { |
| if (!timeline_offset_.is_null() && |
| params.timeline_offset != timeline_offset_) { |
| MEDIA_LOG(ERROR, media_log_) |
| << "Timeline offset is not the same across all SourceBuffers."; |
| ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN); |
| return; |
| } |
| |
| timeline_offset_ = params.timeline_offset; |
| } |
| |
| if (params.liveness != DemuxerStream::LIVENESS_UNKNOWN) { |
| for (size_t i = 0; i < audio_streams_.size(); ++i) |
| audio_streams_[i]->SetLiveness(params.liveness); |
| for (size_t i = 0; i < video_streams_.size(); ++i) |
| video_streams_[i]->SetLiveness(params.liveness); |
| } |
| |
| detected_audio_track_count_ += params.detected_audio_track_count; |
| detected_video_track_count_ += params.detected_video_track_count; |
| detected_text_track_count_ += params.detected_text_track_count; |
| |
| // Wait until all streams have initialized. |
| pending_source_init_ids_.erase(source_id); |
| if (!pending_source_init_ids_.empty()) return; |
| |
| // Record detected track counts by type corresponding to an MSE playback. |
| // Counts are split into 50 buckets, capped into [0,100] range. |
| UMA_HISTOGRAM_COUNTS_100("Media.MSE.DetectedTrackCount.Audio", |
| detected_audio_track_count_); |
| UMA_HISTOGRAM_COUNTS_100("Media.MSE.DetectedTrackCount.Video", |
| detected_video_track_count_); |
| UMA_HISTOGRAM_COUNTS_100("Media.MSE.DetectedTrackCount.Text", |
| detected_text_track_count_); |
| |
| for (size_t i = 0; i < video_streams_.size(); ++i) { |
| media_log_->RecordRapporWithSecurityOrigin( |
| "Media.OriginUrl.MSE.VideoCodec." + |
| GetCodecName(video_streams_[i]->video_decoder_config().codec())); |
| } |
| |
| SeekAllSources(GetStartTime()); |
| StartReturningData(); |
| |
| if (duration_ == kNoTimestamp) duration_ = kInfiniteDuration; |
| |
| // The demuxer is now initialized after the |start_timestamp_| was set. |
| ChangeState_Locked(INITIALIZED); |
| base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK); |
| } |
| |
| // static |
| MediaTrack::Id ChunkDemuxer::GenerateMediaTrackId() { |
| static unsigned g_track_count = 0; |
| return base::UintToString(++g_track_count); |
| } |
| |
| ChunkDemuxerStream* ChunkDemuxer::CreateDemuxerStream( |
| const std::string& source_id, DemuxerStream::Type type) { |
| // New ChunkDemuxerStreams can be created only during initialization segment |
| // processing, which happens when a new chunk of data is appended and the |
| // lock_ must be held by ChunkDemuxer::AppendData. |
| lock_.AssertAcquired(); |
| |
| MediaTrack::Id media_track_id = GenerateMediaTrackId(); |
| |
| OwnedChunkDemuxerStreamVector* owning_vector = NULL; |
| switch (type) { |
| case DemuxerStream::AUDIO: |
| owning_vector = &audio_streams_; |
| break; |
| |
| case DemuxerStream::VIDEO: |
| owning_vector = &video_streams_; |
| break; |
| |
| case DemuxerStream::TEXT: |
| owning_vector = &text_streams_; |
| break; |
| |
| case DemuxerStream::UNKNOWN: |
| case DemuxerStream::NUM_TYPES: |
| NOTREACHED(); |
| return NULL; |
| } |
| |
| scoped_ptr<ChunkDemuxerStream> stream( |
| new ChunkDemuxerStream(type, splice_frames_enabled_, media_track_id)); |
| DCHECK(track_id_to_demux_stream_map_.find(media_track_id) == |
| track_id_to_demux_stream_map_.end()); |
| track_id_to_demux_stream_map_[media_track_id] = stream.get(); |
| id_to_streams_map_[source_id].push_back(stream.get()); |
| owning_vector->push_back(stream.release()); |
| return owning_vector->back(); |
| } |
| |
| void ChunkDemuxer::OnNewTextTrack(ChunkDemuxerStream* text_stream, |
| const TextTrackConfig& config) { |
| lock_.AssertAcquired(); |
| DCHECK_NE(state_, SHUTDOWN); |
| host_->AddTextStream(text_stream, config); |
| } |
| |
| bool ChunkDemuxer::IsValidId(const std::string& source_id) const { |
| lock_.AssertAcquired(); |
| return source_state_map_.count(source_id) > 0u; |
| } |
| |
| void ChunkDemuxer::UpdateDuration(TimeDelta new_duration) { |
| DCHECK(duration_ != new_duration); |
| user_specified_duration_ = -1; |
| duration_ = new_duration; |
| host_->SetDuration(new_duration); |
| } |
| |
| void ChunkDemuxer::IncreaseDurationIfNecessary(TimeDelta new_duration) { |
| DCHECK(new_duration != kNoTimestamp); |
| DCHECK(new_duration != kInfiniteDuration); |
| |
| // Per April 1, 2014 MSE spec editor's draft: |
| // https://dvcs.w3.org/hg/html-media/raw-file/d471a4412040/media-source/ |
| // media-source.html#sourcebuffer-coded-frame-processing |
| // 5. If the media segment contains data beyond the current duration, then run |
| // the duration change algorithm with new duration set to the maximum of |
| // the current duration and the group end timestamp. |
| |
| if (new_duration <= duration_) return; |
| |
| DVLOG(2) << __func__ << ": Increasing duration: " << duration_.InSecondsF() |
| << " -> " << new_duration.InSecondsF(); |
| |
| UpdateDuration(new_duration); |
| } |
| |
| void ChunkDemuxer::DecreaseDurationIfNecessary() { |
| lock_.AssertAcquired(); |
| |
| TimeDelta max_duration; |
| |
| for (SourceBufferStateMap::iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| max_duration = |
| std::max(max_duration, itr->second->GetMaxBufferedDuration()); |
| } |
| |
| if (max_duration.is_zero()) return; |
| |
| if (max_duration < duration_) UpdateDuration(max_duration); |
| } |
| |
| Ranges<TimeDelta> ChunkDemuxer::GetBufferedRanges() const { |
| base::AutoLock auto_lock(lock_); |
| return GetBufferedRanges_Locked(); |
| } |
| |
| Ranges<TimeDelta> ChunkDemuxer::GetBufferedRanges_Locked() const { |
| lock_.AssertAcquired(); |
| |
| bool ended = state_ == ENDED; |
| // TODO(acolwell): When we start allowing SourceBuffers that are not active, |
| // we'll need to update this loop to only add ranges from active sources. |
| SourceBufferState::RangesList ranges_list; |
| for (SourceBufferStateMap::const_iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| ranges_list.push_back(itr->second->GetBufferedRanges(duration_, ended)); |
| } |
| |
| return SourceBufferState::ComputeRangesIntersection(ranges_list, ended); |
| } |
| |
| void ChunkDemuxer::StartReturningData() { |
| for (SourceBufferStateMap::iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| itr->second->StartReturningData(); |
| } |
| } |
| |
| void ChunkDemuxer::AbortPendingReads_Locked() { |
| for (SourceBufferStateMap::iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| itr->second->AbortReads(); |
| } |
| } |
| |
| void ChunkDemuxer::SeekAllSources(TimeDelta seek_time) { |
| for (SourceBufferStateMap::iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| itr->second->Seek(seek_time); |
| } |
| } |
| |
| void ChunkDemuxer::AdjustSeekOnAudioSource() { |
| // This function assumes that there is only one audio and one video stream in |
| // the video. As we won't be able to align seek to more than one video |
| // streams unless their keyframes are aligned. |
| |
| // Find the timestamp of the video keyframe just before the seek timestamp of |
| // the first video stream. |
| TimeDelta adjusted_seek_time = kNoTimestamp; |
| for (SourceBufferStateMap::iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| TimeDelta seek_keyframe_timestamp = |
| itr->second->GetVideoSeekKeyframeTimestamp(); |
| if (seek_keyframe_timestamp == kNoTimestamp) { |
| continue; |
| } |
| adjusted_seek_time = seek_keyframe_timestamp; |
| break; |
| } |
| |
| if (adjusted_seek_time == kNoTimestamp) { |
| return; |
| } |
| |
| for (SourceBufferStateMap::iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| Ranges<TimeDelta> ranges_list = |
| itr->second->GetAudioBufferedRanges(duration_, state_ == ENDED); |
| if (ranges_list.size() == 0) { |
| continue; |
| } |
| // Note that in rare situations the seek may fail as the append after seek |
| // may eject some audio buffer and no longer be able to fulfill the seek. |
| // Avoid calling Seek() in this case so the demuxer won't hang. The video |
| // can continue to play, just with some leading silence. |
| for (size_t i = 0; i < ranges_list.size(); ++i) { |
| if (adjusted_seek_time >= ranges_list.start(i) && |
| adjusted_seek_time <= ranges_list.end(i)) { |
| itr->second->Seek(adjusted_seek_time); |
| } |
| } |
| return; |
| } |
| } |
| |
| void ChunkDemuxer::CompletePendingReadsIfPossible() { |
| for (SourceBufferStateMap::iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| itr->second->CompletePendingReadIfPossible(); |
| } |
| } |
| |
| void ChunkDemuxer::ShutdownAllStreams() { |
| for (SourceBufferStateMap::iterator itr = source_state_map_.begin(); |
| itr != source_state_map_.end(); ++itr) { |
| itr->second->Shutdown(); |
| } |
| } |
| |
| } // namespace media |
| } // namespace cobalt |