HarmonyOS音频编解码开发实战
HarmonyOS开发中的音频编解码:@ohos.multimedia.media、CodecBase、AAC/MP3/FLAC 编解码实战
核心要点:音频编解码是音频开发的核心基础设施。本文从 @ohos.multimedia.media 的编解码 API 入手,深入讲解 CodecBase 基础框架、AAC/MP3/FLAC 三种主流格式的编解码实现、硬件编解码的选择策略,以及编解码性能优化的实战技巧。
一、背景与动机
你有没有想过,为什么一首 5 分钟的无损 WAV 歌曲动辄 50MB,而同样一首 AAC 格式的歌只要 5MB,听起来却差不多?
这就是音频编解码的魔力——用巧妙的数学算法,把音频数据压缩到原来的十分之一甚至更小,同时尽量保持听觉上的"无感"。
在鸿蒙开发中,音频编解码无处不在:录音需要编码保存、播放需要解码渲染、语音通话需要实时编解码、音乐制作需要无损处理……如果你不理解编解码的原理和 API 用法,就很难做出高质量的音频应用。
更关键的是,鸿蒙支持硬件编解码和软件编解码两种模式。选错了,轻则性能浪费,重则发热卡顿。怎么选?这就是本文要回答的问题。
二、核心原理
2.1 音频编解码的基本概念
音频编码(Encoding)是将原始 PCM 音频数据压缩为特定格式的过程,解码(Decoding)则是逆过程。
flowchart LR
A[原始音频 PCM] --> B[编码器 Encoder]
B --> C[压缩数据<br/>AAC/MP3/FLAC]
C --> D[存储/传输]
D --> E[解码器 Decoder]
E --> F[还原音频 PCM]
F --> G[音频渲染播放]
classDef primary fill:#4FC3F7,stroke:#0288D1,color:#000
classDef warning fill:#FFB74D,stroke:#F57C00,color:#000
classDef error fill:#EF5350,stroke:#C62828,color:#fff
classDef info fill:#81C784,stroke:#388E3C,color:#000
classDef purple fill:#CE93D8,stroke:#7B1FA2,color:#000
class A,F info
class B,E primary
class C warning
class D purple
class G info
2.2 三种主流格式对比
| 特性 | AAC | MP3 | FLAC |
|---|---|---|---|
| 压缩类型 | 有损 | 有损 | 无损 |
| 典型码率 | 128-256 kbps | 128-320 kbps | 700-1100 kbps |
| 压缩比 | ~10:1 | ~10:1 | ~2:1 |
| 音质 | 同码率优于 MP3 | 经典格式 | 完美还原 |
| 编码延迟 | 低(~20ms) | 中(~50ms) | 低(~10ms) |
| 适用场景 | 流媒体、直播 | 兼容性播放 | 音乐制作、HiFi |
| 鸿蒙支持 | ✅ 硬编硬解 | ✅ 软解为主 | ✅ 软解为主 |
2.3 CodecBase 基础框架
鸿蒙的编解码框架基于 CodecBase 抽象层,提供了统一的编解码接口:
// CodecBase 核心概念
interface CodecBase {
// 编解码器类型
mimeType: string; // 如 'audio/aac', 'audio/mpeg', 'audio/flac'
// 是否为硬件编解码器
isHardware: boolean;
// 编解码能力查询
name: string;
}
2.4 硬编解码 vs 软编解码
flowchart TD
A[编解码选择] --> B{需要实时处理?}
B -->|是| C{设备支持硬编?}
B -->|否| D[软编解码<br/>兼容性好]
C -->|是| E[硬编解码<br/>低延迟低功耗]
C -->|否| F[软编解码<br/>降级方案]
A --> G{需要无损?}
G -->|是| H[FLAC 软编解码]
G -->|否| I{追求压缩率?}
I -->|是| J[AAC 硬编解码]
I -->|否| K[MP3 软编解码]
classDef primary fill:#4FC3F7,stroke:#0288D1,color:#000
classDef warning fill:#FFB74D,stroke:#F57C00,color:#000
classDef error fill:#EF5350,stroke:#C62828,color:#fff
classDef info fill:#81C784,stroke:#388E3C,color:#000
classDef purple fill:#CE93D8,stroke:#7B1FA2,color:#000
class A info
class E,J primary
class D,F,K warning
class H purple
硬件编解码利用设备 DSP/NPU 专用硬件单元,功耗低、延迟小,但格式支持有限。软件编解码使用 CPU 计算,兼容性好但功耗高、延迟大。
三、代码实战
3.1 基础:使用 AVMuxer + AVCodec 进行 AAC 编码
下面的示例展示了如何将 PCM 音频数据编码为 AAC 格式。
// 文件名:AacEncoder.ets
// 功能:PCM → AAC 编码器
import { media } from '@kit.MediaKit';
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct AacEncoderPage {
@State encodeStatus: string = '就绪';
@State encodeProgress: number = 0;
@State outputInfo: string = '';
// AAC 编码配置
private aacConfig: media.AVCodecConfig = {
audioConfig: {
sampleRate: 44100, // 采样率 44.1kHz
channelCount: 2, // 双声道
bitRate: 128000, // 码率 128kbps
sampleFormat: media.AudioSampleFormat.SAMPLE_F32LE, // 32位浮点
},
mimeType: 'audio/aac',
};
aboutToAppear() {
// 查询设备支持的编码能力
this.queryEncoderCapabilities();
}
// 查询编码能力
async queryEncoderCapabilities() {
try {
// 获取所有音频编码器
const codecs = await media.getAudioEncoderCaps();
console.info('[Encoder] 支持的音频编码器:');
for (const codec of codecs) {
console.info(` - ${codec.name} (${codec.mimeType}) 硬件: ${codec.isHardware}`);
}
} catch (error) {
console.error(`[Encoder] 查询编码能力失败: ${JSON.stringify(error)}`);
}
}
// 执行 AAC 编码
async encodeToAac() {
this.encodeStatus = '编码中...';
this.encodeProgress = 0;
let encoder: media.AVCodec | null = null;
let muxer: media.AVMuxer | null = null;
let outputFile: fileIo.File | null = null;
try {
// 1. 创建 AAC 编码器
encoder = await media.createAudioEncoderByName('avcodec_aac_encoder');
console.info('[Encoder] AAC 编码器创建成功');
// 2. 配置编码器
await encoder.configure(this.aacConfig);
console.info('[Encoder] 编码器配置完成');
// 3. 准备输出文件
const context = getContext(this) as common.UIAbilityContext;
const outputPath = context.filesDir + '/output_aac.mp4';
outputFile = fileIo.openSync(outputPath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
// 4. 创建封装器(Muxer)——将编码后的 AAC 数据封装为 MP4 容器
muxer = await media.createAVMuxer(outputFile.fd, media.ContainerFormatType.CFT_MPEG_4A);
console.info('[Encoder] 封装器创建成功');
// 5. 注册编码回调
encoder.on('outputBuffer', (index: number, buffer: media.BufferInfo, flags: media.BufferFlag) => {
// 获取编码后的 AAC 数据
const encodedData = encoder?.getOutputBuffer(index);
if (encodedData && buffer.size > 0) {
// 将编码数据写入封装器
muxer?.writeSampleData(0, encodedData, buffer);
console.info(`[Encoder] 编码输出: ${buffer.size} 字节, presentationTimeUs: ${buffer.presentationTimeUs}`);
}
// 释放输出缓冲区
encoder?.releaseOutputBuffer(index);
});
encoder.on('inputBuffer', (index: number) => {
// 填充 PCM 数据到输入缓冲区
this.fillPcmData(encoder!, index);
});
// 6. 启动编码
await encoder.start();
console.info('[Encoder] 编码启动');
// 模拟编码过程
for (let i = 0; i < 100; i++) {
this.encodeProgress = i;
await this.sleep(20);
}
// 7. 停止编码
await encoder.stop();
await encoder.release();
encoder = null;
// 8. 停止封装器
await muxer?.stop();
await muxer?.release();
muxer = null;
this.encodeStatus = '编码完成';
this.encodeProgress = 100;
this.outputInfo = `输出文件: ${outputPath}`;
} catch (error) {
console.error(`[Encoder] 编码失败: ${JSON.stringify(error)}`);
this.encodeStatus = `编码失败: ${error.message}`;
} finally {
// 清理资源
if (encoder) {
await encoder.release().catch(() => {});
}
if (muxer) {
await muxer.release().catch(() => {});
}
if (outputFile) {
fileIo.closeSync(outputFile);
}
}
}
// 填充 PCM 数据(模拟)
private fillPcmData(encoder: media.AVCodec, index: number) {
const inputBuffer = encoder.getInputBuffer(index);
if (!inputBuffer) return;
// 实际开发中,这里应该从录音缓冲区或文件读取真实的 PCM 数据
// 此处填充静音数据作为示例
const frameSize = 1024 * 2 * 4; // 1024 采样 × 2 声道 × 4 字节(float32)
const silenceData = new ArrayBuffer(frameSize);
inputBuffer.writeSync(silenceData, 0, frameSize, 0);
const bufferInfo: media.BufferInfo = {
presentationTimeUs: Date.now() * 1000,
size: frameSize,
offset: 0,
flags: 0,
};
encoder.queueInputBuffer(index, bufferInfo);
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
build() {
Column() {
Text('AAC 音频编码器')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#E0E0E0')
.margin({ bottom: 20 })
// 编码配置信息
Column() {
Text('编码配置').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#4FC3F7')
Text('格式: AAC | 采样率: 44100Hz | 声道: 立体声 | 码率: 128kbps')
.fontSize(13)
.fontColor('#AAAAAA')
.margin({ top: 8 })
}
.width('100%')
.padding(15)
.borderRadius(12)
.backgroundColor('#16213e')
.margin({ bottom: 15 })
// 编码状态
Row() {
Text('状态:')
.fontSize(14)
.fontColor('#888888')
Text(this.encodeStatus)
.fontSize(14)
.fontColor(this.encodeStatus === '编码完成' ? '#81C784' :
this.encodeStatus.includes('失败') ? '#EF5350' : '#4FC3F7')
.margin({ left: 8 })
}
.margin({ bottom: 15 })
// 进度条
Progress({ value: this.encodeProgress, total: 100, type: ProgressType.Linear })
.width('100%')
.color('#6C63FF')
.margin({ bottom: 15 })
// 输出信息
if (this.outputInfo) {
Text(this.outputInfo)
.fontSize(12)
.fontColor('#81C784')
.margin({ bottom: 15 })
}
// 开始编码按钮
Button('开始编码')
.onClick(() => this.encodeToAac())
.width('100%')
.height(50)
.backgroundColor('#6C63FF')
.borderRadius(12)
.enabled(this.encodeStatus !== '编码中...')
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#0d0d1a')
}
}
3.2 进阶:MP3/FLAC 解码播放
解码是编码的逆过程。下面的示例展示了如何使用 AVCodec 解码 MP3 和 FLAC 文件。
// 文件名:AudioDecoder.ets
// 功能:MP3/FLAC 音频解码器,支持格式自动检测
import { media } from '@kit.MediaKit';
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
// 解码格式枚举
enum DecodeFormat {
AAC = 'audio/aac',
MP3 = 'audio/mpeg',
FLAC = 'audio/flac',
AUTO = 'auto',
}
// 解码器状态
interface DecoderState {
format: string;
sampleRate: number;
channelCount: number;
bitRate: number;
duration: number;
isDecoding: boolean;
decodedFrames: number;
}
@Entry
@Component
struct AudioDecoderPage {
@State decoderState: DecoderState = {
format: '未选择',
sampleRate: 0,
channelCount: 0,
bitRate: 0,
duration: 0,
isDecoding: false,
decodedFrames: 0,
};
@State selectedFormat: string = 'auto';
private decoder: media.AVCodec | null = null;
private audioRenderer: media.AudioRenderer | null = null;
// 根据文件扩展名检测格式
private detectFormat(fileName: string): string {
const ext = fileName.split('.').pop()?.toLowerCase();
switch (ext) {
case 'mp3':
return 'audio/mpeg';
case 'flac':
return 'audio/flac';
case 'aac':
case 'm4a':
return 'audio/aac';
case 'wav':
return 'audio/wav';
default:
return 'audio/mpeg'; // 默认按 MP3 处理
}
}
// 创建解码器
async createDecoder(mimeType: string): Promise<media.AVCodec> {
// 查询支持的解码器
const decoderCaps = await media.getAudioDecoderCaps();
console.info('[Decoder] 支持的音频解码器:');
for (const cap of decoderCaps) {
console.info(` - ${cap.name} (${cap.mimeType}) 硬件: ${cap.isHardware}`);
}
// 优先选择硬件解码器
let decoderName = '';
for (const cap of decoderCaps) {
if (cap.mimeType === mimeType && cap.isHardware) {
decoderName = cap.name;
console.info(`[Decoder] 选择硬件解码器: ${decoderName}`);
break;
}
}
// 如果没有硬件解码器,选择软件解码器
if (!decoderName) {
for (const cap of decoderCaps) {
if (cap.mimeType === mimeType) {
decoderName = cap.name;
console.info(`[Decoder] 选择软件解码器: ${decoderName}`);
break;
}
}
}
if (!decoderName) {
throw new Error(`不支持的格式: ${mimeType}`);
}
return await media.createAudioDecoderByName(decoderName);
}
// 执行解码
async startDecode() {
this.decoderState.isDecoding = true;
this.decoderState.decodedFrames = 0;
try {
// 1. 确定解码格式
const mimeType = this.selectedFormat === 'auto'
? 'audio/mpeg' // 实际应从文件检测
: this.selectedFormat;
// 2. 创建解码器
this.decoder = await this.createDecoder(mimeType);
this.decoderState.format = mimeType;
// 3. 配置解码器
const decodeConfig: media.AVCodecConfig = {
audioConfig: {
sampleRate: 44100,
channelCount: 2,
bitRate: 128000,
sampleFormat: media.AudioSampleFormat.SAMPLE_S16LE,
},
mimeType: mimeType,
};
await this.decoder.configure(decodeConfig);
// 4. 注册解码回调
this.decoder.on('outputBuffer', (index: number, buffer: media.BufferInfo, flags: media.BufferFlag) => {
this.decoderState.decodedFrames++;
// 获取解码后的 PCM 数据
const pcmData = this.decoder?.getOutputBuffer(index);
if (pcmData && buffer.size > 0) {
// 将 PCM 数据送入音频渲染器播放
this.renderPcmData(pcmData, buffer.size);
// 更新解码状态信息
this.decoderState.sampleRate = 44100;
this.decoderState.channelCount = 2;
}
// 释放输出缓冲区
this.decoder?.releaseOutputBuffer(index);
});
this.decoder.on('inputBuffer', (index: number) => {
// 从文件读取编码数据并送入解码器
this.feedEncodedData(this.decoder!, index);
});
// 5. 启动解码
await this.decoder.start();
console.info('[Decoder] 解码启动');
} catch (error) {
console.error(`[Decoder] 解码失败: ${JSON.stringify(error)}`);
this.decoderState.isDecoding = false;
}
}
// 喂入编码数据(模拟)
private feedEncodedData(decoder: media.AVCodec, index: number) {
const inputBuffer = decoder.getInputBuffer(index);
if (!inputBuffer) return;
// 实际开发中,从文件或网络读取编码数据
// 此处模拟一小段 MP3 帧数据
const frameSize = 4096;
const mockData = new ArrayBuffer(frameSize);
inputBuffer.writeSync(mockData, 0, frameSize, 0);
const bufferInfo: media.BufferInfo = {
presentationTimeUs: Date.now() * 1000,
size: frameSize,
offset: 0,
flags: 0,
};
decoder.queueInputBuffer(index, bufferInfo);
}
// 渲染 PCM 数据
private async renderPcmData(data: ArrayBuffer, size: number) {
// 实际开发中,将解码后的 PCM 数据送入 AudioRenderer 播放
// 此处仅打印日志
if (this.decoderState.decodedFrames % 100 === 0) {
console.info(`[Decoder] 已解码 ${this.decoderState.decodedFrames} 帧, 当前帧大小: ${size}`);
}
}
// 停止解码
async stopDecode() {
if (this.decoder) {
await this.decoder.stop();
await this.decoder.release();
this.decoder = null;
}
this.decoderState.isDecoding = false;
}
build() {
Scroll() {
Column() {
Text('音频解码器')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#E0E0E0')
.margin({ bottom: 20 })
// 格式选择
Text('选择解码格式').fontSize(14).fontColor('#888888').margin({ bottom: 8 })
Row() {
ForEach(['auto', 'audio/mpeg', 'audio/flac', 'audio/aac'], (format: string) => {
Button(format === 'auto' ? '自动检测' : format.split('/')[1].toUpperCase())
.onClick(() => { this.selectedFormat = format; })
.fontSize(12)
.height(36)
.backgroundColor(this.selectedFormat === format ? '#6C63FF' : '#16213e')
.fontColor(this.selectedFormat === format ? '#FFFFFF' : '#888888')
.margin({ right: 8 })
})
}
.margin({ bottom: 20 })
// 解码状态信息
Column() {
Text('解码信息').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#4FC3F7')
Grid() {
GridItem() {
this.InfoItem('格式', this.decoderState.format)
}
GridItem() {
this.InfoItem('采样率', `${this.decoderState.sampleRate}Hz`)
}
GridItem() {
this.InfoItem('声道数', `${this.decoderState.channelCount}`)
}
GridItem() {
this.InfoItem('已解码帧', `${this.decoderState.decodedFrames}`)
}
}
.columnsTemplate('1fr 1fr')
.rowsTemplate('1fr 1fr')
.width('100%')
.height(120)
}
.width('100%')
.padding(15)
.borderRadius(12)
.backgroundColor('#16213e')
.margin({ bottom: 20 })
// 控制按钮
Row() {
Button(this.decoderState.isDecoding ? '停止解码' : '开始解码')
.onClick(() => {
if (this.decoderState.isDecoding) {
this.stopDecode();
} else {
this.startDecode();
}
})
.width(160)
.height(50)
.backgroundColor(this.decoderState.isDecoding ? '#EF5350' : '#6C63FF')
.borderRadius(12)
}
.width('100%')
.justifyContent(FlexAlign.Center)
}
.width('100%')
.padding(20)
}
.width('100%')
.height('100%')
.backgroundColor('#0d0d1a')
}
@Builder
InfoItem(label: string, value: string) {
Column() {
Text(value).fontSize(16).fontWeight(FontWeight.Bold).fontColor('#E0E0E0')
Text(label).fontSize(11).fontColor('#888888').margin({ top: 4 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
aboutToDisappear() {
this.stopDecode();
}
}
3.3 高级:编解码性能优化器
在实时音频场景(如语音通话、直播)中,编解码性能至关重要。下面的示例展示了如何构建一个自适应的编解码性能优化器。
// 文件名:CodecOptimizer.ets
// 功能:自适应编解码性能优化器
import { media } from '@kit.MediaKit';
// 性能指标
interface CodecPerformance {
encodeLatency: number; // 编码延迟(ms)
decodeLatency: number; // 解码延迟(ms)
cpuUsage: number; // CPU 占用率(%)
memoryUsage: number; // 内存占用(MB)
frameDropRate: number; // 丢帧率(%)
}
// 优化策略
interface OptimizationStrategy {
useHardware: boolean; // 是否使用硬件编解码
targetBitrate: number; // 目标码率
targetSampleRate: number; // 目标采样率
targetChannelCount: number; // 目标声道数
bufferSize: number; // 缓冲区大小
}
// 性能等级
type PerformanceLevel = 'ultra' | 'high' | 'medium' | 'low';
class AdaptiveCodecOptimizer {
private currentLevel: PerformanceLevel = 'high';
private performanceHistory: CodecPerformance[] = [];
private maxHistoryLength = 30; // 保留最近 30 次性能记录
// 性能等级对应的编解码配置
private levelConfigs: Record<PerformanceLevel, OptimizationStrategy> = {
ultra: {
useHardware: true,
targetBitrate: 256000,
targetSampleRate: 48000,
targetChannelCount: 2,
bufferSize: 8192,
},
high: {
useHardware: true,
targetBitrate: 128000,
targetSampleRate: 44100,
targetChannelCount: 2,
bufferSize: 4096,
},
medium: {
useHardware: true,
targetBitrate: 64000,
targetSampleRate: 44100,
targetChannelCount: 1, // 降为单声道
bufferSize: 2048,
},
low: {
useHardware: false, // 低端设备可能不支持硬编
targetBitrate: 32000,
targetSampleRate: 16000,
targetChannelCount: 1,
bufferSize: 1024,
},
};
// 记录性能数据
recordPerformance(perf: CodecPerformance) {
this.performanceHistory.push(perf);
if (this.performanceHistory.length > this.maxHistoryLength) {
this.performanceHistory.shift();
}
// 根据性能数据自适应调整等级
this.adaptLevel();
}
// 自适应调整性能等级
private adaptLevel() {
if (this.performanceHistory.length < 5) return;
// 取最近 5 次的平均值
const recent = this.performanceHistory.slice(-5);
const avgCpu = recent.reduce((sum, p) => sum + p.cpuUsage, 0) / recent.length;
const avgLatency = recent.reduce((sum, p) => sum + p.encodeLatency + p.decodeLatency, 0) / recent.length;
const avgDropRate = recent.reduce((sum, p) => sum + p.frameDropRate, 0) / recent.length;
// 决策逻辑
if (avgCpu > 80 || avgLatency > 100 || avgDropRate > 5) {
// 性能不足,降低等级
this.downgrade();
} else if (avgCpu < 40 && avgLatency < 30 && avgDropRate < 1) {
// 性能充裕,可以提升等级
this.upgrade();
}
}
// 降级
private downgrade() {
const levels: PerformanceLevel[] = ['ultra', 'high', 'medium', 'low'];
const currentIndex = levels.indexOf(this.currentLevel);
if (currentIndex < levels.length - 1) {
this.currentLevel = levels[currentIndex + 1];
console.warn(`[Optimizer] 性能不足,降级到 ${this.currentLevel}`);
}
}
// 升级
private upgrade() {
const levels: PerformanceLevel[] = ['ultra', 'high', 'medium', 'low'];
const currentIndex = levels.indexOf(this.currentLevel);
if (currentIndex > 0) {
this.currentLevel = levels[currentIndex - 1];
console.info(`[Optimizer] 性能充裕,升级到 ${this.currentLevel}`);
}
}
// 获取当前优化策略
getCurrentStrategy(): OptimizationStrategy {
return this.levelConfigs[this.currentLevel];
}
// 获取当前性能等级
getCurrentLevel(): PerformanceLevel {
return this.currentLevel;
}
// 获取性能报告
getPerformanceReport(): string {
const strategy = this.getCurrentStrategy();
return [
`当前等级: ${this.currentLevel}`,
`硬件编解码: ${strategy.useHardware ? '是' : '否'}`,
`目标码率: ${strategy.targetBitrate}bps`,
`采样率: ${strategy.targetSampleRate}Hz`,
`声道数: ${strategy.targetChannelCount}`,
`缓冲区: ${strategy.bufferSize}`,
].join('\n');
}
}
// ==================== UI 组件 ====================
@Entry
@Component
struct CodecOptimizerPage {
private optimizer: AdaptiveCodecOptimizer = new AdaptiveCodecOptimizer();
@State currentLevel: string = 'high';
@State strategyInfo: string = '';
@State isRunning: boolean = false;
@State performanceLog: string[] = [];
aboutToAppear() {
this.updateDisplay();
}
// 更新显示信息
private updateDisplay() {
this.currentLevel = this.optimizer.getCurrentLevel();
this.strategyInfo = this.optimizer.getPerformanceReport();
}
// 启动性能监控
startMonitoring() {
this.isRunning = true;
this.simulatePerformance();
}
// 模拟性能数据采集
private simulatePerformance() {
if (!this.isRunning) return;
// 模拟不同负载下的性能指标
const cpuBase = this.currentLevel === 'low' ? 30 : this.currentLevel === 'medium' ? 50 : 70;
const perf: CodecPerformance = {
encodeLatency: Math.random() * 20 + 5,
decodeLatency: Math.random() * 15 + 3,
cpuUsage: cpuBase + Math.random() * 30,
memoryUsage: 20 + Math.random() * 30,
frameDropRate: Math.random() * 3,
};
this.optimizer.recordPerformance(perf);
// 添加日志
const logEntry = `[${new Date().toLocaleTimeString()}] CPU: ${perf.cpuUsage.toFixed(1)}% | ` +
`编码延迟: ${perf.encodeLatency.toFixed(1)}ms | 等级: ${this.optimizer.getCurrentLevel()}`;
this.performanceLog = [logEntry, ...this.performanceLog].slice(0, 20);
this.updateDisplay();
// 持续监控
setTimeout(() => this.simulatePerformance(), 1000);
}
// 停止监控
stopMonitoring() {
this.isRunning = false;
}
build() {
Scroll() {
Column() {
Text('编解码性能优化器')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#E0E0E0')
.margin({ bottom: 20 })
// 当前性能等级指示器
Row() {
ForEach(['ultra', 'high', 'medium', 'low'] as PerformanceLevel[], (level: PerformanceLevel) => {
Column() {
Circle({ width: 16, height: 16 })
.fill(this.currentLevel === level ? '#6C63FF' : '#333333')
Text(level.toUpperCase())
.fontSize(10)
.fontColor(this.currentLevel === level ? '#6C63FF' : '#666666')
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
})
}
.width('100%')
.padding(15)
.borderRadius(12)
.backgroundColor('#16213e')
.margin({ bottom: 15 })
// 当前策略信息
Column() {
Text('当前优化策略').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#4FC3F7')
Text(this.strategyInfo)
.fontSize(13)
.fontColor('#AAAAAA')
.margin({ top: 10 })
.lineHeight(22)
}
.width('100%')
.padding(15)
.borderRadius(12)
.backgroundColor('#16213e')
.alignItems(HorizontalAlign.Start)
.margin({ bottom: 15 })
// 性能日志
Column() {
Text('性能日志').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#4FC3F7')
Column() {
ForEach(this.performanceLog, (log: string, index: number) => {
Text(log)
.fontSize(11)
.fontColor(index === 0 ? '#E0E0E0' : '#666666')
.width('100%')
.padding({ top: 4, bottom: 4 })
})
}
.width('100%')
.maxHeight(200)
}
.width('100%')
.padding(15)
.borderRadius(12)
.backgroundColor('#16213e')
.alignItems(HorizontalAlign.Start)
.margin({ bottom: 15 })
// 控制按钮
Button(this.isRunning ? '停止监控' : '开始监控')
.onClick(() => {
if (this.isRunning) {
this.stopMonitoring();
} else {
this.startMonitoring();
}
})
.width('100%')
.height(50)
.backgroundColor(this.isRunning ? '#EF5350' : '#6C63FF')
.borderRadius(12)
}
.width('100%')
.padding(20)
}
.width('100%')
.height('100%')
.backgroundColor('#0d0d1a')
}
}
四、踩坑与注意事项
4.1 编解码常见陷阱
| 陷阱 | 现象 | 解决方案 |
|---|---|---|
| 缓冲区大小不匹配 | 解码输出数据截断或噪声 | 根据采样率和声道数精确计算缓冲区大小 |
| 采样格式不统一 | 编码器输出和解码器输入格式不一致 | 统一使用 SAMPLE_S16LE 或 SAMPLE_F32LE |
| 硬件解码器不可用 | 在部分低端设备上创建解码器失败 | 先查询 getAudioDecoderCaps(),做好软解降级 |
| Muxer 封装格式错误 | AAC 数据封装为 MP4 但无法播放 | 确保 ContainerFormatType 与 MIME 类型匹配 |
| 内存泄漏 | 长时间编解码后内存持续增长 | 及时调用 releaseOutputBuffer() 和 releaseInputBuffer() |
4.2 缓冲区大小计算
// PCM 缓冲区大小计算公式
// bufferSize = sampleRate × channelCount × bytesPerSample × durationSeconds
function calculatePcmBufferSize(
sampleRate: number,
channelCount: number,
sampleFormat: media.AudioSampleFormat,
durationMs: number
): number {
// 每个采样的字节数
const bytesPerSample = sampleFormat === media.AudioSampleFormat.SAMPLE_S16LE ? 2 :
sampleFormat === media.AudioSampleFormat.SAMPLE_S24LE ? 3 :
sampleFormat === media.AudioSampleFormat.SAMPLE_S32LE ? 4 :
sampleFormat === media.AudioSampleFormat.SAMPLE_F32LE ? 4 : 2;
return Math.ceil(sampleRate * channelCount * bytesPerSample * durationMs / 1000);
}
// 示例:44100Hz, 立体声, 16bit, 20ms 一帧
// bufferSize = 44100 × 2 × 2 × 0.02 = 3528 字节
4.3 硬件编解码选择指南
// 硬件编解码选择决策树
function shouldUseHardwareCodec(
mimeType: string,
isRealTime: boolean,
deviceLevel: 'high' | 'medium' | 'low'
): boolean {
// 1. 低端设备不建议使用硬编(可能不支持)
if (deviceLevel === 'low') return false;
// 2. FLAC 无损格式通常没有硬件编解码器
if (mimeType === 'audio/flac') return false;
// 3. 实时场景优先使用硬件编解码
if (isRealTime) return true;
// 4. AAC 格式硬件编解码支持最好
if (mimeType === 'audio/aac') return true;
// 5. MP3 硬件解码支持有限,建议软解
if (mimeType === 'audio/mpeg') return false;
return false;
}
五、HarmonyOS 6 适配
5.1 版本差异
| 特性 | HarmonyOS 5 (API 12) | HarmonyOS 6 (API 14) |
|---|---|---|
| 硬件编解码 | 仅 AAC 硬编硬解 | 新增:MP3 硬件解码、Opus 编解码支持 |
| CodecBase | 基础接口 | 增强:新增 getCodecProfile() 支持查询编码器配置 |
| 性能监控 | 无系统接口 | 新增:CodecPerformanceMonitor 系统级性能监控 |
| 低延迟编解码 | 不支持 | 新增:lowLatency 模式,编解码延迟降低 40% |
| 多实例并发 | 最多 2 个 | 新增:支持最多 8 个编解码实例并发 |
5.2 迁移指南
// HarmonyOS 5 写法——手动管理编解码实例
const encoder = await media.createAudioEncoderByName('avcodec_aac_encoder');
// ... 编码逻辑 ...
// HarmonyOS 6 写法——使用新的性能监控和低延迟模式
// const encoder = await media.createAudioEncoderByName('avcodec_aac_encoder');
// const perfMonitor = media.createCodecPerformanceMonitor();
// perfMonitor.on('performanceUpdate', (perf: media.CodecPerformanceInfo) => {
// console.info(`编码延迟: ${perf.encodeLatency}ms, CPU: ${perf.cpuUsage}%`);
// });
//
// // 启用低延迟模式
// const config: media.AVCodecConfig = {
// audioConfig: {
// sampleRate: 48000,
// channelCount: 1,
// bitRate: 64000,
// sampleFormat: media.AudioSampleFormat.SAMPLE_S16LE,
// },
// mimeType: 'audio/aac',
// lowLatency: true, // ★ 新增:低延迟模式
// };
5.3 注意事项
- HarmonyOS 6 的
lowLatency模式会降低编码效率(码率相同时音质略降),适合语音通话场景 CodecPerformanceMonitor的回调频率为每秒 1 次,不会影响编解码性能- 多实例并发时注意总内存占用,每个实例约占用 5-10MB 内存
六、总结
mindmap
root((音频编解码))
核心概念
编码 PCM→压缩格式
解码 压缩格式→PCM
CodecBase 抽象层
主流格式
AAC 有损低延迟
MP3 有损兼容性好
FLAC 无损高质量
硬件vs软件
硬件 低功耗低延迟
软件 兼容性好
按场景选择
性能优化
自适应码率
缓冲区优化
降级策略
性能监控
踩坑点
缓冲区大小计算
采样格式统一
内存泄漏防护
HarmonyOS 6
MP3硬解支持
Opus编解码
低延迟模式
多实例并发
classDef primary fill:#4FC3F7,stroke:#0288D1,color:#000
classDef warning fill:#FFB74D,stroke:#F57C00,color:#000
classDef error fill:#EF5350,stroke:#C62828,color:#fff
classDef info fill:#81C784,stroke:#388E3C,color:#000
classDef purple fill:#CE93D8,stroke:#7B1FA2,color:#000
关键知识点回顾:
- AAC 是鸿蒙上最推荐的编解码格式——硬件支持好、延迟低、压缩率高
- 硬件编解码优先,软件编解码兜底——务必做好降级方案
- 缓冲区大小必须精确计算——公式:
sampleRate × channelCount × bytesPerSample × duration - 长时间编解码必须监控性能——CPU 占用、延迟、丢帧率是三大核心指标
- HarmonyOS 6 新增低延迟模式和多实例并发——实时音频场景的福音
编解码是音频开发的"地基",地基打好了,上层的播放、录制、通话才能稳如磐石。
- 点赞
- 收藏
- 关注作者
评论(0)