音视频开发对于客户端来讲,是一个深水区,
而音视频开发中音频处理编辑
又是深水区里面最深的那一块了。逃离客户端开发
最卷的UI应用层
,
避免学完这样UI学那样UI,
学完Android学React Native,
学完React Native学Flutter,
学完Flutter 学Kotlin Multiplatform(KMP)Compose Multiplatform(CMP)
然后你还要学Html + Css +Js,还要学Vue, Uniapp
何不一心一意搞音视频开发呢
一、前言
今天给大家带来音视频中皮毛中的皮毛功能
:
如何拦截其他Android应用程序播放器的原始音频数据自定义保存下来?
什么意思?
有个词叫内录
,
先介绍外录
吧:
就是原始音频信号数据是怎么来的,是通过麦克风录制,传输到硬件设备,转化为数字信号,保存为文件比如:mp4,mp3等,画一个简单图如下:
通过上述图可以很清楚的知道:
外录
:其实就是录制麦克风采样的环境中的声音,然后转化为数字信号,
这里是真正的有麦克风采样捕获环境中声音的过程,捕获完交给硬件DSP芯片编码成音频数据文件,如aac,mp3等
内录
:其实没有真正录制采样的过程,只是相对外录命名的一个词语,它是怎么实现的过程呢?它是在上图中拦截了解码出能播放的数据,在播放该数据时,直接通过录音机,将该数据写入
(原始数字信号数据直接拿来往里面写,当作是录制过程,这个是没有录制采样的过程)
,所以这种方式操作保存出来的音频流是和原始音频文件流是一样的。如果音频录制配置的参数和原来的碰巧一样,基本上等于复制一份,其实大多数情况是一样的。即便你播放时候把外放声音全部关掉,结果是没有任何影响的。
这样做有什么意义,有哪些应用场景
-
1. 手机录屏,外加播放音乐,不录环境中任何声音的制作视频(直接麦克风录制
会有环境中噪音
,那样需要对音频进行降噪音处理
) -
2. 其他应用程序播放的音乐和视频,但是找不到它的原文件链接(找到源文件链接直接自己下载下来提取出里面音频数据就可以),可以这样内录出来拿到自己想要的音频数据
-
3. 和2中一样,现在绝大多数VIP音乐收费,但是看MV免费,但是手机上MV可能抓包抓不到地址,也或者自己不想抓包或者不会抓,可以把MV中音频内录取下来,又可能有些音乐播放器能免费听,但实际它对链接进行了安全加密,抓取破解难度大了点,但是有试用期时间期限,可以在这个期限内先把自己喜欢的歌曲内录下来。保存好MP3.
二、基础工具Api介绍
- 1. MediaProjection:是Android系统提供的一个API,用于屏幕捕捉和屏幕录制
- 2. AudioPlaybackCaptureConfiguration:允许应用捕获正在播放的音频数据。需要设置
MediaProjection
的权限,并且要在支持该特性的设备上运行 - 3. MediaCodec:是Android平台上用于音视频编解码的API,它允许开发者直接访问底层的编解码器,实现高效的音视频处理。MediaCodec支持硬件加速,可以利用设备的硬件资源来提高编解码的性能。其主要功能和特点包括:
3.1. 音视频编解码:MediaCodec可以对音频和视频进行编解码处理,支持常见的音视频格式如H.264、AAC、MP3等。
3.2. 硬件加速:MediaCodec支持硬件加速,可以利用设备的GPU或专用编解码芯片来提高编解码的效率和性能
3.3. 实时处理:适用于实时通信、直播等需要实时处理的场景。
3.4. 自定义处理:支持自定义的音视频处理,如滤镜、特效等操作 - 4. AudioRecord:是Android平台上用于录制音频的工具,它支持捕获原始的PCM音频数据。与MediaRecorder不同,AudioRecord更加底层,提供了更多的控制权,允许开发者自定义处理音频数据,如录音存储、音频分析和语音识别等。
4.1.主要作用:是从麦克风实时捕获音频。它支持多种音频源,如麦克风、电话线路和语音识别等。通过设置不同的参数,如采样率、声道配置和音频格式,开发者可以控制录音的质量和需求
4.2.主要参数:
audioSource:音频的来源,通常是麦克风(MediaRecorder.AudioSource.MIC)。 sampleRateInHz:采样率,常见的有16000Hz(电话音质)和44100Hz(CD音质)。 channelConfig:声道配置,可以是单声道(AudioFormat.CHANNEL_IN_MONO)或双声道(AudioFormat.CHANNEL_IN_STEREO)。
audioFormat:音频数据的编码格式,常见的有16位PCM(AudioFormat.ENCODING_PCM_16BIT)和8位PCM(AudioFormat.ENCODING_PCM_8BIT)。 bufferSizeInBytes:录音时使用的缓存大小,需要通过AudioRecord.getMinBufferSize()
计算得出 - 5. MediaMuxer: 用来封装编码后的视频流和音频流到mp4容器中,MediaMuxer从api18开始提供,可以封装编码后的视频流和音频流到视频文件中。目前MediaMuxer支持的文件输出格式包括MP4,webm和3gp。
三、代码实现过程
1. 配置录音权限,和文件读写权限,即配置:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application android:name=".App" android:allowAudioPlaybackCapture="true" tools:targetApi="q"/>
2. 申请录音权限,文件读写权限
fun checkPermission(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED && checkSelfPermission(
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED && checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO), 1)
}
return false
}
3.申请录屏权限:
val mediaProjectionManager = context.getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
val intent = mediaProjectionManager.createScreenCaptureIntent()
(context as Activity).startActivityForResult(intent, 1001)
4. 得到录屏权限后需要拿到 MediaProjection
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 1001 && resultCode == RESULT_OK) {
val mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
//需要存到外部变量使用
val mediaProjection: MediaProjection = mediaProjectionManager!!.getMediaProjection(resultCode, data!!)
}
}
5. 封装好内录类:WXAudioRecorder,传入MediaProjection
和要生成的音频文件audioOutputFile
,准备好MediaMuxer
和AudioPlaybackCaptureConfiguration
如下:
@RequiresApi(Build.VERSION_CODES.Q)
class WXAudioRecorder(val context: Context, val mediaProjection: MediaProjection, val audioOutputFile: File) {
private var mCodec: MediaCodec? = null
private var mAudioRecord: AudioRecord? = null
private val mMuxer by lazy {
MediaMuxer(audioOutputFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
}
private val playbackConfig by lazy {
AudioPlaybackCaptureConfiguration.Builder(mediaProjection).addMatchingUsage(AudioAttributes.USAGE_MEDIA).addMatchingUsage(AudioAttributes.USAGE_UNKNOWN).addMatchingUsage(AudioAttributes.USAGE_GAME).build();
}
}
6. 准备录制参数配置数据:
// 采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。
private const val SAMPLE_RATE_INHZ = 44100
// 声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。
private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO
// 返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT
// 音频采样位数
private const val AUDIO_BIT_RATE = 196000
private const val MIC_VOLUME_SCALE: Float = 1.4f
private const val TIMEOUT = 500L
7. 录音机监听系统播放背景音乐
//返回成功创建AudioRecord对象所需的最小缓冲区大小
val minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT) * 2
//音频数据编码方式
val format = AudioFormat.Builder().setEncoding(AUDIO_FORMAT).setSampleRate(SAMPLE_RATE_INHZ).setChannelMask(CHANNEL_CONFIG).build()
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
//使用前需要先申请号权限
return
}
//录音机和系统播放音频关联起来
mAudioRecord = AudioRecord.Builder().setAudioFormat(format).setAudioPlaybackCaptureConfig(playbackConfig).build()
8. 准备手机硬编码解码工具MediaCodec
,参数配置
//封装描述媒体数据格式的信息
val medFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, SAMPLE_RATE_INHZ, 1)
// 设置音频编码音质等级
medFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
// 设置音频编码比特率
medFormat.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE)
//音频的字节顺序。对于 PCM 编码的音频
medFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, AUDIO_FORMAT)
//实例化支持给定mime类型输出数据的首选编码器、MIMETYPE_AUDIO_AAC是一种压缩格式
mCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC).apply {
//配置参数
configure(medFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
}
9. 开始拦截捕获实现内录::需要在异步线程里面执行
fun start() {
jobAudioRecorder = CoroutineScope(Dispatchers.IO).launch {
mAudioRecord?.startRecording()
mCodec?.start()
if (mAudioRecord?.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
throw IllegalStateException("ScreenInternalAudioRecorder Audio recording failed to start");
}
flowAudio?.collect()//后面9,10,11都得在异步线程里面执行,我这里采用flow切换到线程里面实现,可以其他方式,只要去报在异步里面不影响UI主线程就行
}
}
10. 拿到播放的音频数据存入到byte数组中:
var buffer = ByteArray(minBufferSize)
while (true) {
var readBytes = mAudioRecord!!.read(buffer!!, 0, buffer.size)
//exit the loop when at end of stream
if (readBytes < 0) {
break
}
encode(buffer, readBytes)
}
11. 拿到数据后重新去编码成可生产音频文件的数据
//音频编码
private fun encode(buffer: ByteArray, readBytes: Int) {
var readBytes = readBytes
var offset = 0
while (readBytes > 0) {
var totalBytesRead = 0
val bufferIndex = mCodec!!.dequeueInputBuffer(TIMEOUT)
if (bufferIndex < 0) {
writeOutput()
return
}
val buff = mCodec!!.getInputBuffer(bufferIndex)
buff!!.clear()
val bufferSize = buff!!.capacity()
val bytesToRead = min(readBytes.toDouble(), bufferSize.toDouble()).toInt()
totalBytesRead += bytesToRead
readBytes -= bytesToRead
buff!!.put(buffer, offset, bytesToRead)
offset += bytesToRead
mCodec!!.queueInputBuffer(bufferIndex, 0, bytesToRead, mPresentationTime, 0)
mTotalBytes += totalBytesRead
mPresentationTime = 1000000L * (mTotalBytes / 2) / SAMPLE_RATE_INHZ
writeOutput()
}
}
12. 编码完成保存到音频文件
private fun writeOutput() {
while (true) {
val bufferInfo = MediaCodec.BufferInfo()
val bufferIndex = mCodec!!.dequeueOutputBuffer(bufferInfo, TIMEOUT)
if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
mTrackId = mMuxer.addTrack(mCodec!!.outputFormat)
mMuxer.start()
continue
}
if (bufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
break
}
if (mTrackId < 0) return
val buff = mCodec!!.getOutputBuffer(bufferIndex)
if (!((bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0 && bufferInfo.size != 0)) {
mMuxer.writeSampleData(mTrackId, buff!!, bufferInfo)
}
mCodec!!.releaseOutputBuffer(bufferIndex, false)
}
}
13. 停止内录结束:
private fun endStream() {
val bufferIndex = mCodec!!.dequeueInputBuffer(TIMEOUT)
mCodec!!.queueInputBuffer(bufferIndex, 0, 0, mPresentationTime, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
writeOutput()
}
四、总结
本文主要分享了:
- 如何拦截其他Android应用程序播放器的原始音频数据自定义保存下来?
- 内录的过程,它相对于外录的区别
- 内录相关API介绍以及代码实现过程