[Android] 通过 AudioRecord 和 AudioTrack 对音频 PCM 进行采集及播放

AudioRecord

AudioRecord 是 Android 提供的用于获取来自 Platform 硬件的音频资源,它允许访问原始的 PCM 音频流,使用 AudioRecord 仅需要构造对象并传入参数即可。我们需要知道的是 AudioRecord 并没有对音频进行压缩操作。

AudioRecord 的构造方法为:

AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)

audioSource:音频源,已经在 MediaRecorder.AudioSource 定义,具体可见MediaRecorder.AudioSource

sampleRateInHz:采样率,现在能够保证在所有设备上使用的采样率是44100Hz,其他采样率需要看硬件是否支持。

channelConfig:声道数,有 AudioFormat.CHANNEL_IN_MONO 和 AudioFormat.CHANNEL_IN_STEREO,其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。

audioFormat:返回的音频数据的格式。包括 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT,和 ENCODING_PCM_FLOAT。

bufferSizeInBytes:缓冲区大小,可以通过 AudioRecord.getMinBufferSize(SAMPLE_RATE_IN_HZ, CHANNEL_CONFIG, AUDIO_FORMAT) 来获取。

我们可以通过以下步骤使用 AudioRecord:

  1. 实例化 AudioRecord,传入音频源、采样率、声道数、返回的音频数据格式、缓冲区大小;
  2. 打开数据流,开始录音,将数据写入到流中的 File 中;
  3. 关闭数据流;
  4. 停止录音。

代码示例:

private void startRecord() {
        final int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_IN_HZ, CHANNEL_CONFIG, AUDIO_FORMAT);
        mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_IN_HZ, CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize);
        final byte data[] = new byte[minBufferSize];
        final File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");
        if (!file.mkdirs()) {
            Log.e(TAG, "Directory not created");
        }
        if (file.exists()) {
            file.delete();
        }

        mAudioRecord.startRecording();
        mIsRecording = true;
        new Thread(new Runnable() {
            @Override
            public void run() {
                FileOutputStream fos = null;
                try {
                    fos = new FileOutputStream(file);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
                if (fos != null) {
                    while (mIsRecording) {
                        int read = mAudioRecord.read(data, 0, data.length);
                        if (read != AudioRecord.ERROR_INVALID_OPERATION) {
                            try {
                                fos.write(data);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

 

AudioTrack

AudioTrack 是 Android 提供的用于播放 PCM 音频源的一个类。它可以通过 write() 方法将数据源发送到 AudioTrack 并播放。

在 API 26 之后 Android 建议使用 AudioTrack.Builder 进行构造,官方提供的示例代码如下:

 AudioTrack player = new AudioTrack.Builder()
         .setAudioAttributes(new AudioAttributes.Builder()
                  .setUsage(AudioAttributes.USAGE_ALARM)
                  .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                  .build())
         .setAudioFormat(new AudioFormat.Builder()
                 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                 .setSampleRate(44100)
                 .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
                 .build())
         .setBufferSizeInBytes(minBuffSize)
         .build();
 

其中 setAudioAttributes() 是用来设置音频属性的,如果不设置将默认为 AudioAttributes.USAGE_MEDIA,上面代码中将其设为了闹钟。setAudioFormat() 是用来设置音频的格式的,如果不设置将默认通道自动为 AudioFormat.CHANNEL_OUT_STEREO,默认编码为 AudioFormat.ENCODING_PCM_16BIT,默认采样率通过 AudioTrack.getSampleRate() 获取,同时上面的代码设置了 BufferSizeInBytes,那么将会以 AudioTrack.MODE_STREAM 模式播放,那个 BufferSizeInBytes 可以通过 AudioTrack.getMinBufferSize 获取。

由于 public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) 已经被 Deprecated,我用到了以下构造方法构造,参数与 Builder 中的那些相似:

public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId)

上面提到了 AudioTrack.MODE_STREAM,在 AudioTrack 中有两种模式:

MODE_STREAM:在这种模式下,通过多次 write() 把音频数据写到 AudioTrack 中。这种工作方式每次都需要把数据从用户提供的 Buffer 中拷贝到 AudioTrack 内部的 Buffer 中,这在一定程度上会使引入延时。

MODE_STATIC:这种模式下,在 play() 之前只需要把所有数据通过一次 write() 调用传递到 AudioTrack 中的内部缓冲区,后续就不必再传递数据了。这种模式适用于像铃声这种内存占用量较小,延时要求较高的文件。一次 write() 的数据不能太多,否则系统无法分配足够的内存来存储全部数据。

参考来自:https://www.cnblogs.com/renhui/p/7463287.html

我们可以通过以下步骤使用 AudioTrack:

  1. 构造 AudioTrack,传入必要的参数;
  2. AudioTrack 开始播放,调用 play();
  3. 将刚刚 AudioRecord 保存的 File 读入流,然后 AudioTrack 将来自其的流写入自身;
  4. 结束播放。

参考代码:

private void play() {
        int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
        final int minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_IN_HZ, channelConfig, AUDIO_FORMAT);
        mAudioTrack = new AudioTrack(
                new AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build(),
                new AudioFormat.Builder()
                        .setSampleRate(SAMPLE_RATE_IN_HZ)
                        .setEncoding(AUDIO_FORMAT)
                        .setChannelMask(channelConfig)
                        .build(),
                minBufferSize,
                AudioTrack.MODE_STREAM,
                AudioManager.AUDIO_SESSION_ID_GENERATE);
//        mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE_IN_HZ, AUDIO_FORMAT, minBufferSize, AudioTrack.MODE_STREAM);
        mAudioTrack.play();


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

 

参考资料

https://developer.android.com/reference/android/media/AudioRecord

https://developer.android.com/reference/android/media/AudioTrack

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注