在鸿蒙(HarmonyOS)应用开发中,音视频编解码是多媒体处理的核心能力。系统通过 AVCodec Kit 提供了强大的底层原子能力,支持包括 AAC、MP3、FLAC 在内的多种主流音频格式的编码与解码。

一、 核心编解码格式支持

鸿蒙系统对音频编解码格式的支持非常全面,开发者可以通过 AVCodec 模块直接调用:

  1. 音频解码支持
    系统支持对多种主流音频格式进行解码,包括 AAC、MPEG(MP3)、FLAC、Vorbis、AMR(amrnb/amrwb)、G711MU、APE、AudioVivid11+ 以及 OPUS。同时支持对应的容器格式解封装,如 mp4、m4a、flac、ogg、aac、mp3、amr、raw 和 ape。
  2. 音频编码支持
    在音频编码方面,系统支持将 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;
}

二、 音频转码与处理流程

在实际业务(如格式转换、音频剪辑)中,通常需要经过“解封装 -> 解码 -> 编码 -> 封装”的标准流程:

  1. 解封装(Demuxer):使用 AVDemuxer 将完整的音频文件(如 .m4a)解封装,获取内部的编码数据流(如 AAC 码流)。
  2. 解码(Decode):将编码数据送入 AVCodec 解码器,还原为 PCM 原始音频数据。
  3. 编码(Encode):将 PCM 数据送入目标格式的编码器(如 MP3 或 FLAC 编码器),压缩为新的编码数据。
  4. 封装(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 进行音频编解码时,需要严格遵循生命周期并处理异步回调:

  1. 创建编解码器:通过 MimeType(如 OH_AVCODEC_MIMETYPE_AUDIO_AAC)或 Codec Name 创建编码器或解码器实例。
  2. 注册异步回调:必须注册 OH_AVCodecCallback,处理 OH_AVCodecOnNeedInputBuffer(请求输入数据)、OH_AVCodecOnNewOutputBuffer(输出解码/编码数据)以及 OH_AVCodecOnError(异常处理)等关键事件。
  3. 配置与启动:通过 OH_AudioCodec_Configure 设置采样率、声道数、码率等参数,随后调用 Prepare 和 Start 启动编解码流程。
  4. 资源释放:处理完成后,必须按顺序调用 StopReset 和 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);
    }
}

四、 性能优化

  1. 容器与编码的匹配:在进行音频封装时,必须确保编码格式与目标容器兼容。例如,系统 Muxer 目前不支持将 OPUS 封装为 OGG 容器,若需实现此功能,开发者需自行实现 OGG 容器的封装逻辑。
  2. 异步回调防阻塞:在 OH_AVCodecCallback 回调函数中,严禁执行耗时操作或再次调用编解码器接口,应通过队列(Queue)和条件变量将数据传递到独立的工作线程中处理。
  3. 参数动态变化处理:对于 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++ 的队列与条件变量来安全地流转数据。

  1. 回调函数注册与数据流转
    开发者必须通过 OH_AudioCodec_RegisterCallback 注册回调集合。当解码器准备好接收数据时,会触发 OH_AVCodecOnNeedInputBuffer 回调,此时应将待解码的码流数据送入输入队列(inBufferQueue_);当解码完成时,触发 OH_AVCodecOnNewOutputBuffer,将解码后的 PCM 数据送入输出队列(outBufferQueue_)。
  2. 多线程安全与防阻塞
    由于回调函数中严禁进行耗时操作,必须使用 std::mutex 和 std::condition_variable 进行线程同步。当主工作线程从队列中取出 Buffer 处理完毕后,务必及时释放归还,否则达到 Buffer 上限后解码器将停止工作。
  3. 动态参数监听
    针对 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 层解决方案。

  1. Flutter 跨平台音频处理
    在 Flutter for OpenHarmony 开发中,可使用 flutter_sound 插件。该插件完整适配了鸿蒙的底层能力,支持 AAC、MP3、FLAC、Opus 等格式的录制与播放。开发者只需在 pubspec.yaml 中引入依赖,并在 module.json5 中声明 ohos.permission.MICROPHONE 权限,即可通过 FlutterSoundPlayer 快速实现音频流的控制。
  2. 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;
        }
    }
}

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐