RTMP 协议精讲 / 08 - 音频编解码
音频编解码(Audio Codecs)
8.1 RTMP 音频架构
RTMP 中的音频数据以 FLV Audio Tag 格式封装,通过 Type 8 消息传输。音频在直播中的重要性不亚于视频——观众对音频卡顿的容忍度远低于视频。
音频数据在 RTMP 中的位置:
RTMP Connection
├── Message Type 8 (Audio)
│ ┌────────────────────────────────────────────┐
│ │ FLV Audio Tag Body │
│ │ ┌──────────┬──────────┬─────────────────┐│
│ │ │ Audio │ AAC │ Audio Data ││
│ │ │ Header │ Packet │ (Raw/ADTS) ││
│ │ │ (1 byte) │ Type │ ││
│ │ │ │ (1 byte) │ ││
│ │ └──────────┴──────────┴─────────────────┘│
│ └────────────────────────────────────────────┘
│
└── Message Type 9 (Video) ...
8.2 FLV Audio Tag 结构
音频头(第一个字节)
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
│Sound │Sound │
│Format │Rate │
│(4b) │(2b) │
+-+-+-+-+-+-+-+-+
│Sound │Sound │
│Size │Type │
│(1b) │(1b) │
+-+-+-+-+-+-+-+-+
字段详解:
┌──────────────┬────────┬──────────────────────────────────────┐
│ 字段 │ 位数 │ 取值 │
├──────────────┼────────┼──────────────────────────────────────┤
│ Sound Format │ 4 bits │ 音频编码格式 │
│ │ │ 0=PCM/PCM_LE │
│ │ │ 1=ADPCM │
│ │ │ 2=MP3 │
│ │ │ 3=PCM_LE (Little Endian) │
│ │ │ 4=Nellymoser 16kHz Mono │
│ │ │ 5=Nellymoser 8kHz Mono │
│ │ │ 6=Nellymoser │
│ │ │ 7=G.711 A-law │
│ │ │ 8=G.711 mu-law │
│ │ │ 9=reserved │
│ │ │ 10=AAC │
│ │ │ 11=Speex │
│ │ │ 14=MP3 8kHz │
│ │ │ 15=Device-specific │
├──────────────┼────────┼──────────────────────────────────────┤
│ Sound Rate │ 2 bits │ 采样率索引 │
│ │ │ 0=5.5kHz (仅 Nellymoser/Speex) │
│ │ │ 1=11kHz (22.05kHz for MP3/AAC) │
│ │ │ 2=22kHz (44.1kHz for MP3/AAC) │
│ │ │ 3=44kHz │
├──────────────┼────────┼──────────────────────────────────────┤
│ Sound Size │ 1 bit │ 采样位深 │
│ │ │ 0=8-bit │
│ │ │ 1=16-bit │
├──────────────┼────────┼──────────────────────────────────────┤
│ Sound Type │ 1 bit │ 声道数 │
│ │ │ 0=mono (单声道) │
│ │ │ 1=stereo (立体声) │
└──────────────┴────────┴──────────────────────────────────────┘
常见音频头值
| 十六进制 | Sound Format | Rate | Size | Type | 典型配置 |
|---|---|---|---|---|---|
0xAF | AAC (10) | 3 (44kHz) | 1 (16bit) | 1 (stereo) | AAC 44.1kHz 立体声 |
0x2F | MP3 (2) | 3 (44kHz) | 1 (16bit) | 1 (stereo) | MP3 44.1kHz 立体声 |
0xAE | AAC (10) | 3 (44kHz) | 1 (16bit) | 0 (mono) | AAC 44.1kHz 单声道 |
8.3 AAC 编码详解
AAC(Advanced Audio Coding)是 RTMP 直播中最常用的音频编码格式。
8.3.1 AAC Audio Tag 结构
当 Sound Format = 10 (AAC) 时:
AAC Audio Tag Body:
┌────────────────┬─────────────────┬──────────────────────────┐
│ Audio Header │ AAC Packet Type │ AAC Data │
│ (1 byte) │ (1 byte) │ │
│ 0xAF │ │ │
│ │ 0=Sequence Header│ AudioSpecificConfig │
│ │ 1=AAC Raw │ AAC 压缩数据 │
└────────────────┴─────────────────┴──────────────────────────┘
8.3.2 AAC Sequence Header (AudioSpecificConfig)
AAC Sequence Header 是 AudioSpecificConfig 数据,用于初始化 AAC 解码器:
AudioSpecificConfig (ISO 14496-3):
5 bits 4 bits 4 bits variable
┌──────────┬───────────┬───────────┬──────────────────┐
│ audio │ sampling │ channel │ GASpecificConfig│
│ object │ freq │ config │ (可选) │
│ type │ index │ │ │
└──────────┴───────────┴───────────┴──────────────────┘
Audio Object Type:
┌──────┬──────────────────────────────────────────────┐
│ 值 │ 类型 │
├──────┼──────────────────────────────────────────────┤
│ 1 │ AAC Main │
│ 2 │ AAC LC (Low Complexity) — 最常用 │
│ 3 │ AAC SSR (Scalable Sample Rate) │
│ 4 │ AAC LTP (Long Term Prediction) │
│ 5 │ SBR (Spectral Band Replication) │
│ 6 │ AAC Scalable │
│ 7 │ TwinVQ │
│ 8 │ CELP │
│ 39 │ ER AAC LD │
└──────┴──────────────────────────────────────────────┘
Sampling Frequency Index:
┌──────┬──────────────────────┐
│ 值 │ 采样率 (Hz) │
├──────┼──────────────────────┤
│ 0 │ 96000 │
│ 1 │ 88200 │
│ 2 │ 64000 │
│ 3 │ 48000 │
│ 4 │ 44100 ← 最常用 │
│ 5 │ 32000 │
│ 6 │ 24000 │
│ 7 │ 22050 │
│ 8 │ 16000 │
│ 9 │ 12000 │
│ 10 │ 11025 │
│ 11 │ 8000 │
│ 12 │ 7350 │
└──────┴──────────────────────┘
Channel Configuration:
┌──────┬──────────────────────┐
│ 值 │ 声道配置 │
├──────┼──────────────────────┤
│ 0 │ 特定配置 (在 ASC 中) │
│ 1 │ 中心 (单声道) │
│ 2 │ 左+右 (立体声) │
│ 3 │ 中心+左+右 │
│ 4 │ 中心+左+右+后环绕 │
│ 5 │ 中心+左+右+后左+后右 │
│ 6 │ 5.1 (6 声道) │
│ 7 │ 7.1 (7.1 声道) │
└──────┴──────────────────────┘
8.3.3 AudioSpecificConfig 解析实现
#!/usr/bin/env python3
"""
AudioSpecificConfig 解析器
用于解析 AAC Sequence Header
"""
import struct
from dataclasses import dataclass
@dataclass
class AudioSpecificConfig:
"""AAC AudioSpecificConfig"""
audio_object_type: int # 音频对象类型
sampling_frequency_index: int # 采样率索引
sampling_frequency: int # 实际采样率 (Hz)
channel_configuration: int # 声道配置
frame_length_flag: int = 0 # 帧长度标志
depends_on_core_coder: int = 0
extension_flag: int = 0
# AAC-LC 特有
sbr_present_flag: int = 0
ps_present_flag: int = 0
# 采样率映射表
SAMPLING_FREQ_TABLE = {
0: 96000, 1: 88200, 2: 64000, 3: 48000,
4: 44100, 5: 32000, 6: 24000, 7: 22050,
8: 16000, 9: 12000, 10: 11025, 11: 8000,
12: 7350
}
# 音频对象类型名称
AUDIO_OBJECT_TYPE_NAMES = {
0: "Null", 1: "AAC Main", 2: "AAC-LC", 3: "AAC-SSR",
4: "AAC-LTP", 5: "SBR", 6: "AAC Scalable",
7: "TwinVQ", 8: "CELP", 9: "HVXC",
12: "TTSI", 13: "Main Synthesis", 14: "Wavetable Synthesis",
39: "ER AAC LD", 42: "ER AAC ELD"
}
# 声道配置名称
CHANNEL_CONFIG_NAMES = {
0: "Defined in AOT", 1: "Center (Mono)", 2: "L+R (Stereo)",
3: "Center+L+R", 4: "Center+L+R+Rear", 5: "5 Surround",
6: "5.1 Surround", 7: "7.1 Surround"
}
def parse_audio_specific_config(data: bytes) -> AudioSpecificConfig:
"""
解析 AudioSpecificConfig
参数:
data: AAC Sequence Header 的数据部分(不含 Audio Header 和 AAC Packet Type)
返回:
AudioSpecificConfig 对象
"""
if len(data) < 2:
raise ValueError("AudioSpecificConfig 至少需要 2 字节")
# 位读取器
bits = BitReader(data)
# Audio Object Type (5 bits)
audio_object_type = bits.read_bits(5)
if audio_object_type == 31:
# 扩展类型:再读 6 位 + 32
audio_object_type = 32 + bits.read_bits(6)
# Sampling Frequency Index (4 bits)
sampling_freq_index = bits.read_bits(4)
if sampling_freq_index == 15:
# 自定义采样率:读 24 位
sampling_freq = bits.read_bits(24)
else:
sampling_freq = SAMPLING_FREQ_TABLE.get(sampling_freq_index, 0)
# Channel Configuration (4 bits)
channel_config = bits.read_bits(4)
config = AudioSpecificConfig(
audio_object_type=audio_object_type,
sampling_frequency_index=sampling_freq_index,
sampling_frequency=sampling_freq,
channel_configuration=channel_config,
)
# GASpecificConfig (如果 AOT 是 1-7 等)
if audio_object_type in (1, 2, 3, 4, 6, 7):
config.frame_length_flag = bits.read_bits(1)
config.depends_on_core_coder = bits.read_bits(1)
config.extension_flag = bits.read_bits(1)
return config
def parse_aac_sequence_header(body: bytes) -> AudioSpecificConfig:
"""
解析 AAC Sequence Header(完整的 Audio Tag Body)
参数:
body: Audio Tag Body(包含 1 byte Audio Header + 1 byte AAC Packet Type + ASC)
"""
if len(body) < 4:
raise ValueError("AAC Sequence Header 至少需要 4 字节")
audio_header = body[0] # 0xAF
aac_packet_type = body[1] # 0 = Sequence Header
if aac_packet_type != 0:
raise ValueError(f"不是 AAC Sequence Header,type={aac_packet_type}")
asc_data = body[2:]
return parse_audio_specific_config(asc_data)
class BitReader:
"""位读取器"""
def __init__(self, data: bytes):
self.data = data
self.byte_pos = 0
self.bit_pos = 0
def read_bits(self, n: int) -> int:
"""读取 n 位"""
result = 0
for _ in range(n):
if self.byte_pos >= len(self.data):
break
bit = (self.data[self.byte_pos] >> (7 - self.bit_pos)) & 1
result = (result << 1) | bit
self.bit_pos += 1
if self.bit_pos >= 8:
self.bit_pos = 0
self.byte_pos += 1
return result
def create_audio_specific_config(
object_type: int = 2,
sample_rate: int = 44100,
channels: int = 2
) -> bytes:
"""
创建 AudioSpecificConfig
参数:
object_type: 音频对象类型 (2=AAC-LC)
sample_rate: 采样率
channels: 声道数
"""
# 查找采样率索引
freq_index = None
for idx, freq in SAMPLING_FREQ_TABLE.items():
if freq == sample_rate:
freq_index = idx
break
if freq_index is None:
raise ValueError(f"不支持的采样率: {sample_rate}")
# 编码 AudioSpecificConfig
bits = 0
bits |= (object_type & 0x1F) << (32 - 5)
bits |= (freq_index & 0x0F) << (32 - 5 - 4)
bits |= (channels & 0x0F) << (32 - 5 - 4 - 4)
result = bits.to_bytes(2, 'big')
return result
# 测试
def test_audio_specific_config():
"""测试 AudioSpecificConfig 解析"""
# AAC-LC, 44100Hz, Stereo
# Object Type=2, Freq Index=4, Channel=2
# Binary: 00010 0100 0010 0000
# 00010010 00010000 = 0x12 0x10
data = bytes([0x12, 0x10])
config = parse_audio_specific_config(data)
print(f"Audio Object Type: {config.audio_object_type} ({AUDIO_OBJECT_TYPE_NAMES.get(config.audio_object_type, 'Unknown')})")
print(f"Sampling Frequency: {config.sampling_frequency} Hz (index={config.sampling_frequency_index})")
print(f"Channel Configuration: {config.channel_configuration} ({CHANNEL_CONFIG_NAMES.get(config.channel_configuration, 'Unknown')})")
# 验证
assert config.audio_object_type == 2 # AAC-LC
assert config.sampling_frequency == 44100
assert config.channel_configuration == 2 # Stereo
# 编码验证
encoded = create_audio_specific_config(2, 44100, 2)
assert encoded == data
print("✅ AudioSpecificConfig 测试通过")
if __name__ == '__main__':
test_audio_specific_config()
8.4 AAC Raw Data
当 AAC Packet Type = 1 时,Body 中包含的是 AAC 压缩音频帧:
AAC Raw Frame:
┌─────────────────────────────────────────────────────┐
│ AAC Frame (variable length) │
│ 通常是 ADTS 或 RAW AAC 数据 │
└─────────────────────────────────────────────────────┘
注意:
- RTMP 中的 AAC Raw 不含 ADTS 头
- 直接是 AAC 编码器输出的原始数据
- 需要配合之前收到的 AudioSpecificConfig 解码
8.5 ADTS 格式
ADTS(Audio Data Transport Stream)是 AAC 数据的传输格式,常用于 HLS/文件存储:
ADTS Header (7/9 bytes):
┌──────────────────────────────────────────────────────────────┐
│ syncword (12 bits) = 0xFFF │
│ ID (1 bit) = 0 (MPEG-4) │
│ layer (2 bits) = 00 │
│ protection_absent (1 bit) = 1 (无 CRC) │
│ profile (2 bits) = AAC-LC 对应的 profile │
│ sampling_freq_index (4 bits) │
│ private_bit (1 bit) │
│ channel_config (3 bits) │
│ original_copy (1 bit) │
│ home (1 bit) │
│ copyright_id (1 bit) │
│ copyright_start (1 bit) │
│ frame_length (13 bits) = ADTS header + AAC data │
│ buffer_fullness (11 bits) = 0x7FF (VBR) │
│ num_frames_minus_1 (2 bits) = 0 (1 frame per ADTS) │
│ CRC (16 bits, 可选) │
└──────────────────────────────────────────────────────────────┘
def create_adts_header(
object_type: int,
sample_rate: int,
channels: int,
data_length: int
) -> bytes:
"""
创建 ADTS Header
参数:
object_type: AAC Profile (0=Main, 1=LC, 2=SSR)
sample_rate: 采样率
channels: 声道数
data_length: AAC 原始数据长度
"""
# 采样率索引
freq_table = {
96000: 0, 88200: 1, 64000: 2, 48000: 3,
44100: 4, 32000: 5, 24000: 6, 22050: 7,
16000: 8, 12000: 9, 11025: 10, 8000: 11,
7350: 12
}
freq_index = freq_table.get(sample_rate, 4)
profile = object_type - 1 # AAC-LC=2, profile=1
frame_length = 7 + data_length # 7 bytes ADTS header + data
adts = bytearray(7)
adts[0] = 0xFF # syncword high
adts[1] = 0xF0 # syncword low + ID=0 + layer=00 + protection=1
adts[1] |= (profile & 0x03) << 2 # profile
adts[1] |= (freq_index >> 2) & 0x01 # sampling_freq_index bit 3
adts[2] = (freq_index & 0x03) << 6 # sampling_freq_index bits 2-1
adts[2] |= (channels & 0x07) << 2 # channel_config
adts[3] = 0 # 其他标志
adts[4] = (frame_length >> 5) & 0xFF # frame_length 高 8 位
adts[5] = (frame_length & 0x1F) << 3 # frame_length 低 5 位
adts[6] = 0xFC # buffer_fullness + num_frames
return bytes(adts)
def add_adts_to_aac_raw(aac_raw: bytes, object_type: int = 2,
sample_rate: int = 44100, channels: int = 2) -> bytes:
"""为 AAC Raw 数据添加 ADTS 头"""
adts = create_adts_header(object_type, sample_rate, channels, len(aac_raw))
return adts + aac_raw
8.6 MP3 编码
虽然 AAC 是主流,但 RTMP 也支持 MP3 编码(Sound Format = 2):
MP3 Audio Tag
MP3 Audio Tag Body:
┌────────────────┬──────────────────────────────┐
│ Audio Header │ MP3 Frame Data │
│ (1 byte) │ (含 MP3 Header + Data) │
│ 0x2F (典型) │ │
└────────────────┴──────────────────────────────┘
MP3 Frame Header (4 bytes):
┌──────────────────────────────────────────────────┐
│ syncword (11 bits) = 0x7FF │
│ version (2 bits) = 11 (MPEG1) │
│ layer (2 bits) = 01 (Layer III) │
│ protection (1 bit) │
│ bitrate_index (4 bits) = 码率索引 │
│ sampling_rate (2 bits) = 采样率索引 │
│ padding (1 bit) │
│ channel_mode (2 bits) = 声道模式 │
│ ... 其他标志位 │
└──────────────────────────────────────────────────┘
8.7 音频配置参数
推流音频参数
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 编码格式 | AAC-LC | 最广泛兼容 |
| 采样率 | 44100Hz | CD 质量 |
| 声道 | 立体声 (2ch) | 或单声道 (1ch) |
| 采样位深 | 16-bit | 标准位深 |
| 码率 | 128-192kbps | 音乐/语音场景 |
| AAC Profile | LC | Low Complexity |
FFmpeg 音频编码命令
# AAC 编码(推荐)
ffmpeg -i input.mp4 -c:a aac -b:a 128k -ar 44100 -ac 2 -f flv rtmp://...
# AAC HE v1(低码率优化)
ffmpeg -i input.mp4 -c:a libfdk_aac -profile:a aac_he -b:a 64k -f flv rtmp://...
# AAC HE v2(更低码率)
ffmpeg -i input.mp4 -c:a libfdk_aac -profile:a aac_he_v2 -b:a 32k -f flv rtmp://...
# MP3 编码
ffmpeg -i input.mp4 -c:a libmp3lame -b:a 128k -ar 44100 -f flv rtmp://...
# Opus 编码(Enhanced RTMP,需服务端支持)
ffmpeg -i input.mp4 -c:a libopus -b:a 64k -f flv rtmp://...
AAC Profile 对比
| Profile | 说明 | 适用场景 | 码率范围 |
|---|---|---|---|
| AAC-LC | Low Complexity | 通用 | 64-256kbps |
| HE-AAC v1 | LC + SBR | 低码率 | 32-64kbps |
| HE-AAC v2 | LC + SBR + PS | 超低码率 | 16-32kbps |
| AAC-LD | Low Delay | 实时通信 | 64-128kbps |
| AAC-ELD | Enhanced LD | 超低延迟 | 32-64kbps |
8.8 音频与视频同步
音视频同步是直播系统中的核心挑战:
RTMP 时间戳同步
时间戳(毫秒) 视频帧 音频帧
0 Video Keyframe Audio Frame
33 Video Inter Audio Frame
66 Video Inter Audio Frame
99 Video Inter Audio Frame
100 Audio Frame
133 Video Inter
...
同步原则:
- 视频和音频使用同一个时间基(毫秒)
- 播放器根据时间戳对齐音视频
- 时间戳单调递增
- 音频时间戳通常更均匀(固定间隔)
音视频同步实现
class AVMuxer:
"""音视频复用器 — 同步音频和视频"""
def __init__(self):
self.video_timestamp = 0 # ms
self.audio_timestamp = 0 # ms
self.audio_frame_duration = 23 # ms (44100Hz, 1024 samples/frame)
self.video_frame_duration = 33 # ms (30fps)
def get_next_video_timestamp(self) -> int:
ts = self.video_timestamp
self.video_timestamp += self.video_frame_duration
return ts
def get_next_audio_timestamp(self) -> int:
ts = self.audio_timestamp
self.audio_timestamp += self.audio_frame_duration
return ts
8.9 音频解码器完整实现
#!/usr/bin/env python3
"""
RTMP Audio Decoder
完整的音频消息解析器
"""
import struct
from dataclasses import dataclass
@dataclass
class AudioFrame:
"""音频帧信息"""
codec: str
codec_id: int
sample_rate: int
sample_size: int
channels: int
is_stereo: bool
data: bytes
timestamp: int = 0
# AAC 特有
aac_packet_type: int = -1
aac_sequence_header: object = None
SOUND_FORMAT_NAMES = {
0: 'PCM', 1: 'ADPCM', 2: 'MP3',
3: 'PCM_LE', 4: 'Nellymoser 16kHz', 5: 'Nellymoser 8kHz',
6: 'Nellymoser', 7: 'G.711 A-law', 8: 'G.711 mu-law',
10: 'AAC', 11: 'Speex', 14: 'MP3 8kHz', 15: 'Device'
}
SAMPLE_RATE_MAP = {
0: 5512, 1: 11025, 2: 22050, 3: 44100
}
def decode_audio_message(body: bytes, timestamp: int = 0) -> AudioFrame:
"""
解析 Audio Message Body
参数:
body: Type 8 消息的 Body
timestamp: 消息时间戳
返回:
AudioFrame 对象
"""
if len(body) < 1:
raise ValueError("Audio message body too short")
header = body[0]
sound_format = (header >> 4) & 0x0F
sound_rate = (header >> 2) & 0x03
sound_size = (header >> 1) & 0x01
sound_type = header & 0x01
codec_name = SOUND_FORMAT_NAMES.get(sound_format, f'Unknown({sound_format})')
sample_rate = SAMPLE_RATE_MAP.get(sound_rate, 44100)
sample_size = 8 if sound_size == 0 else 16
channels = 1 if sound_type == 0 else 2
frame = AudioFrame(
codec=codec_name,
codec_id=sound_format,
sample_rate=sample_rate,
sample_size=sample_size,
channels=channels,
is_stereo=(sound_type == 1),
data=body[1:],
timestamp=timestamp,
)
# AAC 特殊处理
if sound_format == 10 and len(body) >= 2:
frame.aac_packet_type = body[1]
if body[1] == 0:
# AAC Sequence Header
try:
config = parse_audio_specific_config(body[2:])
frame.aac_sequence_header = config
frame.sample_rate = config.sampling_frequency
frame.channels = config.channel_configuration
except Exception:
pass
frame.data = body[2:]
return frame
# 测试
def test_audio_decoder():
"""测试音频解码"""
# AAC Sequence Header: 0xAF 0x00 0x12 0x10
body = bytes([0xAF, 0x00, 0x12, 0x10])
frame = decode_audio_message(body, timestamp=1000)
print(f"Codec: {frame.codec}")
print(f"Sample Rate: {frame.sample_rate} Hz")
print(f"Channels: {frame.channels}")
print(f"Is Stereo: {frame.is_stereo}")
print(f"AAC Packet Type: {'Sequence Header' if frame.aac_packet_type == 0 else 'Raw'}")
assert frame.codec == 'AAC'
assert frame.sample_rate == 44100
assert frame.channels == 2
print("✅ Audio decoder 测试通过")
if __name__ == '__main__':
test_audio_decoder()
注意事项
- Sequence Header 优先:AAC Sequence Header 必须在任何 AAC Raw 数据之前发送
- 采样率映射:Sound Rate 字段对于 AAC/MP3 使用不同的映射(22kHz/44kHz vs 5.5kHz/11kHz)
- ADTS 头:RTMP 中的 AAC Raw 不含 ADTS 头,转封装为 HLS/MP4 时需要添加
- 音频帧时长:AAC-LC 每帧通常 1024 个采样,44100Hz 下约 23ms
- 单声道 vs 立体声:低码率场景建议使用单声道(减半码率),语音场景尤为推荐
- G.711 通话场景:WebRTC 关联的 RTMP 流可能使用 G.711 编码,采样率为 8kHz
扩展阅读
上一章:07 - 视频编解码 下一章:09 - 流媒体服务器 — 了解主流 RTMP 服务器与部署