如何实现Android平台GB28181设备接入模块按需打开摄像头并回传数据

news2024/11/29 4:46:53

技术背景

实际上,我在年前的blog,已经写过Android平台GB28181后台service模式启动摄像头按需回传数据了,此次版本,是上个demo的迭代版,目的是平台侧如果不发起回传请求的话,摄像头不打开。

后台service模式启动后,仅完成平台上线注册,如果有语音广播过来,自动播放语音广播audio,如果平台侧订阅实时位置,安卓端按照位置订阅间隔,实时上报当前位置,当前端发起回传请求时,打开摄像头,再投递数据到底层模块,完成数据编码打包和回传,关闭回传后,摄像头自动关闭,达到最大限度节约资源占用的目的。

技术实现

懒得截图了,还是用老图吧,新的版本,在任务栏加了notify提醒,下面图片没有。

目前,Android平台GB28181设备接入侧模块,覆盖了以下功能:

  •  [视频格式]H.264/H.265(Android H.265硬编码);
  •  [音频格式]G.711 A律、AAC;
  •  [音量调节]Android平台采集端支持实时音量调节;
  •  [H.264硬编码]支持H.264特定机型硬编码;
  •  [H.265硬编码]支持H.265特定机型硬编码;
  •  [软硬编码参数配置]支持gop间隔、帧率、bit-rate设置;
  •  [软编码参数配置]支持软编码profile、软编码速度、可变码率设置;
  •  支持横屏、竖屏推流;
  •  Android平台支持后台service推送屏幕(推送屏幕需要5.0+版本);
  • 支持纯视频、音视频PS打包传输;
  • 支持RTP OVER UDP和RTP OVER TCP被动模式(TCP媒体流传输客户端);
  • 支持信令通道网络传输协议TCP/UDP设置;
  • 支持注册、注销,支持注册刷新及注册有效期设置;
  • 支持设备目录查询应答;
  • 支持心跳机制,支持心跳间隔、心跳检测次数设置;
  • 支持移动设备位置(MobilePosition)订阅和通知;
  •  适用国家标准:GB/T 28181—2016;
  • 支持语音广播;
  • 支持语音对讲;
  • 支持图像抓拍;
  • 支持历史视音频文件检索;
  • 支持历史视音频文件下载;
  • 支持历史视音频文件回放;
  • 支持云台控制和预置位查询;
  •  [实时水印]支持动态文字水印、png水印;
  •  [镜像]Android平台支持前置摄像头实时镜像功能;
  •  [实时静音]支持实时静音/取消静音;
  •  [实时快照]支持实时快照;
  •  [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
  •  [外部编码前视频数据对接]支持YUV数据对接;
  •  [外部编码前音频数据对接]支持PCM对接;
  •  [外部编码后视频数据对接]支持外部H.264数据对接;
  •  [外部编码后音频数据对接]外部AAC数据对接;
  •  [扩展录像功能]支持和录像SDK组合使用,录像相关功能。

先说这个提醒代码吧,onCreate()的时候,调用show_notification();即可:

   private Notification get_notification() {
        Notification.Builder builder = new Notification.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("NT-GB28181-Service-Demo")
                .setContentText("大牛直播SDK-安卓国标28181-Service-测试程序");

        if (Build.VERSION.SDK_INT >=26)
            builder.setChannelId(notification_id_);

        Notification notification = builder.build();
        return notification;
    }

    private void show_notification(){
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
       // if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
        if(Build.VERSION.SDK_INT >= 26){
            NotificationChannel channel = new NotificationChannel(notification_id_, notification_name_, NotificationManager.IMPORTANCE_HIGH);
            notificationManager.createNotificationChannel(channel);
        }

        startForeground(313,get_notification());
    }

onStart()里面,调用initGB28181Agent()完成国标设备侧到平台侧的register、catalog和keepalive交互,如果平台侧需要订阅位置,Android GB28181设备端可按照订阅时间间隔,实时上报位置信息。

    @Override
    public void onStart(Intent intent, int startId) {
        // TODO Auto-generated method stub
        super.onStart(intent, startId);

        Log.i(TAG, "onStart..");

        video_width_ = intent.getExtras().getInt("CAMERAWIDTH");
        video_height_ = intent.getExtras().getInt("CAMERAHEIGHT");

        boolean isCameraFaceFront = intent.getExtras().getBoolean("SWITCHCAMERA");

        Log.i(TAG, "video_width: " + video_width_ + ", video_height: " + video_height_ + " isCameraFaceFront: " + isCameraFaceFront);

        if (isCameraFaceFront)
            current_camera_type_ = FRONT;
         else
            current_camera_type_ = BACK;

        is_hardware_encoder = intent.getExtras().getBoolean("HWENCODER");

        window_manager_ = (WindowManager) getSystemService(Service.WINDOW_SERVICE);

        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent contentIntent = PendingIntent.getActivity(this,
                0, intent, 0);

        bgSurfaceView = new SurfaceView(this);

        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                1, 1, WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
                PixelFormat.TRANSLUCENT);
        layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
        window_manager_.addView(bgSurfaceView, layoutParams);

        bgSurfaceView.getHolder().addCallback(this);

        if (null == gb28181_agent_ ) {
            if(!initGB28181Agent() )
                return;
        }

        if (!gb28181_agent_.isRunning()) {
            if ( gb28181_agent_.start() )
                Log.i(TAG, "gb28181_agent_.start ok");
            else
                Log.e(TAG, "gb28181_agent_.start failed");
        }
    }

GB28181信令交互处理如下,收到平台侧invite请求后,尝试打开摄像头:

    // BackgroudService.java
    // Author: daniusdk.com
    @Override
    public void ntsOnInvitePlay(String deviceId, SessionDescription session_des) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                // 先振铃响应下
                gb28181_agent_.respondPlayInvite(180, device_id_);

                if (!try_preview_camera()) {
                    gb28181_agent_.respondPlayInvite(488, device_id_);
                    Log.i(TAG, "ntsOnInvitePlay try_preview_camera failed, response 488, device_id:" + device_id_);
                    return;
                }

                MediaSessionDescription video_des = null;
                SDPRtpMapAttribute ps_rtpmap_attr = null;

                // 28181 视频使用PS打包
                Vector<MediaSessionDescription> video_des_list = session_des_.getVideoPSDescriptions();
                if (video_des_list != null && !video_des_list.isEmpty()) {
                    for(MediaSessionDescription m : video_des_list) {
                        if (m != null && m.isValidAddressType() && m.isHasAddress() ) {
                            video_des = m;
                            ps_rtpmap_attr = video_des.getPSRtpMapAttribute();
                            break;
                        }
                    }
                }

                if (null == video_des) {
                    gb28181_agent_.respondPlayInvite(488, device_id_);
                    Log.i(TAG, "ntsOnInvitePlay get video description is null, response 488, device_id:" + device_id_);
                    return;
                }

                if (null == ps_rtpmap_attr ) {
                    gb28181_agent_.respondPlayInvite(488, device_id_);
                    Log.i(TAG, "ntsOnInvitePlay get ps rtp map attribute is null, response 488, device_id:" + device_id_);
                    return;
                }

                Log.i(TAG,"ntsOnInvitePlay, device_id:" +device_id_+", is_tcp:" + video_des.isRTPOverTCP()
                        + " rtp_port:" + video_des.getPort() + " ssrc:" + video_des.getSSRC()
                        + " address_type:" + video_des.getAddressType() + " address:" + video_des.getAddress());

                long rtp_sender_handle = lib_publisher_.CreateRTPSender(0);
                if ( rtp_sender_handle == 0 ) {
                    gb28181_agent_.respondPlayInvite(488, device_id_);
                    Log.i(TAG, "ntsOnInvitePlay CreateRTPSender failed, response 488, device_id:" + device_id_);
                    return;
                }

                gb28181_rtp_payload_type_  = ps_rtpmap_attr.getPayloadType();
                gb28181_rtp_encoding_name_ =  ps_rtpmap_attr.getEncodingName();

                lib_publisher_.SetRTPSenderTransportProtocol(rtp_sender_handle, video_des.isRTPOverUDP()?0:1);
                lib_publisher_.SetRTPSenderIPAddressType(rtp_sender_handle, video_des.isIPv4()?0:1);
                lib_publisher_.SetRTPSenderLocalPort(rtp_sender_handle, 0);
                lib_publisher_.SetRTPSenderSSRC(rtp_sender_handle, video_des.getSSRC());
                lib_publisher_.SetRTPSenderSocketSendBuffer(rtp_sender_handle, 2*1024*1024); // 设置到2M
                lib_publisher_.SetRTPSenderClockRate(rtp_sender_handle, ps_rtpmap_attr.getClockRate());
                lib_publisher_.SetRTPSenderDestination(rtp_sender_handle, video_des.getAddress(), video_des.getPort());

                if ( lib_publisher_.InitRTPSender(rtp_sender_handle) != 0 ) {
                    gb28181_agent_.respondPlayInvite(488, device_id_);
                    lib_publisher_.DestoryRTPSender(rtp_sender_handle);
                    return;
                }

                int local_port = lib_publisher_.GetRTPSenderLocalPort(rtp_sender_handle);
                if (local_port == 0) {
                    gb28181_agent_.respondPlayInvite(488, device_id_);
                    lib_publisher_.DestoryRTPSender(rtp_sender_handle);
                    return;
                }

                Log.i(TAG,"get local_port:" + local_port);

                String local_ip_addr = IPAddrUtils.getIpAddress(context_);

                MediaSessionDescription local_video_des = new MediaSessionDescription(video_des.getType());

                local_video_des.addFormat(String.valueOf(ps_rtpmap_attr.getPayloadType()));
                local_video_des.addRtpMapAttribute(ps_rtpmap_attr);

                local_video_des.setAddressType(video_des.getAddressType());
                local_video_des.setAddress(local_ip_addr);
                local_video_des.setPort(local_port);

                local_video_des.setTransportProtocol(video_des.getTransportProtocol());
                local_video_des.setSSRC(video_des.getSSRC());

                if (!gb28181_agent_.respondPlayInviteOK(device_id_,local_video_des) ) {
                    lib_publisher_.DestoryRTPSender(rtp_sender_handle);
                    Log.e(TAG, "ntsOnInvitePlay call respondPlayInviteOK failed.");
                    return;
                }

                gb28181_rtp_sender_handle_ = rtp_sender_handle;
            }

            private String device_id_;
            private SessionDescription session_des_;

            public Runnable set(String device_id, SessionDescription session_des) {
                this.device_id_ = device_id;
                this.session_des_ = session_des;
                return this;
            }
        }.set(deviceId, session_des),0);
    }

bye的时候,关闭释放摄像头:

    @Override
    public void ntsOnByePlay(String deviceId) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG, "ntsOnByePlay, stop GB28181 media stream, deviceId=" + device_id_);

                stopGB28181Stream();
                destoryRTPSender();

                release_camera();
            }

            private String device_id_;

            public Runnable set(String device_id) {
                this.device_id_ = device_id;
                return this;
            }

        }.set(deviceId),0);
    }

其他部分,和年前的版本基本类似,这里就不再赘述。

总结

后台采集摄像头,如果想再进一步扩展,可以把android平台gb28181的camera2 demo,都移植过来,实现功能更强大的国标设备侧,这里主要是展示,收到国标平台侧的回传请求后,才打开摄像头,才开始编码打包,最大限度的减少资源的占用,感兴趣的开发者可以跟我单独交流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1455821.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

系统调用之文件IO

学习完使用标准io库函数&#xff0c;进一步深入linux的内核实现系统的调用&#xff0c;本节主要学习文件io的相关函数。 标准IO与文件IO的区别 标准io是库函数&#xff0c;是系统调用的封装。 文件io是系统调用&#xff0c;是linux内核中的函数。 标准io有缓存&#xff0c;文件…

云原生之容器编排实践-在K8S集群中使用Registry2搭建私有镜像仓库

背景 基于前面搭建的3节点 Kubernetes 集群&#xff0c;今天我们使用 Registry2 搭建私有镜像仓库&#xff0c;这在镜像安全性以及离线环境下运维等方面具有重要意义。 Note: 由于是测试环境&#xff0c;以下创建了一个 local-storage 的 StorageClass &#xff0c;并使用本地…

STM32 TIM输入捕获测频率占空比库函数

目录 一、输入捕获初始化函数 TIM_ICInit TIM_PWMIConfig TIM_ICStructInit 二、主从触发模式对应函数 TIM_SelectInputTrigger TIM_SelectOutputTrigger TIM_SelectSlaveMode 三、配置分频器函数 TIM_SetIC1Prescaler TIM_SetIC2Prescaler TIM_SetIC3Prescaler T…

OpenAI视频生成模型Sora背后的技术及其深远的影响

前言 Sora的视频生成技术在保真度、长度、稳定性、一致性、分辨率和文字理解等方面都达到了当前最优水平。其核心技术包括使用视觉块编码将不同格式的视频统一编码成Transformer可训练的嵌入向量&#xff0c;以及类似于扩散过程的UNet方法进行降维和升维的加噪与去噪操作。通过…

IO 流分类

一、File File 类&#xff08;磁盘操作&#xff09;可以用于表示文件和目录的信息&#xff0c;但是它不表示文件的内容。递归地列出一个目录下所有文件&#xff1a; public static void listAllFiles(File dir) {if (dir null || !dir.exists()) {return;}if (dir.isFile())…

Java集合篇之深入解析LinkedList

写在开头 作为ArrayList的同门师兄弟&#xff0c;LinkedList的师门地位逊色不少&#xff0c;除了在做算法题的时候我们会用到它之外&#xff0c;在实际的开发工作中我们极少使用它&#xff0c;就连它的创造者都说&#xff1a;“I wrote it&#xff0c;and I never use it”&am…

MySQL之json数据操作

1 MySQL之JSON数据 总所周知&#xff0c;mysql5.7以上提供了一种新的字段格式json&#xff0c;大概是mysql想把非关系型和关系型数据库一口通吃&#xff0c;所以推出了这种非常好用的格式&#xff0c;这样&#xff0c;我们的很多基于mongoDB的业务都可以用mysql去实现了。当然…

Java 基于微信小程序的汽车4S店客户管理系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

C++类和对象-多态->案例1计算器类、案例2制作饮品、案例3电脑组装需求分析和电脑组装具体实现

#include<iostream> using namespace std; #include<string> //分别利用普通写法和多态技术实现计算器 //普通写法 class Calculator { public: int getResult(string oper) { if (oper "") { return m_Num1 m_Num2; …

引入成熟的Pytest自动化测试框架

虽然我们能使用脚本编写自动化测试框架&#xff0c;但没有必要重复找车轮子&#xff0c;引入成熟的自动化测试框架即可&#xff0c; Pytest是目前最成熟、功能最全面的Python测试框架之一&#xff0c;简单灵活、易于上手&#xff0c;可完全兼容其他测试框架如unitest&#xff…

普中51单片机学习(十)

动态数码管 数码管显示原理 动态显示的特点是将所有数码管的段选线并联在一起&#xff0c;由位选线控制是哪一位数码管有效。选亮数码管采用动态扫描显示。所谓动态扫描显示即轮流向各位数码管送出字形码和相应的位选&#xff0c;利用发光管的余辉和人眼视觉暂留作用&#xf…

SSTI模板注入漏洞(vulhub 复现)

首先了解模板引擎&#xff1a; 模板引擎&#xff08;这里特指用于Web开发的模板引擎&#xff09;是为了使用户界面与业务数据&#xff08;内容&#xff09;分离而产生的&#xff0c;它可以生成特定格式的文档&#xff0c;利用模板引擎来生成前端的html代码&#xff0c;模板引擎…

OpenAI:Sora视频生成模型技术报告(中文)

概述 视频生成模型作为世界模拟器 我们探索视频数据生成模型的大规模训练。具体来说&#xff0c;我们在可变持续时间、分辨率和宽高比的视频和图像上联合训练文本条件扩散模型。我们利用transformer架构&#xff0c;在视频和图像潜在代码的时空补丁上运行。我们最大的模型Sor…

HCIA-HarmonyOS设备开发认证V2.0-IOT硬件子系统-UART

目录 一、UART 概述二、UART 模块相关API三、UART 接口调用实例四、UART HDF驱动开发4.1、开发步骤(待续...) 坚持就有收获 一、UART 概述 UART 是通用异步收发传输器&#xff08;Universal Asynchronous Receiver/Transmitter&#xff09;的缩写&#xff0c;是通用串行数据总…

Chrome浏览器安装Axure-Chrome-Extension插件

Chrome浏览器打开Axure生成的HTML静态文件页面时&#xff0c;会显示如下图AXURE RP EXTENSION FOR CHROME&#xff0c;这是因为Chrome浏览器没有安装Axure插件Axure-Chrome-Extension导致的。 解决方法&#xff1a; 插件下载地址&#xff1a;https://download.csdn.net/downlo…

CleanMyMacX需要付费吗?多少钱?有哪些新功能

CleanMyMac X是一个付费应用程序**&#xff0c;需要许可证或订阅来解锁所有功能。不过&#xff0c;CleanMyMac X提供免费试用版供您访问其有限的功能。在试用模式下&#xff0c;用户可以使用部分功能进行体验&#xff0c;但这并非完全免费&#xff0c;因为某些功能会受到限制。…

Android下SF合成流程重学习之GPU合成

Android下SF合成流程重学习之GPU合成 引言 SurfaceFlinger中的图层选择GPU合成(CLIENT合成方式)时&#xff0c;会把待合成的图层Layers通过renderengine(SkiaGLRenderEngine)绘制到一块GraphicBuffer中&#xff0c;然后把这块GraphicBuffer图形缓存通过调用setClientTarget传递…

代码随想录算法训练营 DAY20 | 二叉树(7)

一、LeetCode 530 二叉搜索树的最小绝对值 题目链接&#xff1a;530.二叉搜索树的最小绝对值https://leetcode.cn/problems/minimum-absolute-difference-in-bst/ 思路一&#xff1a;利用搜索二叉树的中序遍历结果为有序数组的性质&#xff0c;将遍历结果保存到数组中&#xf…

分析:香港2亿港元诈骗案的风险特征及技术检测思路

目录 深度伪造带来的新挑战 如何有效检测深度伪造&#xff1f; 多管齐下的安全策略 据香港公共广播公司报道&#xff0c;一家跨国公司香港分行的一名员工在一次电话会议后被骗支付了2亿港元&#xff08;超过2500万美元&#xff09;的资金。 据介绍&#xff0c;这起骗局始于202…

【深度优先搜索】【树】【有向图】【推荐】685. 冗余连接 II

LeetCode685. 冗余连接 II 在本问题中&#xff0c;有根树指满足以下条件的 有向 图。该树只有一个根节点&#xff0c;所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点&#xff0c;而根节点没有父节点。 输入一个有向图&#xff0c;该图由…