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

本文涉及的产品
可视分析地图(DataV-Atlas),3 个项目,100M 存储空间
简介: 我们在对接GB28181设备接入模块的时候,遇到这样的技术诉求,好多开发者期望能提供编码后(H.264/H.265、AAC/PCMA)数据对接,确保外部采集设备,比如无人机类似回调过来的数据,直接通过模块,对接到GB28181平台侧,此外,还期望不支持或者内网没有外部网络权限的RTSP设备,也能间接接入到国标平台。

技术背景

我们在对接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平台

b76bb4b87a3b4e13a2f435fc99a12990.jpg

简单那来说,把摄像机的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;
    @Override
    public 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_;
    }
    @Override
    public 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;
    @Override
    public 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平台外部编码数据实时预览播放模块。

dddc1d8ba481438f836f87abef743c59.png

外部(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流数据,包括数据预览,如果有技术积累的话,实现起来也没那么麻烦,感兴趣的开发者,可以尝试看。

相关实践学习
DataV Board用户界面概览
本实验带领用户熟悉DataV Board这款可视化产品的用户界面
阿里云实时数仓实战 - 项目介绍及架构设计
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3&nbsp;)前置知识要求 &nbsp; 课程大纲 第一章&nbsp;了解数据仓库概念 初步了解数据仓库是干什么的 第二章&nbsp;按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章&nbsp;数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章&nbsp;采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章&nbsp;用户行为数据仓库 严格按照企业的标准开发 第六章&nbsp;搭建业务数仓理论基础和对表的分类同步 第七章&nbsp;业务数仓的搭建&nbsp; 业务行为数仓效果图&nbsp;&nbsp;
相关文章
|
编解码 监控 API
Android平台GB28181设备接入侧音频采集推送示例
GB/T28181是广泛应用于视频监控行业的标准协议规范,可以在不同设备之间实现互联互通。今天我们主要探讨Android平台的Audio采集部分。
134 1
|
数据采集 前端开发 Android开发
Android平台RTMP推送或GB28181设备接入端如何实现采集audio音量放大?
我们在做Android平台RTMP推送和GB28181设备对接的时候,遇到这样的问题,有的设备,麦克风采集出来的audio,音量过高或过低,特别是有些设备,采集到的麦克风声音过低,导致播放端听不清前端采集的audio,这时候,就需要针对采集到的audio,做音量放大处理。
|
Android开发 开发者
Android平台GB28181设备接入端如何实现本地录像?
实现Android平台GB28181设备接入的时候,有个功能点不可避免,那就是本地录像,实际上,在实现GB28181设备接入模块之前,我们前些年做RTMP推送和轻量级RTSP服务的时候,早已经实现了本地录像功能。
108 0
|
4月前
|
开发工具 Android开发 开发者
Android平台如何不推RTMP|不发布RTSP流|不实时录像|不回传GB28181数据时实时快照?
本文介绍了一种在Android平台上实现实时截图快照的方法,尤其适用于无需依赖系统接口的情况,如在RTMP推送、RTSP服务或GB28181设备接入等场景下进行截图。通过底层模块(libSmartPublisher.so)实现了截图功能,封装了`SnapShotImpl.java`类来管理截图流程。此外,提供了关键代码片段展示初始化SDK实例、执行截图、以及在Activity销毁时释放资源的过程。此方案还考虑到了快照数据的灵活处理需求,符合GB/T28181-2022的技术规范。对于寻求更灵活快照机制的开发者来说,这是一个值得参考的设计思路。
|
4月前
|
编解码 网络协议 Android开发
Android平台GB28181设备接入模块实现后台service按需回传摄像头数据到国标平台侧
我们在做Android平台GB28181设备对接模块的时候,遇到这样的技术需求,开发者希望能以后台服务的形式运行程序,国标平台侧没有视频回传请求的时候,仅保持信令链接,有发起视频回传请求或语音广播时,打开摄像头,并实时回传音视频数据或接收处理国标平台侧发过来的语音广播数据。
|
4月前
|
编解码 网络协议 前端开发
如何实现Android平台GB28181设备接入模块按需打开摄像头并回传数据
后台采集摄像头,如果想再进一步扩展,可以把android平台gb28181的camera2 demo,都移植过来,实现功能更强大的国标设备侧,这里主要是展示,收到国标平台侧的回传请求后,才打开摄像头,才开始编码打包,最大限度的减少资源的占用
|
存储 前端开发 Android开发
GB28181设备接入侧录像查询和录像下载技术探究之实时录像
我们在对接GB28181设备接入侧的时候,除了常规实时音视频按需上传外,还有个重要的功能,就是本地实时录像,录像后的数据,在执法记录仪等前端设备留底,然后,到工作站拷贝到专门的平台。
245 1
|
Android开发
Android平台GB28181设备接入端如何实现多视频通道接入?
GB28181设备接入端如何实现多视频通道接入?
|
编解码 监控 网络协议
Android平台GB28181设备接入侧如何实现按需打开视音频采集传输
Android平台GB28181设备接入侧如何实现按需打开视音频采集传输
164 2
|
编解码 Android开发 数据安全/隐私保护
Android平台GB28181设备接入端对接编码前后音视频源类型浅析
今天主要对Android平台GB28181设备接入模块支持的接入数据类型,做个简单的汇总: 1. 编码前数据(目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型),其中,Android平台前后摄像头数据,或者屏幕数据,或者Unity拿到的数据,均属编码前数据; 2. 编码后数据(如无人机等264/HEVC数据,或者本地解析的MP4音视频数据); 3. 拉取RTSP或RTMP流并接入至GB28181平台(比如其他IPC的RTSP流,可通过Android平台GB28181接入到国标平台)。