Android国标接入端如何播放GB28181平台端语音广播数据

简介: GB28181语音广播这块,我们依据GB/T28181-2016针对流程和实例代码,做过详细的描述,本次主要是探讨下,广播数据过来后,如何处理。

GB28181语音广播这块,我们依据GB/T28181-2016针对流程和实例代码,做过详细的描述,本次主要是探讨下,广播数据过来后,如何处理。


鉴于我们之前有非常成熟的RTMP|RTSP低延迟播放模块,语音广播数据过来后,调用startAudioPlay(),ntsOnInviteAudioBroadcastResponse()处理如下:

@Override
public void ntsOnInviteAudioBroadcastResponse(String sourceID, String targetID, int statusCode, PlaySessionDescription sessionDescription) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG, "ntsOnInviteAudioBroadcastResponse, statusCode:" + status_code_ +" sourceID:" + source_id_ + ", targetID:" + target_id_);
      boolean is_need_destory_rtp = true;
      if (gb28181_agent_ != null ) {
        boolean is_need_bye = 200==status_code_;
        if (200 == status_code_ && session_description_ != null && rtp_receiver_handle_ != 0 ) {
          MediaSessionDescription audio_des = session_description_.getAudioDescription();
          SDPRtpMapAttribute audio_attr = null;
          if (audio_des != null && audio_des.getRtpMapAttributes() != null && !audio_des.getRtpMapAttributes().isEmpty() )
            audio_attr = audio_des.getRtpMapAttributes().get(0);
          if ( audio_des != null && audio_attr != null ) {
            lib_player_.SetRTPReceiverSSRC(rtp_receiver_handle_, audio_des.getSSRC());
            lib_player_.SetRTPReceiverPayloadType(rtp_receiver_handle_, audio_attr.getPayloadType(),
                audio_attr.getEncodingName(), 2, audio_attr.getClockRate());
            // 如果是PCMA, SDK会默认填 采样率8000, 通道1, 其他音频编码需要手动填入
            // lib_player_.SetRTPReceiverAudioSamplingRate(rtp_receiver_handle_, 8000);
            // lib_player_.SetRTPReceiverAudioChannels(rtp_receiver_handle_, 1);
            lib_player_.SetRTPReceiverRemoteAddress(rtp_receiver_handle_, audio_des.getAddress(), audio_des.getPort());
            lib_player_.InitRTPReceiver(rtp_receiver_handle_);
            if (startAudioPlay()) {
              is_need_bye = false;
              is_need_destory_rtp = false;
              gb_broadcast_source_id_ = source_id_;
              gb_broadcast_target_id_ = target_id_;
              btnGB28181AudioBroadcast.setText("终止GB28181语音广播");
              btnGB28181AudioBroadcast.setEnabled(true);
            }
          }
        } else {
          btnGB28181AudioBroadcast.setText("GB28181语音广播");
        }
        if (is_need_bye)
          gb28181_agent_.byeAudioBroadcast(source_id_, target_id_);
      }
      if (is_need_destory_rtp)
        destoryRTPReceiver();
    }
    private String source_id_;
    private String target_id_;
    private int status_code_;
    private PlaySessionDescription session_description_;
    public Runnable set(String source_id, String target_id, int status_code, PlaySessionDescription session_description) {
      this.source_id_ = source_id;
      this.target_id_ = target_id;
      this.status_code_ = status_code;
      this.session_description_ = session_description;
      return this;
    }
  }.set(sourceID, targetID, statusCode, sessionDescription),0);
}

startAudioPlay()初始化实例后,为了保证低延迟,拉流端设置0 buffer,处于调试方便,设置download speed回调2-5秒一次(可以看到是不是有音频数据过来),由于只需要播放音频,不需要视频,所以不要设置surface下去,然后设置拉流数据回调,需要注意的是,拉到的audio数据,不要转aac输出:

private boolean startAudioPlay() {
  if (player_handle_ != 0 )
    return false;
  player_handle_ = lib_player_.SmartPlayerOpen(context_);
  if (player_handle_ == 0)
    return false;
  lib_player_.SetSmartPlayerEventCallbackV2(player_handle_,new EventHandePlayerV2());
  // 缓存大小可以调整
  lib_player_.SmartPlayerSetBuffer(player_handle_, 0);
  // lib_player_.SmartPlayerSetFastStartup(player_handle_, 0);
  // set report download speed(默认2秒一次回调 用户可自行调整report间隔)
  lib_player_.SmartPlayerSetReportDownloadSpeed(player_handle_, 1, 20);
  lib_player_.SmartPlayerClearRtpReceivers(player_handle_);
  lib_player_.SmartPlayerAddRtpReceiver(player_handle_, rtp_receiver_handle_);
  lib_player_.SmartPlayerSetSurface(player_handle_, null);
  // lib_player_.SmartPlayerSetRenderScaleMode(player_handle_, 1);
  lib_player_.SmartPlayerSetAudioOutputType(player_handle_, 1);
  lib_player_.SmartPlayerSetMute(player_handle_, 0);
  lib_player_.SmartPlayerSetAudioVolume(player_handle_, 100);
  lib_player_.SmartPlayerSetExternalAudioOutput(player_handle_, new PlayerExternalPCMOutput());
  lib_player_.SmartPlayerSetUrl(player_handle_, "rtp://ntinternal/rtpreceiver/implemention0");
  if (0 != lib_player_.SmartPlayerStartPlay(player_handle_)) {
    lib_player_.SmartPlayerClose(player_handle_);
    player_handle_ = 0;
    Log.e(TAG,  "start audio paly failed");
    return false;
  }
  lib_player_.SmartPlayerSetAudioDataCallback(player_handle_, new PlayerAudioDataOutput());
  lib_player_.SmartPlayerSetPullStreamAudioTranscodeAAC(player_handle_, 0);
  if (0 ==lib_player_.SmartPlayerStartPullStream(player_handle_) ) {
    // 启动定时器,长时间收不到音频数据,则停止播放,发送BYE
    last_received_audio_data_time_.set(SystemClock.elapsedRealtime());
    handler_.postDelayed(new AudioPlayerPCMTimer(player_handle_), AudioPlayerPCMTimer.INTERVAL_MS);
  }
  return true;
}

调用StartPlay后,拿到的audio数据,塞到publisher端,做回音消除处理:

class PlayerExternalPCMOutput implements NTExternalAudioOutput {
  private int buffer_size_ = 0;
  private ByteBuffer pcm_buffer_ = null;
  @Override
  public ByteBuffer getPcmByteBuffer(int size)  {
    //Log.i("getPcmByteBuffer", "size: " + size);
    if(size < 1)
      return null;
    if(buffer_size_ != size) {
      buffer_size_ = size;
      pcm_buffer_ = ByteBuffer.allocateDirect(buffer_size_);
    }
    return pcm_buffer_;
  }
  public void onGetPcmFrame(int ret, int sampleRate, int channel, int sampleSize, int is_low_latency) {
    /*Log.i("onGetPcmFrame", "ret: " + ret + ", sampleRate: " + sampleRate + ", channel: " + channel + ", sampleSize: " + sampleSize +
        ",is_low_latency:" + is_low_latency + " buffer_size:" + buffer_size);*/
    if (null == pcm_buffer_)
      return;
    pcm_buffer_.rewind();
    if (ret == 0 && isGB28181StreamRunning && publisherHandle != 0 )
      libPublisher.SmartPublisherOnFarEndPCMData(publisherHandle, pcm_buffer_, sampleRate, channel, sampleSize, is_low_latency);
  }
}
private static int align(int d, int a) { return (d + (a - 1)) & ~(a - 1); }
class PlayerAudioDataOutput implements NTAudioDataCallback {
  private int buffer_size_ = 0;
  private int param_info_size_ = 0;
  private ByteBuffer 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 <= buffer_size_ && buffer_ != null )
      return buffer_;
    buffer_size_ = align(size + 256, 16);
    buffer_ = ByteBuffer.allocateDirect(buffer_size_);
    // Log.i("getAudioByteBuffer", "size: " + size + " buffer_size:" + audio_buffer_size);
    return 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_ = align(size + 32, 16);
    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)  {
    /*Log.i("onAudioDataCallback", "ret: " + ret + ", audio_codec_id: " + audio_codec_id + ", sample_size: " + sample_size + ", timestamp: " + timestamp +
        ",sample_rate:" + sample_rate);
     */
    last_received_audio_data_time_.set(SystemClock.elapsedRealtime());
  }
}


如果长时间收不到数据,主动断掉音频广播:

class AudioPlayerPCMTimer implements Runnable {
  public static final int THRESHOLD_MS = 60*1000; // 暂时设置到1分钟
  public static final int INTERVAL_MS = 10*1000; // 十秒一次, 太频繁影响主线程
  public AudioPlayerPCMTimer(long handle) {
    handle_ = handle;
  }
  @Override
  public void run() {
    if (0 == handle_)
      return;
    if (handle_ != player_handle_) {
      Log.i(TAG, "AudioPlayerPCMTimer handle changed, will stop this timer, handle:" + handle_ + " new handle:" + player_handle_);
      return;
    }
    long last_update_time = last_received_audio_data_time_.get();
    long cur_time = SystemClock.elapsedRealtime();
    // Log.i(TAG, "AudioPlayerPCMTimer last_update_time:" + last_update_time + " cur_time:" + cur_time);
    if ( (last_update_time + this.THRESHOLD_MS) >  cur_time) {
      // 继续定时器
      handler_.postDelayed(new AudioPlayerPCMTimer(this.handle_), this.INTERVAL_MS);
      //  Log.i(TAG, "AudioPlayerPCMTimer running.");
    }
    else {
      Log.i(TAG, "AudioPlayerPCMTimer,trigger threshold, bye audio, stop player.");
      if (gb_broadcast_source_id_ != null && gb_broadcast_target_id_ != null) {
        if (gb28181_agent_ != null)
          gb28181_agent_.byeAudioBroadcast(gb_broadcast_source_id_, gb_broadcast_target_id_);
      }
      gb_broadcast_source_id_ = null;
      gb_broadcast_target_id_ = null;
      stopAudioPlayer();
      destoryRTPReceiver();
      btnGB28181AudioBroadcast.setText("GB28181语音广播");
      btnGB28181AudioBroadcast.setEnabled(false);
    }
  }
  private long handle_;
}


停止广播数据播放:

private void stopAudioPlayer() {
  if (player_handle_ != 0 ) {
    lib_player_.SmartPlayerStopPullStream(player_handle_);
    lib_player_.SmartPlayerStopPlay(player_handle_);
    lib_player_.SmartPlayerClose(player_handle_);
    player_handle_ = 0;
  }
}


销毁RTPReceiver:

private void destoryRTPReceiver() {
  if (rtp_receiver_handle_ != 0) {
    lib_player_.UnInitRTPReceiver(rtp_receiver_handle_);
    lib_player_.DestoryRTPReceiverSession(rtp_receiver_handle_);
    lib_player_.DestoryRTPReceiver(rtp_receiver_handle_);
    rtp_receiver_handle_ = 0;
  }
}


以上是针对GB28181平台端音频广播播放的一点说明,感兴趣的开发者,可以酌情参考,也可以和我探讨Android平台GB28181接入模块的测试。

相关文章
|
1月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
166 4
|
2月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
122 1
|
3月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
22天前
|
IDE 开发工具 Android开发
移动应用开发之旅:探索Android和iOS平台
在这篇文章中,我们将深入探讨移动应用开发的两个主要平台——Android和iOS。我们将了解它们的操作系统、开发环境和工具,并通过代码示例展示如何在这两个平台上创建一个简单的“Hello World”应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧,帮助你更好地理解和掌握移动应用开发。
50 17
|
1月前
|
Android开发
布谷语音软件开发:android端语音软件搭建开发教程
语音软件搭建android端语音软件开发教程!
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
116 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
3月前
|
开发工具 Android开发 iOS开发
安卓与iOS开发环境对比:选择适合你的平台
【9月更文挑战第26天】在移动应用开发的广阔天地中,安卓和iOS是两大巨头。它们各自拥有独特的优势和挑战,影响着开发者的选择和决策。本文将深入探讨这两个平台的开发环境,帮助你理解它们的核心差异,并指导你根据个人或项目需求做出明智的选择。无论你是初学者还是资深开发者,了解这些平台的异同都至关重要。让我们一起探索,找到最适合你的那片开发天地。
|
2月前
|
存储 大数据 数据库
Android经典面试题之Intent传递数据大小为什么限制是1M?
在 Android 中,使用 Intent 传递数据时存在约 1MB 的大小限制,这是由于 Binder 机制的事务缓冲区限制、Intent 的设计初衷以及内存消耗和性能问题所致。推荐使用文件存储、SharedPreferences、数据库存储或 ContentProvider 等方式传递大数据。
100 0
|
3月前
|
Android开发 开发者
Android平台无纸化同屏如何实现实时录像功能
Android平台无纸化同屏,如果需要本地录像的话,实现难度不大,只要复用之前开发的录像模块的就可以,对我们来说,同屏采集这块,只是数据源不同而已,如果是自采集的其他数据,我们一样可以编码录像。
|
3月前
|
安全 API 开发工具
Android平台RTMP推送|轻量级RTSP服务如何实现麦克风|扬声器声音采集切换
Android平台扬声器播放声音的采集,在无纸化同屏等场景下,意义很大,早期低版本的Android设备,是没法直接采集扬声器audio的(从Android 10开始支持),所以,如果需要采集扬声器audio,需要先做系统版本判断,添加相应的权限。