最近有使用到屏幕录制功能并需要将屏幕数据推送到服务器端。采用RTMP推送,MediaCodec编码,MediaProjection 获取屏幕数据。

(新版本已经上传架构优化加录制音频+camer直播,请直接查看github源码,博文就不修改了。基于rtmp传输,mediacodec编码,流媒体服务器是SRS,播放器是基于ijk的直播播放器。有需要帮忙搭建的可以直接留言)

demo地址:github地址喜欢请点个start

1 屏幕+camer+mediacodec :新地址

   在Android5.0 后可以采用原生的API MediaProjection 来获取一个virtual的display 从而实现屏幕录制。

我们第一步就是要先把屏幕数据拿出来

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)private void creatVirtualDisPlay(){Log.d(TAG, "created virtual display: " +this.mediaProjection);VirtualDisplay mVirtualDisplay = mediaProjection.createVirtualDisplay(TAG + "-display",width, heigiht, 1, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,inputSurface, null, null);Log.d(TAG, "created virtual display: " + mVirtualDisplay);}

​​​​​​2.数据编码

   在Android 4.1 后我们可以采用原生的MediaCodec 进行编码也就是我们常说的硬件编码。

 @SuppressLint("NewApi")private void prepareEncoder()  {try {String MIME_TYPE = "video/avc";MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, heigiht);format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);format.setInteger(MediaFormat.KEY_BIT_RATE, 1024*1000);format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); // 设置帧率format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);format.setInteger(MediaFormat.KEY_BITRATE_MODE,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ);Log.d("chenzhu", "created video format: " + format);mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);inputSurface = mEncoder.createInputSurface();Log.d(TAG, "created input surface: " + inputSurface);mEncoder.start();}catch (Exception e){Log.d("chenzhu", "created  encoder fail" );}}

3.数据处理

将编码后的数据打包封装成FLV数据

 @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)private void recordVirtualDisplay() {while (true) {int eobIndex = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);switch (eobIndex) {case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:Log.d(TAG,"VideoSenderThread,MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED");break;case MediaCodec.INFO_TRY_AGAIN_LATER:
//                     Log.d(TAG,"VideoSenderThread,MediaCodec.INFO_TRY_AGAIN_LATER");break;case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:Log.d(TAG,"VideoSenderThread,MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:" + mEncoder.getOutputFormat().toString());sendAVCDecoderConfigurationRecord(0, mEncoder.getOutputFormat());break;default:Log.d(TAG,"VideoSenderThread,MediaCode,eobIndex=" + eobIndex);if (startTime == 0) {startTime = mBufferInfo.presentationTimeUs / 1000;}/*** we send sps pps already in INFO_OUTPUT_FORMAT_CHANGED* so we ignore MediaCodec.BUFFER_FLAG_CODEC_CONFIG*/if (mBufferInfo.flags != MediaCodec.BUFFER_FLAG_CODEC_CONFIG && mBufferInfo.size != 0) {ByteBuffer realData = mEncoder.getOutputBuffers()[eobIndex];realData.position(mBufferInfo.offset + 4);realData.limit(mBufferInfo.offset + mBufferInfo.size);sendRealData((mBufferInfo.presentationTimeUs / 1000) - startTime, realData);}mEncoder.releaseOutputBuffer(eobIndex, false);break;}}}private void sendAVCDecoderConfigurationRecord(long tms, MediaFormat format) {byte[] AVCDecoderConfigurationRecord = Packager.H264Packager.generateAVCDecoderConfigurationRecord(format);int packetLen = Packager.FLVPackager.FLV_VIDEO_TAG_LENGTH +AVCDecoderConfigurationRecord.length;byte[] finalBuff = new byte[packetLen];Packager.FLVPackager.fillFlvVideoTag(finalBuff,0,true,true,AVCDecoderConfigurationRecord.length);System.arraycopy(AVCDecoderConfigurationRecord, 0,finalBuff, Packager.FLVPackager.FLV_VIDEO_TAG_LENGTH, AVCDecoderConfigurationRecord.length);RESFlvData resFlvData = new RESFlvData();resFlvData.droppable = false;resFlvData.byteBuffer = finalBuff;resFlvData.size = finalBuff.length;resFlvData.dts = (int) tms;resFlvData.flvTagType = FLV_RTMP_PACKET_TYPE_VIDEO;resFlvData.videoFrameType = RESFlvData.NALU_TYPE_IDR;//        TODO sendRtmpClient.write(jniRtmpPointer, resFlvData.byteBuffer, resFlvData.byteBuffer.length, resFlvData.flvTagType, resFlvData.dts);}private void sendRealData(long tms, ByteBuffer realData) {int realDataLength = realData.remaining();int packetLen = Packager.FLVPackager.FLV_VIDEO_TAG_LENGTH +Packager.FLVPackager.NALU_HEADER_LENGTH +realDataLength;byte[] finalBuff = new byte[packetLen];realData.get(finalBuff, Packager.FLVPackager.FLV_VIDEO_TAG_LENGTH +Packager.FLVPackager.NALU_HEADER_LENGTH,realDataLength);int frameType = finalBuff[Packager.FLVPackager.FLV_VIDEO_TAG_LENGTH +Packager.FLVPackager.NALU_HEADER_LENGTH] & 0x1F;Packager.FLVPackager.fillFlvVideoTag(finalBuff,0,false,frameType == 5,realDataLength);RESFlvData resFlvData = new RESFlvData();resFlvData.droppable = true;resFlvData.byteBuffer = finalBuff;resFlvData.size = finalBuff.length;resFlvData.dts = (int) tms;resFlvData.flvTagType = FLV_RTMP_PACKET_TYPE_VIDEO;resFlvData.videoFrameType = frameType;
//        TODO sendRtmpClient.write(jniRtmpPointer, resFlvData.byteBuffer, resFlvData.byteBuffer.length, resFlvData.flvTagType, resFlvData.dts);}

4 集成rtmp包并且发送到服务器端

我们采用rtmp推送 对rtmp进行封装

extern "C"
JNIEXPORT jlong JNICALL
Java_com_example_administrator_mypush_RtmpClient_open(JNIEnv *env, jclass type, jstring url_,jboolean isPublishMode) {const char *url = (env)->GetStringUTFChars( url_, 0);LOGD("RTMP_OPENING:%s",url);RTMP* rtmp = RTMP_Alloc();if (rtmp == NULL) {LOGD("RTMP_Alloc=NULL");return NULL;}RTMP_Init(rtmp);int ret = RTMP_SetupURL(rtmp, const_cast<char *>(url));if (!ret) {RTMP_Free(rtmp);rtmp=NULL;LOGD("RTMP_SetupURL=ret");return NULL;}if (isPublishMode) {RTMP_EnableWrite(rtmp);}ret = RTMP_Connect(rtmp, NULL);if (!ret) {RTMP_Free(rtmp);rtmp=NULL;LOGD("RTMP_Connect=ret");return NULL;}ret = RTMP_ConnectStream(rtmp, 0);if (!ret) {ret = RTMP_ConnectStream(rtmp, 0);RTMP_Close(rtmp);RTMP_Free(rtmp);rtmp=NULL;LOGD("RTMP_ConnectStream=ret");return NULL;}(env)->ReleaseStringUTFChars( url_, url);LOGD("RTMP_OPENED");return reinterpret_cast<jlong>(rtmp);
}extern "C"
JNIEXPORT jint JNICALL
Java_com_example_administrator_mypush_RtmpClient_write(JNIEnv *env, jclass type_, jlong rtmpPointer,jbyteArray data_, jint size, jint type,jint ts) {LOGD("start write");jbyte *buffer = (env)->GetByteArrayElements( data_, NULL);RTMPPacket *packet = (RTMPPacket*)malloc(sizeof(RTMPPacket));RTMPPacket_Alloc(packet, size);RTMPPacket_Reset(packet);if (type == RTMP_PACKET_TYPE_INFO) { // metadatapacket->m_nChannel = 0x03;} else if (type == RTMP_PACKET_TYPE_VIDEO) { // videopacket->m_nChannel = 0x04;} else if (type == RTMP_PACKET_TYPE_AUDIO) { //audiopacket->m_nChannel = 0x05;} else {packet->m_nChannel = -1;}packet->m_nInfoField2  =  ((RTMP*)rtmpPointer)->m_stream_id;LOGD("write data type: %d, ts %d", type, ts);memcpy(packet->m_body,  buffer,  size);packet->m_headerType = RTMP_PACKET_SIZE_LARGE;packet->m_hasAbsTimestamp = FALSE;packet->m_nTimeStamp = ts;packet->m_packetType = type;packet->m_nBodySize  = size;int ret = RTMP_SendPacket((RTMP*)rtmpPointer, packet, 0);RTMPPacket_Free(packet);free(packet);(env)->ReleaseByteArrayElements( data_, buffer, 0);if (!ret) {LOGD("end write error %d", ret);return ret;}else{LOGD("end write success");return 0;}

以上就是整体的思路

最后附上效果图:

大概有1s左右的延时

优化后的ijk播放器可以达到100ms

ijk播放优化地址:ijk播放器优化到100ms 思路

云手机同步效果

极速简单实现Android 屏幕录制编码为H264并且使用RTMP推流相关推荐

  1. android 实现屏幕录制功能,极速简单实现Android 屏幕录制编码为H264并且使用RTMP推流...

    最近有使用到屏幕录制功能并需要将屏幕数据推送到服务器端.采用RTMP推送,MediaCodec编码,MediaProjection 获取屏幕数据. 1.录制屏幕 在Android5.0 后可以采用原生 ...

  2. android 屏幕录制方案,Android录制屏幕的实现方法

    原文:Paul Kinlan 翻译:Agora.io 长久以来,我一直希望能够直接从Android屏幕上进行录制并将其编码为多种格式,以便将录制内容嵌入在任意位置,而不需要安装任何软件. 如今,我们已 ...

  3. android屏幕 录制检测,Android 录制屏幕的实现方法

    Android 录制屏幕的实现方法,长久以来,我一直希望能够直接从Android屏幕上进行录制并将其编码为多种格式,以便将录制内容嵌入在任意位置,而不需要安装任何软件. 如今,我们已经接近这个目标.C ...

  4. Android屏幕录制并传输,Android录制屏幕的实现方法

    *原文:Paul Kinlan 翻译:Agora.io* 长久以来,我一直希望能够直接从Android屏幕上进行录制并将其编码为多种格式,以便将录制内容嵌入在任意位置,而不需要安装任何软件. 如今,我 ...

  5. android 屏幕录制代码,Android 录制屏幕的实现方法

    长久以来,我一直希望能够直接从Android屏幕上进行录制并将其编码为多种格式,以便将录制内容嵌入在任意位置,而不需要安装任何软件. 如今,我们已经接近这个目标.Chrome团队正在添加一种功能,可以 ...

  6. android屏幕录制功能,Android利用ADB进行屏幕录制

    前言 在写博客时,为了方便大家理解,我们经常需要把一些操作或动画录制成Gif,一般需要下载一个屏幕录制App将手机屏幕录制成视频(可能需要Root权限),然后导出到电脑,再转为Gif.今天就来教大家一 ...

  7. android p 录制屏幕,Android 屏幕录制

    自己实现了Android的屏幕录制App. 用了MediaProjection类来作为源,MediaRecoder来捕捉,编码转换为本地视频. 效果图: 主要是这段代码开始录像: startActiv ...

  8. Android屏幕录制源码Demo下载

    Android实现屏幕录制的方式介绍两种,第一种是通过adb shell命令,还一种使用SDK的提供的方法实现. adb shell 命令 语法: screenrecord [options] < ...

  9. python web Android屏幕录制

    执行测试用例时,自动触发屏幕录制,单用例单录制 录制方式采用多线程,主线程开始,另开一个线程,跑录制脚本 web端 录制检测的是电脑页面,录制电脑页面展示的数据. from datetime impo ...

最新文章

  1. python广告刷量_Python一日一练05----怒刷点击量
  2. linux yum 安装mysql_Linux下使用yum安装MySQL
  3. 枚举类型和各种类型之间转换
  4. 并发编程之多线程线程安全(上)
  5. phppage类封装分页功能_PHP封装的page分页类定义与用法完整示例
  6. docker 删除所有镜像_Docker常用命令
  7. 计算机 64位和32位区别,32位和64位的区别
  8. python如何使用函数_python中函数使用
  9. LeetCode-2: Add Two Numbers
  10. Debian, Ubuntu 和 Linux Mint 中安装WPS
  11. @media scree 手机移动端屏幕自适应
  12. 字体 流光css,实例详解CSS3制作文字流光渐变特效
  13. 基于JavaEE的医院网上预约挂号系统
  14. 台阶的意思_台阶词语解释
  15. 谈一谈量化投资从哪里获取数据(会经常更新-2020-09-06)
  16. C++语法特性cheat paper
  17. Linux 开启22 端口
  18. 保姆级教程 | Java 8 安装及环境变量配置
  19. 什么是CMA检测报告
  20. 安卓rk3288软件的烧写过程

热门文章

  1. 数据库查找姓李的人_数据库中查询姓李的老师的个数
  2. npm ERR 404 You should bug the author to publish it (or use the name yourself)
  3. SQL优化之SQL查询语句的执行顺序解析
  4. 如何利用 Python 爬虫实现给微信群发新闻早报?(详细)
  5. Python 实现图片格式转换,jpg\png\webp等,附源码
  6. js设置鼠标两秒不动,隐藏鼠标
  7. 该如何解决odoo手机移动客户端app
  8. 远程计算机 这可能是由于credssp加密,win10系统远程桌面提示“这可能是由于credssp加密数据库修正”错误的解决办法...
  9. 什么是网络代理,如何设置浏览器代理
  10. Windows系统日志收集