[audio] AudioTrack (一) 使用

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;
}
相关推荐
我会来砍你3 小时前
2025.12.28
其他
xiaolang_8616_wjl3 小时前
2025年——迈入新的环境,接受新的挑战
其他·2025
老陈头聊SEO10 小时前
生成引擎优化(GEO)助力内容创作与用户体验相互提升的创新路径
其他·搜索引擎·seo优化
pingcode14 小时前
四大血型科普
其他
m0_467031361 天前
西兰花矮砧密植与水肥一体化系统:铺设实操指南
其他
北京海得康2 天前
艾曲波帕联合治疗方案:肿瘤放化疗后血小板低的优化策
其他
北京海得康2 天前
艾曲波帕vs其他升血小板药物:肿瘤化疗患者该怎么选?
其他