音视频开发2——使用 AudioRecord 和 AudioTrack API 完成音频 PCM 数据的采集和播放,并实现读写音频 wav 文件

本文详细介绍了Android平台上的音视频开发,包括使用AudioRecord和MediaRecorder进行录音,以及AudioTrack实现音频播放。通过实例代码展示了如何录制PCM数据并封装成WAV格式,以及使用静态和流式模式播放音频文件。同时,讨论了不同音频格式的优缺点,如PCM、AAC和AMR。
摘要由CSDN通过智能技术生成

音视频开发路线:

Android 音视频开发入门指南_Jhuster的专栏的技术博客_51CTO博客_android 音视频开发入门

demo地址:

https://github.com/wygsqsj/videoPath

 参考资料:

api介绍:在 Android 平台使用 AudioRecord 和 AudioTrack API 完成音频 PCM 数据的采集和播放,并实现读写音频 wav 文件 - 简书

wav格式的存储解析: Android音频开发(4):如何存储和解析wav文件_u010880786的博客-CSDN博客

  • Android提供了两个API用于实现录音功能:android.media.AudioRecord、android.media.MediaRecorder。

1、AudioRecord

主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫、语音)

优点:语音的实时处理,可以用代码实现各种音频的封装

缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩

示例:

使用AudioRecord类录音,并实现WAV格式封装。录音20s,输出的音频文件大概为3.5M左右(已写测试代码)

2、MediaRecorder

已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有.aac(API = 16) .amr .3gp

优点:大部分以及集成,直接调用相关接口即可,代码量小

缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件

示例:

使用MediaRecorder类录音,输出amr格式文件。录音20s,输出的音频文件大概为33K(已写测试代码)

  • AudioTrack用于播放音频

两种模式static 或 streaming。
在Streaming模式下,应用程序使用其中一种write()方法将连续的数据流写入AudioTrack 。当数据从Java层传输到native层并排队等待播放时,它们会阻塞并返回。在音频文件较大或格式问题不能加载到内存时流模式非常有用

在处理能够装入内存的短音时,应选择静态模式

WAV格式:录音质量高,但是压缩率小,文件大

PCM格式:最原始的音频数据,未经过压缩的

AAC格式:相对于mp3,AAC格式的音质更佳,文件更小;有损压缩;一般苹果或者Android SDK4.1.2(API 16)及以上版本支持播放

AMR格式:压缩比比较大,但相对其他的压缩格式质量比较差,多用于人声,通话录音

至于常用的mp3格式,使用MediaRecorder没有该视频格式输出。一些人的做法是使用AudioRecord录音,然后编码成wav格式,再转换成mp3格式

package com.wish.videopath.demo2

import android.Manifest
import android.content.pm.PackageManager
import android.media.*
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.wish.videopath.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.*
import java.lang.Exception


/**
 * 类名称:Demo2Activity
 * 类描述:
 *
 * 创建时间:2021/10/25
 */
class Demo2Activity : AppCompatActivity() {

    private lateinit var mRecord: Button
    private lateinit var mPlayView: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_demo2)
        mRecord = findViewById<Button>(R.id.record)
        mPlayView = findViewById<Button>(R.id.play)
        mRecord.setOnClickListener {
            //当前正在录音,停止录音
            if (isRecording) {
                isRecording = false
                mRecord.text = "录音完成"
                return@setOnClickListener
            }

            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                record()
                return@setOnClickListener
            }

            if (ActivityCompat.checkSelfPermission(
                    this,
                    Manifest.permission.RECORD_AUDIO
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                ActivityCompat.requestPermissions(
                    this,
                    arrayOf(
                        Manifest.permission.RECORD_AUDIO,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE
                    ),
                    666
                )
            } else {
                record()
            }
        }
        mPlayView.setOnClickListener { play() }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == 666 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
            record()
        } else {
            Toast.makeText(this, "请授予录音权限权限", Toast.LENGTH_SHORT).show()
        }
    }


    /**
     * 采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。
     */
    private final val SAMPLE_RATE_INHZ = 44100

    /**
     * 声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。
     */
    private val CHANNEL_CONFIG: Int = AudioFormat.CHANNEL_IN_MONO

    /**
     * 返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
     */
    private val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT

    //缓冲区
    private val minBufferSize =
        AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT)

    private val PCM_NAME = "demo2.pcm"

    private var isRecording = false

    private lateinit var audioRecord: AudioRecord

    private fun record() {
        audioRecord = AudioRecord(
            MediaRecorder.AudioSource.MIC,
            SAMPLE_RATE_INHZ,
            CHANNEL_CONFIG,
            AUDIO_FORMAT,
            minBufferSize
        )

        val data = ByteArray(minBufferSize)
        val dirFile = getExternalFilesDir(Environment.DIRECTORY_MUSIC)
        dirFile?.mkdirs()

        val file = File(dirFile, PCM_NAME)

        audioRecord.startRecording()
        isRecording = true
        mRecord.text = "正在录音"
        Thread {
            val os = FileOutputStream(file)
            try {
                while (isRecording) {
                    val read = audioRecord.read(data, 0, minBufferSize)
                    //没有错误就写入文件
                    if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                        os.write(data)
                    }
                }
                os.close()
            } catch (e: IOException) {
                runOnUiThread {
                    Log.i("音视频", "录音异常:" + e.toString())
                    mRecord.text = "录音异常"
                    Toast.makeText(this, "录音异常", Toast.LENGTH_SHORT).show()
                }
            } finally {
                audioRecord.stop()
                os.close()
            }
        }.start()
    }


    //播放音频
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private fun play() {
//        playOfSteam()
        playOfStatic()
    }


    private lateinit var audioTrack: AudioTrack

    /**
     * static模式
     * 先一次性读取数据到内存中,再进行播放,读取时间会比较长
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private fun playOfStatic() {
        GlobalScope.launch(Dispatchers.IO) {//开启协程运行在IO线程
            var input: InputStream? = null
            try {
                val audioFile = File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), PCM_NAME)
                input = FileInputStream(audioFile)
                val out = ByteArrayOutputStream()
                var b: Int
                var audioData: ByteArray? = null
                while (input.read().also { b = it } != -1) {
                    out.write(b)
                    audioData = out.toByteArray()
                }
                //开启协程运行在主线程
                GlobalScope.launch(Dispatchers.Main) {
                    startStatic(audioData)
                }
            } catch (e: Exception) {
                GlobalScope.launch(Dispatchers.Main) {
                    Log.i("音视频", "播放异常:$e")
                    mPlayView.text = "播放异常"
                    Toast.makeText(this@Demo2Activity, "录音异常", Toast.LENGTH_SHORT).show()
                }
            } finally {
                input?.close()
            }
        }


    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private fun startStatic(audioData: ByteArray?) {
        audioTrack = AudioTrack(
            AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build(),
            AudioFormat.Builder()
                .setEncoding(AUDIO_FORMAT)
                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                .build(),
            audioData!!.size,
            AudioTrack.MODE_STATIC,
            AudioManager.AUDIO_SESSION_ID_GENERATE
        )
        audioTrack.write(audioData, 0, audioData.size)
        audioTrack.play()
    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private fun playOfSteam() {
        val channelConfig = AudioFormat.CHANNEL_OUT_MONO
        val minBufferSize =
            AudioTrack.getMinBufferSize(SAMPLE_RATE_INHZ, channelConfig, AUDIO_FORMAT)

        val audioTrack = AudioTrack(
            AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build(),
            AudioFormat.Builder()
                .setEncoding(AUDIO_FORMAT)
                .setChannelMask(channelConfig)
                .build(),
            minBufferSize,
            AudioTrack.MODE_STREAM,
            AudioManager.AUDIO_SESSION_ID_GENERATE
        )
        //开始播放
        audioTrack.play()

        val audioFile = File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), PCM_NAME)

        try {
            val fileInputStream = FileInputStream(audioFile)
            Thread {
                try {
                    val tempBuffer = ByteArray(minBufferSize)
                    while (fileInputStream.available() > 0) {
                        val readCount: Int = 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 (e: IOException) {
                    e.printStackTrace()
                }
            }.start()
        } catch (e: IOException) {
            e.printStackTrace()
            runOnUiThread {
                Log.i("音视频", "录音播放异常:" + e.toString())
                Toast.makeText(this, "录音播放异常", Toast.LENGTH_SHORT).show()
            }
        }

    }


}

参考链接http://www.cnblogs.com/Amandaliu/archive/2013/02/04/2891604.html 在链接内容基础上修改了amr编码格式为aac编码格式 Android提供了两个API用于实现录音功能:android.media.AudioRecordandroid.media.MediaRecorder。 网上有很多谈论这两个类的资料。现在大致总结下: 1、AudioRecord 主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫、语音) 优点:语音的实时处理,可以用代码实现各种音频的封装 缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放播放的,所以必须先写代码实现数据编码以及压缩 示例: 使用AudioRecord类录音,并实现WAV格式封装。录音20s,输出的音频文件大概为3.5M左右(已写测试代码) 2、MediaRecorder 已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有.aac(API = 16) .amr .3gp 优点:大部分以及集成,直接调用相关接口即可,代码量小 缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件 示例: 使用MediaRecorder类录音,输出amr格式文件。录音20s,输出的音频文件大概为33K(已写测试代码) 3、音频格式比较 WAV格式:录音质量高,但是压缩率小,文件大 AAC格式:相对于mp3,AAC格式的音质更佳,文件更小;有损压缩;一般苹果或者Android SDK4.1.2(API 16)及以上版本支持播放 AMR格式:压缩比比较大,但相对其他的压缩格式质量比较差,多用于人声,通话录音 至于常用的mp3格式,使用MediaRecorder没有该视频格式输出。一些人的做法是使用AudioRecord录音,然后编码成wav格式,再转换成mp3格式 再贴上一些测试工程。 功能描述: 1、点击“录音WAV文件”,开始录音。录音完成后,生成文件/sdcard/FinalAudio.wav 2、点击“录音AMR文件”,开始录音。录音完成后,生成文件/sdcard/FinalAudio.amr 3、点击“停止录音”,停止录音,并显示录音输出文件以及该文件大小。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值