kt版本的使用代码
com/example/myapplication/MainActivity.kt
Kotlin
package com.example.myapplication
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.example.myapplication.ui.theme.MyApplicationTheme
import kotlin.concurrent.thread
class MainActivity : ComponentActivity() {
private lateinit var pcmPlayer: PcmAudioPlayer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
pcmPlayer = PcmAudioPlayer(this)
setContent {
MyApplicationTheme {
Scaffold { padding ->
PlayerUI(
modifier = Modifier.padding(padding),
onPlay = {
thread {
pcmPlayer.playFromAssets("test.pcm")
}
},
onStop = {
pcmPlayer.stop()
}
)
}
}
}
}
override fun onDestroy() {
super.onDestroy()
pcmPlayer.release()
}
}
@Composable
fun PlayerUI(
modifier: Modifier = Modifier,
onPlay: () -> Unit,
onStop: () -> Unit
) {
Column(
modifier = modifier
.fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "PCM Player (Assets)",
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.height(32.dp))
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Button(onClick = onPlay) {
Text("播放")
}
Button(onClick = onStop) {
Text("停止")
}
}
}
}
com.example.myapplication.PcmAudioPlayer
Kotlin
// file: PcmAudioPlayer.kt
package com.example.myapplication
import android.content.Context
import android.media.AudioAttributes
import android.media.AudioFormat
import android.media.AudioTrack
import java.util.concurrent.atomic.AtomicBoolean
class PcmAudioPlayer(context: Context) {
private val sampleRate = 44100
private val channelConfig = AudioFormat.CHANNEL_OUT_MONO
private val audioFormat = AudioFormat.ENCODING_PCM_16BIT
private val bufferSize =
AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat)
private val audioTrack: AudioTrack = AudioTrack.Builder()
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
)
.setAudioFormat(
AudioFormat.Builder()
.setSampleRate(sampleRate)
.setEncoding(audioFormat)
.setChannelMask(channelConfig)
.build()
)
.setTransferMode(AudioTrack.MODE_STREAM)
.setBufferSizeInBytes(bufferSize)
.build()
private val assetManager = context.assets
private val isPlaying = AtomicBoolean(false)
fun playFromAssets(fileName: String) {
if (isPlaying.get()) return
isPlaying.set(true)
audioTrack.play()
assetManager.open(fileName).use { input ->
val buffer = ByteArray(bufferSize)
var len: Int
while (input.read(buffer).also { len = it } > 0) {
audioTrack.write(buffer, 0, len)
}
}
}
fun stop() {
if (!isPlaying.get()) return
isPlaying.set(false)
audioTrack.pause()
audioTrack.flush()
audioTrack.stop()
}
fun release() {
isPlaying.set(false)
audioTrack.release()
}
}
native版本的使用方式
cpp
// pcm_player.cpp
// 一个在 AOSP 源码环境下运行的 native 可执行程序
// 功能:使用 AudioTrack 播放原始 PCM 音频文件
#include <android/log.h> // Android 日志
#include <media/AudioTrack.h> // AudioTrack(native 层)
#include <utils/Errors.h> // NO_ERROR 等错误码
#include <fcntl.h> // open
#include <unistd.h> // read / close
#include <sys/stat.h> // stat
using namespace android;
// ========================
// 音频参数定义(必须与 PCM 文件一致)
// ========================
// 采样率:44100Hz
static constexpr int SAMPLE_RATE = 44100;
// 音频格式:16bit PCM
static constexpr audio_format_t FORMAT = AUDIO_FORMAT_PCM_16_BIT;
// 声道:立体声输出
static constexpr audio_channel_mask_t CHANNEL_MASK = AUDIO_CHANNEL_OUT_STEREO;
#define USE_AUDIO_ATTRIBUTES
int main(int argc, char* argv[]) {
// ------------------------
// 1. 参数检查
// ------------------------
// 期望参数:pcm_player <pcm_file_path>
if (argc < 2) {
__android_log_print(
ANDROID_LOG_ERROR,
"PCM_PLAYER",
"Usage: pcm_player <pcm_file>"
);
return -1;
}
// PCM 文件路径
const char* path = argv[1];
// ------------------------
// 2. 打开 PCM 文件
// ------------------------
int fd = open(path, O_RDONLY);
if (fd < 0) {
__android_log_print(
ANDROID_LOG_ERROR,
"PCM_PLAYER",
"Failed to open %s",
path
);
return -1;
}
// ------------------------
// 3. 计算 AudioTrack 最小 frame 数
// ------------------------
// frame = 一个采样点(包含所有声道)
// AudioTrack 内部 buffer 的最小安全大小
size_t frameCount = 0;
status_t status = AudioTrack::getMinFrameCount(
&frameCount, // 输出参数:最小 frame 数
AUDIO_STREAM_MUSIC, // 音频流类型
SAMPLE_RATE // 采样率
);
sp<AudioTrack> track;
#ifdef USE_AUDIO_ATTRIBUTES
// ------------------------
// 3. 构造 AudioAttributes
// ------------------------
// 告诉系统这是"媒体播放"类型的音频
audio_attributes_t attributes = AUDIO_ATTRIBUTES_INITIALIZER;
attributes.usage = AUDIO_USAGE_MEDIA;
attributes.content_type = AUDIO_CONTENT_TYPE_MUSIC;
// ------------------------
// 6. 使用 AudioAttributes 创建 AudioTrack
// ------------------------
track = new AudioTrack(
AUDIO_STREAM_MUSIC,
SAMPLE_RATE,
FORMAT,
CHANNEL_MASK,
frameCount,
AUDIO_OUTPUT_FLAG_NONE,
nullptr,
0,
AUDIO_SESSION_ALLOCATE,
AudioTrack::TRANSFER_DEFAULT,
nullptr,
AttributionSourceState(),
&attributes
);
// av/media/libaudioclient/include/media/AudioTrack.h
// AudioTrack( audio_stream_type_t streamType,
// uint32_t sampleRate,
// audio_format_t format,
// audio_channel_mask_t channelMask,
// size_t frameCount = 0,
// audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_NONE,
// const wp<IAudioTrackCallback>& callback = nullptr,
// int32_t notificationFrames = 0,
// audio_session_t sessionId = AUDIO_SESSION_ALLOCATE,
// transfer_type transferType = TRANSFER_DEFAULT,
// const audio_offload_info_t *offloadInfo = nullptr,
// const AttributionSourceState& attributionSource =
// AttributionSourceState(),
// const audio_attributes_t* pAttributes = nullptr,
// bool doNotReconnect = false,
// float maxRequiredSpeed = 1.0f,
// audio_port_handle_t selectedDeviceId = AUDIO_PORT_HANDLE_NONE);
#else
// ------------------------
// 4. 创建 AudioTrack 对象
// ------------------------
// 这是 native 层播放音频的核心对象
track = new AudioTrack(
AUDIO_STREAM_MUSIC, // 音频流类型(音乐)
SAMPLE_RATE, // 采样率
FORMAT, // PCM 16bit
CHANNEL_MASK, // 立体声
frameCount // buffer frame 数
);
#endif
// ------------------------
// 5. 检查 AudioTrack 初始化是否成功
// ------------------------
if (track->initCheck() != NO_ERROR) {
__android_log_print(
ANDROID_LOG_ERROR,
"PCM_PLAYER",
"AudioTrack init failed"
);
close(fd);
return -1;
}
// ------------------------
// 6. 启动音频播放
// ------------------------
track->start();
// ------------------------
// 7. 循环读取 PCM 文件并写入 AudioTrack
// ------------------------
// 每次读取一小块,模拟流式播放
constexpr size_t BUF_SIZE = 4096;
uint8_t buffer[BUF_SIZE];
ssize_t readBytes;
while ((readBytes = read(fd, buffer, BUF_SIZE)) > 0) {
// 将 PCM 数据写入 AudioTrack 的内部 buffer
// AudioTrack 会自动送给 AudioFlinger 播放
ssize_t written = track->write(buffer, readBytes);
// 如果写入失败,直接退出
if (written < 0) {
__android_log_print(
ANDROID_LOG_ERROR,
"PCM_PLAYER",
"AudioTrack write error"
);
break;
}
}
// ------------------------
// 8. 停止播放并释放资源
// ------------------------
track->stop(); // 停止 AudioTrack
track.clear(); // 释放强引用,析构 AudioTrack
close(fd); // 关闭 PCM 文件
__android_log_print(
ANDROID_LOG_INFO,
"PCM_PLAYER",
"Playback finished"
);
return 0;
}