极速简单实现Android 屏幕录制编码为H264并且使用RTMP推流
最近有使用到屏幕录制功能并需要将屏幕数据推送到服务器端。采用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推流相关推荐
- android 实现屏幕录制功能,极速简单实现Android 屏幕录制编码为H264并且使用RTMP推流...
最近有使用到屏幕录制功能并需要将屏幕数据推送到服务器端.采用RTMP推送,MediaCodec编码,MediaProjection 获取屏幕数据. 1.录制屏幕 在Android5.0 后可以采用原生 ...
- android 屏幕录制方案,Android录制屏幕的实现方法
原文:Paul Kinlan 翻译:Agora.io 长久以来,我一直希望能够直接从Android屏幕上进行录制并将其编码为多种格式,以便将录制内容嵌入在任意位置,而不需要安装任何软件. 如今,我们已 ...
- android屏幕 录制检测,Android 录制屏幕的实现方法
Android 录制屏幕的实现方法,长久以来,我一直希望能够直接从Android屏幕上进行录制并将其编码为多种格式,以便将录制内容嵌入在任意位置,而不需要安装任何软件. 如今,我们已经接近这个目标.C ...
- Android屏幕录制并传输,Android录制屏幕的实现方法
*原文:Paul Kinlan 翻译:Agora.io* 长久以来,我一直希望能够直接从Android屏幕上进行录制并将其编码为多种格式,以便将录制内容嵌入在任意位置,而不需要安装任何软件. 如今,我 ...
- android 屏幕录制代码,Android 录制屏幕的实现方法
长久以来,我一直希望能够直接从Android屏幕上进行录制并将其编码为多种格式,以便将录制内容嵌入在任意位置,而不需要安装任何软件. 如今,我们已经接近这个目标.Chrome团队正在添加一种功能,可以 ...
- android屏幕录制功能,Android利用ADB进行屏幕录制
前言 在写博客时,为了方便大家理解,我们经常需要把一些操作或动画录制成Gif,一般需要下载一个屏幕录制App将手机屏幕录制成视频(可能需要Root权限),然后导出到电脑,再转为Gif.今天就来教大家一 ...
- android p 录制屏幕,Android 屏幕录制
自己实现了Android的屏幕录制App. 用了MediaProjection类来作为源,MediaRecoder来捕捉,编码转换为本地视频. 效果图: 主要是这段代码开始录像: startActiv ...
- Android屏幕录制源码Demo下载
Android实现屏幕录制的方式介绍两种,第一种是通过adb shell命令,还一种使用SDK的提供的方法实现. adb shell 命令 语法: screenrecord [options] < ...
- python web Android屏幕录制
执行测试用例时,自动触发屏幕录制,单用例单录制 录制方式采用多线程,主线程开始,另开一个线程,跑录制脚本 web端 录制检测的是电脑页面,录制电脑页面展示的数据. from datetime impo ...
最新文章
- python广告刷量_Python一日一练05----怒刷点击量
- linux yum 安装mysql_Linux下使用yum安装MySQL
- 枚举类型和各种类型之间转换
- 并发编程之多线程线程安全(上)
- phppage类封装分页功能_PHP封装的page分页类定义与用法完整示例
- docker 删除所有镜像_Docker常用命令
- 计算机 64位和32位区别,32位和64位的区别
- python如何使用函数_python中函数使用
- LeetCode-2: Add Two Numbers
- Debian, Ubuntu 和 Linux Mint 中安装WPS
- @media scree 手机移动端屏幕自适应
- 字体 流光css,实例详解CSS3制作文字流光渐变特效
- 基于JavaEE的医院网上预约挂号系统
- 台阶的意思_台阶词语解释
- 谈一谈量化投资从哪里获取数据(会经常更新-2020-09-06)
- C++语法特性cheat paper
- Linux 开启22 端口
- 保姆级教程 | Java 8 安装及环境变量配置
- 什么是CMA检测报告
- 安卓rk3288软件的烧写过程
热门文章
- 数据库查找姓李的人_数据库中查询姓李的老师的个数
- npm ERR 404 You should bug the author to publish it (or use the name yourself)
- SQL优化之SQL查询语句的执行顺序解析
- 如何利用 Python 爬虫实现给微信群发新闻早报?(详细)
- Python 实现图片格式转换,jpg\png\webp等,附源码
- js设置鼠标两秒不动,隐藏鼠标
- 该如何解决odoo手机移动客户端app
- 远程计算机 这可能是由于credssp加密,win10系统远程桌面提示“这可能是由于credssp加密数据库修正”错误的解决办法...
- 什么是网络代理,如何设置浏览器代理
- Windows系统日志收集