这一节我们将了解 ACodecBufferChannel
上一节我们了解到input buffer 和 output buffer 是如何分配的了,allocateBuffersOnPort 方法的最后会将ACodec::BufferInfo 中的 mData 成员组织成为数组,最后提交给 ACodecBufferChannel 管理。这一节我们将尝试了解ACodecBufferChannel 的作用,以及 ACodec::BufferInfo 中部分成员的作用。
1、ACodecBufferChannel
ACodecBufferChannel 介于 MediaCodec 和 ACodec之间,我们之前了解过 BufferChannelBase
的接口,基类中的接口主要是给 MediaCodec 调用的,我们后面要了解的CCodec中的BufferChannel同样也是继承于该基类。ACodecBufferChannel起着中介的作用,降低了 MediaCodec 和 ACodec 的耦合度,不过我还是会想为什么要有 BufferChannel 呢?除了降低耦合还干了什么呢?其实 BufferChannel 还有另外的功能,那就是解扰(descrambler
)和解密(decrypto
)的作用,只不过是我们平时不常用,所以就忽略它们了。解扰和解密的流程放在 BufferChannel 中可以简化 ACodec 的内容,让分工更加明确。
回到 ACodecBufferChannel 中来,头文件中声明的函数分为两部分,一部分是给MediaCodec使用的(BufferChannelBase中的接口),另一部分是给ACodec调用的,我们这里主要来看给ACodec调用的接口。
cpp
struct BufferInfo {
BufferInfo(
const sp<MediaCodecBuffer> &buffer,
IOMX::buffer_id bufferId,
const sp<IMemory> &sharedEncryptedBuffer);
BufferInfo() = delete;
// Buffer facing MediaCodec and its clients.
const sp<MediaCodecBuffer> mClientBuffer;
// Buffer facing CodecBase.
const sp<MediaCodecBuffer> mCodecBuffer;
// OMX buffer ID.
const IOMX::buffer_id mBufferId;
// Encrypted buffer in case of secure input.
const sp<IMemory> mSharedEncryptedBuffer;
};
ACodeBufferChannel 中也声明了一个 BufferInfo
,mCodecBuffer
指向的是ACodec中传递的mData,mClientBuffer
指向的是回传给 MediaCodec 的 buffer,未什么要分成两个部分我们后面再说。
先来看 ACodec 是如何调用 setInputBufferArray 把 buffer 交给ACodecBufferChannel管理的,setInputBufferArray传入参数为一个BufferAndId
数组,将MediaCodecBuffer和buffer-id组成成为BufferAndId,然后再传入ACodecBufferChannel。
cpp
void ACodecBufferChannel::setInputBufferArray(const std::vector<BufferAndId> &array) {
...
std::vector<const BufferInfo> inputBuffers;
for (const BufferAndId &elem : array) {
sp<IMemory> sharedEncryptedBuffer;
if (hasCryptoOrDescrambler()) {
// 分配用于解密的buffer
sharedEncryptedBuffer = mDealer->allocate(elem.mBuffer->capacity());
}
// 创建ACodec::BufferInfo
inputBuffers.emplace_back(elem.mBuffer, elem.mBufferId, sharedEncryptedBuffer);
}
std::atomic_store(
&mInputBuffers,
std::make_shared<const std::vector<const BufferInfo>>(inputBuffers));
}
ACodecBufferChannel::BufferInfo::BufferInfo(
const sp<MediaCodecBuffer> &buffer,
IOMX::buffer_id bufferId,
const sp<IMemory> &sharedEncryptedBuffer)
: mClientBuffer(
(sharedEncryptedBuffer == nullptr)
? buffer
: new SharedMemoryBuffer(buffer->format(), sharedEncryptedBuffer)),
mCodecBuffer(buffer),
mBufferId(bufferId),
mSharedEncryptedBuffer(sharedEncryptedBuffer) {
}
setInputBufferArray 中会判断是否需要解密或者解扰,这些内容是在 MediaCodec configure过程中设定的,如果需要会分配出一块用于解密的buffer,最后会创建一个ACodec::BufferInfo对象。继续来看BufferInfo的构造函数,如果sharedEncryptedBuffer
这个参数不为NULL,那么mClientBuffer
将会使用这块新分配的用于解密的buffer,否则就直接使用ACodec传来的MediaCodecBuffer。
到这我们应该就可以猜测了,如果上层需要对input进行解密/解扰,那么数据也需要加密传输,因此需要一块受保护的buffer,因此需要单独分配buffer给上层使用(buffer的拷贝过程也是加密的)。上层把填好数据的buffer(mClientBuffer)送给 BufferChannel,在这里会对数据进行解密,再将解密后的数据拷贝要input buffer当中(mCodecBuffer),最后送给 OMX 组件,因此这里使用两个MediaCodecBuffer来处理需要加密解密的情况,这种情况下mClientBuffer和mCodecBuffer指向的内容是不同的。
但是如果只是普通播放,那么mClientBuffer和mCodecBuffer会指向同一个地址,使用 ==
判断是就会返回 true 了。
我们去阅读 ACodecBufferChannel::queueInputBuffer 和 ACodecBufferChannel::queueSecureInputBuffer 大致能能理解上面的意思了:
cpp
status_t ACodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
std::shared_ptr<const std::vector<const BufferInfo>> array(
std::atomic_load(&mInputBuffers));
BufferInfoIterator it = findClientBuffer(array, buffer);
if (it == array->end()) {
return -ENOENT;
}
if (it->mClientBuffer != it->mCodecBuffer) {
// Copy metadata from client to codec buffer.
it->mCodecBuffer->meta()->clear();
int64_t timeUs;
CHECK(it->mClientBuffer->meta()->findInt64("timeUs", &timeUs));
it->mCodecBuffer->meta()->setInt64("timeUs", timeUs);
int32_t eos;
if (it->mClientBuffer->meta()->findInt32("eos", &eos)) {
it->mCodecBuffer->meta()->setInt32("eos", eos);
}
int32_t csd;
if (it->mClientBuffer->meta()->findInt32("csd", &csd)) {
it->mCodecBuffer->meta()->setInt32("csd", csd);
}
}
ALOGV("queueInputBuffer #%d", it->mBufferId);
sp<AMessage> msg = mInputBufferFilled->dup();
msg->setObject("buffer", it->mCodecBuffer);
msg->setInt32("buffer-id", it->mBufferId);
msg->post();
return OK;
}
我们来看普通模式,这时候mClientBuffer和mCodecBuffer中的mData指向的内容是相同的,所以不需要做buffer拷贝,但是可能有人又要问了,之前不是说 ==
判断 是返回true的吗,这里要注意,== 返回true指的是把 MediaCodecBuffer 中的 mBuffer
进行比较。
简单来说是数据块相同,但是 meta 信息还是独立的,所以每次调用 queueInputBuffer 都是要拷贝 meta 数据的。
queueSecureInputBuffer 比较长,这里就不贴代码了,这里面主要做的就是把secure input buffer 中的数据安全的拷贝到 ACodec input buffer 当中。
这里抛出一个问题,是否使用 queueSecureInputBuffer 来输入 buffer,那么就一定用的 secure 组件呢?答案不是的。
cpp
} else if (mCrypto != NULL) {
hardware::drm::V1_0::DestinationBuffer destination;
if (secure) {
destination.type = DrmBufferType::NATIVE_HANDLE;
destination.secureMemory = hidl_handle(secureHandle);
} else {
destination.type = DrmBufferType::SHARED_MEMORY;
IMemoryToSharedBuffer(
mDecryptDestination, mHeapSeqNum, &destination.nonsecureMemory);
}
hardware::drm::V1_0::SharedBuffer source;
IMemoryToSharedBuffer(it->mSharedEncryptedBuffer, mHeapSeqNum, &source);
result = mCrypto->decrypt(key, iv, mode, pattern,
source, it->mClientBuffer->offset(),
subSamples, numSubSamples, destination, errorDetailMsg);
if (result < 0) {
return result;
}
if (destination.type == DrmBufferType::SHARED_MEMORY) {
memcpy(it->mCodecBuffer->base(), mDecryptDestination->unsecurePointer(), result);
}
从代码中我们可以看出来,将secure input 解密到目标buffer时分为两种情况,一种是secure的情况,需要解密到 handle 指向的buffer中,另一种情况就是解密到一块非安全的buffer中,最后拷贝到ACodec分配的普通buffer中,这种方式上层同样无法接触到ACodec分配的buffer,因此也是安全的。
2、ACodec::BufferInfo
了解了以上内容,我们再回头看 ACodec::BufferInfo,同样的里面会有两块 MediaCodecBuffer,之所以要这样设计,是因为还考虑了 DataConverter
的情况,DataConverter 用于数据转换,ACodec 中用作与 Audio 解码后的 PCM 数据转换(我们不做深入研究)。因为涉及到转换,所以需要一个 MediaCodecBuffer 做中转,不过一般情况下 mData 和 mCodecData 指向的是同一块 MediaCodecBuffer。
最后给出一张示意图:
有了它我们大致就能理解为什么 ACodec 和 ACodecBufferChannel 中为什么要有那么多 MediaCodecBuffer 指针了。