1.はじめに:
以前のブログですでに言及されているメカニズムOMX_FillThisBuffer
と、ここのブログで分析されたメカニズムOMX_EmptyThisBuffer
。前に多くの準備作業について言及したので、ここで直接トピックに行きましょう。
2. OMX は EmptyBufferDone を介して MediaCodec に通知します。
基になる vdec または adec コンポーネントがデコードするデータを消費した後、callbacks.EmptyBufferDone
MediaCodec への通知を呼び出す必要があります。
pMSComponent->callbacks.EmptyBufferDone(pOMXComponent, pMSComponent->callbackData, bufferHeader);
OMXNodeInstance中OnEmptyBufferDone
具体的な実装を見てみましょう:
OMX_ERRORTYPE OMXNodeInstance::OnEmptyBufferDone(
OMX_IN OMX_HANDLETYPE /* hComponent */,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) {
if (pAppData == NULL) {
ALOGE("b/25884056");
return OMX_ErrorBadParameter;
}
OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData);
if (instance->mDying) {
return OMX_ErrorNone;
}
int fenceFd = instance->retrieveFenceFromMeta_l(pBuffer, kPortIndexOutput);
omx_message msg;
msg.type = omx_message::EMPTY_BUFFER_DONE;
msg.fenceFd = fenceFd;
msg.u.buffer_data.buffer = instance->findBufferID(pBuffer);
instance->mDispatcher->post(msg);
return OMX_ErrorNone;
}
pBuffer の情報を msg に書き込み、配信します。
void OMXNodeInstance::onMessages(std::list<omx_message> &messages) {
for (std::list<omx_message>::iterator it = messages.begin(); it != messages.end(); ) {
if (handleMessage(*it)) {
messages.erase(it++);
} else {
++it;
}
}
if (!messages.empty()) {
mObserver->onMessages(messages);
}
}
handleMessage はあまり多くの重要な操作を行わないため、omx_message::EMPTY_BUFFER_DONE
ACodec での処理に注目してください。
case omx_message::EMPTY_BUFFER_DONE:
{
IOMX::buffer_id bufferID;
int32_t fenceFd;
CHECK(msg->findInt32("buffer", (int32_t*)&bufferID));
CHECK(msg->findInt32("fence_fd", &fenceFd));
return onOMXEmptyBufferDone(bufferID, fenceFd);
}
までフォローアップonOMXEmptyBufferDone
:
bool ACodec::BaseState::onOMXEmptyBufferDone(IOMX::buffer_id bufferID, int fenceFd) {
...
PortMode mode = getPortMode(kPortIndexInput);
switch (mode) {
case KEEP_BUFFERS:
break;
case RESUBMIT_BUFFERS:
/* 跟进如下函数 */
postFillThisBuffer(info);
break;
case FREE_BUFFERS:
default:
ALOGE("SHOULD NOT REACH HERE: cannot free empty output buffers");
return false;
}
return true;
}
ファローアップ:
void ACodec::BaseState::postFillThisBuffer(BufferInfo *info) {
if (mCodec->mPortEOS[kPortIndexInput]) {
return;
}
CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_US);
info->mData->setFormat(mCodec->mInputFormat);
/* 调用ACodecBufferChannel */
mCodec->mBufferChannel->fillThisBuffer(info->mBufferID);
info->mData.clear();
info->mStatus = BufferInfo::OWNED_BY_UPSTREAM;
}
ここで、ACodecBufferChannel に転送されます。fillThisBuffer
以下の関数の実装を参照してください。
void ACodecBufferChannel::fillThisBuffer(IOMX::buffer_id bufferId) {
ALOGV("fillThisBuffer #%d", bufferId);
std::shared_ptr<const std::vector<const BufferInfo>> array(
std::atomic_load(&mInputBuffers));
BufferInfoIterator it = findBufferId(array, bufferId);
if (it == array->end()) {
ALOGE("fillThisBuffer: unrecognized buffer #%d", bufferId);
return;
}
if (it->mClientBuffer != it->mCodecBuffer) {
it->mClientBuffer->setFormat(it->mCodecBuffer->format());
}
/* 回调通知至MediaCodec */
mCallback->onInputBufferAvailable(
std::distance(array->begin(), it),
it->mClientBuffer);
}
MediaCodec に戻る:
void BufferCallback::onInputBufferAvailable(
size_t index, const sp<MediaCodecBuffer> &buffer) {
sp<AMessage> notify(mNotify->dup());
notify->setInt32("what", kWhatFillThisBuffer);
notify->setSize("index", index);
notify->setObject("buffer", buffer);
notify->post();
}
kWhatFillThisBuffer のメッセージ実装:
case kWhatFillThisBuffer:
{
/* size_t index = */updateBuffers(kPortIndexInput, msg);
...
}
EmptyBufferDone と同じように、入力チャネルの利用可能なバッファの数がここで更新され、アプリケーションは対応する ID を照会し、デコードする新しいデータで埋めることができます。
size_t MediaCodec::updateBuffers(
int32_t portIndex, const sp<AMessage> &msg) {
CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);
size_t index;
CHECK(msg->findSize("index", &index));
sp<RefBase> obj;
CHECK(msg->findObject("buffer", &obj));
sp<MediaCodecBuffer> buffer = static_cast<MediaCodecBuffer *>(obj.get());
{
Mutex::Autolock al(mBufferLock);
if (mPortBuffers[portIndex].size() <= index) {
mPortBuffers[portIndex].resize(align(index + 1, kNumBuffersAlign));
}
mPortBuffers[portIndex][index].mData = buffer;
}
mAvailPortBuffers[portIndex].push_back(index);
return index;
}
ここまで、OMX コンポーネントが MediaCodec に通知するプロセスを分析しました。
3. dequeueInputBuffer の分析:
OMX の最下層でデコードが完了し、バッファー情報が更新された後mAvailPortBuffers
、アプリケーションは引き続き新しい es データを入力できます。最初に行うことは、呼び出してdequeueInputBuffer
使用可能なバッファーのインデックスを取得することです。
status_t MediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
sp<AMessage> msg = new AMessage(kWhatDequeueInputBuffer, this);
msg->setInt64("timeoutUs", timeoutUs);
sp<AMessage> response;
status_t err;
if ((err = PostAndAwaitResponse(msg, &response)) != OK) {
return err;
}
CHECK(response->findSize("index", index));
return OK;
}
kWhatDequeueInputBuffer
メッセージ処理:
case kWhatDequeueInputBuffer:
{
...
if (handleDequeueInputBuffer(replyID, true /* new request */)) {
break;
}
...
}
ファローアップ:
bool MediaCodec::handleDequeueInputBuffer(const sp<AReplyToken> &replyID, bool newRequest) {
...
ssize_t index = dequeuePortBuffer(kPortIndexInput);
if (index < 0) {
CHECK_EQ(index, -EAGAIN);
return false;
}
sp<AMessage> response = new AMessage;
response->setSize("index", index);
response->postReply(replyID);
return true;
}
への呼び出しdequeuePortBuffer
:
ssize_t MediaCodec::dequeuePortBuffer(int32_t portIndex) {
CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);
/* 1.获取buffer队列的临时指针对象 */
List<size_t> *availBuffers = &mAvailPortBuffers[portIndex];
if (availBuffers->empty()) {
return -EAGAIN;
}
/* 2.获得队首index */
size_t index = *availBuffers->begin();
/* 3.将此index从队列中删除 */
availBuffers->erase(availBuffers->begin());
/* 4.通过index拿到对应的buffer信息并填充 */
BufferInfo *info = &mPortBuffers[portIndex][index];
CHECK(!info->mOwnedByClient);
{
Mutex::Autolock al(mBufferLock);
info->mOwnedByClient = true;
// set image-data
if (info->mData->format() != NULL) {
sp<ABuffer> imageData;
if (info->mData->format()->findBuffer("image-data", &imageData)) {
info->mData->meta()->setBuffer("image-data", imageData);
}
int32_t left, top, right, bottom;
if (info->mData->format()->findRect("crop", &left, &top, &right, &bottom)) {
info->mData->meta()->setRect("crop-rect", left, top, right, bottom);
}
}
}
return index;
}
dequeuePortBuffer
この関数の目的は非常に単純です。mAvailPortBuffers
使用。そうでない場合は、 を返します-EAGAIN
。
4. queueInputBuffer の分析:
データがいっぱいになると、アプリケーションは呼び出してqueueInputBuffer
バッファをエンキューし、OMX コンポーネントにデコードするよう通知します。
status_t MediaCodec::queueInputBuffer(
size_t index,
size_t offset,
size_t size,
int64_t presentationTimeUs,
uint32_t flags,
AString *errorDetailMsg) {
if (errorDetailMsg != NULL) {
errorDetailMsg->clear();
}
sp<AMessage> msg = new AMessage(kWhatQueueInputBuffer, this);
msg->setSize("index", index);
msg->setSize("offset", offset);
msg->setSize("size", size);
msg->setInt64("timeUs", presentationTimeUs);
msg->setInt32("flags", flags);
msg->setPointer("errorDetailMsg", errorDetailMsg);
sp<AMessage> response;
status_t err = PostAndAwaitResponse(msg, &response);
return err;
}
フォローアップkWhatQueueInputBuffer
メッセージ:
case kWhatQueueInputBuffer:
{
sp<AReplyToken> replyID;
CHECK(msg->senderAwaitsResponse(&replyID));
if (!isExecuting()) {
PostReplyWithError(replyID, INVALID_OPERATION);
break;
} else if (mFlags & kFlagStickyError) {
PostReplyWithError(replyID, getStickyError());
break;
}
status_t err = onQueueInputBuffer(msg);
PostReplyWithError(replyID, err);
break;
}
onQueueInputBuffer の重要な部分を引き続き見ていきます。
status_t MediaCodec::onQueueInputBuffer(const sp<AMessage> &msg) {
...
if (...)
{
err = mBufferChannel->queueSecureInputBuffer(...):
} else {
err = mBufferChannel->queueInputBuffer(buffer);
}
...
}
queueInputBuffer
ここでは、実装を確認するために ACodecBufferChannel に移動する必要があります。
status_t ACodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
if (mDealer != nullptr) {
return -ENOSYS;
}
std::shared_ptr<const std::vector<const BufferInfo>> array(
std::atomic_load(&mInputBuffers));
BufferInfoIterator it = findClientBuffer(array, buffer);
if (it == array->end()) {
return -ENOENT;
}
ALOGV("queueInputBuffer #%d", it->mBufferId);
sp<AMessage> msg = mInputBufferFilled->dup();
msg->setObject("buffer", it->mCodecBuffer);
msg->setInt32("buffer-id", it->mBufferId);
msg->post();
return OK;
}
mInputBufferFilled->dup()
取得するのはkWhatInputBufferFilled
メッセージです。対応するメッセージの実装を見てください。
case kWhatInputBufferFilled:
{
onInputBufferFilled(msg);
break;
}
onInputBufferFilled のフォローアップ:
void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
...
PortMode mode = getPortMode(kPortIndexInput);
...
switch (mode) {
...
case RESUBMIT_BUFFERS:
{
...
status_t err2 = mCodec->mOMXNode->emptyBuffer(
bufferID, OMXBuffer::sPreset, OMX_BUFFERFLAG_EOS, 0, info->mFenceFd);
...
}
...
}
...
}
見てくださいOMXNodeInstance::emptyBuffer
:
status_t OMXNodeInstance::emptyBuffer(
buffer_id buffer, const OMXBuffer &omxBuffer,
OMX_U32 flags, OMX_TICKS timestamp, int fenceFd) {
Mutex::Autolock autoLock(mLock);
switch (omxBuffer.mBufferType) {
case OMXBuffer::kBufferTypePreset:
return emptyBuffer_l(
buffer, omxBuffer.mRangeOffset, omxBuffer.mRangeLength,
flags, timestamp, fenceFd);
case OMXBuffer::kBufferTypeANWBuffer:
return emptyGraphicBuffer_l(
buffer, omxBuffer.mGraphicBuffer, flags, timestamp, fenceFd);
case OMXBuffer::kBufferTypeNativeHandle:
return emptyNativeHandleBuffer_l(
buffer, omxBuffer.mNativeHandle, flags, timestamp, fenceFd);
default:
break;
}
return BAD_VALUE;
}
status_t OMXNodeInstance::emptyBuffer_l(
IOMX::buffer_id buffer,
OMX_U32 rangeOffset, OMX_U32 rangeLength,
OMX_U32 flags, OMX_TICKS timestamp, int fenceFd) {
/* 1.找到BufferHeader */
OMX_BUFFERHEADERTYPE *header = findBufferHeader(buffer, kPortIndexInput);
...
/* 2.将es数据拷贝到底层buffer中 */
buffer_meta->CopyToOMX(header);
...
return emptyBuffer_l(header, flags, timestamp, (intptr_t)buffer, fenceFd);
}
オーバーロードされた関数のフォローアップを続けます。
status_t OMXNodeInstance::emptyBuffer_l(
OMX_BUFFERHEADERTYPE *header, OMX_U32 flags, OMX_TICKS timestamp,
intptr_t debugAddr, int fenceFd) {
...
OMX_ERRORTYPE err = OMX_EmptyThisBuffer(mHandle, header);
...
}
ここは、OMX 標準インターフェイスを呼び出す場所です。これは、現在送信されているバッファを OMX の最下層にデコードさせることを意味します。
最後に、OMX IL レイヤー チップ プラットフォームの一般的な実装を簡単に見てみましょう。
OMX_ERRORTYPE MS_OMX_EmptyThisBuffer(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_BUFFERHEADERTYPE *pBuffer)
{
...
/* 1.check传下来的buffer的有效性 */
for (i = 0; i < pMSPort->portDefinition.nBufferCountActual; i++) {
if (pBuffer == pMSPort->bufferHeader[i]) {
findBuffer = OMX_TRUE;
break;
}
}
...
/* 2.将buffer信息写入到msg中,供buffer处理线程使用 */
if (bPushQueue) {
MS_OMX_MESSAGE *message = MS_OSAL_Malloc(sizeof(MS_OMX_MESSAGE));
if (message == NULL) {
ret = OMX_ErrorInsufficientResources;
goto EXIT;
}
message->messageType = MS_OMX_CommandEmptyBuffer;
message->messageParam = (OMX_U32) i;
message->pCmdData = (OMX_PTR)pBuffer;
MS_OSAL_Queue(&pMSPort->bufferQ, (void *)message);
MS_OSAL_SemaphorePost(pMSPort->bufferSemID);
}
}
同様にOMX_FillThisBuffer
、L層は利用可能なバッファの情報をmsgに書き込んで、IL層が実装するメッセージキューにプッシュするだけでよく、バッファが出現したスレッドについては、利用可能なmsgを連続して読み込み、次に、msg から使用可能なバッファーを読み取り、新しいラウンドのデータ デコードを実行します。
V. まとめ:絵の
分析OMX_EmptyThisBuffer
とメカニズム:EmptyBufferDone