Commit b7389d11 authored by Chris Watkins's avatar Chris Watkins Committed by Commit Bot

media: Implement MediaCodecVideoDecoder Reset()

This CL implements Reset() which enables seeking to work as expected.
CodecWrapper also now takes a closure that it will call whenever an
output buffer is released back to the codec to signal that the codec
might now accept more input.

Bug: 660942
Cq-Include-Trybots: master.tryserver.chromium.android:android_optional_gpu_tests_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel
Change-Id: I2b91cffafc6ca4940a7bdc819491359f47b592f0
Reviewed-on: https://chromium-review.googlesource.com/597383
Commit-Queue: Chris Watkins <watk@chromium.org>
Reviewed-by: default avatarFrank Liberato <liberato@chromium.org>
Cr-Commit-Position: refs/heads/master@{#491919}
parent c1663ed8
...@@ -7,8 +7,10 @@ ...@@ -7,8 +7,10 @@
#include "media/base/encryption_scheme.h" #include "media/base/encryption_scheme.h"
#include "media/base/subsample_entry.h" #include "media/base/subsample_entry.h"
using ::testing::_; using ::testing::DoAll;
using ::testing::Return; using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::_;
namespace media { namespace media {
...@@ -21,4 +23,19 @@ MockMediaCodecBridge::MockMediaCodecBridge() { ...@@ -21,4 +23,19 @@ MockMediaCodecBridge::MockMediaCodecBridge() {
MockMediaCodecBridge::~MockMediaCodecBridge() {} MockMediaCodecBridge::~MockMediaCodecBridge() {}
void MockMediaCodecBridge::AcceptOneInput(IsEos eos) {
EXPECT_CALL(*this, DequeueInputBuffer(_, _))
.WillOnce(DoAll(SetArgPointee<1>(42), Return(MEDIA_CODEC_OK)))
.WillRepeatedly(Return(MEDIA_CODEC_TRY_AGAIN_LATER));
if (eos == kEos)
EXPECT_CALL(*this, QueueEOS(_));
}
void MockMediaCodecBridge::ProduceOneOutput(IsEos eos) {
EXPECT_CALL(*this, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(DoAll(SetArgPointee<5>(eos == kEos ? true : false),
Return(MEDIA_CODEC_OK)))
.WillRepeatedly(Return(MEDIA_CODEC_TRY_AGAIN_LATER));
}
} // namespace media } // namespace media
...@@ -18,6 +18,12 @@ class MockMediaCodecBridge : public MediaCodecBridge, ...@@ -18,6 +18,12 @@ class MockMediaCodecBridge : public MediaCodecBridge,
public: public:
MockMediaCodecBridge(); MockMediaCodecBridge();
~MockMediaCodecBridge() override; ~MockMediaCodecBridge() override;
// Helpers for conveniently setting expectations.
enum IsEos { kEos, kNotEos };
void AcceptOneInput(IsEos eos = kNotEos);
void ProduceOneOutput(IsEos eos = kNotEos);
MOCK_METHOD0(Stop, void()); MOCK_METHOD0(Stop, void());
MOCK_METHOD0(Flush, MediaCodecStatus()); MOCK_METHOD0(Flush, MediaCodecStatus());
MOCK_METHOD1(GetOutputSize, MediaCodecStatus(gfx::Size* size)); MOCK_METHOD1(GetOutputSize, MediaCodecStatus(gfx::Size* size));
......
...@@ -68,7 +68,8 @@ bool CodecImage::CopyTexImage(unsigned target) { ...@@ -68,7 +68,8 @@ bool CodecImage::CopyTexImage(unsigned target) {
if (bound_service_id != static_cast<GLint>(surface_texture_->GetTextureId())) if (bound_service_id != static_cast<GLint>(surface_texture_->GetTextureId()))
return false; return false;
return RenderToSurfaceTextureFrontBuffer(BindingsMode::kDontRestore); RenderToSurfaceTextureFrontBuffer(BindingsMode::kDontRestore);
return true;
} }
bool CodecImage::CopyTexSubImage(unsigned target, bool CodecImage::CopyTexSubImage(unsigned target,
......
...@@ -40,7 +40,8 @@ class CodecImageTest : public testing::Test { ...@@ -40,7 +40,8 @@ class CodecImageTest : public testing::Test {
void SetUp() override { void SetUp() override {
auto codec = base::MakeUnique<NiceMock<MockMediaCodecBridge>>(); auto codec = base::MakeUnique<NiceMock<MockMediaCodecBridge>>();
codec_ = codec.get(); codec_ = codec.get();
wrapper_ = base::MakeUnique<CodecWrapper>(std::move(codec)); wrapper_ = base::MakeUnique<CodecWrapper>(std::move(codec),
base::Bind(&base::DoNothing));
ON_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _)) ON_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillByDefault(Return(MEDIA_CODEC_OK)); .WillByDefault(Return(MEDIA_CODEC_OK));
......
This diff is collapsed.
...@@ -63,9 +63,13 @@ class MEDIA_GPU_EXPORT CodecOutputBuffer { ...@@ -63,9 +63,13 @@ class MEDIA_GPU_EXPORT CodecOutputBuffer {
// can be released on any thread. // can be released on any thread.
class MEDIA_GPU_EXPORT CodecWrapper { class MEDIA_GPU_EXPORT CodecWrapper {
public: public:
// |codec| should be in the state referred to by the MediaCodec docs as // |codec| should be in the flushed state, i.e., freshly configured or after a
// "Flushed", i.e., freshly configured or after a Flush() call. // Flush(). |output_buffer_release_cb| will be run whenever an output buffer
CodecWrapper(std::unique_ptr<MediaCodecBridge> codec); // is released back to the codec (whether it's rendered or not). This is a
// signal that the codec might be ready to accept more input. It may be run on
// any thread.
CodecWrapper(std::unique_ptr<MediaCodecBridge> codec,
base::Closure output_buffer_release_cb);
~CodecWrapper(); ~CodecWrapper();
// Takes the backing codec and discards all outstanding codec buffers. This // Takes the backing codec and discards all outstanding codec buffers. This
...@@ -73,6 +77,15 @@ class MEDIA_GPU_EXPORT CodecWrapper { ...@@ -73,6 +77,15 @@ class MEDIA_GPU_EXPORT CodecWrapper {
// referencing |this|. // referencing |this|.
std::unique_ptr<MediaCodecBridge> TakeCodec(); std::unique_ptr<MediaCodecBridge> TakeCodec();
// Whether the codec is in the flushed state.
bool IsFlushed() const;
// Whether an EOS has been queued but not yet dequeued.
bool IsDraining() const;
// Whether an EOS has been dequeued but the codec hasn't been flushed yet.
bool IsDrained() const;
// Whether there are any valid CodecOutputBuffers that have not been released. // Whether there are any valid CodecOutputBuffers that have not been released.
bool HasValidCodecOutputBuffers() const; bool HasValidCodecOutputBuffers() const;
...@@ -111,7 +124,10 @@ class MEDIA_GPU_EXPORT CodecWrapper { ...@@ -111,7 +124,10 @@ class MEDIA_GPU_EXPORT CodecWrapper {
base::TimeDelta* presentation_time, base::TimeDelta* presentation_time,
bool* end_of_stream, bool* end_of_stream,
std::unique_ptr<CodecOutputBuffer>* codec_buffer); std::unique_ptr<CodecOutputBuffer>* codec_buffer);
bool SetSurface(const base::android::JavaRef<jobject>& surface);
// Sets the given surface and returns MEDIA_CODEC_OK on success or
// MEDIA_CODEC_ERROR on failure.
MediaCodecStatus SetSurface(const base::android::JavaRef<jobject>& surface);
private: private:
scoped_refptr<CodecWrapperImpl> impl_; scoped_refptr<CodecWrapperImpl> impl_;
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop.h"
#include "base/test/mock_callback.h"
#include "media/base/android/media_codec_bridge.h" #include "media/base/android/media_codec_bridge.h"
#include "media/base/android/mock_media_codec_bridge.h" #include "media/base/android/mock_media_codec_bridge.h"
#include "media/base/encryption_scheme.h" #include "media/base/encryption_scheme.h"
...@@ -15,11 +16,11 @@ ...@@ -15,11 +16,11 @@
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
using testing::DoAll;
using testing::Invoke; using testing::Invoke;
using testing::NiceMock;
using testing::Return; using testing::Return;
using testing::DoAll;
using testing::SetArgPointee; using testing::SetArgPointee;
using testing::NiceMock;
using testing::_; using testing::_;
namespace media { namespace media {
...@@ -29,9 +30,12 @@ class CodecWrapperTest : public testing::Test { ...@@ -29,9 +30,12 @@ class CodecWrapperTest : public testing::Test {
CodecWrapperTest() { CodecWrapperTest() {
auto codec = base::MakeUnique<NiceMock<MockMediaCodecBridge>>(); auto codec = base::MakeUnique<NiceMock<MockMediaCodecBridge>>();
codec_ = codec.get(); codec_ = codec.get();
wrapper_ = base::MakeUnique<CodecWrapper>(std::move(codec)); wrapper_ = base::MakeUnique<CodecWrapper>(std::move(codec),
output_buffer_release_cb_.Get());
ON_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _)) ON_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillByDefault(Return(MEDIA_CODEC_OK)); .WillByDefault(Return(MEDIA_CODEC_OK));
ON_CALL(*codec_, QueueInputBuffer(_, _, _, _))
.WillByDefault(Return(MEDIA_CODEC_OK));
} }
~CodecWrapperTest() override { ~CodecWrapperTest() override {
...@@ -41,13 +45,15 @@ class CodecWrapperTest : public testing::Test { ...@@ -41,13 +45,15 @@ class CodecWrapperTest : public testing::Test {
std::unique_ptr<CodecOutputBuffer> DequeueCodecOutputBuffer() { std::unique_ptr<CodecOutputBuffer> DequeueCodecOutputBuffer() {
std::unique_ptr<CodecOutputBuffer> codec_buffer; std::unique_ptr<CodecOutputBuffer> codec_buffer;
wrapper_->DequeueOutputBuffer(base::TimeDelta(), nullptr, nullptr, bool eos = false;
wrapper_->DequeueOutputBuffer(base::TimeDelta(), nullptr, &eos,
&codec_buffer); &codec_buffer);
return codec_buffer; return codec_buffer;
} }
NiceMock<MockMediaCodecBridge>* codec_; NiceMock<MockMediaCodecBridge>* codec_;
std::unique_ptr<CodecWrapper> wrapper_; std::unique_ptr<CodecWrapper> wrapper_;
NiceMock<base::MockCallback<base::Closure>> output_buffer_release_cb_;
}; };
TEST_F(CodecWrapperTest, TakeCodecReturnsTheCodecFirstAndNullLater) { TEST_F(CodecWrapperTest, TakeCodecReturnsTheCodecFirstAndNullLater) {
...@@ -195,6 +201,52 @@ TEST_F(CodecWrapperTest, CodecOutputBuffersHaveTheCorrectSize) { ...@@ -195,6 +201,52 @@ TEST_F(CodecWrapperTest, CodecOutputBuffersHaveTheCorrectSize) {
ASSERT_EQ(codec_buffer->size(), gfx::Size(42, 42)); ASSERT_EQ(codec_buffer->size(), gfx::Size(42, 42));
} }
TEST_F(CodecWrapperTest, OutputBufferReleaseCbIsCalledWhenRendering) {
auto codec_buffer = DequeueCodecOutputBuffer();
EXPECT_CALL(output_buffer_release_cb_, Run()).Times(1);
codec_buffer->ReleaseToSurface();
}
TEST_F(CodecWrapperTest, OutputBufferReleaseCbIsCalledWhenDestructing) {
auto codec_buffer = DequeueCodecOutputBuffer();
EXPECT_CALL(output_buffer_release_cb_, Run()).Times(1);
}
TEST_F(CodecWrapperTest, CodecStartsInFlushedState) {
ASSERT_TRUE(wrapper_->IsFlushed());
ASSERT_FALSE(wrapper_->IsDraining());
ASSERT_FALSE(wrapper_->IsDrained());
}
TEST_F(CodecWrapperTest, CodecIsNotFlushedAfterAnInputIsQueued) {
wrapper_->QueueInputBuffer(0, nullptr, 0, base::TimeDelta());
ASSERT_FALSE(wrapper_->IsFlushed());
ASSERT_FALSE(wrapper_->IsDraining());
ASSERT_FALSE(wrapper_->IsDrained());
}
TEST_F(CodecWrapperTest, FlushReturnsCodecToFlushed) {
wrapper_->QueueInputBuffer(0, nullptr, 0, base::TimeDelta());
wrapper_->Flush();
ASSERT_TRUE(wrapper_->IsFlushed());
}
TEST_F(CodecWrapperTest, EosTransitionsToStateDraining) {
wrapper_->QueueInputBuffer(0, nullptr, 0, base::TimeDelta());
wrapper_->QueueEOS(0);
ASSERT_TRUE(wrapper_->IsDraining());
}
TEST_F(CodecWrapperTest, DequeuingEosTransitionsToStateDrained) {
// Set EOS on next dequeue.
codec_->ProduceOneOutput(MockMediaCodecBridge::kEos);
DequeueCodecOutputBuffer();
ASSERT_FALSE(wrapper_->IsFlushed());
ASSERT_TRUE(wrapper_->IsDrained());
wrapper_->Flush();
ASSERT_FALSE(wrapper_->IsDrained());
}
#if DCHECK_IS_ON() #if DCHECK_IS_ON()
TEST_F(CodecWrapperTest, CallsDcheckAfterTakingTheCodec) { TEST_F(CodecWrapperTest, CallsDcheckAfterTakingTheCodec) {
wrapper_->TakeCodec(); wrapper_->TakeCodec();
......
...@@ -86,6 +86,12 @@ void CodecAllocatorAdapter::OnCodecConfigured( ...@@ -86,6 +86,12 @@ void CodecAllocatorAdapter::OnCodecConfigured(
codec_created_cb.Run(std::move(media_codec)); codec_created_cb.Run(std::move(media_codec));
} }
// static
PendingDecode PendingDecode::CreateEos() {
auto nop = [](DecodeStatus s) {};
return {DecoderBuffer::CreateEOSBuffer(), base::Bind(nop)};
}
PendingDecode::PendingDecode(scoped_refptr<DecoderBuffer> buffer, PendingDecode::PendingDecode(scoped_refptr<DecoderBuffer> buffer,
VideoDecoder::DecodeCB decode_cb) VideoDecoder::DecodeCB decode_cb)
: buffer(buffer), decode_cb(decode_cb) {} : buffer(buffer), decode_cb(decode_cb) {}
...@@ -115,6 +121,9 @@ MediaCodecVideoDecoder::MediaCodecVideoDecoder( ...@@ -115,6 +121,9 @@ MediaCodecVideoDecoder::MediaCodecVideoDecoder(
MediaCodecVideoDecoder::~MediaCodecVideoDecoder() { MediaCodecVideoDecoder::~MediaCodecVideoDecoder() {
ReleaseCodec(); ReleaseCodec();
// Mojo callbacks require that they're run before destruction.
if (reset_cb_)
reset_cb_.Run();
codec_allocator_->StopThread(&codec_allocator_adapter_); codec_allocator_->StopThread(&codec_allocator_adapter_);
} }
...@@ -212,7 +221,7 @@ void MediaCodecVideoDecoder::OnSurfaceChosen( ...@@ -212,7 +221,7 @@ void MediaCodecVideoDecoder::OnSurfaceChosen(
codec_config_->surface_bundle = codec_config_->surface_bundle =
overlay ? new AVDASurfaceBundle(std::move(overlay)) overlay ? new AVDASurfaceBundle(std::move(overlay))
: new AVDASurfaceBundle(surface_texture_); : new AVDASurfaceBundle(surface_texture_);
StartCodecCreation(); CreateCodec();
return; return;
} }
...@@ -260,8 +269,8 @@ void MediaCodecVideoDecoder::OnSurfaceDestroyed(AndroidOverlay* overlay) { ...@@ -260,8 +269,8 @@ void MediaCodecVideoDecoder::OnSurfaceDestroyed(AndroidOverlay* overlay) {
if (!device_info_->IsSetOutputSurfaceSupported()) { if (!device_info_->IsSetOutputSurfaceSupported()) {
state_ = State::kSurfaceDestroyed; state_ = State::kSurfaceDestroyed;
ReleaseCodecAndBundle(); ReleaseCodecAndBundle();
// TODO(watk): If we're draining, signal completion now because the drain if (drain_type_)
// can no longer proceed. OnCodecDrained();
return; return;
} }
...@@ -277,7 +286,7 @@ void MediaCodecVideoDecoder::TransitionToIncomingSurface() { ...@@ -277,7 +286,7 @@ void MediaCodecVideoDecoder::TransitionToIncomingSurface() {
DCHECK(codec_); DCHECK(codec_);
auto surface_bundle = std::move(*incoming_surface_); auto surface_bundle = std::move(*incoming_surface_);
incoming_surface_.reset(); incoming_surface_.reset();
if (codec_->SetSurface(surface_bundle->GetJavaSurface())) { if (codec_->SetSurface(surface_bundle->GetJavaSurface()) == MEDIA_CODEC_OK) {
codec_config_->surface_bundle = std::move(surface_bundle); codec_config_->surface_bundle = std::move(surface_bundle);
} else { } else {
ReleaseCodecAndBundle(); ReleaseCodecAndBundle();
...@@ -285,9 +294,8 @@ void MediaCodecVideoDecoder::TransitionToIncomingSurface() { ...@@ -285,9 +294,8 @@ void MediaCodecVideoDecoder::TransitionToIncomingSurface() {
} }
} }
void MediaCodecVideoDecoder::StartCodecCreation() { void MediaCodecVideoDecoder::CreateCodec() {
DCHECK(!codec_); DCHECK(!codec_);
DCHECK(state_ == State::kBeforeSurfaceInit);
state_ = State::kWaitingForCodec; state_ = State::kWaitingForCodec;
codec_allocator_adapter_.codec_created_cb = base::Bind( codec_allocator_adapter_.codec_created_cb = base::Bind(
&MediaCodecVideoDecoder::OnCodecCreated, weak_factory_.GetWeakPtr()); &MediaCodecVideoDecoder::OnCodecCreated, weak_factory_.GetWeakPtr());
...@@ -300,8 +308,12 @@ void MediaCodecVideoDecoder::OnCodecCreated( ...@@ -300,8 +308,12 @@ void MediaCodecVideoDecoder::OnCodecCreated(
DCHECK(state_ == State::kWaitingForCodec || DCHECK(state_ == State::kWaitingForCodec ||
state_ == State::kSurfaceDestroyed); state_ == State::kSurfaceDestroyed);
DCHECK(!codec_); DCHECK(!codec_);
if (codec) if (codec) {
codec_ = base::MakeUnique<CodecWrapper>(std::move(codec)); codec_ = base::MakeUnique<CodecWrapper>(
std::move(codec),
BindToCurrentLoop(base::Bind(&MediaCodecVideoDecoder::ManageTimer,
weak_factory_.GetWeakPtr(), true)));
}
// If we entered state kSurfaceDestroyed while we were waiting for // If we entered state kSurfaceDestroyed while we were waiting for
// the codec, then it's already invalid and we have to drop it. // the codec, then it's already invalid and we have to drop it.
...@@ -324,6 +336,11 @@ void MediaCodecVideoDecoder::OnCodecCreated( ...@@ -324,6 +336,11 @@ void MediaCodecVideoDecoder::OnCodecCreated(
void MediaCodecVideoDecoder::Decode(const scoped_refptr<DecoderBuffer>& buffer, void MediaCodecVideoDecoder::Decode(const scoped_refptr<DecoderBuffer>& buffer,
const DecodeCB& decode_cb) { const DecodeCB& decode_cb) {
DVLOG(2) << __func__; DVLOG(2) << __func__;
if (state_ == State::kError) {
decode_cb.Run(DecodeStatus::DECODE_ERROR);
return;
}
pending_decodes_.emplace_back(buffer, std::move(decode_cb)); pending_decodes_.emplace_back(buffer, std::move(decode_cb));
if (lazy_init_pending_) { if (lazy_init_pending_) {
...@@ -332,17 +349,37 @@ void MediaCodecVideoDecoder::Decode(const scoped_refptr<DecoderBuffer>& buffer, ...@@ -332,17 +349,37 @@ void MediaCodecVideoDecoder::Decode(const scoped_refptr<DecoderBuffer>& buffer,
return; return;
} }
// If the codec is drained we need to flush it before continuing.
if (codec_ && codec_->IsDrained())
FlushCodec();
PumpCodec(true); PumpCodec(true);
} }
void MediaCodecVideoDecoder::PumpCodec(bool force_start_timer) { void MediaCodecVideoDecoder::FlushCodec() {
DVLOG(4) << __func__; DVLOG(2) << __func__;
if (state_ == State::kError || state_ == State::kWaitingForCodec || if (!codec_ || codec_->IsFlushed())
state_ == State::kSurfaceDestroyed ||
state_ == State::kBeforeSurfaceInit) {
return; return;
if (codec_->SupportsFlush(device_info_)) {
DVLOG(2) << "Flushing codec";
if (!codec_->Flush()) {
HandleError();
return;
}
} else {
DVLOG(2) << "flush() workaround: creating a new codec";
// Release the codec and create a new one with the same surface bundle.
// TODO(watk): We should guarantee that the new codec is created after the
// current one is released so they aren't attached to the same surface at
// the same time. This doesn't usually happen because the release and
// creation usually post to the same thread, but it's not guaranteed.
ReleaseCodec();
CreateCodec();
} }
}
void MediaCodecVideoDecoder::PumpCodec(bool force_start_timer) {
DVLOG(4) << __func__;
bool did_work = false, did_input = false, did_output = false; bool did_work = false, did_input = false, did_output = false;
do { do {
did_input = QueueInput(); did_input = QueueInput();
...@@ -374,7 +411,8 @@ void MediaCodecVideoDecoder::ManageTimer(bool start_timer) { ...@@ -374,7 +411,8 @@ void MediaCodecVideoDecoder::ManageTimer(bool start_timer) {
bool MediaCodecVideoDecoder::QueueInput() { bool MediaCodecVideoDecoder::QueueInput() {
DVLOG(4) << __func__; DVLOG(4) << __func__;
if (state_ == State::kError || state_ == State::kWaitingForCodec || if (state_ == State::kError || state_ == State::kWaitingForCodec ||
state_ == State::kWaitingForKey || state_ == State::kBeforeSurfaceInit) { state_ == State::kWaitingForKey || state_ == State::kBeforeSurfaceInit ||
state_ == State::kSurfaceDestroyed) {
return false; return false;
} }
if (pending_decodes_.empty()) if (pending_decodes_.empty())
...@@ -390,17 +428,15 @@ bool MediaCodecVideoDecoder::QueueInput() { ...@@ -390,17 +428,15 @@ bool MediaCodecVideoDecoder::QueueInput() {
} else if (status == MEDIA_CODEC_TRY_AGAIN_LATER) { } else if (status == MEDIA_CODEC_TRY_AGAIN_LATER) {
return false; return false;
} }
DCHECK(status == MEDIA_CODEC_OK); DCHECK(status == MEDIA_CODEC_OK);
DCHECK_GE(input_buffer, 0); DCHECK_GE(input_buffer, 0);
PendingDecode pending_decode = std::move(pending_decodes_.front()); PendingDecode pending_decode = std::move(pending_decodes_.front());
pending_decodes_.pop_front(); pending_decodes_.pop_front();
if (pending_decode.buffer->end_of_stream()) { if (pending_decode.buffer->end_of_stream()) {
codec_->QueueEOS(input_buffer); codec_->QueueEOS(input_buffer);
pending_decode.decode_cb.Run(DecodeStatus::OK); eos_decode_cb_ = std::move(pending_decode.decode_cb);
// TODO(watk): Make CodecWrapper track this.
drain_type_ = DrainType::kFlush;
return true; return true;
} }
...@@ -426,7 +462,8 @@ bool MediaCodecVideoDecoder::QueueInput() { ...@@ -426,7 +462,8 @@ bool MediaCodecVideoDecoder::QueueInput() {
bool MediaCodecVideoDecoder::DequeueOutput() { bool MediaCodecVideoDecoder::DequeueOutput() {
DVLOG(4) << __func__; DVLOG(4) << __func__;
if (state_ == State::kError || state_ == State::kWaitingForCodec || if (state_ == State::kError || state_ == State::kWaitingForCodec ||
state_ == State::kBeforeSurfaceInit) { state_ == State::kWaitingForKey || state_ == State::kBeforeSurfaceInit ||
state_ == State::kSurfaceDestroyed) {
return false; return false;
} }
...@@ -448,7 +485,8 @@ bool MediaCodecVideoDecoder::DequeueOutput() { ...@@ -448,7 +485,8 @@ bool MediaCodecVideoDecoder::DequeueOutput() {
if (status == MEDIA_CODEC_ERROR) { if (status == MEDIA_CODEC_ERROR) {
DVLOG(1) << "DequeueOutputBuffer() error"; DVLOG(1) << "DequeueOutputBuffer() error";
HandleError(); HandleError();
// TODO(watk): Complete the pending drain. if (drain_type_)
OnCodecDrained();
return false; return false;
} else if (status == MEDIA_CODEC_TRY_AGAIN_LATER) { } else if (status == MEDIA_CODEC_TRY_AGAIN_LATER) {
return false; return false;
...@@ -459,14 +497,27 @@ bool MediaCodecVideoDecoder::DequeueOutput() { ...@@ -459,14 +497,27 @@ bool MediaCodecVideoDecoder::DequeueOutput() {
<< " size=" << output_buffer->size().ToString() << " eos=" << eos; << " size=" << output_buffer->size().ToString() << " eos=" << eos;
if (eos) { if (eos) {
// TODO(watk): Complete the pending drain. if (eos_decode_cb_) {
// Post the EOS decode cb through the gpu task runner to ensure it follows
// all previous outputs.
gpu_task_runner_->PostTaskAndReply(
FROM_HERE, base::Bind(&base::DoNothing),
base::Bind(eos_decode_cb_, DecodeStatus::OK));
eos_decode_cb_.Reset();
}
if (drain_type_)
OnCodecDrained();
// The codec still needs flushing if we're not doing an explicit drain, but
// we can't do it right now without unbacking unrendered frames near EOS.
// Instead the codec will be flushed the next time Decode() is called and it
// sees that the codec is drained.
return false; return false;
} }
if (drain_type_ == DrainType::kReset || drain_type_ == DrainType::kDestroy) { // If we're draining for reset or destroy we can discard |output_buffer|
// Returning here discards |output_buffer| without rendering it. // without rendering it.
if (drain_type_)
return true; return true;
}
video_frame_factory_->CreateVideoFrame( video_frame_factory_->CreateVideoFrame(
std::move(output_buffer), surface_texture_, presentation_time, std::move(output_buffer), surface_texture_, presentation_time,
...@@ -476,15 +527,63 @@ bool MediaCodecVideoDecoder::DequeueOutput() { ...@@ -476,15 +527,63 @@ bool MediaCodecVideoDecoder::DequeueOutput() {
void MediaCodecVideoDecoder::Reset(const base::Closure& closure) { void MediaCodecVideoDecoder::Reset(const base::Closure& closure) {
DVLOG(2) << __func__; DVLOG(2) << __func__;
NOTIMPLEMENTED(); ClearPendingDecodes(DecodeStatus::ABORTED);
if (!codec_) {
closure.Run();
return;
}
reset_cb_ = std::move(closure);
StartDrainingCodec(DrainType::kForReset);
}
void MediaCodecVideoDecoder::StartDrainingCodec(DrainType drain_type) {
DVLOG(2) << __func__;
DCHECK(pending_decodes_.empty());
// It's okay if there's already a drain ongoing. We'll only enqueue an EOS if
// the codec isn't already draining.
drain_type_ = drain_type;
// Skip the drain if possible. Only VP8 codecs need draining because
// they can hang in release() or flush() otherwise
// (http://crbug.com/598963).
if (!codec_ || decoder_config_.codec() != kCodecVP8 || codec_->IsFlushed() ||
codec_->IsDrained()) {
OnCodecDrained();
return;
}
// Queue EOS if the codec isn't already processing one.
if (!codec_->IsDraining())
pending_decodes_.push_back(PendingDecode::CreateEos());
}
void MediaCodecVideoDecoder::OnCodecDrained() {
DVLOG(2) << __func__;
if (drain_type_ == DrainType::kForDestroy) {
// TODO(watk): Delete |this|.
return;
}
DCHECK(drain_type_ == DrainType::kForReset);
base::ResetAndReturn(&reset_cb_).Run();
if (state_ == State::kSurfaceDestroyed || state_ == State::kError)
return;
FlushCodec();
drain_type_.reset();
} }
void MediaCodecVideoDecoder::HandleError() { void MediaCodecVideoDecoder::HandleError() {
DVLOG(2) << __func__; DVLOG(2) << __func__;
state_ = State::kError; state_ = State::kError;
ClearPendingDecodes(DecodeStatus::DECODE_ERROR);
}
void MediaCodecVideoDecoder::ClearPendingDecodes(DecodeStatus status) {
for (auto& pending_decode : pending_decodes_) for (auto& pending_decode : pending_decodes_)
pending_decode.decode_cb.Run(DecodeStatus::DECODE_ERROR); pending_decode.decode_cb.Run(status);
pending_decodes_.clear(); pending_decodes_.clear();
if (eos_decode_cb_)
base::ResetAndReturn(&eos_decode_cb_).Run(status);
} }
void MediaCodecVideoDecoder::ReleaseCodec() { void MediaCodecVideoDecoder::ReleaseCodec() {
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
namespace media { namespace media {
struct PendingDecode { struct PendingDecode {
static PendingDecode CreateEos();
PendingDecode(scoped_refptr<DecoderBuffer> buffer, PendingDecode(scoped_refptr<DecoderBuffer> buffer,
VideoDecoder::DecodeCB decode_cb); VideoDecoder::DecodeCB decode_cb);
PendingDecode(PendingDecode&& other); PendingDecode(PendingDecode&& other);
...@@ -82,6 +83,9 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder : public VideoDecoder { ...@@ -82,6 +83,9 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder : public VideoDecoder {
void SetOverlayInfo(const OverlayInfo& overlay_info); void SetOverlayInfo(const OverlayInfo& overlay_info);
private: private:
// The test has access for PumpCodec().
friend class MediaCodecVideoDecoderTest;
enum class State { enum class State {
kOk, kOk,
kError, kError,
...@@ -98,9 +102,8 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder : public VideoDecoder { ...@@ -98,9 +102,8 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder : public VideoDecoder {
}; };
enum class DrainType { enum class DrainType {
kFlush, kForReset,
kReset, kForDestroy,
kDestroy,
}; };
// Finishes initialization. // Finishes initialization.
...@@ -116,13 +119,26 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder : public VideoDecoder { ...@@ -116,13 +119,26 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder : public VideoDecoder {
// Sets |codecs_|'s output surface to |incoming_surface_|. Releases the codec // Sets |codecs_|'s output surface to |incoming_surface_|. Releases the codec
// and both the current and incoming bundles on failure. // and both the current and incoming bundles on failure.
void TransitionToIncomingSurface(); void TransitionToIncomingSurface();
void StartCodecCreation(); void CreateCodec();
void OnCodecCreated(std::unique_ptr<MediaCodecBridge> codec); void OnCodecCreated(std::unique_ptr<MediaCodecBridge> codec);
// Flushes the codec, or if flush() is not supported, releases it and creates
// a new one.
void FlushCodec();
// Attempts to queue input and dequeue output from the codec. If
// |force_start_timer| is true the timer idle timeout is reset.
void PumpCodec(bool force_start_timer); void PumpCodec(bool force_start_timer);
void ManageTimer(bool start_timer);
bool QueueInput(); bool QueueInput();
bool DequeueOutput(); bool DequeueOutput();
void ManageTimer(bool start_timer);
// Starts draining the codec by queuing an EOS if required. It skips the drain
// if possible.
void StartDrainingCodec(DrainType drain_type);
void OnCodecDrained();
void ClearPendingDecodes(DecodeStatus status);
// Sets |state_| and runs pending callbacks. // Sets |state_| and runs pending callbacks.
void HandleError(); void HandleError();
...@@ -137,12 +153,22 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder : public VideoDecoder { ...@@ -137,12 +153,22 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder : public VideoDecoder {
AndroidOverlayFactoryCB CreateOverlayFactoryCb(); AndroidOverlayFactoryCB CreateOverlayFactoryCb();
State state_; State state_;
// Whether initialization still needs to be done on the first decode call.
bool lazy_init_pending_; bool lazy_init_pending_;
std::deque<PendingDecode> pending_decodes_; std::deque<PendingDecode> pending_decodes_;
VideoFrameFactory::OutputWithReleaseMailboxCB output_cb_;
// The ongoing drain operation, if any. // The reason for the current drain operation if any.
base::Optional<DrainType> drain_type_; base::Optional<DrainType> drain_type_;
// The current reset cb if a Reset() is in progress.
base::Closure reset_cb_;
// The EOS decode cb for an EOS currently being processed by the codec. Called
// when the EOS is output.
VideoDecoder::DecodeCB eos_decode_cb_;
VideoFrameFactory::OutputWithReleaseMailboxCB output_cb_;
VideoDecoderConfig decoder_config_; VideoDecoderConfig decoder_config_;
// The surface bundle that we're transitioning to, if any. // The surface bundle that we're transitioning to, if any.
...@@ -177,12 +203,12 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder : public VideoDecoder { ...@@ -177,12 +203,12 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder : public VideoDecoder {
// codec with. // codec with.
std::unique_ptr<AndroidVideoSurfaceChooser> surface_chooser_; std::unique_ptr<AndroidVideoSurfaceChooser> surface_chooser_;
// The factory for creating VideoFrames from CodecOutputBuffers.
std::unique_ptr<VideoFrameFactory> video_frame_factory_;
// Current state for the chooser. // Current state for the chooser.
AndroidVideoSurfaceChooser::State chooser_state_; AndroidVideoSurfaceChooser::State chooser_state_;
// The factory for creating VideoFrames from CodecOutputBuffers.
std::unique_ptr<VideoFrameFactory> video_frame_factory_;
// An optional factory callback for creating mojo AndroidOverlays. // An optional factory callback for creating mojo AndroidOverlays.
AndroidOverlayMojoFactoryCB overlay_factory_cb_; AndroidOverlayMojoFactoryCB overlay_factory_cb_;
......
...@@ -74,6 +74,8 @@ class MediaCodecVideoDecoderTest : public testing::Test { ...@@ -74,6 +74,8 @@ class MediaCodecVideoDecoderTest : public testing::Test {
JNIEnv* env = base::android::AttachCurrentThread(); JNIEnv* env = base::android::AttachCurrentThread();
RegisterJni(env); RegisterJni(env);
uint8_t data = 0;
fake_decoder_buffer_ = DecoderBuffer::CopyFrom(&data, 1);
codec_allocator_ = base::MakeUnique<FakeCodecAllocator>(); codec_allocator_ = base::MakeUnique<FakeCodecAllocator>();
device_info_ = base::MakeUnique<NiceMock<MockDeviceInfo>>(); device_info_ = base::MakeUnique<NiceMock<MockDeviceInfo>>();
auto surface_chooser = base::MakeUnique<NiceMock<FakeSurfaceChooser>>(); auto surface_chooser = base::MakeUnique<NiceMock<FakeSurfaceChooser>>();
...@@ -110,9 +112,10 @@ class MediaCodecVideoDecoderTest : public testing::Test { ...@@ -110,9 +112,10 @@ class MediaCodecVideoDecoderTest : public testing::Test {
// Call Initialize() and Decode() to start lazy init. MCVD will be waiting for // Call Initialize() and Decode() to start lazy init. MCVD will be waiting for
// a codec. // a codec.
MockAndroidOverlay* InitializeWithOverlay() { MockAndroidOverlay* InitializeWithOverlay(
Initialize(TestVideoConfig::NormalH264()); VideoDecoderConfig config = TestVideoConfig::Large(kCodecVP8)) {
mcvd_->Decode(nullptr, decode_cb_.Get()); Initialize(config);
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
auto overlay_ptr = base::MakeUnique<MockAndroidOverlay>(); auto overlay_ptr = base::MakeUnique<MockAndroidOverlay>();
auto* overlay = overlay_ptr.get(); auto* overlay = overlay_ptr.get();
surface_chooser_->ProvideOverlay(std::move(overlay_ptr)); surface_chooser_->ProvideOverlay(std::move(overlay_ptr));
...@@ -121,23 +124,36 @@ class MediaCodecVideoDecoderTest : public testing::Test { ...@@ -121,23 +124,36 @@ class MediaCodecVideoDecoderTest : public testing::Test {
// Call Initialize() and Decode() to start lazy init. MCVD will be waiting for // Call Initialize() and Decode() to start lazy init. MCVD will be waiting for
// a codec. // a codec.
void InitializeWithSurfaceTexture() { void InitializeWithSurfaceTexture(
Initialize(TestVideoConfig::NormalH264()); VideoDecoderConfig config = TestVideoConfig::Large(kCodecVP8)) {
mcvd_->Decode(nullptr, decode_cb_.Get()); Initialize(config);
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
surface_chooser_->ProvideSurfaceTexture(); surface_chooser_->ProvideSurfaceTexture();
} }
// Fully initializes MCVD and returns the codec it's configured with.
MockMediaCodecBridge* InitializeFully(
VideoDecoderConfig config = TestVideoConfig::Large(kCodecVP8)) {
InitializeWithSurfaceTexture(config);
return codec_allocator_->ProvideMockCodecAsync();
}
// Provide access to MCVD's private PumpCodec() to avoid trying to make its
// repeating task run in the test environment.
void PumpCodec() { mcvd_->PumpCodec(false); }
protected: protected:
base::test::ScopedTaskEnvironment scoped_task_environment_;
scoped_refptr<DecoderBuffer> fake_decoder_buffer_;
std::unique_ptr<MockDeviceInfo> device_info_; std::unique_ptr<MockDeviceInfo> device_info_;
std::unique_ptr<FakeCodecAllocator> codec_allocator_; std::unique_ptr<FakeCodecAllocator> codec_allocator_;
FakeSurfaceChooser* surface_chooser_; FakeSurfaceChooser* surface_chooser_;
MockSurfaceTextureGLOwner* surface_texture_; MockSurfaceTextureGLOwner* surface_texture_;
MockVideoFrameFactory* video_frame_factory_; MockVideoFrameFactory* video_frame_factory_;
base::MockCallback<VideoDecoder::DecodeCB> decode_cb_; NiceMock<base::MockCallback<VideoDecoder::DecodeCB>> decode_cb_;
// mcvd_ is last so it's destructed after its dependencies. // mcvd_ is last so it's destructed first.
std::unique_ptr<MediaCodecVideoDecoder> mcvd_; std::unique_ptr<MediaCodecVideoDecoder> mcvd_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
}; };
TEST_F(MediaCodecVideoDecoderTest, DestructBeforeInitWorks) { TEST_F(MediaCodecVideoDecoderTest, DestructBeforeInitWorks) {
...@@ -322,11 +338,10 @@ TEST_F(MediaCodecVideoDecoderTest, PumpCodecPerformsPendingSurfaceTransitions) { ...@@ -322,11 +338,10 @@ TEST_F(MediaCodecVideoDecoderTest, PumpCodecPerformsPendingSurfaceTransitions) {
InitializeWithOverlay(); InitializeWithOverlay();
auto* codec = codec_allocator_->ProvideMockCodecAsync(); auto* codec = codec_allocator_->ProvideMockCodecAsync();
// Set a pending surface transition and then call Decode() (which calls // Set a pending surface transition and then call PumpCodec().
// PumpCodec()).
surface_chooser_->ProvideSurfaceTexture(); surface_chooser_->ProvideSurfaceTexture();
EXPECT_CALL(*codec, SetSurface(_)).WillOnce(Return(true)); EXPECT_CALL(*codec, SetSurface(_)).WillOnce(Return(true));
mcvd_->Decode(nullptr, decode_cb_.Get()); PumpCodec();
} }
TEST_F(MediaCodecVideoDecoderTest, TEST_F(MediaCodecVideoDecoderTest,
...@@ -383,4 +398,97 @@ TEST_F(MediaCodecVideoDecoderTest, ...@@ -383,4 +398,97 @@ TEST_F(MediaCodecVideoDecoderTest,
mcvd_->Decode(nullptr, decode_cb_.Get()); mcvd_->Decode(nullptr, decode_cb_.Get());
} }
TEST_F(MediaCodecVideoDecoderTest,
ResetBeforeCodecInitializedSucceedsImmediately) {
InitializeWithSurfaceTexture();
base::MockCallback<base::Closure> reset_cb;
EXPECT_CALL(reset_cb, Run());
mcvd_->Reset(reset_cb.Get());
}
TEST_F(MediaCodecVideoDecoderTest, ResetAbortsPendingDecodes) {
InitializeWithSurfaceTexture();
// One decode is now pending.
EXPECT_CALL(decode_cb_, Run(DecodeStatus::ABORTED));
mcvd_->Reset(base::Bind(&base::DoNothing));
}
TEST_F(MediaCodecVideoDecoderTest, ResetAbortsPendingEosDecode) {
// EOS is treated differently by MCVD. This verifies that it's also aborted.
auto* codec = InitializeFully();
base::MockCallback<VideoDecoder::DecodeCB> eos_decode_cb;
mcvd_->Decode(DecoderBuffer::CreateEOSBuffer(), eos_decode_cb.Get());
// Accept the two pending decodes.
codec->AcceptOneInput();
PumpCodec();
codec->AcceptOneInput(MockMediaCodecBridge::kEos);
PumpCodec();
EXPECT_CALL(eos_decode_cb, Run(DecodeStatus::ABORTED));
mcvd_->Reset(base::Bind(&base::DoNothing));
}
TEST_F(MediaCodecVideoDecoderTest, ResetDoesNotFlushAnAlreadyFlushedCodec) {
auto* codec = InitializeFully();
// The codec is still in the flushed state so Reset() doesn't need to flush.
EXPECT_CALL(*codec, Flush()).Times(0);
base::MockCallback<base::Closure> reset_cb;
EXPECT_CALL(reset_cb, Run());
mcvd_->Reset(reset_cb.Get());
}
TEST_F(MediaCodecVideoDecoderTest, ResetDrainsVP8CodecsBeforeFlushing) {
auto* codec = InitializeFully();
// Accept the first decode to transition out of the flushed state.
codec->AcceptOneInput();
PumpCodec();
// The reset should not complete immediately because the codec needs to be
// drained.
EXPECT_CALL(*codec, Flush()).Times(0);
base::MockCallback<base::Closure> reset_cb;
EXPECT_CALL(reset_cb, Run()).Times(0);
mcvd_->Reset(reset_cb.Get());
// The next input should be an EOS.
codec->AcceptOneInput(MockMediaCodecBridge::kEos);
PumpCodec();
testing::Mock::VerifyAndClearExpectations(codec);
// After the EOS is dequeued, the reset should complete.
EXPECT_CALL(reset_cb, Run());
codec->ProduceOneOutput(MockMediaCodecBridge::kEos);
PumpCodec();
testing::Mock::VerifyAndClearExpectations(&reset_cb);
}
TEST_F(MediaCodecVideoDecoderTest, ResetDoesNotDrainNonVp8Codecs) {
auto* codec = InitializeFully(TestVideoConfig::NormalH264());
// Accept the first decode to transition out of the flushed state.
codec->AcceptOneInput();
PumpCodec();
// The reset should complete immediately because the codec is not VP8 so
// it doesn't need draining.
EXPECT_CALL(*codec, Flush());
base::MockCallback<base::Closure> reset_cb;
EXPECT_CALL(reset_cb, Run());
mcvd_->Reset(reset_cb.Get());
}
TEST_F(MediaCodecVideoDecoderTest, DestructionCompletesPendingReset) {
auto* codec = InitializeFully();
// Accept the first decode to transition out of the flushed state.
codec->AcceptOneInput();
PumpCodec();
base::MockCallback<base::Closure> reset_cb;
EXPECT_CALL(reset_cb, Run()).Times(0);
mcvd_->Reset(reset_cb.Get());
EXPECT_CALL(reset_cb, Run());
mcvd_.reset();
}
} // namespace media } // namespace media
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment