| // Copyright 2020 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/formats/webcodecs/webcodecs_encoded_chunk_stream_parser.h" |
| |
| #include <string> |
| |
| #include "base/callback.h" |
| #include "base/logging.h" |
| #include "base/notreached.h" |
| #include "media/base/media_log.h" |
| #include "media/base/media_track.h" |
| #include "media/base/media_tracks.h" |
| #include "media/base/stream_parser_buffer.h" |
| #include "media/base/text_track_config.h" |
| #include "media/base/timestamp_constants.h" |
| |
| namespace { |
| |
| // TODO(crbug.com/1144908): Since these must be identical to those generated |
| // in the SourceBuffer, consider moving these to possibly stream_parser.h. |
| // Meanwhile, must be kept in sync with similar constexpr in SourceBuffer |
| // manually. |
| constexpr media::StreamParser::TrackId kWebCodecsAudioTrackId = 1; |
| constexpr media::StreamParser::TrackId kWebCodecsVideoTrackId = 2; |
| |
| } // namespace |
| |
| namespace media { |
| |
| WebCodecsEncodedChunkStreamParser::WebCodecsEncodedChunkStreamParser( |
| std::unique_ptr<AudioDecoderConfig> audio_config) |
| : state_(kWaitingForInit), audio_config_(std::move(audio_config)) { |
| DCHECK(audio_config_ && !video_config_); |
| } |
| |
| WebCodecsEncodedChunkStreamParser::WebCodecsEncodedChunkStreamParser( |
| std::unique_ptr<VideoDecoderConfig> video_config) |
| : state_(kWaitingForInit), video_config_(std::move(video_config)) { |
| DCHECK(video_config_ && !audio_config_); |
| } |
| |
| WebCodecsEncodedChunkStreamParser::~WebCodecsEncodedChunkStreamParser() = |
| default; |
| |
| void WebCodecsEncodedChunkStreamParser::Init( |
| InitCB init_cb, |
| const NewConfigCB& config_cb, |
| const NewBuffersCB& new_buffers_cb, |
| bool /* ignore_text_tracks */, |
| const EncryptedMediaInitDataCB& /* ignored */, |
| const NewMediaSegmentCB& new_segment_cb, |
| const EndMediaSegmentCB& end_of_segment_cb, |
| MediaLog* media_log) { |
| DCHECK_EQ(state_, kWaitingForInit); |
| DCHECK(!init_cb_); |
| DCHECK(init_cb); |
| DCHECK(config_cb); |
| DCHECK(new_buffers_cb); |
| DCHECK(new_segment_cb); |
| DCHECK(end_of_segment_cb); |
| |
| ChangeState(kWaitingForConfigEmission); |
| init_cb_ = std::move(init_cb); |
| config_cb_ = config_cb; |
| new_buffers_cb_ = new_buffers_cb; |
| new_segment_cb_ = new_segment_cb; |
| end_of_segment_cb_ = end_of_segment_cb; |
| media_log_ = media_log; |
| } |
| |
| void WebCodecsEncodedChunkStreamParser::Flush() { |
| DCHECK_NE(state_, kWaitingForInit); |
| if (state_ == kWaitingForEncodedChunks) |
| ChangeState(kWaitingForConfigEmission); |
| } |
| |
| bool WebCodecsEncodedChunkStreamParser::GetGenerateTimestampsFlag() const { |
| return false; |
| } |
| |
| bool WebCodecsEncodedChunkStreamParser::Parse(const uint8_t* /* buf */, |
| int /* size */) { |
| // TODO(crbug.com/1144908): Protect against app reaching this (and similer |
| // inverse case in other parsers) simply by using the wrong append method on |
| // the SourceBuffer. Maybe a better MEDIA_LOG here would be sufficient? Or |
| // instead have the top-level SourceBuffer throw synchronous exception when |
| // attempting the wrong append method, without causing parse/decode error? |
| NOTREACHED(); // ProcessChunks() is the method to use instead for this |
| // parser. |
| return false; |
| } |
| |
| bool WebCodecsEncodedChunkStreamParser::ProcessChunks( |
| std::unique_ptr<BufferQueue> buffer_queue) { |
| DCHECK_NE(state_, kWaitingForInit); |
| |
| if (state_ == kError) |
| return false; |
| |
| if (state_ == kWaitingForConfigEmission) { |
| // Must (still) have only one config. We'll retain ownership. |
| // MediaTracks::AddAudio/VideoTrack copies the config. |
| DCHECK((audio_config_ && !video_config_) || |
| (video_config_ && !audio_config_)); |
| auto media_tracks = std::make_unique<MediaTracks>(); |
| if (audio_config_) { |
| media_tracks->AddAudioTrack( |
| *audio_config_, kWebCodecsAudioTrackId, MediaTrack::Kind("main"), |
| MediaTrack::Label(""), MediaTrack::Language("")); |
| } else if (video_config_) { |
| media_tracks->AddVideoTrack( |
| *video_config_, kWebCodecsVideoTrackId, MediaTrack::Kind("main"), |
| MediaTrack::Label(""), MediaTrack::Language("")); |
| } |
| |
| if (!config_cb_.Run(std::move(media_tracks), TextTrackConfigMap())) { |
| ChangeState(kError); |
| return false; |
| } |
| |
| if (init_cb_) { |
| InitParameters params(kInfiniteDuration); |
| params.liveness = DemuxerStream::LIVENESS_UNKNOWN; |
| if (audio_config_) |
| params.detected_audio_track_count = 1; |
| if (video_config_) |
| params.detected_video_track_count = 1; |
| params.detected_text_track_count = 0; |
| std::move(init_cb_).Run(params); |
| } |
| |
| ChangeState(kWaitingForEncodedChunks); |
| } |
| |
| DCHECK_EQ(state_, kWaitingForEncodedChunks); |
| |
| // All of |buffer_queue| must be of the media type (audio or video) |
| // corresponding to the exactly one type of decoder config we have. Otherwise, |
| // the caller has provided encoded chunks for the wrong kind of config. |
| DemuxerStream::Type expected_type = |
| audio_config_ ? DemuxerStream::AUDIO : DemuxerStream::VIDEO; |
| for (const auto& it : *buffer_queue) { |
| if (it->type() != expected_type) { |
| MEDIA_LOG(ERROR, media_log_) |
| << "Incorrect EncodedChunk type (audio vs video) appended"; |
| ChangeState(kError); |
| return false; |
| } |
| } |
| |
| // TODO(crbug.com/1144908): Add a different new_buffers_cb type for us to use |
| // so that we can just std::move the buffer_queue, and avoid potential issues |
| // with out-of-order timestamps in the caller-provided queue that would |
| // otherwise cause parse failure in MergeBufferQueues with the current, legacy |
| // style of new_buffers_cb that depends on parsers to emit sanely time-ordered |
| // groups of frames from *muxed* multi-track bytestreams. FrameProcessor is |
| // capable of handling our buffer_queue verbatim. |
| BufferQueueMap buffers; |
| if (audio_config_) |
| buffers.insert(std::make_pair(kWebCodecsAudioTrackId, *buffer_queue)); |
| else |
| buffers.insert(std::make_pair(kWebCodecsVideoTrackId, *buffer_queue)); |
| new_segment_cb_.Run(); |
| if (!new_buffers_cb_.Run(buffers)) |
| return false; |
| end_of_segment_cb_.Run(); |
| |
| return true; |
| } |
| |
| void WebCodecsEncodedChunkStreamParser::ChangeState(State new_state) { |
| DVLOG(1) << __func__ << ": " << state_ << " -> " << new_state; |
| state_ = new_state; |
| } |
| |
| } // namespace media |