前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android平台实现无纸化同屏并推送RTMP或轻量级RTSP服务(毫秒级延迟)

Android平台实现无纸化同屏并推送RTMP或轻量级RTSP服务(毫秒级延迟)

原创
作者头像
音视频牛哥
修改于 2024-06-18 13:11:46
修改于 2024-06-18 13:11:46
4530
举报

​技术背景

在写这篇文章之前,实际上几年之前,我们就有非常稳定的无纸化同屏的模块,本文借demo更新,算是做个新的总结,废话不多说,先看图,本文以Android平台屏幕实时采集推送,Windows播放为例,和大家做个技术分享。

技术考量指标

本文以大牛直播SDK前些年实现的Android同屏采集推送为例,大概介绍下一些技术考量指标。

1. 轻量级RTSP服务还是RTMP?

我们在做无纸化同屏的时候,问的最多的是,能不能不要自建服务,直接主讲人或教师端,直接启动轻量级RTSP服务,其他终端拉流,如果是小并发,比如5人内的小范围的同屏,Windows平台走轻量级RTSP无可厚非,如果是30-60甚至100人的会议室,建议走RTMP。

2. 推送分辨率和码率选择

我们接触到好多设备,性能一般,但是屏幕是高分屏,甚至可以采集到4K的,考虑到实时编码和并发环境下,AP的承载能力,一般建议选择适合自己的分辨率码率即可,不要只追求高分辨率高码率,导致组网困难,单个或双通道AP压力大,一般建议控制在1920*1080分辨率内,码率控制在1-5M。

3. 软编码还是硬编码

Windows平台,一般优先考虑软编,因为大多Windows性能瓶颈不太大,超过1080P可以考虑硬编,Android平台建议直接硬编码。

4. 高分屏采集编码效率低怎么办

高分屏,不管是Windows还是Android,采集后的数据,建议先压缩,再编码,Windows平台我们可以设置压缩比例(scale rate),Android平台亦可,比如采集原始屏幕,或者缩放后的屏幕,具体见下图:

代码语言:java
AI代码解释
复制
  /* BackgroudService.java
   * Author: daniusdk.com
   */ 
  private void createScreenEnvironment() {

        sreenWindowWidth = mWindowManager.getDefaultDisplay().getWidth();
        screenWindowHeight = mWindowManager.getDefaultDisplay().getHeight();

        Log.i(TAG, "screenWindowWidth: " + sreenWindowWidth + ",screenWindowHeight: "
                + screenWindowHeight);

        if (sreenWindowWidth > 800)
        {
            if (screen_resolution_type_ == SCREEN_RESOLUTION_STANDARD)
            {
                scale_rate = SCALE_RATE_HALF;
                sreenWindowWidth = align(sreenWindowWidth / 2, 16);
                screenWindowHeight = align(screenWindowHeight / 2, 16);
            }
            else if(screen_resolution_type_ == SCREEN_RESOLUTION_LOW)
            {
                scale_rate = SCALE_RATE_TWO_FIFTHS;
                sreenWindowWidth = align(sreenWindowWidth * 2 / 5, 16);
                screenWindowHeight = align(screenWindowHeight * 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);
    }

5. Android横竖屏自动适配

Android平台,如果是pad采集,基本就是横屏采集,如果手机端,需要确保横竖屏模式下都可以正常采集。

4. 为什么要考虑补帧

Android的时候,一定的采集模式下,屏幕如果没有变化,不会一直有实时屏幕数据回调下来,这时候,为了保持帧率或数据采集的完整性,建议补帧。

5. 异常网络处理、事件回调机制

网络状态,不管是推送端,还是播放端,都是需要有实时的状态回调,确保客户端可以实时感知网络状态。

代码语言:java
AI代码解释
复制
backgroudService.SetEventListener(new EventListener() {
                  @Override
                  public void onPublisherEventCallback(long handle, int id, long param1, long param2, String param3, String param4, Object param5) {
                      String publisher_event = "";

                      switch (id) {
                          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_STARTED:
                              publisher_event = "开始..";
                              break;
                          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTING:
                              publisher_event = "连接中..";
                              break;
                          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTION_FAILED:
                              publisher_event = "连接失败..";
                              break;
                          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTED:
                              publisher_event = "连接成功..";
                              break;
                          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_DISCONNECTED:
                              publisher_event = "连接断开..";
                              break;
                          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_STOP:
                              publisher_event = "关闭..";
                              break;
                          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RECORDER_START_NEW_FILE:
                              publisher_event = "开始一个新的录像文件 : " + param3;
                              break;
                          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_ONE_RECORDER_FILE_FINISHED:
                              publisher_event = "已生成一个录像文件 : " + param3;
                              break;

                          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_SEND_DELAY:
                              publisher_event = "发送时延: " + param1 + " 帧数:" + param2;
                              break;

                          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CAPTURE_IMAGE:
                              publisher_event = "快照: " + param1 + " 路径:" + param3;

                              if (param1 == 0) {
                                  publisher_event = publisher_event + "截取快照成功..";
                              } else {
                                  publisher_event = publisher_event + "截取快照失败..";
                              }
                              break;
                          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:
                              publisher_event = "RTSP服务URL: " + param3;
                              break;
                          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUSH_RTSP_SERVER_RESPONSE_STATUS_CODE:
                              publisher_event = "RTSP status code received, codeID: " + param1 + ", RTSP URL: " + param3;
                              break;
                          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUSH_RTSP_SERVER_NOT_SUPPORT:
                              publisher_event = "服务器不支持RTSP推送, 推送的RTSP URL: " + param3;
                              break;
                      }

                      String str = "当前状态:" + publisher_event;

                      Log.i(TAG, str);

                      if (handler_ != null) {
                          Message message = new Message();
                          message.what = PUBLISHER_EVENT_MSG;
                          message.obj = publisher_event;
                          handler_.sendMessage(message);
                      }
                  }
            });

6. 采集到的数据可以按需录像吗

可以,而且很有必要,同屏的时候,如果需要把开会或教授内容实时保存下来,可以随时启动录像。

代码语言:java
AI代码解释
复制
    public boolean startRecorder()
    {
        Log.i(TAG, "onClick startRecorder..");

        if(!stream_publisher_.is_publishing())
        {
            startCaptureScreen();
        }

        if (layer_post_thread_ != null)
            layer_post_thread_.update_layers();

        if (stream_publisher_.is_recording()) {
            stopRecorder();
            return false;
        }

        InitAndSetConfig();

        ConfigRecorderParam();

        boolean start_ret = stream_publisher_.StartRecorder();
        if (!start_ret) {
            stream_publisher_.try_release();
            Log.e(TAG, "Failed to start recorder.");
            return false;
        }

        startAudioRecorder();
        startLayerPostThread();

        return true;
    }

    //停止录像
    public void stopRecorder() {
        stream_publisher_.StopRecorder();
        stream_publisher_.try_release();

        if (!stream_publisher_.is_publishing())
            stopAudioRecorder();
    }

7. 文字、图片水印

需要而且建议支持,比如实时时间、学校或公司logo等。

代码语言:java
AI代码解释
复制
        //水印效果选择++++++++++
        watermarkSelctor = (Spinner) findViewById(R.id.watermarkSelctor);
        watermarkSelctor.setEnabled(false);
        final String[] watermarks = new String[]{"图片水印", "全部水印", "文字水印", "不加水印"};

        ArrayAdapter<String> adapterWatermark = new ArrayAdapter<String>(this,
                android.R.layout.simple_spinner_item, watermarks);

        adapterWatermark.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

        watermarkSelctor.setAdapter(adapterWatermark);

        watermarkSelctor.setSelection(3,true);
        watemarkType = 3;   //默认不加水印

        watermarkSelctor.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

            @Override
            public void onItemSelected(AdapterView<?> parent, View view,
                                       int position, long id) {

                watemarkType = position;

                Log.i(TAG, "[水印类型]Currently choosing: " + watermarks[position] + ", watemarkType: " + watemarkType);

                if(backgroudService !=null) {
                    backgroudService.updateWatermarker(watemarkType);
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });

8. 可以同时启动轻量级RTSP服务吗

代码语言:java
AI代码解释
复制
    public boolean startRtspService(int port)
    {
        Log.i(TAG, "startRtspService++");

        rtsp_handle_ = lib_publisher_.OpenRtspServer(0);

        if (rtsp_handle_ == 0) {
            Log.e(TAG, "创建rtsp server实例失败! 请检查SDK有效性");
        } else {
            if (lib_publisher_.SetRtspServerPort(rtsp_handle_, port) != 0) {
                lib_publisher_.CloseRtspServer(rtsp_handle_);
                rtsp_handle_ = 0;
                Log.e(TAG, "创建rtsp server端口失败! 请检查端口是否重复或者端口不在范围内!");
            }

            if (lib_publisher_.StartRtspServer(rtsp_handle_, 0) == 0) {
                Log.i(TAG, "启动rtsp server 成功!");
            } else {
                lib_publisher_.CloseRtspServer(rtsp_handle_);
                rtsp_handle_ = 0;
                Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!");
            }

            isRTSPServiceRunning = true;
        }

        return true;
    }

    //停止RTSP服务
    public void stopRtspService() {
        Log.i(TAG, "stopRtspService++");
        if(!isRTSPServiceRunning)
        {
            return;
        }
        if (lib_publisher_ != null && rtsp_handle_ != 0) {
            lib_publisher_.StopRtspServer(rtsp_handle_);
            lib_publisher_.CloseRtspServer(rtsp_handle_);
            rtsp_handle_ = 0;
        }

        isRTSPServiceRunning = false;
    }

    public boolean startRtspPublisher(){
        Log.i(TAG, "startRtspPublisher++");

        if(!stream_publisher_.is_publishing())
        {
            startCaptureScreen();
        }

        InitAndSetConfig();

        String rtsp_stream_name = "stream1";
        stream_publisher_.SetRtspStreamName(rtsp_stream_name);
        stream_publisher_.ClearRtspStreamServer();

        stream_publisher_.AddRtspStreamServer(rtsp_handle_);

        if (!stream_publisher_.StartRtspStream()) {
            stream_publisher_.try_release();
            Log.e(TAG, "调用发布rtsp流接口失败!");
            return false;
        }

        startAudioRecorder();
        startLayerPostThread();

        return true;
    }

    //停止发布RTSP流
    public void stopRtspPublisher() {
        Log.i(TAG, "stopRtspPublisher++");
        stream_publisher_.StopRtspStream();
        stream_publisher_.try_release();

        if (!stream_publisher_.is_publishing())
            stopAudioRecorder();
    }

    public int getRtspSessionNumbers(){
        int session_numbers = 0;
        if (lib_publisher_ != null && rtsp_handle_ != 0) {
            session_numbers = lib_publisher_.GetRtspServerClientSessionNumbers(rtsp_handle_);
            Log.i(TAG, "GetRtspSessionNumbers: " + session_numbers);
        }

        return session_numbers;
    }

9. 同屏延迟,能不能做到毫秒级

废话不多说,上视频,延迟毫秒级。

10. 能不能采集到扬声器的audio?

Windows不在话下,Android平台需要高版本支持,高版本是可以采集到扬声器数据的,我们也实现了相关的demo,可以同时采集麦克风和扬声器的audio,单独推送或者同时混音输出。

11. 同屏过程中,重点画面可以快照吗?

当然可以,我们同屏采集端,支持采集编码png或jpg格式输出。

总结

其实一个好的无纸化同屏系统,需要考虑的有整体组网、分辨率、码率、实时延迟、音视频同步和连续性等各个指标,做容易,做好难,上述抛砖引玉,未能面面俱到,感兴趣的开发者,可以跟我单独交流。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Android平台轻量级RTSP服务模块技术接入说明
为满足内网无纸化/电子教室等内网超低延迟需求,避免让用户配置单独的服务器,大牛直播SDK在推送端发布了轻量级RTSP服务SDK。
音视频牛哥
2024/07/17
2090
Android平台轻量级RTSP服务模块技术接入说明
GB28181设备接入侧录像查询和录像下载技术探究之实时录像
我们在对接GB28181设备接入侧的时候,除了常规实时音视频按需上传外,还有个重要的功能,就是本地实时录像,录像后的数据,在执法记录仪等前端设备留底,然后,到工作站拷贝到专门的平台。
音视频牛哥
2023/07/16
7020
GB28181设备接入侧录像查询和录像下载技术探究之实时录像
Android平台RTSP流如何添加动态水印后转推RTMP或轻量级RTSP服务
我们在对接外部开发者的时候,遇到这样的技术诉求,客户用于地下管道检测场景,需要把摄像头的数据拉取过来,然后叠加上实时位置、施工单位、施工人员等信息,然后对外输出新的RTSP流,并本地录制一份带动态水印叠加后的数据。整个过程,因为摄像头位置一直在变化,所以需要整体尽可能的低延迟,达到可操控摄像头的目的。
音视频牛哥
2023/12/25
4280
Android平台RTSP流如何添加动态水印后转推RTMP或轻量级RTSP服务
Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?
JPG和PNG是两种常见的图片文件格式,在压缩方式、图像质量、透明效果和可编辑性等方面存在显著差异。
音视频牛哥
2024/01/14
2280
Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?
Android平台轻量级RTSP服务模块如何实现一个服务发布多路RTSP流?
我们在做Android平台轻量级RTSP服务和内网RTSP网关的时候,遇到过这样的问题,如何同时发布多路RTSP流出去?
音视频牛哥
2023/09/25
3640
Android平台轻量级RTSP服务模块如何实现一个服务发布多路RTSP流?
拉取RTSP流后的几个去向探讨(播放|转RTMP|轻量级RTSP服务|本地录制|GB28181)
写了很多关于RTSP播放和转发的blog了,今天我们做个简单的汇总,以大牛直播SDK的Android平台为例,拉取到RTSP流,除了本地播放,还有几个流向:
音视频牛哥
2024/01/31
3410
拉取RTSP流后的几个去向探讨(播放|转RTMP|轻量级RTSP服务|本地录制|GB28181)
Android平台不需要单独部署流媒体服务如何实现内网环境下一对一音视频互动
我们在做内网环境的一对一音视频互动的时候,遇到这样的技术诉求:如智能硬件场景下(比如操控智能硬件),纯内网环境,如何不要单独部署RTMP或类似流媒体服务,实现一对一音视频互动。
音视频牛哥
2023/05/17
2980
Android平台RTMP直播推送模块技术接入说明
大牛直播SDK跨平台RTMP直播推送模块,始于2015年,支持Windows、Linux(x64_64架构|aarch64)、Android、iOS平台,支持采集推送摄像头、屏幕、麦克风、扬声器、编码前、编码后数据对接,功能强大,性能优异,配合大牛直播SDK的SmartPlayer播放器,轻松实现毫秒级的延迟体验,满足大多数行业的使用场景。
音视频牛哥
2024/08/07
3090
Android平台RTMP直播推送模块技术接入说明
Android平台实现内网无纸化会议|智慧教室|实时同屏功能
1. 组网:无线组网,需要好的AP模块才能撑得住大的并发流量,推送端到AP,最好是有线网链接;
音视频牛哥
2021/05/12
1.7K0
Android平台实现RTSP拉流转发至轻量级RTSP服务
我们在做Android平台RTSP转发模块的时候,有公司提出来这样的技术需求,他们希望拉取外部RTSP摄像头的流,然后提供个轻量级RTSP服务,让内网其他终端过来拉流。实际上,这块,大牛直播SDK前几年就已经实现。
音视频牛哥
2024/07/08
1910
Android平台实现RTSP拉流转发至轻量级RTSP服务
Android平台GB28181设备接入端实现实时快照
Android平台GB28181设计开发的时候,有个功能必不可少的:实时快照,特别是用于执法记录仪等场景下,用于图像留底或分析等考量。
音视频牛哥
2022/10/04
2950
Android平台RTSP|RTMP直播播放器技术接入说明
本文详细介绍了在 Android 平台上集成 RTSP 和 RTMP 直播播放模块的技术背景、系统要求、准备工作、接口设计、功能支持以及接口调用流程。通过合理的架构设计和优化,开发者可以高效地实现直播播放功能,满足不同场景下的应用需求。
音视频牛哥
2025/03/02
2821
Android平台RTSP|RTMP直播播放器技术接入说明
Android平台GB28181设备接入侧如何同时对外输出RTSP流?
GB28181的应用场景非常广泛,如公共安全、交通管理、企业安全、教育、医疗等众多领域,细分场景可用于如执法记录仪、智能安全帽、智能监控、智慧零售、智慧教育、远程办公、明厨亮灶、智慧交通、智慧工地、雪亮工程、平安乡村、生产运输、车载终端等:
音视频牛哥
2023/07/28
2620
Android平台GB28181设备接入侧如何同时对外输出RTSP流?
Android平台RTSP|RTMP直播播放器技术接入说明
大牛直播SDK自2015年发布RTSP、RTMP直播播放模块,迭代从未停止,SmartPlayer功能强大、性能强劲、高稳定、超低延迟、超低资源占用。无需赘述,全自研内核,行业内一致认可的跨平台RTSP、RTMP直播播放器。本文以Android平台为例,介绍下如何集成RTSP、RTMP播放模块。
音视频牛哥
2024/07/24
4650
Android平台RTSP|RTMP直播播放器技术接入说明
Android平台轻量级RTSP服务之GStreamer还是SmartRtspServer
Android上启动一个轻量级RTSP服务,让Android终端像网络摄像头一样提供个外部可供RTSP拉流的服务,在内网小并发又不希望部署单独流媒体服务的场景下非常适用,在Android终端实现这样的流媒体服务,决定了,只能是轻量级的服务。可以通过集成第三方库或编写自定义的RTSP服务器代码来实现这一功能。
音视频牛哥
2024/09/08
1K0
Android平台轻量级RTSP服务之GStreamer还是SmartRtspServer
[技术分享]Android平台实时音视频录像模块设计之道
录像有什么难的?无非就是数据过来,编码保存mp4而已,这可能是好多开发者在做录像模块的时候的思考输出。是的,确实不难,但是做好,或者和其他模块有非常好的逻辑配合,确实不容易。
音视频牛哥
2023/05/23
6630
[技术分享]Android平台实时音视频录像模块设计之道
Android平台轻量级RTSP服务模块二次封装版调用说明
在前面的blog,我们发布了Android平台轻量级RTSP服务模块的技术对接说明,好多开发者希望,更黑盒的对接轻量级RTSP服务这块,专注于自身业务逻辑。为此,我们针对Android平台轻量级RTSP服务模块,做了更进一步的封装(LibPublisherWrapper.java)。
音视频牛哥
2024/07/25
1590
Android平台轻量级RTSP服务模块二次封装版调用说明
GB/T28181-2022图像抓拍规范解读及技术实现
GB28181-2022相对2016,增加了设备软件升级、图像抓拍信令流程和协议接口。我们先回顾下规范说明:
音视频牛哥
2023/03/06
1.7K0
GB/T28181-2022图像抓拍规范解读及技术实现
Android平台实现屏幕录制(屏幕投影)|音频播放采集|麦克风采集并推送RTMP或轻量级RTSP服务
好多开发者,希望我们能系统的介绍下无纸化同屏的原理和集成步骤,以Android平台为例,无纸化同屏将Android设备上的屏幕内容实时投射到另一个显示设备(如Windows终端、国产化操作系统或另一台Android设备)上,从而实现多屏互动和内容的无缝共享。
音视频牛哥
2024/08/16
2460
Android平台实现屏幕录制(屏幕投影)|音频播放采集|麦克风采集并推送RTMP或轻量级RTSP服务
Android平台通过RTSP服务实现摄像头麦克风共享
前些年,我们在完成Android平台RTMP直播推送模块后,遇到这样的技术需求,好多开发者希望在Android平台,实现摄像头和麦克风音视频数据采集编码打包后,对外提供RTSP(Real Time Streaming Protocol)服务。
音视频牛哥
2024/09/05
2600
Android平台通过RTSP服务实现摄像头麦克风共享
推荐阅读
Android平台轻量级RTSP服务模块技术接入说明
2090
GB28181设备接入侧录像查询和录像下载技术探究之实时录像
7020
Android平台RTSP流如何添加动态水印后转推RTMP或轻量级RTSP服务
4280
Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?
2280
Android平台轻量级RTSP服务模块如何实现一个服务发布多路RTSP流?
3640
拉取RTSP流后的几个去向探讨(播放|转RTMP|轻量级RTSP服务|本地录制|GB28181)
3410
Android平台不需要单独部署流媒体服务如何实现内网环境下一对一音视频互动
2980
Android平台RTMP直播推送模块技术接入说明
3090
Android平台实现内网无纸化会议|智慧教室|实时同屏功能
1.7K0
Android平台实现RTSP拉流转发至轻量级RTSP服务
1910
Android平台GB28181设备接入端实现实时快照
2950
Android平台RTSP|RTMP直播播放器技术接入说明
2821
Android平台GB28181设备接入侧如何同时对外输出RTSP流?
2620
Android平台RTSP|RTMP直播播放器技术接入说明
4650
Android平台轻量级RTSP服务之GStreamer还是SmartRtspServer
1K0
[技术分享]Android平台实时音视频录像模块设计之道
6630
Android平台轻量级RTSP服务模块二次封装版调用说明
1590
GB/T28181-2022图像抓拍规范解读及技术实现
1.7K0
Android平台实现屏幕录制(屏幕投影)|音频播放采集|麦克风采集并推送RTMP或轻量级RTSP服务
2460
Android平台通过RTSP服务实现摄像头麦克风共享
2600
相关推荐
Android平台轻量级RTSP服务模块技术接入说明
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档