随着无纸化、智慧教室等场景的普及,好多企业或者开发者开始寻求更高效稳定低延迟的RTMP同屏方案,本文以大牛直播SDK(Github)的同屏demo(对应工程:SmartServicePublisherV2)为例,介绍下如何采集编码推送RTMP数据到流媒体服务器。

系统要求:Android 5.0及以上系统。

废话不多说,上代码:

获取screen windows宽高,如需缩放,按照一定的比例缩放即可:

    private void createScreenEnvironment() {sreenWindowWidth = mWindowManager.getDefaultDisplay().getWidth();screenWindowHeight = mWindowManager.getDefaultDisplay().getHeight();Log.i(TAG, "screenWindowWidth: " + sreenWindowWidth + ",screenWindowHeight: "+ screenWindowHeight);if (sreenWindowWidth > 800){if (screenResolution == SCREEN_RESOLUTION_STANDARD){scale_rate = SCALE_RATE_HALF;sreenWindowWidth = align(sreenWindowWidth / 2, 16);screenWindowHeight = align(screenWindowHeight / 2, 16);}else if(screenResolution == SCREEN_RESOLUTION_LOW){scale_rate = SCALE_RATE_TWO_FIFTHS;sreenWindowWidth = align(sreenWindowWidth * 2 / 5, 16);}}Log.i(TAG, "After adjust mWindowWidth: " + sreenWindowWidth + ", mWindowHeight: " + screenWindowHeight);int pf = mWindowManager.getDefaultDisplay().getPixelFormat();Log.i(TAG, "display format:" + pf);DisplayMetrics displayMetrics = new DisplayMetrics();mWindowManager.getDefaultDisplay().getMetrics(displayMetrics);mScreenDensity = displayMetrics.densityDpi;mImageReader = ImageReader.newInstance(sreenWindowWidth,screenWindowHeight, 0x1, 6);mMediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);}

获取到image数据后,传递到processScreenImage()处理:

   private void setupMediaProjection() {mMediaProjection = mMediaProjectionManager.getMediaProjection(MainActivity.mResultCode, MainActivity.mResultData);}private void setupVirtualDisplay() {mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenCapture", sreenWindowWidth, screenWindowHeight,mScreenDensity,DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,mImageReader.getSurface(), null, null);mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {Image image = mImageReader.acquireLatestImage();if (image != null) {processScreenImage(image);//image.close();}}}, null);}

数据放到image list里面:

    private void  pushImage(Image image){if ( null ==image )return;final int image_list_max_count = 1;LinkedList<Image> close_images = null;synchronized (image_list_lock){if (image_list.size() > image_list_max_count ){close_images = new LinkedList();while ( image_list.size() > image_list_max_count){close_images.add(image_list.poll());}}image_list.add(image);}if ( close_images != null ){while( !close_images.isEmpty() ) {Image i = close_images.poll();if ( i != null ){i.close();//Log.i("PushImage", "drop image");}}}}

调用大牛直播SDK的RTMP初始化和参数设置接口:

        libPublisher = new SmartPublisherJniV2();    private void InitAndSetConfig() {//开始要不要采集音频或视频,请自行设置publisherHandle = libPublisher.SmartPublisherOpen(this.getApplicationContext(),audio_opt, video_opt, sreenWindowWidth,screenWindowHeight);if ( publisherHandle == 0 ){return;}Log.i(TAG, "publisherHandle=" + publisherHandle);libPublisher.SetSmartPublisherEventCallbackV2(publisherHandle, new EventHandeV2());if(videoEncodeType == 1){int h264HWKbps = setHardwareEncoderKbps(true, sreenWindowWidth,screenWindowHeight);Log.i(TAG, "h264HWKbps: " + h264HWKbps);int isSupportH264HWEncoder = libPublisher.SetSmartPublisherVideoHWEncoder(publisherHandle, h264HWKbps);if (isSupportH264HWEncoder == 0) {Log.i(TAG, "Great, it supports h.264 hardware encoder!");}}else if (videoEncodeType == 2){int hevcHWKbps = setHardwareEncoderKbps(false, sreenWindowWidth,screenWindowHeight);Log.i(TAG, "hevcHWKbps: " + hevcHWKbps);int isSupportHevcHWEncoder = libPublisher.SetSmartPublisherVideoHevcHWEncoder(publisherHandle, hevcHWKbps);if (isSupportHevcHWEncoder == 0) {Log.i(TAG, "Great, it supports hevc hardware encoder!");}}if(is_sw_vbr_mode){int is_enable_vbr = 1;int video_quality = CalVideoQuality(sreenWindowWidth,screenWindowHeight, true);int vbr_max_bitrate = CalVbrMaxKBitRate(sreenWindowWidth,screenWindowHeight);libPublisher.SmartPublisherSetSwVBRMode(publisherHandle, is_enable_vbr, video_quality, vbr_max_bitrate);}//音频相关可以参考SmartPublisher工程/*if (!is_speex){// set AAC encoderlibPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 1);}else{// set Speex encoderlibPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 2);libPublisher.SmartPublisherSetSpeexEncoderQuality(publisherHandle, 8);}libPublisher.SmartPublisherSetNoiseSuppression(publisherHandle, is_noise_suppression ? 1: 0);libPublisher.SmartPublisherSetAGC(publisherHandle, is_agc ? 1 : 0);*/// libPublisher.SmartPublisherSetClippingMode(publisherHandle, 0);//libPublisher.SmartPublisherSetSWVideoEncoderProfile(publisherHandle, sw_video_encoder_profile);//libPublisher.SmartPublisherSetSWVideoEncoderSpeed(publisherHandle, sw_video_encoder_speed);// libPublisher.SetRtmpPublishingType(publisherHandle, 0);libPublisher.SmartPublisherSetFPS(publisherHandle, 18);    //帧率可调libPublisher.SmartPublisherSetGopInterval(publisherHandle, 18*3);//libPublisher.SmartPublisherSetSWVideoBitRate(publisherHandle, 1200, 2400); //针对软编码有效,一般最大码率是平均码率的二倍libPublisher.SmartPublisherSetSWVideoEncoderSpeed(publisherHandle, 3);//libPublisher.SmartPublisherSaveImageFlag(publisherHandle, 1);}

初始化、参数设置后,设置RTMP推送的URL,并调用SartPublisher()接口,开始推送:

        //如果同时推送和录像,设置一次就可以InitAndSetConfig();if ( publisherHandle == 0 ){stopScreenCapture();return;}if(push_type == PUSH_TYPE_RTMP){String publishURL = intent.getStringExtra("PUBLISHURL");Log.i(TAG, "publishURL: " + publishURL);if (libPublisher.SmartPublisherSetURL(publisherHandle, publishURL) != 0) {stopScreenCapture();Log.e(TAG, "Failed to set publish stream URL..");if (publisherHandle != 0) {if (libPublisher != null) {libPublisher.SmartPublisherClose(publisherHandle);publisherHandle = 0;}}return;}}//启动传递数据线程post_data_thread = new Thread(new DataRunnable());Log.i(TAG, "new post_data_thread..");is_post_data_thread_alive = true;post_data_thread.start();
  int startRet = libPublisher.SmartPublisherStartPublisher(publisherHandle);if (startRet != 0) {isPushingRtmp = false;Log.e(TAG, "Failed to start push rtmp stream..");return;}//如果同时推送和录像,Audio启动一次就可以了CheckInitAudioRecorder();

开始推送后,传递数据到底层SDK:

    public class DataRunnable implements Runnable{private final static String TAG = "DataRunnable==> ";@Overridepublic void run() {// TODO Auto-generated method stubLog.i(TAG, "post data thread is running..");ByteBuffer last_buffer = null;Image last_image = null;long last_post_time = System.currentTimeMillis();while (is_post_data_thread_alive){boolean is_skip = false;/*synchronized (data_list_lock){if ( data_list.isEmpty()){if((System.currentTimeMillis() - last_post_time) > frame_added_interval_setting){if(last_buffer != null){Log.i(TAG, "补帧中..");}else{is_skip = true;}}else{is_skip = true;}}else{last_buffer = data_list.get(0);data_list.remove(0);}}*/Image new_image = popImage();if ( new_image == null ){if((System.currentTimeMillis() - last_post_time) > frame_added_interval_setting){if(last_image != null){Log.i(TAG, "补帧中..");}else{is_skip = true;}}else{is_skip = true;}}else{if ( last_image != null ){last_image.close();}last_image = new_image;}if( is_skip ){// Log.i("OnScreenImage", "is_skip");try {Thread.sleep(5);   //休眠5ms} catch (InterruptedException e) {e.printStackTrace();}}else{//if( last_buffer != null && publisherHandle != 0 && (isPushing || isRecording || isRTSPPublisherRunning) )if( last_image != null && publisherHandle != 0 && (isPushingRtmp || isRecording || isRTSPPublisherRunning) ){long post_begin_time = System.currentTimeMillis();final Image.Plane[] planes = last_image.getPlanes();if ( planes != null && planes.length > 0 ){libPublisher.SmartPublisherOnCaptureVideoRGBAData(publisherHandle, planes[0].getBuffer(), planes[0].getRowStride(),last_image.getWidth(), last_image.getHeight());}last_post_time = System.currentTimeMillis();long post_cost_time = last_post_time - post_begin_time;if ( post_cost_time >=0 && post_cost_time < 10 ){try {Thread.sleep(10-post_cost_time);} catch (InterruptedException e) {e.printStackTrace();}}/*libPublisher.SmartPublisherOnCaptureVideoRGBAData(publisherHandle, last_buffer, row_stride_,width_, height_);*//*//实际裁剪比例,可酌情自行调整int left = 100;int cliped_left = 0;int top = 0;int cliped_top = 0;int cliped_width = width_;int cliped_height = height_;if(scale_rate == SCALE_RATE_HALF){cliped_left = left / 2;cliped_top = top / 2;//宽度裁剪后,展示3/4比例cliped_width = (width_ *3)/4;//高度不做裁剪cliped_height = height_;}else if(scale_rate == SCALE_RATE_TWO_FIFTHS){cliped_left = left * 2 / 5;cliped_top = top * 2 / 5;//宽度裁剪后,展示3/4比例cliped_width = (width_ *3)/4;//高度不做裁剪cliped_height = height_;}if(cliped_width % 2 != 0){cliped_width = cliped_width + 1;}if(cliped_height % 2 != 0){cliped_height = cliped_height + 1;}if ( (cliped_left + cliped_width) > width_){Log.e(TAG, " invalid cliped region settings, cliped_left: " + cliped_left + " cliped_width:" + cliped_width + " width:" + width_);return;}if ( (cliped_top + cliped_height) > height_){Log.e(TAG, "invalid cliped region settings, cliped_top: " + cliped_top + " cliped_height:" + cliped_height + " height:" + height_);return;}//Log.i(TAG, " clipLeft: " + cliped_left + " clipTop: " + cliped_top +  " clipWidth: " + cliped_width + " clipHeight: " + cliped_height);libPublisher.SmartPublisherOnCaptureVideoClipedRGBAData(publisherHandle, last_buffer, row_stride_,width_, height_, cliped_left, cliped_top, cliped_width, cliped_height );*/// Log.i(TAG, "post data: " + last_post_time + " cost:" + post_cost_time);}else{try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}}if ( last_image != null){last_image.close();last_image = null;}}}

关闭采集推送:

    public void onDestroy() {// TODO Auto-generated method stubLog.i(TAG, "Service stopped..");stopScreenCapture();clearAllImages();if( is_post_data_thread_alive && post_data_thread != null ){Log.i(TAG, "onDestroy close post_data_thread++");is_post_data_thread_alive = false;try {post_data_thread.join();} catch (InterruptedException e) {e.printStackTrace();}post_data_thread = null;Log.i(TAG, "onDestroy post_data_thread closed--");}if (isPushingRtmp || isRecording || isRTSPPublisherRunning){if (audioRecord_ != null) {Log.i(TAG, "surfaceDestroyed, call StopRecording..");audioRecord_.Stop();if (audioRecordCallback_ != null) {audioRecord_.RemoveCallback(audioRecordCallback_);audioRecordCallback_ = null;}audioRecord_ = null;}stopPush();isPushingRtmp = false;stopRecorder();isRecording = false;stopRtspPublisher();isRTSPPublisherRunning = false;stopRtspService();isRTSPServiceRunning = false;if (publisherHandle != 0) {if (libPublisher != null) {libPublisher.SmartPublisherClose(publisherHandle);publisherHandle = 0;}}}libPublisher.UnInitRtspServer();super.onDestroy();}

以上就是Android平台数据采集、编码并推送的大概流程,感兴趣的开发者可参考下。

Android平台如何实现屏幕数据采集并推送至RTMP服务器相关推荐

  1. 音视频7——安卓软编音视频数据推送到rtmp服务器

    音视频开发路线: Android 音视频开发入门指南_Jhuster的专栏的技术博客_51CTO博客_android 音视频开发入门 demo地址: videoPath/Demo8Activity.j ...

  2. 音视频7——安卓硬编音视频数据推送到rtmp服务器

    音视频开发路线: Android 音视频开发入门指南_Jhuster的专栏的技术博客_51CTO博客_android 音视频开发入门 demo地址: videoPath/Demo8Activity.j ...

  3. 安卓硬编音视频数据推送到rtmp服务器

    一.RTMP使用流程 rtmp协议的api调用顺序如下: 二.初始化RTMP,连接服务器 有两种构建rtmp服务器的方式我们使用的b站的服务器,要使用b站的服务器,你得认证一下,审核还需要大概1天得时 ...

  4. 将h.264裸码流推送到RTMP服务器

    h.264裸码流的格式,参考"H.264-AVC-ISO_IEC_14496-10.pdf, page 211.",这个文档的下载地址:https://github.com/win ...

  5. 直播平台开发过程中关于谷歌fcm推送介绍

    在直播平台开发中要集成谷歌fcm,首先需要满足一下条件: 1.设备必须是android4.0以上,Google Play Services 必须是 11.2.0以上版本 2.Android SDK M ...

  6. 小米9android q测试版,小米9 Android Q Beta优先体验版已推送:新增深色模式

    原标题:小米9 Android Q Beta优先体验版已推送:新增深色模式 7月12日消息,小米MIUI官方微博称,小米9的MIUI Android Q Beta优先体验版现已推送!已获得测试资格的小 ...

  7. 魅族android n彩蛋,魅族Flyme Android 10首个内测版本已推送 强制开启90Hz彩蛋

    7月21日消息,魅族Flyme based on Android 10 首个内测版本已推送,本次推送机型:16s Pro.16s. 16th Plus.16th目前仍存在异常重启问题未能如期发布,预计 ...

  8. 魅族打开Android彩蛋,魅族 Flyme Android 10 首个内测版本已推送 强制开启 90Hz 彩蛋...

    7 月 21 日消息,魅族 Flyme based on Android 10 首个内测版本已推送,本次推送机型:16s Pro.16s. 16th Plus.16th 目前仍存在异常重启问题未能如期 ...

  9. android7.1.1彩蛋魅族,魅族Flyme Android 10首个内测版本已推送 强制开启90Hz彩蛋

    7月21日消息,魅族Flyme based on Android 10 首个内测版本已推送,本次推送机型:16s Pro.16s. 16th Plus.16th目前仍存在异常重启问题未能如期发布,预计 ...

最新文章

  1. for循环console输出结果的问题
  2. eclipse 设置 按键提示
  3. python可以自学吗-大家觉得自学python多久能学会?
  4. 第二课 壳的介绍以及脱壳常用思路
  5. el-amap 第一次正常第二次报错_flutter run: build tools revision 报错解决
  6. ES6-11 Symbol、iterator、forOf、typeArray
  7. 【qduoj - 312】寻找唯一的萌妹(卡时)
  8. java发送jsp表格邮件_JSP 发送邮件
  9. 与数据绑定相关的接口(转)
  10. osgconv使用指南(转)
  11. JAVA的对象创建与调用的内存解析
  12. 网关支付、银联代扣通道、快捷支付、银行卡支付分别是怎么样进行支付的?...
  13. DFS+记忆化搜索 -- 简单练习
  14. pytorch模型转onnx Exporting the operator _thnn_fused_lstm_cell to ONNX opset version 9 is not supported
  15. 如何把一张照片的像素提高_不改变像素尺寸怎么增加图片的文件大小
  16. omap gpio irq
  17. MAMP Pro 6 mac强大的本地服务器环境软件套装
  18. 微信小程序详解——小程序的生命周期和页面的生命周期
  19. LTE-PHY物理资源划分(一)
  20. 我国1000公斤推力涡扇发动机完成首飞试验

热门文章

  1. Myeclipse 更改web项目的访问名
  2. JQuery调用iframe子页面函数/对象的方法
  3. 进度条设置_为你的练习设置进度条
  4. Java Hashtable containsValue()方法与示例
  5. java 系统自动检测_如何在Java中检测OS(操作系统)名称?
  6. kotlin中判断字符串_Kotlin程序删除字符串中所有出现的字符
  7. android点击加号,Android仿微信朋友圈点击加号添加图片功能
  8. linux nohup不生成日志,linux重定向及nohup不输出的方法
  9. python手把手教程_【Python 1-7】Python手把手教程之——详解列表List
  10. java中的Attribute类_java培训技术ModelAttribute注解修饰POJO类型的入参