音频编解码:支持AAC/MP3/FLAC等格式处理(194)
·
在鸿蒙(HarmonyOS)应用开发中,音视频编解码是多媒体处理的核心能力。系统通过 AVCodec Kit 提供了强大的底层原子能力,支持包括 AAC、MP3、FLAC 在内的多种主流音频格式的编码与解码。
一、 核心编解码格式支持
鸿蒙系统对音频编解码格式的支持非常全面,开发者可以通过 AVCodec 模块直接调用:
- 音频解码支持
系统支持对多种主流音频格式进行解码,包括 AAC、MPEG(MP3)、FLAC、Vorbis、AMR(amrnb/amrwb)、G711MU、APE、AudioVivid11+ 以及 OPUS。同时支持对应的容器格式解封装,如 mp4、m4a、flac、ogg、aac、mp3、amr、raw 和 ape。 - 音频编码支持
在音频编码方面,系统支持将 PCM 原始音频数据压缩为 AAC、FLAC、MP3、G711mu、AMR、OPUS 等格式,并可封装为 mp4、m4a、flac、aac、mp3、raw、amr、ogg 等目标容器格式。
// AudioDecoderInit.cpp
#include <multimedia/player_framework/native_avcodec_audiocodec.h>
#include <multimedia/player_framework/native_avcapability.h>
OH_AVCodec *CreateAudioDecoder() {
// 设置判定是否为编码;设置 false 表示当前是解码
bool isEncoder = false;
// 通过 MimeType 创建 AAC 解码器
OH_AVCodec *audioDec = OH_AudioCodec_CreateByMime(OH_AVCODEC_MIMETYPE_AUDIO_AAC, isEncoder);
if (audioDec == nullptr) {
// 异常处理:解码器创建失败
}
return audioDec;
}
二、 音频转码与处理流程
在实际业务(如格式转换、音频剪辑)中,通常需要经过“解封装 -> 解码 -> 编码 -> 封装”的标准流程:
- 解封装(Demuxer):使用
AVDemuxer将完整的音频文件(如 .m4a)解封装,获取内部的编码数据流(如 AAC 码流)。 - 解码(Decode):将编码数据送入
AVCodec解码器,还原为 PCM 原始音频数据。 - 编码(Encode):将 PCM 数据送入目标格式的编码器(如 MP3 或 FLAC 编码器),压缩为新的编码数据。
- 封装(Muxer):使用
AVMuxer将新的编码数据按照目标格式封装为最终的音频文件。
// Transcoding.cpp
#include <multimedia/player_framework/native_avcodec_audiocodec.h>
class Transcoding {
public:
int32_t Init(SampleInfo &sampleInfo) {
sampleInfo_ = sampleInfo;
// 1. 创建解复用器 (Demuxer)
CreateDemuxer();
// 2. 创建音频解码器 (Decoder)
CreateAudioDecoder();
// 3. 创建音频编码器 (Encoder)
CreateAudioEncoder();
// 4. 创建复用器 (Muxer)
CreateMuxer();
return AV_ERR_OK;
}
};
三、 开发实战与生命周期管理
使用 Native API 进行音频编解码时,需要严格遵循生命周期并处理异步回调:
- 创建编解码器:通过 MimeType(如
OH_AVCODEC_MIMETYPE_AUDIO_AAC)或 Codec Name 创建编码器或解码器实例。 - 注册异步回调:必须注册
OH_AVCodecCallback,处理OH_AVCodecOnNeedInputBuffer(请求输入数据)、OH_AVCodecOnNewOutputBuffer(输出解码/编码数据)以及OH_AVCodecOnError(异常处理)等关键事件。 - 配置与启动:通过
OH_AudioCodec_Configure设置采样率、声道数、码率等参数,随后调用Prepare和Start启动编解码流程。 - 资源释放:处理完成后,必须按顺序调用
Stop、Reset和Destroy接口释放底层资源,防止内存泄漏。
// AudioCodecCallback.cpp
#include <mutex>
#include <queue>
#include <multimedia/player_framework/native_avcodec_audiocodec.h>
// 1. 初始化线程同步队列
class ADecBufferSignal {
public:
std::mutex inMutex_;
std::mutex outMutex_;
std::condition_variable inCond_;
std::condition_variable outCond_;
std::queue<uint32_t> inQueue_;
std::queue<uint32_t> outQueue_;
std::queue<OH_AVBuffer *> inBufferQueue_;
std::queue<OH_AVBuffer *> outBufferQueue_;
};
// 2. 实现核心回调函数
static void OnInputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *data, void *userData) {
ADecBufferSignal *signal = static_cast<ADecBufferSignal *>(userData);
std::unique_lock<std::mutex> lock(signal->inMutex_);
// 将待解码的码流数据送入输入队列
signal->inQueue_.push(index);
signal->inBufferQueue_.push(data);
signal->inCond_.notify_all();
}
static void OnOutputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *data, void *userData) {
ADecBufferSignal *signal = static_cast<ADecBufferSignal *>(userData);
std::unique_lock<std::mutex> lock(signal->outMutex_);
// 将解码完成的 PCM 数据送入输出队列
signal->outQueue_.push(index);
signal->outBufferQueue_.push(data);
signal->outCond_.notify_all();
}
// 3. 注册回调并启动流程
void RegisterAndStart(OH_AVCodec *audioDec, ADecBufferSignal *signal_) {
OH_AVCodecCallback cb_ = {
nullptr, // OH_AVCodecOnError
nullptr, // OH_AVCodecOnStreamChanged
&OnInputBufferAvailable, // OH_AVCodecOnNeedInputBuffer
&OnOutputBufferAvailable // OH_AVCodecOnNewOutputBuffer
};
int32_t ret = OH_AudioCodec_RegisterCallback(audioDec, cb_, signal_);
if (ret == AV_ERR_OK) {
OH_AudioCodec_Prepare(audioDec);
OH_AudioCodec_Start(audioDec);
}
}
四、 性能优化
- 容器与编码的匹配:在进行音频封装时,必须确保编码格式与目标容器兼容。例如,系统 Muxer 目前不支持将 OPUS 封装为 OGG 容器,若需实现此功能,开发者需自行实现 OGG 容器的封装逻辑。
- 异步回调防阻塞:在
OH_AVCodecCallback回调函数中,严禁执行耗时操作或再次调用编解码器接口,应通过队列(Queue)和条件变量将数据传递到独立的工作线程中处理。 - 参数动态变化处理:对于 Audio Vivid、AAC、FLAC 等格式,解码过程中可能会发生采样率或声道数的变化。需注册
OH_AVCodecOnStreamChanged回调,及时获取并更新输出参数,避免音频播放异常。
// StreamChangedAndCapability.cpp
#include <multimedia/player_framework/native_avcodec_audiocodec.h>
#include <multimedia/player_framework/native_avcapability.h>
// 1. 处理解码过程中的动态参数变化(如采样率、声道数改变)
static void OnOutputFormatChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData) {
int32_t sampleRate;
int32_t channelCount;
// 实时获取并更新输出参数,避免音频播放异常
if (OH_AVFormat_GetIntValue(format, OH_MD_KEY_AUD_SAMPLE_RATE, &sampleRate) == AV_ERR_OK) {
// 动态调整下游音频渲染器的采样率配置
}
if (OH_AVFormat_GetIntValue(format, OH_MD_KEY_AUD_CHANNEL_COUNT, &channelCount) == AV_ERR_OK) {
// 动态调整声道数
}
}
// 2. 编码前校验参数支持情况(防错指南)
bool ValidateEncoderParams(const char *mimeType, int32_t sampleRate) {
// 获取编码器能力
OH_AVCapability *capability = OH_AVCodec_GetCapability(mimeType, true);
if (capability == nullptr) return false;
// 确认待配置的采样率是否被支持
const int32_t *sampleRates = nullptr;
uint32_t sampleRateNum = 0;
OH_AVCapability_GetAudioSupportedSampleRates(capability, &sampleRates, &sampleRateNum);
for (uint32_t i = 0; i < sampleRateNum; i++) {
if (sampleRates[i] == sampleRate) {
return true; // 参数合法
}
}
return false; // 参数不合法,需调整
}
五、 音频编解码实战:Native API 异步回调与队列管理
鸿蒙的 Native 音频编解码采用高度异步的设计,开发者需要通过 C++ 的队列与条件变量来安全地流转数据。
- 回调函数注册与数据流转
开发者必须通过OH_AudioCodec_RegisterCallback注册回调集合。当解码器准备好接收数据时,会触发OH_AVCodecOnNeedInputBuffer回调,此时应将待解码的码流数据送入输入队列(inBufferQueue_);当解码完成时,触发OH_AVCodecOnNewOutputBuffer,将解码后的 PCM 数据送入输出队列(outBufferQueue_)。 - 多线程安全与防阻塞
由于回调函数中严禁进行耗时操作,必须使用std::mutex和std::condition_variable进行线程同步。当主工作线程从队列中取出 Buffer 处理完毕后,务必及时释放归还,否则达到 Buffer 上限后解码器将停止工作。 - 动态参数监听
针对 AAC、FLAC 等格式,解码过程中可能发生采样率或声道数变化。通过注册OH_AVCodecOnStreamChanged回调,应用可实时获取最新的OH_MD_KEY_AUD_SAMPLE_RATE等参数,从而动态调整下游的音频渲染器配置。
// NativeAudioCodec.cpp
#include <multimedia/player_framework/native_avcodec_audiocodec.h>
#include <mutex>
#include <queue>
#include <condition_variable>
// 1. 定义线程安全的 Buffer 信号队列
class AEncBufferSignal {
public:
std::mutex inMutex_;
std::mutex outMutex_;
std::condition_variable inCond_;
std::condition_variable outCond_;
std::queue<uint32_t> inQueue_;
std::queue<uint32_t> outQueue_;
std::queue<OH_AVBuffer *> inBufferQueue_;
std::queue<OH_AVBuffer *> outBufferQueue_;
};
// 2. 实现输入与输出回调
static void OnInputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *data, void *userData) {
AEncBufferSignal *signal = static_cast<AEncBufferSignal *>(userData);
std::unique_lock<std::mutex> lock(signal->inMutex_);
signal->inQueue_.push(index);
signal->inBufferQueue_.push(data);
signal->inCond_.notify_all(); // 通知工作线程数据已就绪
}
static void OnOutputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *data, void *userData) {
AEncBufferSignal *signal = static_cast<AEncBufferSignal *>(userData);
std::unique_lock<std::mutex> lock(signal->outMutex_);
signal->outQueue_.push(index);
signal->outBufferQueue_.push(data);
signal->outCond_.notify_all(); // 通知工作线程处理输出数据
}
// 3. 监听动态参数变化(如采样率改变)
static void OnStreamChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData) {
int32_t sampleRate = 0;
OH_AVFormat_GetIntValue(format, OH_MD_KEY_AUD_SAMPLE_RATE, &sampleRate);
// 根据新的 sampleRate 动态调整下游音频渲染器配置
}
// 4. 注册回调并启动
void InitAudioCodec(OH_AVCodec *audioEnc, AEncBufferSignal *signal_) {
OH_AVCodecCallback cb_ = {
nullptr, // OnError
&OnStreamChanged, // OnStreamChanged
&OnInputBufferAvailable, // OnNeedInputBuffer
&OnOutputBufferAvailable // OnNewOutputBuffer
};
OH_AudioCodec_RegisterCallback(audioEnc, cb_, signal_);
OH_AudioCodec_Prepare(audioEnc);
OH_AudioCodec_Start(audioEnc);
}
六、 跨平台与 ArkTS 层的高效集成
对于不需要进行底层 C++ 复杂处理的常规音频业务,鸿蒙同样提供了高效的跨平台与 ArkTS 层解决方案。
- Flutter 跨平台音频处理
在 Flutter for OpenHarmony 开发中,可使用flutter_sound插件。该插件完整适配了鸿蒙的底层能力,支持 AAC、MP3、FLAC、Opus 等格式的录制与播放。开发者只需在pubspec.yaml中引入依赖,并在module.json5中声明ohos.permission.MICROPHONE权限,即可通过FlutterSoundPlayer快速实现音频流的控制。 - ArkTS 层播放器实例管理
在 ArkTS 层使用AVPlayer播放音频时,极易因实例未释放导致内存泄漏。最佳实践是采用单例模式管理AVPlayer实例,或在每次播放结束后严格调用release()方法。同时,应遵循“创建 -> 配置 -> 准备 -> 播放 -> 释放”的黄金生命周期流程,避免重复触发MediaService启动带来的性能开销。
// ArkTSAudioManager.ets
import { media } from '@kit.MediaKit';
export class ArkTSAudioManager {
private static instance: ArkTSAudioManager;
private avPlayer: media.AVPlayer | null = null;
// 1. 单例模式管理,防止内存泄漏
public static getInstance(): ArkTSAudioManager {
if (!this.instance) {
this.instance = new ArkTSAudioManager();
}
return this.instance;
}
// 2. 遵循黄金生命周期:创建 -> 配置 -> 准备 -> 播放
public async playAudio(url: string) {
try {
// 释放旧实例
await this.release();
// 创建新实例
this.avPlayer = await media.createAVPlayer();
// 状态机监听
this.avPlayer.on('stateChange', (state: string) => {
if (state === 'initialized') {
this.avPlayer?.prepare();
} else if (state === 'prepared') {
this.avPlayer?.play();
}
});
// 设置资源路径
this.avPlayer.url = url;
} catch (err) {
console.error('AVPlayer 初始化失败:', err);
}
}
// 3. 严格的资源释放
public async release() {
if (this.avPlayer) {
this.avPlayer.off('stateChange'); // 取消监听
await this.avPlayer.stop();
await this.avPlayer.reset();
await this.avPlayer.release();
this.avPlayer = null;
}
}
}
更多推荐

所有评论(0)