이번 섹션에서는 ACodecBufferChannel의 역할과 구조를 분석합니다. 이전 섹션에서 입력 버퍼와 출력 버퍼의 할당 과정을 살펴보았으며, allocateBuffersOnPort 메서드는 ACodec::BufferInfo 내부의 mData 멤버를 배열로 구성한 후, 이를 ACodecBufferChannel에 위임합니다.
1. ACodecBufferChannel 개요
ACodecBufferChannel은 MediaCodec와 ACodec 사이의 중개자 역할을 합니다. 기반 클래스인 BufferChannelBase는 주로 MediaCodec가 호출하는 인터페이스를 제공하며, CCodec에서도 유사하게 상속됩니다. 이 채널을 도입함으로써 두 컴포넌트 간 결합도를 낮추었고, 단순화된 책임 분담이 가능해졌습니다.
하지만 단순한 커플링 감소 외에도, BufferChannel는 데이터의 복호화(Decryption) 및 디스크램블링(Desrambling) 처리까지 담당합니다. 보통 사용되지 않아 생략되지만, 이 기능은 안전한 미디어 스트림 처리에 핵심적인 역할을 합니다. 이러한 로직을 채널 계층에 두면, ACodec 자체는 오직 인코더/디코더 전달에 집중할 수 있어 설계가 명확해집니다.
2. BufferInfo 구조체 분석
헤더 파일에 정의된 BufferInfo는 버퍼 관리의 핵심입니다:
struct BufferInfo {
BufferInfo(
const sp<MediaCodecBuffer> &buffer,
IOMX::buffer_id bufferId,
const sp<IMemory> &sharedEncryptedBuffer);
BufferInfo() = delete;
// 클라이언트(=MediaCodec) 입장에서의 버퍼
const sp<MediaCodecBuffer> mClientBuffer;
// 코덱(=CodecBase) 입장에서의 버퍼
const sp<MediaCodecBuffer> mCodecBuffer;
// OMX 레이어에서의 버퍼 ID
const IOMX::buffer_id mBufferId;
// 암호화된 입력일 경우 공유 메모리 버퍼
const sp<IMemory> mSharedEncryptedBuffer;
};
mClientBuffer는 MediaCodec에게 반환되는 버퍼이며, mCodecBuffer는 ACodec가 실제로 전달하는 버퍼입니다. 두 가지가 다를 수 있는 이유는 암호화된 입력을 처리할 때입니다. 이 경우, 암호화된 데이터를 보호된 영역에서 복호화하기 위해 별도의 sharedEncryptedBuffer가 생성되며, 이는 mClientBuffer에 연결됩니다.
3. setInputBufferArray 동작
setInputBufferArray는 BufferAndId 벡터를 받아 각 요소를 BufferInfo로 변환하여 저장합니다:
void ACodecBufferChannel::setInputBufferArray(const std::vector<BufferAndId> &array) {
std::vector<const BufferInfo> inputBuffers;
for (const BufferAndId &elem : array) {
sp<IMemory> sharedEncryptedBuffer;
if (hasCryptoOrDescrambler()) {
sharedEncryptedBuffer = mDealer->allocate(elem.mBuffer->capacity());
}
inputBuffers.emplace_back(elem.mBuffer, elem.mBufferId, sharedEncryptedBuffer);
}
std::atomic_store(&mInputBuffers, std::make_shared<const std::vector<const BufferInfo>>(inputBuffers));
}
암호화 또는 디스크램블링이 필요하면, mDealer를 통해 보안 버퍼를 할당하고, BufferInfo 생성 시 해당 버퍼를 mClientBuffer로 설정합니다. 그렇지 않은 경우, 원본 버퍼가 그대로 사용됩니다.
4. queueInputBuffer 작동 원리
일반 모드에서는 mClientBuffer와 mCodecBuffer가 같은 메모리 블록을 참조하므로, 버퍼 복사는 필요 없지만, meta 정보는 별도로 복사되어야 합니다:
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) {
it->mCodecBuffer->meta()->clear();
int64_t timeUs;
CHECK(it->mClientBuffer->meta()->findInt64("timeUs", &timeUs));
it->mCodecBuffer->meta()->setInt64("timeUs", timeUs);
// eos, 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;
}
여기서 == 비교가 true일지라도, meta 정보는 독립적입니다. 따라서 항상 메타 데이터를 재설정해야 합니다.
5. secure 버퍼 처리
queueSecureInputBuffer는 암호화된 입력 버퍼를 안전하게 복호화하여 내부 코덱 버퍼로 전달합니다. 이 함수는 메모리 보안을 보장하며, 데이터가 노출되지 않도록 하며, 복호화 과정을 완전히 캡슐화합니다.
다만 중요한 점은, queueSecureInputBuffer를 사용한다고 해서 반드시 secure 컴포넌트가 사용되는 것은 아닙니다. 암호화된 버퍼를 처리할 수 있는 일반 컴포넌트에서도 이 메커니즘을 활용할 수 있습니다. 즉, 보안 처리 여부는 컴포넌트의 구성 방식에 따라 결정되며, 채널의 인터페이스는 단순히 데이터 흐름을 관리합니다.