Android平台RTMP多实例推送的几种情况探讨

简介: 好多开发者提到,如何实现Android平台,多实例推送,多实例推送,有几种理解:1. 多路编码,多个实例分别推送到不同的RTMP URL(如Android采集板卡同时接2路出去);2. 同一路编码,多个实例分别推送到不同的RTMP URL(如推送到内网、外网不同的RTMP服务器);3. 部分路编码、部分路对接编码后的H.264/AAC数据,多个实例分别推送到不同的RTMP URL(混合推)。

好多开发者提到,如何实现Android平台,多实例推送,多实例推送,有几种理解:


  1. 多路编码,多个实例分别推送到不同的RTMP URL(如Android采集板卡同时接2路出去);
  2. 同一路编码,多个实例分别推送到不同的RTMP URL(如推送到内网、外网不同的RTMP服务器);
  3. 部分路编码、部分路对接编码后的H.264/AAC数据,多个实例分别推送到不同的RTMP URL(混合推)。


目前,市面上的大多设计,都不够灵活,以下以“Android回调编码后的音视频数据”为例,推送一路原始的编码后的RTMP数据出去,然后,编码后的数据,回调到上层,再启动一个新的Publisher实例,推到新的RTMP地址(当然只是业务展示,实际可用于对接第三方系统,如GB28181或其他服务):


具体流程如下:


  1. 设置音视频callback


对应接口:

  /**
   * Set Audio Encoded Data Callback.
   *
   * @param audio_encoded_data_callback: Audio Encoded Data Callback.
   *
   * @return {0} if successful
   */
  public native int SmartPublisherSetAudioEncodedDataCallback(long handle, Object audio_encoded_data_callback);
  /**
   * Set Video Encoded Data Callback.
   *
   * @param video_encoded_data_callback: Video Encoded Data Callback.
   *
   * @return {0} if successful
   */
  public native int SmartPublisherSetVideoEncodedDataCallback(long handle, Object video_encoded_data_callback);
设置回调:
libPublisher.SmartPublisherSetAudioEncodedDataCallback(publisherHandle, new PublisherAudioEncodedDataCallback());
libPublisher.SmartPublisherSetVideoEncodedDataCallback(publisherHandle, new PublisherVideoEncodedDataCallback());
  1. 实现 PublisherAudioEncodedDataCallback 和 PublisherVideoEncodedDataCallback:
    class PublisherAudioEncodedDataCallback 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)
        {
            Log.i("onAudioDataCallback", "ret: " + ret + ", audio_codec_id: " + audio_codec_id + ", sample_size: " + sample_size + ", timestamp: " + timestamp +
                ",sample_rate:" + sample_rate + ",chn: " + channel + ", parameter_info_size:" + parameter_info_size);
            if ( audio_buffer_ == null)
                return;
            audio_buffer_.rewind();
            if ( ret == 0 && publisherHandle2 != 0 ) {
                libPublisher.SmartPublisherPostAudioEncodedData(publisherHandle2, audio_codec_id, audio_buffer_, sample_size, is_key_frame, timestamp, parameter_info_, parameter_info_size);
            }
        }
    }
    class PublisherVideoEncodedDataCallback implements NTVideoDataCallback
    {
        private int video_buffer_size = 0;
        private ByteBuffer video_buffer_ = null;
        @Override
        public ByteBuffer getVideoByteBuffer(int size)
        {
            //Log.i("getVideoByteBuffer", "size: " + 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);
            // Log.i("getVideoByteBuffer", "size: " + size + " buffer_size:" + 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)
        {
            Log.i("onVideoDataCallback", "ret: " + ret + ", video_codec_id: " + video_codec_id + ", sample_size: " + sample_size + ", is_key_frame: "+ is_key_frame +  ", timestamp: " + timestamp +
                ",width: " + width + ", height:" + height + ",presentation_timestamp:" + presentation_timestamp);
            if ( video_buffer_ == null)
                return;
            video_buffer_.rewind();
            if ( ret == 0 && publisherHandle2 !=0 ) {
                libPublisher.SmartPublisherPostVideoEncodedData(publisherHandle2, video_codec_id, video_buffer_, sample_size, is_key_frame, timestamp, presentation_timestamp);
            }
        }
    }
  1. 提供开始回调数据和停止回调数据接口:
  /**
   * Start output Encoded Data(用于编码后的音视频数据回调)
   *
   * @return {0} if successful
   */
  public native int SmartPublisherStartOutputEncodedData(long handle);
  /**
   *  Stop output Encoded Data
   *
   * @return {0} if successful
   */
  public native int SmartPublisherStopOutputEncodedData(long handle);
  1. 上层demo调用实例:
    class ButtonEncodedDataCallbackListener implements OnClickListener {
        public void onClick(View v) {
            if (isEncodedDatacallbackRunning) {
                stopEncodedDataCallback();
                if (!isPushing && !isRTSPPublisherRunning && !isRecording) {
                    ConfigControlEnable(true);
                }
                btnEncodedDataCallback.setText("启动编码数据回调");
                isEncodedDatacallbackRunning = false;
                if (publisherHandle2 != 0) {
                   libPublisher.SmartPublisherStopPublisher(publisherHandle2);
                    libPublisher.SmartPublisherClose(publisherHandle2);
                    publisherHandle2 = 0;
                }
                return;
            }
            Log.i(TAG, "onClick start encoded data callback..");
            if (libPublisher == null)
                return;
            if (!isPushing && !isRTSPPublisherRunning && !isRecording) {
                InitAndSetConfig();
            }
            libPublisher.SmartPublisherSetAudioEncodedDataCallback(publisherHandle, new PublisherAudioEncodedDataCallback());
            libPublisher.SmartPublisherSetVideoEncodedDataCallback(publisherHandle, new PublisherVideoEncodedDataCallback());
            int startRet = libPublisher.SmartPublisherStartOutputEncodedData(publisherHandle);
            if (startRet != 0) {
                isEncodedDatacallbackRunning = false;
                Log.e(TAG, "Failed to start encoded data callback.");
                return;
            }
            if (!isPushing && !isRTSPPublisherRunning && !isRecording) {
                if (pushType == 0 || pushType == 1) {
                    CheckInitAudioRecorder();    //enable pure video publisher..
                }
                ConfigControlEnable(false);
            }
            btnEncodedDataCallback.setText("停止编码数据回调");
            isEncodedDatacallbackRunning = true;
            int audio_opt = 2;
            int video_opt = 2;
            publisherHandle2 = libPublisher.SmartPublisherOpen(myContext, audio_opt, video_opt,
                    videoWidth, videoHeight);
            if (publisherHandle2 == 0) {
                Log.e(TAG, "sdk open failed!");
                return;
            }
            String relayUrl = "rtmp://player.daniulive.com:1935/hls/stream8888";
            libPublisher.SmartPublisherSetURL(publisherHandle2, relayUrl);
            libPublisher.SmartPublisherStartPublisher(publisherHandle2);
        }
    }
    ;
    //停止编码后数据回调
    private void stopEncodedDataCallback() {
        if(!isEncodedDatacallbackRunning)
        {
            return;
        }
        if (!isPushing && !isRTSPPublisherRunning && !isRecording) {
            if (audioRecord_ != null) {
                Log.i(TAG, "stopRecorder, call audioRecord_.StopRecording..");
                audioRecord_.Stop();
                if (audioRecordCallback_ != null) {
                    audioRecord_.RemoveCallback(audioRecordCallback_);
                    audioRecordCallback_ = null;
                }
                audioRecord_ = null;
            }
        }
        if (libPublisher != null) {
            libPublisher.SmartPublisherStopOutputEncodedData(publisherHandle);
        }
        if (!isPushing && !isRTSPPublisherRunning && !isRecording) {
            if (publisherHandle != 0) {
                if (libPublisher != null) {
                    libPublisher.SmartPublisherClose(publisherHandle);
                    publisherHandle = 0;
                }
            }
        }
    }

以上demo为了便于演示多实例效果,另启了个新的推送实例(对应新的publisherHandle),音视频编码后的数据,通过新的实例,调用编码后的音视频数据接口,继续推RTMP出去,从而实现多实例推送目的。

相关文章
|
2月前
|
监控 Android开发 数据安全/隐私保护
批量发送短信的平台,安卓群发短信工具插件脚本,批量群发短信软件【autojs版】
这个Auto.js脚本实现了完整的批量短信发送功能,包含联系人管理、短信内容编辑、发送状态监控等功能
|
11月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
266 1
|
6月前
|
存储 编解码 监控
Android平台GB28181执法记录仪技术方案与实现
本文介绍了大牛直播SDK的SmartGBD在执法记录仪场景中的应用。GB28181协议作为视频监控联网的国家标准,为设备互联互通提供规范。SmartGBD专为Android平台设计,支持音视频采集、编码与传输,具备自适应算法和多功能扩展优势。文章分析了执法记录仪的需求,如实时音视频传输、设备管理及数据安全,并详细阐述了基于SmartGBD的技术实现方案,包括环境准备、SDK集成、设备注册、音视频处理及功能扩展等步骤。最后展望了SmartGBD在未来智慧物联领域的广阔应用前景。
243 13
|
6月前
|
存储 编解码 开发工具
Android平台毫秒级低延迟HTTP-FLV直播播放器技术探究与实现
本文详细探讨了在Android平台上实现HTTP-FLV播放器的过程。首先介绍了FLV格式的基础,包括文件头和标签结构。接着分析了HTTP-FLV传输原理,通过分块传输实现流畅播放。然后重点讲解了播放器的实现步骤,涵盖网络请求、数据解析、音视频解码与渲染,以及播放控制功能的设计。文章还讨论了性能优化和网络异常处理的方法,并总结了HTTP-FLV播放器的技术价值,尤其是在特定场景下的应用意义。
253 11
|
12月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
6月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
9月前
|
IDE 开发工具 Android开发
移动应用开发之旅:探索Android和iOS平台
在这篇文章中,我们将深入探讨移动应用开发的两个主要平台——Android和iOS。我们将了解它们的操作系统、开发环境和工具,并通过代码示例展示如何在这两个平台上创建一个简单的“Hello World”应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧,帮助你更好地理解和掌握移动应用开发。
235 17
|
11月前
|
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开发知识可参考相关书籍。
365 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
12月前
|
开发工具 Android开发 iOS开发
安卓与iOS开发环境对比:选择适合你的平台
【9月更文挑战第26天】在移动应用开发的广阔天地中,安卓和iOS是两大巨头。它们各自拥有独特的优势和挑战,影响着开发者的选择和决策。本文将深入探讨这两个平台的开发环境,帮助你理解它们的核心差异,并指导你根据个人或项目需求做出明智的选择。无论你是初学者还是资深开发者,了解这些平台的异同都至关重要。让我们一起探索,找到最适合你的那片开发天地。
|
12月前
|
Android开发 开发者
Android平台无纸化同屏如何实现实时录像功能
Android平台无纸化同屏,如果需要本地录像的话,实现难度不大,只要复用之前开发的录像模块的就可以,对我们来说,同屏采集这块,只是数据源不同而已,如果是自采集的其他数据,我们一样可以编码录像。
122 0