三、音频播放

326 阅读4分钟

前言

Android SDK提供了多种音频播放API来满足不同场景的需求

  1. MediaPlayerandroid.media.MediaPlayer 是 Android 中一个常用的音频和视频播放类。它可以用来播放各种格式的媒体文件,如 MP3、AAC 等,并且支持本地文件和网络流媒体。
  2. SoundPoolandroid.media.SoundPool 类适用于短音频片段的播放,特别适合游戏中的音效。它能够并发播放多个声音,并提供音量控制、优先级等特性。
  3. AudioTrackandroid.media.AudioTrack 类用于直接从内存中播放原始音频数据流,例如 PCM 格式的数据。对于需要低延迟播放或者处理自定义编码音频的应用非常有用。

在这里,我们主要了解一下 AudioTrack 是如何来播放音频的。

AudioTrack 的工作流程

初始化 AudioTrack 对象

首先看一下AudioTrack 的构造函数

public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
            int bufferSizeInBytes, int mode, int sessionId)

创建 AudioTrack 实例时,需要指定一系列参数:

  • streamType 音频流类型:这个参数代表着当前应用使用的哪一种音频管理策略,当系统有多个进程需要播放音频时,这个管理策略会决定最终的展现效果,常见的有:
    • STREAM_VOCIE_CALL:电话声音
    • STREAM_SYSTEM:系统声音
    • STREAM_RING:铃声
    • STREAM_MUSCI:音乐声
    • STREAM_ALARM:警告声
    • STREAM_NOTIFICATION:通知声
  • sampleRateInHz 采样率:指定音频数据每秒的采样次数。采样率越高,音质越好,但也会占用更多存储空间和计算资源。现在唯一能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。
  • channelConfig 声道配置:使用 AudioFormat 类中的常量指定声道数。
    • AudioFormat.CHANNEL_OUT_MONO 表示单声道,
    • AudioFormat.CHANNEL_OUT_STEREO表示立体声双声道。
  • audioFormat 编码格式:使用 AudioFormat 类中的常量指定音频数据的编码格式,这个参数是用来配置 数据位宽 的
    • ENCODING_PCM_16BIT(16bit)表示16位有符号PCM格式 (这个格式可以保证兼容所有Android手机)
    • ENCODING_PCM_8BIT(8bit)表示8位无符号PCM格式
  • bufferSizeInBytes 缓冲区大小:使用 AudioTrack.getMinBufferSize() 方法计算最小有效缓冲区大小
  • mode 播放模式:通过 AudioTrack.MODE_STREAMAudioTrack.MODE_STATIC 进行设置。
    • MODE_STREAM:连续播放模式,适用于实时生成或者持续写入音频数据的情况。
    • MODE_STATIC:静态播放模式,适用于一次性加载所有音频数据到缓冲区中并一次性播放完的情况。

填充音频数据

  • 从某个来源获取音频数据(例如读取文件或接收网络传输的PCM数据)。
  • 将音频数据写入 AudioTrack 的内部缓冲区。这通常在一个循环或者单独的线程中进行以保证连续播放。
byte[] audioData = ...; // 填充音频数据
audioTrack.write(audioData, 0, audioData.length); // 将音频数据写入AudioTrack的缓冲区

准备和启动播放

  • 在将音频数据写入缓冲区后,调用 AudioTrack.play() 方法开始播放。
audioTrack.play();

实时音频处理

  • 如果需要进行实时音频处理(比如回声消除、音量控制等),则在写入音频数据到 AudioTrack 之前进行相应的处理。

停止和释放资源

  • 当音频播放完毕或者需要停止播放时,调用 AudioTrack.stop() 方法停止播放,并且在不再需要 AudioTrack 对象时,调用 release() 方法释放相关的系统资源。
audioTrack.stop();
audioTrack.release();

音频录制播放代码示例:

这个是 MODE_STREAM 模式的代码示例,如果是 MODE_STATIC 模式,需要先把音频一次性加载到缓冲区,然后再播放

/**
 * 采样率,现在唯一能够保证在所有设备上使用的采样率是44100Hz。
 */
public static final int SAMPLE_RATE_INHZ = 44100;

/**
 * 声道数。
 */
public static final int CHANNEL_IN_CONFIG = AudioFormat.CHANNEL_IN_MONO;
public static final int CHANNEL_OUT_CONFIG = AudioFormat.CHANNEL_OUT_MONO;
/**
 * 音频数据的格式。
 */
public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
   public void startRecord() {
        final int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_IN_CONFIG, AUDIO_FORMAT);
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ,
                CHANNEL_IN_CONFIG, AUDIO_FORMAT, minBufferSize);

        final byte data[] = new byte[minBufferSize];
        final File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "audioDemo.pcm");
        if (!file.mkdirs()) {
            Log.e(TAG, "Directory not created");
        }
        if (file.exists()) {
            file.delete();
        }

        audioRecord.startRecording();
        isRecording = true;


        new Thread(new Runnable() {
            @Override
            public void run() {

                FileOutputStream os = null;
                try {
                    os = new FileOutputStream(file);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }

                if (null != os) {
                    while (isRecording) {
                        int read = audioRecord.read(data, 0, minBufferSize);
                        // 如果读取音频数据没有出现错误,就将数据写入到文件
                        if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                            try {
                                os.write(data);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    try {
                        os.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
    public void stopRecord() {
        isRecording = false;
        // 释放资源
        if (null != audioRecord) {
            audioRecord.stop();
            audioRecord.release();
            audioRecord = null;
        }
    }
    private void startPlay() {
        final int minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_OUT_CONFIG, AUDIO_FORMAT);
        audioTrack = new AudioTrack(
                new AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                        .build(),
                new AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ)
                        .setEncoding(AUDIO_FORMAT)
                        .setChannelMask(CHANNEL_OUT_CONFIG)
                        .build(),
                minBufferSize,
                AudioTrack.MODE_STREAM,
                AudioManager.AUDIO_SESSION_ID_GENERATE);
        audioTrack.play();

        File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "audioDemo.pcm");
        try {
            fileInputStream = new FileInputStream(file);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        byte[] tempBuffer = new byte[minBufferSize];
                        while (fileInputStream.available() > 0) {
                            int readCount = fileInputStream.read(tempBuffer);
                            if (readCount == AudioTrack.ERROR_INVALID_OPERATION ||
                                    readCount == AudioTrack.ERROR_BAD_VALUE) {
                                continue;
                            }
                            if (readCount != 0 && readCount != -1) {
                                audioTrack.write(tempBuffer, 0, readCount);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private void stopPlay() {
        if (audioTrack != null) {
            audioTrack.stop();
            audioTrack.release();
        }
    }

参考

www.cnblogs.com/renhui/p/74… blog.51cto.com/ticktick/ca…