一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情。
3.Encoder工作流程图
b.Decoder
Decoder负责渲染屏幕数据,将从网络接收的屏幕数据进行入列处理后出列再进行渲染。
1.Decoder配置及创建
private void startVideoDecoder() {
MediaCodec decoder = MediaCodec.createDecoderByType(MIME_TYPE);
final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, VIDEO_WIDTH, VIDEO_HEIGHT);
format.setInteger(MediaFormat.KEY_BIT_RATE, VIDEO_WIDTH * VIDEO_HEIGHT);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
//横屏
byte[] header_sps = {0, 0, 0, 1, 103, 66, -128, 31, -38, 1, 64, 22, -24, 6, -48, -95, 53};
byte[] header_pps = {0, 0 ,0, 1, 104, -50, 6, -30};
//竖屏
byte[] header_sps = {0, 0, 0, 1, 103, 66, -128, 31, -38, 2, -48, 40, 104, 6, -48, -95, 53};
byte[] header_pps = {0, 0 ,0, 1, 104, -50, 6, -30};
format.setByteBuffer("csd-0", ByteBuffer.wrap(header_sps));
format.setByteBuffer("csd-1", ByteBuffer.wrap(header_pps));
decoder.configure(format, mSurface, null, 0);//mSurface对应需要展示surfaceview的surface
decoder.start();
}
调用MediaCodec的createDecoderByType()来创建Decoder,对应的type为"video/avc",代表屏幕视频数据是H264编码;配置好Format后,调用start()来启动。
2.将远端传输过来的屏幕数据渲染显示
a.远端屏幕数据过来后,将数据存入到Input Buffer中;
// Get input buffer index
int inputBufferIndex = decoder.dequeueInputBuffer(100);//dequeue可以存储的有效索引
ByteBuffer inputBuffer;
if (inputBufferIndex >= 0) {
if (Build.VERSION.SDK_INT < 21) {
ByteBuffer[] inputBuffers = decoder.getInputBuffers();//获取可以存储的input buffer数组
inputBuffer = inputBuffers[inputBufferIndex];
} else {
inputBuffer = decoder.getInputBuffer(inputBufferIndex);
}
inputBuffer.clear();
inputBuffer.put(buf, offset, length);//将传过来的buf放入有效的buffer索引中
decoder.queueInputBuffer(inputBufferIndex, 0, length, System.currentTimeMillis(), 0);//将数据queue到需要渲染的input buffer中
}
通过getInputBuffer(inputBufferIndex)得到当前请求的输入缓存,在使用之前要进行clear(),避免之前的缓存数据影响当前数据,然后把网络接收的数据添加到输入缓存中,并调用queueInputBuffer(…)把缓存数据入队;
b.不断去获取存入input buffer中的数据,渲染到surfaceview上显示
while (mIsRunning) {
// Get output buffer index
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 100);//dequeue一块已经存好数据[a步queueInputBuffer的数据]的 输出buffer索引
while (outputBufferIndex >= 0) {
decoder.releaseOutputBuffer(outputBufferIndex, true[渲染到surface]);//将数据在surface上渲染[surfaceview上显示]
outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 0);//不断dequeue,以备渲染
}
}
通过以上可以看到,首先请求一个空的输入缓存(input buffer),向其中填充满数据并将它传递给编解码器处理,编解码器处理完这些数据并将处理结果输出至一个空的输出缓存(output buffer)中。最终请求到一个填充了结果数据的输出缓存(output buffer),使用完其中的数据,并将其释放给编解码器再次使用。
3.具体流程
a. Client 从 input 缓冲区队列申请 empty buffer [dequeueInputBuffer];
b. Client 把需要编解码的数据拷贝到 empty buffer,然后放入 input 缓冲区队列 [queueInputBuffer];
c. MediaCodec 模块从 input 缓冲区队列取一帧数据进行编解码处理;
d. 编解码处理结束后,MediaCodec 将原始数据 buffer 置为 empty 后放回 input 缓冲区队列,将编解码后的数据放入到 output 缓冲区队列;
e. Client 从 output 缓冲区队列申请编解码后的 buffer [dequeueOutputBuffer];
f. Client 对编解码后的 buffer 进行渲染/播放;
g. 渲染/播放完成后,Client 再将该 buffer 放回 output 缓冲区队列 ;[releaseOutputBuffer]\
4.Decoder工作流程图\
四.总结
针对以上的分析,最后总结一下屏幕录制及分享的工作流程:
- 屏幕分享端先获取MediaProjection;
- 屏幕分享端通过MediaCodec的createEncoderByType创建编码器,进行配置后start();
- 屏幕观看端通过MediaCodec的createDecoderByType创建解码器,进行配置后start();
- 屏幕分享端循环执行dequeueOutputBuffer(),getOutputBuffer(),sendData(),releaseOutputBuffer(,false);
- 屏幕观看端循环执行dequeueInputBuffer(),getInputBuffer(),queueInputBuffer(),dequeueOutputBuffer(),releaseOutputBuffer(,true);
- 分享及观看结束时,执行stop()、release();
配置帧
cfgFrame:配置帧,解码器在收到该帧后,才能开始解码,否则的话,会出现绿屏等现象,格式如下:
byte [] cfgFrame1 = {0, 0, 0, 1, 103, 66, -128, 31, -38, 1, 64, 22, -23, 72, 40, 48, 48, 54, -123, 9, -88, 0, 0, 0, 1, 104, -50, 6, -30};
byte [] cfgFrame2 = {0, 0, 0, 1, 103, 66, -128, 31, -38, 1, 64, 61, -91, 32, -96, -64, -64, -38, 20, 38, -96, 0, 0, 0, 1, 104, -50, 6, -30};
至此在Android平台上屏幕直播流程已经完成了。