ACodec的状态转移如下:
UninitializedState -> LoadedState -> LoadedToIdleState -> IdleToExecutingState -> ExecutingState -> ……
在LoadedState时需要allocatebuffers进入loadedtoidlestate。
具体的allocatebuffers是怎么分配buffers的,接下来将具体介绍:
先看代码:
status_t ACodec::LoadedToIdleState::allocateBuffers() {
status_t err = mCodec->allocateBuffersOnPort(kPortIndexInput); // Input bufer
err = mCodec->allocateBuffersOnPort(kPortIndexOutput); // output buffer
err = mCodec->allocateBuffersOnPort(kPortIndexInputExtradata); // inputrxteadata
err = mCodec->allocateBuffersOnPort(kPortIndexOutputExtradata); // otputextradata
return OK;
}
所以实际就是分配了input / output / inputextra / outputextra 四组buffer。下面接着看具体的buffer分配。
status_t ACodec::allocateBuffersOnPort(OMX_U32 portIndex) {
if (portIndex == kPortIndexInputExtradata ||
portIndex == kPortIndexOutputExtradata) { // 如果是extradate直接返回ok
return OK;
}
CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);
CHECK(mAllocator[portIndex] == NULL); // 此处是allocator,是由此处分配buffer的
CHECK(mBuffers[portIndex].isEmpty()); // 分配的buffer最终会push到mBuffers中
status_t err;
if (mNativeWindow != NULL && portIndex == kPortIndexOutput) { // 当前要分配的内存是Output,且mNativeWindow不为null
if (storingMetadataInDecodedBuffers()) {
err = allocateOutputMetadataBuffers();
} else {
err = allocateOutputBuffersFromNativeWindow(); // 就从nativewindow分配buffer
}
} else { // 如果是input或者mNativeWindow==null,我们重点看这块
OMX_PARAM_PORTDEFINITIONTYPE def;
InitOMXParams(&def); // 初始化def
def.nPortIndex = portIndex;
err = mOMXNode->getParameter(
OMX_IndexParamPortDefinition, &def, sizeof(def));
// 从OMXNode获取def的参数。此处主要是拿到buffersize和buffercount。
// 在getParameter之前我们已经调用过了configure函数,所以已经把参数设置给了omx,
// 此处omx就可以返回给我们具体的buffer数量和大小,具体计算方法暂不研究。
if (err == OK) {
const IOMX::PortMode &mode = mPortMode[portIndex]; // 默认是IOMX::kPortModePresetByteBuffer;
size_t bufSize = def.nBufferSize; // bufSize赋值为从omx取出的值
if (mode == IOMX::kPortModeDynamicANWBuffer) { // 根据不同mode重新设置buffer大小
bufSize = sizeof(VideoNativeMetadata);
} else if (mode == IOMX::kPortModeDynamicNativeHandle) {
bufSize = sizeof(VideoNativeHandleMetadata);
}
size_t conversionBufferSize = 0; // conversionBufferSize
sp converter = mConverter[portIndex];
if (converter != NULL) {
// 如果需要转换buffer,我理解此处的意思是比如解码完成是yuv420格式,
// 但是app需要的是yuv444格式,我们就需要做转换,转换前后的buffer大小也是不一样的。
// here we assume sane conversions of max 4:1, so result fits in int32
if (portIndex == kPortIndexInput) {
conversionBufferSize = converter->sourceSize(bufSize);
// 把之前获取到的bufSize转换成conversionBufferSize
} else {
conversionBufferSize = converter->targetSize(bufSize);
}
}
size_t alignment = 32; // This is the value currently returned by
// MemoryDealer::getAllocationAlignment().
// TODO: Fix this when Treble has
// MemoryHeap/MemoryDealer.
// 这里是判断buffer的大小和数量是否超过了限制
if (bufSize == 0 || max(bufSize, conversionBufferSize) > kMaxCodecBufferSize) {
ALOGE("b/22885421");
return NO_MEMORY;
}
size_t alignedSize = align(bufSize, alignment);
size_t alignedConvSize = align(conversionBufferSize, alignment);
if (def.nBufferCountActual > SIZE_MAX / (alignedSize + alignedConvSize)) {
ALOGE("b/22885421");
return NO_MEMORY;
}
if (mode != IOMX::kPortModePresetSecureBuffer) { // 不是SecureBuffer
mAllocator[portIndex] = TAllocator::getService("ashmem");
// 获取mAllocator, 安卓的一种IPC机制: 匿名共享内存:
// Anonymous Shared Memory-Ashmem,安卓在自己的驱动中添加了互斥锁,另外通过fd的传递来实现共享内存的传递。
if (mAllocator[portIndex] == nullptr) {
ALOGE("hidl allocator on port %d is null",
(int)portIndex);
return NO_MEMORY;
}
}
const sp &format =
portIndex == kPortIndexInput ? mInputFormat : mOutputFormat; // format
for (OMX_U32 i = 0; i < def.nBufferCountActual && err == OK; ++i) { // for循环创建nBufferCountActual个buffer
hidl_memory hidlMemToken;
sp hidlMem;
sp mem;
BufferInfo info;
info.mStatus = BufferInfo::OWNED_BY_US; // 刚创建的buffer都是属于us的
info.mFenceFd = -1;
info.mRenderInfo = NULL;
info.mGraphicBuffer = NULL;
info.mNewGraphicBuffer = false;
if (mode == IOMX::kPortModePresetSecureBuffer) {
void *ptr = NULL;
sp native_handle;
err = mOMXNode->allocateSecureBuffer(
portIndex, bufSize, &info.mBufferID,
&ptr, &native_handle);
// 如果是SecureBuffer就直接从mOMXNode中allocateSecureBuffer
info.mData = (native_handle == NULL)
? new SecureBuffer(format, ptr, bufSize)
: new SecureBuffer(format, native_handle, bufSize);
info.mCodecData = info.mData;
} else { // 如果不是安全模式,就直接分配buffer
bool success;
auto transStatus = mAllocator[portIndex]->allocate(
// 分配buffer,此处是从hidl_memory中获取buffer
bufSize,
[&success, &hidlMemToken](
bool s,
hidl_memory const& m) {
success = s;
hidlMemToken = m;
});
if (!transStatus.isOk()) {
ALOGE("hidl's AshmemAllocator failed at the "
"transport: %s",
transStatus.description().c_str());
return NO_MEMORY;
}
if (!success) {
return NO_MEMORY;
}
hidlMem = mapMemory(hidlMemToken);
// 把共享内存映射到当前进程空间, hidlMemToken是共享内存返回的指针,
// hidlMem是映射到当前进程之后的指针
if (hidlMem == nullptr) {
return NO_MEMORY;
}
err = mOMXNode->useBuffer(
portIndex, hidlMemToken, &info.mBufferID);
// 此处就把buffer给omx,传的是hidlMemToken,因为到另一个进程
// 也需要把这个地址map成自己进程空间的地址
if (mode == IOMX::kPortModeDynamicANWBuffer) {
VideoNativeMetadata* metaData = (VideoNativeMetadata*)(
(void*)hidlMem->getPointer());
metaData->nFenceFd = -1;
}
info.mCodecData = new SharedMemoryBuffer(
// 此处只是对hidlMem做了封装,没别的操作
format, hidlMem);
info.mCodecRef = hidlMem;
// if we require conversion, allocate conversion buffer for client use;
// otherwise, reuse codec buffer
if (mConverter[portIndex] != NULL) {
// 这里应该是说如果有转换操作,需要再开额外的buffer,比如,yuv420转成yuv444等
CHECK_GT(conversionBufferSize, (size_t)0);
bool success;
mAllocator[portIndex]->allocate(
conversionBufferSize,
[&success, &hidlMemToken](
bool s,
hidl_memory const& m) {
success = s;
hidlMemToken = m;
});
if (!success) {
return NO_MEMORY;
}
hidlMem = mapMemory(hidlMemToken);
if (hidlMem == nullptr) {
return NO_MEMORY;
}
info.mData = new SharedMemoryBuffer(format, hidlMem);
info.mMemRef = hidlMem;
} else {
info.mData = info.mCodecData;
info.mMemRef = info.mCodecRef;
}
}
mBuffers[portIndex].push(info); // 最后就是把info push进mBuffers里面
}
}
}
if (err != OK) {
return err;
}
std::vector array(mBuffers[portIndex].size());
for (size_t i = 0; i < mBuffers[portIndex].size(); ++i) {
array[i] = {mBuffers[portIndex][i].mData, mBuffers[portIndex][i].mBufferID}; // 把buffer_id和实际buffer地址一一对应
}
if (portIndex == kPortIndexInput) {
mBufferChannel->setInputBufferArray(array);
// 把array队列赋值到mBufferChannel中
} else if (portIndex == kPortIndexOutput) {
mBufferChannel->setOutputBufferArray(array);
} else {
TRESPASS();
}
return OK;
}
总结: 整个allocateBuffers其实很简单,就是从omx获取了需要的参数,buffersize, buffercount等,然后for循环创建input/ output需要的内容。具体内存的分配就是调用了ashmem server,开辟了共享内存。共享内存开辟之后我们要对内存做操作就首先把这个共享内存的地址做映射,映射到当前进程的地址,然后再去做内存操作。分配完buffer之后,在ACodec中会告诉omx我们分配了这个buffer,把所有的buffer push到mBuffers里面。
tips: 这个mBuffers很重要,后面所有和内存相关的操作都需要从mBuffers里面去找内存。
后话:
1. 分配完buffer之后具体整个编解码对内存的操作是什么过程?
2. omx到底怎么计算buffer大小和数量的?
针对以上问题,后续会持续更新,敬请期待。