亚马逊AWS官方博客
在Amazon Kinesis Video Streams WebRTC中增加AAC编解码支持
1.概述
本文主要介绍Amazon Kinesis Video Streams WebRTC是什么,AAC编解码器相比现有音频编解码器的技术优势,在Amazon KVS WebRTC中集成AAC编解码器的完整技术方案,详细实现步骤,性能对比测试结果,以及生产环境部署的注意事项。
通过本文,您将学会如何利用AAC编解码器在保持音频质量的同时显著降低带宽消耗,提升Amazon Kinesis Video Streams WebRTC应用的整体音频性能表现。
2.背景介绍
2.1 Amazon Kinesis Video Streams WebRTC** **简介
Amazon Kinesis Video Streams 提供符合标准的 WebRTC 实现作为完全托管的功能。您可以使用 Amazon Kinesis Video Streams with WebRTC 安全地进行媒体的实时流式传输,或在任何摄像头 IoT 设备与符合 WebRTC 的移动或 Web 播放器之间执行双向音频或视频交互。借助这项全面托管的功能,您不必构建、运营或扩展任何与 WebRTC 相关的云基础设施(例如信令或媒体中继服务器)便能安全地在应用程序和设备间流式传输媒体。
2.1.1 Amazon KVS WebRTC** **的拓扑结构
2.1.2 Amazon KVS WebRTC** **的架构图
2.2 Amazon Kinesis Video Streams WebRTC** **支持的编解码器** **音频
- G.711 A-Law
- G.711 U-Law
- Opus
- H.264
- VP8
视频
2.3 AAC** **编解码器技术优势
AAC(Advanced Audio Coding)是一种高效的音频编解码器,相比传统的音频编解码器具有以下优势:
更高的压缩效率:相同音质下,AAC比MP3节省约30%的带宽 更好的音质:在低码率下仍能保持较高的音频质量 广泛的设备支持:几乎所有现代设备都支持AAC解码 低延迟:适合实时音频传输场景 多声道支持:支持立体声和多声道音频
2.4 AAC** **支持情况
AAC音频格式支持非常广泛,在各端的支持情况如下
iOS:从iOS 3.0开始原生支持AAC硬件编解码 Android:从Android 3.0(API Level 11)开始支持AAC编解码 IoT 设备:大多数音频芯片方案都支持AAC硬件编码
2.5 ** **为什么在KVS WebRTC中需要AAC支持
KVS WebRTC中已经有了很多的音频格式支持,需要AAC支持的原因如下
降低带宽消耗:在保持音质的前提下减少网络带宽使用 提升用户体验:更好的音频质量和更低的延迟 降低成本:减少数据传输费用 设备兼容性:更好地支持移动设备和IoT设备
3.
技术实现方案
3.1 ** **整体架构设计
- 修改设备端SDK
- 修改客户端NDK
- 无需修改服务端KVS
3.2 ** **关键技术实现点
3.2.1 SDP 中添加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
3.2.2 RTP** **载荷的封包与解包
AAC的RTP封包遵循RFC 3640标准,主要特点:
- 支持多个AAC帧在单个RTP包中传输
- 使用AU-headers来描述每个音频帧
- 支持分片传输大的AAC帧
参照文档:AAC的RTP封包与解包,参照RFC 3640标准
链接:https://www.rfc-editor.org/rfc/rfc3640.html
设备端代码实现:
- amazon-kinesis-video-streams-webrtc-sdk-c/src/source/Rtp/Codecs/RtpAacPayloader.c
iOS 和Android的NDK实现:
- webrtc/modules/audio_coding/aac_config_parser.cc
3.2.3 实际数据的AAC编解码 设备端编码:大多数音频芯片都支持AAC硬件编码,通过相应SDK接口获取AAC音频帧 iOS 客户端解码:
- webrtc/sdk/objc/components/audio_codec/RTCAudioDecoderAAC.m
Android 客户端解码:
- webrtc/sdk/android/src/java/org/webrtc/HardwareAudioDecoder.java
4.具体实现步骤
4.1 ** **环境准备
修改涉及到设备端SDK的修改和客户端NDK的修改,需要具备代码编辑和编译环境:
- 设备端使用Linux进行编辑和编译
- Android使用Linux进行编译
- iOS使用Mac环境进行编译
4.2 ** **设备端实现
4.2.1 AAC RTP 载荷处理器 核心文件:
- src/source/PeerConnection/Rtp/Codecs/RtpAacPayloader.h – 头文件定义
- src/source/PeerConnection/Rtp/Codecs/RtpAacPayloader.c – 实现文件
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;
}
4.2.2 ** **核心处理函数
- createPayloadForAac – 创建AAC载荷
- getNextAacFrameLength – 获取下一个AAC帧长度
- createPayloadFromAacFrame – 从AAC帧创建载荷
- depayAacFromRtpPayload – 从RTP载荷中解包AAC
- 编解码器枚举:RTC_CODEC_AAC
- 默认载荷类型:DEFAULT_PAYLOAD_AAC (97)
- SDP配置:”AAC/16000/1″ 用于rtpmap
4.2.3 ** **编解码器集成
4.2.4 ** **核心枚举和常量定义
在 src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h 中:
- 在 RTC_CODEC 枚举中添加了 RTC_CODEC_AAC = 8
- 这使得AAC成为SDK支持的第8种编解码器
4.2.5 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”
4.2.6 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)
4.2.7 RTP** **打包和解包实现
创建专门的AAC RTP处理文件: src/source/Rtp/Codecs/RtpAacPayloader.h:
- 定义AAC RTP打包函数:createPayloadForAac()
- 定义AAC RTP解包函数:depayAacFromRtpPayload()
src/source/Rtp/Codecs/RtpAacPayloader.c:
- 实现了AAC RTP打包逻辑(直接复制AAC帧数据)
- 实现了AAC RTP解包逻辑(直接提取AAC数据)
4.2.8 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)
4.2.9 ** **示例应用程序支持
在 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/kvsWebRTCClientViewer.c 中:
- 添加AAC接收支持
4.2.10 ** **样本数据文件
创建 samples/aacSampleFrames/ 目录:
- 包含206个AAC样本帧文件(sample-001.aac 到 sample-206.aac)
- 每个文件包含预编码的AAC音频数据
- 构建开关:rtc_use_aac 在 build_overrides/build.gni 中定义
- 平台支持:默认在 Windows、iOS、Android 平台启用AAC
- 编译宏:使用 DISABLE_AAC 宏来控制AAC功能的编译
4.3 ** **客户端实现
4.3.1 ** **核心配置
4.3.2 AAC 解析和处理模块 common_audio/aac/ 目录下的文件:
- aac_common.h/cc – AAC通用定义和帧处理
- aac_config_parser.h/cc – AAC配置解析器
- aac_decoder_impl.h/cc – AAC解码器实现
4.3.3 RTP 打包和解包 modules/rtp_rtcp/source/ 目录下的文件:
- rtp_format_aac.h/cc – AAC RTP打包器
- audio_rtp_depacketizer_aac.h/cc – AAC RTP解包器
4.3.4 平台特定实现 iOS/macOS:
- sdk/objc/components/audio_codec/RTCAudioEncoderAAC.h/mm
- sdk/objc/components/audio_codec/RTCAudioDecoderAAC.h/mm
Android:
- sdk/android/src/jni/audio_codec_aac.cc
4.3.5 ** **核心 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 采样率。
4.3.6 WebRTC ** **音频编码模块集成
新增的音频编码模块文件:
- modules/audio_coding/codecs/aac/aac_interface.h – AAC 接口定义
- modules/audio_coding/codecs/aac/aac_interface.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 通用功能
4.3.7 API ** **层面的支持
新增的 API 文件:
- api/audio_codecs/aac/audio_encoder_aac.h/cc – AAC 编码器 API
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;
}
- api/audio_codecs/aac/audio_decoder_aac.h/cc – AAC 解码器 API
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());
}
- api/audio_codecs/aac/audio_encoder_aac_config.h – AAC 编码器配置
4.3.8 ** **构建系统配置
BUILD.gn 文件修改:
- 在 modules/audio_coding/BUILD.gn 中添加了 AAC 相关的构建目标
- 在 api/audio_codecs/BUILD.gn 中集成 AAC 编解码器工厂
- 在 webrtc.gni 中添加了 rtc_include_aac = true 配置选项
4.3.9 ** **编解码器工厂集成
工厂类修改:
- builtin_audio_encoder_factory.cc – 添加 AAC 编码器到内置工厂
- builtin_audio_decoder_factory.cc – 添加 AAC 解码器到内置工厂
- 在 AudioEncoder::CodecType 枚举中添加了 kAac = 7
4.3.10. Android ** **平台支持
Android 特定修改:
- WebRtcAudioManager.java – 强制设置采样率为 16000Hz
- audio_record_jni.cc 和 audio_track_jni.cc – 改进音频设备初始化逻辑
4.3.11 iOS ** **平台支持
iOS 特定修改:
- RTCRtpCodecParameters.mm – 添加了 kRTCAacCodecName 常量
4.3.12 ** **技术特性
AAC 实现特点:
- 支持 16kHz 采样率
- 支持单声道和立体声
- 帧长度为 20ms (可配置为 10-60ms)
- 比特率约为 28kbps (单声道)
5.测试与验证
5.1 ** **设备端测试
5.1.1 ** **下载和编译
5.1.2 ** **配置环境变量
export AWS_KVS_LOG_LEVEL=1
export AWS_KVS_CACERT_PATH=../certs/cert.pem
export AWS_DEFAULT_REGION=cn-north-1
export AWS_ACCESS_KEY_ID=
export AWS_SECRET_ACCESS_KEY=
export AWS_IOT_CORE_THING_NAME=demo-channel
export AWS_ENABLE_FILE_LOGGING=TRUE
5.1.3 ** **执行测试
5.2 ** **客户端测试
5.2.1 ** **下载仓库
5.2.2 Android** **客户端配置
在awsconfiguration.json中配置:
{
“Version”: “1.0”,
“CredentialsProvider”: {
“CognitoIdentity”: {
“Default”: {
“PoolId”: “us-west-2:[已去除电话]-89ab-cdef-[已去除电话]89abcdef”,
“Region”: “us-west-2”
}
}
},
“IdentityManager”: {
“Default”: {}
},
“CognitoUserPool”: {
“Default”: {
“AppClientSecret”: “abcdefghijklmnopqrstuvwxyz[已去除电话]abcdefghijklmno”,
“AppClientId”: “[已去除电话]abcdefghijklmnop”,
“PoolId”: “us-west-2_qRsTuVwXy”,
“Region”: “us-west-2”
}
}
}
5.2.2 ** **编译
5.3 ** **测试结果
5.3.1 offer** **信息如下
5.3.2 Answer** **信息如下
5.3.3 实时画面 测试结果对比:
- G.711 vs AAC:相同音质下,AAC带宽消耗减少约50%
- Opus vs AAC:AAC在低码率下音质更稳定
- 延迟对比:AAC编解码延迟与Opus相当,优于G.711
6.总结
本文介绍了如何在Amazon Kinesis Video Streams WebRTC中成功集成AAC编解码器支持。通过详细的技术方案设计、完整的实现步骤和全面的测试验证,我们展示了AAC在实时音频通信场景下的显著优势。
AAC编解码器的集成为Amazon KVS WebRTC带来了更好的音频质量、更低的带宽消耗和更广泛的设备兼容性,特别适合移动设备和IoT设备的音频传输场景。
附录** **参考资料
- Kinesis Video Streams with WebRTC 开发人员指南
- RTP Payload Format for Transport of MPEG-4 Elementary Streams
代码仓库** ***前述特定亚马逊云科技生成式人工智能相关的服务目前在亚马逊云科技海外区域可用。亚马逊云科技中国区域相关云服务由西云数据和光环新网运营,具体信息以中国区域官网为准。