Android平台外部编码数据(H264/H265/AAC/PCMA/PCMU)实时预览播放技术实现

简介: 好多开发者可能疑惑,外部数据实时预览播放,到底有什么用?是的,一般场景是用不到的,我们在开发这块前几年已经开发了非常稳定的RTMP、RTSP直播播放模块,不过也遇到这样的场景,部分设备输出编码后(视频:H.264/H.265,音频:AAC/PCMA/PCMU)的数据,比如无人机或部分智能硬件设备,回调出来的H.264/H.265数据,除了想转推到RTMP、轻量级RTSP服务或GB28181外,还需要本地预览甚至对数据做二次处理(视频分析、实时水印字符叠加等,然后二次编码),基于这样的场景诉求,我们开发了Android平台外部编码数据实时预览播放模块。

开发背景

好多开发者可能疑惑,外部数据实时预览播放,到底有什么用?


是的,一般场景是用不到的,我们在开发这块前几年已经开发了非常稳定的RTMP、RTSP直播播放模块,不过也遇到这样的场景,部分设备输出编码后(视频:H.264/H.265,音频:AAC/PCMA/PCMU)的数据,比如无人机或部分智能硬件设备,回调出来的H.264/H.265数据,除了想转推到RTMP、轻量级RTSP服务或GB28181外,还需要本地预览甚至对数据做二次处理(视频分析、实时水印字符叠加等,然后二次编码),基于这样的场景诉求,我们开发了Android平台外部编码数据实时预览播放模块。

cfd2a373be904ed7a50ca38ab03a331f.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)投递接口设计如下:

  // SmartPlayerJniV2.java
    // Author: daniusdk.com
    /**
   * 投递音频包给外部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);

调用逻辑

下面我们看看逻辑调用,本文基于大牛直播SDK的RTSP|RTMP转RTMP推送demo做展示,先拉取到RTSP或RTMP的流数据,然后把拉取到的H.264/H.265视频数据和AAC音频数据,然后回调上来,调用我们外部音视频live source对接接口,投递到底层,实现实时音视频数据的播放,如果外部数据,可以忽略拉流这块,直接在数据回调的地方,调live source数据投递接口即可。

59bf2981184b401c963dc267ec44d91d.jpg

video数据投递:

    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 (0 == ret) {
        if (ex_live_src_player_handle_ != 0) {
          if (ex_live_src_player_read_lock_.tryLock()) {
            try {
              if (ex_live_src_player_handle_ != 0) {
                libPlayer.PostVideoPacketByteBuffer(ex_live_src_player_handle_, video_codec_id, video_buffer_, 0, sample_size, timestamp, 0, is_key_frame, null,0, 0, 0);
              }
            }finally {
              ex_live_src_player_read_lock_.unlock();
            }
          }
        }
      }
    }


audio数据投递:

    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 (0 == ret && !ex_live_src_player_mute_) {
        if (ex_live_src_player_handle_ != 0) {
          if (ex_live_src_player_read_lock_.tryLock()) {
            try {
              if (ex_live_src_player_handle_ != 0) {
                libPlayer.PostAudioPacket(ex_live_src_player_handle_, audio_codec_id, audio_buffer_, 0, sample_size,
                    timestamp, false,parameter_info_,0, parameter_info_size, sample_rate, channel);
              }
            }finally {
              ex_live_src_player_read_lock_.unlock();
            }
          }
        }
      }
    }


启动外部数据播放:


可以看到,外部数据可以用软解码或硬解码播放,如果分辨率很大可以考虑特定机型硬解码,外部数据播放,依然可以设置铺满或按比例显示。如果需要针对数据做二次处理,也可以把设置RGB或YUV数据回调,对回调后的数据做二次处理,甚至二次编码(如做视频分析、实时水印等)。

  private long start_ex_live_src_player(SmartPlayerJniV2 lib_player, Context context, SurfaceView surface_view, boolean is_mute, boolean is_hardware_decoder) {
    if (null ==lib_player || null == context || null == surface_view)
      return 0;
    long handle = lib_player.SmartPlayerOpen(context);
    if (0 == handle) {
      Log.e(TAG, "start_ex_live_src_player open player failed");
      return 0;
    }
    // 设置0, 尽可能降低预览延时
    lib_player.SmartPlayerSetBuffer(handle, 0);
    lib_player.SmartPlayerSetUrl(handle, "ntexternal://livesource/implemention0");
    lib_player.SmartPlayerSetSurface(handle, surface_view);
    // 图像等比例缩放或铺满view
    lib_player.SmartPlayerSetRenderScaleMode(handle, 1);
    lib_player.SmartPlayerSetFastStartup(handle, 1);
    lib_player.SmartPlayerSetAudioOutputType(handle, 1);
    // 不要播放音频,静音就好
    lib_player.SmartPlayerSetMute(handle, is_mute?1:0);
    // 大分辨率可能需要硬解,小分辨率推荐软解,硬解延时可能大些
    if (is_hardware_decoder) {
      lib_player.SetSmartPlayerVideoHevcHWDecoder(handle, 1);
      lib_player.SetSmartPlayerVideoHWDecoder(handle, 1);
    }
    // 有些场景可能需要解码出来的图像用来做分析或重新编码
    // 这里可以设置yuv或rgb callback, 把图像给Caller
    // lib_player.SmartPlayerSetExternalRender(handle, new RGBAExternalRender());
    // lib_player.SmartPlayerSetExternalRender(handle, new I420ExternalRender());
    if (0 == lib_player.SmartPlayerStartPlay(handle))
      return handle;
    lib_player.SmartPlayerClose(handle);
    return 0;
  }

停止外部数据播放:

  private void stop_ex_live_src_play(SmartPlayerJniV2 lib_player, long handle) {
    if (null == lib_player)
      return;
    if (0 == handle)
      return;
    lib_player.SmartPlayerStopPlay(handle);
    lib_player.SmartPlayerClose(handle);
  }


总结

Android平台外部编码后H.264/H.265/AAC/PCMA/PCMU数据实时预览播放,在一些传统行业里面,可以说是意义非常大,除了可以预览回调过来的数据外,还可以针对外部数据做二次视频分析、二次编辑投递(实时水印、字符叠加等),感兴趣的开发者可以试试看。

相关文章
|
7天前
|
消息中间件 网络协议 Java
Android 开发中实现数据传递:广播和Handler
Android 开发中实现数据传递:广播和Handler
12 1
|
8天前
|
Android开发
Android MediaTek 平台增加UART接口的红外模块支持,支持NEC红外遥控
Android MediaTek 平台增加UART接口的红外模块支持,支持NEC红外遥控
11 0
|
2月前
|
API 开发工具 Android开发
iOS 和 Android 平台的开发有哪些主要区别?
iOS与Android开发区别:iOS用Objective-C/Swift,App Store唯一下载渠道;Android用Java/Kotlin,多商店发布(如Google Play、华为市场)。设计上,iOS简洁一致,Android灵活可定制。开发工具,iOS用Xcode,Android用Android Studio。硬件和系统多样性,iOS统一,Android复杂。权限管理、审核流程及API各有特点,开发者需依据目标平台特性进行选择。
29 3
|
7天前
|
Android开发 内存技术
Android 通过tinyalsa调试解决录制和播放音频问题
Android 通过tinyalsa调试解决录制和播放音频问题
23 1
|
7天前
|
存储 Linux Android开发
Android存储分区与Rockchip平台的分区命名及U-Boot配置
Android存储分区与Rockchip平台的分区命名及U-Boot配置
13 0
|
8天前
|
存储 安全 Ubuntu
Android 生成平台应用签名keystore文件
Android 生成平台应用签名keystore文件
8 0
|
14天前
|
Android开发 开发者
Android网络和数据交互: 请解释Android中的AsyncTask的作用。
Android's AsyncTask simplifies asynchronous tasks for brief background work, bridging UI and worker threads. It involves execute() for starting tasks, doInBackground() for background execution, publishProgress() for progress updates, and onPostExecute() for returning results to the main thread.
11 0
|
14天前
|
网络协议 安全 API
Android网络和数据交互: 什么是HTTP和HTTPS?在Android中如何进行网络请求?
HTTP和HTTPS是网络数据传输协议,HTTP基于TCP/IP,简单快速,HTTPS则是加密的HTTP,确保数据安全。在Android中,过去常用HttpURLConnection和HttpClient,但HttpClient自Android 6.0起被移除。现在推荐使用支持TLS、流式上传下载、超时配置等特性的HttpsURLConnection进行网络请求。
11 0
|
22天前
|
JSON 安全 Java
Android网络部分-----网络数据请求、解析
Android网络部分-----网络数据请求、解析
Android网络部分-----网络数据请求、解析
|
28天前
|
XML Java Android开发
Android每点击一次按钮就添加一条数据
Android每点击一次按钮就添加一条数据
24 1