Commit 4a732e3b authored by Chris Watkins's avatar Chris Watkins Committed by Commit Bot

media: Add CodecWrapper for threadsafe MediaCodec access

MediaCodecVideoDecoder needs to decode using a MediaCodec on the MCVD
thread while dequeued buffers are released back to the codec on the GPU
thread. CodecWrapper supports this use case by vending CodecOutputBuffers
which keep a reference to their backing wrapper, and a unique id so that
CodecWrapper can keep track of which buffers are valid.

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: I798df44a7639071f9eb3f2803d018ced4b121137
Reviewed-on: https://chromium-review.googlesource.com/517351Reviewed-by: default avatarFrank Liberato <liberato@chromium.org>
Commit-Queue: Chris Watkins <watk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#475740}
parent 075f218d
......@@ -217,6 +217,8 @@ component("gpu") {
if (enable_media_codec_video_decoder) {
assert(mojo_media_host == "gpu", "MCVD requires the CDM")
sources += [
"android/codec_wrapper.cc",
"android/codec_wrapper.h",
"android/media_codec_video_decoder.cc",
"android/media_codec_video_decoder.h",
]
......@@ -460,7 +462,10 @@ source_set("android_video_decode_accelerator_unittests") {
"surface_texture_gl_owner_unittest.cc",
]
if (enable_media_codec_video_decoder) {
sources += [ "android/media_codec_video_decoder_unittest.cc" ]
sources += [
"android/codec_wrapper_unittest.cc",
"android/media_codec_video_decoder_unittest.cc",
]
}
deps = [
":gpu",
......
This diff is collapsed.
// Copyright 2017 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.
#ifndef MEDIA_GPU_ANDROID_CODEC_WRAPPER_H_
#define MEDIA_GPU_ANDROID_CODEC_WRAPPER_H_
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <string>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/memory/ref_counted.h"
#include "base/synchronization/lock.h"
#include "media/base/android/media_codec_bridge.h"
#include "media/gpu/media_gpu_export.h"
#include "media/gpu/surface_texture_gl_owner.h"
namespace media {
class CodecWrapperImpl;
// A MediaCodec output buffer that can be released on any thread.
class MEDIA_GPU_EXPORT CodecOutputBuffer {
public:
// Releases the buffer without rendering it.
~CodecOutputBuffer();
// Releases this buffer and renders it to the surface.
bool ReleaseToSurface();
// The size of the image.
gfx::Size size() const { return size_; }
private:
// Let CodecWrapperImpl call the constructor.
friend class CodecWrapperImpl;
CodecOutputBuffer(scoped_refptr<CodecWrapperImpl> codec,
int64_t id,
gfx::Size size);
scoped_refptr<CodecWrapperImpl> codec_;
int64_t id_;
gfx::Size size_;
DISALLOW_COPY_AND_ASSIGN(CodecOutputBuffer);
};
// This wraps a MediaCodecBridge and provides a pared down version of its
// interface. It also adds the following features:
// * It outputs CodecOutputBuffers from DequeueOutputBuffer() which can be
// safely rendered on any thread, and that will release their buffers on
// destruction. This lets us decode on one thread while rendering on another.
// * It maintains codec specific state like whether an error has occurred.
//
// CodecWrapper is not threadsafe, but the CodecOutputBuffers it outputs
// can be released on any thread.
class MEDIA_GPU_EXPORT CodecWrapper {
public:
// |codec| should be in the state referred to by the MediaCodec docs as
// "Flushed", i.e., freshly configured or after a Flush() call.
CodecWrapper(std::unique_ptr<MediaCodecBridge> codec);
~CodecWrapper();
// Takes the backing codec and discards all outstanding codec buffers. This
// lets you tear down the codec while there are still CodecOutputBuffers
// referencing |this|.
std::unique_ptr<MediaCodecBridge> TakeCodec();
// Whether there are any valid CodecOutputBuffers that have not been released.
bool HasValidCodecOutputBuffers() const;
// Releases currently dequeued codec buffers back to the codec without
// rendering.
void DiscardCodecOutputBuffers();
// Whether the codec supports Flush().
bool SupportsFlush() const;
// See MediaCodecBridge documentation for the following.
bool Flush();
MediaCodecStatus QueueInputBuffer(int index,
const uint8_t* data,
size_t data_size,
base::TimeDelta presentation_time);
MediaCodecStatus QueueSecureInputBuffer(
int index,
const uint8_t* data,
size_t data_size,
const std::string& key_id,
const std::string& iv,
const std::vector<SubsampleEntry>& subsamples,
const EncryptionScheme& encryption_scheme,
base::TimeDelta presentation_time);
void QueueEOS(int input_buffer_index);
MediaCodecStatus DequeueInputBuffer(base::TimeDelta timeout, int* index);
// Like MediaCodecBridge::DequeueOutputBuffer() but it outputs a
// CodecOutputBuffer instead of an index. And it's guaranteed to not return
// either of MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED or
// MEDIA_CODEC_OUTPUT_FORMAT_CHANGED. It will try to dequeue another
// buffer instead. |*codec_buffer| must be null.
MediaCodecStatus DequeueOutputBuffer(
base::TimeDelta timeout,
base::TimeDelta* presentation_time,
bool* end_of_stream,
std::unique_ptr<CodecOutputBuffer>* codec_buffer);
bool SetSurface(jobject surface);
private:
scoped_refptr<CodecWrapperImpl> impl_;
DISALLOW_COPY_AND_ASSIGN(CodecWrapper);
};
} // namespace media
#endif // MEDIA_GPU_ANDROID_CODEC_WRAPPER_H_
// Copyright 2017 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/gpu/android/codec_wrapper.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "media/base/android/media_codec_bridge.h"
#include "media/base/android/mock_media_codec_bridge.h"
#include "media/base/encryption_scheme.h"
#include "media/base/subsample_entry.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::Invoke;
using testing::Return;
using testing::DoAll;
using testing::SetArgPointee;
using testing::NiceMock;
using testing::_;
namespace media {
class CodecWrapperTest : public testing::Test {
public:
CodecWrapperTest() {
auto codec = base::MakeUnique<NiceMock<MockMediaCodecBridge>>();
codec_ = codec.get();
wrapper_ = base::MakeUnique<CodecWrapper>(std::move(codec));
ON_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillByDefault(Return(MEDIA_CODEC_OK));
}
~CodecWrapperTest() override {
// ~CodecWrapper asserts that the codec was taken.
wrapper_->TakeCodec();
}
std::unique_ptr<CodecOutputBuffer> DequeueCodecOutputBuffer() {
std::unique_ptr<CodecOutputBuffer> codec_buffer;
wrapper_->DequeueOutputBuffer(base::TimeDelta(), nullptr, nullptr,
&codec_buffer);
return codec_buffer;
}
NiceMock<MockMediaCodecBridge>* codec_;
std::unique_ptr<CodecWrapper> wrapper_;
};
TEST_F(CodecWrapperTest, TakeCodecReturnsTheCodecFirstAndNullLater) {
ASSERT_EQ(wrapper_->TakeCodec().get(), codec_);
ASSERT_EQ(wrapper_->TakeCodec(), nullptr);
}
TEST_F(CodecWrapperTest, NoCodecOutputBufferReturnedIfDequeueFails) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MEDIA_CODEC_ERROR));
auto codec_buffer = DequeueCodecOutputBuffer();
ASSERT_EQ(codec_buffer, nullptr);
}
TEST_F(CodecWrapperTest, InitiallyThereAreNoValidCodecOutputBuffers) {
ASSERT_FALSE(wrapper_->HasValidCodecOutputBuffers());
}
TEST_F(CodecWrapperTest, FlushInvalidatesCodecOutputBuffers) {
auto codec_buffer = DequeueCodecOutputBuffer();
wrapper_->Flush();
ASSERT_FALSE(codec_buffer->ReleaseToSurface());
}
TEST_F(CodecWrapperTest, TakingTheCodecInvalidatesCodecOutputBuffers) {
auto codec_buffer = DequeueCodecOutputBuffer();
wrapper_->TakeCodec();
ASSERT_FALSE(codec_buffer->ReleaseToSurface());
}
TEST_F(CodecWrapperTest, SetSurfaceInvalidatesCodecOutputBuffers) {
auto codec_buffer = DequeueCodecOutputBuffer();
wrapper_->SetSurface(0);
ASSERT_FALSE(codec_buffer->ReleaseToSurface());
}
TEST_F(CodecWrapperTest, CodecOutputBuffersAreAllInvalidatedTogether) {
auto codec_buffer1 = DequeueCodecOutputBuffer();
auto codec_buffer2 = DequeueCodecOutputBuffer();
wrapper_->Flush();
ASSERT_FALSE(codec_buffer1->ReleaseToSurface());
ASSERT_FALSE(codec_buffer2->ReleaseToSurface());
ASSERT_FALSE(wrapper_->HasValidCodecOutputBuffers());
}
TEST_F(CodecWrapperTest, CodecOutputBuffersAfterFlushAreValid) {
auto codec_buffer = DequeueCodecOutputBuffer();
wrapper_->Flush();
codec_buffer = DequeueCodecOutputBuffer();
ASSERT_TRUE(codec_buffer->ReleaseToSurface());
}
TEST_F(CodecWrapperTest, CodecOutputBufferReleaseUsesCorrectIndex) {
// The second arg is the buffer index pointer.
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(DoAll(SetArgPointee<1>(42), Return(MEDIA_CODEC_OK)));
auto codec_buffer = DequeueCodecOutputBuffer();
EXPECT_CALL(*codec_, ReleaseOutputBuffer(42, true));
codec_buffer->ReleaseToSurface();
}
TEST_F(CodecWrapperTest, CodecOutputBuffersAreInvalidatedByRelease) {
auto codec_buffer = DequeueCodecOutputBuffer();
codec_buffer->ReleaseToSurface();
ASSERT_FALSE(codec_buffer->ReleaseToSurface());
}
TEST_F(CodecWrapperTest, CodecOutputBuffersReleaseOnDestruction) {
auto codec_buffer = DequeueCodecOutputBuffer();
EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, false));
codec_buffer = nullptr;
}
TEST_F(CodecWrapperTest, CodecOutputBuffersDoNotReleaseIfAlreadyReleased) {
auto codec_buffer = DequeueCodecOutputBuffer();
codec_buffer->ReleaseToSurface();
EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, _)).Times(0);
codec_buffer = nullptr;
}
TEST_F(CodecWrapperTest, ReleasingCodecOutputBuffersAfterTheCodecIsSafe) {
auto codec_buffer = DequeueCodecOutputBuffer();
wrapper_->TakeCodec();
codec_buffer->ReleaseToSurface();
}
TEST_F(CodecWrapperTest, DeletingCodecOutputBuffersAfterTheCodecIsSafe) {
auto codec_buffer = DequeueCodecOutputBuffer();
wrapper_->TakeCodec();
// This test ensures the destructor doesn't crash.
codec_buffer = nullptr;
}
TEST_F(CodecWrapperTest, CodecOutputBufferReleaseDoesNotInvalidateOthers) {
auto codec_buffer1 = DequeueCodecOutputBuffer();
auto codec_buffer2 = DequeueCodecOutputBuffer();
codec_buffer1->ReleaseToSurface();
ASSERT_TRUE(codec_buffer2->ReleaseToSurface());
}
TEST_F(CodecWrapperTest, FormatChangedStatusIsSwallowed) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MEDIA_CODEC_OUTPUT_FORMAT_CHANGED))
.WillOnce(Return(MEDIA_CODEC_TRY_AGAIN_LATER));
std::unique_ptr<CodecOutputBuffer> codec_buffer;
auto status = wrapper_->DequeueOutputBuffer(base::TimeDelta(), nullptr,
nullptr, &codec_buffer);
ASSERT_EQ(status, MEDIA_CODEC_TRY_AGAIN_LATER);
}
TEST_F(CodecWrapperTest, BuffersChangedStatusIsSwallowed) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED))
.WillOnce(Return(MEDIA_CODEC_TRY_AGAIN_LATER));
std::unique_ptr<CodecOutputBuffer> codec_buffer;
auto status = wrapper_->DequeueOutputBuffer(base::TimeDelta(), nullptr,
nullptr, &codec_buffer);
ASSERT_EQ(status, MEDIA_CODEC_TRY_AGAIN_LATER);
}
TEST_F(CodecWrapperTest, MultipleFormatChangedStatusesIsAnError) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillRepeatedly(Return(MEDIA_CODEC_OUTPUT_FORMAT_CHANGED));
std::unique_ptr<CodecOutputBuffer> codec_buffer;
auto status = wrapper_->DequeueOutputBuffer(base::TimeDelta(), nullptr,
nullptr, &codec_buffer);
ASSERT_EQ(status, MEDIA_CODEC_ERROR);
}
TEST_F(CodecWrapperTest, CodecOutputBuffersHaveTheCorrectSize) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MEDIA_CODEC_OUTPUT_FORMAT_CHANGED))
.WillOnce(Return(MEDIA_CODEC_OK));
EXPECT_CALL(*codec_, GetOutputSize(_))
.WillOnce(
DoAll(SetArgPointee<0>(gfx::Size(42, 42)), Return(MEDIA_CODEC_OK)));
auto codec_buffer = DequeueCodecOutputBuffer();
ASSERT_EQ(codec_buffer->size(), gfx::Size(42, 42));
}
#if DCHECK_IS_ON()
TEST_F(CodecWrapperTest, CallsDcheckAfterTakingTheCodec) {
wrapper_->TakeCodec();
ASSERT_DEATH(wrapper_->Flush(), "");
}
TEST_F(CodecWrapperTest, CallsDcheckAfterAnError) {
EXPECT_CALL(*codec_, Flush()).WillOnce(Return(MEDIA_CODEC_ERROR));
wrapper_->Flush();
ASSERT_DEATH(wrapper_->SetSurface(0), "");
}
#endif
} // 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