Android音视频系列(三):使用AudioRecord录制PCM音频和播放

前言

之前我们使用了MediaRecorder录制了音频和视频,虽然API使用简便,但是欠缺灵活,例如直播中的混音,变声等等,有些我们需要边录制边处理,MediaRecorder已经满足不了这些更高的需求,这个时候就需要使用AudioRecord。

正文

使用AudioRecord录制的是pcm原始音频,具体的概念这里就不多说了,如果你需要MP3,3gp可以自行转换。也就是说录制之后的文件基本上不能使用市面上的播放器直接播放,在安全性有一些小小的提升。

创建AudioRecord

首先了解一下AudioRecord的常用构造方法:

public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes)
audioSource :录音的来源
sampleRateInHz:采样率
channelConfig:配置的录音的频道,常用的有立体声,左声道,右声道
audioFormat:录音的编码格式,今天我们使用ENCODING_PCM_8BIT和ENCODING_PCM_16BIT
bufferSizeInBytes:每个数据的最小的大小,用于保存和读取

其中有两个参数:audioFormat和bufferSizeInBytes需要再说几句:

audioFormat:
ENCODING_PCM_8BIT:每一个采样使用8位保存,也就是Byte
ENCODING_PCM_16BIT:每一个采样使用16位保存,也就是short
所以这两种编码的格式,保存和读取会有区别。之后的案例会有说明。
·
bufferSizeInBytes:每一个采样的大小,AudioRecord提供了方法来计算
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)

AudioRecord录制

已经创建好了AudioRecord,接下来可以开始录制了,千万不要忘记权限的申请。

inner class RecordTask : AsyncTask() {
        ...

        override fun doInBackground(vararg params: Unit) {
            // 开始录音
            audioRecord.startRecording()
            // 通过io流,把录制的音频内容保存到文件中
            val dataInputStream = DataOutputStream(BufferedOutputStream(FileOutputStream(filePath)))
            // 16位需要使用short来保存
            if (ENCODER == AudioFormat.ENCODING_PCM_16BIT) {
                saveBy16Bit(dataInputStream)
            }
            // 8位使用byte
            else if (ENCODER == AudioFormat.ENCODING_PCM_8BIT) {
                saveBy8Bit(dataInputStream)
            }
            dataInputStream.flush()
            dataInputStream.close()
        }
        
        /**
        * 保存16位的数据需要使用short 
        */
        private fun saveBy16Bit(dataInputStream: DataOutputStream) {
            val byteArray = ShortArray(getMinBufferSize())
            while (isRecording) {
                val result = audioRecord.read(byteArray, 0, byteArray.size)
                for (i in 0 until result) {
                    dataInputStream.writeShort(byteArray[i].toInt())
                }
            }
        }

        /**
        * 保存8位的数据需要使用short  
        */
        private fun saveBy8Bit(dataInputStream: DataOutputStream) {
            val byteArray = ByteArray(getMinBufferSize())
            while (isRecording) {
                audioRecord.read(byteArray, 0, byteArray.size)
                dataInputStream.write(
                    byteArray,
                    0,
                    byteArray.size
                )
            }
        }

        fun stop() {
            audioRecord.stop()
            audioRecord.release()
        }
    }

上面是非常简单的IO读写操作,之前也提到保存16位和8位是有区别的,如果强行用8位保存16位的数据,肯定是要出问题的。

播放PCM

再刚才的录制中,我们没有做格式转换,使用播放器肯定播放不了,所以需要我们自己完成播放PCM的功能,核心肯定是通过IO流再读出来,不过我们需要使用AudioTrack。

public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId)
AudioAttributes:音轨相关的属性
AudioFormat:录制的音频的格式,与录制时相同
bufferSizeInBytes:每一个采样的最小大小,与录制时相同
mode:播放的模式。我们使用的时流MODE_STREAM
sessionId:此次播放的id,可以使用 AudioManager.AUDIO_SESSION_ID_GENERATE和 AudioManager.generateAudioSessionId()

 inner class PlayTaks : AsyncTask() {

        override fun doInBackground(vararg params: Unit?) {
            // 创建音轨
            val audioTrack = AudioTrack(
                AudioAttributes.Builder()
                    .setLegacyStreamType(AudioManager.STREAM_MUSIC)
                    .build(),
                AudioFormat.Builder()
                    // 这里会和录制的时候不一样,录制时时CHANNEL_IN_MONO,这里时CHANNEL_OUT_MONO
                    .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                    .setEncoding(ENCODER)
                    .setSampleRate(11025)
                    .build(),
                getMinBufferSize(),
                AudioTrack.MODE_STREAM,
                AudioManager.AUDIO_SESSION_ID_GENERATE
            )

            audioTrack.play()

            val dataInputStream = DataInputStream(BufferedInputStream(FileInputStream(filePath)))

            // 读取数据也是一样,16位要读取short
            if (ENCODER == AudioFormat.ENCODING_PCM_16BIT) {
                readBy16Bit(dataInputStream, audioTrack)
            }
            // 8位可以直接读取byte
            else if (ENCODER == AudioFormat.ENCODING_PCM_8BIT) {
                readBy8Bit(dataInputStream, audioTrack)
            }
            // 播放结束,释放资源
            dataInputStream.close()
            audioTrack.stop()
            audioTrack.release()
        }
        
        /**
        * 读取8位数据
        */
        private fun readBy8Bit(dataInputStream: DataInputStream, audioTrack: AudioTrack) {
            val byteArray = ByteArray(getMinBufferSize())
            while (dataInputStream.available() > 0) {
                dataInputStream.read(byteArray, 0, byteArray.size)
                audioTrack.write(byteArray, 0, byteArray.size)
            }
        }

        /**
        * 读取16位数据
        */
        private fun readBy16Bit(dataInputStream: DataInputStream, audioTrack: AudioTrack) {
            val byteArray = ShortArray(getMinBufferSize())
            while (dataInputStream.available() > 0) {
                var i = 0
                while (i < byteArray.size) {
                    byteArray[i] = dataInputStream.readShort()
                    i++
                }
                audioTrack.write(byteArray, 0, byteArray.size)
            }
        }

    }

总结

今天我们了解使用AudioRecord录制音频和使用AudioTrack播放音频,下一篇我们了解一下MediaCodec。

Github下载地址

你可能感兴趣的:(Android音视频系列(三):使用AudioRecord录制PCM音频和播放)