Android实现录屏直播(一)ScreenRecorder的简单分析

Android实现录屏直播(二)需求才是硬道理之产品功能调研

Android实现录屏直播(三)MediaProjection + VirtualDisplay + librtmp + MediaCodec实现视频编码并推流到rtmp服务器

应项目需求瞄准了Bilibili的录屏直播功能,基本就仿着做一个吧。研究后发现Bilibili是使用的MediaProjection 与 VirtualDisplay结合实现的,需要 Android 5.0 Lollipop API 21以上的系统才能使用。

其实官方提供的android-ScreenCapture这个Sample中已经有了MediaRecorder的实现与使用方式,还有使用MediaRecorder实现的录制屏幕到本地文件的Demo,从中我们都能了解这些API的使用。

而如果需要直播推流的话就需要自定义MediaCodec,再从MediaCodec进行编码后获取编码后的帧,免去了我们进行原始帧的采集的步骤省了不少事。可是问题来了,因为之前没有仔细了解H264文件的结构与FLV封装的相关技术,其中爬了不少坑,此后我会一一记录下来,希望对用到的朋友有帮助。

项目中对我参考意义最大的一个Demo是网友Yrom的GitHub项目ScreenRecorder,Demo中实现了录屏并将视频流存为本地的MP4文件(咳咳,其实Yrom就是Bilibili的员工吧?( ゜- ゜)つロ)��。在此先大致分析一下该Demo的实现,之后我会再说明我的实现方式。

ScreenRecorder

具体的原理在Demo的README中已经说得很明白了:

  • Display 可以“投影”到一个 VirtualDisplay
  • 通过 MediaProjectionManager 取得的 MediaProjection创建VirtualDisplay
  • VirtualDisplay 会将图像渲染到 Surface中,而这个Surface是由MediaCodec所创建的
mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
...
mSurface = mEncoder.createInputSurface();
...
mVirtualDisplay = mMediaProjection.createVirtualDisplay(name, mWidth, mHeight, mDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mSurface, null, null);
  • MediaMuxer 将从 MediaCodec 得到的图像元数据封装并输出到MP4文件中
int index = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);
...
ByteBuffer encodedData = mEncoder.getOutputBuffer(index);
...
mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);

所以其实在Android 4.4上可以通过DisplayManager来创建VirtualDisplay也是可以实现录屏,但因为权限限制需要ROOT。 (see DisplayManager.createVirtualDisplay())

Demo很简单,两个Java文件:

  • MainActivity.java
  • ScreenRecorder.java

MainActivity

类中仅仅是实现的入口,最重要的方法是onActivityResult,因为MediaProjection就需要从该方法开启。但是别忘了先进行MediaProjectionManager的初始化

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {MediaProjection mediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);if (mediaProjection == null) {Log.e("@@", "media projection is null");return;}// video sizefinal int width = 1280;final int height = 720;File file = new File(Environment.getExternalStorageDirectory(),"record-" + width + "x" + height + "-" + System.currentTimeMillis() + ".mp4");final int bitrate = 6000000;mRecorder = new ScreenRecorder(width, height, bitrate, 1, mediaProjection, file.getAbsolutePath());mRecorder.start();mButton.setText("Stop Recorder");Toast.makeText(this, "Screen recorder is running...", Toast.LENGTH_SHORT).show();moveTaskToBack(true);
}

ScreenRecorder

这是一个线程,结构很清晰,run()方法中完成了MediaCodec的初始化,VirtualDisplay的创建,以及循环进行编码的全部实现。

线程主体

 @Override
public void run() {try {try {prepareEncoder();mMuxer = new MediaMuxer(mDstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);} catch (IOException e) {throw new RuntimeException(e);}mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG + "-display",mWidth, mHeight, mDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,mSurface, null, null);Log.d(TAG, "created virtual display: " + mVirtualDisplay);recordVirtualDisplay();} finally {release();}
}

MediaCodec的初始化

方法中进行了编码器的参数配置与启动、Surface的创建两个关键的步骤

private void prepareEncoder() throws IOException {MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // 录屏必须配置的参数format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);Log.d(TAG, "created video format: " + format);mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);mSurface = mEncoder.createInputSurface(); // 需要在createEncoderByType之后和start()之前才能创建,源码注释写的很清楚Log.d(TAG, "created input surface: " + mSurface);mEncoder.start();
}

编码器实现循环编码

下面的代码就是编码过程,由于作者使用的是Muxer来进行视频的采集,所以在resetOutputFormat方法中实际意义是将编码后的视频参数信息传递给Muxer并启动Muxer。

private void recordVirtualDisplay() {while (!mQuit.get()) {int index = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);Log.i(TAG, "dequeue output buffer index=" + index);if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {resetOutputFormat();} else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {Log.d(TAG, "retrieving buffers time out!");try {// wait 10msThread.sleep(10);} catch (InterruptedException e) {}} else if (index >= 0) {if (!mMuxerStarted) {throw new IllegalStateException("MediaMuxer dose not call addTrack(format) ");}encodeToVideoTrack(index);mEncoder.releaseOutputBuffer(index, false);}}
}
private void resetOutputFormat() {// should happen before receiving buffers, and should only happen onceif (mMuxerStarted) {throw new IllegalStateException("output format already changed!");}MediaFormat newFormat = mEncoder.getOutputFormat();// 在此也可以进行sps与pps的获取,获取方式参见方法getSpsPpsByteBuffer()Log.i(TAG, "output format changed.\n new format: " + newFormat.toString());mVideoTrackIndex = mMuxer.addTrack(newFormat);mMuxer.start();mMuxerStarted = true;Log.i(TAG, "started media muxer, videoIndex=" + mVideoTrackIndex);
}

获取sps pps的ByteBuffer,注意此处的sps pps都是read-only只读状态

private void getSpsPpsByteBuffer(MediaFormat newFormat) {ByteBuffer rawSps = newFormat.getByteBuffer("csd-0");  ByteBuffer rawPps = newFormat.getByteBuffer("csd-1");
}

录屏视频帧的编码过程

BufferInfo.flags表示当前编码的信息,如源码注释:

 /*** This indicates that the (encoded) buffer marked as such contains* the data for a key frame.*/
public static final int BUFFER_FLAG_KEY_FRAME = 1; // 关键帧/*** This indicated that the buffer marked as such contains codec* initialization / codec specific data instead of media data.*/
public static final int BUFFER_FLAG_CODEC_CONFIG = 2; // 该状态表示当前数据是avcc,可以在此获取sps pps/*** This signals the end of stream, i.e. no buffers will be available* after this, unless of course, {@link #flush} follows.*/
public static final int BUFFER_FLAG_END_OF_STREAM = 4;

实现编码:

private void encodeToVideoTrack(int index) {ByteBuffer encodedData = mEncoder.getOutputBuffer(index);if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {// The codec config data was pulled out and fed to the muxer when we got// the INFO_OUTPUT_FORMAT_CHANGED status.// Ignore it.// 大致意思就是配置信息(avcc)已经在之前的resetOutputFormat()中喂给了Muxer,此处已经用不到了,然而在我的项目中这一步却是十分重要的一步,因为我需要手动提前实现sps, pps的合成发送给流媒体服务器Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");mBufferInfo.size = 0;}if (mBufferInfo.size == 0) {Log.d(TAG, "info.size == 0, drop it.");encodedData = null;} else {Log.d(TAG, "got buffer, info: size=" + mBufferInfo.size+ ", presentationTimeUs=" + mBufferInfo.presentationTimeUs+ ", offset=" + mBufferInfo.offset);}if (encodedData != null) {encodedData.position(mBufferInfo.offset);encodedData.limit(mBufferInfo.offset + mBufferInfo.size); // encodedData是编码后的视频帧,但注意作者在此并没有进行关键帧与普通视频帧的区别,统一将数据写入MuxermMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);Log.i(TAG, "sent " + mBufferInfo.size + " bytes to muxer...");}
}

以上就是对ScreenRecorder这个Demo的大体分析,由于总结时间仓促,很多细节部分我也没有进行深入的发掘研究,所以请大家抱着怀疑的态度阅读,如果说明有误或是理解不到位的地方,希望大家帮忙指出,谢谢!

参考文档

在功能的开发中还参考了很多有价值的资料与文章:

  1. Android屏幕直播方案
  2. Google官方的EncodeVirtualDisplayTest
  3. FLV文件格式解析
  4. 使用librtmp进行H264与AAC直播
  5. 后续更新…

Android实现录屏直播(一)ScreenRecorder的简单分析相关推荐

  1. Android实现录屏直播(二)需求才是硬道理之产品功能调研

    请尊重分享成果,转载请注明出处,本文来自Coder包子哥,原文链接:http://blog.csdn.net/zxccxzzxz/article/details/54254244 Android实现录 ...

  2. android 录屏自动运行,Android实现录屏直播+远程控制(二)

    前言 前面Android实现录屏直播+远程控制(一)的文章说到Android5.0的录屏直播实现方式,今天来说说实现录屏直播的另外一种方案 1 启动一个后台服务实现录屏 1.服务的创建这边就不在赘述了 ...

  3. android实现录像功能吗,Android实现录屏直播(一)ScreenRecorder的简单分析

    应项目需求瞄准了Bilibili的录屏直播功能,基本就仿着做一个吧.研究后发现Bilibili是使用的MediaProjection 与 VirtualDisplay结合实现的,需要 Android ...

  4. 如何在Android实现录屏直播

    许多开发者在做智慧教室同屏亦或会议同屏时,基于Andriod平台采集屏幕并编码推送,往往遇到各种各样的问题,以下就我们开发过程中的一些技术考量做个分享,权当抛砖引玉: 协议选择.数据来源和处理 1. ...

  5. Android PC投屏简单尝试(录屏直播)2—硬解章(MediaCodec+RMTP)

    代码地址 :https://github.com/deepsadness/MediaProjectionDemo 想法来源 上一边文章的最后说使用录制的Api进行录屏直播.本来这边文章是预计在5月份完 ...

  6. Android之间互相的录屏直播 --点对点传输(tcp长连接发送h264)(一)

    前言 转载请注明出处 ,来自: 暂时两篇: (1) Android之间互相的录屏直播 –点对点传输(tcp长连接发送h264)(一) http://blog.csdn.net/baidu_335462 ...

  7. c语言安卓录屏,手写 Android 录屏直播

    简介 观看手游直播时,我们观众端看到的是选手的屏幕上的内容,这是如何实现的呢?这篇博客将手写一个录屏直播 Demo,实现类似手游直播的效果 获取屏幕数据很简单,Android 系统有提供对应的服务,难 ...

  8. iOS录屏直播(一)初识ReplayKit

    Morris_2019.05.08 本篇主要功能: 认识ReplayKit框架 RPScreenRecorder实现在应用内录屏功能 RPPreviewViewController查看录屏内容 RPB ...

  9. 如何做电脑游戏桌面录屏直播实现手机直接观看

    原创教程 ( 转载请注明出处 ) 2017-6-26,今天来做一下是电脑游戏桌面录屏直播的教程,就是把桌面的游戏直播出去,加上话筒做讲解.最终实现在电脑.手机.微信中都可以观看到游戏的直播和讲解画面. ...

  10. 手游录屏直播技术详解 | 直播 SDK 性能优化实践

    直播无疑是 2016 年的大热话题,七牛云在 6 月底发布了实时流网络 LiveNet 和直播云解决方案后,我们用<直播技术详解>系列文章系统地介绍了直播各个环节的关键技术,帮助视频直播创 ...

最新文章

  1. python零基础怎么学-编程零基础应当如何开始学习 Python?
  2. 补天白帽大会:无处不在的无线电攻击
  3. Acwing799. 最长连续不重复子序列[C++题解]:双指针算法O(n)
  4. 网络安全系列之十一 系统命令注入***
  5. AutoHotkey纯命令获取Chrome等浏览器的当前网址
  6. ie浏览器升级_微软呼吁用户停用IE浏览器 2020年将不再更新升级
  7. php文本框自动补全,PHP自动补全表单的两种方法
  8. Flutter底部导航栏BottomNavigationBar页面状态保持解决方案
  9. Google安全团队对Android安全的认识
  10. 设置hash后导致的返回问题的解决方案
  11. Educational Codeforces Round 7
  12. paip.提升效率---request自动绑定domain object
  13. java接收前端JSON字符串
  14. ElementUI腾讯云开发者开发指南
  15. 40余个超好用的在线影视站点
  16. 2019年大前端技术趋势深度解读
  17. css实现3D动画效果——正方体变六边形
  18. 【可视化入门】智慧物流服务中心——可视化实例(动态)
  19. Quorum简介部署
  20. 红包分配:指定金额指定上下限后随机分发成若干个红包,随机抽

热门文章

  1. jrtplib for android,Jrtplib Android平台编译
  2. 计算机控制技术微课,课程名称:微型计算机控制技术课程
  3. 清华大学829考研 初试436经验谈
  4. 永久免费的内网端口映射工具推荐【无公网IP】
  5. foobar 更换皮肤
  6. 漏洞扫描器简单教程(天镜、Nessus、Appscan、Awvs)
  7. java中 移位运算符_java中的移位运算符心得总结
  8. weblogic部署静态网页
  9. VB程序设计—For循环结构
  10. 【病毒查杀】CAD杀毒方法