HarmonyOS音频流管理开发实战
HarmonyOS开发中的音频流管理:AudioRenderer、AudioCapturer、流类型与低延迟流实战
核心要点:音频流管理是鸿蒙音频开发的中枢系统。本文从 AudioRenderer(播放流)和 AudioCapturer(录制流)两大核心类入手,深入讲解流类型与用途的映射关系、低延迟流的实现原理、流状态机的完整生命周期管理,以及生产环境下的最佳实践。
一、背景与动机
想象一下这个场景:你正在用手机听音乐,突然来了个电话,音乐自动暂停,通话结束后音乐又自动恢复。这个看似简单的功能,背后涉及的就是音频流管理。
音频流管理要解决的核心问题是:当多个应用同时想要使用音频硬件时,谁优先?谁让步?谁被静音?
在鸿蒙系统中,每个音频流都有自己的"身份"——流类型(StreamUsage)和内容类型(ContentType)。系统根据这些信息来决定音频焦点(Audio Focus)的分配策略。如果你不理解流类型,你的 App 可能会在不该播放的时候播放,在该暂停的时候不暂停,用户体验一塌糊涂。
更关键的是,对于游戏、乐器、实时通信等场景,低延迟是刚需。普通音频流的延迟可能在 100ms 以上,而低延迟流可以做到 20ms 以内。这 80ms 的差距,对于打击乐手来说就是"能用"和"不能用"的天壤之别。
二、核心原理
2.1 音频流的两面:Renderer 与 Capturer
鸿蒙的音频流分为两个方向:
- AudioRenderer:输出流,把 PCM 数据"渲染"到音频硬件(扬声器/耳机)
- AudioCapturer:输入流,从音频硬件(麦克风)“采集” PCM 数据

2.2 流类型与用途
鸿蒙定义了丰富的流类型,每种类型对应不同的音频焦点策略:
| StreamUsage | ContentType | 典型场景 | 焦点策略 |
|---|---|---|---|
| STREAM_USAGE_MUSIC | CONTENT_TYPE_MUSIC | 音乐播放 | 可被通话打断,打断后恢复 |
| STREAM_USAGE_VOICE_COMMUNICATION | CONTENT_TYPE_SPEECH | 语音通话 | 最高优先级,打断其他所有流 |
| STREAM_USAGE_GAME | CONTENT_TYPE_MUSIC | 游戏音效 | 可被通话打断 |
| STREAM_USAGE_NOTIFICATION | CONTENT_TYPE_SONIFICATION | 通知铃声 | 短暂播放,不长期占用焦点 |
| STREAM_USAGE_ALARM | CONTENT_TYPE_SONIFICATION | 闹钟 | 高优先级,仅被通话打断 |
| STREAM_USAGE_RINGTONE | CONTENT_TYPE_RINGTONE | 来电铃声 | 高优先级 |
| STREAM_USAGE_VOICE_ASSISTANT | CONTENT_TYPE_SPEECH | 语音助手 | 中等优先级 |
2.3 流状态机
AudioRenderer 和 AudioCapturer 都遵循严格的状态机模型:

重要规则:
- 状态转换必须按顺序进行,跳步会导致异常
release()是终止状态,调用后流不可复用stop()后可以重新prepare()→start()
2.4 低延迟流原理
低延迟流的核心是减少缓冲区层级。普通音频流的数据路径:
App → 系统混音器 → 音频策略服务 → HAL → DSP → 扬声器
低延迟流的数据路径:
App → HAL → DSP → 扬声器
跳过了系统混音器和策略服务两个环节,延迟可以降低 60-80ms。但代价是:低延迟流不参与系统混音,同一时间只能有一个低延迟流活跃。
三、代码实战
3.1 基础:AudioRenderer 播放 PCM 音频
// 文件名:BasicAudioRenderer.ets
// 功能:使用 AudioRenderer 播放 PCM 音频数据
import { audio } from '@kit.AudioKit';
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct BasicAudioRendererPage {
@State isPlaying: boolean = false;
@State playProgress: number = 0;
@State audioInfo: string = '未加载音频';
@State volumeLevel: number = 50;
private audioRenderer: audio.AudioRenderer | null = null;
private renderBuffer: ArrayBuffer | null = null;
aboutToAppear() {
this.initRenderer();
}
// 初始化音频渲染器
async initRenderer() {
try {
// 1. 创建渲染器参数——定义流的"身份"
const rendererInfo: audio.AudioRendererInfo = {
usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 音乐流
rendererFlags: 0, // 默认标志
};
// 2. 创建渲染器选项——定义音频参数
const rendererOptions: audio.AudioRendererOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, // 44.1kHz
channels: audio.AudioChannel.CHANNEL_2, // 立体声
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 16bit
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW, // PCM 原始数据
},
rendererInfo: rendererInfo,
};
// 3. 创建渲染器实例
this.audioRenderer = await audio.createAudioRenderer(rendererOptions);
console.info('[Renderer] 渲染器创建成功');
// 4. 注册状态变化回调
this.audioRenderer.on('stateChange', (state: audio.AudioState) => {
console.info(`[Renderer] 状态变化: ${state}`);
switch (state) {
case audio.AudioState.STATE_RUNNING:
this.isPlaying = true;
break;
case audio.AudioState.STATE_PAUSED:
case audio.AudioState.STATE_STOPPED:
this.isPlaying = false;
break;
}
});
// 5. 注册音频中断回调——处理焦点抢占
this.audioRenderer.on('audioInterrupt', (interruptEvent: audio.InterruptEvent) => {
console.info(`[Renderer] 音频中断事件: ${JSON.stringify(interruptEvent)}`);
this.handleAudioInterrupt(interruptEvent);
});
// 更新音频信息
this.audioInfo = '44100Hz | 立体声 | 16bit PCM';
} catch (error) {
console.error(`[Renderer] 初始化失败: ${JSON.stringify(error)}`);
}
}
// 处理音频中断
private handleAudioInterrupt(interruptEvent: audio.InterruptEvent) {
if (!this.audioRenderer) return;
// 判断中断类型
if (interruptEvent.eventType === audio.InterruptType.INTERRUPT_TYPE_BEGIN) {
// 中断开始——有更高优先级的流抢占了焦点
switch (interruptEvent.hintType) {
case audio.InterruptHint.INTERRUPT_HINT_PAUSE:
// 系统建议暂停
this.audioRenderer.pause();
console.info('[Renderer] 被中断,已暂停');
break;
case audio.InterruptHint.INTERRUPT_HINT_STOP:
// 系统建议停止
this.audioRenderer.stop();
console.info('[Renderer] 被中断,已停止');
break;
case audio.InterruptHint.INTERRUPT_HINT_DUCK:
// 系统建议降低音量("压低"效果)
this.audioRenderer.setVolume(0.2);
console.info('[Renderer] 被压低,音量降低');
break;
}
} else if (interruptEvent.eventType === audio.InterruptType.INTERRUPT_TYPE_END) {
// 中断结束——焦点归还
switch (interruptEvent.hintType) {
case audio.InterruptHint.INTERRUPT_HINT_RESUME:
// 系统建议恢复播放
this.audioRenderer.start();
console.info('[Renderer] 焦点归还,恢复播放');
break;
case audio.InterruptHint.INTERRUPT_HINT_UNDUCK:
// 系统建议恢复音量
this.audioRenderer.setVolume(this.volumeLevel / 100);
console.info('[Renderer] 压低结束,恢复音量');
break;
}
}
}
// 开始播放
async startPlay() {
if (!this.audioRenderer) return;
try {
// 启动渲染器
await this.audioRenderer.start();
console.info('[Renderer] 开始播放');
// 模拟写入 PCM 数据
this.writePcmData();
} catch (error) {
console.error(`[Renderer] 播放失败: ${JSON.stringify(error)}`);
}
}
// 写入 PCM 数据
private async writePcmData() {
if (!this.audioRenderer) return;
const bufferSize = await this.audioRenderer.getBufferSize();
console.info(`[Renderer] 缓冲区大小: ${bufferSize}`);
// 生成模拟 PCM 数据(440Hz 正弦波,即标准 A 音)
const sampleRate = 44100;
const duration = 2; // 2 秒
const totalSamples = sampleRate * duration;
const pcmData = new Int16Array(totalSamples * 2); // 立体声
for (let i = 0; i < totalSamples; i++) {
// 440Hz 正弦波
const sample = Math.sin(2 * Math.PI * 440 * i / sampleRate) * 0.3 * 32767;
const intSample = Math.floor(sample);
pcmData[i * 2] = intSample; // 左声道
pcmData[i * 2 + 1] = intSample; // 右声道
}
// 分块写入
const chunkSize = Math.floor(bufferSize / 2); // Int16 每个 2 字节
let offset = 0;
while (offset < pcmData.length && this.isPlaying) {
const remaining = pcmData.length - offset;
const writeSize = Math.min(chunkSize, remaining);
const chunk = pcmData.slice(offset, offset + writeSize);
await this.audioRenderer.write(chunk.buffer);
offset += writeSize;
// 更新进度
this.playProgress = Math.floor((offset / pcmData.length) * 100);
}
}
// 暂停播放
async pausePlay() {
await this.audioRenderer?.pause();
}
// 停止播放
async stopPlay() {
await this.audioRenderer?.stop();
this.playProgress = 0;
}
// 设置音量
async setVolume(value: number) {
this.volumeLevel = value;
await this.audioRenderer?.setVolume(value / 100);
}
build() {
Column() {
Text('AudioRenderer 音频播放')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#E0E0E0')
.margin({ bottom: 20 })
// 音频信息
Row() {
Text('🎵').fontSize(20)
Text(this.audioInfo)
.fontSize(14)
.fontColor('#4FC3F7')
.margin({ left: 10 })
}
.width('100%')
.padding(15)
.borderRadius(12)
.backgroundColor('#16213e')
.margin({ bottom: 15 })
// 播放进度
Progress({ value: this.playProgress, total: 100, type: ProgressType.Linear })
.width('100%')
.color('#6C63FF')
.margin({ bottom: 15 })
// 音量控制
Row() {
Text('🔊').fontSize(18)
Slider({
value: this.volumeLevel,
min: 0,
max: 100,
step: 1,
})
.onChange((value: number) => this.setVolume(value))
.layoutWeight(1)
.margin({ left: 10 })
Text(`${this.volumeLevel}%`)
.fontSize(14)
.fontColor('#AAAAAA')
.width(50)
}
.margin({ bottom: 20 })
// 播放控制按钮
Row() {
Button('播放')
.onClick(() => this.startPlay())
.width(100)
.backgroundColor('#6C63FF')
.enabled(!this.isPlaying)
Button('暂停')
.onClick(() => this.pausePlay())
.width(100)
.backgroundColor('#FFB74D')
.enabled(this.isPlaying)
.margin({ left: 10 })
Button('停止')
.onClick(() => this.stopPlay())
.width(100)
.backgroundColor('#EF5350')
.margin({ left: 10 })
}
.width('100%')
.justifyContent(FlexAlign.Center)
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#0d0d1a')
}
aboutToDisappear() {
// 释放渲染器
this.audioRenderer?.release();
this.audioRenderer = null;
}
}
3.2 进阶:AudioCapturer 录制音频
// 文件名:AudioCapturerDemo.ets
// 功能:使用 AudioCapturer 录制麦克风音频
import { audio } from '@kit.AudioKit';
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct AudioCapturerPage {
@State isRecording: boolean = false;
@State recordDuration: number = 0; // 录制时长(秒)
@State audioLevel: number = 0; // 音频电平(0-100)
@State recordStatus: string = '就绪';
private audioCapturer: audio.AudioCapturer | null = null;
private recordTimer: number = -1;
private recordedChunks: ArrayBuffer[] = [];
aboutToAppear() {
this.initCapturer();
}
// 初始化音频采集器
async initCapturer() {
try {
// 1. 创建采集器参数
const capturerInfo: audio.AudioCapturerInfo = {
source: audio.SourceType.SOURCE_TYPE_MIC, // 麦克风源
capturerFlags: 0,
};
// 2. 创建采集器选项
const capturerOptions: audio.AudioCapturerOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
channels: audio.AudioChannel.CHANNEL_1, // 单声道录音
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW,
},
capturerInfo: capturerInfo,
};
// 3. 创建采集器实例
this.audioCapturer = await audio.createAudioCapturer(capturerOptions);
console.info('[Capturer] 采集器创建成功');
// 4. 注册状态变化回调
this.audioCapturer.on('stateChange', (state: audio.AudioState) => {
console.info(`[Capturer] 状态变化: ${state}`);
switch (state) {
case audio.AudioState.STATE_RUNNING:
this.isRecording = true;
this.recordStatus = '录制中';
break;
case audio.AudioState.STATE_STOPPED:
this.isRecording = false;
this.recordStatus = '已停止';
break;
}
});
// 5. 注册音频中断回调
this.audioCapturer.on('audioInterrupt', (interruptEvent: audio.InterruptEvent) => {
console.info(`[Capturer] 音频中断: ${JSON.stringify(interruptEvent)}`);
// 录制中断处理(如电话呼入时停止录音)
if (interruptEvent.eventType === audio.InterruptType.INTERRUPT_TYPE_BEGIN) {
this.stopRecording();
}
});
// 6. 注册读数据回调——持续获取麦克风数据
this.audioCapturer.on('readData', (buffer: ArrayBuffer) => {
// 计算音频电平(RMS 值)
this.calculateAudioLevel(buffer);
// 保存录音数据
this.recordedChunks.push(buffer.slice(0));
});
} catch (error) {
console.error(`[Capturer] 初始化失败: ${JSON.stringify(error)}`);
}
}
// 计算音频电平
private calculateAudioLevel(buffer: ArrayBuffer) {
const data = new Int16Array(buffer);
let sum = 0;
for (let i = 0; i < data.length; i++) {
sum += data[i] * data[i];
}
const rms = Math.sqrt(sum / data.length);
// 归一化到 0-100
this.audioLevel = Math.min(100, Math.floor((rms / 32767) * 200));
}
// 开始录制
async startRecording() {
if (!this.audioCapturer) return;
try {
// 清空之前的录音数据
this.recordedChunks = [];
this.recordDuration = 0;
// 启动采集器
await this.audioCapturer.start();
console.info('[Capturer] 开始录制');
// 启动计时器
this.recordTimer = setInterval(() => {
this.recordDuration++;
}, 1000);
} catch (error) {
console.error(`[Capturer] 录制失败: ${JSON.stringify(error)}`);
}
}
// 停止录制
async stopRecording() {
if (!this.audioCapturer) return;
try {
await this.audioCapturer.stop();
if (this.recordTimer !== -1) {
clearInterval(this.recordTimer);
this.recordTimer = -1;
}
// 保存录音文件
await this.saveRecording();
} catch (error) {
console.error(`[Capturer] 停止失败: ${JSON.stringify(error)}`);
}
}
// 保存录音到文件
private async saveRecording() {
if (this.recordedChunks.length === 0) return;
try {
const context = getContext(this) as common.UIAbilityContext;
const outputPath = context.filesDir + `/recording_${Date.now()}.pcm`;
const file = fileIo.openSync(outputPath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
// 合并所有录音块
let totalSize = 0;
for (const chunk of this.recordedChunks) {
totalSize += chunk.byteLength;
}
const merged = new ArrayBuffer(totalSize);
const mergedView = new Uint8Array(merged);
let offset = 0;
for (const chunk of this.recordedChunks) {
mergedView.set(new Uint8Array(chunk), offset);
offset += chunk.byteLength;
}
fileIo.writeSync(file.fd, merged);
fileIo.closeSync(file);
console.info(`[Capturer] 录音已保存: ${outputPath}, 大小: ${totalSize} 字节`);
this.recordStatus = `已保存 (${(totalSize / 1024).toFixed(1)}KB)`;
} catch (error) {
console.error(`[Capturer] 保存失败: ${JSON.stringify(error)}`);
}
}
build() {
Column() {
Text('AudioCapturer 音频录制')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#E0E0E0')
.margin({ bottom: 20 })
// 录制状态
Row() {
Circle({ width: 16, height: 16 })
.fill(this.isRecording ? '#EF5350' : '#555555')
.animation({ duration: 300 })
Text(this.recordStatus)
.fontSize(16)
.fontColor(this.isRecording ? '#EF5350' : '#888888')
.margin({ left: 12 })
}
.margin({ bottom: 20 })
// 录制时长
Text(this.formatDuration(this.recordDuration))
.fontSize(48)
.fontWeight(FontWeight.Bold)
.fontColor('#E0E0E0')
.margin({ bottom: 20 })
// 音频电平指示器
Column() {
Text('音频电平').fontSize(12).fontColor('#888888').margin({ bottom: 8 })
Row() {
ForEach(Array.from({ length: 20 }, (_, i) => i), (index: number) => {
Column()
.width(8)
.height(`${Math.max(5, this.audioLevel * (index < this.audioLevel / 5 ? 1 : 0.05))}%`)
.backgroundColor(
index < this.audioLevel / 5 * 0.6 ? '#81C784' :
index < this.audioLevel / 5 * 0.8 ? '#FFB74D' : '#EF5350'
)
.borderRadius(2)
.margin({ right: 2 })
})
}
.width('100%')
.height(60)
.alignItems(VerticalAlign.Bottom)
}
.width('100%')
.padding(15)
.borderRadius(12)
.backgroundColor('#16213e')
.margin({ bottom: 20 })
// 录制配置信息
Row() {
Text('44100Hz | 单声道 | 16bit PCM')
.fontSize(12)
.fontColor('#888888')
}
.margin({ bottom: 20 })
// 控制按钮
Button(this.isRecording ? '停止录制' : '开始录制')
.onClick(() => {
if (this.isRecording) {
this.stopRecording();
} else {
this.startRecording();
}
})
.width(200)
.height(60)
.backgroundColor(this.isRecording ? '#EF5350' : '#6C63FF')
.borderRadius(30)
.fontSize(18)
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#0d0d1a')
.alignItems(HorizontalAlign.Center)
}
// 格式化时长
private formatDuration(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
aboutToDisappear() {
if (this.recordTimer !== -1) {
clearInterval(this.recordTimer);
}
this.audioCapturer?.release();
this.audioCapturer = null;
}
}
3.3 高级:低延迟流实现实时音频回路
低延迟流最常见的应用场景是实时音频回路(Audio Loopback)——从麦克风采集音频,经过处理后立即从扬声器输出。这是乐器 App、K 歌 App 的核心能力。
// 文件名:LowLatencyAudioStream.ets
// 功能:低延迟音频流——实时麦克风到扬声器的回路
import { audio } from '@kit.AudioKit';
// 低延迟流配置
interface LowLatencyConfig {
sampleRate: number;
channelCount: number;
bufferSize: number; // 每帧缓冲区大小(采样数)
totalBufferFrames: number; // 总缓冲帧数
}
@Entry
@Component
struct LowLatencyAudioStreamPage {
@State isLoopbackActive: boolean = false;
@State latencyMs: number = 0;
@State statusText: string = '就绪';
@State cpuUsage: number = 0;
private capturer: audio.AudioCapturer | null = null;
private renderer: audio.AudioRenderer | null = null;
private isRunning: boolean = false;
// 低延迟配置——使用更小的缓冲区
private lowLatencyConfig: LowLatencyConfig = {
sampleRate: 48000, // 48kHz 更适合低延迟
channelCount: 1, // 单声道减少数据量
bufferSize: 480, // 10ms 一帧(48000 / 100 = 480)
totalBufferFrames: 3, // 3 帧总缓冲 = 30ms
};
aboutToAppear() {
this.initLowLatencyStreams();
}
// 初始化低延迟流
async initLowLatencyStreams() {
try {
// ===== 创建低延迟采集器 =====
const capturerOptions: audio.AudioCapturerOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000,
channels: audio.AudioChannel.CHANNEL_1,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW,
},
capturerInfo: {
source: audio.SourceType.SOURCE_TYPE_MIC,
capturerFlags: 0, // 低延迟标志(HarmonyOS 6 可用)
},
};
this.capturer = await audio.createAudioCapturer(capturerOptions);
// ===== 创建低延迟渲染器 =====
const rendererOptions: audio.AudioRendererOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000,
channels: audio.AudioChannel.CHANNEL_1,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW,
},
rendererInfo: {
usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
rendererFlags: 0,
},
};
this.renderer = await audio.createAudioRenderer(rendererOptions);
// 注册采集器的读数据回调——核心回路逻辑
this.capturer.on('readData', (buffer: ArrayBuffer) => {
if (!this.isRunning || !this.renderer) return;
// ★ 核心:将采集到的数据直接写入渲染器
// 这就是"回路"——麦克风 → 扬声器
const writeLen = this.renderer.write(buffer);
if (writeLen < 0) {
console.warn('[Loopback] 写入渲染器失败');
}
});
// 注册渲染器的音频中断回调
this.renderer.on('audioInterrupt', (interruptEvent: audio.InterruptEvent) => {
if (interruptEvent.eventType === audio.InterruptType.INTERRUPT_TYPE_BEGIN) {
this.stopLoopback();
console.info('[Loopback] 被中断,停止回路');
}
});
this.statusText = '初始化完成';
console.info('[Loopback] 低延迟流初始化完成');
} catch (error) {
console.error(`[Loopback] 初始化失败: ${JSON.stringify(error)}`);
this.statusText = `初始化失败: ${error.message}`;
}
}
// 启动音频回路
async startLoopback() {
if (!this.capturer || !this.renderer) return;
try {
// 先启动渲染器(消费者),再启动采集器(生产者)
// 顺序很重要!先启动消费者避免缓冲区溢出
await this.renderer.start();
await this.capturer.start();
this.isRunning = true;
this.isLoopbackActive = true;
this.statusText = '回路运行中';
// 计算理论延迟
const frameDuration = this.lowLatencyConfig.bufferSize / this.lowLatencyConfig.sampleRate * 1000;
this.latencyMs = Math.round(frameDuration * this.lowLatencyConfig.totalBufferFrames);
console.info(`[Loopback] 回路启动,理论延迟: ${this.latencyMs}ms`);
// 启动性能监控
this.startPerformanceMonitor();
} catch (error) {
console.error(`[Loopback] 启动失败: ${JSON.stringify(error)}`);
this.statusText = `启动失败: ${error.message}`;
}
}
// 停止音频回路
async stopLoopback() {
this.isRunning = false;
try {
// 先停采集器,再停渲染器
await this.capturer?.stop();
await this.renderer?.stop();
this.isLoopbackActive = false;
this.statusText = '已停止';
console.info('[Loopback] 回路已停止');
} catch (error) {
console.error(`[Loopback] 停止失败: ${JSON.stringify(error)}`);
}
}
// 性能监控
private startPerformanceMonitor() {
const monitorInterval = setInterval(() => {
if (!this.isRunning) {
clearInterval(monitorInterval);
return;
}
// 模拟 CPU 占用(实际应通过系统 API 获取)
this.cpuUsage = 5 + Math.random() * 15;
}, 1000);
}
build() {
Column() {
Text('低延迟音频回路')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#E0E0E0')
.margin({ bottom: 8 })
Text('Low Latency Audio Loopback')
.fontSize(12)
.fontColor('#6C63FF')
.margin({ bottom: 20 })
// 延迟指标
Row() {
Column() {
Text(`${this.latencyMs}`)
.fontSize(36)
.fontWeight(FontWeight.Bold)
.fontColor('#4FC3F7')
Text('延迟 (ms)')
.fontSize(12)
.fontColor('#888888')
.margin({ top: 4 })
}
.layoutWeight(1)
Column() {
Text(`${this.cpuUsage.toFixed(1)}`)
.fontSize(36)
.fontWeight(FontWeight.Bold)
.fontColor(this.cpuUsage > 30 ? '#EF5350' : '#81C784')
Text('CPU (%)')
.fontSize(12)
.fontColor('#888888')
.margin({ top: 4 })
}
.layoutWeight(1)
}
.width('100%')
.padding(20)
.borderRadius(12)
.backgroundColor('#16213e')
.margin({ bottom: 15 })
// 配置信息
Column() {
Text('低延迟配置').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#CE93D8')
Row() {
Text(`采样率: ${this.lowLatencyConfig.sampleRate}Hz`)
.fontSize(12).fontColor('#AAAAAA')
Text(' | ').fontColor('#555')
Text(`缓冲: ${this.lowLatencyConfig.bufferSize} 采样/帧`)
.fontSize(12).fontColor('#AAAAAA')
Text(' | ').fontColor('#555')
Text(`总帧: ${this.lowLatencyConfig.totalBufferFrames}`)
.fontSize(12).fontColor('#AAAAAA')
}
.margin({ top: 8 })
}
.width('100%')
.padding(15)
.borderRadius(12)
.backgroundColor('#16213e')
.alignItems(HorizontalAlign.Start)
.margin({ bottom: 15 })
// 状态文本
Text(this.statusText)
.fontSize(14)
.fontColor(this.isLoopbackActive ? '#81C784' : '#888888')
.margin({ bottom: 20 })
// 控制按钮
Button(this.isLoopbackActive ? '停止回路' : '启动回路')
.onClick(() => {
if (this.isLoopbackActive) {
this.stopLoopback();
} else {
this.startLoopback();
}
})
.width(200)
.height(60)
.backgroundColor(this.isLoopbackActive ? '#EF5350' : '#6C63FF')
.borderRadius(30)
.fontSize(18)
// 警告提示
if (this.isLoopbackActive) {
Row() {
Text('⚠️')
.fontSize(16)
Text('回路运行中,请使用耳机避免啸叫!')
.fontSize(13)
.fontColor('#FFB74D')
.margin({ left: 8 })
}
.width('100%')
.padding(12)
.borderRadius(10)
.backgroundColor('#3E2723')
.margin({ top: 15 })
}
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#0d0d1a')
.alignItems(HorizontalAlign.Center)
}
aboutToDisappear() {
this.isRunning = false;
this.capturer?.release();
this.renderer?.release();
this.capturer = null;
this.renderer = null;
}
}
四、踩坑与注意事项
4.1 常见陷阱
| 陷阱 | 现象 | 解决方案 |
|---|---|---|
| 流类型选错 | 音乐被闹钟打断后不恢复 | 使用正确的 StreamUsage,音乐用 STREAM_USAGE_MUSIC |
| 忘记处理音频中断 | 来电后 App 继续播放但无声音 | 必须注册 audioInterrupt 回调并正确处理 |
| 缓冲区大小不当 | 播放卡顿或爆音 | 使用 getBufferSize() 获取系统推荐大小 |
| 状态转换跳步 | 调用 start() 直接报错 | 严格按 PREPARED → RUNNING → PAUSED/STOPPED 顺序 |
| 低延迟流啸叫 | 扬声器播放麦克风采集的声音形成正反馈 | 必须使用耳机,或加入回声消除算法 |
| release 后复用 | 调用 release() 后再调用 start() 崩溃 | release 是终止操作,需要重新 create |
4.2 音频中断处理最佳实践
// 完整的音频中断处理模板
function handleAudioInterrupt(
renderer: audio.AudioRenderer,
event: audio.InterruptEvent,
savedVolume: number
): number {
let currentVolume = savedVolume;
if (event.eventType === audio.InterruptType.INTERRUPT_TYPE_BEGIN) {
switch (event.hintType) {
case audio.InterruptHint.INTERRUPT_HINT_PAUSE:
// 电话呼入等场景——暂停播放
renderer.pause();
// ★ 重要:不要在这里调用 release()
// 等中断结束后可以恢复
break;
case audio.InterruptHint.INTERRUPT_HINT_STOP:
// 闹钟等场景——停止播放
renderer.stop();
break;
case audio.InterruptHint.INTERRUPT_HINT_DUCK:
// 导航提示音等场景——降低音量但不暂停
currentVolume = 0.2; // 降到 20%
renderer.setVolume(currentVolume);
break;
case audio.InterruptHint.INTERRUPT_HINT_NONE:
// 被静音但不暂停(如另一个音乐 App 播放)
renderer.setVolume(0);
break;
}
} else {
// INTERRUPT_TYPE_END——中断结束
switch (event.hintType) {
case audio.InterruptHint.INTERRUPT_HINT_RESUME:
// 恢复播放
renderer.start();
break;
case audio.InterruptHint.INTERRUPT_HINT_UNDUCK:
// 恢复音量
renderer.setVolume(savedVolume);
currentVolume = savedVolume;
break;
}
}
return currentVolume;
}
4.3 低延迟流注意事项
- 必须使用耳机:扬声器和麦克风在同一设备上会形成声学回路,导致啸叫
- 采样率选择 48000Hz:这是大多数移动设备音频硬件的原生采样率,避免重采样开销
- 缓冲区越小延迟越低,但稳定性越差:建议从 10ms(480 采样)开始测试
- 低延迟流不参与系统混音:同一时间只能有一个低延迟流活跃
- 优先启动消费者:先 start Renderer 再 start Capturer,避免缓冲区溢出
五、HarmonyOS 6 适配
5.1 版本差异
| 特性 | HarmonyOS 5 (API 12) | HarmonyOS 6 (API 14) |
|---|---|---|
| 低延迟流 | 无专用接口 | 新增:AudioRendererFlags 低延迟标志 |
| 缓冲区控制 | 固定大小 | 新增:setBufferSize() 动态调整 |
| 采集器降噪 | 无 | 新增:内置 AEC(回声消除)和 NS(噪声抑制) |
| 多声道录制 | 最多 2 声道 | 新增:支持最多 8 声道录制 |
| 音频焦点 | 基础焦点管理 | 增强:AudioFocusManager 统一焦点管理 |
5.2 迁移指南
// HarmonyOS 5 写法——手动处理低延迟
const rendererOptions: audio.AudioRendererOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000,
channels: audio.AudioChannel.CHANNEL_1,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW,
},
rendererInfo: {
usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
rendererFlags: 0, // 普通模式
},
};
// HarmonyOS 6 写法——使用低延迟标志
// const rendererOptions: audio.AudioRendererOptions = {
// streamInfo: {
// samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000,
// channels: audio.AudioChannel.CHANNEL_1,
// sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
// encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW,
// },
// rendererInfo: {
// usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
// rendererFlags: audio.AudioRendererFlags.FLAG_LOW_LATENCY, // ★ 低延迟标志
// },
// };
//
// // 采集器启用内置降噪
// const capturerOptions: audio.AudioCapturerOptions = {
// streamInfo: { ... },
// capturerInfo: {
// source: audio.SourceType.SOURCE_TYPE_MIC,
// capturerFlags: audio.AudioCapturerFlags.FLAG_AEC | // 回声消除
// audio.AudioCapturerFlags.FLAG_NS, // 噪声抑制
// },
// };
5.3 注意事项
- HarmonyOS 6 的低延迟标志需要在创建渲染器时指定,创建后无法修改
- 内置 AEC/NS 功能会增加约 5ms 延迟,但能显著提升语音通话质量
setBufferSize()动态调整时,新大小必须大于最小缓冲区大小(通过getBufferSize()获取)
六、总结
mindmap
root((音频流管理))
AudioRenderer
播放 PCM 数据
流类型与用途
音频中断处理
音量控制
AudioCapturer
麦克风采集
音频电平检测
录制文件保存
降噪处理
流类型映射
MUSIC 音乐
VOICE_COMMUNICATION 通话
GAME 游戏
ALARM 闹钟
NOTIFICATION 通知
低延迟流
小缓冲区
跳过混音器
48kHz 原生采样率
耳机防啸叫
状态管理
PREPARED → RUNNING
RUNNING → PAUSED/STOPPED
release 不可复用
严格状态转换
HarmonyOS 6
FLAG_LOW_LATENCY
内置 AEC/NS
动态缓冲区
AudioFocusManager
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
关键知识点回顾:
- AudioRenderer 和 AudioCapturer 是音频流的两个方向——一个输出一个输入,参数配置要匹配
- 流类型决定音频焦点策略——选错了类型,你的 App 可能"该响不响、不该响乱响"
- 音频中断回调必须正确处理——不处理中断 = 用户体验灾难
- 低延迟流的核心是减少缓冲区层级——代价是不参与系统混音
- 状态转换必须严格按顺序——跳步调用会导致异常甚至崩溃
音频流管理是音频开发的"交通指挥系统",搞懂了它,你的音频 App 才能在多应用共存的环境中优雅地运行。
- 点赞
- 收藏
- 关注作者

评论(0)