blob: 5eec1efc8e8aefcf24b8778c3ecf6e581d6ebc9f [file] [log] [blame]
// 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.
//#define LOG_NDEBUG 0
#define LOG_TAG "C2VDAComponent"
#ifdef V4L2_CODEC2_ARC
#include <C2VDAAdaptorProxy.h>
#else
#include <C2VDAAdaptor.h>
#endif
#define __C2_GENERATE_GLOBAL_VARS__
#include <C2VDAAllocatorStore.h>
#include <C2VdaBqBlockPool.h>
#include <C2VDAComponent.h>
#include <C2VDASupport.h> // to getParamReflector from vda store
#include <videodev2.h>
#include <C2AllocatorGralloc.h>
#include <C2ComponentFactory.h>
#include <C2PlatformSupport.h>
#include <base/bind.h>
#include <base/bind_helpers.h>
#include <media/stagefright/MediaDefs.h>
#include <utils/Log.h>
#include <utils/misc.h>
#include <inttypes.h>
#include <string.h>
#include <algorithm>
#define UNUSED(expr) \
do { \
(void)(expr); \
} while (0)
namespace android {
namespace {
// Mask against 30 bits to avoid (undefined) wraparound on signed integer.
int32_t frameIndexToBitstreamId(c2_cntr64_t frameIndex) {
return static_cast<int32_t>(frameIndex.peeku() & 0x3FFFFFFF);
}
// Use basic graphic block pool/allocator as default.
const C2BlockPool::local_id_t kDefaultOutputBlockPool = C2BlockPool::BASIC_GRAPHIC;
const C2String kH264DecoderName = "c2.vda.avc.decoder";
const C2String kVP8DecoderName = "c2.vda.vp8.decoder";
const C2String kVP9DecoderName = "c2.vda.vp9.decoder";
const uint32_t kDpbOutputBufferExtraCount = 3; // Use the same number as ACodec.
const int kDequeueRetryDelayUs = 10000; // Wait time of dequeue buffer retry in microseconds.
// Hack(b/79239042): Max size of mMockBufferQueueInClient.
// This value is empirically picked from previous CTS try-run. If this value is too big, it may
// cause VDA deadlock when it requires more buffers to decode and dequeue a new one. On the other
// hand, too small value may produce wrong display picture because recycling goes faster than
// rendering.
const size_t kMockMaxBuffersInClient = 5;
} // namespace
C2VDAComponent::IntfImpl::IntfImpl(C2String name, const std::shared_ptr<C2ReflectorHelper>& helper)
: C2InterfaceHelper(helper), mInitStatus(C2_OK) {
setDerivedInstance(this);
// TODO(johnylin): use factory function to determine whether V4L2 stream or slice API is.
uint32_t inputFormatFourcc;
char inputMime[128];
if (name == kH264DecoderName) {
strcpy(inputMime, MEDIA_MIMETYPE_VIDEO_AVC);
inputFormatFourcc = V4L2_PIX_FMT_H264_SLICE;
} else if (name == kVP8DecoderName) {
strcpy(inputMime, MEDIA_MIMETYPE_VIDEO_VP8);
inputFormatFourcc = V4L2_PIX_FMT_VP8_FRAME;
} else if (name == kVP9DecoderName) {
strcpy(inputMime, MEDIA_MIMETYPE_VIDEO_VP9);
inputFormatFourcc = V4L2_PIX_FMT_VP9_FRAME;
} else {
ALOGE("Invalid component name: %s", name.c_str());
mInitStatus = C2_BAD_VALUE;
return;
}
// Get supported profiles from VDA.
// TODO: re-think the suitable method of getting supported profiles for both pure Android and
// ARC++.
media::VideoDecodeAccelerator::SupportedProfiles supportedProfiles;
#ifdef V4L2_CODEC2_ARC
supportedProfiles = arc::C2VDAAdaptorProxy::GetSupportedProfiles(inputFormatFourcc);
#else
supportedProfiles = C2VDAAdaptor::GetSupportedProfiles(inputFormatFourcc);
#endif
if (supportedProfiles.empty()) {
ALOGE("No supported profile from input format: %u", inputFormatFourcc);
mInitStatus = C2_BAD_VALUE;
return;
}
mCodecProfile = supportedProfiles[0].profile;
auto minSize = supportedProfiles[0].min_resolution;
auto maxSize = supportedProfiles[0].max_resolution;
addParameter(
DefineParam(mInputFormat, C2_PARAMKEY_INPUT_STREAM_BUFFER_TYPE)
.withConstValue(new C2StreamBufferTypeSetting::input(0u, C2FormatCompressed))
.build());
addParameter(DefineParam(mOutputFormat, C2_PARAMKEY_OUTPUT_STREAM_BUFFER_TYPE)
.withConstValue(new C2StreamBufferTypeSetting::output(0u, C2FormatVideo))
.build());
addParameter(
DefineParam(mInputMediaType, C2_PARAMKEY_INPUT_MEDIA_TYPE)
.withConstValue(AllocSharedString<C2PortMediaTypeSetting::input>(inputMime))
.build());
addParameter(DefineParam(mOutputMediaType, C2_PARAMKEY_OUTPUT_MEDIA_TYPE)
.withConstValue(AllocSharedString<C2PortMediaTypeSetting::output>(
MEDIA_MIMETYPE_VIDEO_RAW))
.build());
struct LocalSetter {
static C2R SizeSetter(bool mayBlock, C2P<C2StreamPictureSizeInfo::output>& videoSize) {
(void)mayBlock;
// TODO: maybe apply block limit?
return videoSize.F(videoSize.v.width)
.validatePossible(videoSize.v.width)
.plus(videoSize.F(videoSize.v.height).validatePossible(videoSize.v.height));
}
};
addParameter(DefineParam(mSize, C2_PARAMKEY_STREAM_PICTURE_SIZE)
.withDefault(new C2StreamPictureSizeInfo::output(0u, 176, 144))
.withFields({
C2F(mSize, width).inRange(minSize.width(), maxSize.width(), 16),
C2F(mSize, height).inRange(minSize.height(), maxSize.height(), 16),
})
.withSetter(LocalSetter::SizeSetter)
.build());
C2Allocator::id_t inputAllocators[] = {C2PlatformAllocatorStore::ION};
C2Allocator::id_t outputAllocators[] = {C2VDAAllocatorStore::V4L2_BUFFERQUEUE};
addParameter(
DefineParam(mInputAllocatorIds, C2_PARAMKEY_INPUT_ALLOCATORS)
.withConstValue(C2PortAllocatorsTuning::input::AllocShared(inputAllocators))
.build());
addParameter(
DefineParam(mOutputAllocatorIds, C2_PARAMKEY_OUTPUT_ALLOCATORS)
.withConstValue(C2PortAllocatorsTuning::output::AllocShared(outputAllocators))
.build());
C2BlockPool::local_id_t outputBlockPools[] = {kDefaultOutputBlockPool};
addParameter(
DefineParam(mOutputBlockPoolIds, C2_PARAMKEY_OUTPUT_BLOCK_POOLS)
.withDefault(C2PortBlockPoolsTuning::output::AllocShared(outputBlockPools))
.withFields({C2F(mOutputBlockPoolIds, m.values[0]).any(),
C2F(mOutputBlockPoolIds, m.values).inRange(0, 1)})
.withSetter(Setter<C2PortBlockPoolsTuning::output>::NonStrictValuesWithNoDeps)
.build());
}
////////////////////////////////////////////////////////////////////////////////
#define EXPECT_STATE_OR_RETURN_ON_ERROR(x) \
do { \
if (mComponentState == ComponentState::ERROR) return; \
CHECK_EQ(mComponentState, ComponentState::x); \
} while (0)
#define EXPECT_RUNNING_OR_RETURN_ON_ERROR() \
do { \
if (mComponentState == ComponentState::ERROR) return; \
CHECK_NE(mComponentState, ComponentState::UNINITIALIZED); \
} while (0)
C2VDAComponent::VideoFormat::VideoFormat(HalPixelFormat pixelFormat, uint32_t minNumBuffers,
media::Size codedSize, media::Rect visibleRect)
: mPixelFormat(pixelFormat),
mMinNumBuffers(minNumBuffers),
mCodedSize(codedSize),
mVisibleRect(visibleRect) {}
static uint32_t getSlotFromGraphicBlockHandle(const C2Handle* const handle) {
uint32_t width, height, format, stride, igbp_slot, generation;
uint64_t usage, igbp_id;
_UnwrapNativeCodec2GrallocMetadata(
handle, &width, &height, &format, &usage, &stride, &generation, &igbp_id, &igbp_slot);
ALOGV("Unwrap Metadata: igbp[%" PRIu64 ", %u] (%u*%u, fmt %#x, usage %" PRIx64 ", stride %u)",
igbp_id, igbp_slot, width, height, format, usage, stride);
return igbp_slot;
}
C2VDAComponent::C2VDAComponent(C2String name, c2_node_id_t id,
const std::shared_ptr<C2ReflectorHelper>& helper)
: mIntfImpl(std::make_shared<IntfImpl>(name, helper)),
mIntf(std::make_shared<SimpleInterface<IntfImpl>>(name.c_str(), id, mIntfImpl)),
mThread("C2VDAComponentThread"),
mDequeueThread("C2VDAComponentDequeueThread"),
mVDAInitResult(VideoDecodeAcceleratorAdaptor::Result::ILLEGAL_STATE),
mComponentState(ComponentState::UNINITIALIZED),
mPendingOutputEOS(false),
mLastOutputTimestamp(-1),
mSurfaceMode(true),
mCodecProfile(media::VIDEO_CODEC_PROFILE_UNKNOWN),
mState(State::UNLOADED),
mWeakThisFactory(this) {
// TODO(johnylin): the client may need to know if init is failed.
if (mIntfImpl->status() != C2_OK) {
ALOGE("Component interface init failed (err code = %d)", mIntfImpl->status());
return;
}
if (!mThread.Start()) {
ALOGE("Component thread failed to start.");
return;
}
mTaskRunner = mThread.task_runner();
mState.store(State::LOADED);
}
C2VDAComponent::~C2VDAComponent() {
CHECK_EQ(mState.load(), State::LOADED);
if (mThread.IsRunning()) {
mTaskRunner->PostTask(FROM_HERE,
::base::Bind(&C2VDAComponent::onDestroy, ::base::Unretained(this)));
mThread.Stop();
}
}
void C2VDAComponent::onDestroy() {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("onDestroy");
if (mVDAAdaptor.get()) {
mVDAAdaptor->destroy();
mVDAAdaptor.reset(nullptr);
}
stopDequeueThread();
}
void C2VDAComponent::onStart(media::VideoCodecProfile profile, ::base::WaitableEvent* done) {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("onStart");
CHECK_EQ(mComponentState, ComponentState::UNINITIALIZED);
#ifdef V4L2_CODEC2_ARC
mVDAAdaptor.reset(new arc::C2VDAAdaptorProxy());
#else
mVDAAdaptor.reset(new C2VDAAdaptor());
#endif
// TODO: Set secureMode value dynamically.
bool secureMode = false;
mVDAInitResult = mVDAAdaptor->initialize(profile, secureMode, this);
if (mVDAInitResult == VideoDecodeAcceleratorAdaptor::Result::SUCCESS) {
mComponentState = ComponentState::STARTED;
}
done->Signal();
}
void C2VDAComponent::onQueueWork(std::unique_ptr<C2Work> work) {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("onQueueWork: flags=0x%x, index=%llu, timestamp=%llu", work->input.flags,
work->input.ordinal.frameIndex.peekull(), work->input.ordinal.timestamp.peekull());
EXPECT_RUNNING_OR_RETURN_ON_ERROR();
uint32_t drainMode = NO_DRAIN;
if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
drainMode = DRAIN_COMPONENT_WITH_EOS;
}
mQueue.push({std::move(work), drainMode});
// TODO(johnylin): set a maximum size of mQueue and check if mQueue is already full.
mTaskRunner->PostTask(FROM_HERE,
::base::Bind(&C2VDAComponent::onDequeueWork, ::base::Unretained(this)));
}
void C2VDAComponent::onDequeueWork() {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("onDequeueWork");
EXPECT_RUNNING_OR_RETURN_ON_ERROR();
if (mQueue.empty()) {
return;
}
if (mComponentState == ComponentState::DRAINING ||
mComponentState == ComponentState::FLUSHING) {
ALOGV("Temporarily stop dequeueing works since component is draining/flushing.");
return;
}
if (mComponentState != ComponentState::STARTED) {
ALOGE("Work queue should be empty if the component is not in STARTED state.");
return;
}
// Dequeue a work from mQueue.
std::unique_ptr<C2Work> work(std::move(mQueue.front().mWork));
auto drainMode = mQueue.front().mDrainMode;
mQueue.pop();
CHECK_LE(work->input.buffers.size(), 1u);
if (work->input.buffers.empty()) {
// Client may queue an EOS work with no input buffer, otherwise every work must have one
// input buffer.
CHECK(drainMode != NO_DRAIN);
} else {
// If input.buffers is not empty, the buffer should have meaningful content inside.
C2ConstLinearBlock linearBlock = work->input.buffers.front()->data().linearBlocks().front();
CHECK_GT(linearBlock.size(), 0u);
// Send input buffer to VDA for decode.
// Use frameIndex as bitstreamId.
int32_t bitstreamId = frameIndexToBitstreamId(work->input.ordinal.frameIndex);
sendInputBufferToAccelerator(linearBlock, bitstreamId);
}
CHECK_EQ(work->worklets.size(), 1u);
work->worklets.front()->output.flags = static_cast<C2FrameData::flags_t>(0);
work->worklets.front()->output.buffers.clear();
work->worklets.front()->output.ordinal = work->input.ordinal;
if (drainMode != NO_DRAIN) {
mVDAAdaptor->flush();
mComponentState = ComponentState::DRAINING;
mPendingOutputEOS = drainMode == DRAIN_COMPONENT_WITH_EOS;
}
// Put work to mPendingWorks.
mPendingWorks.emplace_back(std::move(work));
if (!mQueue.empty()) {
mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onDequeueWork,
::base::Unretained(this)));
}
}
void C2VDAComponent::onInputBufferDone(int32_t bitstreamId) {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("onInputBufferDone: bitstream id=%d", bitstreamId);
EXPECT_RUNNING_OR_RETURN_ON_ERROR();
C2Work* work = getPendingWorkByBitstreamId(bitstreamId);
if (!work) {
reportError(C2_CORRUPTED);
return;
}
// When the work is done, the input buffer shall be reset by component.
work->input.buffers.front().reset();
reportFinishedWorkIfAny();
}
void C2VDAComponent::onOutputBufferReturned(uint32_t slotId) {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("onOutputBufferReturned: slot id=%u", slotId);
if (mComponentState == ComponentState::UNINITIALIZED) {
// Output buffer is returned from client after component is stopped. Just let the buffer be
// released.
return;
}
// TODO(johnylin): when buffer is returned, we should confirm that output format is not changed
// yet. If changed, just let the buffer be released.
GraphicBlockInfo* info = getGraphicBlockBySlot(slotId);
if (!info) {
reportError(C2_CORRUPTED);
return;
}
CHECK_EQ(info->mState, GraphicBlockInfo::State::OWNED_BY_CLIENT);
info->mState = GraphicBlockInfo::State::OWNED_BY_COMPONENT;
if (mPendingOutputFormat) {
tryChangeOutputFormat();
} else {
sendOutputBufferToAccelerator(info);
}
}
void C2VDAComponent::onOutputBufferDone(int32_t pictureBufferId, int32_t bitstreamId) {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("onOutputBufferDone: picture id=%d, bitstream id=%d", pictureBufferId, bitstreamId);
EXPECT_RUNNING_OR_RETURN_ON_ERROR();
C2Work* work = getPendingWorkByBitstreamId(bitstreamId);
if (!work) {
reportError(C2_CORRUPTED);
return;
}
GraphicBlockInfo* info = getGraphicBlockById(pictureBufferId);
if (!info) {
reportError(C2_CORRUPTED);
return;
}
CHECK_EQ(info->mState, GraphicBlockInfo::State::OWNED_BY_ACCELERATOR);
// Output buffer will be passed to client soon along with mListener->onWorkDone_nb().
info->mState = GraphicBlockInfo::State::OWNED_BY_CLIENT;
if (mSurfaceMode) {
mBuffersInClient++;
} else { // byte-buffer mode
// Hack(b/79239042)
mMockBufferQueueInClient.push_back(info->mSlotId);
if (mMockBufferQueueInClient.size() > kMockMaxBuffersInClient) {
mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onOutputBufferReturned,
::base::Unretained(this),
mMockBufferQueueInClient.front()));
mMockBufferQueueInClient.pop_front();
}
}
// Attach output buffer to the work corresponded to bitstreamId.
auto block = info->mGraphicBlock;
work->worklets.front()->output.buffers.emplace_back(C2Buffer::CreateGraphicBuffer(
block->share(C2Rect(mOutputFormat.mVisibleRect.width(),
mOutputFormat.mVisibleRect.height()),
C2Fence())));
// TODO: this does not work for timestamps as they can wrap around
int64_t currentTimestamp = ::base::checked_cast<int64_t>(work->input.ordinal.timestamp.peek());
CHECK_GE(currentTimestamp, mLastOutputTimestamp);
mLastOutputTimestamp = currentTimestamp;
reportFinishedWorkIfAny();
}
void C2VDAComponent::onDrain(uint32_t drainMode) {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("onDrain: mode = %u", drainMode);
EXPECT_RUNNING_OR_RETURN_ON_ERROR();
if (!mQueue.empty()) {
// Mark last queued work as "drain-till-here" by setting drainMode. Do not change drainMode
// if last work already has one.
if (mQueue.back().mDrainMode == NO_DRAIN) {
mQueue.back().mDrainMode = drainMode;
}
} else if (!mPendingWorks.empty()) {
// Neglect drain request if component is not in STARTED mode. Otherwise, enters DRAINING
// mode and signal VDA flush immediately.
if (mComponentState == ComponentState::STARTED) {
mVDAAdaptor->flush();
mComponentState = ComponentState::DRAINING;
mPendingOutputEOS = drainMode == DRAIN_COMPONENT_WITH_EOS;
} else {
ALOGV("Neglect drain. Component in state: %d", mComponentState);
}
} else {
// Do nothing.
ALOGV("No buffers in VDA, drain takes no effect.");
}
}
void C2VDAComponent::onDrainDone() {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("onDrainDone");
if (mComponentState == ComponentState::DRAINING) {
mComponentState = ComponentState::STARTED;
} else if (mComponentState == ComponentState::STOPPING) {
// The client signals stop right before VDA notifies drain done. Let stop process goes.
return;
} else if (mComponentState != ComponentState::FLUSHING) {
// It is reasonable to get onDrainDone in FLUSHING, which means flush is already signaled
// and component should still expect onFlushDone callback from VDA.
ALOGE("Unexpected state while onDrainDone(). State=%d", mComponentState);
reportError(C2_BAD_STATE);
return;
}
if (mPendingOutputEOS) {
// Return EOS work.
reportEOSWork();
}
// mPendingWorks must be empty after draining is finished.
CHECK(mPendingWorks.empty());
// Last stream is finished. Reset the timestamp record.
mLastOutputTimestamp = -1;
// Work dequeueing was stopped while component draining. Restart it.
mTaskRunner->PostTask(FROM_HERE,
::base::Bind(&C2VDAComponent::onDequeueWork, ::base::Unretained(this)));
}
void C2VDAComponent::onFlush() {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("onFlush");
if (mComponentState == ComponentState::FLUSHING ||
mComponentState == ComponentState::STOPPING) {
return; // Ignore other flush request when component is flushing or stopping.
}
EXPECT_RUNNING_OR_RETURN_ON_ERROR();
mVDAAdaptor->reset();
// Pop all works in mQueue and put into mAbandonedWorks.
while (!mQueue.empty()) {
mAbandonedWorks.emplace_back(std::move(mQueue.front().mWork));
mQueue.pop();
}
mComponentState = ComponentState::FLUSHING;
}
void C2VDAComponent::onStop(::base::WaitableEvent* done) {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("onStop");
EXPECT_RUNNING_OR_RETURN_ON_ERROR();
// Do not request VDA reset again before the previous one is done. If reset is already sent by
// onFlush(), just regard the following NotifyResetDone callback as for stopping.
if (mComponentState != ComponentState::FLUSHING) {
mVDAAdaptor->reset();
}
// Pop all works in mQueue and put into mAbandonedWorks.
while (!mQueue.empty()) {
mAbandonedWorks.emplace_back(std::move(mQueue.front().mWork));
mQueue.pop();
}
mStopDoneEvent = done; // restore done event which shoud be signaled in onStopDone().
mComponentState = ComponentState::STOPPING;
}
void C2VDAComponent::onResetDone() {
DCHECK(mTaskRunner->BelongsToCurrentThread());
if (mComponentState == ComponentState::ERROR) {
return;
}
if (mComponentState == ComponentState::FLUSHING) {
onFlushDone();
} else if (mComponentState == ComponentState::STOPPING) {
onStopDone();
} else {
reportError(C2_CORRUPTED);
}
}
void C2VDAComponent::onFlushDone() {
ALOGV("onFlushDone");
reportAbandonedWorks();
// Reset the timestamp record.
mLastOutputTimestamp = -1;
mComponentState = ComponentState::STARTED;
// Work dequeueing was stopped while component flushing. Restart it.
mTaskRunner->PostTask(FROM_HERE,
::base::Bind(&C2VDAComponent::onDequeueWork, ::base::Unretained(this)));
}
void C2VDAComponent::onStopDone() {
ALOGV("onStopDone");
CHECK(mStopDoneEvent);
// TODO(johnylin): At this moment, there may be C2Buffer still owned by client, do we need to
// do something for them?
reportAbandonedWorks();
mPendingOutputFormat.reset();
mLastOutputTimestamp = -1;
if (mVDAAdaptor.get()) {
mVDAAdaptor->destroy();
mVDAAdaptor.reset(nullptr);
}
mGraphicBlocks.clear();
mMockBufferQueueInClient.clear(); // Hack(b/79239042)
stopDequeueThread();
mStopDoneEvent->Signal();
mStopDoneEvent = nullptr;
mComponentState = ComponentState::UNINITIALIZED;
}
c2_status_t C2VDAComponent::setListener_vb(const std::shared_ptr<C2Component::Listener>& listener,
c2_blocking_t mayBlock) {
UNUSED(mayBlock);
// TODO(johnylin): API says this method must be supported in all states, however I'm quite not
// sure what is the use case.
if (mState.load() != State::LOADED) {
return C2_BAD_STATE;
}
mListener = listener;
return C2_OK;
}
void C2VDAComponent::sendInputBufferToAccelerator(const C2ConstLinearBlock& input,
int32_t bitstreamId) {
ALOGV("sendInputBufferToAccelerator");
int dupFd = dup(input.handle()->data[0]);
if (dupFd < 0) {
ALOGE("Failed to dup(%d) input buffer (bitstreamId=%d), errno=%d", input.handle()->data[0],
bitstreamId, errno);
reportError(C2_CORRUPTED);
return;
}
ALOGV("Decode bitstream ID: %d, offset: %u size: %u", bitstreamId, input.offset(),
input.size());
mVDAAdaptor->decode(bitstreamId, dupFd, input.offset(), input.size());
}
C2Work* C2VDAComponent::getPendingWorkByBitstreamId(int32_t bitstreamId) {
auto workIter = std::find_if(mPendingWorks.begin(), mPendingWorks.end(),
[bitstreamId](const std::unique_ptr<C2Work>& w) {
return frameIndexToBitstreamId(w->input.ordinal.frameIndex) ==
bitstreamId;
});
if (workIter == mPendingWorks.end()) {
ALOGE("Can't find pending work by bitstream ID: %d", bitstreamId);
return nullptr;
}
return workIter->get();
}
C2VDAComponent::GraphicBlockInfo* C2VDAComponent::getGraphicBlockById(int32_t blockId) {
if (blockId < 0 || blockId >= static_cast<int32_t>(mGraphicBlocks.size())) {
ALOGE("getGraphicBlockById failed: id=%d", blockId);
return nullptr;
}
return &mGraphicBlocks[blockId];
}
C2VDAComponent::GraphicBlockInfo* C2VDAComponent::getGraphicBlockBySlot(uint32_t slotId) {
auto blockIter = std::find_if(mGraphicBlocks.begin(), mGraphicBlocks.end(),
[slotId](const GraphicBlockInfo& gb) {
return gb.mSlotId == slotId;
});
if (blockIter == mGraphicBlocks.end()) {
ALOGE("getGraphicBlockBySlot failed: slot=%u", slotId);
return nullptr;
}
return &(*blockIter);
}
void C2VDAComponent::onOutputFormatChanged(std::unique_ptr<VideoFormat> format) {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("onOutputFormatChanged");
EXPECT_RUNNING_OR_RETURN_ON_ERROR();
ALOGV("New output format(pixel_format=0x%x, min_num_buffers=%u, coded_size=%s, crop_rect=%s)",
static_cast<uint32_t>(format->mPixelFormat), format->mMinNumBuffers,
format->mCodedSize.ToString().c_str(), format->mVisibleRect.ToString().c_str());
for (auto& info : mGraphicBlocks) {
if (info.mState == GraphicBlockInfo::State::OWNED_BY_ACCELERATOR)
info.mState = GraphicBlockInfo::State::OWNED_BY_COMPONENT;
}
CHECK(!mPendingOutputFormat);
mPendingOutputFormat = std::move(format);
tryChangeOutputFormat();
}
void C2VDAComponent::tryChangeOutputFormat() {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("tryChangeOutputFormat");
CHECK(mPendingOutputFormat);
// Change the output format only after all output buffers are returned
// from clients.
// TODO(johnylin): don't need to wait for new proposed buffer flow.
for (const auto& info : mGraphicBlocks) {
if (info.mState == GraphicBlockInfo::State::OWNED_BY_CLIENT) {
ALOGV("wait buffer: %d for output format change", info.mBlockId);
return;
}
}
CHECK_EQ(mPendingOutputFormat->mPixelFormat, HalPixelFormat::YCbCr_420_888);
mOutputFormat.mPixelFormat = mPendingOutputFormat->mPixelFormat;
mOutputFormat.mMinNumBuffers = mPendingOutputFormat->mMinNumBuffers;
mOutputFormat.mCodedSize = mPendingOutputFormat->mCodedSize;
setOutputFormatCrop(mPendingOutputFormat->mVisibleRect);
c2_status_t err = allocateBuffersFromBlockAllocator(
mPendingOutputFormat->mCodedSize,
static_cast<uint32_t>(mPendingOutputFormat->mPixelFormat));
if (err != C2_OK) {
reportError(err);
return;
}
for (auto& info : mGraphicBlocks) {
sendOutputBufferToAccelerator(&info);
}
mPendingOutputFormat.reset();
}
c2_status_t C2VDAComponent::allocateBuffersFromBlockAllocator(const media::Size& size,
uint32_t pixelFormat) {
ALOGV("allocateBuffersFromBlockAllocator(%s, 0x%x)", size.ToString().c_str(), pixelFormat);
mMockBufferQueueInClient.clear(); // Hack(b/79239042)
stopDequeueThread();
size_t bufferCount = mOutputFormat.mMinNumBuffers + kDpbOutputBufferExtraCount;
// Allocate the output buffers.
mVDAAdaptor->assignPictureBuffers(bufferCount);
// Get block pool ID configured from the client.
std::shared_ptr<C2BlockPool> blockPool;
auto poolId = mIntfImpl->getBlockPoolId();
ALOGI("Using C2BlockPool ID = %" PRIu64 " for allocating output buffers", poolId);
auto err = GetCodec2BlockPool(poolId, shared_from_this(), &blockPool);
if (err != C2_OK) {
ALOGE("Graphic block allocator is invalid");
reportError(err);
return err;
}
mGraphicBlocks.clear();
if (blockPool->getAllocatorId() == C2PlatformAllocatorStore::BUFFERQUEUE) {
// Set requested buffer count to C2VdaBqBlockPool.
std::shared_ptr<C2VdaBqBlockPool> bqPool =
std::static_pointer_cast<C2VdaBqBlockPool>(blockPool);
if (bqPool) {
err = bqPool->requestNewBufferSet(static_cast<int32_t>(bufferCount));
if (err == C2_NO_INIT) {
ALOGD("No surface in block pool, output is byte-buffer mode...");
mSurfaceMode = false;
} else if (err != C2_OK) {
ALOGE("failed to set buffer count magic to block pool: %d", err);
reportError(err);
return err;
}
} else {
ALOGE("static_pointer_cast C2VdaBqBlockPool failed...");
reportError(C2_CORRUPTED);
return C2_CORRUPTED;
}
} else { // CCodec falls back to use C2BasicGraphicBlockPool
ALOGD("CCodec falls back to use C2BasicGraphicBlockPool...");
mSurfaceMode = false;
}
for (size_t i = 0; i < bufferCount; ++i) {
std::shared_ptr<C2GraphicBlock> block;
C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, 0};
err = blockPool->fetchGraphicBlock(size.width(), size.height(), pixelFormat, usage, &block);
if (err != C2_OK) {
mGraphicBlocks.clear();
ALOGE("failed to allocate buffer: %d", err);
reportError(err);
return err;
}
appendOutputBuffer(std::move(block));
}
mOutputFormat.mMinNumBuffers = bufferCount;
if (mSurfaceMode && !startDequeueThread(size, pixelFormat, std::move(blockPool))) {
reportError(C2_CORRUPTED);
return C2_CORRUPTED;
}
return C2_OK;
}
void C2VDAComponent::appendOutputBuffer(std::shared_ptr<C2GraphicBlock> block) {
GraphicBlockInfo info;
info.mBlockId = static_cast<int32_t>(mGraphicBlocks.size());
info.mGraphicBlock = std::move(block);
C2ConstGraphicBlock constBlock = info.mGraphicBlock->share(
C2Rect(info.mGraphicBlock->width(), info.mGraphicBlock->height()), C2Fence());
const C2GraphicView& view = constBlock.map().get();
const uint8_t* const* data = view.data();
CHECK_NE(data, nullptr);
const C2PlanarLayout& layout = view.layout();
ALOGV("allocate graphic buffer: %p, id: %d, size: %dx%d", info.mGraphicBlock->handle(),
info.mBlockId, info.mGraphicBlock->width(), info.mGraphicBlock->height());
// get offset from data pointers
uint32_t offsets[C2PlanarLayout::MAX_NUM_PLANES];
auto baseAddress = reinterpret_cast<intptr_t>(data[0]);
for (uint32_t i = 0; i < layout.numPlanes; ++i) {
auto planeAddress = reinterpret_cast<intptr_t>(data[i]);
offsets[i] = static_cast<uint32_t>(planeAddress - baseAddress);
}
bool crcb = false;
if (layout.numPlanes == 3 &&
offsets[C2PlanarLayout::PLANE_U] > offsets[C2PlanarLayout::PLANE_V]) {
// YCrCb format
std::swap(offsets[C2PlanarLayout::PLANE_U], offsets[C2PlanarLayout::PLANE_V]);
crcb = true;
}
bool semiplanar = false;
uint32_t passedNumPlanes = layout.numPlanes;
if (layout.planes[C2PlanarLayout::PLANE_U].colInc == 2) { // chroma_step
// Semi-planar format
passedNumPlanes--;
semiplanar = true;
}
for (uint32_t i = 0; i < passedNumPlanes; ++i) {
ALOGV("plane %u: stride: %d, offset: %u", i, layout.planes[i].rowInc, offsets[i]);
}
#ifdef V4L2_CODEC2_ARC
info.mPixelFormat = arc::C2VDAAdaptorProxy::ResolveBufferFormat(crcb, semiplanar);
#else
info.mPixelFormat = C2VDAAdaptor::ResolveBufferFormat(crcb, semiplanar);
#endif
ALOGV("HAL pixel format: 0x%x", static_cast<uint32_t>(info.mPixelFormat));
::base::ScopedFD passedHandle(dup(info.mGraphicBlock->handle()->data[0]));
if (!passedHandle.is_valid()) {
ALOGE("Failed to dup(%d), errno=%d", info.mGraphicBlock->handle()->data[0], errno);
reportError(C2_CORRUPTED);
return;
}
std::vector<VideoFramePlane> passedPlanes;
for (uint32_t i = 0; i < passedNumPlanes; ++i) {
CHECK_GT(layout.planes[i].rowInc, 0);
passedPlanes.push_back({offsets[i], static_cast<uint32_t>(layout.planes[i].rowInc)});
}
info.mHandle = std::move(passedHandle);
info.mPlanes = std::move(passedPlanes);
if (mSurfaceMode) {
info.mSlotId = getSlotFromGraphicBlockHandle(info.mGraphicBlock->handle());
} else { // byte-buffer mode
info.mSlotId = static_cast<uint32_t>(info.mBlockId);
}
mGraphicBlocks.push_back(std::move(info));
}
void C2VDAComponent::sendOutputBufferToAccelerator(GraphicBlockInfo* info) {
ALOGV("sendOutputBufferToAccelerator index=%d", info->mBlockId);
CHECK_EQ(info->mState, GraphicBlockInfo::State::OWNED_BY_COMPONENT);
info->mState = GraphicBlockInfo::State::OWNED_BY_ACCELERATOR;
// is_valid() is true for the first time the buffer is passed to VDA. In that case, VDA needs to
// import the buffer first.
if (info->mHandle.is_valid()) {
mVDAAdaptor->importBufferForPicture(info->mBlockId, info->mPixelFormat,
info->mHandle.release(), info->mPlanes);
} else {
mVDAAdaptor->reusePictureBuffer(info->mBlockId);
}
}
void C2VDAComponent::onVisibleRectChanged(const media::Rect& cropRect) {
DCHECK(mTaskRunner->BelongsToCurrentThread());
ALOGV("onVisibleRectChanged");
EXPECT_RUNNING_OR_RETURN_ON_ERROR();
// We should make sure there is no pending output format change. That is, the input cropRect is
// corresponding to current output format.
CHECK(mPendingOutputFormat == nullptr);
setOutputFormatCrop(cropRect);
}
void C2VDAComponent::setOutputFormatCrop(const media::Rect& cropRect) {
ALOGV("setOutputFormatCrop(%dx%d)", cropRect.width(), cropRect.height());
// This visible rect should be set as crop window for each C2ConstGraphicBlock passed to
// framework.
mOutputFormat.mVisibleRect = cropRect;
}
c2_status_t C2VDAComponent::queue_nb(std::list<std::unique_ptr<C2Work>>* const items) {
if (mState.load() != State::RUNNING) {
return C2_BAD_STATE;
}
while (!items->empty()) {
mTaskRunner->PostTask(FROM_HERE,
::base::Bind(&C2VDAComponent::onQueueWork, ::base::Unretained(this),
::base::Passed(&items->front())));
items->pop_front();
}
return C2_OK;
}
c2_status_t C2VDAComponent::announce_nb(const std::vector<C2WorkOutline>& items) {
UNUSED(items);
return C2_OMITTED; // Tunneling is not supported by now
}
c2_status_t C2VDAComponent::flush_sm(flush_mode_t mode,
std::list<std::unique_ptr<C2Work>>* const flushedWork) {
if (mode != FLUSH_COMPONENT) {
return C2_OMITTED; // Tunneling is not supported by now
}
if (mState.load() != State::RUNNING) {
return C2_BAD_STATE;
}
mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onFlush,
::base::Unretained(this)));
// Instead of |flushedWork|, abandoned works will be returned via onWorkDone_nb() callback.
return C2_OK;
}
c2_status_t C2VDAComponent::drain_nb(drain_mode_t mode) {
if (mode != DRAIN_COMPONENT_WITH_EOS && mode != DRAIN_COMPONENT_NO_EOS) {
return C2_OMITTED; // Tunneling is not supported by now
}
if (mState.load() != State::RUNNING) {
return C2_BAD_STATE;
}
mTaskRunner->PostTask(FROM_HERE,
::base::Bind(&C2VDAComponent::onDrain, ::base::Unretained(this),
static_cast<uint32_t>(mode)));
return C2_OK;
}
c2_status_t C2VDAComponent::start() {
// Use mStartStopLock to block other asynchronously start/stop calls.
std::lock_guard<std::mutex> lock(mStartStopLock);
if (mState.load() != State::LOADED) {
return C2_BAD_STATE; // start() is only supported when component is in LOADED state.
}
mCodecProfile = mIntfImpl->getCodecProfile();
ALOGI("get parameter: mCodecProfile = %d", static_cast<int>(mCodecProfile));
::base::WaitableEvent done(::base::WaitableEvent::ResetPolicy::AUTOMATIC,
::base::WaitableEvent::InitialState::NOT_SIGNALED);
mTaskRunner->PostTask(FROM_HERE,
::base::Bind(&C2VDAComponent::onStart, ::base::Unretained(this),
mCodecProfile, &done));
done.Wait();
if (mVDAInitResult != VideoDecodeAcceleratorAdaptor::Result::SUCCESS) {
ALOGE("Failed to start component due to VDA error: %d", static_cast<int>(mVDAInitResult));
return C2_CORRUPTED;
}
mState.store(State::RUNNING);
return C2_OK;
}
c2_status_t C2VDAComponent::stop() {
// Use mStartStopLock to block other asynchronously start/stop calls.
std::lock_guard<std::mutex> lock(mStartStopLock);
auto state = mState.load();
if (!(state == State::RUNNING || state == State::ERROR)) {
return C2_OK; // Component is already in stopped state.
}
::base::WaitableEvent done(::base::WaitableEvent::ResetPolicy::AUTOMATIC,
::base::WaitableEvent::InitialState::NOT_SIGNALED);
mTaskRunner->PostTask(FROM_HERE,
::base::Bind(&C2VDAComponent::onStop, ::base::Unretained(this), &done));
done.Wait();
mState.store(State::LOADED);
return C2_OK;
}
c2_status_t C2VDAComponent::reset() {
return stop();
// TODO(johnylin): reset is different than stop that it could be called in any state.
// TODO(johnylin): when reset is called, set ComponentInterface to default values.
}
c2_status_t C2VDAComponent::release() {
return reset();
}
std::shared_ptr<C2ComponentInterface> C2VDAComponent::intf() {
return mIntf;
}
void C2VDAComponent::providePictureBuffers(uint32_t minNumBuffers, const media::Size& codedSize) {
// Always use fexible pixel 420 format YCbCr_420_888 in Android.
// Uses coded size for crop rect while it is not available.
auto format = std::make_unique<VideoFormat>(HalPixelFormat::YCbCr_420_888, minNumBuffers,
codedSize, media::Rect(codedSize));
// Set mRequestedVisibleRect to default.
mRequestedVisibleRect = media::Rect();
mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onOutputFormatChanged,
::base::Unretained(this),
::base::Passed(&format)));
}
void C2VDAComponent::dismissPictureBuffer(int32_t pictureBufferId) {
UNUSED(pictureBufferId);
// no ops
}
void C2VDAComponent::pictureReady(int32_t pictureBufferId, int32_t bitstreamId,
const media::Rect& cropRect) {
UNUSED(pictureBufferId);
UNUSED(bitstreamId);
if (mRequestedVisibleRect != cropRect) {
mRequestedVisibleRect = cropRect;
mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onVisibleRectChanged,
::base::Unretained(this), cropRect));
}
mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onOutputBufferDone,
::base::Unretained(this),
pictureBufferId, bitstreamId));
}
void C2VDAComponent::notifyEndOfBitstreamBuffer(int32_t bitstreamId) {
mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onInputBufferDone,
::base::Unretained(this), bitstreamId));
}
void C2VDAComponent::notifyFlushDone() {
mTaskRunner->PostTask(FROM_HERE,
::base::Bind(&C2VDAComponent::onDrainDone, ::base::Unretained(this)));
}
void C2VDAComponent::notifyResetDone() {
mTaskRunner->PostTask(FROM_HERE,
::base::Bind(&C2VDAComponent::onResetDone, ::base::Unretained(this)));
}
void C2VDAComponent::notifyError(VideoDecodeAcceleratorAdaptor::Result error) {
ALOGE("Got notifyError from VDA error=%d", error);
c2_status_t err;
switch (error) {
case VideoDecodeAcceleratorAdaptor::Result::ILLEGAL_STATE:
err = C2_BAD_STATE;
break;
case VideoDecodeAcceleratorAdaptor::Result::INVALID_ARGUMENT:
case VideoDecodeAcceleratorAdaptor::Result::UNREADABLE_INPUT:
err = C2_BAD_VALUE;
break;
case VideoDecodeAcceleratorAdaptor::Result::PLATFORM_FAILURE:
err = C2_CORRUPTED;
break;
case VideoDecodeAcceleratorAdaptor::Result::INSUFFICIENT_RESOURCES:
err = C2_NO_MEMORY;
break;
case VideoDecodeAcceleratorAdaptor::Result::SUCCESS:
ALOGE("Shouldn't get SUCCESS err code in NotifyError(). Skip it...");
return;
}
reportError(err);
}
void C2VDAComponent::reportFinishedWorkIfAny() {
DCHECK(mTaskRunner->BelongsToCurrentThread());
std::list<std::unique_ptr<C2Work>> finishedWorks;
// Work should be reported as done if both input and output buffer are returned by VDA.
// Note that not every input buffer has matched output (ex. CSD header for H.264).
// However, the timestamp is guaranteed to be monotonic increasing for buffers in display order.
// That is, since VDA output is in display order, if we get a returned output with timestamp T,
// it implies all works with timestamp <= T are done.
// EOS work will not be reported here. reportEOSWork() does it.
auto iter = mPendingWorks.begin();
while (iter != mPendingWorks.end()) {
if (isWorkDone(iter->get())) {
iter->get()->result = C2_OK;
iter->get()->workletsProcessed = static_cast<uint32_t>(iter->get()->worklets.size());
finishedWorks.emplace_back(std::move(*iter));
iter = mPendingWorks.erase(iter);
} else {
++iter;
}
}
if (!finishedWorks.empty()) {
mListener->onWorkDone_nb(shared_from_this(), std::move(finishedWorks));
}
}
bool C2VDAComponent::isWorkDone(const C2Work* work) const {
if (work->input.buffers.empty()) {
// This is EOS work with no input buffer and should be processed by reportEOSWork().
return false;
}
if (work->input.buffers.front()) {
// Input buffer is still owned by VDA.
return false;
}
if (mPendingOutputEOS && mPendingWorks.size() == 1u) {
// If mPendingOutputEOS is true, the last returned work should be marked EOS flag and
// returned by reportEOSWork() instead.
return false;
}
if (mLastOutputTimestamp < 0) {
return false; // No output buffer is returned yet.
}
if (work->input.ordinal.timestamp > static_cast<uint64_t>(mLastOutputTimestamp)) {
return false; // Output buffer is not returned by VDA yet.
}
return true; // Output buffer is returned, or it has no related output buffer.
}
void C2VDAComponent::reportEOSWork() {
ALOGV("reportEOSWork");
DCHECK(mTaskRunner->BelongsToCurrentThread());
// In this moment all works prior to EOS work should be done and returned to listener.
if (mPendingWorks.size() != 1u) { // only EOS work left
ALOGE("It shouldn't have remaining works in mPendingWorks except EOS work.");
reportError(C2_CORRUPTED);
return;
}
mPendingOutputEOS = false;
std::unique_ptr<C2Work> eosWork(std::move(mPendingWorks.front()));
mPendingWorks.pop_front();
if (!eosWork->input.buffers.empty()) {
eosWork->input.buffers.front().reset();
}
eosWork->result = C2_OK;
eosWork->workletsProcessed = static_cast<uint32_t>(eosWork->worklets.size());
eosWork->worklets.front()->output.flags = C2FrameData::FLAG_END_OF_STREAM;
std::list<std::unique_ptr<C2Work>> finishedWorks;
finishedWorks.emplace_back(std::move(eosWork));
mListener->onWorkDone_nb(shared_from_this(), std::move(finishedWorks));
}
void C2VDAComponent::reportAbandonedWorks() {
DCHECK(mTaskRunner->BelongsToCurrentThread());
std::list<std::unique_ptr<C2Work>> abandonedWorks;
while (!mPendingWorks.empty()) {
std::unique_ptr<C2Work> work(std::move(mPendingWorks.front()));
mPendingWorks.pop_front();
// TODO: correlate the definition of flushed work result to framework.
work->result = C2_NOT_FOUND;
// When the work is abandoned, buffer in input.buffers shall reset by component.
if (!work->input.buffers.empty()) {
work->input.buffers.front().reset();
}
abandonedWorks.emplace_back(std::move(work));
}
for (auto& work : mAbandonedWorks) {
// TODO: correlate the definition of flushed work result to framework.
work->result = C2_NOT_FOUND;
// When the work is abandoned, buffer in input.buffers shall reset by component.
if (!work->input.buffers.empty()) {
work->input.buffers.front().reset();
}
abandonedWorks.emplace_back(std::move(work));
}
mAbandonedWorks.clear();
// Pending EOS work will be abandoned here due to component flush if any.
mPendingOutputEOS = false;
if (!abandonedWorks.empty()) {
mListener->onWorkDone_nb(shared_from_this(), std::move(abandonedWorks));
}
}
void C2VDAComponent::reportError(c2_status_t error) {
mListener->onError_nb(shared_from_this(), static_cast<uint32_t>(error));
}
bool C2VDAComponent::startDequeueThread(const media::Size& size, uint32_t pixelFormat,
std::shared_ptr<C2BlockPool> blockPool) {
CHECK(!mDequeueThread.IsRunning());
if (!mDequeueThread.Start()) {
ALOGE("failed to start dequeue thread!!");
return false;
}
mDequeueLoopStop.store(false);
mBuffersInClient.store(0u);
mDequeueThread.task_runner()->PostTask(
FROM_HERE, ::base::Bind(&C2VDAComponent::dequeueThreadLoop, ::base::Unretained(this),
size, pixelFormat, std::move(blockPool)));
return true;
}
void C2VDAComponent::stopDequeueThread() {
if (mDequeueThread.IsRunning()) {
mDequeueLoopStop.store(true);
mDequeueThread.Stop();
}
}
void C2VDAComponent::dequeueThreadLoop(const media::Size& size, uint32_t pixelFormat,
std::shared_ptr<C2BlockPool> blockPool) {
ALOGV("dequeueThreadLoop starts");
DCHECK(mDequeueThread.task_runner()->BelongsToCurrentThread());
while (!mDequeueLoopStop.load()) {
if (mBuffersInClient.load() == 0) {
::usleep(kDequeueRetryDelayUs); // wait for retry
continue;
}
std::shared_ptr<C2GraphicBlock> block;
C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, 0};
auto err = blockPool->fetchGraphicBlock(size.width(), size.height(), pixelFormat, usage,
&block);
if (err == C2_TIMED_OUT) {
continue; // wait for retry
}
if (err == C2_OK) {
auto slot = getSlotFromGraphicBlockHandle(block->handle());
mTaskRunner->PostTask(FROM_HERE, ::base::Bind(&C2VDAComponent::onOutputBufferReturned,
::base::Unretained(this), slot));
mBuffersInClient--;
} else {
ALOGE("dequeueThreadLoop got error: %d", err);
break;
}
}
ALOGV("dequeueThreadLoop terminates");
}
class C2VDAComponentFactory : public C2ComponentFactory {
public:
C2VDAComponentFactory(C2String decoderName)
: mDecoderName(decoderName),
mReflector(std::static_pointer_cast<C2ReflectorHelper>(
GetCodec2VDAComponentStore()->getParamReflector())){};
c2_status_t createComponent(c2_node_id_t id, std::shared_ptr<C2Component>* const component,
ComponentDeleter deleter) override {
UNUSED(deleter);
*component = std::shared_ptr<C2Component>(new C2VDAComponent(mDecoderName, id, mReflector));
return C2_OK;
}
c2_status_t createInterface(c2_node_id_t id,
std::shared_ptr<C2ComponentInterface>* const interface,
InterfaceDeleter deleter) override {
UNUSED(deleter);
*interface =
std::shared_ptr<C2ComponentInterface>(new SimpleInterface<C2VDAComponent::IntfImpl>(
mDecoderName.c_str(), id,
std::make_shared<C2VDAComponent::IntfImpl>(mDecoderName, mReflector)));
return C2_OK;
}
~C2VDAComponentFactory() override = default;
private:
const C2String mDecoderName;
std::shared_ptr<C2ReflectorHelper> mReflector;
};
} // namespace android
extern "C" ::C2ComponentFactory* CreateC2VDAH264Factory() {
ALOGV("in %s", __func__);
return new ::android::C2VDAComponentFactory(android::kH264DecoderName);
}
extern "C" void DestroyC2VDAH264Factory(::C2ComponentFactory* factory) {
ALOGV("in %s", __func__);
delete factory;
}
extern "C" ::C2ComponentFactory* CreateC2VDAVP8Factory() {
ALOGV("in %s", __func__);
return new ::android::C2VDAComponentFactory(android::kVP8DecoderName);
}
extern "C" void DestroyC2VDAVP8Factory(::C2ComponentFactory* factory) {
ALOGV("in %s", __func__);
delete factory;
}
extern "C" ::C2ComponentFactory* CreateC2VDAVP9Factory() {
ALOGV("in %s", __func__);
return new ::android::C2VDAComponentFactory(android::kVP9DecoderName);
}
extern "C" void DestroyC2VDAVP9Factory(::C2ComponentFactory* factory) {
ALOGV("in %s", __func__);
delete factory;
}