天天看点

Android音频基础与录制播放

作者:音视频流媒体技术

一、音频概览

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

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

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

总结来说:

  1. 音频录制与播放(Android相关类的使用,Framework调用原理)
  2. 音频处理(去噪、静音检测、回声消除、音效处理、功放/增强、混音分离)
  3. 音频的编解码和格式转换(MediaCodec、FFmpeg)
  4. 音频传输协议(SIP\A2DP、AVRCP等)

1、PCM

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

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

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

Android音频基础与录制播放

2、采样率

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

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

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

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

    22.050k Hz和24.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进行封装。

元数据

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

相关学习资料推荐,点击下方链接免费报名,先码住不迷路~】

音视频免费学习地址:https://xxetb.xet.tech/s/2cGd0

【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击788280672加群免费领取~

Android音频基础与录制播放

二、AudioRecord与AudioTrack使用

1. AudioRecord实现录音

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

getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)           

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

  • sampleRateInHz采样率,因为我们这里是录音,所以采用44.1kHz。可以根据需要选择其他采样率。
  • channelConfig声道数,这里选择单声道AudioFormat.CHANNEL_IN_MONO,而立体声为CHANNEL_IN_STEREO。其他声道可以根据参数结合配置。
Android音频基础与录制播放
  • audioFormat 编码位深,这里选择默认PCM16位编码,AudioFormat.ENCODING_PCM_16BIT。具体根据业务选择不同的编码位深。

接下来是创建AudioRecord实例:

//采样率 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实例进行录音,下面代码将录音内容保存到文件中。

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的创建一致。

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实例读取本地音频进行播放。

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

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

本文主要参考学习文章:

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

硬核音频系列

脉冲编码调制

原文 Android音频基础与录制播放 - 掘金

继续阅读