现在很多安卓手机都自带了录音机或者屏幕录制功能,但是系统自带的录音机是录制麦克风声音;而系统的屏幕录制又是录制的视频,仅需要系统声音的话,使用其他第三方剪辑软件把声音单独剪出来。如果你恰恰只需要录制手机本身发出的声音,那么本文应该对你有些帮助。
在安卓5.0的时候,系统就开放了视频录制接口:MediaProjection 和 MediaProjectionManager,到了安卓10.0的时候系统又提供了音频捕捉接口AudioPlaybackCapture,有了这个接口我们就可以实现对系统播放的音频进行录制了。
AndroidManifest配置
申请和声明必要的用户权限,注意高版本在kt或者java代码中动态申请。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
AndroidManifest.xml中配置允许音频播放捕获
<application
android:name=".App"
android:allowAudioPlaybackCapture="true"
...
tools:targetApi="q">
Activity中向用户申请录制授权
- 首先是向用户申请授权。
val mProjectionManager = this.getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
val screenCaptureIntent = mProjectionManager.createScreenCaptureIntent()
startActivityForResult(screenCaptureIntent, reqCode)
注意,此时弹出系统弹窗,该弹窗的样式是无法改变的,主要是为了保护用户隐私,防止钓鱼违规录音。
- 在用户同意授权之后,我们会在onActivityResult中收到回调。然后启动音频录制的service
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == reqCode && resultCode == Activity.RESULT_OK) {
val intent = Intent(this, RecordService::class.java)
intent.putExtra("resultData", data)
intent.putExtra("resultCode", resultCode)
startForegroundService(intent)
}
}
service中执行录音功能
- 定义通知栏
override fun onCreate() {
super.onCreate()
EventBus.getDefault().register(this)
initNotification()
}
...
...
...
/**
* 初始化通知栏
*/
private fun initNotification() {
val builder: Notification.Builder
val channelID = "MRecordService"
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel(channelID, "录音服务", NotificationManager.IMPORTANCE_HIGH)
channel.enableLights(true) //设置提示灯
channel.lightColor = Color.RED //设置提示灯颜色
channel.setShowBadge(true) //显示logo
channel.description = "MRecordService Notification" //设置描述
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC //设置锁屏可见 VISIBILITY_PUBLIC=可见
manager.createNotificationChannel(channel)
builder = Notification.Builder(this, channelID)
val notification = builder.setAutoCancel(false)
.setContentTitle("录音服务") //标题
.setContentText("录音服务正在运行...") //内容
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher) //设置小图标
.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))//设置大图标
.build()
startForeground(1, notification)
}
- 初始化录音机
...
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val currentResultCode = intent.getIntExtra("resultCode", 0)
val resultData = intent.getParcelableExtra<Intent>("resultData")
initAudioRecord(currentResultCode, resultData)
return super.onStartCommand(intent, flags, startId)
}
...
...
/**
* 初始化录音器
*/
private fun initAudioRecord(resultCode: Int, intent: Intent) {
minBufferSize = AudioRecord.getMinBufferSize(mSampleRateInHZ, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT)
val mediaProjectionManager = baseContext.getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
//设置应用程序录制系统音频的能力
val mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, intent)
val builder = AudioRecord.Builder()
builder.setAudioFormat(AudioFormat.Builder()
.setSampleRate(mSampleRateInHZ) //设置采样率(一般为可选的三个-> 8000Hz 、16000Hz、44100Hz)
.setChannelMask(AudioFormat.CHANNEL_IN_MONO) //音频通道的配置,可选的有-> AudioFormat.CHANNEL_IN_MONO 单声道,CHANNEL_IN_STEREO为双声道,立体声道,选择单声道就行
.setEncoding(AudioFormat.ENCODING_PCM_16BIT).build()) //音频数据的格式,可选的有-> AudioFormat.ENCODING_PCM_8BIT,AudioFormat.ENCODING_PCM_16BIT
.setBufferSizeInBytes(minBufferSize) //设置最小缓存区域
val config = AudioPlaybackCaptureConfiguration.Builder(mediaProjection)
.addMatchingUsage(AudioAttributes.USAGE_MEDIA) //设置捕获多媒体音频
.addMatchingUsage(AudioAttributes.USAGE_GAME) //设置捕获游戏音频
.build()
//将 AudioRecord 设置为录制其他应用播放的音频
builder.setAudioPlaybackCaptureConfig(config)
try {
if (ContextCompat.checkSelfPermission(this,Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
audioRecord = builder.build()
}
} catch (e: Exception) {
e.printStackTrace()
LogUtils.e("录音器错误", "录音器初始化失败")
}
//做完准备工作,就可以开始录音了
startRecord()
}
- 开始录音,保存录音
...
/**
* 开始录音
*/
private fun startRecord() {
if (isRecording) {
ToastUtils.showShort("已经在录音了")
return
}
ToastUtils.showShort("开始录音了")
isRecording = true
//承接音频数据的字节数组
val mAudioData = ByteArray(320)
//保存到本地录音文件名
val tmpName = System.currentTimeMillis().toString()
//新建文件,承接音频数据
val tmpFile = Utils.createFile("$tmpName.pcm")
//新建文件,后面会把音频数据转换为.wav格式,写入到该文件
val tmpOutFile = Utils.createFile("$tmpName.wav")
//开始录音
audioRecord?.startRecording()
Thread {
try {
val outputStream = FileOutputStream(tmpFile.absoluteFile)
while (isRecording) {
//循环从音频硬件读取音频数据录制到字节数组中
audioRecord?.read(mAudioData, 0, mAudioData.size)
//将字节数组写入到tmpFile文件
outputStream.write(mAudioData)
}
outputStream.close()
//将.pcm文件转换为.wav文件
Utils.pcmToWave(mSampleRateInHZ.toLong(), minBufferSize, tmpFile.absolutePath, tmpOutFile.absolutePath)
} catch (e: IOException) {
e.printStackTrace()
}
}.start()
}
- 停止录音
我们可以在activity中发出停止录音的消息,service接收到该消息之后,执行停止录音。我这里用的是EventBus
...
...
@Subscribe(threadMode = ThreadMode.POSTING)
fun OnEvent(event: String?) {
if (TextUtils.equals(event, "stop")) {
ToastUtils.showShort("停止录音了")
isRecording = false
audioRecord!!.stop()
}
}