Chromium Code Reviews| Index: media/filters/media_source_state.cc |
| diff --git a/media/filters/media_source_state.cc b/media/filters/media_source_state.cc |
| index c3152a2d51bbd7d2c238c28ebc4cd7f2b21ea96d..4fdcc6af93752e5a360d629fe56f5a49de781d72 100644 |
| --- a/media/filters/media_source_state.cc |
| +++ b/media/filters/media_source_state.cc |
| @@ -4,6 +4,8 @@ |
| #include "media/filters/media_source_state.h" |
| +#include <set> |
| + |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/stl_util.h" |
| @@ -11,6 +13,7 @@ |
| #include "media/base/media_switches.h" |
| #include "media/base/media_track.h" |
| #include "media/base/media_tracks.h" |
| +#include "media/base/mime_util.h" |
| #include "media/filters/chunk_demuxer.h" |
| #include "media/filters/frame_processor.h" |
| #include "media/filters/source_buffer_stream.h" |
| @@ -24,10 +27,27 @@ enum { |
| kMaxMissingTrackInSegmentLogs = 10, |
| }; |
| -static TimeDelta EndTimestamp(const StreamParser::BufferQueue& queue) { |
| +namespace { |
| + |
| +TimeDelta EndTimestamp(const StreamParser::BufferQueue& queue) { |
| return queue.back()->timestamp() + queue.back()->duration(); |
| } |
| +const char* ToStr(MediaTrack::Type type) { |
|
wolenetz
2016/09/13 21:03:14
nit: useful elsewhere? If so, move to some method
servolk
2016/09/14 18:15:25
Done.
|
| + switch (type) { |
| + case MediaTrack::Audio: |
| + return "audio"; |
| + case MediaTrack::Text: |
| + return "text"; |
| + case MediaTrack::Video: |
| + return "video"; |
| + } |
| + NOTREACHED(); |
| + return "INVALID"; |
| +} |
| + |
| +} // namespace |
| + |
| // List of time ranges for each SourceBuffer. |
| // static |
| Ranges<TimeDelta> MediaSourceState::ComputeRangesIntersection( |
| @@ -94,11 +114,7 @@ MediaSourceState::MediaSourceState( |
| : create_demuxer_stream_cb_(create_demuxer_stream_cb), |
| timestamp_offset_during_append_(NULL), |
| parsing_media_segment_(false), |
| - media_segment_contained_audio_frame_(false), |
| - media_segment_contained_video_frame_(false), |
| stream_parser_(stream_parser.release()), |
| - audio_(NULL), |
| - video_(NULL), |
| frame_processor_(frame_processor.release()), |
| media_log_(media_log), |
| state_(UNINITIALIZED), |
| @@ -115,8 +131,7 @@ MediaSourceState::~MediaSourceState() { |
| void MediaSourceState::Init( |
| const StreamParser::InitCB& init_cb, |
| - bool allow_audio, |
| - bool allow_video, |
| + const std::string& expected_codecs, |
| const StreamParser::EncryptedMediaInitDataCB& encrypted_media_init_data_cb, |
| const NewTextTrackCB& new_text_track_cb) { |
| DCHECK_EQ(state_, UNINITIALIZED); |
| @@ -127,7 +142,7 @@ void MediaSourceState::Init( |
| stream_parser_->Init( |
| base::Bind(&MediaSourceState::OnSourceInitDone, base::Unretained(this)), |
| base::Bind(&MediaSourceState::OnNewConfigs, base::Unretained(this), |
| - allow_audio, allow_video), |
| + expected_codecs), |
| base::Bind(&MediaSourceState::OnNewBuffers, base::Unretained(this)), |
| new_text_track_cb_.is_null(), encrypted_media_init_data_cb, |
| base::Bind(&MediaSourceState::OnNewMediaSegment, base::Unretained(this)), |
| @@ -196,18 +211,19 @@ void MediaSourceState::ResetParserState(TimeDelta append_window_start, |
| frame_processor_->Reset(); |
| parsing_media_segment_ = false; |
| - media_segment_contained_audio_frame_ = false; |
| - media_segment_contained_video_frame_ = false; |
| + media_segment_has_data_for_track_.clear(); |
| } |
| void MediaSourceState::Remove(TimeDelta start, |
| TimeDelta end, |
| TimeDelta duration) { |
| - if (audio_) |
| - audio_->Remove(start, end, duration); |
| + for (const auto& it : audio_streams_) { |
| + it.second->Remove(start, end, duration); |
| + } |
| - if (video_) |
| - video_->Remove(start, end, duration); |
| + for (const auto& it : video_streams_) { |
| + it.second->Remove(start, end, duration); |
| + } |
| for (TextStreamMap::iterator itr = text_stream_map_.begin(); |
| itr != text_stream_map_.end(); ++itr) { |
| @@ -215,93 +231,71 @@ void MediaSourceState::Remove(TimeDelta start, |
| } |
| } |
| -size_t MediaSourceState::EstimateVideoDataSize( |
| - size_t muxed_data_chunk_size) const { |
| - DCHECK(audio_); |
| - DCHECK(video_); |
| - |
| - size_t videoBufferedSize = video_->GetBufferedSize(); |
| - size_t audioBufferedSize = audio_->GetBufferedSize(); |
| - if (videoBufferedSize == 0 || audioBufferedSize == 0) { |
| - // At this point either audio or video buffer is empty, which means buffer |
| - // levels are probably low anyway and we should have enough space in the |
| - // buffers for appending new data, so just take a very rough guess. |
| - return muxed_data_chunk_size * 7 / 8; |
| - } |
| - |
| - // We need to estimate how much audio and video data is going to be in the |
| - // newly appended data chunk to make space for the new data. And we need to do |
| - // that without parsing the data (which will happen later, in the Append |
| - // phase). So for now we can only rely on some heuristic here. Let's assume |
| - // that the proportion of the audio/video in the new data chunk is the same as |
| - // the current ratio of buffered audio/video. |
| - // Longer term this should go away once we further change the MSE GC algorithm |
| - // to work across all streams of a SourceBuffer (see crbug.com/520704). |
| - double videoBufferedSizeF = static_cast<double>(videoBufferedSize); |
| - double audioBufferedSizeF = static_cast<double>(audioBufferedSize); |
| - |
| - double totalBufferedSizeF = videoBufferedSizeF + audioBufferedSizeF; |
| - CHECK_GT(totalBufferedSizeF, 0.0); |
| - |
| - double videoRatio = videoBufferedSizeF / totalBufferedSizeF; |
| - CHECK_GE(videoRatio, 0.0); |
| - CHECK_LE(videoRatio, 1.0); |
| - double estimatedVideoSize = muxed_data_chunk_size * videoRatio; |
| - return static_cast<size_t>(estimatedVideoSize); |
| -} |
| - |
| bool MediaSourceState::EvictCodedFrames(DecodeTimestamp media_time, |
| size_t newDataSize) { |
| bool success = true; |
| DVLOG(3) << __func__ << " media_time=" << media_time.InSecondsF() |
| - << " newDataSize=" << newDataSize |
| - << " videoBufferedSize=" << (video_ ? video_->GetBufferedSize() : 0) |
| - << " audioBufferedSize=" << (audio_ ? audio_->GetBufferedSize() : 0); |
| + << " newDataSize=" << newDataSize; |
| - size_t newAudioSize = 0; |
| - size_t newVideoSize = 0; |
| - if (audio_ && video_) { |
| - newVideoSize = EstimateVideoDataSize(newDataSize); |
| - newAudioSize = newDataSize - newVideoSize; |
| - } else if (video_) { |
| - newVideoSize = newDataSize; |
| - } else if (audio_) { |
| - newAudioSize = newDataSize; |
| + DVLOG(4) << "Before EvictCodedFrames:"; |
| + for (const auto& it : audio_streams_) { |
| + DVLOG(4) << "Audio track_id=" << it.second->media_track_id() |
| + << " buffered_size=" << it.second->GetBufferedSize(); |
| + } |
| + for (const auto& it : video_streams_) { |
| + DVLOG(4) << "Video track_id=" << it.second->media_track_id() |
| + << " buffered_size=" << it.second->GetBufferedSize(); |
| } |
| - DVLOG(3) << __func__ |
| - << " estimated audio/video sizes: newVideoSize=" << newVideoSize |
| - << " newAudioSize=" << newAudioSize; |
| - |
| - if (audio_) |
| - success = audio_->EvictCodedFrames(media_time, newAudioSize) && success; |
| + size_t estimatedAudioSize = newDataSize; |
| + size_t estimatedVideoSize = newDataSize; |
| + if (!audio_streams_.empty() && !video_streams_.empty()) { |
| + estimatedAudioSize = newDataSize / 16; |
|
wolenetz
2016/09/13 21:03:14
nit: Assumption is overall, across all A+V tracks
servolk
2016/09/14 18:15:26
Yeah, after pondering this a bit more, I think we
wolenetz
2016/09/14 23:31:21
Acknowledged.
|
| + estimatedVideoSize = newDataSize - estimatedAudioSize; |
| + } |
| + if (audio_streams_.size() > 0) |
| + estimatedAudioSize /= audio_streams_.size(); |
| + if (video_streams_.size() > 0) |
| + estimatedVideoSize /= video_streams_.size(); |
| - if (video_) |
| - success = video_->EvictCodedFrames(media_time, newVideoSize) && success; |
| + for (const auto& it : audio_streams_) { |
| + success &= it.second->EvictCodedFrames(media_time, estimatedAudioSize); |
| + } |
| + for (const auto& it : video_streams_) { |
| + success &= it.second->EvictCodedFrames(media_time, estimatedVideoSize); |
| + } |
| for (TextStreamMap::iterator itr = text_stream_map_.begin(); |
| itr != text_stream_map_.end(); ++itr) { |
| - success = itr->second->EvictCodedFrames(media_time, 0) && success; |
| + success &= itr->second->EvictCodedFrames(media_time, 0); |
| } |
| - DVLOG(3) << __func__ << " result=" << success |
| - << " videoBufferedSize=" << (video_ ? video_->GetBufferedSize() : 0) |
| - << " audioBufferedSize=" << (audio_ ? audio_->GetBufferedSize() : 0); |
| + DVLOG(4) << "After EvictCodedFrames (success=" << success << "):"; |
| + for (const auto& it : audio_streams_) { |
| + DVLOG(4) << "Audio track_id=" << it.second->media_track_id() |
| + << " buffered_size=" << it.second->GetBufferedSize(); |
| + } |
| + for (const auto& it : video_streams_) { |
| + DVLOG(4) << "Video track_id=" << it.second->media_track_id() |
| + << " buffered_size=" << it.second->GetBufferedSize(); |
| + } |
| return success; |
| } |
| Ranges<TimeDelta> MediaSourceState::GetBufferedRanges(TimeDelta duration, |
|
wolenetz
2016/09/13 21:03:14
hmm. I think that TODO was wrong. SourceBuffer.buf
servolk
2016/09/14 18:15:26
Ok, in that case we can just drop the check for en
|
| bool ended) const { |
| - // TODO(acolwell): When we start allowing disabled tracks we'll need to update |
| - // this code to only add ranges from active tracks. |
| RangesList ranges_list; |
| - if (audio_) |
| - ranges_list.push_back(audio_->GetBufferedRanges(duration)); |
| + for (const auto& it : audio_streams_) { |
| + if (it.second->enabled()) |
| + ranges_list.push_back(it.second->GetBufferedRanges(duration)); |
| + } |
| - if (video_) |
| - ranges_list.push_back(video_->GetBufferedRanges(duration)); |
| + for (const auto& it : video_streams_) { |
| + if (it.second->enabled()) |
| + ranges_list.push_back(it.second->GetBufferedRanges(duration)); |
| + } |
| for (TextStreamMap::const_iterator itr = text_stream_map_.begin(); |
| itr != text_stream_map_.end(); ++itr) { |
| @@ -314,11 +308,13 @@ Ranges<TimeDelta> MediaSourceState::GetBufferedRanges(TimeDelta duration, |
| TimeDelta MediaSourceState::GetHighestPresentationTimestamp() const { |
| TimeDelta max_pts; |
| - if (audio_) |
| - max_pts = std::max(max_pts, audio_->GetHighestPresentationTimestamp()); |
| + for (const auto& it : audio_streams_) { |
| + max_pts = std::max(max_pts, it.second->GetHighestPresentationTimestamp()); |
| + } |
| - if (video_) |
| - max_pts = std::max(max_pts, video_->GetHighestPresentationTimestamp()); |
| + for (const auto& it : video_streams_) { |
| + max_pts = std::max(max_pts, it.second->GetHighestPresentationTimestamp()); |
| + } |
| for (TextStreamMap::const_iterator itr = text_stream_map_.begin(); |
| itr != text_stream_map_.end(); ++itr) { |
| @@ -331,11 +327,13 @@ TimeDelta MediaSourceState::GetHighestPresentationTimestamp() const { |
| TimeDelta MediaSourceState::GetMaxBufferedDuration() const { |
| TimeDelta max_duration; |
| - if (audio_) |
| - max_duration = std::max(max_duration, audio_->GetBufferedDuration()); |
| + for (const auto& it : audio_streams_) { |
| + max_duration = std::max(max_duration, it.second->GetBufferedDuration()); |
| + } |
| - if (video_) |
| - max_duration = std::max(max_duration, video_->GetBufferedDuration()); |
| + for (const auto& it : video_streams_) { |
| + max_duration = std::max(max_duration, it.second->GetBufferedDuration()); |
| + } |
| for (TextStreamMap::const_iterator itr = text_stream_map_.begin(); |
| itr != text_stream_map_.end(); ++itr) { |
| @@ -346,11 +344,13 @@ TimeDelta MediaSourceState::GetMaxBufferedDuration() const { |
| } |
| void MediaSourceState::StartReturningData() { |
| - if (audio_) |
| - audio_->StartReturningData(); |
| + for (const auto& it : audio_streams_) { |
| + it.second->StartReturningData(); |
| + } |
| - if (video_) |
| - video_->StartReturningData(); |
| + for (const auto& it : video_streams_) { |
| + it.second->StartReturningData(); |
| + } |
| for (TextStreamMap::iterator itr = text_stream_map_.begin(); |
| itr != text_stream_map_.end(); ++itr) { |
| @@ -359,11 +359,13 @@ void MediaSourceState::StartReturningData() { |
| } |
| void MediaSourceState::AbortReads() { |
| - if (audio_) |
| - audio_->AbortReads(); |
| + for (const auto& it : audio_streams_) { |
| + it.second->AbortReads(); |
| + } |
| - if (video_) |
| - video_->AbortReads(); |
| + for (const auto& it : video_streams_) { |
| + it.second->AbortReads(); |
| + } |
| for (TextStreamMap::iterator itr = text_stream_map_.begin(); |
| itr != text_stream_map_.end(); ++itr) { |
| @@ -372,11 +374,13 @@ void MediaSourceState::AbortReads() { |
| } |
| void MediaSourceState::Seek(TimeDelta seek_time) { |
| - if (audio_) |
| - audio_->Seek(seek_time); |
| + for (const auto& it : audio_streams_) { |
| + it.second->Seek(seek_time); |
| + } |
| - if (video_) |
| - video_->Seek(seek_time); |
| + for (const auto& it : video_streams_) { |
| + it.second->Seek(seek_time); |
| + } |
| for (TextStreamMap::iterator itr = text_stream_map_.begin(); |
| itr != text_stream_map_.end(); ++itr) { |
| @@ -385,11 +389,13 @@ void MediaSourceState::Seek(TimeDelta seek_time) { |
| } |
| void MediaSourceState::CompletePendingReadIfPossible() { |
| - if (audio_) |
| - audio_->CompletePendingReadIfPossible(); |
| + for (const auto& it : audio_streams_) { |
| + it.second->CompletePendingReadIfPossible(); |
| + } |
| - if (video_) |
| - video_->CompletePendingReadIfPossible(); |
| + for (const auto& it : video_streams_) { |
| + it.second->CompletePendingReadIfPossible(); |
| + } |
| for (TextStreamMap::iterator itr = text_stream_map_.begin(); |
| itr != text_stream_map_.end(); ++itr) { |
| @@ -398,11 +404,13 @@ void MediaSourceState::CompletePendingReadIfPossible() { |
| } |
| void MediaSourceState::OnSetDuration(TimeDelta duration) { |
| - if (audio_) |
| - audio_->OnSetDuration(duration); |
| + for (const auto& it : audio_streams_) { |
| + it.second->OnSetDuration(duration); |
| + } |
| - if (video_) |
| - video_->OnSetDuration(duration); |
| + for (const auto& it : video_streams_) { |
| + it.second->OnSetDuration(duration); |
| + } |
| for (TextStreamMap::iterator itr = text_stream_map_.begin(); |
| itr != text_stream_map_.end(); ++itr) { |
| @@ -411,11 +419,13 @@ void MediaSourceState::OnSetDuration(TimeDelta duration) { |
| } |
| void MediaSourceState::MarkEndOfStream() { |
| - if (audio_) |
| - audio_->MarkEndOfStream(); |
| + for (const auto& it : audio_streams_) { |
| + it.second->MarkEndOfStream(); |
| + } |
| - if (video_) |
| - video_->MarkEndOfStream(); |
| + for (const auto& it : video_streams_) { |
| + it.second->MarkEndOfStream(); |
| + } |
| for (TextStreamMap::iterator itr = text_stream_map_.begin(); |
| itr != text_stream_map_.end(); ++itr) { |
| @@ -424,11 +434,13 @@ void MediaSourceState::MarkEndOfStream() { |
| } |
| void MediaSourceState::UnmarkEndOfStream() { |
| - if (audio_) |
| - audio_->UnmarkEndOfStream(); |
| + for (const auto& it : audio_streams_) { |
| + it.second->UnmarkEndOfStream(); |
| + } |
| - if (video_) |
| - video_->UnmarkEndOfStream(); |
| + for (const auto& it : video_streams_) { |
| + it.second->UnmarkEndOfStream(); |
| + } |
| for (TextStreamMap::iterator itr = text_stream_map_.begin(); |
| itr != text_stream_map_.end(); ++itr) { |
| @@ -437,11 +449,13 @@ void MediaSourceState::UnmarkEndOfStream() { |
| } |
| void MediaSourceState::Shutdown() { |
| - if (audio_) |
| - audio_->Shutdown(); |
| + for (const auto& it : audio_streams_) { |
| + it.second->Shutdown(); |
| + } |
| - if (video_) |
| - video_->Shutdown(); |
| + for (const auto& it : video_streams_) { |
| + it.second->Shutdown(); |
| + } |
| for (TextStreamMap::iterator itr = text_stream_map_.begin(); |
| itr != text_stream_map_.end(); ++itr) { |
| @@ -453,12 +467,14 @@ void MediaSourceState::SetMemoryLimits(DemuxerStream::Type type, |
| size_t memory_limit) { |
| switch (type) { |
| case DemuxerStream::AUDIO: |
| - if (audio_) |
| - audio_->SetStreamMemoryLimit(memory_limit); |
| + for (const auto& it : audio_streams_) { |
| + it.second->SetStreamMemoryLimit(memory_limit); |
| + } |
| break; |
| case DemuxerStream::VIDEO: |
| - if (video_) |
| - video_->SetStreamMemoryLimit(memory_limit); |
| + for (const auto& it : video_streams_) { |
| + it.second->SetStreamMemoryLimit(memory_limit); |
| + } |
| break; |
| case DemuxerStream::TEXT: |
| for (TextStreamMap::iterator itr = text_stream_map_.begin(); |
| @@ -474,11 +490,15 @@ void MediaSourceState::SetMemoryLimits(DemuxerStream::Type type, |
| } |
| bool MediaSourceState::IsSeekWaitingForData() const { |
| - if (audio_ && audio_->IsSeekWaitingForData()) |
| - return true; |
| + for (const auto& it : audio_streams_) { |
| + if (it.second->IsSeekWaitingForData()) |
| + return true; |
| + } |
| - if (video_ && video_->IsSeekWaitingForData()) |
| - return true; |
| + for (const auto& it : video_streams_) { |
| + if (it.second->IsSeekWaitingForData()) |
| + return true; |
| + } |
| // NOTE: We are intentionally not checking the text tracks |
| // because text tracks are discontinuous and may not have data |
| @@ -490,164 +510,200 @@ bool MediaSourceState::IsSeekWaitingForData() const { |
| return false; |
| } |
| +bool CheckBytestreamTrackIds( |
|
wolenetz
2016/09/13 21:03:14
move to anon namespace at top of file
servolk
2016/09/14 18:15:25
Done.
|
| + const MediaTracks& tracks, |
| + const StreamParser::TextTrackConfigMap& text_configs) { |
| + std::set<StreamParser::TrackId> bytestream_ids; |
| + for (const auto& track : tracks.tracks()) { |
| + const StreamParser::TrackId& track_id = track->bytestream_track_id(); |
| + if (bytestream_ids.find(track_id) != bytestream_ids.end()) { |
| + return false; |
| + } |
| + bytestream_ids.insert(track_id); |
| + } |
| + for (const auto& text_track : text_configs) { |
| + const StreamParser::TrackId& track_id = text_track.first; |
| + if (bytestream_ids.find(track_id) != bytestream_ids.end()) { |
| + return false; |
| + } |
| + bytestream_ids.insert(track_id); |
| + } |
| + return true; |
| +} |
| + |
| bool MediaSourceState::OnNewConfigs( |
| - bool allow_audio, |
| - bool allow_video, |
| + std::string expected_codecs, |
| std::unique_ptr<MediaTracks> tracks, |
| const StreamParser::TextTrackConfigMap& text_configs) { |
| - DCHECK_GE(state_, PENDING_PARSER_CONFIG); |
| DCHECK(tracks.get()); |
| + DVLOG(1) << __func__ << " expected_codecs=" << expected_codecs |
| + << " tracks=" << tracks->tracks().size(); |
| + DCHECK_GE(state_, PENDING_PARSER_CONFIG); |
| - MediaTrack* audio_track = nullptr; |
| - MediaTrack* video_track = nullptr; |
| - AudioDecoderConfig audio_config; |
| - VideoDecoderConfig video_config; |
| - for (const auto& track : tracks->tracks()) { |
| - const auto& track_id = track->bytestream_track_id(); |
| - |
| - if (track->type() == MediaTrack::Audio) { |
| - if (audio_track) { |
| - MEDIA_LOG(ERROR, media_log_) |
| - << "Error: more than one audio track is currently not supported."; |
| - return false; |
| - } |
| - audio_track = track.get(); |
| - audio_config = tracks->getAudioConfig(track_id); |
| - DCHECK(audio_config.IsValidConfig()); |
| - } else if (track->type() == MediaTrack::Video) { |
| - if (video_track) { |
| - MEDIA_LOG(ERROR, media_log_) |
| - << "Error: more than one video track is currently not supported."; |
| - return false; |
| - } |
| - video_track = track.get(); |
| - video_config = tracks->getVideoConfig(track_id); |
| - DCHECK(video_config.IsValidConfig()); |
| - } else { |
| - MEDIA_LOG(ERROR, media_log_) << "Error: unsupported media track type " |
| - << track->type(); |
| - return false; |
| + // Check that there is no clashing bytestream track ids. |
| + if (!CheckBytestreamTrackIds(*tracks, text_configs)) { |
| + MEDIA_LOG(ERROR, media_log_) |
| + << "Error: duplicate bytestream track ids detected"; |
|
wolenetz
2016/09/13 21:03:13
nit: s/Error: duplicate/Duplicate/ (ERROR already
servolk
2016/09/14 18:15:26
Done.
|
| + for (const auto& track : tracks->tracks()) { |
| + const StreamParser::TrackId& track_id = track->bytestream_track_id(); |
| + MEDIA_LOG(ERROR, media_log_) << ToStr(track->type()) << " track " |
|
wolenetz
2016/09/13 21:03:13
nit:s/ERROR/DEBUG/ so we can expose the last cache
servolk
2016/09/14 18:15:25
Done.
|
| + << " bytestream track id=" << track_id; |
| } |
| + return false; |
| } |
| - DVLOG(1) << "OnNewConfigs(" << allow_audio << ", " << allow_video << ", " |
| - << audio_config.IsValidConfig() << ", " |
| - << video_config.IsValidConfig() << ")"; |
| // MSE spec allows new configs to be emitted only during Append, but not |
| // during Flush or parser reset operations. |
| CHECK(append_in_progress_); |
| - if (!audio_config.IsValidConfig() && !video_config.IsValidConfig()) { |
| - DVLOG(1) << "OnNewConfigs() : Audio & video config are not valid!"; |
| - return false; |
| - } |
| - |
| - // Signal an error if we get configuration info for stream types that weren't |
| - // specified in AddId() or more configs after a stream is initialized. |
| - if (allow_audio != audio_config.IsValidConfig()) { |
| - MEDIA_LOG(ERROR, media_log_) |
| - << "Initialization segment" |
| - << (audio_config.IsValidConfig() ? " has" : " does not have") |
| - << " an audio track, but the mimetype" |
| - << (allow_audio ? " specifies" : " does not specify") |
| - << " an audio codec."; |
| - return false; |
| - } |
| + bool success = true; |
| - if (allow_video != video_config.IsValidConfig()) { |
| - MEDIA_LOG(ERROR, media_log_) |
| - << "Initialization segment" |
| - << (video_config.IsValidConfig() ? " has" : " does not have") |
| - << " a video track, but the mimetype" |
| - << (allow_video ? " specifies" : " does not specify") |
| - << " a video codec."; |
| - return false; |
| - } |
| + std::vector<std::string> expected_codecs_parsed; |
| + ParseCodecString(expected_codecs, &expected_codecs_parsed, false); |
|
wolenetz
2016/09/13 21:03:14
nit: Every init segment, we parse these? Can we in
servolk
2016/09/14 18:15:25
Yeah, I don't think it's going to make a big diffe
wolenetz
2016/09/14 23:31:21
Acknowledged.
|
| - bool success = true; |
| - if (audio_config.IsValidConfig()) { |
| - if (!audio_) { |
| - media_log_->SetBooleanProperty("found_audio_stream", true); |
| + std::vector<AudioCodec> expected_acodecs; |
| + std::vector<VideoCodec> expected_vcodecs; |
| + for (const auto& codec_id : expected_codecs_parsed) { |
| + AudioCodec acodec = StringToAudioCodec(codec_id); |
| + if (acodec != kUnknownAudioCodec) { |
| + expected_acodecs.push_back(acodec); |
| + continue; |
| } |
| - if (!audio_ || |
| - audio_->audio_decoder_config().codec() != audio_config.codec()) { |
| - media_log_->SetStringProperty("audio_codec_name", |
| - GetCodecName(audio_config.codec())); |
| + VideoCodec vcodec = StringToVideoCodec(codec_id); |
| + if (vcodec != kUnknownVideoCodec) { |
| + expected_vcodecs.push_back(vcodec); |
| + continue; |
| } |
| + MEDIA_LOG(INFO, media_log_) << "Unrecognized media codec: " << codec_id; |
| + } |
| - bool audio_stream_just_created = false; |
| - if (!audio_) { |
| - audio_ = create_demuxer_stream_cb_.Run(DemuxerStream::AUDIO); |
| + for (const auto& track : tracks->tracks()) { |
| + const auto& track_id = track->bytestream_track_id(); |
| - if (!audio_) { |
| - DVLOG(1) << "Failed to create an audio stream."; |
| - return false; |
| - } |
| - audio_stream_just_created = true; |
| + if (track->type() == MediaTrack::Audio) { |
| + AudioDecoderConfig audio_config = tracks->getAudioConfig(track_id); |
| + DVLOG(1) << "Audio track_id=" << track_id |
| + << " config: " << audio_config.AsHumanReadableString(); |
| + DCHECK(audio_config.IsValidConfig()); |
| - if (!frame_processor_->AddTrack(FrameProcessor::kAudioTrackId, audio_)) { |
| - DVLOG(1) << "Failed to add audio track to frame processor."; |
| + const auto& it = std::find(expected_acodecs.begin(), |
| + expected_acodecs.end(), audio_config.codec()); |
| + if (it == expected_acodecs.end()) { |
| + MEDIA_LOG(ERROR, media_log_) << "Audio stream codec " |
| + << GetCodecName(audio_config.codec()) |
| + << " doesn't match SourceBuffer codecs."; |
| return false; |
| } |
| - } |
| - |
| - frame_processor_->OnPossibleAudioConfigUpdate(audio_config); |
| - success &= audio_->UpdateAudioConfig(audio_config, media_log_); |
| - |
| - if (audio_stream_just_created) { |
| - std::string audio_buf_limit_switch = |
| - base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| - switches::kMSEAudioBufferSizeLimit); |
| - unsigned audio_buf_size_limit = 0; |
| - if (base::StringToUint(audio_buf_limit_switch, &audio_buf_size_limit) && |
| - audio_buf_size_limit > 0) { |
| - MEDIA_LOG(INFO, media_log_) << "Custom audio SourceBuffer size limit=" |
| - << audio_buf_size_limit; |
| - audio_->SetStreamMemoryLimit(audio_buf_size_limit); |
| + expected_acodecs.erase(it); |
|
wolenetz
2016/09/13 21:03:13
This is a problem for the following test I suggest
servolk
2016/09/14 18:15:26
Wait, shouldn't the mime type be 'audio/webm; code
wolenetz
2016/09/14 23:31:21
I've filed spec bug (MSE vNext) https://github.com
servolk
2016/09/15 00:18:32
Done.
|
| + |
| + ChunkDemuxerStream* stream = nullptr; |
| + if (!first_init_segment_received_) { |
| + DCHECK(audio_streams_.find(track_id) == audio_streams_.end()); |
| + stream = create_demuxer_stream_cb_.Run(DemuxerStream::AUDIO); |
| + if (!stream || !frame_processor_->AddTrack(track_id, stream)) { |
| + MEDIA_LOG(ERROR, media_log_) << "Failed to create audio stream."; |
| + return false; |
| + } |
| + audio_streams_[track_id] = stream; |
| + media_log_->SetBooleanProperty("found_audio_stream", true); |
| + media_log_->SetStringProperty("audio_codec_name", |
| + GetCodecName(audio_config.codec())); |
| + } else { |
| + if (audio_streams_.size() > 1) { |
| + stream = audio_streams_[track_id]; |
| + } else { |
| + // If there is only one video track then bytestream id might change in |
|
wolenetz
2016/09/13 21:03:14
nit:s/video/audio/
servolk
2016/09/14 18:15:26
Done.
|
| + // a new init segment. So update our state and nofity frame processor. |
| + const auto& it = audio_streams_.begin(); |
| + if (it != audio_streams_.end()) { |
| + stream = it->second; |
| + if (it->first != track_id) { |
| + frame_processor_->UpdateTrack(it->first, track_id); |
| + audio_streams_[track_id] = stream; |
| + audio_streams_.erase(it->first); |
| + } |
| + } |
| + } |
| + if (!stream) { |
| + MEDIA_LOG(ERROR, media_log_) << "Got unexpected audio track" |
| + << " track_id=" << track_id; |
| + return false; |
| + } |
| } |
| - } |
| - } |
| - |
| - if (video_config.IsValidConfig()) { |
| - if (!video_) { |
| - media_log_->SetBooleanProperty("found_video_stream", true); |
| - } |
| - if (!video_ || |
| - video_->video_decoder_config().codec() != video_config.codec()) { |
| - media_log_->SetStringProperty("video_codec_name", |
| - GetCodecName(video_config.codec())); |
| - } |
| - bool video_stream_just_created = false; |
| - if (!video_) { |
| - video_ = create_demuxer_stream_cb_.Run(DemuxerStream::VIDEO); |
| + track->set_id(stream->media_track_id()); |
| + frame_processor_->OnPossibleAudioConfigUpdate(audio_config); |
| + success &= stream->UpdateAudioConfig(audio_config, media_log_); |
| + } else if (track->type() == MediaTrack::Video) { |
| + VideoDecoderConfig video_config = tracks->getVideoConfig(track_id); |
| + DVLOG(1) << "Video track_id=" << track_id |
| + << " config: " << video_config.AsHumanReadableString(); |
| + DCHECK(video_config.IsValidConfig()); |
| - if (!video_) { |
| - DVLOG(1) << "Failed to create a video stream."; |
| + const auto& it = std::find(expected_vcodecs.begin(), |
| + expected_vcodecs.end(), video_config.codec()); |
| + if (it == expected_vcodecs.end()) { |
| + MEDIA_LOG(ERROR, media_log_) << "Video stream codec " |
| + << GetCodecName(video_config.codec()) |
| + << " doesn't match SourceBuffer codecs."; |
| return false; |
| } |
| - video_stream_just_created = true; |
| - |
| - if (!frame_processor_->AddTrack(FrameProcessor::kVideoTrackId, video_)) { |
| - DVLOG(1) << "Failed to add video track to frame processor."; |
| - return false; |
| + expected_vcodecs.erase(it); |
|
wolenetz
2016/09/13 21:03:13
ditto missing test and problem for multiple video
servolk
2016/09/14 18:15:26
Acknowledged.
|
| + |
| + ChunkDemuxerStream* stream = nullptr; |
| + if (!first_init_segment_received_) { |
| + DCHECK(video_streams_.find(track_id) == video_streams_.end()); |
| + stream = create_demuxer_stream_cb_.Run(DemuxerStream::VIDEO); |
| + if (!stream || !frame_processor_->AddTrack(track_id, stream)) { |
| + MEDIA_LOG(ERROR, media_log_) << "Failed to create video stream."; |
| + return false; |
| + } |
| + video_streams_[track_id] = stream; |
| + media_log_->SetBooleanProperty("found_video_stream", true); |
| + media_log_->SetStringProperty("video_codec_name", |
| + GetCodecName(video_config.codec())); |
| + } else { |
| + if (video_streams_.size() > 1) { |
| + stream = video_streams_[track_id]; |
| + } else { |
| + // If there is only one video track then bytestream id might change in |
| + // a new init segment. So update our state and nofity frame processor. |
| + const auto& it = video_streams_.begin(); |
| + if (it != video_streams_.end()) { |
| + stream = it->second; |
| + if (it->first != track_id) { |
| + frame_processor_->UpdateTrack(it->first, track_id); |
| + video_streams_[track_id] = stream; |
| + video_streams_.erase(it->first); |
| + } |
| + } |
| + } |
| + if (!stream) { |
| + MEDIA_LOG(ERROR, media_log_) << "Got unexpected video track" |
| + << " track_id=" << track_id; |
| + return false; |
| + } |
| } |
| + |
| + track->set_id(stream->media_track_id()); |
| + success &= stream->UpdateVideoConfig(video_config, media_log_); |
| + } else { |
| + MEDIA_LOG(ERROR, media_log_) << "Error: unsupported media track type " |
| + << track->type(); |
| + return false; |
| } |
| + } |
| - success &= video_->UpdateVideoConfig(video_config, media_log_); |
| - |
| - if (video_stream_just_created) { |
| - std::string video_buf_limit_switch = |
| - base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| - switches::kMSEVideoBufferSizeLimit); |
| - unsigned video_buf_size_limit = 0; |
| - if (base::StringToUint(video_buf_limit_switch, &video_buf_size_limit) && |
| - video_buf_size_limit > 0) { |
| - MEDIA_LOG(INFO, media_log_) << "Custom video SourceBuffer size limit=" |
| - << video_buf_size_limit; |
| - video_->SetStreamMemoryLimit(video_buf_size_limit); |
| - } |
| + if (!expected_acodecs.empty() || !expected_vcodecs.empty()) { |
| + for (const auto& acodec : expected_acodecs) { |
| + MEDIA_LOG(ERROR, media_log_) << "Initialization segment misses expected " |
| + << GetCodecName(acodec) << " track."; |
| } |
| + for (const auto& vcodec : expected_vcodecs) { |
| + MEDIA_LOG(ERROR, media_log_) << "Initialization segment misses expected " |
| + << GetCodecName(vcodec) << " track."; |
| + } |
| + return false; |
| } |
| typedef StreamParser::TextTrackConfigMap::const_iterator TextConfigItr; |
| @@ -725,15 +781,16 @@ bool MediaSourceState::OnNewConfigs( |
| } |
| } |
| + if (audio_streams_.empty() && video_streams_.empty()) { |
| + DVLOG(1) << __func__ << ": couldn't find a valid audio or video stream"; |
| + return false; |
| + } |
| + |
| frame_processor_->SetAllTrackBuffersNeedRandomAccessPoint(); |
| - if (audio_track) { |
| - DCHECK(audio_); |
| - audio_track->set_id(audio_->media_track_id()); |
| - } |
| - if (video_track) { |
| - DCHECK(video_); |
| - video_track->set_id(video_->media_track_id()); |
| + if (!first_init_segment_received_) { |
| + first_init_segment_received_ = true; |
| + SetStreamMemoryLimits(); |
| } |
| DVLOG(1) << "OnNewConfigs() : " << (success ? "success" : "failed"); |
| @@ -747,12 +804,39 @@ bool MediaSourceState::OnNewConfigs( |
| return success; |
| } |
| +void MediaSourceState::SetStreamMemoryLimits() { |
| + auto cmd_line = base::CommandLine::ForCurrentProcess(); |
| + |
| + std::string audio_buf_limit_switch = |
| + cmd_line->GetSwitchValueASCII(switches::kMSEAudioBufferSizeLimit); |
| + unsigned audio_buf_size_limit = 0; |
| + if (base::StringToUint(audio_buf_limit_switch, &audio_buf_size_limit) && |
| + audio_buf_size_limit > 0) { |
| + MEDIA_LOG(INFO, media_log_) << "Custom audio SourceBuffer size limit=" |
|
wolenetz
2016/09/13 21:03:14
nit: audio *per-track* SourceBuffer size limit=...
servolk
2016/09/14 18:15:26
Done.
|
| + << audio_buf_size_limit; |
| + for (const auto& it : audio_streams_) { |
| + it.second->SetStreamMemoryLimit(audio_buf_size_limit); |
| + } |
| + } |
| + |
| + std::string video_buf_limit_switch = |
| + cmd_line->GetSwitchValueASCII(switches::kMSEVideoBufferSizeLimit); |
| + unsigned video_buf_size_limit = 0; |
| + if (base::StringToUint(video_buf_limit_switch, &video_buf_size_limit) && |
| + video_buf_size_limit > 0) { |
| + MEDIA_LOG(INFO, media_log_) << "Custom video SourceBuffer size limit=" |
|
wolenetz
2016/09/13 21:03:14
nit ditto
servolk
2016/09/14 18:15:26
Done.
|
| + << video_buf_size_limit; |
| + for (const auto& it : video_streams_) { |
| + it.second->SetStreamMemoryLimit(video_buf_size_limit); |
| + } |
| + } |
| +} |
| + |
| void MediaSourceState::OnNewMediaSegment() { |
| DVLOG(2) << "OnNewMediaSegment()"; |
| DCHECK_EQ(state_, PARSER_INITIALIZED); |
| parsing_media_segment_ = true; |
| - media_segment_contained_audio_frame_ = false; |
| - media_segment_contained_video_frame_ = false; |
| + media_segment_has_data_for_track_.clear(); |
| } |
| void MediaSourceState::OnEndOfMediaSegment() { |
| @@ -760,24 +844,31 @@ void MediaSourceState::OnEndOfMediaSegment() { |
| DCHECK_EQ(state_, PARSER_INITIALIZED); |
| parsing_media_segment_ = false; |
| - const bool missing_audio = audio_ && !media_segment_contained_audio_frame_; |
| - const bool missing_video = video_ && !media_segment_contained_video_frame_; |
| - if (!missing_audio && !missing_video) |
| - return; |
| - |
| - LIMITED_MEDIA_LOG(DEBUG, media_log_, num_missing_track_logs_, |
| - kMaxMissingTrackInSegmentLogs) |
| - << "Media segment did not contain any " |
| - << (missing_audio && missing_video ? "audio or video" |
| - : missing_audio ? "audio" : "video") |
| - << " coded frames, mismatching initialization segment. Therefore, MSE " |
| - "coded frame processing may not interoperably detect discontinuities " |
| - "in appended media."; |
| + for (const auto& it : audio_streams_) { |
| + if (!media_segment_has_data_for_track_[it.first]) { |
| + LIMITED_MEDIA_LOG(DEBUG, media_log_, num_missing_track_logs_, |
| + kMaxMissingTrackInSegmentLogs) |
| + << "Media segment did not contain any coded frames for track " |
| + << it.first << ", mismatching initialization segment. Therefore, MSE" |
| + " coded frame processing may not interoperably detect" |
| + " discontinuities in appended media."; |
| + } |
| + } |
| + for (const auto& it : video_streams_) { |
| + if (!media_segment_has_data_for_track_[it.first]) { |
| + LIMITED_MEDIA_LOG(DEBUG, media_log_, num_missing_track_logs_, |
| + kMaxMissingTrackInSegmentLogs) |
| + << "Media segment did not contain any coded frames for track " |
| + << it.first << ", mismatching initialization segment. Therefore, MSE" |
| + " coded frame processing may not interoperably detect" |
| + " discontinuities in appended media."; |
| + } |
| + } |
| } |
| bool MediaSourceState::OnNewBuffers( |
| const StreamParser::BufferQueueMap& buffer_queue_map) { |
| - DVLOG(2) << "OnNewBuffers()"; |
| + DVLOG(2) << __func__ << " buffer_queues=" << buffer_queue_map.size(); |
| DCHECK_EQ(state_, PARSER_INITIALIZED); |
| DCHECK(timestamp_offset_during_append_); |
| DCHECK(parsing_media_segment_); |
| @@ -785,11 +876,7 @@ bool MediaSourceState::OnNewBuffers( |
| for (const auto& it : buffer_queue_map) { |
| const StreamParser::BufferQueue& bufq = it.second; |
| DCHECK(!bufq.empty()); |
| - if (bufq[0]->type() == DemuxerStream::AUDIO) { |
| - media_segment_contained_audio_frame_ = true; |
| - } else if (bufq[0]->type() == DemuxerStream::VIDEO) { |
| - media_segment_contained_video_frame_ = true; |
| - } |
| + media_segment_has_data_for_track_[it.first] = true; |
| } |
| const TimeDelta timestamp_offset_before_processing = |
| @@ -827,7 +914,6 @@ bool MediaSourceState::OnNewBuffers( |
| return true; |
| } |
| - |
| void MediaSourceState::OnSourceInitDone( |
| const StreamParser::InitParameters& params) { |
| DCHECK_EQ(state_, PENDING_PARSER_INIT); |