| /* |
| * Copyright 2014 Google Inc. All Rights Reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "media/filters/shell_video_decoder_impl.h" |
| |
| #include <limits.h> // for ULLONG_MAX |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/debug/trace_event.h" |
| #include "base/logging.h" |
| #include "build/build_config.h" // Must come before OS_STARBOARD. |
| #include "media/base/bind_to_loop.h" |
| #include "media/base/pipeline_status.h" |
| #include "media/base/shell_buffer_factory.h" |
| #include "media/base/shell_media_statistics.h" |
| #include "media/base/video_decoder_config.h" |
| #include "media/base/video_frame.h" |
| |
| #if defined(OS_STARBOARD) |
| #include "starboard/configuration.h" |
| #endif // defined(OS_STARBOARD) |
| |
| namespace media { |
| |
| //============================================================================== |
| // ShellVideoDecoderImpl |
| // |
| |
| ShellVideoDecoderImpl::ShellVideoDecoderImpl( |
| const scoped_refptr<base::MessageLoopProxy>& message_loop, |
| ShellRawVideoDecoderFactory* raw_video_decoder_factory) |
| : state_(kUninitialized), |
| media_pipeline_message_loop_(message_loop), |
| raw_video_decoder_factory_(raw_video_decoder_factory), |
| decoder_thread_("Video Decoder") { |
| DCHECK(raw_video_decoder_factory_); |
| } |
| |
| ShellVideoDecoderImpl::~ShellVideoDecoderImpl() {} |
| |
| void ShellVideoDecoderImpl::Initialize( |
| const scoped_refptr<DemuxerStream>& stream, |
| const PipelineStatusCB& status_cb, |
| const StatisticsCB& statistics_cb) { |
| TRACE_EVENT0("media_stack", "ShellVideoDecoderImpl::Initialize()"); |
| DCHECK(!decoder_thread_.IsRunning()); |
| DCHECK(media_pipeline_message_loop_->BelongsToCurrentThread()); |
| // check for no already attached stream, valid input stream, and save it |
| DCHECK(!demuxer_stream_); |
| |
| if (!stream) { |
| status_cb.Run(PIPELINE_ERROR_DECODE); |
| return; |
| } |
| demuxer_stream_ = stream; |
| |
| VideoDecoderConfig decoder_config; |
| decoder_config.CopyFrom(demuxer_stream_->video_decoder_config()); |
| LOG(INFO) << "Configuration at Start: " |
| << decoder_config.AsHumanReadableString(); |
| |
| raw_decoder_ = raw_video_decoder_factory_->Create( |
| decoder_config, demuxer_stream_->GetDecryptor(), |
| demuxer_stream_->StreamWasEncrypted()); |
| |
| if (!raw_decoder_) { |
| status_cb.Run(DECODER_ERROR_NOT_SUPPORTED); |
| return; |
| } |
| |
| UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_CODEC, decoder_config.codec()); |
| UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_WIDTH, |
| decoder_config.natural_size().width()); |
| UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_HEIGHT, |
| decoder_config.natural_size().height()); |
| |
| base::Thread::Options options; |
| |
| #if defined(OS_STARBOARD) && defined(SB_MEDIA_THREAD_STACK_SIZE) |
| options.stack_size = SB_MEDIA_THREAD_STACK_SIZE; |
| #endif // defined(OS_STARBOARD) && defined(SB_MEDIA_THREAD_STACK_SIZE) |
| |
| if (decoder_thread_.StartWithOptions(options)) { |
| state_ = kNormal; |
| status_cb.Run(PIPELINE_OK); |
| } else { |
| status_cb.Run(PIPELINE_ERROR_DECODE); |
| } |
| } |
| |
| void ShellVideoDecoderImpl::Read(const ReadCB& read_cb) { |
| TRACE_EVENT0("media_stack", "ShellVideoDecoderImpl::DoRead()"); |
| if (!decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()) { |
| decoder_thread_.message_loop_proxy()->PostTask( |
| FROM_HERE, base::Bind(&ShellVideoDecoderImpl::Read, this, read_cb)); |
| return; |
| } |
| |
| DCHECK(!read_cb.is_null()); |
| DCHECK(read_cb_.is_null()) << "overlapping reads not supported"; |
| DCHECK_NE(state_, kUninitialized); |
| |
| read_cb_ = BindToLoop(media_pipeline_message_loop_, read_cb); |
| |
| // if an error has occurred, return error |
| if (state_ == kShellDecodeError) { |
| base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL); |
| return; |
| } |
| |
| // if decoding is done return empty frame |
| if (state_ == kDecodeFinished) { |
| base::ResetAndReturn(&read_cb_).Run(kOk, VideoFrame::CreateEmptyFrame()); |
| return; |
| } |
| |
| if (buffer_to_decode_) { |
| DecodeBuffer(buffer_to_decode_); |
| } else if (state_ == kFlushCodec) { |
| DecodeBuffer(eof_buffer_); |
| } else { |
| ReadFromDemuxerStream(); // Kick off a read |
| } |
| } |
| |
| void ShellVideoDecoderImpl::ReadFromDemuxerStream() { |
| DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
| DCHECK_NE(state_, kDecodeFinished); |
| |
| media_pipeline_message_loop_->PostTask( |
| FROM_HERE, base::Bind(&DemuxerStream::Read, demuxer_stream_, |
| BindToCurrentLoop(base::Bind( |
| &ShellVideoDecoderImpl::BufferReady, this)))); |
| } |
| |
| void ShellVideoDecoderImpl::BufferReady( |
| DemuxerStream::Status demuxer_status, |
| const scoped_refptr<DecoderBuffer>& buffer) { |
| DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
| // must either be Ok and have a buffer or not Ok and no buffer |
| DCHECK_EQ(demuxer_status != DemuxerStream::kOk, !buffer) << demuxer_status; |
| |
| if (state_ == kUninitialized) { |
| // Stop has been called before BufferReady is posted. read_cb_ should be |
| // called and cleared inside Stop(). |
| DCHECK(read_cb_.is_null()); |
| return; |
| } |
| |
| if (state_ == kShellDecodeError) { |
| DLOG(WARNING) << "read returned but decoder is in error state"; |
| return; |
| } |
| |
| // if we deferred reset based on a pending read, process that reset now |
| // after returning an empty frame |
| if (!reset_cb_.is_null()) { |
| DoReset(); |
| return; |
| } |
| |
| if (demuxer_status == DemuxerStream::kConfigChanged) { |
| VideoDecoderConfig decoder_config; |
| decoder_config.CopyFrom(demuxer_stream_->video_decoder_config()); |
| LOG(INFO) << "Configuration Changed: " |
| << decoder_config.AsHumanReadableString(); |
| // One side effect of asking for the video configuration is that |
| // the MediaSource demuxer stack uses that request to determine |
| // that the video decoder has updated its configuration. |
| // We therefore must ask for the updated video configuration |
| // before requesting any data, or the MediaSource stack will |
| // assert. |
| if (!raw_decoder_->UpdateConfig(decoder_config)) { |
| DLOG(ERROR) << "Error Reconfig H264 decoder"; |
| DecoderFatalError(); |
| return; |
| } |
| |
| UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_CODEC, decoder_config.codec()); |
| UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_WIDTH, |
| decoder_config.natural_size().width()); |
| UPDATE_MEDIA_STATISTICS(STAT_TYPE_VIDEO_HEIGHT, |
| decoder_config.natural_size().height()); |
| |
| ReadFromDemuxerStream(); |
| return; |
| } |
| |
| // if stream is aborted service this and any pending reads with |
| // empty frames |
| if (demuxer_status == DemuxerStream::kAborted) { |
| if (!read_cb_.is_null()) { |
| base::ResetAndReturn(&read_cb_).Run(kOk, NULL); |
| } |
| return; |
| } |
| |
| DCHECK(buffer); |
| if (!buffer) { |
| DecoderFatalError(); |
| return; |
| } |
| |
| // at this point demuxerstream state should be OK |
| DCHECK_EQ(demuxer_status, DemuxerStream::kOk); |
| |
| // Decode this one |
| DecodeBuffer(buffer); |
| } |
| |
| void ShellVideoDecoderImpl::DecodeBuffer( |
| const scoped_refptr<DecoderBuffer>& buffer) { |
| TRACE_EVENT0("media_stack", "ShellVideoDecoderImpl::DecodeBuffer()"); |
| SCOPED_MEDIA_STATISTICS(STAT_TYPE_VIDEO_FRAME_DECODE); |
| |
| DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
| DCHECK_NE(state_, kUninitialized); |
| DCHECK_NE(state_, kDecodeFinished); |
| DCHECK(buffer); |
| if (buffer_to_decode_) |
| DCHECK_EQ(buffer_to_decode_, buffer); |
| |
| buffer_to_decode_ = buffer; |
| |
| // if we deferred reset based on a pending read, process that reset now |
| // after returning an empty frame |
| if (!reset_cb_.is_null()) { |
| DoReset(); |
| return; |
| } |
| |
| // if we've encountered an EOS buffer then attempt no more reads from upstream |
| // empty queue and prepare to transition to kDecodeFinished. |
| if (buffer->IsEndOfStream()) { |
| TRACE_EVENT0("media_stack", |
| "ShellVideoDecoderImpl::DecodeBuffer() EOS received"); |
| eof_buffer_ = buffer; |
| // We pipeline reads, so it is possible that we will receive more than one |
| // read callback with EOS after the first |
| if (state_ == kNormal) { |
| state_ = kFlushCodec; |
| } |
| } |
| |
| ShellRawVideoDecoder::DecodeCB decode_cb = |
| base::Bind(&ShellVideoDecoderImpl::DecodeCallback, this); |
| raw_decoder_->Decode(buffer, BindToCurrentLoop(decode_cb)); |
| } |
| |
| void ShellVideoDecoderImpl::DecodeCallback( |
| ShellRawVideoDecoder::DecodeStatus status, |
| const scoped_refptr<VideoFrame>& frame) { |
| DCHECK(buffer_to_decode_); |
| |
| if (!reset_cb_.is_null()) { |
| DoReset(); |
| return; |
| } |
| |
| if (status == ShellRawVideoDecoder::RETRY_WITH_SAME_BUFFER) { |
| if (frame) { |
| base::ResetAndReturn(&read_cb_).Run(kOk, frame); |
| } else { |
| decoder_thread_.message_loop_proxy()->PostTask( |
| FROM_HERE, base::Bind(&ShellVideoDecoderImpl::DecodeBuffer, this, |
| buffer_to_decode_)); |
| } |
| return; |
| } |
| |
| buffer_to_decode_ = NULL; |
| |
| if (status == ShellRawVideoDecoder::FRAME_DECODED) { |
| TRACE_EVENT1("media_stack", "ShellVideoDecoderImpl frame decoded", |
| "timestamp", frame->GetTimestamp().InMicroseconds()); |
| DCHECK(frame); |
| base::ResetAndReturn(&read_cb_).Run(kOk, frame); |
| return; |
| } |
| |
| if (status == ShellRawVideoDecoder::NEED_MORE_DATA) { |
| DCHECK(!frame); |
| if (state_ == kFlushCodec) { |
| state_ = kDecodeFinished; |
| base::ResetAndReturn(&read_cb_).Run(kOk, VideoFrame::CreateEmptyFrame()); |
| return; |
| } |
| |
| ReadFromDemuxerStream(); |
| return; |
| } |
| |
| if (status == ShellRawVideoDecoder::FATAL_ERROR) { |
| DecoderFatalError(); |
| return; |
| } |
| |
| NOTREACHED(); |
| } |
| |
| void ShellVideoDecoderImpl::Reset(const base::Closure& closure) { |
| TRACE_EVENT0("media_stack", "ShellVideoDecoderImpl::Reset()"); |
| if (!decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()) { |
| decoder_thread_.message_loop_proxy()->PostTask( |
| FROM_HERE, base::Bind(&ShellVideoDecoderImpl::Reset, this, closure)); |
| return; |
| } |
| |
| reset_cb_ = BindToLoop(media_pipeline_message_loop_, closure); |
| |
| // Defer the reset if a read is pending. |
| if (!read_cb_.is_null()) |
| return; |
| |
| DoReset(); |
| } |
| |
| void ShellVideoDecoderImpl::DoReset() { |
| DCHECK(!reset_cb_.is_null()); |
| DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
| |
| // service any pending read call with a NULL buffer |
| if (!read_cb_.is_null()) { |
| base::ResetAndReturn(&read_cb_).Run(kOk, NULL); |
| } |
| |
| if (!raw_decoder_->Flush()) { |
| DLOG(ERROR) << "Error Flush Decoder"; |
| DecoderFatalError(); |
| } |
| |
| if (state_ != kShellDecodeError) |
| state_ = kNormal; |
| eof_buffer_ = NULL; |
| buffer_to_decode_ = NULL; |
| |
| base::ResetAndReturn(&reset_cb_).Run(); |
| } |
| |
| void ShellVideoDecoderImpl::DecoderFatalError() { |
| TRACE_EVENT0("media_stack", "ShellVideoDecoderImpl::DecoderFatalError()"); |
| DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
| // fatal error within the decoder |
| DLOG(ERROR) << "fatal video decoder error."; |
| // service any read callbacks with error |
| if (!read_cb_.is_null()) { |
| base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL); |
| } |
| // terminate playback |
| state_ = kShellDecodeError; |
| } |
| |
| void ShellVideoDecoderImpl::Stop(const base::Closure& closure) { |
| DCHECK(media_pipeline_message_loop_->BelongsToCurrentThread()); |
| |
| decoder_thread_.Stop(); |
| raw_decoder_.reset(NULL); |
| |
| // terminate playback |
| state_ = kUninitialized; |
| |
| if (!read_cb_.is_null()) |
| base::ResetAndReturn(&read_cb_).Run(kOk, NULL); |
| |
| closure.Run(); |
| } |
| |
| void ShellVideoDecoderImpl::NearlyUnderflow() { |
| DCHECK(media_pipeline_message_loop_->BelongsToCurrentThread()); |
| if (raw_decoder_) { |
| raw_decoder_->NearlyUnderflow(); |
| } |
| } |
| |
| void ShellVideoDecoderImpl::HaveEnoughFrames() { |
| DCHECK(media_pipeline_message_loop_->BelongsToCurrentThread()); |
| if (raw_decoder_) { |
| raw_decoder_->HaveEnoughFrames(); |
| } |
| } |
| |
| } // namespace media |