最后看一下学习需要的所有知识点的思维导图。在刚刚那份学习笔记里包含了下面知识点所有内容!文章里已经展示了部分!如果你正愁这块不知道如何学习或者想提升学习这块知识的学习效率,那么这份学习笔记绝对是你的秘密武器!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
-
码率控制模式
-
帧率
-
I 帧间隔
其中,码率就是指单位传输时间传送的数据位数,一般用 kbps
即千位每秒来表示。而帧率就是指每秒显示的帧数。
其实对于码率有三种模式可以控制:
-
BITRATE_MODE_CQ
-
表示不控制码率,尽最大可能保证图像质量
-
BITRATE_MODE_VBR
-
表示 MediaCodec 会根据图像内容的复杂度来动态调整输出码率,图像负责则码率高,图像简单则码率低
-
BITRATE_MODE_CBR
-
表示 MediaCodec 会把输出的码率控制为设定的大小
对于颜色格式,由于是将 YUV 数据编码成 H264,而 YUV 格式又有很多,这又涉及到机型兼容性问题。在对相机编码时要做好格式的处理,比如相机使用的是 NV21
格式,MediaFormat 使用的是 COLOR_FormatYUV420SemiPlanar
,也就是 NV12
模式,那么就得做一个转换,把 NV21
转换到 NV12
。
对于 I 帧间隔,也就是隔多久出现一个 H264 编码中的 I 帧。
完整 MediaFormat 设置示例:
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
// 马率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);
// 调整码率的控流模式
mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
// 设置帧率
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
// 设置 I 帧间隔
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
当开始编解码操作时,开启编解码线程,处理相机预览返回的 YUV 数据。
在这里用到了相机的一个封装库:
https://github.com/glumes/EzCameraKit
编解码操作
编解码操作代码如下:
while (isEncoding) {
// YUV 颜色格式转换
if (!mEncodeDataQueue.isEmpty()) {
input = mEncodeDataQueue.poll();
byte[] yuv420sp = newbyte[mWidth * mHeight * 3 / 2];
NV21ToNV12(input, yuv420sp, mWidth, mHeight);
input = yuv420sp;
}
if (input != null) {
try {
// 从输入缓冲区队列中拿到可用缓冲区,填充数据,再入队
ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();
int inputBufferIndex = mMediaCodec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
// 计算时间戳
pts = computePresentationTime(generateIndex);
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(input);
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, pts, 0);
generateIndex += 1;
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
// 从输出缓冲区队列中拿到编码好的内容,对内容进行相应处理后在释放
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = newbyte[bufferInfo.size];
outputBuffer.get(outData);
// flags 利用位操作,定义的 flag 都是 2 的倍数
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { // 配置相关的内容,也就是 SPS,PPS
mOutputStream.write(outData, 0, outData.length);
} elseif ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { // 关键帧
mOutputStream.write(outData, 0, outData.length);
} else {
// 非关键帧和SPS、PPS,直接写入文件,可能是B帧或者P帧
mOutputStream.write(outData, 0, outData.length);
}
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
}
} catch (IOException e) {
Log.e(TAG, e.getMessage());
}
} else {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Log.e(TAG, e.getMessage());
}
}
}
首先,要把要把相机的 NV21
格式转换成 NV12
格式,然后 通过 dequeueInputBuffer
方法去从可用的输入缓冲区队列中出队取出缓冲区,填充完数据后再通过 queueInputBuffer
方法入队。
dequeueInputBuffer
返回缓冲区索引,如果索引小于 0 ,则表示当前没有可用的缓冲区。它的参数 timeoutUs
表示超时时间 ,毕竟用的是 MediaCodec 的同步模式,如果没有可用缓冲区,就会阻塞指定参数时间,如果参数为负数,则会一直阻塞下去。
queueInputBuffer
方法将数据入队时,除了要传递出队时的索引值,然后还需要传入当前缓冲区的时间戳 presentationTimeUs
和当前缓冲区的一个标识 flag
。
其中,时间戳通常是缓冲区渲染的时间,而标识则有多种标识,标识当前缓冲区属于那种类型:
-
BUFFER_FLAG_CODEC_CONFIG
-
标识当前缓冲区携带的是编解码器的初始化信息,并不是媒体数据
-
BUFFER_FLAG_END_OF_STREAM
-
结束标识,当前缓冲区是最后一个了,到了流的末尾
-
BUFFER_FLAG_KEY_FRAME
-
表示当前缓冲区是关键帧信息,也就是 I 帧信息
在编码的时候可以计算当前缓冲区的时间戳,也可以直接传递 0 就好了,对于标识也可以直接传递 0 作为参数。
把数据传入给 MediaCodec 之后,通过 dequeueOutputBuffer
方法取出编解码后的数据,除了指定超时时间外,还需要传入 MediaCodec.BufferInfo
对象,这个对象里面有着编码后数据的长度、偏移量以及标识符。
取出 MediaCodec.BufferInfo
内的数据之后,根据不同的标识符进行不同的操作:
-
BUFFER_FLAG_CODEC_CONFIG
-
表示当前数据是一些配置数据,在 H264 编码中就是 SPS 和 PPS 数据,也就是
00 00 00 01 67
和00 00 00 01 68
开头的数据,这个数据是必须要有的,它里面有着视频的宽、高信息。 -
BUFFER_FLAG_KEY_FRAME
-
关键帧数据,对于 I 帧数据,也就是开头是
00 00 00 01 65
的数据, -
BUFFER_FLAG_END_OF_STREAM
-
表示结束,MediaCodec 工作结束
对于返回的 flags ,不符合预定义的标识,则可以直接写入,那些数据可能代表的是 H264 中的 P 帧 或者 B 帧。
对于编解码后的数据,进行操作后,通过 releaseOutputBuffer
方法释放对应的缓冲区,其中第二个参数 render
代表是否要渲染到 surface 上,这里暂时不需要就为 false 。
停止编码
当想要停止编码时,通过 MediaCodec 的 stop
方法切换到 Uninitialized
状态,然后再调用 release
方法释放掉。
这里并没有采用使用 BUFFER_FLAG_END_OF_STREAM
标识符的方式来停止编码,而是直接切换状态了,在通过 Surface 方式进行录制时,再去采用这种方式了。
架构师筑基包括哪些内容
我花了将近半个月时间将:深入 Java 泛型.、注解深入浅出、并发编程.、数据传输与序列化、Java 虚拟机原理、反射与类加载、高效 IO、Kotlin项目实战等等Android架构师筑基必备技能整合成了一套系统知识笔记PDF,相信看完这份文档,你将会对这些Android架构师筑基必备技能有着更深入、更系统的理解。
由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容
注:资料与上面思维导图一起看会更容易学习哦!每个点每个细节分支,都有对应的目录内容与知识点!
这份资料就包含了所有Android初级架构师所需的所有知识!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
…(img-Y29gkH5n-1715703221303)]
[外链图片转存中…(img-rTIt8tb2-1715703221303)]
这份资料就包含了所有Android初级架构师所需的所有知识!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!