GB28181设备接入侧如何对接外部编码后音视频数据并实现预览播放

 技术背景

我们在对接GB28181设备接入模块的时候,遇到这样的技术诉求,好多开发者期望能提供编码后(H.264/H.265、AAC/PCMA)数据对接,确保外部采集设备,比如无人机类似回调过来的数据,直接通过模块,对接到GB28181平台侧,此外,还期望不支持或者内网没有外部网络权限的RTSP设备,也能间接接入到国标平台。

技术实现

编码后音视频数据

本文以Android平台为例,基于上述诉求,我们设计的接口如下,简单来说,GB28181交互流程不变,只要提供数据接入接口即可:

/*** 设置编码后视频数据(H.264)** @param codec_id, H.264对应 1** @param data 编码后的video数据** @param size data length** @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0.** @param timestamp video timestamp** @param pts Presentation Time Stamp, 显示时间戳** @return {0} if successful*/public native int SmartPublisherPostVideoEncodedData(long handle, int codec_id, ByteBuffer data, int size, int is_key_frame, long timestamp, long pts);/*** 设置编码后视频数据(H.264)** @param codec_id, H.264对应 1** @param data 编码后的video数据**@param offset data的偏移** @param size data length** @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0.** @param timestamp video timestamp** @param pts Presentation Time Stamp, 显示时间戳** @return {0} if successful*/public native int SmartPublisherPostVideoEncodedDataV2(long handle, int codec_id,ByteBuffer data, int offset, int size,int is_key_frame, long timestamp, long pts,byte[] sps, int sps_len,byte[] pps, int pps_len);/*** 设置编码后视频数据(H.264),如需录制编码后的数据,用此接口,且设置实际宽高** @param codec_id, H.264对应 1** @param data 编码后的video数据**@param offset data的偏移** @param size data length** @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0.** @param timestamp video timestamp** @param pts Presentation Time Stamp, 显示时间戳** @param width, height: 编码后视频宽高** @return {0} if successful*/public native int SmartPublisherPostVideoEncodedDataV3(long handle, int codec_id,ByteBuffer data, int offset, int size,int is_key_frame, long timestamp, long pts,byte[] sps, int sps_len,byte[] pps, int pps_len,int width, int height);/*** 设置音频数据(AAC/PCMA/PCMU/SPEEX)** @param codec_id:**  NT_MEDIA_CODEC_ID_AUDIO_BASE = 0x10000,*   NT_MEDIA_CODEC_ID_PCMA = NT_MEDIA_CODEC_ID_AUDIO_BASE,*   NT_MEDIA_CODEC_ID_PCMU,*   NT_MEDIA_CODEC_ID_AAC,*   NT_MEDIA_CODEC_ID_SPEEX,*   NT_MEDIA_CODEC_ID_SPEEX_NB,*   NT_MEDIA_CODEC_ID_SPEEX_WB,*   NT_MEDIA_CODEC_ID_SPEEX_UWB,** @param data audio数据** @param size data length** @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0, audio忽略** @param timestamp video timestamp** @param parameter_info 用于AAC special config信息填充** @param parameter_info_size parameter info size** @return {0} if successful*/public native int SmartPublisherPostAudioEncodedData(long handle, int codec_id, ByteBuffer data, int size, int is_key_frame, long timestamp,ByteBuffer parameter_info, int parameter_info_size);/*** 设置音频数据(AAC/PCMA/PCMU/SPEEX)** @param codec_id:**  NT_MEDIA_CODEC_ID_AUDIO_BASE = 0x10000,*   NT_MEDIA_CODEC_ID_PCMA = NT_MEDIA_CODEC_ID_AUDIO_BASE,*   NT_MEDIA_CODEC_ID_PCMU,*   NT_MEDIA_CODEC_ID_AAC,*   NT_MEDIA_CODEC_ID_SPEEX,*   NT_MEDIA_CODEC_ID_SPEEX_NB,*   NT_MEDIA_CODEC_ID_SPEEX_WB,*   NT_MEDIA_CODEC_ID_SPEEX_UWB,** @param data audio数据** @param offset data的偏移** @param size data length** @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0, audio忽略** @param timestamp video timestamp** @param parameter_info 用于AAC special config信息填充** @param parameter_info_size parameter info size** @return {0} if successful*/public native int SmartPublisherPostAudioEncodedDataV2(long handle, int codec_id,ByteBuffer data, int offset, int size,int is_key_frame, long timestamp,byte[] parameter_info, int parameter_info_size);/*** 设置音频数据(AAC/PCMA/PCMU/SPEEX)** @param codec_id:**  NT_MEDIA_CODEC_ID_AUDIO_BASE = 0x10000,*   NT_MEDIA_CODEC_ID_PCMA = NT_MEDIA_CODEC_ID_AUDIO_BASE,*   NT_MEDIA_CODEC_ID_PCMU,*   NT_MEDIA_CODEC_ID_AAC,*   NT_MEDIA_CODEC_ID_SPEEX,*   NT_MEDIA_CODEC_ID_SPEEX_NB,*   NT_MEDIA_CODEC_ID_SPEEX_WB,*   NT_MEDIA_CODEC_ID_SPEEX_UWB,** @param data audio数据** @param offset data的偏移** @param size data length** @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0, audio忽略** @param timestamp video timestamp** @param parameter_info 用于AAC special config信息填充** @param parameter_info_size parameter info size** @param sample_rate 采样率,如果需要录像的话必须传正确的值**@param channels 通道数, 如果需要录像的话必须传正确的值, 一般是1或者2** @return {0} if successful*/public native int SmartPublisherPostAudioEncodedDataV3(long handle, int codec_id,ByteBuffer data, int offset, int size,int is_key_frame, long timestamp,byte[] parameter_info, int parameter_info_size,int sample_rate, int channels);

拉取RTSP流接入到GB28181平台

简单那来说,把摄像机的RTSP流数据拉下来,然后回调编码后的数据到上层,上层根据GB28181数据格式要求,实现PS打包,然后通过对接GB28181平台信令和数据交互,国标平台侧需要预览的时候,信令交互后,拉RTSP即可。

如何拉流:

private boolean StartPull(){if ( isPulling )return false;if (!OpenPullHandle())return false;libPlayer.SmartPlayerSetAudioDataCallback(playerHandle, new PlayerAudioDataCallback());libPlayer.SmartPlayerSetVideoDataCallback(playerHandle, new PlayerVideoDataCallback());int is_pull_trans_code  = 1;libPlayer.SmartPlayerSetPullStreamAudioTranscodeAAC(playerHandle, is_pull_trans_code);int startRet = libPlayer.SmartPlayerStartPullStream(playerHandle);if (startRet != 0) {Log.e(TAG, "Failed to start pull stream!");if(!isPlaying && !isRecording && isPushing && !isRTSPPublisherRunning){libPlayer.SmartPlayerClose(playerHandle);playerHandle = 0;}return false;}isPulling = true;return true;}private void StopPull(){if ( !isPulling )return;libPlayer.SmartPlayerStopPullStream(playerHandle);if ( !isPlaying && !isRecording && !isPushing && !isRTSPPublisherRunning){libPlayer.SmartPlayerClose(playerHandle);playerHandle = 0;}isPulling = false;}

拉到的音视频数据,投递到GB28181接入模块:

class PlayerAudioDataCallback implements NTAudioDataCallback{private int audio_buffer_size = 0;private int param_info_size = 0;private ByteBuffer audio_buffer_ = null;private ByteBuffer parameter_info_ = null;@Overridepublic ByteBuffer getAudioByteBuffer(int size){//Log.i("getAudioByteBuffer", "size: " + size);if( size < 1 ){return null;}if ( size <= audio_buffer_size && audio_buffer_ != null ){return audio_buffer_;}audio_buffer_size = size + 512;audio_buffer_size = (audio_buffer_size+0xf) & (~0xf);audio_buffer_ = ByteBuffer.allocateDirect(audio_buffer_size);// Log.i("getAudioByteBuffer", "size: " + size + " buffer_size:" + audio_buffer_size);return audio_buffer_;}@Overridepublic ByteBuffer getAudioParameterInfo(int size){//Log.i("getAudioParameterInfo", "size: " + size);if(size < 1){return null;}if ( size <= param_info_size &&  parameter_info_ != null ){return  parameter_info_;}param_info_size = size + 32;param_info_size = (param_info_size+0xf) & (~0xf);parameter_info_ = ByteBuffer.allocateDirect(param_info_size);//Log.i("getAudioParameterInfo", "size: " + size + " buffer_size:" + param_info_size);return parameter_info_;}public void onAudioDataCallback(int ret, int audio_codec_id, int sample_size, int is_key_frame, long timestamp, int sample_rate, int channel, int parameter_info_size, long reserve){if ( audio_buffer_ == null)return;audio_buffer_.rewind();if ( ret == 0 && (isPushing || isRTSPPublisherRunning || isGB28181StreamRunning)) {libPublisher.SmartPublisherPostAudioEncodedData(publisherHandle, audio_codec_id, audio_buffer_, sample_size, is_key_frame, timestamp, parameter_info_, parameter_info_size);}}}class PlayerVideoDataCallback implements NTVideoDataCallback{private int video_buffer_size = 0;private ByteBuffer video_buffer_ = null;@Overridepublic ByteBuffer getVideoByteBuffer(int size){if( size < 1 ){return null;}if ( size <= video_buffer_size &&  video_buffer_ != null ){return  video_buffer_;}video_buffer_size = size + 1024;video_buffer_size = (video_buffer_size+0xf) & (~0xf);video_buffer_ = ByteBuffer.allocateDirect(video_buffer_size);return video_buffer_;}public void onVideoDataCallback(int ret, int video_codec_id, int sample_size, int is_key_frame, long timestamp, int width, int height, long presentation_timestamp){if ( video_buffer_ == null)return;video_buffer_.rewind();if ( ret == 0 &&  (isPushing || isRTSPPublisherRunning || isGB28181StreamRunning) ) {libPublisher.SmartPublisherPostVideoEncodedData(publisherHandle, video_codec_id, video_buffer_, sample_size, is_key_frame, timestamp, presentation_timestamp);}}}

如何预览播放外部音视频数据?

除了想把编码后的音视频数据转至GB28181外,有些场景下,还需要本地预览甚至对数据做二次处理(视频分析、实时水印字符叠加等,然后二次编码),基于这样的场景诉求,我们实现了Android平台外部编码数据实时预览播放模块。

外部(H.264/H.265)投递接口设计如下:

    // SmartPlayerJniV2.java// Author: daniusdk.com/*** 投递视频包给外部Live Source** @param codec_id: 编码id, 当前仅支持H264和H265, 1:H264, 2:H265** @param packet: 视频数据, ByteBuffer必须是DirectBuffer, 包格式请参考H264/H265 Annex B Byte stream format, 例如:*                0x00000001 nal_unit 0x00000001 ...*                H264 IDR: 0x00000001 sps 0x00000001 pps 0x00000001 IDR_nal_unit .... 或 0x00000001 IDR_nal_unit ....*                H265 IDR: 0x00000001 vps 0x00000001 sps 0x00000001 pps 0x00000001 IDR_nal_unit .... 或 0x00000001 IDR_nal_unit ....** @param offset: 偏移量* @param size: packet size* @param timestamp_ms: 时间戳, 单位毫秒* @param is_timestamp_discontinuity: 是否时间戳间断,0:未间断,1:间断* @param is_key: 是否是关键帧, 0:非关键帧, 1:关键帧* @param extra_data: 可选参数,可传null, 对于H264关键帧包, 如果packet不含sps和pps, 可传0x00000001 sps 0x00000001 pps*                    ,对于H265关键帧包, 如果packet不含vps,sps和pps, 可传0x00000001 vps 0x00000001 sps 0x00000001 pps* @param extra_data_size: extra_data size* @param width: 图像宽, 可传0* @param height: 图像高, 可传0** @return {0} if successful*/public native int PostVideoPacketByteBuffer(long handle, int codec_id,java.nio.ByteBuffer packet, int offset, int size, long timestamp_ms, int is_timestamp_discontinuity, int is_key,byte[] extra_data, int extra_data_size, int width, int height);/** 请参考 PostVideoPacketByteBuffer说明*/public native int PostVideoPacketByteArray(long handle, int codec_id,byte[] packet, int offset, int size, long timestamp_ms, int is_timestamp_discontinuity, int is_key,byte[] extra_data, int extra_data_size, int width, int height);

PostVideoPacketByteBuffer()和PostVideoPacketByteArray()接口设计基本类似,唯一的区别在于,一个数据类型是ByteBuffer,一个是byte数组。

其中codec_id,系编码id,目前仅支持H.264和H.265类型。

 packet视频数据,需要注意的是,ByteBuffer必须是DirectBuffer, 包格式请参考H264/H265 Annex B Byte stream format, 例如:

0x00000001 nal_unit 0x00000001 ...
H264 IDR: 0x00000001 sps 0x00000001 pps 0x00000001 IDR_nal_unit .... 或 0x00000001 IDR_nal_unit ....
H265 IDR: 0x00000001 vps 0x00000001 sps 0x00000001 pps 0x00000001 IDR_nal_unit .... 或 0x00000001 IDR_nal_unit ....

extra_data: 可选参数,可传null, 对于H264关键帧包,如果packet不含sps和pps,可传0x00000001 sps 0x00000001 pps,对于H265关键帧包,如果packet不含vps,sps和pps, 可传0x00000001 vps 0x00000001 sps 0x00000001 pps

音频(AAC/PCMA/PCMU)投递接口设计如下:

    /*** 投递音频包给外部Live source, 注意ByteBuffer对象必须是DirectBuffer** @param handle: return value from SmartPlayerOpen()** @param codec_id: 编码id, 当前支持PCMA、PCMU和AAC, 65536:PCMA, 65537:PCMU, 65538:AAC* @param packet: 音频数据* @param offset:packet偏移量* @param size: packet size* @param pts_ms: 时间戳, 单位毫秒* @param is_pts_discontinuity: 是否时间戳间断,false:未间断,true:间断* @param extra_data: 如果是AAC的话,需要传 Audio Specific Configuration* @param extra_data_offset: extra_data 偏移量* @param extra_data_size: extra_data size* @param sample_rate: 采样率* @param channels: 通道数** @return {0} if successful*/public native int PostAudioPacket(long handle, int codec_id,java.nio.ByteBuffer packet, int offset, int size, long pts_ms, boolean is_pts_discontinuity,java.nio.ByteBuffer extra_data, int extra_data_offset, int extra_data_size, int sample_rate, int channels);/** 投递音频包给外部Live source, byte数组版本, 具体请参考PostAudioPacket** @param is_pts_discontinuity: 是否时间戳间断,0:未间断,1:间断* @return {0} if successful*/public native int PostAudioPacketByteArray(long handle, int codec_id,byte[] packet, int offset, int size, long pts_ms, int is_pts_discontinuity,byte[] extra_data, int extra_data_size, int sample_rate, int channels);

总结

通过以上描述,大家可以看到,GB/T 28181音视频数据源接入,无论是编码前还是编码后数据,或外部RTSP流数据,包括数据预览,如果有技术积累的话,实现起来也没那么麻烦,感兴趣的开发者,可以尝试看。

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

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

相关文章

金字塔原理(思考的逻辑)

前言&#xff1a;前面学习了表达的逻辑&#xff0c;那在表达之前&#xff0c;如何组织内容&#xff1f;如何进行思考&#xff1f;接下来看第二篇——思考的逻辑。 目录 应用逻辑顺序 时间顺序 结构顺序 程度顺序 概括各组思想 什么是概括&#xff1f; 思想表达方式 如…

C语言学习笔记---指针进阶01

C语言程序设计笔记---016 C语言指针进阶前篇1、字符指针2、指针数组2.1、指针数组例程1 -- 模拟一个二维数组2.2、指针数组例程2 3、数组指针3.1、回顾数组名&#xff1f;3.2、数组指针定义与初始化&#xff08;格式&#xff09;3.3、数组指针的作用 --- 常用于二维数组3.4、数…

Vue中使用element-plus中的el-dialog定义弹窗-内部样式修改-v-model实现-demo

效果图 实现代码 <template><el-dialog class"no-code-dialog" v-model"isShow" title"没有收到验证码&#xff1f;"><div class"nocode-body"><div class"tips">请尝试一下操作</div><d…

智能化巡护通信解决方案全天候保障油气管线

油气管道在我国油气资源运输过程中发挥着“大动脉"的角色&#xff0c;维护油气管道运输安全与国家能源安全、公共安全之间有着密不可分的联系。从这一角度来看&#xff0c;落实油气管道巡查保护工作的现实价值&#xff0c;在当前的实践中&#xff0c;无线通信解决方案需要…

C语言易错点整理

前言&#xff1a; 本文涵盖了博主在平常写C语言题目时经常犯的一些错误&#xff0c;在这里帮大家整理出来&#xff0c;一些易错点会帮大家标识出来&#xff0c;希望大家看完这篇文章后有所得&#xff0c;引以为戒~ 一、 题目&#xff1a; 解答&#xff1a; 首先在这个程序中…

Unity ProBuilder SetUVs 不起作用

ProBuilder SetUVs 不起作用 &#x1f41f; 需要设置face.manulUV true public static void Set01UV(this ProBuilderMesh mesh){foreach (var face in mesh.faces){face.manualUV true;//设置为手动uv}var vertices mesh.GetVertices().Select(v > v.position).ToArray(…

mq与mqtt的关系

文章目录 mqtt 与 mq的区别mqtt 与 mq的详细区别传统消息队列RocketMQ和微消息队列MQTT对比&#xff1a;MQ与RPC的区别 mqtt 与 mq的区别 mqtt&#xff1a;一种通信协议&#xff0c;规范 MQ&#xff1a;一种通信通道&#xff08;方式&#xff09;&#xff0c;也叫消息队列 MQ…

openCV实战-系列教程5:边缘检测(Canny边缘检测/高斯滤波器/Sobel算子/非极大值抑制/线性插值法/梯度方向/双阈值检测 )、原理解析、源码解读

打印一个图片可以做出一个函数&#xff1a; def cv_show(img,name):cv2.imshow(name,img)cv2.waitKey()cv2.destroyAllWindows() 1、Canny边缘检测流程 Canny是一个科学家在1986年写了一篇论文&#xff0c;所以用自己的名字来命名这个检测算法&#xff0c;Canny边缘检测算法…

微信小程序开发教学系列(3)- 页面设计与布局

3. 页面设计与布局 在微信小程序开发中&#xff0c;页面的设计和布局是非常重要的。一个好的页面设计可以提升用户体验&#xff0c;并使小程序更加吸引人。本章节将介绍如何设计和布局微信小程序的页面。 3.1 页面结构和样式的创建和设置 在创建微信小程序页面时&#xff0c…

C#-Tolewer和ToUpper的使用

目录 简介: 好处:​ 过程: 总结&#xff1a; 简介: 字符串是不可变的&#xff0c;所以这些函数都不会直接改变字符串的内容&#xff0c;而是把修改后的字符串的值通过函数返回值的形式返回。 ToLower和ToUpper是字符串处理函数&#xff0c;用于将字符中的英文字母转换为小…

使用DPO微调Llama2

简介 基于人类反馈的强化学习 (Reinforcement Learning from Human Feedback&#xff0c;RLHF) 事实上已成为 GPT-4 或 Claude 等 LLM 训练的最后一步&#xff0c;它可以确保语言模型的输出符合人类在闲聊或安全性等方面的期望。然而&#xff0c;它也给 NLP 引入了一些 RL 相关…

linux篇---使用systemctl start xxx启动自己的程序|开机启动|守护进程

linux篇---使用systemctl start xxx启动自己的程序|开机启动|守护进程 1、创建服务2、修改权限3、启动服务4、测试 机器&#xff1a;Nvidia Jetson Xavier系统&#xff1a;ubuntu 18.04 最近在使用symfony的console组件&#xff0c;需要执行一个后台的php进程&#xff0c;并且…

C++ Day5

目录 一、静态成员 1.1 概念 1.2 格式 1.3 银行账户实例 二、类的继承 2.1 目的 2.2 概念 2.3 格式 2.4 继承方式 2.5 继承中的特殊成员函数 2.5.1 构造函数 2.5.2析构函数 2.5.3 拷贝构造函数 2.5.4拷贝赋值函数 总结&#xff1a; 三、多继承 3.1 概念 3.2 格…

k8s--基本概念理解

必填字段 在要创建的 Kubernetes 对象的文件中.yaml&#xff0c;您需要设置以下字段的值&#xff1a; apiVersion- 您使用哪个版本的 Kubernetes API 创建此对象 kind- 你想创建什么样的对象 metadata- 有助于唯一标识对象的数据&#xff0c;包括name字符串、UID和可选namesp…

大数据领域都有什么发展方向

近年来越来越多的人选择大数据行业&#xff0c;大数据行业前景不错薪资待遇好&#xff0c;各大名企对于大数据人才需求不断上涨。 大数据从业领域很宽广&#xff0c;不管是科技领域还是食品产业&#xff0c;零售业等都是需要大数据人才进行大数据的处理&#xff0c;以提供更好…

Python调用文心一言的API

最近申请了文心一言的key&#xff0c;然后尝试调用了一下文心一言&#xff0c;这里使用一个简单的方式来调用文心一言&#xff1a; pip install paddle-pipelinesfrom pipelines.nodes import ErnieBotapi_key "your apply key" secret_key "your apply secr…

【python】Leetcode(primer-set)

文章目录 78. 子集&#xff08;集合的所有子集&#xff09;90. 子集 II&#xff08;集合的所有子集&#xff09;792. 匹配子序列的单词数&#xff08;判断是否为子集&#xff09;500. 键盘行&#xff08;集合的交集&#xff09;409. 最长回文串&#xff08;set&#xff09; 更多…

测试先行:探索测试驱动开发的深层价值

引言 在软件开发的世界中,如何确保代码的质量和可维护性始终是一个核心议题。测试驱动开发(TDD)为此提供了一个答案。与传统的开发方法相比,TDD鼓励开发者从用户的角度出发,先定义期望的结果,再进行实际的开发。这种方法不仅可以确保代码满足预期的需求,还可以在整个开…

IBM Db2 笔记

目录 1. IBM Db2 笔记1.1. 常用命令1.2. 登录命令行模式 (Using the Db2 command line processor)1.3. issue1.3.1. db2: command not found/SQL10007N Message "-1390" could not be retreived. Reason code: "3".1.3.2. db2 修改 dbm cfg 的时候报 SQL50…

C++ Day4

目录 一、拷贝赋值函数 1.1 作用 1.2 格式 二、匿名对象 2.2 格式 三、友元 3.1作用 3.2格式 3.3 种类 3.4 全局函数做友元 3.5类做友元 3.6 成员函数做友元 3.7注意 四、常成员函数和常对象 4.1 常成员函数 4.1.1格式 示例&#xff1a; 4.2 常对象 作用&…