Android 13 - 미디어 프레임워크(23) - ACodecBufferChannel

이번 섹션에서는 ACodecBufferChannel의 역할과 구조를 분석합니다. 이전 섹션에서 입력 버퍼와 출력 버퍼의 할당 과정을 살펴보았으며, allocateBuffersOnPort 메서드는 ACodec::BufferInfo 내부의 mData 멤버를 배열로 구성한 후, 이를 ACodecBufferChannel에 위임합니다.

1. ACodecBufferChannel 개요

ACodecBufferChannelMediaCodecACodec 사이의 중개자 역할을 합니다. 기반 클래스인 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;
};

mClientBufferMediaCodec에게 반환되는 버퍼이며, mCodecBufferACodec가 실제로 전달하는 버퍼입니다. 두 가지가 다를 수 있는 이유는 암호화된 입력을 처리할 때입니다. 이 경우, 암호화된 데이터를 보호된 영역에서 복호화하기 위해 별도의 sharedEncryptedBuffer가 생성되며, 이는 mClientBuffer에 연결됩니다.

3. setInputBufferArray 동작

setInputBufferArrayBufferAndId 벡터를 받아 각 요소를 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 작동 원리

일반 모드에서는 mClientBuffermCodecBuffer가 같은 메모리 블록을 참조하므로, 버퍼 복사는 필요 없지만, 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 컴포넌트가 사용되는 것은 아닙니다. 암호화된 버퍼를 처리할 수 있는 일반 컴포넌트에서도 이 메커니즘을 활용할 수 있습니다. 즉, 보안 처리 여부는 컴포넌트의 구성 방식에 따라 결정되며, 채널의 인터페이스는 단순히 데이터 흐름을 관리합니다.

태그: Android Media Framework ACodecBufferChannel OpenMAX MediaCodec Secure Buffer

6월 23일 17:27에 게시됨