Android 多媒体,音视频领域,虽然还是在应用层开发领域,但是要熟练掌握它,并不容易,因为它不仅仅是需要掌握好涉及到的相关api就能完全理解了,需要理解数字音频知识,数字图像视频知识,还需要对音视频的编解码规则掌握好才能算是彻底理解。
(一)如何拦截其他Android应用程序播放器的原始音频数据自定义保存下来?
(二)Android拦截其它播放声音:内录音,外录音,录屏,剪辑,混音,一键制作大片全搞定
一、前言
作为大学“双编”专业研究方向出来的,所谓双编:既编曲,又编程,对音乐、音频制作编辑相比于单独程序员有独到的理解。
想当年作曲,编曲后的录音混音制作 Samplitude, Cubase, Nuendo, FL Studio, Sonar, Cool edit
等知名的音乐制作软件,及相关采样器,合成器,混响,过滤器,MIDI制作都能轻松驾驭,这些制作软件都是基于PC版的,大多数是Windows和Mac
版的,大多数大学教学还是用的 Sonar
。
然儿,互联网科技已经发从10多年前慢慢发展到了移动端,这些PC端的软件,也是那些程序大佬和音乐大佬一起开发出来的,虽说制作音乐软件,以及数字MIDI音乐无法从PC端完全迁移到手机端。但是,近几年来流行的短视频,音频,这些简单的处理,可以在手机上面开发出相关的软件出来。
本文重点从编程的角度,简单讲解Android下面
: 内录音,外录音,录屏,剪辑,混音,一键制作大片全
二、内、外录音
1. 内录音: 我在前面文章已经讲过,可以参考如何拦截其他Android应用程序播放器的原始音频数据自定义保存下来?
2. 外录音: 即是录制MIC的环境声音:与内录音唯一不同的是,内录音是捕获系统声音,外录音是录MIC环境中声音:区别如下:AudioRecord
的创建方式里面写法不一样,传入了:MediaRecorder.AudioSource.MIC
audioRecord = new AudioRecord(
MediaRecorder.AudioSource.MIC,//传入捕获声音源头是MIC :麦克风
SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize);
audioRecord.startRecording();
3. 录音读到的数据拿去交给Android原生MediaCodec进行编码成AAC格式音频文件,和内录后续处理是一样的。参考我前面文章
三、录屏
1. 录屏: 需要先拿到录屏权限,我在前面文章也已经讲过,可以参考如何拦截其他Android应用程序播放器的原始音频数据自定义保存下来?
2.录制视频需要用到MediaRecorder: :
是Android平台上用于录制音频和视频的高级API,它封装了音频和视频的采集、编码和封装逻辑,最终生成多媒体容器格式的文件,如mp4、ogg等 ,它是内部帮你实现了硬编码到h264或者和h265的视频编码。
基本使用方法:
1). 创建MediaRecorder对象:首先需要创建一个MediaRecorder对象。
2). 设置音频源:通过调用setAudioSource()
方法设置音频源,通常使用MediaRecorder.AudioSource.MIC
(麦克风)。
3). 设置输出格式:使用setOutputFormat()
方法设置输出格式,例如MediaRecorder.OutputFormat.MPEG_4
。
4). 设置音频编码器:通过setAudioEncoder()
方法设置音频编码器,例如MediaRecorder.AudioEncoder.AAC
。
5). 设置输出文件:使用setOutputFile()
方法指定输出文件的路径和名称。
6). 准备录制:调用prepare()
方法准备录制。
7). 开始录制:调用start()
方法开始录制。
8). 停止录制:调用stop()
方法停止录制。
9). 释放资源:调用release()
方法释放资源
3.录屏代码实现: 先配置录制视频参数及准备录屏
//配置录制视频参数及准备录屏
fun prepareVideo(): Boolean {
try {
mediaRecorder = (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) MediaRecorder(context) else MediaRecorder()).apply {
setVideoSource(MediaRecorder.VideoSource.SURFACE) // 设置录制的视频源为屏幕
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)// 设置输出文件的格式
setOutputFile(videoOutputFile.getAbsolutePath())// 设置输出文件路径
setVideoEncoder(MediaRecorder.VideoEncoder.H264)// 设置视频编码 H264
setVideoSize(mScreenWidth, mScreenHeight) // 设置视频的分辨率
setVideoEncodingBitRate(mScreenWidth * mScreenHeight)// 设置视频比特率
// setCaptureRate(VIDEO_CAPTURE_RATE) // 设置延时摄影帧率为3FPS
setVideoFrameRate(VIDEO_FRAME_RATE)// 设置视频的帧率 30帧 一个I帧
}
mediaRecorder?.run {
prepare()
virtualDisplay = mediaProjection?.createVirtualDisplay(
"WXScreenVideoRecorder", mScreenWidth, mScreenHeight, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null
)
}
return true
} catch (e: Exception) {
e.printStackTrace()
return false
}
}
4.开始录屏
//开始录屏
fun startRecordVideo() {
mediaRecorder?.start()
}
5.停止录屏
//停止录屏
fun stopRecordVideo() {
try {
mediaRecorder?.apply {
setOnErrorListener(null)
setOnInfoListener(null)
setPreviewDisplay(null)
stop()
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
mediaRecorder?.reset()
virtualDisplay?.release()
}
}
四、剪辑
1. 剪辑音频: 不管是剪辑音频AAC格式还是mp3格式,都是需要从压缩的音频格式里面提取出来PCM格式来进行处理:
2. 简单音频格式介绍:
音频格式 | 说明 |
---|---|
·WAV | 一种无压缩的音频格式,它以线性脉冲编码调制(PCM)方式存储音频数据。WAV文件通常比其他格式的文件大,但具有高质量的声音效果。 |
·MP3 | 一种有损压缩的音频格式,它通过删除音频信号中不必要的部分来减小文件大小,从而使得传输和存储更加方便。MP3是最常见的音频格式之一,因为它的压缩率较高,同时保持了良好的音质 |
·AAC | 一种有损压缩的音频格式,主要用于移动设备和网络流媒体应用。与MP3相比,AAC提供了更好的音质和更高的压缩率。 |
·FLAC | 一种无损压缩的音频格式,它可以将音频文件压缩到原始文件大小的一半或更少,同时保留完整的音频信息。FLAC通常被音乐发烧友使用,因为它提供了高质量的音频效果,同时不会丢失任何重要的细节 |
·OGG | 一种自由、开放、无损和有损的音频格式,它采用了基金会的技术。OGG通常用于网络流媒体应用,因为它可以提供高质量的音频效果,并且可以快速传输 |
·PCM | 一种将模拟信号数字化的技术,广泛应用于音频和视频信号的编码。PCM格式通过对模拟信号进行采样、量化和编码,将其转换为数字信号,以便在数字媒介上进行存储、传输和处理 |
3.媒体提取器:MediaExtractor是Android系统中的一个媒体解封装库,主要用于从视频或音频文件中提取媒体轨道(如视频、音频、字幕等)的数据。其主要功能是将多媒体文件中的音频、视频等数据分离出来,并提供给应用程序进行解码、处理和播放
主要使用以下几个步骤:
1). 打开媒体文件: 通过setDataSource
方法打开媒体文件,可以是本地文件或网络文件。
2). 识别媒体文件格式: MediaExtractor
会读取媒体文件的头部信息,识别其格式。
3). 创建Extractor: 根据媒体文件的格式,MediaExtractor会创建对应的Extractor(如MP4Extractor等),用于读取媒体文件中的音频和视频数据。
4). 提取数据:通过readSampleData
方法将分离出来的数据读取到ByteBuffer
中,供后续处理使用。
4. 剪辑音频代码实现: 通过MediaExtractor来提取出里面的数据,它可以指定到起始为止,从原始音频数据中提取出pcm数据。
/**
* 从文件中提取出音频 并输出成 PCM 格式
*
* @param inputPath :文件输入目录
* @param outPath : 输出的PCM 文件目录
* @param startTime :截取的开始时间
* @param endTime : 截取的结束时间
* @throws Exception :抛出的异常
*/
@SuppressLint("WrongConstant")
public static void decodeToPCM(String inputPath, String outPath, int startTime, int endTime) throws Exception {
if (endTime < startTime) {
return;
}
// 准备媒体提取器
MediaExtractor mediaExtractor = new MediaExtractor();
// 设值路径
mediaExtractor.setDataSource(inputPath);
// 找到音频索引
int audioTrack = selectTrack(mediaExtractor, true);
// 选择轨道
mediaExtractor.selectTrack(audioTrack);
// 指定到开始位置
mediaExtractor.seekTo(startTime, MediaExtractor.SEEK_TO_NEXT_SYNC);
// 拿到音频配置
MediaFormat audioFormat = mediaExtractor.getTrackFormat(audioTrack);
// 创建媒体解码器
MediaCodec mediaCodec = MediaCodec.createDecoderByType(audioFormat.getString((MediaFormat.KEY_MIME)));
mediaCodec.configure(audioFormat, null, null, 0);
mediaCodec.start();
int maxBufferSize = 100 * 1000;
if (audioFormat.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
maxBufferSize = audioFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
} else {
maxBufferSize = 100 * 1000;
}
//输出的 pcm 格式文件
File pcmFile = new File(outPath);
FileChannel writeChannel = new FileOutputStream(pcmFile).getChannel();
ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize);
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
while (true) {
int inIndex = mediaCodec.dequeueInputBuffer(1000);
if (inIndex >= 0) {
//获取到 视频容器 里面读取的当前时间戳
long sampleTimeUs = mediaExtractor.getSampleTime();
if (sampleTimeUs == -1) {
//已经读到了末尾
break;
} else if (sampleTimeUs < startTime) {
//丢弃的意思
mediaExtractor.advance();
} else if (sampleTimeUs > endTime) {
break;
}
// 此处需要 通过 mediaExtractor 提取器 一帧一帧提取
info.size = mediaExtractor.readSampleData(buffer, 0);
info.presentationTimeUs = sampleTimeUs;
info.flags = mediaExtractor.getSampleFlags();
byte[] content = new byte[buffer.remaining()];
buffer.get(content);
// 将数据 给到 dsp 芯片 进行解码
ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inIndex);
inputBuffer.put(content);
mediaCodec.queueInputBuffer(inIndex, 0, info.size, info.presentationTimeUs, info.flags);
// 释放上一帧的压缩数据
mediaExtractor.advance();
}
/** 以下为 DSP芯片已经解码完成,DSP 芯片输出 ***/
int outIndex = -1;
outIndex = mediaCodec.dequeueOutputBuffer(info, 1_000);
if (outIndex >= 0) {
ByteBuffer decodeOutputBuffer = mediaCodec.getOutputBuffer(outIndex);
// 得到解码后 的 原始数据 写入到输出 pcm 文件
writeChannel.write(decodeOutputBuffer);
mediaCodec.releaseOutputBuffer(outIndex, false);
}
}
writeChannel.close();
mediaExtractor.release();
mediaCodec.stop();
mediaCodec.release();
}
/**
* 找出视频文件中 音频轨
*
* @param extractor : 媒体提取器
* @param audio :是否是音频轨
* @return 返回轨道索引
*/
public static int selectTrack(MediaExtractor extractor, boolean audio) {
int numTracks = extractor.getTrackCount();
for (int i = 0; i < numTracks; i++) {
// 轨道配置信息, 码流读取 ,sps pps 解析
MediaFormat format = extractor.getTrackFormat(i);
// 轨道类型
String mime = format.getString(MediaFormat.KEY_MIME);
if (audio) {
if (mime.startsWith("audio")) {
return i;
}
} else {
if (mime.startsWith("video")) {
return i;
}
}
}
return -1;
}
5. 剪辑视频
需要从视频里面提先提取出音频,和视频,然后剪辑到开始和结束位置
剪辑视频里面音频数据
:和上面剪辑音频代码是一样的
剪辑视频里的视频数据
: 如下
// 视频
if (audioTrack >= 0) {
// 切换轨道
mediaExtractor.unselectTrack(audioTrack);
}
// 切换到视频轨
mediaExtractor.selectTrack(videoIndex);
// seek到 startTimeUs 时间戳的 前一个I帧
mediaExtractor.seekTo(startTimeUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
//视频 最大帧 最大的那一个帧 大小
maxBufferSize = videoFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
buffer = ByteBuffer.allocateDirect(maxBufferSize);
while (true) {
long sampleTimeUs = mediaExtractor.getSampleTime();
if (sampleTimeUs == -1) {
// 提取器读取结束
break;
}
if (sampleTimeUs < startTimeUs) {
mediaExtractor.advance();
continue;
}
if (endTimeUs != null && sampleTimeUs > endTimeUs) {
break;
}
info.presentationTimeUs = sampleTimeUs - startTimeUs + 600;
info.flags = mediaExtractor.getSampleFlags();
// 提取器读取出来到 buffer
info.size = mediaExtractor.readSampleData(buffer, 0);
if (info.size < 0) {
// 提取器里
break;
}
//写入视频数据
mediaMuxer.writeSampleData(muxerVideoIndex, buffer, info);
mediaExtractor.advance();
}
五、混音
混音涉及到多个音频进行处理:比如需要将2个声音混到一起:
从物理学上讲,声音量化成数字信号是一条波形,2条波形混到一起,就是把两条波形数据相加,但是相加得到的结果新的声音波形,它的振幅即声音不能超过数字信号的最大最小值范围,这里的范围是 -32768-32767,超过了就是刺耳的电流噪音了。
比如:两条PCM音频数据进行混合:
/**
* 将两个PCM 原始音频数据 混音
*
* @param pcm1Path : pcm文件1 的路径
* @param pcm2Path : pcm文件2 的路径
* @param toPath : 混音后pcm文件路径
* @param vol1 :音量1
* @param vol2 :音量2
* @throws IOException
*/
public static void mixPcm(String pcm1Path, String pcm2Path, String toPath, int vol1, int vol2) throws IOException {
float volume1 = vol1 / 100f * 1; //先转成float 防止 精度丢失
float volume2 = vol2 / 100f * 1; //先转成float 防止 精度丢失
//待混音的两条数据流
FileInputStream is1 = new FileInputStream(pcm1Path);
FileInputStream is2 = new FileInputStream(pcm2Path);
//输出的数据流
FileOutputStream fileOutputStream = new FileOutputStream(toPath);
boolean end1 = false, end2 = false;
byte[] buffer1 = new byte[2048]; //声音1读取 2个字节
byte[] buffer2 = new byte[2048]; //声音2读取 2个字节
byte[] buffer3 = new byte[2048]; //混合后声音 2个字节
short temp2, temp1;//short 个 16位
while (!end1 || !end2) {
if (!end2) {
//是否读取到结束
end2 = (is2.read(buffer2) == -1);
}
if (!end1) {
//是否读取到结束
end1 = (is1.read(buffer1) == -1);
}
// 混合后声音 音量
int voice = 0;
for (int i = 0; i < buffer2.length; i += 2) {
//前面低8位 低字节 后面高8位字节
temp1 = (short) ((buffer1[i] & 0xff) | (buffer1[i + 1] & 0xff) << 8);
temp2 = (short) ((buffer2[i] & 0xff) | (buffer2[i + 1] & 0xff) << 8);
voice = (int) (temp1 * volume1 + temp2 * volume2);
if (voice > 32767) {
voice = 32767;
} else if (voice < -32768) {
voice = -32768;
}
//相加后 还原 成 前面低8位 低字节 后面高8位字节
buffer3[i] = (byte) (voice & 0xFF);
buffer3[i + 1] = (byte) ((voice >>> 8) & 0xFF);
}
// 混音相加后写出到文件
fileOutputStream.write(buffer3);
}
is1.close();
is2.close();
fileOutputStream.close();
}
六、一键制作大片
怎么一键制作大片:
比如具体步骤:
- 找一条视频,里面含有人声 和 图像
- 找一条配置背景音乐。
- 选取好视频开始时间,结束时间,音频开始时间结束时间,配置好 背景音乐声音,和视频里面音频声音,以便后面混音调节叠加声音
- 把上面的混好制作在一起
- 代码实现步骤:
1)将视频里面音频按照开始结束时间提取出pcm数据格式文件
2)把背景音乐音频按照开始结束时间提取出pcm数据格式文件
3)将上述2段音频数据进行叠加混音得到混音后的PCM数据文件
4)将上述混音后的PCM音频数据处理成wav格式的音频数据文件(mediaExtractor不支持直接处理PCM格式的
)
5)将视频按照开始结束位置剪辑出来,和上述两段音频混后处理好的wav格式进行混合组装通过MediaMuxer
写到MP4文件里面去
完整代码如下:
private static void mixVideoAndMusic(String videoInput, String output, Integer startTimeUs, Integer endTimeUs, File wavFile) throws IOException {
//视频容器 输出 注意支持的格式只有这么几种
MediaMuxer mediaMuxer = new MediaMuxer(output, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
// 准备媒体提取器
MediaExtractor mediaExtractor = new MediaExtractor();
// 设值路径
mediaExtractor.setDataSource(videoInput);
// 拿到原视频中的 视频轨 索引
int videoIndex = selectTrack(mediaExtractor, false);
// 拿到原视频中的 音频轨 索引
int audioIndex = selectTrack(mediaExtractor, true);
// 拿到原视频编码配置
MediaFormat videoFormat = mediaExtractor.getTrackFormat(videoIndex);
// 视频容器 输出 新添加之前视频配置
int muxerVideoIndex = mediaMuxer.addTrack(videoFormat);
/*** ********************************************************** ***/
// 拿到原视频 中音频编码配置
MediaFormat audioFormat = mediaExtractor.getTrackFormat(audioIndex);
// 拿到原视频中 音频采样位数
int audioBitrate = audioFormat.getInteger(MediaFormat.KEY_BIT_RATE);
// 拿到原视频中 音频采样率
int sampleRate = audioFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
// 拿到原视频中 音频通道数
int channelCount = audioFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
audioFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
// 视频容器 输出 新添加之前视频中 音频配置
int muxerAudioIndex = mediaMuxer.addTrack(audioFormat);
// 输出容器开始 工作
mediaMuxer.start();
// 准备提取之前混音好的 wav 文件 提取器
MediaExtractor pcmExtractor = new MediaExtractor();
// 将之前混音好的 wav 文件路径给进去
pcmExtractor.setDataSource(wavFile.getAbsolutePath());
// 找到 混音好的WAV 中音频 索引
int audioTrack = selectTrack(pcmExtractor, true);
// 选取 混音后wav 文件中 音频轨
pcmExtractor.selectTrack(audioTrack);
// 拿到 混音后wab 文件中的 音频pcm 编码配置
MediaFormat pcmTrackFormat = pcmExtractor.getTrackFormat(audioTrack);
//最大一帧的 大小
int maxBufferSize = 0;
if (pcmTrackFormat.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
maxBufferSize = pcmTrackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
} else {
maxBufferSize = 100 * 1000;
}
// 要将 PCM 编码成 AAC 压缩格式 这里创建 音频 编码器配置 格式
MediaFormat encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate, channelCount);//参数对应-> mime type、采样率、声道数
// 设置音频编码比特率
encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, audioBitrate);
// 设置音频编码音质等级
encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
// 设置音频编码 最大一帧大小
encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxBufferSize);
// 硬编编码工具 编码 音频
MediaCodec encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
encoder.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
// 开始编码
encoder.start();
// 准备 音频 一帧 buffer
ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize);
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
//是否编码完成
boolean encodeDone = false;
while (!encodeDone) {
int inputBufferIndex = encoder.dequeueInputBuffer(10000);
if (inputBufferIndex >= 0) {
long sampleTime = pcmExtractor.getSampleTime();
if (sampleTime < 0) {
// 编码结束
encoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
int flags = pcmExtractor.getSampleFlags();
int size = pcmExtractor.readSampleData(buffer, 0);
ByteBuffer inputBuffer = encoder.getInputBuffer(inputBufferIndex);
inputBuffer.clear();
inputBuffer.put(buffer);
inputBuffer.position(0);
// 通知编码
encoder.queueInputBuffer(inputBufferIndex, 0, size, sampleTime, flags);
// 放弃内存, 一定要写 不写不能督导新的数据
pcmExtractor.advance();
}
}
// 输出的容器的索引
int outIndex = encoder.dequeueOutputBuffer(info, TIMEOUT);
while (outIndex >= 0) {
if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
encodeDone = true;
break;
}
// 通过索引 得到 编码好的数据在哪个容器
ByteBuffer encodeOutputBuffer = encoder.getOutputBuffer(outIndex);
//数据写进去了 ,写入到音频轨
mediaMuxer.writeSampleData(muxerAudioIndex, encodeOutputBuffer, info);
// 清空容器数据 方便下次读
encodeOutputBuffer.clear();
// 把编码器的数据释放 ,方便dsp 下一帧存
encoder.releaseOutputBuffer(outIndex, false);
outIndex = encoder.dequeueOutputBuffer(info, TIMEOUT);
}
}
// 视频
if (audioTrack >= 0) {
// 切换轨道
mediaExtractor.unselectTrack(audioTrack);
}
// 切换到视频轨
mediaExtractor.selectTrack(videoIndex);
// seek到 startTimeUs 时间戳的 前一个I帧
mediaExtractor.seekTo(startTimeUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
//视频 最大帧 最大的那一个帧 大小
maxBufferSize = videoFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
buffer = ByteBuffer.allocateDirect(maxBufferSize);
while (true) {
long sampleTimeUs = mediaExtractor.getSampleTime();
if (sampleTimeUs == -1) {
// 提取器读取结束
break;
}
if (sampleTimeUs < startTimeUs) {
mediaExtractor.advance();
continue;
}
if (endTimeUs != null && sampleTimeUs > endTimeUs) {
break;
}
info.presentationTimeUs = sampleTimeUs - startTimeUs + 600;
info.flags = mediaExtractor.getSampleFlags();
// 提取器读取出来到 buffer
info.size = mediaExtractor.readSampleData(buffer, 0);
if (info.size < 0) {
// 提取器里
break;
}
//写入视频数据
mediaMuxer.writeSampleData(muxerVideoIndex, buffer, info);
mediaExtractor.advance();
}
pcmExtractor.release();
mediaExtractor.release();
encoder.stop();
encoder.release();
mediaMuxer.release();
}
七、总结:
本文重点介绍了 内录音,外录音,录屏,剪辑,混音,一键制作大片的简单实现步骤:
用到了相关android原生的组件:
MediaProjection
:用于屏幕捕捉和屏幕录制
MediaCodec
:用于音视频编解码的API
AudioRecord
:用于录制音频的工具
MediaRecorder
:实现音视频录制的功能
MediaMuxer
:用来封装编码后的视频流和音频流到mp4容器中
MediaExtractor
:用于从视频或音频文件中提取媒体轨道(如视频、音频、字幕等)的数据
AudioFormat
:用于指定音频数据格式的类
MediaFormat
:用于描述音视频格式的类
另外:如果需要更深入的对音视频编码数据理解:需要对音视频数字信号理论进行掌握。