Android MediaCodec(一)简介

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而言,它要处理的消息来自于三个地方:

  1. 上层调用(控制命令及buffer处理);
  2. 组件状态回传消息CodecCallback;
  3. 组件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会干两件事:

  1. 将MediaCodecBuffer封装为BufferInfo,然后存储到二维数组中,数组的第一维是端口号(kPortIndexInput为0,kPortIndexOutput为1),数组的第二维是buffer index;
  2. 将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:

  1. 调用getInputBuffer/getOutputBuffer,传入参数index,返回MediaCodecBuffer;
  2. 上层调用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(一)简介

扫描下方二维码,关注公众号《青山渺渺》阅读音视频开发内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值