Codec2 — это программная платформа для мультимедиа в Android. Это промежуточное программное обеспечение MediaCodec. Оно подключается к уровню MediaCodec Native и предоставляет новые стандарты API для базового кодека чипа. Другими словами, после адаптации к Codec2 вы можете перейти. MediaCodec использует возможности аппаратного кодирования и декодирования чипа для выполнения некоторых функций, связанных с мультимедиа. В этой статье сначала объясняются интерфейсы, которые необходимо реализовать для адаптации Codec2, снизу вверх, а затем анализируется процесс MediaCodec сверху вниз, чтобы проанализировать, как эти интерфейсы вызываются. В основном поймите следующие две основные линии
Прежде чем приступить к работе, вам потребуются следующие предварительные знания:
Давайте рассмотрим реализацию программного кодировщика Hevc в Android в качестве примера, чтобы проанализировать, как адаптировать интерфейс Codec2. Сначала давайте посмотрим на базовую архитектуру codec2, которая разделена на 4 слоя. Первый уровень — это sfplugin, который отвечает за это. для связи с разгонным блоком. Для стыковки страха ниже приведен HIDL, уровень аппаратной абстракции каждого компонента. Далее идет ядро, которое инкапсулирует интерфейс, который должен реализовать компонент. Наконец, здесь мы берем программный кодировщик Hevc. Ниже приведен пример конкретной библиотеки кодирования и декодирования. Программный кодер вызывает интерфейс, связанный с ihevce.
Androidвcodec2каталог вframeworks/av/media/codec2
.
├── Android.mk
├── components # Компоненты приспособления, такие как h264, программный кодек hevc и т.п., называются HIDL.
├── core # ядро codec2, стыковочный компонент
├── docs
├── faultinjection
├── fuzzer
├── hidl # реализация полуслойного слоя
├── OWNERS
├── sfplugin # Стыковочный слой со страхом сцены
├── TEST_MAPPING
├── tests
└── vndk
Базовый уровень организует работу компонентов.,Здесь мы сначала анализируем основной слой,Основные файлы::core/include/C2Component.h
,который включает в себяC2Component
иC2ComponentInterface
два класса
C2Component
определяет интерфейс, который должен реализовать компонент,Определяется следующим образом,Здесь нужнососредоточиться Два важных интерфейса
queue_nb
:Может рассматриваться как отправка кадров/Потоковый интерфейс,Перед кодированием и декодированием,Отправьте необработанные данные, которые необходимо обработать,Теперь интерфейс должен быть спроектирован какнеблокирующийизonWorkDone_nb
:Когда один кадр данных будет обработан, он будетперезвонить Интерфейсclass C2Component {
public:
class Listener {
public:
virtual void onWorkDone_nb(std::weak_ptr<C2Component> component,
std::list<std::unique_ptr<C2Work>> workItems) = 0;
virtual void onTripped_nb(std::weak_ptr<C2Component> component,
std::vector<std::shared_ptr<C2SettingResult>> settingResult) = 0;
virtual void onError_nb(std::weak_ptr<C2Component> component,
uint32_t errorCode) = 0;
virtual ~Listener() = default;
};
...
/* Queues up work for the component. */
virtual c2_status_t queue_nb(std::list<std::unique_ptr<C2Work>>* const items) = 0;
/*
* Announces a work to be queued later for the component. This reserves a slot for the queue
* to ensure correct work ordering even if the work is queued later.
*/
virtual c2_status_t announce_nb(const std::vector<C2WorkOutline> &items) = 0;
enum flush_mode_t : uint32_t {
/// flush work from this component only
FLUSH_COMPONENT,
/// flush work from this component and all components connected downstream from it via
/// tunneling
FLUSH_CHAIN = (1 << 16),
};
/*
* Discards and abandons any pending work for the component, and optionally any component
* downstream.
*/
virtual c2_status_t flush_sm(flush_mode_t mode, std::list<std::unique_ptr<C2Work>>* const flushedWork) = 0;
enum drain_mode_t : uint32_t {
DRAIN_COMPONENT_WITH_EOS,
DRAIN_COMPONENT_NO_EOS = (1 << 0),
DRAIN_CHAIN = (1 << 16),
};
/*
* Drains the component, and optionally downstream components. This is a signalling method;
* as such it does not wait for any work completion.
* Marks last work item as "drain-till-here", so component is notified not to wait for further
* work before it processes work already queued. This method can also used to set the
* end-of-stream flag after work has been queued. Client can continue to queue further work
* immediately after this method returns.
*/
virtual c2_status_t drain_nb(drain_mode_t mode) = 0;
// STATE CHANGE METHODS
// =============================================================================================
virtual c2_status_t start() = 0;
virtual c2_status_t stop() = 0;
virtual c2_status_t reset() = 0;
virtual c2_status_t release() = 0;
virtual std::shared_ptr<C2ComponentInterface> intf() = 0;
virtual ~C2Component() = default;
};
TODO
SimpleC2Component
предоставляет компонентизвыполнить,Различные последующие реализации должны только наследовать эту реализацию.,вважныйиз Дизайн интерфейса следующий。можно увидетьSimpleC2Component
унаследованныйC2Component
,И будет реализован механизм асинхронного обмена сообщениями AMessage и AHandle.,Соответствующие функции анализируются ниже.
class SimpleC2Component
: public C2Component, public std::enable_shared_from_this<SimpleC2Component> {
public:
explicit SimpleC2Component(
const std::shared_ptr<C2ComponentInterface> &intf);
virtual ~SimpleC2Component();
// Установите соответствующий обратный вызов
virtual c2_status_t setListener_vb(
const std::shared_ptr<Listener> &listener, c2_blocking_t mayBlock) override;
// Реализовать интерфейс очереди_nb
virtual c2_status_t queue_nb(std::list<std::unique_ptr<C2Work>>* const items) override;
...
// Функция, которая фактически обрабатывает кадр данных
bool processQueue();
protected:
...
// Конкретный процесс кодирования и декодирования реализуется подклассами.
virtual void process(
const std::unique_ptr<C2Work> &work,
const std::shared_ptr<C2BlockPool> &pool) = 0;
...
private:
const std::shared_ptr<C2ComponentInterface> mIntf;
class WorkHandler : public AHandler {
public:
...
protected:
// Интерфейс асинхронной обработки сообщений
void onMessageReceived(const sp<AMessage> &msg) override;
private:
...
};
...
struct ExecState {
ExecState() : mState(UNINITIALIZED) {}
int mState;
std::shared_ptr<C2Component::Listener> mListener;
};
// государственная машина
Mutexed<ExecState> mExecState;
sp<ALooper> mLooper;
sp<WorkHandler> mHandler;
class WorkQueue {
...
}
// Ожидающая рабочая очередь
Mutexed<WorkQueue> mWorkQueue;
...
Этот интерфейс устанавливает прослушиватель, переданный выше, в конечный автомат, а затем в подходящее время вызывает соответствующий интерфейс.
c2_status_t SimpleC2Component::setListener_vb(
const std::shared_ptr<C2Component::Listener> &listener, c2_blocking_t mayBlock) {
mHandler->setComponent(shared_from_this());
Mutexed<ExecState>::Locked state(mExecState);
...
state->mListener = listener;
return C2_OK;
}
Эта функция реализуетC2Component.queue_nb
,На самом деле это просто ставит работу в очередь,И инициировать асинхронное сообщение и вернуть,Соответствуют требованиям неблокирующего
c2_status_t SimpleC2Component::queue_nb(std::list<std::unique_ptr<C2Work>> * const items) {
...
bool queueWasEmpty = false;
{
Mutexed<WorkQueue>::Locked queue(mWorkQueue);
queueWasEmpty = queue->empty();
while (!items->empty()) {
queue->push_back(std::move(items->front()));
items->pop_front();
}
}
if (queueWasEmpty) {
// Инициирование сообщения обработки данных завершит обработку кадров в mWorkQueue.
(new AMessage(WorkHandler::kWhatProcess, mHandler))->post();
}
return C2_OK;
}
Функция Интерфейс асинхронной обработки сообщений,Эта функция в конечном итоге будет вызвана при отправке соответствующего сообщения.,здесьprocessQueue()
Функция заключается в обработке одного кадра данных,Затем сообщите, есть ли в текущей очереди необработанные данные.
void SimpleC2Component::WorkHandler::onMessageReceived(const sp<AMessage> &msg) {
...
switch (msg->what()) {
case kWhatProcess: {
if (mRunning) {
// Если процессQueue() возвращает true, повторно отправьте сообщение kWhatProcess.
if (thiz->processQueue()) {
(new AMessage(kWhatProcess, this))->post();
}
} else {
ALOGV("Ignore process message as we're not running");
}
break;
}
...
}
}
Эта функция завершает фактическую операцию кодирования и декодирования.,Код выглядит следующим образом,вprocess
иonWorkDone_nb
Всереализовано подклассами,workНачни с того, что было раньшеизmWorkQueue
Вынуть из очереди,позвони еще разprocess
Процесс,process
это виртуальная функция,реализовано подклассами,Обработка завершенапослепозвони еще разlistener->onWorkDone_nb
Событие завершения обработки уведомленияonWorkDone_nb
такжеэто виртуальная функция,реализовано подклассами
bool SimpleC2Component::processQueue() {
...
bool hasQueuedWork = false;
{
Mutexed<WorkQueue>::Locked queue(mWorkQueue);
if (queue->empty()) {
return false;
}
...
work = queue->pop_front();
hasQueuedWork = !queue->empty();
}
if (!mOutputBlockPool) {
c2_status_t err = [this] {
...
std::shared_ptr<C2BlockPool> blockPool;
err = GetCodec2BlockPool(poolId, shared_from_this(), &blockPool);
ALOGD("Using output block pool with poolID %llu => got %llu - %d",
(unsigned long long)poolId,
(unsigned long long)(
blockPool ? blockPool->getLocalId() : 111000111),
err);
if (err == C2_OK) {
// Какова функция mOutputBlockPool?
mOutputBlockPool = std::make_shared<BlockingBlockPool>(blockPool);
}
return err;
}();
...
}
...
process(work, mOutputBlockPool);
...
// Проверка обработанного количества в рабочей цепочке на самом деле предназначена для того, чтобы увидеть, успешно ли обрабатывается прогресс?
if (work->workletsProcessed != 0u) {
queue.unlock();
Mutexed<ExecState>::Locked state(mExecState);
ALOGV("returning this work");
std::shared_ptr<C2Component::Listener> listener = state->mListener;
state.unlock();
listener->onWorkDone_nb(shared_from_this(), vec(work));
} else {
...
}
return hasQueuedWork;
}
Анализ нижеprocess
извыполнить,В качестве примера взят программный кодировщик Android Hevc.,Классы наследуются отSimpleC2Component
,Реализовано следующим образом,Основной процесс — вынуть входной буфер из работы,Затем закодируйте кадр,Затем установите вывод на работу
struct C2SoftHevcEnc : public SimpleC2Component {
...
void process(const std::unique_ptr<C2Work>& work,
const std::shared_ptr<C2BlockPool>& pool) override;
...
}
void C2SoftHevcEnc::process(const std::unique_ptr<C2Work>& work,
const std::shared_ptr<C2BlockPool>& pool) {
...
std::shared_ptr<const C2GraphicView> view;
std::shared_ptr<C2Buffer> inputBuffer = nullptr;
...
if (!work->input.buffers.empty()) {
// Получите вклад в работу buffer
inputBuffer = work->input.buffers[0];
// Привязать буфер для просмотра
view = std::make_shared<const C2GraphicView>(
inputBuffer->data().graphicBlocks().front().map().get());
...
}
...
ihevce_inp_buf_t s_encode_ip{};
ihevce_out_buf_t s_encode_op{};
...
// Преобразовать представление в s_encode_ip
status = setEncodeArgs(&s_encode_ip, view.get(), workIndex);
...
memset(&s_encode_op, 0, sizeof(s_encode_op));
...
if (inputBuffer) {
// Возьмите s_encode_ip в качестве входных данных для завершения одного кадра кодирования hevc.
err = ihevce_encode(mCodecCtx, &s_encode_ip, &s_encode_op);
...
}
...
if (s_encode_op.i4_bytes_generated) {
// s_encode_op имеет данные, настройте его в работу
finishWork(s_encode_op.u8_pts, work, pool, &s_encode_op);
}
}
Анализ нижеfinishWork
,Видно, что выходной буфер мягкого кодирования в конечном итоге копируется в C2Buffer.,Наконец-то вставилwork->worklets.front()->output.buffers
очередь
void C2SoftHevcEnc::finishWork(uint64_t index,
const std::unique_ptr<C2Work>& work,
const std::shared_ptr<C2BlockPool>& pool,
ihevce_out_buf_t* ps_encode_op) {
std::shared_ptr<C2LinearBlock> block;
...
// Получить линейный блок
c2_status_t status =
pool->fetchLinearBlock(ps_encode_op->i4_bytes_generated, usage, &block);
...
// Блок карты для просмотра
C2WriteView wView = block->map().get();
...
// Скопируйте содержимое выходного буфера в блок
memcpy(wView.data(), ps_encode_op->pu1_output_buf,
ps_encode_op->i4_bytes_generated);
// Создайте C2Buffer из блока
std::shared_ptr<C2Buffer> buffer =
createLinearBuffer(block, 0, ps_encode_op->i4_bytes_generated);
...
// Включите буфер в работу
auto fillWork = [buffer](const std::unique_ptr<C2Work>& work) {
work->worklets.front()->output.flags = (C2FrameData::flags_t)0;
work->worklets.front()->output.buffers.clear();
work->worklets.front()->output.buffers.push_back(buffer);
work->worklets.front()->output.ordinal = work->input.ordinal;
work->workletsProcessed = 1u;
};
if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) {
fillWork(work);
...
} else {
finish(index, fillWork);
}
}
C2Component
изначальствослойдаHIDLслой,Можно понимать как слой HAL Android.,Интерфейс, унаследованный заголовочным файлом этого уровня, динамически генерируется с помощью языка под названием HIDL (язык определения аппаратного интерфейса).,Вывод в выходной каталог,Например, этовIComponent
Заголовочный файл находится по адресу:
out/soong/.intermediates/hardware/interfaces/media/c2/1.0/android.hardware.media.c2@1.0_genc++_headers/gen/android/hardware/media/c2/1.0/IComponent.h
Его определение следующее
struct IComponent : public ::android::hidl::base::V1_0::IBase {
typedef ::android::hardware::details::i_tag _hidl_tag;
static const char* descriptor;
virtual bool isRemote() const override { return false; }
virtual ::android::hardware::Return<::android::hardware::media::c2::V1_0::Status> queue(const ::android::hardware::media::c2::V1_0::WorkBundle& workBundle) = 0;
using flush_cb = std::function<void(::android::hardware::media::c2::V1_0::Status status, const ::android::hardware::media::c2::V1_0::WorkBundle& flushedWorkBundle)>;
virtual ::android::hardware::Return<void> flush(flush_cb _hidl_cb) = 0;
...
}
переписыватьсяизHIDLФайлhardware/interfaces/media/c2/1.0/IComponent.hal
interface IComponent {
queue(WorkBundle workBundle) generates (Status status);
flush(
) generates (
Status status,
WorkBundle flushedWorkBundle
);
...
}
Component
Унаследовано отIComponent.h
,Он также будет реализовывать интерфейс,проходитьComponent
就可以вызовначальство面讲到изC2Component
,Его определение следующее,Давайте сосредоточимся на еговqueue
иListener
。
struct Component : public IComponent,
public std::enable_shared_from_this<Component> {
...
virtual Return<Status> queue(const WorkBundle& workBundle) override;
...
protected:
...
struct Listener;
...
}
queue
из Реализовано следующим образом,здесьизmComponent
На самом деле этоC2Component
,Как это реализовать здесь пока не показано.,Анализ позже,Возьмите мягкое кодирование в качестве примера.,поэтомуздесьпоследний звонокизqueue_nb
на самом делевызовиздаSimpleC2Component.queue_nb
Return<Status> Component::queue(const WorkBundle& workBundle) {
std::list<std::unique_ptr<C2Work>> c2works;
if (!objcpy(&c2works, workBundle)) {
return Status::CORRUPTED;
}
// Register input buffers.
for (const std::unique_ptr<C2Work>& work : c2works) {
if (work) {
InputBufferManager::
registerFrameData(mListener, work->input);
}
}
/* Вызовите C2Component.queue_nb, если компонент наследуется от SimpleC2Component,
* Затем вызовите SimpleC2Component.queue_nb.
* Обратите внимание, что это неблокирующий
*/
return static_cast<Status>(mComponent->queue_nb(&c2works));
}
Посмотрите еще разListener
изопределение,здесьфактическое наследованиеиздаC2Component::Listener
,и чтобыonWorkDone_nb
Реализовано,потому чтоonWorkDone_nb
даперезвонитьфункция,поэтому由вызов者выполнитьтакжеда Как и ожидалосьиз。здесь На самом деледавызов了другойперезвонитьlistener->onWorkDone
struct Component::Listener : public C2Component::Listener {
Listener(const sp<Component>& component) :
mComponent(component),
mListener(component->mListener) {
}
...
virtual void onWorkDone_nb(
std::weak_ptr<C2Component> /* c2component */,
std::list<std::unique_ptr<C2Work>> c2workItems) override {
...
sp<IComponentListener> listener = mListener.promote();
if (listener) {
WorkBundle workBundle;
// Копировать в работуBundle
sp<Component> strongComponent = mComponent.promote();
beginTransferBufferQueueBlocks(c2workItems, true);
if (!objcpy(&workBundle, c2workItems, strongComponent ?
&strongComponent->mBufferPoolSender : nullptr)) {
...
}
// перезвонить
Return<void> transStatus = listener->onWorkDone(workBundle);
...
endTransferBufferQueueBlocks(c2workItems, true, true);
}
}
Вышеупомянутый прослушиватель - это просто определение,Это также зависит от того, где объявлен слушатель.,И когда он был зарегистрирован. Сначала первый вопрос,ListenerзаявлениедасуществоватьSimpleC2Component::ExecState.mListener
,если бы толькоунаследованныйSimpleC2Component
Внутри есть этот член,второй вопрос,зарегистрироватьсядасуществоватьComponent::initListener
функциясередина,Определяется следующим образом,такой же Возьмите мягкое кодирование в качестве примера.,здесьmComponent->setListener_vb
вызовдействительныйдаSimpleC2Component.setListener_vb
void Component::initListener(const sp<Component>& self) {
std::shared_ptr<C2Component::Listener> c2listener =
std::make_shared<Listener>(self);
/* Вызовите C2Component.setListener_vb, если компонент наследуется от SimpleC2Component,
* Затем вызовите SimpleC2Component.setListener_vb.
*/
c2_status_t res = mComponent->setListener_vb(c2listener, C2_DONT_BLOCK);
...
}
Для HIDL существует длительный процесс вызова. Давайте пока остановимся на этом. Позже мы проанализируем его сверху вниз через MediaCodec, чтобы увидеть, как вызывать HIDL.
MediaCodec — это модуль для кодирования и декодирования мультимедиа на уровне приложения Android. Он разделен на уровень Java и уровень CPP. Здесь мы начинаем только с уровня cpp.
Сначала давайте посмотрим, как используется MediaCodec. Поскольку MediaCodec также основан на механизме AMessage, сначала необходимо создать ALooper, а затем передать его в MediaCodec. Ниже приведен пример создания кодировщика Hevc. Псевдокод выглядит следующим образом.
sp<android::ALooper> looper = new android::ALooper; // Создать ALooper
looper->setName("TestLooper");
looper->start();
// Создайте кодировщик и автоматически найдите подходящие компоненты кодера
mMediaCodec = MediaCodec::CreateByType(looper, "video/hevc", true);
mMediaCodec->start();
for (uint32_t i = 0; i < 100; i++) {
// Получить индекс входного буфера из MediaCodec
mMediaCodec->dequeueInputBuffer(&inputBufIdx, sTimeOut);
// Получить входной буфер
sp<MediaCodecBuffer> inputBuf;
mMediaCodec->getInputBuffer(inputBufIdx, &inputBuf);
// Записать YUV во входной буфер
int readSize = writeYUVToBuffer(inputBuf->data(), inputBuf->size());
uint32_t flags = 0;
if (readSize == 0) {
flags = BUFFER_FLAG_END_OF_STREAM;
}
// Отправить входной буфер обратно в MediaCodec
mMediaCodec->queueInputBuffer(inputBufIdx, 0, readSize, getCurTimeUs(), flags);
// Получить индекс выходного буфера из MediaCodec
size_t outputBufIdx, outputOffset, outputSize;
mMediaCodec->dequeueOutputBuffer(&outputBufIdx, &outputOffset, &outputSize,
&outputPts, &outputFlags, sTimeOut);
// Получить выходной буфер
sp<MediaCodecBuffer> outputBuf;
mMediaCodec->getOutputBuffer(outputBufIdx, &outputBuf);
// Записать поток кода в файл
writeStreamToFile(outputBuf);
// КMediaCodecОсвободить выходной буфер
mMediaCodec->releaseOutputBuffer(outputBufIdx)
}
Сначала проанализируйтеqueueInputBuffer
,Посмотрите, как YUV отправляется на определенные компоненты кодировщика.,Вы можете видеть, что на самом деле здесь отправляется только асинхронное сообщение.,Воляindex
Отправьте это,а потом позвониPostAndAwaitResponse
блокировка ожиданияответ на сообщение
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);
}
Посмотрите еще раз на функцию обработки сообщений MediaCodec.,потому чтоqueueInputBuffer
даблокировка ожиданияиз,поэтомуздесь要вызовPostReplyWithError
после,queueInputBuffer
Только что вернулся,здесьпозжедавызов了onQueueInputBuffer
функция
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
...
case kWhatQueueInputBuffer:
{
sp<AReplyToken> replyID;
CHECK(msg->senderAwaitsResponse(&replyID));
...
status_t err = UNKNOWN_ERROR;
if (!mLeftover.empty()) {
mLeftover.push_back(msg);
size_t index;
msg->findSize("index", &index);
err = handleLeftover(index);
} else {
err = onQueueInputBuffer(msg);
}
// ответ на сообщение
PostReplyWithError(replyID, err);
break;
}
...
}
}
Анализ нижеonQueueInputBuffer
,можно увидетьздесьдействительныйдапроходитьindexПолучатьMediaCodecBuffer
,и доставленоmBufferChannel->queueInputBuffer
середина,Обратите внимание на выполнение самой внешней части здесь.слойизqueueInputBuffer
все еще ждуответ на сообщение,Поэтому он заблокирован до сих пор
struct MediaCodec : public AHandler {
private:
...
// Два порта, вход 0, выход 1
std::vector<BufferInfo> mPortBuffers[2];
...
std::shared_ptr<BufferChannelBase> mBufferChannel;
}
status_t MediaCodec::onQueueInputBuffer(const sp<AMessage> &msg) {
size_t index;
...
CHECK(msg->findSize("index", &index));
...
BufferInfo *info = &mPortBuffers[kPortIndexInput][index];
sp<MediaCodecBuffer> buffer = info->mData;
...
if (hasCryptoOrDescrambler() && !c2Buffer && !memory) {
// безопасный процесс кодирования
...
} else {
// Процесс кодирования, не связанный с безопасностью
mBufferChannel->queueInputBuffer(buffer);
}
...
}
Посмотрите ниже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;
}
снова вернутьсяonMessageReceived
функция,в конечном итоге будет вызванdequeuePortBuffer
функция,можно увидетьMediaCodecсерединаесть одинmAvailPortBuffers
связанный список,Сохраняет индекс доступного в данный момент буфера.,При удалении из очереди вам нужно удалить только первый индекс из этого связанного списка.
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
...
case kWhatDequeueInputBuffer:
{
sp<AReplyToken> replyID;
CHECK(msg->senderAwaitsResponse(&replyID));
handleDequeueInputBuffer(replyID, true /* new request */);
...
break;
}
...
}
}
bool MediaCodec::handleDequeueInputBuffer(const sp<AReplyToken> &replyID, bool newRequest) {
...
ssize_t index = dequeuePortBuffer(kPortIndexInput);
...
sp<AMessage> response = new AMessage;
response->setSize("index", index);
// ответное сообщение
response->postReply(replyID);
return true;
}
struct MediaCodec : public AHandler {
private:
...
// Хранить доступный индекс
List<size_t> mAvailPortBuffers[2];
...
}
ssize_t MediaCodec::dequeuePortBuffer(int32_t portIndex) {
...
List<size_t> *availBuffers = &mAvailPortBuffers[portIndex];
size_t index = *availBuffers->begin();
...
availBuffers->erase(availBuffers->begin());
return index;
}
Продолжить анализavailBuffers
дакогда обновлятьиз,ПроверятьupdateBuffers
функция,Было обнаружено, что об индексе сообщалось посредством асинхронных сообщений.,updateBuffers
ввод можно обновитьочередьивыходочередь,Мы смотрим только на входную очередь,Обнаружитьдасуществовать收到kWhatFillThisBuffer
Обновление новостейиз
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));
}
// Поместите буфер в очередь с индексом index
mPortBuffers[portIndex][index].mData = buffer;
}
// Поместить индекс в доступный связанный список
mAvailPortBuffers[portIndex].push_back(index);
return index;
}
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
...
case kWhatFillThisBuffer:
{
updateBuffers(kPortIndexInput, msg);
...
break;
}
...
}
Пока что вы можете найти,Доступность входных и выходных буферов в MediaCodec уведомляется асинхронными сообщениями.,И асинхронные сообщениядаконецслойизкомпонентыпроходитьперезвонитьMediaCodecиз Отправка интерфейсаиз,Эта часть кода находится вBufferCallback
середина,на самом делеBufferCallback
даунаследованныйCodecBase::BufferCallback
добрый,и реализовал интерфейс,можно увидетьвходитьивыходbufferВседасуществоватьздесьперезвонитьиз
class BufferCallback : public CodecBase::BufferCallback {
public:
explicit BufferCallback(const sp<AMessage> ¬ify);
virtual ~BufferCallback() = default;
virtual void onInputBufferAvailable(
size_t index, const sp<MediaCodecBuffer> &buffer) override;
virtual void onOutputBufferAvailable(
size_t index, const sp<MediaCodecBuffer> &buffer) override;
private:
const sp<AMessage> mNotify;
};
BufferCallback::BufferCallback(const sp<AMessage> ¬ify)
: mNotify(notify) {}
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();
}
CodecBase
на самом делесновадаcodec2винтерфейс,Принадлежит модулю sfplugin,Из-за нехватки места мы пока не будем здесь анализировать дальше.
Получение выходного буфера и получение входного буфера — это процесс. Они оба отправляют сообщение и ждут ответа. Здесь выходной буфер — это данные, обрабатываемые базовым компонентом, поэтому базовый компонент также необходимо уведомить.
status_t MediaCodec::dequeueOutputBuffer(
size_t *index,
size_t *offset,
size_t *size,
int64_t *presentationTimeUs,
uint32_t *flags,
int64_t timeoutUs) {
sp<AMessage> msg = new AMessage(kWhatDequeueOutputBuffer, this);
msg->setInt64("timeoutUs", timeoutUs);
sp<AMessage> response;
status_t err;
if ((err = PostAndAwaitResponse(msg, &response)) != OK) {
return err;
}
CHECK(response->findSize("index", index));
CHECK(response->findSize("offset", offset));
CHECK(response->findSize("size", size));
CHECK(response->findInt64("timeUs", presentationTimeUs));
CHECK(response->findInt32("flags", (int32_t *)flags));
return OK;
}
Получить выходной буфери Получить входной буфертакой же,ВседавызовизdequeuePortBuffer
функция,Просто параметры передаваемые разные,также就даздесьвсе ещедаотmAvailPortBuffers
очередьсередина获取以及Обработка завершенаизbuffer index
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
...
case kWhatDequeueOutputBuffer:
{
sp<AReplyToken> replyID;
CHECK(msg->senderAwaitsResponse(&replyID));
...
handleDequeueOutputBuffer(replyID, true /* new request */))
...
break;
}
...
}
}
bool MediaCodec::handleDequeueOutputBuffer(const sp<AReplyToken> &replyID, bool newRequest) {
...
} else {
sp<AMessage> response = new AMessage;
...
dequeuePortBuffer(kPortIndexOutput);
...
response->postReply(replyID);
}
}
Получить входной буфервсе ещедавызовupdateBuffersруководитьmAvailPortBuffers
извозобновлять,толькоздесьдаотkWhatDrainThisBuffer
возобновлятьиз,и отправь это сообщениеиз地方такжедадругойonOutputBufferAvailable
перезвонить
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
...
case kWhatDrainThisBuffer:
{
...
updateBuffers(kPortIndexInput, msg);
...
break;
}
...
}
void BufferCallback::onOutputBufferAvailable(
size_t index, const sp<MediaCodecBuffer> &buffer) {
sp<AMessage> notify(mNotify->dup());
notify->setInt32("what", kWhatDrainThisBuffer);
notify->setSize("index", index);
notify->setObject("buffer", buffer);
notify->post();
}
Освободить выходной Процесс относительно прост,Также отправьте асинхронное сообщение, а затем заблокируйте и подождите.,существовать消息处理середина ВоляmPortBuffers
очередьпереписыватьсяизbufferПрозрачный,и наконец позвониmBufferChannel->discardBuffer
通知конецслойкомпоненты
status_t MediaCodec::releaseOutputBuffer(size_t index) {
sp<AMessage> msg = new AMessage(kWhatReleaseOutputBuffer, this);
msg->setSize("index", index);
sp<AMessage> response;
return PostAndAwaitResponse(msg, &response);
}
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
...
case kWhatDrainThisBuffer:
{
...
onReleaseOutputBuffer(msg);
PostReplyWithError(replyID, err);
break;
}
...
}
status_t MediaCodec::onReleaseOutputBuffer(const sp<AMessage> &msg) {
size_t index;
CHECK(msg->findSize("index", &index));
...
BufferInfo *info = &mPortBuffers[kPortIndexOutput][index];
...
sp<MediaCodecBuffer> buffer;
{
Mutex::Autolock al(mBufferLock);
info->mOwnedByClient = false;
buffer = info->mData;
info->mData.clear();
}
if (render && buffer->size() != 0) {
...
} else {
mBufferChannel->discardBuffer(buffer);
}
}
Подводя итог вышеописанному процессу, помимо HIDL и sfplugin, ниже представлен общий анализ процесса использования MediaCodec для вызова codec2 для кодирования и декодирования. Сначала рассмотрим общий процесс работы и взаимосвязь реализации следующим образом.