Android录制系列之音频录制

简介

  Android的多媒体框架包含并支持录制和解码多种多样的音视频格式。如果设备支持,你可以使用MediaRecorder来进行这些操作。( Android模拟器无法录制音频 )

MediaRecorder的使用

权限

  如果你想进行音频录制,你必须添加RECORDER_AUDIO的权限,这是一个 危险权限 ,如果你不知道如何请求权限,你可以查看之前的一篇文章 Android运行时权限的处理

1
<uses-permission android:name="android.permission.RECORD_AUDIO" />

配置MediaRecorder

  配置一个MediaRecorder你只需要简单的几步就可以了。

  1. 首先,我们得创建一个 MediaRecorder 的实例对象;

    1
    val mediaRecorder = MediaRecorder()
  2. 通过 setAudioSource() 方法设置音频源,音频源的取值你可以直接查看 MediaRecorder.AudioSource,一般情况下我们会选择 MIC 也就是麦克风作为音频源;

    1
    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)
  3. 通过 setOutputFormat() 方法设置输出格式,输出格式的取值你可以直接查看 MediaRecorder.OutputFormat

    1
    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
  4. 通过 setAudioEncoder() 方法设置音频解码方式,解码方式的取值你可以直接查看 MediaRecorder.AudioEncoder

    1
    mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
  5. 通过 setOutputFile() 方法设置保存文件的路径,

    1
    2
    val file = File(getExternalFilesDir(Environment.DIRECTORY_MOVIES), "audioRecorder.3gp")
    mediaRecorder.setOutputFile(file.absolutePath)
  6. 完成初始化配置后,你就可以调用 prepare()方法 ;然后调用 start() 你就可以开始录制了。

    1
    2
    mediaRecorder.prepare()
    mediaRecorder.start()

值得注意的

  大部分(包括DEFAULT)的音频源 都会对音频信号做处理 ,如果你想录制原始的音频,你可以选择MediaRecorder.AudioSource.UNPROCESSED(当然你也可以选择AudioRecorder这个更接近底层的类,后面会找时间写一篇关于AudioRecorder录制的文章)。 有些设备是不支持录制未处理的音频,你可以通过下面的这种方式来获取设备是否支持录制原始音频。

1
2
3
4
5
6
7
val audioManager: AudioManager? = getSystemService(Context.AUDIO_SERVICE) as? AudioManager
var isSupportedRawAudioInput = "false"
audioManager?.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
isSupportedRawAudioInput = getProperty("android.media.property.SUPPORT_AUDIO_SOURCE_UNPROCESSED")
}
}

  嗯~what,我相信你也发现了,这个方法是 API 17 才出现的。目前公司项目的最低支持是16,所以得找一种方式在 API 16 上也能够获取到这个属性。结合之前的一些经验,我最初的想法是先看看API16的AudioManager的源码,希望这个方法是本身存在的,只是被隐藏了,这样我就可以直接通过反射的方式去调用,但是事实不是如此,在API16中,AudioManager确实是没有getProperty()方法 ;于是,本着学习的态度,我翻看了更高版本的AudioManager的源码,发现它的调用链是这个样子的。

1
2
3
if (PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED.equals(key)) {
return String.valueOf(getContext().getResources().getBoolean(com.android.internal.R.bool.config_supportAudioSourceUnprocessed));
}

  仿佛抓到一颗救命稻草,于是我想我直接在代码里面获取这个属性不就好了,这里给你萌看一张图吧,上面代码是这个样子的,找不到com.android.interal.R。
002  然后,我去google上搜索了关于如何引用这个资源文件,首先,通过反射是肯定可以获取到的,但是google会有一个警告告诉你 通过反射的方式访问内部APIs可能会在某些设备上不支持 ,所以还是用下面这个安全的方式吧。

1
resources.getBoolean(Resources.getSystem().getIdentifier("config_supportAudioSourceUnprocessed", "bool", "android"))

  嗯哼,高兴的太早了,于是乎我就开启了API16的模拟器运行了起来,当走到这一行代码的时候,就出现了下面的结果,好吧,这个属性的判断似乎只能是API17才能用,所以还是用官方推荐的那种方式吧。
003
  如果不支持的情况下,你可以尝试使用 MediaRecorder.AudioSource.VOICE_RECOGNITION 。它不使用AGC(Auto Gain Control,自动增益控制,当信号源较强的时候,使其增益自动降低;当信号源较弱的时候,使其增益自动增高),并且不会做降噪处理。当然,即使设备不支持UNPROCESSED,你仍旧可以设置为这个,只是这样你就不知道音频信号有没有被处理了。

MediaRecorder的状态

  这里直接使用官方的状态图了,如下
MediaRecorder Status

事件监听

  MediaRecorder提供了两个监听事件的接口,一个是MediaRecorder.OnInfoListener,另一个是MediaRecorder.OnErrorListener

OnInfoListener

  可以用来监听MediaRecorder的一些状态,比如最大录制事件到了等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mediaRecorder.setOnInfoListener{ _,what,_->
when(what){
MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED->{
//这个枚举值对应mediaRecorder.setMaxDuration(),当设置的最大录制时间到了后,会回调这个
}
MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING->{
//这个枚举值对应setMaxFileSize(),当录制文件大小快接近最大值的时候会回调这个,这个是API26新增
}
MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED->{
//当录制文件大小达到最大文件大小的时候会回调这个
}
MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED->{
//这个方法对应的是setNextOutputFile(),当录制视频超过指定大小后保存到next文件中的时候就会回调这个值,这个是API26新增
}
}
}

OnErrorListener

  监听Error信息,

1
2
3
4
5
6
7
8
mediaRecorder.setOnErrorListener{_, what, _ ->
when(what){
MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN->{
}
MediaRecorder.MEDIA_ERROR_SERVER_DIED->{
}
}
}

举个栗子

  布局文件就不细说了,只有一个录制按钮和一个停止按钮。我们来看看Activity中核心的的代码。

请求权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
override fun onClick(v: View?) {
when (v?.id) {
R.id.startRecorderTv -> checkRecordAudioPermission()
R.id.stopRecorderTv -> stopRecorder()
}
}

private fun checkRecordAudioPermission() {
val hasRecordAudioPermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
when {
hasRecordAudioPermission == PackageManager.PERMISSION_GRANTED -> startRecorder()
ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO) -> AlertDialog.Builder(this)
.setCancelable(false)
.setMessage("请求音频录制权限,否则无法录制音频")
.setNegativeButton("取消") { _, _ -> }
.setPositiveButton("确定") { _, _ -> requestRecordAudioPermission() }
.show()
else -> requestRecordAudioPermission()
}
}

private fun requestRecordAudioPermission() {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), REQUEST_RECORD_AUDIO_PERMISSION)
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startRecorder()
}
}

开始录制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private fun startRecorder() {
if (!isRecording) {
mediaRecorder = MediaRecorder()
mediaRecorder?.apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
val file = File(getExternalFilesDir(Environment.DIRECTORY_MOVIES), AUDIO_RECORDER_FILE_NAME)
setOutputFile(file.absolutePath)
try {
prepare()
} catch (e: Exception) {
}
start()
isRecording = true
}
}
}

停止录制

1
2
3
4
5
6
7
8
9
10
private fun stopRecorder() {
if (isRecording) {
mediaRecorder?.apply{
stop()
release()
}
isRecording = false
mediaRecorder = null
}
}

项目地址

github地址

官方文档

  官网的Guide也是给出了MediaRecorder的详细使用,以及在 API 26 中新增的 MediaMuxer to record multiple channels

毒鸡汤

  将来的你一定会感谢现在拼命的自己~

本文标题:Android录制系列之音频录制

文章作者:严方雄

发布时间:2018-01-06

最后更新:2018-09-13

原始链接:http://yanfangxiong.com/2018/01/06/Android录制系列之音频录制/

0%