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):基础知识

硬核音频系列

脉冲编码调制

相关推荐
落落落sss1 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
消失的旧时光-19433 小时前
kotlin的密封类
android·开发语言·kotlin
服装学院的IT男5 小时前
【Android 13源码分析】WindowContainer窗口层级-4-Layer树
android
CCTV果冻爽6 小时前
Android 源码集成可卸载 APP
android
码农明明6 小时前
Android源码分析:从源头分析View事件的传递
android·操作系统·源码阅读
秋月霜风7 小时前
mariadb主从配置步骤
android·adb·mariadb
Python私教8 小时前
Python ORM 框架 SQLModel 快速入门教程
android·java·python
编程乐学9 小时前
基于Android Studio 蜜雪冰城(奶茶饮品点餐)—原创
android·gitee·android studio·大作业·安卓课设·奶茶点餐
problc10 小时前
Android中的引用类型:Weak Reference, Soft Reference, Phantom Reference 和 WeakHashMap
android
IH_LZH10 小时前
Broadcast:Android中实现组件及进程间通信
android·java·android studio·broadcast