MediaCodec是Android系统为上层应用提供的硬件编解码类,它封装了ACodec和CCodec,隐藏了底层组件的实现细节,向上层应用提供了统一且易用的硬件编解码接口。由于MediaCodec是为上层应用提供的接口,因此我们不会对其内部实现进行深入探讨,本篇文章将先简单介绍MediaCodec的异步消息处理机制、缓冲区管理以及API的使用这三部分内容,然后通过SimplePlayer了解MediaCodec的同步模式用法,为异步模式的播放器实现打下基础。
想要了解MediaCodec内部实现的同学,可以参考笔者早期的学习记录,要注意的是,这些笔记可能包含一些错误的观点,因此请谨慎阅读。
Android Media Framework(十三)MediaCodec的创建与配置
Android Media Framework(十四)MediaCodec API浅析
Android Media Framework(二十九)MediaCodec Input Buffer流转
Android Media Framework(三十)Input Buffer写入过程分析
Android Media Framework(三十三)OutputPortSettingsChangedState
1、异步消息处理
MediaCodec内部使用了ALooper/AHandler/AMessage异步消息机制来处理上层调用与底层callback消息,ALooper是由上层创建并注册给MediaCodec。
sp<MediaCodec> MediaCodec::CreateByType(
const sp<ALooper> &looper, const AString &mime, bool encoder, status_t *err, pid_t pid,
uid_t uid) {
sp<AMessage> format;
return CreateByType(looper, mime, encoder, err, pid, uid, format);
}
sp<MediaCodec> MediaCodec::CreateByComponentName(
const sp<ALooper> &looper, const AString &name, status_t *err, pid_t pid, uid_t uid) {
sp<MediaCodec> codec = new MediaCodec(looper, pid, uid);
const status_t ret = codec->init(name);
if (err != NULL) {
*err = ret;
}
return ret == OK ? codec : NULL; // NULL deallocates codec.
}
MediaCodec的构造函数是私有的,创建MediaCodec实例需要使用上面两个静态方法,无论是哪一种都要传入一个ALooper。
status_t MediaCodec::init(const AString &name) {
// ......
if (mDomain == DOMAIN_VIDEO) {
// video codec needs dedicated looper
if (mCodecLooper == NULL) {
status_t err = OK;
mCodecLooper = new ALooper;
mCodecLooper->setName("CodecLooper");
err = mCodecLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
if (OK != err) {
mErrorLog.log(LOG_TAG, "Fatal error: codec looper failed to start");
return err;
}
}
// 1. video独立looper
mCodecLooper->registerHandler(mCodec);
} else {
// 2. audio共用looper
mLooper->registerHandler(mCodec);
}
// 3. MediaCodec looper
mLooper->registerHandler(this);
// 4. CodecCallback
mCodec->setCallback(
std::unique_ptr<CodecBase::CodecCallback>(
new CodecCallback(new AMessage(kWhatCodecNotify, this))));
mBufferChannel = mCodec->getBufferChannel();
// 5. BufferCallback
mBufferChannel->setCallback(
std::unique_ptr<CodecBase::BufferCallback>(
new BufferCallback(new AMessage(kWhatCodecNotify, this))));
// ...
}
MediaCodec创建完成后,CreateByType/CreateByComponentName会调用其init方法来初始化组件,如果创建的是video组件,init会额外创建一个mCodecLooper,上层的控制命令和组件的callback消息都在这个独立的looper中做处理。如果创建的是audio组件,ACodec/CCodec会共用MediaCodec Looper。
对于MediaCodec而言,它要处理的消息来自于三个地方:
- 上层调用(控制命令及buffer处理);
- 组件状态回传消息CodecCallback;
- 组件buffer回传消息BufferCallback;
从上到下可能涉及到四个线程:应用线程、MediaCodec处理线程、ACodec/CCodec线程、hwbinder线程,我们在debug时要注意每个调用都在哪个线程中。
MediaCodec总共有如下几个控制方法:
- configure:配置组件;
- start:启动组件;
- stop:停止组件;
- reset:重置组件;
- release:释放组件,它还有一个异步的版本releaseAsync(暂不了解);
- flush:刷新组件;
configure、start、stop、release和flush这几个调用会被转换为AMessage,然后以阻塞的方式送到消息处理线程中处理。
以configure为例:
status_t MediaCodec::configure(
const sp<AMessage> &format,
const sp<Surface> &surface,
const sp<ICrypto> &crypto,
const sp<IDescrambler> &descrambler,
uint32_t flags) {
sp<AMessage> msg = new AMessage(kWhatConfigure, this);
// ......
status_t err;
sp<AMessage> response;
err = PostAndAwaitResponse(msg, &response);
// ......
return err;
}
configure会调用postAndAwaitResponse发送消息,然后阻塞等待消息处理。
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
// ...
switch (msg->what()) {
case kWhatConfigure:
{
// ...
mCodec->initiateConfigureComponent(format);
break;
}
}
}
onMessageReceived最终会调用到initiateConfigureComponent,在之前的学习中我们知道这个方法也是异步处理的,所以initiateConfigureComponent调用完成后ALooper线程就会空出。
我们要理解的是,控制方法阻塞的是调用者线程,并没有阻塞MediaCodec消息处理线程,最好不要在多线程环境下使用这些控制方法,否则容易出现MediaCodec状态异常的错误。
2、缓冲区管理
BufferChannelBase会调用onInputBufferAvailable和onOutputBufferAvailable将可用input/output buffer送回到MediaCodec,两个callback的参数都是index和MediaCodecBuffer:
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();
}
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();
}
AHandler处理消息时会调用updateBuffers:
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));
}
// 1.
mPortBuffers[portIndex][index].mData = buffer;
}
// 2.
mAvailPortBuffers[portIndex].push_back(index);
return index;
}
updateBuffers会干两件事:
- 将MediaCodecBuffer封装为BufferInfo,然后存储到二维数组中,数组的第一维是端口号(kPortIndexInput为0,kPortIndexOutput为1),数组的第二维是buffer index;
- 将index添加到对应端口的mAvailPortBuffers数组中。
BufferInfo有两个字段
struct BufferInfo {
BufferInfo();
sp<MediaCodecBuffer> mData;
bool mOwnedByClient;
};
mOwnedByClient表示上层是否持有此buffer。
从端口获取可用buffer时会调用内部方法dequeuePortBuffer:
MediaCodec::BufferInfo *MediaCodec::peekNextPortBuffer(int32_t portIndex) {
CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);
// 获取端口对应的mAvailPortBuffers数组
std::list<size_t> *availBuffers = &mAvailPortBuffers[portIndex];
// 判断数组元素是否为空
if (availBuffers->empty()) {
return nullptr;
}
// 不为空就获取mPortBuffers对应的BufferInfo
return &mPortBuffers[portIndex][*availBuffers->begin()];
}
ssize_t MediaCodec::dequeuePortBuffer(int32_t portIndex) {
CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);
// 1. 获取可用BufferInfo
BufferInfo *info = peekNextPortBuffer(portIndex);
if (!info) {
return -EAGAIN;
}
std::list<size_t> *availBuffers = &mAvailPortBuffers[portIndex];
size_t index = *availBuffers->begin();
CHECK_EQ(info, &mPortBuffers[portIndex][index]);
// 2. 移除mAvailPortBuffers中的索引
availBuffers->erase(availBuffers->begin());
// 3. 检查buffer是否被上层持有
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);
}
}
}
// 3. 返回index
return index;
}
dequeuePortBuffer会从mAvailPortBuffers获取可用的index,然后从mPortBuffers拿到对应的BufferInfo,最后返回index。
上层拿到index之后有两种方式可以获取到MediaCodecBuffer:
- 调用getInputBuffer/getOutputBuffer,传入参数index,返回MediaCodecBuffer;
- 上层调用getInputBuffers/getOutputBuffers拿到buffer数组,获取到index后直接到数组找对应的MediaCodecBuffer。
buffer使用完成后,上层调用queueInputBuffer或renderOutputBufferAndRelease后,mPortBuffers中对应index的BufferInfo会被清空。
3、API使用
这一节对MediaCodec常用API的使用以及一些注意点做一些说明。
首先是控制API:
- configure:传入的配置参数需要包含一些重要信息,如果没有可能会报error。对于video decoder来说,必须要设置画面的宽高(任意设置即可);对于audio而言,channel count 和sample rate比较重要;
- start:配置完成之后就可以调用start启动组件运行了;
- stop:stop会停止组件的运行,重置组件,如果想要再次使用该MediaCodec实例需要重新调用configure方法;
- reset:重置组件,它和stop有很大的区别,reset会先释放组件再重新创建组件,reset之后要重新启动需要调用configure方法;
- release:释放组件,release调用后该MediaCodec实例将无法再被使用;
- flush:刷新input/output缓冲区,该方法调用后上层持有的input output buffer将不再可用。
接着是buffer获取方式:
4、SimplePlayer
原文阅读:
Android MediaCodec(一)简介
扫描下方二维码,关注公众号《青山渺渺》阅读音视频开发内容。