ADTS (Audio Data Transport Stream)
서론
오디오 데이터를 실시간으로 스트리밍하기 위해서 PCM Data
를 AAC 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
로 구성되어 있다.
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 |
상당히 많은 정보가 7 bytes
안에 들어있지만 여기서 중요한 정보는 다음과 같다.
- B (MPEG Version) : MPEG 버전을 의미하며 MPEG-4면 0, MPEG-2면 1이다.
- E (Audio Profile = Audio Object Types) : 오디오 프로파일을 의미하며 종류는 여기서 확인할 수 있다. (여기서 -1 한 값을 사용)
- F (Sample Rate = Sampling Frequencies) : 샘플링 주파수를 의미하며 종류는 여기서 확인할 수 있다.
- H (Channel Configurations) : 채널 설정을 의미하며 종류는 여기서 확인할 수 있다.
- 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 Header
와 AAC Raw Data
를 채워 넣으면 된다.
예시의 Audio Profile은
AAC LC
, Sample Rate는44100 Hz
, Channel Configurations은2
이다.