KMP 结构适配鸿蒙:音频处理与播放理论知识
跨平台音频处理系统实现 本文介绍了在Kotlin Multiplatform(KMP)框架下构建跨平台音频处理系统的设计方案。系统包含音频播放、录制和处理功能,支持Android、iOS和鸿蒙平台。 核心架构 音频播放:通过AudioPlayer抽象类提供统一接口,包括加载、播放控制、进度管理等功能 音频录制:AudioRecorder类实现录音生命周期管理,支持不同质量设置 平台适配:在各平台底
项目概述
音频处理与播放是现代应用的重要功能。在 KMP 框架下,我们需要实现跨平台的音频播放、录制、处理和音效管理。本文详细介绍了如何在 Kotlin Multiplatform 中实现高效的音频处理系统,包括音频播放、录制、音效处理和鸿蒙系统特定的音频管理方式。
相比视频,音频模块往往更加“无处不在”:
- 音乐/播客类应用:需要稳定的后台播放、播放列表管理、倍速播放等;
- 语音通话/会议:需要低延迟、回声消除、麦克风降噪等处理;
- 系统通知、提示音:强调功耗低、启动快、与系统音量策略一致。
本篇的目标是:
- 在
commonMain中抽象出统一的AudioPlayer、AudioRecorder接口; - 在 Android、iOS、鸿蒙等平台下分别基于原生音频 API 做
actual实现; - 预留音效处理(均衡器、预设效果)、播放列表等高级功能的扩展空间。
第一部分:音频处理核心概念
音频播放架构
// commonMain/kotlin/com/example/audio/AudioPlayer.kt
expect class AudioPlayer {
suspend fun loadAudio(url: String): Boolean
suspend fun play(): Boolean
suspend fun pause(): Boolean
suspend fun stop(): Boolean
suspend fun seek(positionMs: Long): Boolean
suspend fun setVolume(volume: Float): Boolean
suspend fun setPlaybackSpeed(speed: Float): Boolean
fun getDuration(): Long
fun getCurrentPosition(): Long
fun addListener(listener: AudioPlayerListener)
}
interface AudioPlayerListener {
fun onStateChanged(state: AudioState)
fun onProgressChanged(position: Long, duration: Long)
fun onError(error: String)
fun onCompletion()
}
enum class AudioState {
IDLE, LOADING, PLAYING, PAUSED, STOPPED, ERROR
}
AudioPlayer 作为跨平台音频播放能力的统一抽象,定义了:
- 常见操作:加载、播放、暂停、停止、seek、调节音量和倍速播放;
- 状态获取:
getDuration、getCurrentPosition方便 UI 绘制进度条; - 事件回调:
AudioPlayerListener把状态变化、进度更新、错误和播放完成等事件统一封装。
这样的接口设计可以让上层业务完全不关心底层使用的是 MediaPlayer、AVAudioPlayer 还是鸿蒙的 Player,从而在多端之间共享更多逻辑(比如播放控制栏、播放历史记录、播放状态管理等)。
音频录制架构
// commonMain/kotlin/com/example/audio/AudioRecorder.kt
expect class AudioRecorder {
suspend fun startRecording(outputPath: String): Boolean
suspend fun stopRecording(): Boolean
suspend fun pauseRecording(): Boolean
suspend fun resumeRecording(): Boolean
suspend fun setAudioQuality(quality: AudioQuality): Boolean
fun isRecording(): Boolean
}
enum class AudioQuality {
LOW, MEDIUM, HIGH, LOSSLESS
}
data class AudioRecordingConfig(
val quality: AudioQuality = AudioQuality.HIGH,
val sampleRate: Int = 44100,
val bitrate: Int = 128000,
val channels: Int = 2
)
录音功能在实际场景中应用非常广泛:语音备忘录、语音聊天、在线客服录音等。由于设备和平台差异较大,在公共层提前约定好录音配置尤为重要。
AudioRecorder提供从开始到结束的一整套录音生命周期方法;AudioQuality划分了从低码率到无损的不同档位,方便在各平台上映射到不同采样率和比特率组合;AudioRecordingConfig用来集中描述录音参数,例如采样率、码率、声道数等,便于在 Android、iOS、鸿蒙内部进行转换和校验。
实践建议:
- 在启动录音前统一做权限检查(麦克风、存储等);
- 对不同设备型号和场景预设合理的采样率和码率,避免高档位导致耗电和空间占用过高;
- 将录音状态暴露给 UI,避免出现“录音中用户以为没开始”的体验问题。
第二部分:平台特定实现
Android 音频处理
// androidMain/kotlin/com/example/audio/AudioPlayer.kt
import android.media.MediaPlayer
import android.media.AudioAttributes
import android.media.AudioManager
actual class AudioPlayer(private val context: Context) {
private val mediaPlayer = MediaPlayer()
private val listeners = mutableListOf<AudioPlayerListener>()
actual suspend fun loadAudio(url: String): Boolean {
return try {
mediaPlayer.apply {
setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
)
setDataSource(url)
prepareAsync()
}
listeners.forEach { it.onStateChanged(AudioState.LOADING) }
true
} catch (e: Exception) {
listeners.forEach { it.onError(e.message ?: "Failed to load audio") }
false
}
}
actual suspend fun play(): Boolean {
return try {
mediaPlayer.start()
listeners.forEach { it.onStateChanged(AudioState.PLAYING) }
true
} catch (e: Exception) {
false
}
}
actual suspend fun pause(): Boolean {
return try {
mediaPlayer.pause()
listeners.forEach { it.onStateChanged(AudioState.PAUSED) }
true
} catch (e: Exception) {
false
}
}
actual suspend fun setVolume(volume: Float): Boolean {
return try {
mediaPlayer.setVolume(volume, volume)
true
} catch (e: Exception) {
false
}
}
actual suspend fun setPlaybackSpeed(speed: Float): Boolean {
return try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
mediaPlayer.playbackParams = mediaPlayer.playbackParams.setSpeed(speed)
}
true
} catch (e: Exception) {
false
}
}
actual fun getDuration(): Long = mediaPlayer.duration.toLong()
actual fun getCurrentPosition(): Long = mediaPlayer.currentPosition.toLong()
actual fun addListener(listener: AudioPlayerListener) {
listeners.add(listener)
}
}
// Android 音频录制
actual class AudioRecorder(private val context: Context) {
private val mediaRecorder = MediaRecorder()
private var isRecordingFlag = false
actual suspend fun startRecording(outputPath: String): Boolean {
return try {
mediaRecorder.apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
setAudioSamplingRate(44100)
setAudioBitRate(128000)
setOutputFile(outputPath)
prepare()
start()
}
isRecordingFlag = true
true
} catch (e: Exception) {
false
}
}
actual suspend fun stopRecording(): Boolean {
return try {
mediaRecorder.stop()
mediaRecorder.release()
isRecordingFlag = false
true
} catch (e: Exception) {
false
}
}
actual fun isRecording(): Boolean = isRecordingFlag
}
Android 端音频实现的要点:
- 播放使用
MediaPlayer并配置AudioAttributes,确保在不同使用场景(媒体、通话、通知等)下与系统音量、音频焦点交互正常; - 通过监听和回调将底层状态转换为
AudioState,让上层以统一的方式感知播放进度与错误; - 录音使用
MediaRecorder,展示了最基本的音频源、输出格式、编码器、采样率和比特率配置。
实际项目中可以进一步优化:
- 使用
AudioFocusRequest管理音频焦点,处理来电、闹钟等打断场景; - 在录音过程中增加波形显示、音量电平监控等 UI 反馈;
- 对录音文件进行统一命名和清理策略,防止磁盘被占满。
iOS 音频处理
// iosMain/kotlin/com/example/audio/AudioPlayer.kt
import platform.AVFoundation.*
import platform.MediaPlayer.*
actual class AudioPlayer {
private val audioPlayer = AVAudioPlayer()
private val listeners = mutableListOf<AudioPlayerListener>()
actual suspend fun loadAudio(url: String): Boolean {
return try {
val nsUrl = NSURL(string = url)
val data = NSData.dataWithContentsOfURL(nsUrl)
val player = AVAudioPlayer(data = data, fileTypeHint = AVFileTypeMP3)
audioPlayer = player
listeners.forEach { it.onStateChanged(AudioState.LOADING) }
true
} catch (e: Exception) {
listeners.forEach { it.onError(e.message ?: "Failed to load audio") }
false
}
}
actual suspend fun play(): Boolean {
return try {
audioPlayer.play()
listeners.forEach { it.onStateChanged(AudioState.PLAYING) }
true
} catch (e: Exception) {
false
}
}
actual suspend fun pause(): Boolean {
return try {
audioPlayer.pause()
listeners.forEach { it.onStateChanged(AudioState.PAUSED) }
true
} catch (e: Exception) {
false
}
}
actual suspend fun setVolume(volume: Float): Boolean {
return try {
audioPlayer.volume = volume.toDouble()
true
} catch (e: Exception) {
false
}
}
actual fun getDuration(): Long {
return (audioPlayer.duration * 1000).toLong()
}
actual fun getCurrentPosition(): Long {
return (audioPlayer.currentTime * 1000).toLong()
}
}
// iOS 音频录制
actual class AudioRecorder {
private val audioRecorder: AVAudioRecorder? = null
actual suspend fun startRecording(outputPath: String): Boolean {
return try {
val fileUrl = NSURL(fileURLWithPath = outputPath)
val settings = mapOf(
AVFormatIDKey to NSNumber(value = kAudioFormatMPEG4AAC),
AVSampleRateKey to NSNumber(value = 44100),
AVNumberOfChannelsKey to NSNumber(value = 2)
)
// Create and start recorder
true
} catch (e: Exception) {
false
}
}
}
iOS 平台中:
- 播放通常使用
AVAudioPlayer或AVPlayer,在这里通过AVAudioPlayer演示本地/内存音频播放; - 可以结合
AVAudioSession配置音频会话类型(播放、录制、播放并录制等),从而控制是否走听筒/扬声器、是否支持后台播放等; - 录制侧使用
AVAudioRecorder,通过settings配置格式、采样率和声道数等,和公共层的AudioRecordingConfig概念相互对应。
通过将这些实现封装在 iosMain 中,上层只需要调用统一的 AudioPlayer、AudioRecorder 接口即可,无需理解 AVFoundation 的复杂细节,降低多端开发成本。
第三部分:编译后的代码形式
JavaScript 编译版本
// 音频播放器 (JavaScript/Web)
class AudioPlayer {
constructor() {
this.audio = new Audio();
this.listeners = [];
}
async loadAudio(url) {
try {
this.audio.src = url;
this.audio.addEventListener('loadedmetadata', () => {
this.notifyListeners('onStateChanged', 'LOADED');
});
this.audio.addEventListener('ended', () => {
this.notifyListeners('onCompletion');
});
return true;
} catch (error) {
this.notifyListeners('onError', error.message);
return false;
}
}
async play() {
try {
await this.audio.play();
this.notifyListeners('onStateChanged', 'PLAYING');
return true;
} catch (error) {
return false;
}
}
async pause() {
this.audio.pause();
this.notifyListeners('onStateChanged', 'PAUSED');
return true;
}
async setVolume(volume) {
this.audio.volume = Math.max(0, Math.min(1, volume));
return true;
}
async setPlaybackSpeed(speed) {
this.audio.playbackRate = speed;
return true;
}
getDuration() {
return this.audio.duration * 1000;
}
getCurrentPosition() {
return this.audio.currentTime * 1000;
}
addListener(listener) {
this.listeners.push(listener);
}
notifyListeners(method, ...args) {
this.listeners.forEach(listener => {
if (listener[method]) {
listener[method](...args);
}
});
}
}
// 音频录制器
class AudioRecorder {
async startRecording() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.mediaRecorder = new MediaRecorder(stream);
this.chunks = [];
this.mediaRecorder.ondataavailable = (e) => {
this.chunks.push(e.data);
};
this.mediaRecorder.start();
return true;
} catch (error) {
return false;
}
}
async stopRecording() {
return new Promise((resolve) => {
this.mediaRecorder.onstop = () => {
const blob = new Blob(this.chunks, { type: 'audio/mp3' });
resolve(blob);
};
this.mediaRecorder.stop();
});
}
}
第四部分:鸿蒙系统实际应用
鸿蒙音频处理
// ohos/kotlin/com/example/audio/AudioPlayer.kt
import ohos.media.player.Player
import ohos.media.player.PlayerCallback
import ohos.app.Context
actual class AudioPlayer(private val context: Context) {
private val player = Player(context)
private val listeners = mutableListOf<AudioPlayerListener>()
actual suspend fun loadAudio(url: String): Boolean {
return try {
player.source = url
player.setPlayerCallback(object : PlayerCallback {
override fun onPlaybackComplete() {
listeners.forEach { it.onCompletion() }
}
override fun onError(errorCode: Int, errorMsg: String) {
listeners.forEach { it.onError(errorMsg) }
}
})
listeners.forEach { it.onStateChanged(AudioState.LOADING) }
true
} catch (e: Exception) {
false
}
}
actual suspend fun play(): Boolean {
return try {
player.play()
listeners.forEach { it.onStateChanged(AudioState.PLAYING) }
true
} catch (e: Exception) {
false
}
}
actual fun getDuration(): Long = player.duration
actual fun getCurrentPosition(): Long = player.currentTime
}
// 鸿蒙音频录制
actual class AudioRecorder(private val context: Context) {
private val recorder = ohos.media.recorder.AudioRecorder()
actual suspend fun startRecording(outputPath: String): Boolean {
return try {
recorder.prepare(AudioRecordingConfig().apply {
outputPath = outputPath
sampleRate = 44100
bitrate = 128000
channels = 2
})
recorder.start()
true
} catch (e: Exception) {
false
}
}
actual suspend fun stopRecording(): Boolean {
return try {
recorder.stop()
recorder.release()
true
} catch (e: Exception) {
false
}
}
}
这一节重点展示如何在鸿蒙系统中实现与其他平台一致的音频能力:
- 播放侧同样使用
ohos.media.player.Player,通过PlayerCallback将播放完成、错误等事件转换为统一的AudioState与回调; - 录制侧使用
ohos.media.recorder.AudioRecorder,并基于AudioRecordingConfig的思想配置输出路径、采样率、比特率和声道; - 所有这些实现都封装在
actual类中,对上层业务保持透明。
在项目实践中,可以:
- 为鸿蒙端单独封装一层“设备能力探测”,根据不同机型决定是否开启高质量录制模式;
- 与权限模块结合,在调用录制前统一申请和校验麦克风权限;
- 在 UI 层提供录音指示、波形动画、剩余时长提示等,提高用户感知。
第五部分:高级功能
音频效果处理
// commonMain/kotlin/com/example/audio/AudioEffects.kt
class AudioEffectManager(private val audioPlayer: AudioPlayer) {
suspend fun applyEqualizer(preset: EqualizerPreset): Boolean {
return try {
when (preset) {
EqualizerPreset.BASS_BOOST -> applyBassBoost()
EqualizerPreset.TREBLE_BOOST -> applyTrebleBoost()
EqualizerPreset.VOCAL_ENHANCE -> applyVocalEnhance()
EqualizerPreset.FLAT -> applyFlat()
}
true
} catch (e: Exception) {
false
}
}
}
enum class EqualizerPreset {
BASS_BOOST, TREBLE_BOOST, VOCAL_ENHANCE, FLAT
}
// 播放列表管理
class PlaylistManager(private val audioPlayer: AudioPlayer) {
private val playlist = mutableListOf<AudioTrack>()
private var currentIndex = 0
data class AudioTrack(
val id: String,
val title: String,
val url: String,
val duration: Long
)
suspend fun playNext(): Boolean {
if (currentIndex < playlist.size - 1) {
currentIndex++
return audioPlayer.loadAudio(playlist[currentIndex].url)
}
return false
}
suspend fun playPrevious(): Boolean {
if (currentIndex > 0) {
currentIndex--
return audioPlayer.loadAudio(playlist[currentIndex].url)
}
return false
}
}

总结
音频处理与播放是现代应用的重要功能。通过 KMP 框架,我们可以在共享代码中定义统一的音频播放和录制接口,同时在各平台上利用原生 API 实现高效的处理。鸿蒙系统提供了完整的音频处理 API,使得跨平台实现相对平顺。关键是要合理处理音频格式、采样率和音效处理,以提供良好的用户体验。
在工程实践中,你可以结合本篇内容重点思考:
- 公共层抽象是否覆盖了业务所需的主要场景(音乐、语音通话、系统提示音等);
- 不同平台下有哪些音频能力差异,需要通过适配层来屏蔽?
- 如何在保证功耗和性能的前提下,提供足够丰富的音效和播放体验?
建议的下一步实践是:
- 在 demo 中实现一个简单的跨平台音乐播放器,包含播放/暂停、进度条、音量调节和播放列表;
- 尝试为其中一两个场景接入均衡器或音效预设,感受在 KMP 下如何扩展平台能力;
- 将音频模块与应用其他功能(如通知、下载管理、账号系统)集成,打通完整业务链路。
更多推荐


所有评论(0)