Android音频基础与录制播放

一、音频概览

声音是振动产生的能量(声波),通过介质传播并能被人或动物听觉器官所感知的波动现象。例如人类说话时通过声带振动产生声波,通过空气传播后到达其他人耳朵。

要采集声音,就需要通过麦克风对声音进行录制。录音就是将声音转为模拟信号或机械记录的过程。而数字录音是进一步将模拟信号经由ADC(模拟数字转换器)将 类比取样成数字记录到存储设备。

Android开发音频,就是利用AudioRecoderMediaRecoder录音,通过AudioTrackMediaPlayer播放声音,而这一过程会涉及到很多知识点,例如录制声音过程的采样率、位深、编解码MediaCodecFFmpeg等等。

总结来说:

  1. 音频录制与播放(Android相关类的使用,Framework调用原理)

  2. 音频处理(去噪、静音检测、回声消除、音效处理、功放/增强、混音分离)

  3. 音频的编解码和格式转换(MediaCodecFFmpeg

  4. 音频传输协议(SIP\A2DPAVRCP等)

1、PCM

PCM全名叫脉冲编码调制,是一种将模拟信号数字化的方法。PCM将信号的强度依照同样的间距分成数段,然后使用独特的数字记号(二进制)量化。这一过程通常由ADC来实现。作为Android程序员,更多是调用AudioTrack录制音频后,保存为PCM编码格式,也可以再进一步编码,存储成其它格式。

例如下图(正常应该是正弦波):

用绿色圆圈来表示连续的模拟信号,而红色圆点则表示数字信号。PCM过程就是在模拟信号上进行采样和收集,形成断断续续的采样点,也就是数字信号。在X轴方向上,可以理解为对音频的量化是时间,体现在采样率上。在Y轴方向,可以理解为量化是数量,体现为位深。

2、采样率

采样率表示在1秒内对声音的模拟信号采样的次数,假设上面是声音在1秒内的模拟信号,那么采样就只有2次,以频率作为计量单位,所以这里采样率2Hz。由此 ,可以看出采样率越高,采样得到的数字信号数据就越多,声音在播放过程就会越真实越接近原声,但这也占用很大的数据存储。所以需要根据人耳能识别频率的范围和具体的使用场景,将采样率控制在合适的范围内。常用的音频采样率为44.1k Hz48k Hz

人耳能识别的频率在20~20kHz,根据奈奎斯特·香农采样定理,采样后的音频要还原成人耳可以识别声音,采样率需要为人耳可以识别频率的2倍,而人耳上限是20KHz,2倍就是40kHz。所以常用的音频采样率为44.1k Hz48k Hz。其他常用采样频率:

  • 8k Hz - 电话所用采样率, 对于人的说话已经足够

  • 11.025k Hz - AM调幅广播所用采样率

  • 22.050k Hz24.000k Hz - FM调频广播所用采样率

  • 32k Hz - miniDV 数码视频 camcorder、DAT (LP mode)所用采样率

  • 44.1k Hz - 音频 CD, 也常用于 MPEG-1 音频(VCD, SVCD, MP3)所用采样率

  • 47.25k Hz - 商用 PCM 录音机所用采样率

  • 48k Hz - miniDV、数字电视、DVD、DAT、电影和专业音频所用的数字声音所用采样率

  • 50k Hz - 商用数字录音机所用采样率

  • 96k 或者 192k Hz - DVD-Audio、一些 LPCM DVD 音轨、BD-ROM(蓝光盘)音轨、和 HD-DVD (高清晰度 DVD)音轨所用所用采样率

3、位深

采样率可以理解为在时间范畴上对模拟信号的量化,如上面X轴上采样数,而位深表示数量上对模拟信号的量化,如上面Y轴的值。例如Android上常用的位深为8位,表示某个采样点上的值,可以用8位进行表示,共有256个值的可能。而声音数据的保存也就是保存每个声音的位数。

4、声道数

比较常见的概念,例如单声道(mono),立体声(stereo),5.1声道,7.1声道等等。多声道以为在采样时候需要同时采样每个声道,这样就会增加数据量。

5、数据大小

通过对采样率、位深、声道数的理解,可以得到下面公式:

  • 数据大小=采样率x声道数x(位深÷8)x时长,单位为Byte。
  • 采样数 = 采样率 × 音频时长(s)× 声道数
  • 音频时长= 采样数 ÷ 采样率 ÷ 声道数,单位为s。

6、编码格式

在存储和传输过程,通常不使用PCM格式,虽然无损以及可以直接被声卡和DAC解析,但存储大小过大,会造成本地和网络资源的浪费。所以通常会进一步编码,进行压缩和添加额外的信息,这就衍生出其他编码格式,如MP3、AAC、FLAC等。

经过编码后的音频数据,一般包含三种信息:编码格式、容器格式、元数据。

编码格式

编码格式分为有损编码和无损编码格式,PCM格式是无损的,而MP3则是有损的。编码后的音频数据不再表示波形采样值,不能直接通过硬件播放,需要通过编解码器解码后才能正常播放。

容器格式

编码后的音频需要和元数据需要一起进行容器封装,才形成最终的音频文件。容器格式一般和文件名称后缀保持一致。例如.mp3、.wav。编码格式与容器格式是相互独立的,也就说,MP3编码的音频是可以用WAV进行封装。

元数据

在容器中用于描述音频数据,辅助编解码,如采样率、长度等。

二、AudioRecord与AudioTrack使用

1. AudioRecord实现录音

在创建AudioRecord实例前,我们需要通过getMinBufferSize函数来获取当前AudioRecord可以使用的最小缓冲区大小。

kotlin 复制代码
getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)

其中sampleRateInHz为采样率,channelConfig为声道,audioFormat位深。

  • sampleRateInHz采样率,因为我们这里是录音,所以采用44.1kHz。可以根据需要选择其他采样率。

  • channelConfig声道数,这里选择单声道AudioFormat.CHANNEL_IN_MONO,而立体声为CHANNEL_IN_STEREO。其他声道可以根据参数结合配置。

  • audioFormat 编码位深,这里选择默认PCM16位编码,AudioFormat.ENCODING_PCM_16BIT。具体根据业务选择不同的编码位深。

接下来是创建AudioRecord实例:

java 复制代码
//采样率 44.1kHz
private val sampleRate = 44100

//声道 单声道
private val channel = AudioFormat.CHANNEL_IN_MONO

//位深
private val pcmBit = AudioFormat.ENCODING_PCM_16BIT

//获取最小缓冲区大小
bufferSize = AudioRecord.getMinBufferSize(
    sampleRate, channel, pcmBit
)

//AudioRecord实例
audioRecord = AudioRecord(
    MediaRecorder.AudioSource.MIC, //音频来源,麦克风
    sampleRate,
    channel,
    pcmBit,
    bufferSize
)

这样就可以通过AudioRecord实例进行录音,下面代码将录音内容保存到文件中。

kotlin 复制代码
private fun startRecording() {
    if (isRecording) return
    isRecording = true
  	//开始录音
    audioRecord.startRecording()
    GlobalScope.launch(Dispatchers.IO) {
        val buffer = ByteArray(bufferSize)
        val file = application.externalCacheDir
        val saveFile = File(file, "audio_${System.currentTimeMillis()}.pcm")
        val ous = FileOutputStream(saveFile)
        while (isRecording) {
            //音频数据写入缓冲区buffer
            val result = audioRecord.read(buffer, 0, bufferSize)
            //将缓冲区buffer数据写入本地文件
            ous.write(buffer)
        }
        isRecording = false
        ous.close()
        audioRecord.stop()
    }
}

2. AudioTrack实现音频播放

通过AudioRecord录制到的音频缓存在本地,我们通过AudioTrack将本地音频文件读取出来进行播放。

创建AudioTrack实例,大多数参数和AudioRecord的创建一致。

kotlin 复制代码
audioTrack = AudioTrack.Builder()
    .setAudioAttributes(
        AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .build()
    )
    .setAudioFormat(
        AudioFormat.Builder()
            .setEncoding(pcmBit)
            .setSampleRate(sampleRate)
            .setChannelMask(channel)
            .build()
    )
    .setBufferSizeInBytes(bufferSize)
    .build()

其中参数channel要改成AudioFormat.CHANNEL_OUT_MONO,其他可以保持一致。然后利用AudioTrack实例读取本地音频进行播放。

kotlin 复制代码
fun startPlay(path: String) {
    if (isPlaying) return
    val sourceFile = File(externalCacheDir,path)
    if (sourceFile.exists()) {
        isPlaying = true
         GlobalScope.launch(Dispatchers.IO) {
           //开始播放音频
           audioTrack!!.play()
           val fileInputStream = FileInputStream(sourceFile)
           val buffer = ByteArray(bufferSize)
            while (isPlaying) {
            	//将音频文件读取缓冲区buffer
                val size = fileInputStream.read(buffer, 0, bufferSize)
                if (size <= 0) {
                    isPlaying = false
                    continue
                }
                //将缓冲区buffer写入audioTrack进行播放
                audioTrack.write(buffer, 0, bufferSize)
            }
            audioTrack.stop()
        }
    } else {
        showToast("当前文件不存在:${path}")
    }
}

通过代码可以看到,音频通过AudioRecord录制和AudioTrack播放是反着来的。同时通过AudioRecordAudioTrack可以看出,作为Android开发者,更多是调用顶层API,配置相关参数,然后交给底层进行录制和播放。所以要熟悉相关音频概念,以便更好的配置参数。

Github Demo路径,包名: com.xxm.mediacodecdemo.audio

本文主要参考学习文章:

Android音频开发(1):基础知识

硬核音频系列

脉冲编码调制

相关推荐
大白要努力!16 分钟前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟1 小时前
Android音频采集
android·音视频
小白也想学C2 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程2 小时前
初级数据结构——树
android·java·数据结构
闲暇部落5 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX7 小时前
Android 分区相关介绍
android
大白要努力!8 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee8 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood8 小时前
Perfetto学习大全
android·性能优化·perfetto
Dnelic-11 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记