서론

오디오 데이터를 실시간으로 스트리밍하기 위해서 PCM DataAAC Codec으로 인코딩한 AAC Raw Data를 전송하고 있었다.

이때 AAC Raw Data를 받는 쪽에서는 FFmpeg을 사용해서 디코딩 작업을 수행했는데 이를 GStreamer로 전환하는 과정에서 디코딩이 안 되는 문제가 발생했다.

원인을 찾는 과정에서 알게 된 내용에 대해 간단히 정리해봤다.


GStreamer에서 AAC Raw Data를 디코딩할 수 없는 이유

AAC 디코더를 초기화하기 위해서는 인코딩된 오디오에 대한 정보(Audio Profile, Sample Rate, Channels..)가 필요하다.

FFmpeg은 디코더(AVCodecContext)에 해당 정보를 직접 입력하여 초기화할 수 있지만 GStreamer는 디코더에 해당 정보를 직접 입력하여 초기화할 수 없다.

그 이유는 일반적으로 GStreamer는 인코딩된 AAC 데이터를 읽을 수 있는 파서(aacparse)와 이를 디코딩할 수 있는 디코더(faad)로 구성되어 있어서 aacparse로부터 얻은 오디오 정보를 바탕으로 faad가 초기화 및 디코딩을 수행하기 때문이다.

하지만 AAC Raw Data는 압축된 오디오 데이터일 뿐 오디오에 대한 정보가 없어서 aacparse가 읽을 수 없다.

따라서 aacparse가 읽을 수 있는 데이터(스트림) 포맷인 ADTS로 변환이 필요하다.

aacparse : This is an AAC parser which handles both ADIF and ADTS stream formats.


ADTS (Audio Data Transport Stream)

ADTS(Audio Data Transport Stream)는 AAC Codec을 통해 인코딩된 오디오 스트림 포맷의 한 종류이다.

프레임마다 헤더를 가진 ADTS는 프레임 단위로 디코딩할 수 있어서 라이브 스트리밍에 적합하다.

이와 유사한 ADIF(Audio Data Interchange Format)는 통합 헤더 하나만 가지고 있는 방식으로 모든 프레임을 얻은 후 디코딩해야 하며 파일 저장에 적합하다.

ADTS는 프레임마다 오디오 메타데이터를 가지고 있는 ADTS Header와 압축된 오디오 데이터를 가지고 있는 AAC Raw Data로 구성되어 있다.

image

ADTS Header는 일반적으로 7 bytes의 고정 길이를 가지며 CRC(Cyclic Redundancy Check)를 포함할 경우 9 bytes가 된다.

ADTS Header의 구조는 다음과 같다.

AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ)
Letter Length (bits) Description
A 12 syncword 0xFFF, all bits must be 1
B 1 MPEG Version: 0 for MPEG-4, 1 for MPEG-2
C 2 Layer: always 0
D 1 protection absent, Warning, set to 1 if there is no CRC and 0 if there is CRC
E 2 profile, the MPEG-4 Audio Object Type minus 1
F 4 MPEG-4 Sampling Frequency Index (15 is forbidden)
G 1 private bit, guaranteed never to be used by MPEG, set to 0 when encoding, ignore when decoding
H 3 MPEG-4 Channel Configuration (in the case of 0, the channel configuration is sent via an inband PCE)
I 1 originality, set to 0 when encoding, ignore when decoding
J 1 home, set to 0 when encoding, ignore when decoding
K 1 copyrighted id bit, the next bit of a centrally registered copyright identifier, set to 0 when encoding, ignore when decoding
L 1 copyright id start, signals that this frame’s copyright id bit is the first bit of the copyright id, set to 0 when encoding, ignore when decoding
M 13 frame length, this value must include 7 or 9 bytes of header length: FrameLength = (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame)
O 11 Buffer fullness
P 2 Number of AAC frames (RDBs) in ADTS frame minus 1, for maximum compatibility always use 1 AAC frame per ADTS frame
Q 16 CRC if protection absent is 0

출처 : https://wiki.multimedia.cx/index.php?title=ADTS

상당히 많은 정보가 7 bytes 안에 들어있지만 여기서 중요한 정보는 다음과 같다.

  1. B (MPEG Version) : MPEG 버전을 의미하며 MPEG-4면 0, MPEG-2면 1이다.
  2. E (Audio Profile = Audio Object Types) : 오디오 프로파일을 의미하며 종류는 여기서 확인할 수 있다. (여기서 -1 한 값을 사용)
  3. F (Sample Rate = Sampling Frequencies) : 샘플링 주파수를 의미하며 종류는 여기서 확인할 수 있다.
  4. H (Channel Configurations) : 채널 설정을 의미하며 종류는 여기서 확인할 수 있다.
  5. M (Frame Length) : 헤더를 포함한 ADTS의 전체 크기를 의미한다.


ADTS 구현

이제 ADTS를 생성해보자.

constexpr int ADTS_HEADER_SIZE = 7;   // ADTS Header Size : 7 byte
constexpr int AUDIO_PROFILE = 2;      // Audio Profile : AAC LC
constexpr int SAMPLE_RATE = 4;        // Sampling Frequencies : 44100 Hz
constexpr int CHANNELS = 2;           // Channel Configurations : 2

std::unique_ptr<uint8_t[]> createADTS(uint8_t* aac_data, int aac_size)
{
    int adts_size = aac_size + ADTS_HEADER_SIZE;
    auto adts_data = std::make_unique<uint8_t[]>(adts_size);

    // Write ADTS Header
    adts_data[0] = static_cast<uint8_t>(0xFF);
    adts_data[1] = static_cast<uint8_t>(0xF9);
    adts_data[2] = static_cast<uint8_t>(((AUDIO_PROFILE - 1) << 6) + (SAMPLE_RATE << 2) + (CHANNELS >> 2));
    adts_data[3] = static_cast<uint8_t>(((CHANNELS & 3) << 6) + (adts_size >> 11));
    adts_data[4] = static_cast<uint8_t>((adts_size & 0x7FF) >> 3);
    adts_data[5] = static_cast<uint8_t>(((adts_size & 7) << 5) + 0x1F);
    adts_data[6] = static_cast<uint8_t>(0xFC);

    // Copy AAC Raw Data
    std::copy(aac_data, aac_data + aac_size, adts_data.get() + ADTS_HEADER_SIZE);

    return adts_data;
}

먼저 ADTS Header + AAC Raw Data 크기의 배열을 생성한다.

그다음 인코딩된 오디오 정보를 바탕으로 작성한 ADTS HeaderAAC Raw Data를 채워 넣으면 된다.

예시의 Audio Profile은 AAC LC, Sample Rate는 44100 Hz, Channel Configurations은 2이다.


카테고리:

업데이트: