核心摘要
- AAC编解码器相比G.711可节省约50%带宽,在低码率场景下音质表现优于传统方案
- 集成方案仅需修改设备端SDK和客户端NDK,无需改动KVS服务端架构
- 完整覆盖SDP协商、RTP封包解包、平台特定编解码实现等关键技术环节
- 支持iOS、Android及主流IoT设备的硬件AAC编解码能力
Amazon Kinesis Video Streams WebRTC集成AAC编解码器完整指南
Amazon Kinesis Video Streams WebRTC技术概览
Amazon Kinesis Video Streams提供了符合标准的WebRTC实现,作为一项完全托管的云服务运行。借助这项能力,开发者可以在摄像头、IoT设备与符合WebRTC标准的移动端或Web播放器之间建立安全的实时媒体流传输通道,实现双向音视频交互。这种托管模式的核心价值在于:无需自行构建、运维或扩展信令服务器、媒体中继服务器等WebRTC基础设施组件。
从架构层面来看,Amazon KVS WebRTC采用了典型的信令与媒体分离设计。信令通道负责处理SDP交换、ICE候选协商等控制面消息,而媒体流则通过P2P直连或TURN中继进行传输。这种设计既保证了低延迟的媒体传输体验,又提供了穿越NAT和防火墙的可靠性保障。
现有编解码器支持与AAC的技术优势
当前支持的编解码器清单
Amazon KVS WebRTC目前原生支持以下编解码器:
- 音频编解码器:G.711 A-Law、G.711 U-Law、Opus
- 视频编解码器:H.264、VP8
AAC编解码器的核心优势
AAC(Advanced Audio Coding)作为一种高效的音频压缩标准,在实时通信场景中展现出多项显著优势:
- 更高的压缩效率:在相同音质条件下,AAC相比MP3可节省约30%的带宽开销
- 低码率音质保持:即使在较低的比特率下,AAC仍能维持令人满意的音频质量
- 广泛的设备兼容性:几乎所有现代移动设备、浏览器和IoT芯片都内置AAC硬件解码支持
- 低延迟特性:编解码延迟可控,适合对实时性要求较高的音频传输场景
- 多声道能力:原生支持立体声及多声道音频配置
各平台AAC支持情况
AAC在主流平台上的支持相当成熟:
- iOS:从iOS 3.0版本起即提供原生硬件AAC编解码支持
- Android:Android 3.0(API Level 11)及以上版本支持AAC编解码
- IoT设备:大多数音频处理芯片方案都集成了AAC硬件编码能力
为何需要在KVS WebRTC中引入AAC
尽管KVS WebRTC已支持多种音频格式,引入AAC仍具有明确的业务价值:
- 带宽成本优化:在保持音质的前提下显著减少网络带宽消耗
- 用户体验提升:更优的音频质量配合更低的传输延迟
- 运营成本降低:减少数据传输产生的费用支出
- 设备适配增强:更好地兼容移动终端和IoT设备的硬件编解码能力
整体技术架构设计
AAC编解码器的集成方案遵循最小化改动原则,整体架构调整范围如下:
- 设备端SDK修改:需要在amazon-kinesis-video-streams-webrtc-sdk-c中添加AAC相关处理逻辑
- 客户端NDK修改:需要在WebRTC原生库中集成AAC编解码器支持
- 服务端KVS:无需任何修改,信令服务透传SDP内容
这种设计的优势在于:服务端作为透明的信令中继,不参与编解码器协商的具体逻辑,因此新增编解码器支持仅需在端侧完成。对于正在评估多云账单代付解决方案的团队而言,这种架构也意味着可以灵活选择不同云服务商的资源进行部署测试。
关键技术实现详解
SDP中添加AAC编解码格式
SDP(Session Description Protocol)协商是WebRTC建立连接的核心环节。需要在以下位置添加AAC格式声明:
- 设备端代码:
amazon-kinesis-video-streams-webrtc-sdk-c/src/source/PeerConnection/SessionDescription.c - iOS客户端:
webrtc/sdk/objc/components/audio_codec/RTCDefaultAudioDecoderFactory.m - Android客户端:
webrtc/sdk/android/src/java/org/webrtc/MediaCodecAudioDecoderFactory.java
RTP载荷的封包与解包规范
AAC的RTP封包遵循RFC 3640标准,该标准定义了MPEG-4基本流的RTP传输格式。主要技术特点包括:
- 支持在单个RTP包中传输多个AAC帧
- 使用AU-headers结构描述每个音频访问单元
- 支持对大尺寸AAC帧进行分片传输
相关实现代码位于:
- 设备端:
amazon-kinesis-video-streams-webrtc-sdk-c/src/source/Rtp/Codecs/RtpAacPayloader.c - iOS/Android NDK:
webrtc/modules/audio_coding/aac_config_parser.cc
实际数据的AAC编解码处理
编解码实现需要针对不同平台采用相应的技术方案:
- 设备端编码:大多数音频芯片支持AAC硬件编码,通过芯片厂商提供的SDK接口获取AAC音频帧
- iOS客户端解码:
webrtc/sdk/objc/components/audio_codec/RTCAudioDecoderAAC.m - Android客户端解码:
webrtc/sdk/android/src/java/org/webrtc/HardwareAudioDecoder.java
设备端SDK实现步骤
开发环境准备
AAC集成涉及设备端SDK和客户端NDK的修改,需要准备以下编译环境:
- 设备端:Linux环境进行代码编辑和编译
- Android客户端:Linux环境进行NDK编译
- iOS客户端:macOS环境进行Xcode编译
AAC RTP载荷处理器实现
核心文件结构:
src/source/PeerConnection/Rtp/Codecs/RtpAacPayloader.h– 头文件定义src/source/PeerConnection/Rtp/Codecs/RtpAacPayloader.c– 实现文件
以下是RTP载荷处理器的核心实现代码:
#define LOG_CLASS "RtpAacPayloader"
#include "../../Include_i.h"
STATUS createPayloadForAac(UINT32 mtu, PBYTE aacFrame, UINT32 aacFrameLength,
PBYTE payloadBuffer, PUINT32 pPayloadLength, PUINT32 pPayloadSubLength,
PUINT32 pPayloadSubLenSize)
{
UNUSED_PARAM(mtu);
ENTERS();
STATUS retStatus = STATUS_SUCCESS;
UINT32 payloadLength = 0;
UINT32 payloadSubLenSize = 0;
BOOL sizeCalculationOnly = (payloadBuffer == NULL);
CHK(aacFrame != NULL && pPayloadSubLenSize != NULL && pPayloadLength != NULL &&
(sizeCalculationOnly || pPayloadSubLength != NULL), STATUS_NULL_ARG);
payloadLength = aacFrameLength;
payloadSubLenSize = 1;
// Only return size if given buffer is NULL
CHK(!sizeCalculationOnly, retStatus);
CHK(payloadLength <= *pPayloadLength && payloadSubLenSize <= *pPayloadSubLenSize,
STATUS_BUFFER_TOO_SMALL);
MEMCPY(payloadBuffer, aacFrame, aacFrameLength);
pPayloadSubLength[0] = aacFrameLength;
CleanUp:
if (STATUS_FAILED(retStatus) && sizeCalculationOnly) {
payloadLength = 0;
payloadSubLenSize = 0;
}
if (pPayloadSubLenSize != NULL && pPayloadLength != NULL) {
*pPayloadLength = payloadLength;
*pPayloadSubLenSize = payloadSubLenSize;
}
LEAVES();
return retStatus;
}
解包函数实现:
STATUS depayAacFromRtpPayload(PBYTE pRawPacket, UINT32 packetLength,
PBYTE pAacData, PUINT32 pAacLength, PBOOL pIsStart)
{
ENTERS();
STATUS retStatus = STATUS_SUCCESS;
UINT32 aacLength = 0;
BOOL sizeCalculationOnly = (pAacData == NULL);
CHK(pRawPacket != NULL && pAacLength != NULL, STATUS_NULL_ARG);
CHK(packetLength > 0, retStatus);
aacLength = packetLength;
CHK(!sizeCalculationOnly, retStatus);
CHK(aacLength <= *pAacLength, STATUS_BUFFER_TOO_SMALL);
MEMCPY(pAacData, pRawPacket, aacLength);
CleanUp:
if (STATUS_FAILED(retStatus) && sizeCalculationOnly) {
aacLength = 0;
}
if (pAacLength != NULL) {
*pAacLength = aacLength;
}
if (pIsStart != NULL) {
*pIsStart = TRUE;
}
LEAVES();
return retStatus;
}
核心处理函数说明
- createPayloadForAac – 创建AAC RTP载荷
- getNextAacFrameLength – 获取下一个AAC帧的长度信息
- createPayloadFromAacFrame – 从原始AAC帧创建RTP载荷
- depayAacFromRtpPayload – 从RTP载荷中解包还原AAC数据
编解码器集成配置
关键配置参数:
- 编解码器枚举:
RTC_CODEC_AAC - 默认载荷类型:
DEFAULT_PAYLOAD_AAC (97) - SDP配置:”AAC/16000/1″ 用于rtpmap属性
核心枚举和常量定义
在 src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h 中添加:
- 在 RTC_CODEC 枚举中添加
RTC_CODEC_AAC = 8 - AAC成为SDK支持的第8种编解码器
SDP协商相关配置
在 src/source/PeerConnection/SessionDescription.h 中定义以下常量:
- AAC的SDP描述值:
#define AAC_VALUE "AAC/16000" - AAC的默认payload类型:
#define DEFAULT_PAYLOAD_AAC (UINT64) 96 - AAC的时钟频率:
#define AAC_CLOCKRATE (UINT64) 16000 - AAC的格式参数:
#define DEFAULT_AAC_FMTP "profile-level-id=1; mode=AAC-hbr; config=F8F1; SizeLength=13; IndexLength=3; IndexDeltaLength=3"
SDP协商处理逻辑
在 src/source/PeerConnection/SessionDescription.c 中需要完成以下修改:
- 在codec表初始化时添加AAC支持:
hashTableUpsert(codecTable, RTC_CODEC_AAC, DEFAULT_PAYLOAD_AAC) - 在SDP解析时识别AAC编解码器:检查AAC_VALUE并设置
rtcCodec = RTC_CODEC_AAC - 在生成SDP offer/answer时包含AAC的rtpmap和fmtp属性
- 在音频编解码器判断逻辑中包含AAC:
isAudioCodec = (codec == RTC_CODEC_MULAW || codec == RTC_CODEC_ALAW || codec == RTC_CODEC_OPUS || codec == RTC_CODEC_AAC)
RTP打包和解包集成
在 src/source/PeerConnection/Rtp.c 的发送路径中添加AAC处理:
case RTC_CODEC_AAC:
rtpPayloadFunc = createPayloadForAac;
rtpTimestamp = CONVERT_TIMESTAMP_TO_RTP(AAC_CLOCKRATE, pFrame->presentationTs);
break;
在 src/source/PeerConnection/PeerConnection.c 的接收路径中添加AAC处理:
case RTC_CODEC_AAC:
depayFunc = depayAacFromRtpPayload;
clockRate = AAC_CLOCKRATE;
break;
同时在编解码器表中注册AAC:hashTablePut(pKvsPeerConnection->pCodecTable, rtcCodec, DEFAULT_PAYLOAD_AAC)
示例应用程序支持
在 samples/Samples.h 中添加以下定义:
- AAC样本帧数量:
#define NUMBER_OF_AAC_FRAME_FILES 206 - AAC编解码器名称:
#define AUDIO_CODEC_NAME_AAC "aac" - AAC帧持续时间:
#define SAMPLE_AUDIO_AAC_FRAME_DURATION (64 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND)
在 samples/kvsWebRTCClientMaster.c 中完成以下集成:
- 添加命令行参数解析以支持AAC选项
- 配置AAC的rolling buffer参数(32kbps比特率)
- 实现AAC样本帧的读取和发送逻辑
- 设置正确的帧间隔时间(64ms)
样本数据文件位于 samples/aacSampleFrames/ 目录,包含206个预编码的AAC样本帧文件(sample-001.aac 到 sample-206.aac)。
客户端NDK实现步骤
核心配置项
- 构建开关:
rtc_use_aac在build_overrides/build.gni中定义 - 平台支持:默认在 Windows、iOS、Android 平台启用AAC
- 编译宏:使用
DISABLE_AAC宏控制AAC功能的编译包含
AAC解析和处理模块
common_audio/aac/ 目录下的核心文件:
- aac_common.h/cc – AAC通用定义和帧处理逻辑
- aac_config_parser.h/cc – AAC配置解析器
- aac_decoder_impl.h/cc – AAC解码器实现
RTP打包和解包模块
modules/rtp_rtcp/source/ 目录下的文件:
- rtp_format_aac.h/cc – AAC RTP打包器
- audio_rtp_depacketizer_aac.h/cc – AAC RTP解包器
平台特定实现
iOS/macOS平台:
sdk/objc/components/audio_codec/RTCAudioEncoderAAC.h/mmsdk/objc/components/audio_codec/RTCAudioDecoderAAC.h/mm
Android平台:
sdk/android/src/jni/audio_codec_aac.cc
核心AAC编解码器库
新增的第三方AAC库文件:
modules/third_party/aac/aac_enc_dec.h– AAC编解码器头文件modules/third_party/aac/aac_encode.c– AAC编码器实现modules/third_party/aac/aac_decode.c– AAC解码器实现
这些文件实现了AAC编解码算法,支持16kHz采样率配置。
WebRTC音频编码模块集成
新增的音频编码模块文件:
modules/audio_coding/codecs/aac/aac_interface.h/cc– AAC接口定义与实现modules/audio_coding/codecs/aac/audio_encoder_aac.h/cc– AAC编码器封装modules/audio_coding/codecs/aac/audio_decoder_aac.h/cc– AAC解码器封装modules/audio_coding/codecs/aac/audio_coder_aac_common.h/cc– AAC通用功能
API层面的编解码器支持
AAC编码器API实现示例(api/audio_codecs/aac/audio_encoder_aac.h/cc):
AudioEncoder::EncodedInfo AudioEncoderAACImpl::EncodeImpl(
uint32_t rtp_timestamp,
rtc::ArrayView audio,
rtc::Buffer* encoded) {
// 缓存音频数据直到有足够的帧
if (num_10ms_frames_buffered_ == 0)
first_timestamp_in_buffer_ = rtp_timestamp;
// 将多声道音频数据分离到各个声道缓冲区
const size_t start = kSampleRateHz / 100 * num_10ms_frames_buffered_;
for (size_t i = 0; i < kSampleRateHz / 100; ++i)
for (size_t j = 0; j < num_channels_; ++j)
encoders_[j].speech_buffer[start + i] = audio[i * num_channels_ + j];
// 检查是否有足够的帧进行编码
if (++num_10ms_frames_buffered_ < num_10ms_frames_per_packet_) {
return EncodedInfo();
}
// 对每个声道分别进行AAC编码
const size_t samples_per_channel = SamplesPerChannel();
size_t bytes_encoded = 0;
for (size_t i = 0; i < num_channels_; ++i) {
bytes_encoded = WebRtcAAC_Encode(
encoders_[i].encoder,
encoders_[i].speech_buffer.get(),
samples_per_channel,
encoders_[i].encoded_buffer.data());
if (bytes_encoded <= 0) {
return EncodedInfo();
}
}
// 构建编码结果
EncodedInfo info;
info.encoded_bytes = bytes_encoded;
info.encoded_timestamp = first_timestamp_in_buffer_;
info.payload_type = payload_type_;
info.encoder_type = CodecType::kAac;
return info;
}
AAC解码器API实现示例(api/audio_codecs/aac/audio_decoder_aac.h/cc):
int AudioDecoderAACImpl::DecodeInternal(const uint8_t* encoded,
size_t encoded_len,
int sample_rate_hz,
int16_t* decoded,
SpeechType* speech_type) {
RTC_DCHECK_EQ(SampleRateHz(), sample_rate_hz);
int16_t temp_type = 1; // 默认为语音类型
// 调用底层AAC解码接口
size_t ret = WebRtcAAC_Decode(dec_state_, encoded, encoded_len,
decoded, &temp_type);
*speech_type = ConvertSpeechType(temp_type);
return static_cast(ret);
}
// 数据包时长计算
int AudioDecoderAACImpl::PacketDuration(const uint8_t* encoded,
size_t encoded_len) const {
// AAC: 每个样本每声道占用1/2字节
return static_cast(2 * encoded_len / Channels());
}
构建系统配置
BUILD.gn文件修改要点:
- 在
modules/audio_coding/BUILD.gn中添加AAC相关的构建目标 - 在
api/audio_codecs/BUILD.gn中集成AAC编解码器工厂 - 在
webrtc.gni中添加rtc_include_aac = true配置选项
编解码器工厂集成
工厂类修改:
builtin_audio_encoder_factory.cc– 添加AAC编码器到内置工厂builtin_audio_decoder_factory.cc– 添加AAC解码器到内置工厂- 在
AudioEncoder::CodecType枚举中添加kAac = 7