Android音视频 MediaCodec框架-启动编码(4)
Android音视频 MediaCodec框架-启动编码
简述
上一节我们介绍了MediaCodec框架创建编码器流程,编解码的流程其实基本是一样的,只是底层的最终的实现组件不同,所以我们只看启动编码流程。
MediaCodec启动编码
从MediaCodec的start方法开始。
1.1 MediaCodec.start
调用jni方法native_start
public final void start() {native_start();
}
1.2 native_start
调用JMediaCodec的start方法。
static void android_media_MediaCodec_start(JNIEnv *env, jobject thiz) {ALOGV("android_media_MediaCodec_start");sp<JMediaCodec> codec = getMediaCodec(env, thiz);// ... JMediaCodec状态检测// 详见1.3status_t err = codec->start();// ...
}
1.3 JMediaCodec::start
调用MediaCodec的start方法。
status_t JMediaCodec::start() {// 详见1.4return mCodec->start();
}
1.4 MediaCodec::start
发送kWhatStart的AMessage通知CCodec2 start,这个流程和init很类似,处理消息start的流程详见1.5
status_t MediaCodec::start() {sp<AMessage> msg = new AMessage(kWhatStart, this);sp<AMessage> callback;status_t err;std::vector<MediaResourceParcel> resources;resources.push_back(MediaResource::CodecResource(mFlags & kFlagIsSecure,toMediaResourceSubType(mDomain)));resources.push_back(MediaResource::GraphicMemoryResource(1));for (int i = 0; i <= kMaxRetry; ++i) {if (i > 0) {// ...sp<AMessage> response;err = PostAndAwaitResponse(mConfigureMsg, &response);// ...}// ...}return err;
}
1.5 MediaCodec::onMessageReceived
消息处理方法,调用CCodec的initiateStart
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {// ...case kWhatStart:{// ...// 更新状态到STARTINGsetState(STARTING);// 调用CCodec的initiateStart,详见1.6mCodec->initiateStart();break;}// ...
}
1.6 CCodec::initiateStart
这里只是修改了状态到STARTING,然后做了消息转发。
void CCodec::initiateStart() {auto setStarting = [this] {Mutexed<State>::Locked state(mState);if (state->get() != ALLOCATED) {return UNKNOWN_ERROR;}state->set(STARTING);return OK;};if (tryAndReportOnError(setStarting) != OK) {return;}(new AMessage(kWhatStart, this))->post();
}
1.7 CCodec::onMessageReceived
调用CCodec::start方法。
void CCodec::onMessageReceived(const sp<AMessage> &msg) {// ...case kWhatStart: {// 调用start方法setDeadline(now, 1500ms, "start");start();break;}// ...
}
1.8 CCodec::start
该方法主要做了几件事:
调用Component的start方法,Codec2的Component都是基于SimpleC2Component实现的,SimpleC2Component中处理了一些状态管理的逻辑,不同的编解码组件继承了SimpleC2Component,实现生命周期回调方法,例如onInit等。
从CCodecConfig获取mOutputFormat或者inputFormat信息,前者是解码时会有,后者是编码才会有的。
调用CCodecBufferChannel::start。
void CCodec::start() {// ...// 调用Component start,Codec2的Component都是基于SimpleC2Component实现的,SimpleC2Component中处理了一些状态管理的逻辑// 不同的编解码最终实现都继承于SimpleC2Component,然后实现对应的生命周期回调方法做自己的事,由于我们H264的实现里没有做什么事,就不看了。c2_status_t err = comp->start();if (err != C2_OK) {mCallback->onError(toStatusT(err, C2_OPERATION_Component_start),ACTION_CODE_FATAL);return;}sp<AMessage> inputFormat;sp<AMessage> outputFormat;status_t err2 = OK;bool buffersBoundToCodec = false;{// CCodecConfig是在之前初始化的,里面有配置信息,这里mOutputFormat是解码时候才有的,而inputFormat则是编码的时候才有// mInputSurface是编码时候配置了InputSurface时传入的参数,表示编码数据来源于一个Surface// 我们之前在SurfaceFlinger章节说过,Surface是表示一个窗口,可以作为图像BufferQueue的一个生产者。Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);const std::unique_ptr<Config> &config = *configLocked;inputFormat = config->mInputFormat;outputFormat = config->mOutputFormat = config->mOutputFormat->dup();if (config->mInputSurface) {err2 = config->mInputSurface->start();config->mInputSurfaceDataspace = config->mInputSurface->getDataspace();}buffersBoundToCodec = config->mBuffersBoundToCodec;}if (err2 != OK) {mCallback->onError(err2, ACTION_CODE_FATAL);return;}// 调用CCodecBufferChannel::start,详见1.9err2 = mChannel->start(inputFormat, outputFormat, buffersBoundToCodec);if (err2 != OK) {mCallback->onError(err2, ACTION_CODE_FATAL);return;}// ... 更新状态std::map<size_t, sp<MediaCodecBuffer>> clientInputBuffers;// 根据input里numSlots数量,调用input->buffers->requestNewBuffer创建buffer填充clientInputBuffers。err2 = mChannel->prepareInitialInputBuffers(&clientInputBuffers);if (err2 != OK) {ALOGE("Initial preparation for Input Buffers failed");mCallback->onError(err2, ACTION_CODE_FATAL);return;}// 回调MediaCodec onStartCompletedmCallback->onStartCompleted();mChannel->requestInitialInputBuffers(std::move(clientInputBuffers));
}
1.9 CCodecBufferChannel::start
这个方法很长,主要是一些参数的初始化,分为编码和解码的情况,其中最重要的参数就是C2BlockPool和input/output。
以编码为例,input里面有一个buffers,这个buffers有多种类型,buffers会持有一个C2BlockPool来分配内存,而C2BlockPool又会通过Allocator分配C2Buffer,这里的Allocator也是在hal层,Allocator是通过AllocatorStore到hal获取到。
所以相当于在上层使用一个buffers来控制buffer到分配释放等,这个buffers类型比如LinearInputBuffers,GraphicInputBuffers,GraphicMetadataInputBuffers等,而buffers会通过C2BlockPool分配buffer,而C2BlockPool会通过Allocator到hal层获取buffer。
解码的情况类似,这里就不细说了。
status_t CCodecBufferChannel::start(const sp<AMessage> &inputFormat,const sp<AMessage> &outputFormat,bool buffersBoundToCodec) {C2StreamBufferTypeSetting::input iStreamFormat(0u);C2StreamBufferTypeSetting::output oStreamFormat(0u);C2ComponentKindSetting kind;C2PortReorderBufferDepthTuning::output reorderDepth;C2PortReorderKeySetting::output reorderKey;C2PortActualDelayTuning::input inputDelay(0);C2PortActualDelayTuning::output outputDelay(0);C2ActualPipelineDelayTuning pipelineDelay(0);C2SecureModeTuning secureMode(C2Config::SM_UNPROTECTED);// ... 参数初始化以及检测// 由C2AllocateStore来获取Allocation,而Allocation是用于分配buffer的,有不同类型的buffer,对应底层分配的内存可能也不同,例如dmastd::shared_ptr<C2AllocatorStore> allocatorStore = GetCodec2PlatformAllocatorStore();int poolMask = GetCodec2PoolMask();C2PlatformAllocatorStore::id_t preferredLinearId = GetPreferredLinearAllocatorId(poolMask);// 编码if (inputFormat != nullptr) {// ... 参数配置// 构造C2BlockPoolstd::shared_ptr<C2BlockPool> pool;{Mutexed<BlockPools>::Locked pools(mBlockPools);// ... 参数检查配置if ((poolMask >> pools->inputAllocatorId) & 1) {// 根据inputAllocatorId构造C2BlockPool,C2BlockPool里面持有Allocator,通过擦欧总Allocator管理Buffer的构建err = CreateCodec2BlockPool(pools->inputAllocatorId, nullptr, &pool);// ...} else {err = C2_NOT_FOUND;}// ...异常处理pools->inputPool = pool;}bool forceArrayMode = false;Mutexed<Input>::Locked input(mInput);// ...构造填充input对象// 其中input->buffers会根据buffer的类型不同而不同// 这里的关系是input->buffers会最终提供给CCodec分配Buffer的能力,而input->buffers是通过前面构造的C2BlockPool来获取或者释放buffer// 而C2BlockPool通过持有的Allocator来分配不同Buffer,不同的Buffer的区别在于底层可能使用不同的系统调用来分配内存,可能是dma之类的。input->buffers->setFormat(inputFormat);if (err == C2_OK) {input->buffers->setPool(pool);} else {// TODO: error}if (forceArrayMode) {input->buffers = input->buffers->toArrayMode(numInputSlots);}}// 解码,这里和上面编码类似,主要是初始化一些必要对象。if (outputFormat != nullptr) {sp<IGraphicBufferProducer> outputSurface;uint32_t outputGeneration;int maxDequeueCount = 0;{Mutexed<OutputSurface>::Locked output(mOutputSurface);maxDequeueCount = output->maxDequeueBuffers = numOutputSlots +reorderDepth.value + mRenderingDepth;outputSurface = output->surface ?output->surface->getIGraphicBufferProducer() : nullptr;if (outputSurface) {output->surface->setMaxDequeuedBufferCount(output->maxDequeueBuffers);}outputGeneration = output->generation;}bool graphic = (oStreamFormat.value == C2BufferData::GRAPHIC);C2BlockPool::local_id_t outputPoolId_;C2BlockPool::local_id_t prevOutputPoolId;{Mutexed<BlockPools>::Locked pools(mBlockPools);// ... 初始化BlockPools,和解码流程构造C2BlockPools类似。}Mutexed<Output>::Locked output(mOutput);// ... 构造output,和上面input类似// 如果接受输出是一个Surface,通知给Componentif (outputSurface) {mComponent->setOutputSurface(outputPoolId_,outputSurface,outputGeneration,maxDequeueCount);} else {// ...}// ...}// 编解码监测器初始化if (inputFormat || outputFormat) {Mutexed<PipelineWatcher>::Locked watcher(mPipelineWatcher);watcher->inputDelay(inputDelayValue).pipelineDelay(pipelineDelayValue).outputDelay(outputDelayValue).smoothnessFactor(kSmoothnessFactor);watcher->flush();}mInputMetEos = false;// 初始化buffer的锁mSync.start();return OK;
}
MediaCodec 获取buffer index
在创建并启动解码器后,我们会通过dequeueInputBuffer获取一个Buffer index,然后再通过getInputBuffers获取所有Buffer数组,然后根据索引在Buffer数组中获取buffer,往里面写入需要编码的数据,接下来我们来看看这个流程。
2.1 MediaCodec.dequeueInputBuffer
jni调用native_dequeueInputBuffer。
public final int dequeueInputBuffer(long timeoutUs) {// ...// 详见2.2int res = native_dequeueInputBuffer(timeoutUs);// ...return res;
}
2.2 android_media_MediaCodec_dequeueInputBuffer
调用JMediaCodec::dequeueInputBuffer
static jint android_media_MediaCodec_dequeueInputBuffer(JNIEnv *env, jobject thiz, jlong timeoutUs) {// ...size_t index;// JMediaCodec::dequeueInputBuffer,详见2.3status_t err = codec->dequeueInputBuffer(&index, timeoutUs);if (err == OK) {return (jint) index;}return throwExceptionAsNecessary(env, err, codec);
}
2.3 JMediaCodec::dequeueInputBuffer
C++层的MediaCodec,MediaCodec::dequeueInputBuffer
status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {// 详见2.4return mCodec->dequeueInputBuffer(index, timeoutUs);
}
2.4 MediaCodec::dequeueInputBuffer
发送kWhatDequeueInputBuffer消息。
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;// 发送kWhatDequeueInputBuffer消息,处理逻辑详见2.5if ((err = PostAndAwaitResponse(msg, &response)) != OK) {return err;}CHECK(response->findSize("index", index));return OK;
}
2.5 MediaCodec::onMessageReceived
调用handleDequeueInputBuffer处理,并且发送kWhatDequeueInputTimedOut消息来配置超时监测。
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {switch (msg->what()) {//...case kWhatDequeueInputBuffer:{sp<AReplyToken> replyID;CHECK(msg->senderAwaitsResponse(&replyID));// ...// 详见2.6if (handleDequeueInputBuffer(replyID, true /* new request */)) {break;}// ... 通过发送kWhatDequeueInputTimedOut消息配置超时监测break;}// ...}
}
2.6 MediaCodec::handleDequeueInputBuffer
通过dequeuePortBuffer获取buffer index,然后返回index。
bool MediaCodec::handleDequeueInputBuffer(const sp<AReplyToken> &replyID, bool newRequest) {// ...异常处理// 详见2.7ssize_t index = dequeuePortBuffer(kPortIndexInput);if (index < 0) {CHECK_EQ(index, -EAGAIN);return false;}// 返回indexsp<AMessage> response = new AMessage;response->setSize("index", index);response->postReply(replyID);return true;
}
2.7 MediaCodec::dequeuePortBuffer
mAvailPortBuffers里是两个int数组,分别给编码和解码使用,这里的int数组用于存储可以使用的buffer index。这个方法做的事就是从这个int数组里获取一个index,并且将它从数组里移除。
ssize_t MediaCodec::dequeuePortBuffer(int32_t portIndex) {CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);BufferInfo *info = peekNextPortBuffer(portIndex);if (!info) {return -EAGAIN;}// 从availBuffers获取可用buffer index,并且把他从数组中移除。 std::list<size_t> *availBuffers = &mAvailPortBuffers[portIndex];size_t index = *availBuffers->begin();CHECK_EQ(info, &mPortBuffers[portIndex][index]);availBuffers->erase(availBuffers->begin());// ...return index;
}
MediaCodec 获取input buffer数组
3.1 MediaCodec.getInputBuffers
public ByteBuffer[] getInputBuffers() {synchronized (mBufferLock) {// ...// java层有一个buffer数组缓存,第一次需要通过jni去获取,详见3.2if (mCachedInputBuffers == null) {cacheBuffersLocked(true /* input */);}// ...return mCachedInputBuffers;}
}
3.2 MediaCodec.cacheBuffersLocked
通过jni获取buffers,并缓存记录。
private void cacheBuffersLocked(boolean input) {ByteBuffer[] buffers = null;try {// 通过jni获取buffer,详见3.3buffers = getBuffers(input);invalidateByteBuffersLocked(buffers);} catch (IllegalStateException e) {// we don't get buffers in async mode}// ...// 缓存记录结果if (input) {mCachedInputBuffers = buffers;} else {mCachedOutputBuffers = buffers;}
}
3.3 JMediaCodec::getBuffers
jni方法通过调用JMediaCodec的getBuffers,而JMediaCodec又通过调用c++层的MediaCodec的getInputBuffers来获取buffers。
status_t JMediaCodec::getBuffers(JNIEnv *env, bool input, jobjectArray *bufArray) const {Vector<sp<MediaCodecBuffer> > buffers;// 详见3.4status_t err =input? mCodec->getInputBuffers(&buffers): mCodec->getOutputBuffers(&buffers);// ...将C++层的buffers通过jni关联给java层return OK;
}
3.4 MediaCodec::getInputBuffers
发送kWhatGetBuffers。
status_t MediaCodec::getInputBuffers(Vector<sp<MediaCodecBuffer> > *buffers) const {sp<AMessage> msg = new AMessage(kWhatGetBuffers, this);msg->setInt32("portIndex", kPortIndexInput);msg->setPointer("buffers", buffers);sp<AMessage> response;return PostAndAwaitResponse(msg, &response);
}
3.5 MediaCodec::onMessageReceived
我们目前流程是在获取inputBuffer,通过inputBuffer给编码器传入编码前的数据,而如果我们配置了InputSurface,以InputSurface为输入,则不需要获取Buffer。
通过CCodecBufferChannel::getInputBufferArray来获取Buffer数组。
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {// ...case kWhatGetBuffers:{// ...异常处理dstBuffers->clear();// 通过getInputBufferArray获取buffer,如果配置来InputSurface则不会走这个模式。详见3.6if (portIndex != kPortIndexInput || !mHaveInputSurface) {if (portIndex == kPortIndexInput) {mBufferChannel->getInputBufferArray(dstBuffers);} else {mBufferChannel->getOutputBufferArray(dstBuffers);}}mApiUsageMetrics.isArrayMode = true;(new AMessage)->postReply(replyID);break;}// ...
}
3.6 CCodecBufferChannel::getInputBufferArray
这里会调用input->buffers->getArray来获取Buffer,这里input->buffers是在start的过程中就已经初始化的,具体是在1.8节,调用mChannel->prepareInitialInputBuffers时候分配的,其中会循环调用buffers的requestNewBuffer方法,我们之前说过buffers里面有pool,而pool会通过allocator来分配buffer。
void CCodecBufferChannel::getInputBufferArray(Vector<sp<MediaCodecBuffer>> *array) {array->clear();Mutexed<Input>::Locked input(mInput);if (!input->buffers) {ALOGE("getInputBufferArray: No Input Buffers allocated");return;}if (!input->buffers->isArrayMode()) {input->buffers = input->buffers->toArrayMode(input->numSlots);}input->buffers->getArray(array);
}
MediacCodec queueInputBuffer
4.1 MediaCodec.queueInputBuffer
调用jni层native_queueInputBuffer
public final void queueInputBuffer(int index,int offset, int size, long presentationTimeUs, int flags)throws CryptoException {// ...try {// 详见4.2native_queueInputBuffer(index, offset, size, presentationTimeUs, flags);} catch (CryptoException | IllegalStateException e) {revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);throw e;}
}
4.2 android_media_MediaCodec_queueInputBuffer
和之前一样,jni会调用JMediaCodec的queueInputBuffer方法。
static void android_media_MediaCodec_queueInputBuffer(JNIEnv *env,jobject thiz,jint index,jint offset,jint size,jlong timestampUs,jint flags) {// ...// 详见4.3status_t err = codec->queueInputBuffer(index, offset, size, timestampUs, flags, &errorDetailMsg);throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL,codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
}
4.3 JMediaCodec::queueInputBuffer
调用MediaCodec的queueInputBuffer
status_t JMediaCodec::queueInputBuffer(size_t index,size_t offset, size_t size, int64_t timeUs, uint32_t flags,AString *errorDetailMsg) {//详见4.4return mCodec->queueInputBuffer(index, offset, size, timeUs, flags, errorDetailMsg);
}
4.4 MediaCodec::queueInputBuffer
发送kWhatQueueInputBuffer消息。
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;return PostAndAwaitResponse(msg, &response);
}
4.5 MediaCodec::onMessageReceived
调用onQueueInputBuffer处理任务。
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {//...case kWhatQueueInputBuffer:{// ...status_t err = UNKNOWN_ERROR;if (!mLeftover.empty()) {mLeftover.push_back(msg);size_t index;msg->findSize("index", &index);err = handleLeftover(index);} else {// 详见4.6err = onQueueInputBuffer(msg);}PostReplyWithError(replyID, err);break;}// ...
}
4.6 MediaCodec::onQueueInputBuffer
处理一些参数的检查和传递,这里分需要加密和不需要加密的两种场景,我们不看加密的逻辑,这里主要构建来一个新的buffer,把数据填充,并且调用attachBuffer把queue进来的buffer里面的数据拷贝到新建到buffer里。
最后调用queueInputBuffer。
status_t MediaCodec::onQueueInputBuffer(const sp<AMessage> &msg) {// ... 参数预处理和检查,将一些参数配置到buffer// ... 加密逻辑if (c2Buffer || memory) {sp<AMessage> tunings = NULL;if (msg->findMessage("tunings", &tunings) && tunings != NULL) {onSetParameters(tunings);}status_t err = OK;if (c2Buffer) {// 拷贝数据到新的buffererr = mBufferChannel->attachBuffer(c2Buffer, buffer);} else if (memory) {AString errorDetailMsg;err = mBufferChannel->attachEncryptedBuffer(memory, (mFlags & kFlagIsSecure), key, iv, mode, pattern,offset, subSamples, numSubSamples, buffer, &errorDetailMsg);if (err != OK && hasCryptoOrDescrambler()&& (mFlags & kFlagUseCryptoAsync)) {// ... 加密逻辑}} else {// error log}// ...}// ...if (hasCryptoOrDescrambler() && !c2Buffer && !memory) {// 需要加密场景} else {// 调用queueInputBuffer,详见4.7err = mBufferChannel->queueInputBuffer(buffer);// ...}// ...return err;
}
4.7 CCodecBufferChannel::queueInputBuffer
调用queueInputBufferInternal。
status_t CCodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {QueueGuard guard(mSync);if (!guard.isRunning()) {ALOGD("[%s] No more buffers should be queued at current state.", mName);return -ENOSYS;}return queueInputBufferInternal(buffer);
}
4.8 CCodecBufferChannel::queueInputBufferInternal
这里主要会解析buffer,然后将buffer里的信息封装到一个C2Work中,然后把C2Work存到一个C2Work数组,调用Component的queue来处理这个这个任务。
这里Component的queue会在hal层调用到对方的queue_nb方法,这里就是SimpleC2Component的::SimpleC2Component。
status_t CCodecBufferChannel::queueInputBufferInternal(sp<MediaCodecBuffer> buffer,std::shared_ptr<C2LinearBlock> encryptedBlock,size_t blockSize) {// ...获取buffer里的参数// ...构造一个C2Work数组和一个C2Work,后续最终会提交这个数组,将信息封装在C2Work中std::list<std::unique_ptr<C2Work>> items;std::unique_ptr<C2Work> work(new C2Work);work->input.ordinal.timestamp = timeUs;work->input.ordinal.frameIndex = mFrameIndex++;work->input.ordinal.customOrdinal = timeUs;work->input.buffers.clear();sp<Codec2Buffer> copy;bool usesFrameReassembler = false;if (buffer->size() > 0u) {Mutexed<Input>::Locked input(mInput);std::shared_ptr<C2Buffer> c2buffer;// 将buffer数据存到c2bufferif (!input->buffers->releaseBuffer(buffer, &c2buffer, false)) {return -ENOENT;}// ...if (input->frameReassembler) {usesFrameReassembler = true;input->frameReassembler.process(buffer, &items);} else {// ...// 将c2buffer存到C2Work里。work->input.buffers.push_back(c2buffer);if (encryptedBlock) {work->input.infoBuffers.emplace_back(C2InfoBuffer::CreateLinearBuffer(kParamIndexEncryptedBuffer,encryptedBlock->share(0, blockSize, C2Fence())));}}} else if (eos) {// ...}if (usesFrameReassembler) {// ...} else {work->input.flags = (C2FrameData::flags_t)flags;// TODO: fill info'swork->input.configUpdate = std::move(mParamsToBeSet);if (tunnelFirstFrame) {C2StreamTunnelHoldRender::input tunnelHoldRender{0u /* stream */,C2_TRUE /* value */};work->input.configUpdate.push_back(C2Param::Copy(tunnelHoldRender));}work->worklets.clear();work->worklets.emplace_back(new C2Worklet);// 将C2Work存到items里items.push_back(std::move(work));eos = eos && buffer->size() > 0u;}if (eos) {// ...}c2_status_t err = C2_OK;if (!items.empty()) {// ...// 将C2Work数组提交给Component(通过hal层到真正实现编解码逻辑实现),这里是SimpleC2Component::queue_nb,详见4.9err = mComponent->queue(&items);}if (err != C2_OK) {// ...} else {// ...释放buffer}feedInputBufferIfAvailableInternal();return err;
}
4.9 SimpleC2Component::queue_nb
这里把前面传过来的C2Work都放到mWorkQueue中,然后发送了kWhatProcess消息触发处理逻辑。
c2_status_t
SimpleC2Component::queue_nb(std::list<std::unique_ptr<C2Work>> *const items) {{Mutexed<ExecState>::Locked state(mExecState);if (state->mState != RUNNING) {return C2_BAD_STATE;}}bool queueWasEmpty = false;{Mutexed<WorkQueue>::Locked queue(mWorkQueue);queueWasEmpty = queue->empty();// 将items里面的数据放到mWorkQueue中while (!items->empty()) {queue->push_back(std::move(items->front()));items->pop_front();}}if (queueWasEmpty) {// 发送kWhatProcess,详见4.10 (new AMessage(WorkHandler::kWhatProcess, mHandler))->post();}return C2_OK;
}
4.10 SimpleC2Component::processQueue
这里主要就是会调用process方法,这个方法是由子类实现的,SimpleC2Component是用于管理流程框架的模版,它的子类来实现具体编解码逻辑。
例如H264的编解码就是由子类C2SoftAacDec和C2SoftAacEnc实现的,我们本节不会介绍编解码的细节,下一节会介绍H264编解码的一些核心原理。
bool SimpleC2Component::processQueue() {// ...{Mutexed<WorkQueue>::Locked queue(mWorkQueue);if (queue->empty()) {return false;}generation = queue->generation();drainMode = queue->drainMode();isFlushPending = queue->popPendingFlush();// 从队列中取出最前面的任务work = queue->pop_front();hasQueuedWork = !queue->empty();}if (isFlushPending) {// flush的回调,这里SimpleC2Component是一个处理流程的架构,最终需要由它的子类来实现对应生命周期所做的事。 c2_status_t err = onFlush_sm();// ...}if (!mOutputBlockPool) {// ...创建用于管理输出Buffer的Pool}// ...// ... input内buffer的检测// 调用 process,也是由子类实现的。 // 传入的参数是work和用于管理输出buffer的Poolprocess(work, mOutputBlockPool);// ...return hasQueuedWork;
}
小结
本节介绍了MediaCodec编解码的流程,从java层MediaCodec调用到JMediaCodec,然后调用C++层到MediaCodec,通过发送对应流程的消息,触发CCodec,而里面通过一个CCodecBufferChannel来管理这些逻辑,CCodecBufferChannel里面分input和output分别用于管理编解码,他们中都有一个buffers用于管理所有buffer,通过一个C2BlockPool来管理buffer的分配,C2BlockPool内有一个Allocator,会通过hal层进行最终的buffer分配释放。
CCodecBufferChannel工作会通过Component调用hal层,传参数到SimpleC2Component,SimpleC2Component的子类进行最终的编解码操作。
下一节我们会介绍H264编解码的核心原理。
相关文章:

Android音视频 MediaCodec框架-启动编码(4)
Android音视频 MediaCodec框架-启动编码 简述 上一节我们介绍了MediaCodec框架创建编码器流程,编解码的流程其实基本是一样的,只是底层的最终的实现组件不同,所以我们只看启动编码流程。 MediaCodec启动编码 从MediaCodec的start方法开始…...
# Go 语言中的 Interface 和 Struct
go package mainimport ("fmt" )// Girl 是一个接口,定义了所有"女性"类型都应该实现的方法 type Girl interface {call()introduce() }// Wife 结构体代表妻子 type wife struct {name stringage intyearsWed int }// call 方法…...
SSM与Springboot是什么关系? -----区别与联系
SSM(Spring Spring MVC MyBatis)和 Spring Boot 都是基于 Spring 框架的技术栈,但它们在使用方式、配置复杂度以及设计理念上有所不同。下面是 SSM 和 Spring Boot 之间的关系及主要区别: SSM (Spring Spring MVC MyBatis) 定…...

MATLAB小波变换图像融合系统
二、应用背景及意义 本课题利用小波变换进行图像的融合,然后对融合的结果进行图像质量的评价。所谓小波变换图像融合就是对多个的信息目标进行一系列的图像提取和合成,进而可以获得对同一个信息目标的更为精确、全面、可靠的高低频图像信息描述。并且也…...

nginx-安装和80端口映射多域名和ssl
一、安装所需插件 1、安装 gcc gcc是linux下的编译器,它可以编译 C,C,Ada,Object C和Java等语言。 yum -y install gcc 2、安装pcre、pcre-devel pcre是一个perl库,包括perl兼容的正则表达式库,nginx的http模块使用pcre来解析 正则表达式。…...
SVN小乌龟 create patch 和 apply patch 功能
在SVN(Subversion)版本控制系统中,使用“小乌龟”(TortoiseSVN)这个图形界面工具可以极大地简化SVN操作。TortoiseSVN中的“create patch”和“apply patch”是两个非常有用的功能,它们与版本控制中的补丁&…...

#MySQL `SELECT` 语句执行流程详解
在数据库操作中,MySQL 的 SELECT 语句是用于查询数据最常见的 SQL 语句之一。理解它的执行流程对数据库优化和性能提升具有至关重要的意义。本文将详细解析 SELECT 语句从发出请求到返回结果的每个步骤,并结合 MySQL 的架构为您提供深度理解。 ## 1. 连接…...

docker容器运行一段时间提示Failed to initialize NVML: Unknown Error
情况描述 服务器使用docker启动容器。启动以后一切正常也能跑程序。但是,在运行一段时间(2天左右不等),会发现gpu掉了。输入nvidia-smi提示 Failed to initialize NVML: Unknown Error 主要发生条件是,docker启动的…...

PPT自动化:快速更换PPT图片(如何保留原图片样式等参数更换图片)
文章目录 📖 介绍 📖🏡 演示环境 🏡📒 PPT更换图片 📒1. 安装 `python-pptx` 模块2. 加载PPT文件3. 查找并替换图片3.1 查找图片形状3.2 获取原图片的样式和位置3.3 替换图片4. 保存修改后的PPT文件5. 设置图片的相关参数5.1 设置透明度5.2 设置边框🚀 保留所有参…...

秒懂MVC, MVP, MVVM框架
框架的目标 关注点分离 (separation of concerns)低耦合 (Loose coupling)容易维护 (Maintainable)容易被测试 (Testable)...

IDEA社区版如何用tomcat运行war包
前言 我们在使用IDEA开发Java应用时,有时候需要运行的不是jar包,而是war包,但IDEA社区版默认是不支持tomcat容器的,无法直接运行war包。不过好在我们有一个强大的插件来支持我们使用tomcat,那么,我们应该如…...
如何使用 Git Cherry-Pick 和 Reset 处理误提交,并确保安全回滚
在开发过程中,偶尔会遇到不小心将功能开发提交到错误分支上的情况。假设我们计划在 10 月 24 号上线某些功能,但却不小心在 10 月 17 号的上线分支上进行了开发。为了解决这个问题并将误提交的内容移到正确的分支上,我们可以借助 Git 的一些功…...

Goland 搭建Gin脚手架
一、使用编辑器goland 搭建gin 打开编辑器 新建项目后 点击 create 二、获得Gin框架的代码 命令行安装 go get -u github.com/gin-gonic/gin 如果安装不上,配置一下环境 下载完成 官网git上下载 这样就下载完成了。、 不过这种方法需要设置一下GOPATH 然后再执…...
Java Spring的高级装配
1.profile与bean 1.1 profile 如果我们在配置类中装配一个bean,但是这个bean与环境相关怎么办? 比如有一个类,它在开发环境采取一种模式,但是到了生产环境,有需要使用另一种环境。 当然,你可能会说&…...
分布式光伏发电系统电气一次部分设计(开题报告2)
毕业论文(设计)开题报告 题目 分布式光伏发电系统电气一次部分设计 题目类别 毕业设计 姓名 专业 班级 学号 一、选题背景及依据(简述国内外研究状况和相关领域中已有的研究成果(文献综述),选题目的、意义,列出主要参考文献) (一)选题背景与依据 选题背景与依据: …...
【设计模式-迪米特法则】
迪米特法则(Law of Demeter,LoD),也称为最少知识原则(Principle of Least Knowledge),是一种面向对象编程中的设计原则。它的核心思想是:一个对象应当尽可能少地了解其他对象&#x…...
Webpack安装
全局安装 npm install -g webpack webpack-cli安装后查看版本号: webpack -v初始化项目 npm init -yJS打包 webpack目录下创建配置文件webpack.config.js 以下配置的意思是:读取当前项目目录下src文件夹中的main.js(入口文件)…...

前端开发学习(一)VUE框架概述
一、MVC模式与MVVM模式 1.1mvc模式 MVC模式是移动端应用广泛的软件架构之一,MVC模式将应用程序划分为3部分:Model(数据模型)、View(用户界面视图)和Controller(控制器)。MVC模式的执行过程是将View层展示给用户,也就是通过 HTML页面接受用户动作&#…...

Linux操作系统的背景、发展历程及对比分析
1. UNIX发展历史 unix_百度百科 UNIX操作系统作为现代操作系统的奠基石,其发展历史可以追溯到20世纪60年代末。1969年,贝尔实验室的Ken Thompson、Dennis Ritchie以及他们的同事们为了实现一种多任务的、可移植的、简洁而高效的操作系统,开发…...
gaussdb 基础管理 数据库 表 用户 模式 权限 存储过程
数据库database #创建数据库,指定字符集UTF8,缺省情况下新数据库将通过复制标准系统数据库template0来创建,且仅支持使用template0来创建。 CREATE DATABASE devdb ENCODING UTF8 template template0; CREATE DATABASE testdb; 标识符的命名…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...

深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...

群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...

什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...