[audio] AudioTrack (一) 使用

app端的代码使用

com/example/myapplication/MainActivity.kt

// file: MainActivity.kt
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.padding
import androidx.compose.material3.Scaffold
import com.example.myapplication.ui.theme.MyApplicationTheme
import kotlin.concurrent.thread

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

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 = androidx.compose.ui.Modifier.padding(padding),
                        onPlayStatic = {
                            pcmPlayer.playStaticFromAssets("test.pcm")
                        },
                        onStopStatic = {
                            pcmPlayer.stopStatic()
                        },
                        onPlayStream = {
                            thread {
                                pcmPlayer.playStreamFromAssets("test.pcm")
                            }
                        },
                        onStopStream = {
                            pcmPlayer.stopStream()
                        })
                }
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        pcmPlayer.release()
    }
}


enum class PlayMode {
    STATIC, STREAM
}

@Composable
fun PlayerUI(
    modifier: Modifier = Modifier,
    onPlayStatic: () -> Unit,
    onStopStatic: () -> Unit,
    onPlayStream: () -> Unit,
    onStopStream: () -> Unit
) {
    var mode by remember { mutableStateOf(PlayMode.STATIC) }

    Column(
        modifier = modifier
            .fillMaxSize()
            .padding(24.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {

        Text(
            text = "PCM Player", style = MaterialTheme.typography.headlineMedium
        )

        Spacer(Modifier.height(24.dp))

        // ===== 模式选择 =====
        Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
            ModeRadio(
                text = "MODE_STATIC", selected = mode == PlayMode.STATIC
            ) { mode = PlayMode.STATIC }

            ModeRadio(
                text = "MODE_STREAM", selected = mode == PlayMode.STREAM
            ) { mode = PlayMode.STREAM }
        }

        Spacer(Modifier.height(32.dp))

        // ===== 控制按钮 =====
        Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
            Button(onClick = {
                when (mode) {
                    PlayMode.STATIC -> onPlayStatic()
                    PlayMode.STREAM -> onPlayStream()
                }
            }) {
                Text("播放")
            }

            Button(onClick = {
                when (mode) {
                    PlayMode.STATIC -> onStopStatic()
                    PlayMode.STREAM -> onStopStream()
                }
            }) {
                Text("停止")
            }
        }
    }
}

@Composable
private fun ModeRadio(
    text: String, selected: Boolean, onClick: () -> Unit
) {
    Row(
        verticalAlignment = Alignment.CenterVertically
    ) {
        RadioButton(
            selected = selected, onClick = onClick
        )
        Text(text)
    }
}

com.example.myapplication.PcmAudioPlayer

// 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.io.ByteArrayOutputStream
import java.util.concurrent.atomic.AtomicBoolean

class PcmAudioPlayer(context: Context) {

    private val sampleRate = 44100
    private val channelConfig = AudioFormat.CHANNEL_OUT_STEREO
    private val audioFormat = AudioFormat.ENCODING_PCM_16BIT

    private val minBufferSize =
        AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat)

    private val assetManager = context.assets

    private val isPlayingStream = AtomicBoolean(false)
    private val isPlayingStatic = AtomicBoolean(false)

    /* =========================
     * MODE_STREAM AudioTrack
     * ========================= */
    private val streamTrack: 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(minBufferSize)
        .build()

    fun playStreamFromAssets(fileName: String) {
        if (isPlayingStream.get()) return

        isPlayingStream.set(true)
        streamTrack.play()

        assetManager.open(fileName).use { input ->
            val buffer = ByteArray(minBufferSize)
            var len: Int

            while (input.read(buffer).also { len = it } > 0) {
                streamTrack.write(buffer, 0, len)
            }
        }
    }

    fun stopStream() {
        if (!isPlayingStream.get()) return

        isPlayingStream.set(false)
        streamTrack.pause()
        streamTrack.flush()
        streamTrack.stop()
    }

    /* =========================
     * MODE_STATIC AudioTrack
     * ========================= */
    private var staticTrack: AudioTrack? = null

    fun playStaticFromAssets(fileName: String) {
        if (isPlayingStatic.get()) return

        val pcmData = loadPcmFromAssets(fileName)
        if (pcmData.isEmpty()) return

        val frameSize = 4 // 16bit * stereo
        val validSize = pcmData.size - (pcmData.size % frameSize)
        if (validSize <= 0) return

        staticTrack?.release()

        val bufferSize = maxOf(validSize, minBufferSize)

        staticTrack = 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_STATIC)
            .setBufferSizeInBytes(bufferSize)
            .build()

        staticTrack?.apply {
            write(pcmData, 0, validSize)
            isPlayingStatic.set(true)
            play()
        }
    }

    fun stopStatic() {
        if (!isPlayingStatic.get()) return

        isPlayingStatic.set(false)
        staticTrack?.stop()
        staticTrack?.flush()
    }

    /* =========================
     * Common
     * ========================= */
    private fun loadPcmFromAssets(fileName: String): ByteArray {
        assetManager.open(fileName).use { input ->
            val output = ByteArrayOutputStream()
            val buffer = ByteArray(4096)
            var len: Int

            while (input.read(buffer).also { len = it } > 0) {
                output.write(buffer, 0, len)
            }
            return output.toByteArray()
        }
    }

    fun release() {
        isPlayingStream.set(false)
        isPlayingStatic.set(false)

        streamTrack.release()
        staticTrack?.release()
        staticTrack = null
    }
}


native版本的使用方式

// 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;
}

内容概要:本文档是份关于交换路由配置的学习笔记,系统地介绍了网络设备的远程管理、交换机与路由器的核心配置技术。内容涵盖Telnet、SSH、Console三种远程控制方式的配置方法;详细讲解了VLAN划分原理及Access、Trunk、Hybrid端口的工作机制,以及端口镜像、端口汇聚、端口隔离等交换技术;深入解析了STP、MSTP、RSTP生成树协议的作用与配置步骤;在路由部分,涵盖了IP地址配置、DHCP服务部署(接口池与全局池)、NAT转换(静态与动态)、静态路由、RIP与OSPF动态路由协议的配置,并介绍了策略路由和ACL访问控制列表的应用;最后简要说明了华为防火墙的安全区域划分与基本安全策略配置。; 适合人群:具备定网络基础知识,从事网络工程、运维或相关技术岗位1-3年的技术人员,以及准备参加HCIA/CCNA等认证考试的学习者。; 使用场景及目标:①掌握企业网络中常见的交换与路由配置技能,提升实际操作能力;②理解VLAN、STP、OSPF、NAT、ACL等核心技术原理并能独立完成中小型网络搭建与调试;③通过命令示例熟悉华为设备CLI配置逻辑,为项目实施和故障排查提供参考。; 阅读建议:此笔记以实用配置为主,建议结合模拟器(如eNSP或Packet Tracer)动手实践每条命令,对照拓扑理解数据流向,重点关注VLAN间通信、路由选择机制、安全策略控制等关键环节,并注意不同设备型号间的命令差异。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值