Android平台轻量级RTSP服务模块二次封装版调用说明

本文涉及的产品
视觉智能开放平台,图像通用资源包5000点
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,视频通用资源包5000点
简介: 本文介绍了Android平台上轻量级RTSP服务模块的二次封装实践,旨在简化开发流程,让开发者能更专注于业务逻辑。通过`LibPublisherWrapper`类提供的API,可在应用中轻松初始化RTSP服务、配置视频参数(如分辨率、编码类型)、启动与停止RTSP服务及流发布,并获取RTSP会话数量。此外,还展示了如何处理音频和视频数据的采集与推送。最后,文章提供了从启动服务到销毁资源的完整示例,帮助开发者快速集成实时流媒体功能。

技术背景

在前面的blog,我们发布了Android平台轻量级RTSP服务模块的技术对接说明,好多开发者希望,更黑盒的对接轻量级RTSP服务这块,专注于自身业务逻辑。为此,我们针对Android平台轻量级RTSP服务模块,做了更进一步的封装(LibPublisherWrapper.java)。

技术对接

本文还是以大牛直播SDK Android平台Camera2Demo为例,如果需要测试轻量级RTSP服务模块,分两步:首先启动RTSP服务,RTSP服务启动后,发布RTSP即可,在此之前,可选择设置视频分辨率、软硬编码类型,音频编码类型等,如需关闭轻量级RTSP服务,先停止发布RTSP流,再停止RTSP服务。

image.gif

onCreate()的时候,调用LibPublisherWrapper的initialize_sdk():

/*
 * MainActivity.java
 * Author: daniusdk.com
 */
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    ...
    context_ = this.getApplicationContext();
    
    libPublisher = new SmartPublisherJniV2();
    LibPublisherWrapper.RTSPServer.initialize_sdk(libPublisher, context_);
}

image.gif

对应封装代码如下:

public static boolean initialize_sdk(SmartPublisherJniV2 lib_publisher, android.content.Context context) {
    return sdk_context_.initialize(lib_publisher, context);
}

image.gif

initalize()具体实现如下:

/*
 * LibPublisherWrapper.java
 * Author: daniusdk.com
 */
public boolean initialize(SmartPublisherJniV2 lib_publisher, android.content.Context context) {
    if (initialized_)
        return initialized_result_;
    if (null == lib_publisher)
        return false;
    if (null == context)
        return false;
    synchronized (this) {
        if (initialized_)
            return initialized_result_;
        try {
            int sdk_ret = lib_publisher.InitRtspServer(context);
            if (0 == sdk_ret)
                initialized_result_ = true;
            else {
                initialized_result_ = false;
                Log.e(TAG, "call sdk InitRtspServer failed, ret:" + sdk_ret);
            }
        } catch (Exception e) {
            initialized_result_ = false;
            Log.e(TAG, "call sdk InitRtspServer Exception:", e);
        }
        initialized_ = true;
        return initialized_result_;
    }
}

image.gif

启动、停止RTSP服务:

//启动/停止RTSP服务
class ButtonRtspServiceListener implements View.OnClickListener {
    public void onClick(View v) {
        if (!rtsp_server_.empty()) {
            rtsp_server_.reset();
            btnRtspService.setText("启动RTSP服务");
            btnRtspPublisher.setEnabled(false);
            return;
        }
        Log.i(TAG, "onClick start rtsp service..");
        int port = 8554;
        String user_name = null;
        String password = null;
        LibPublisherWrapper.RTSPServer.Handle server_handle = LibPublisherWrapper.RTSPServer.create_and_start_server(libPublisher,
                port, user_name, password);
        if (null == server_handle) {
            Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!");
            return;
        }
        rtsp_server_.reset(server_handle);
        btnRtspService.setText("停止RTSP服务");
        btnRtspPublisher.setEnabled(true);
    }
}

image.gif

发布、停止RTSP流:

//发布/停止RTSP流
class ButtonRtspPublisherListener implements View.OnClickListener {
    public void onClick(View v) {
        if (stream_publisher_.is_rtsp_publishing()) {
            stopRtspPublisher();
            btnRtspPublisher.setText("发布RTSP流");
            btnGetRtspSessionNumbers.setEnabled(false);
            btnRtspService.setEnabled(true);
            return;
        }
        Log.i(TAG, "onClick start rtsp publisher..");
        InitAndSetConfig();
        String rtsp_stream_name = "stream1";
        stream_publisher_.SetRtspStreamName(rtsp_stream_name);
        stream_publisher_.ClearRtspStreamServer();
        stream_publisher_.AddRtspStreamServer(rtsp_server_.get_native());
        if (!stream_publisher_.StartRtspStream()) {
            stream_publisher_.try_release();
            Log.e(TAG, "调用发布rtsp流接口失败!");
            return;
        }
        startAudioRecorder();
        startLayerPostThread();
        btnRtspPublisher.setText("停止RTSP流");
        btnGetRtspSessionNumbers.setEnabled(true);
        btnRtspService.setEnabled(false);
    }
}

image.gif

stopRtspPublisher()实现如下:

//停止发布RTSP流
private void stopRtspPublisher() {
    stream_publisher_.StopRtspStream();
    stream_publisher_.try_release();
    if (!stream_publisher_.is_publishing())
        stopAudioRecorder();
}

image.gif

其中,InitAndSetConfig()实现如下,通过调研SmartPublisherOpen()接口,生成推送实例句柄。

/*
 * MainActivity.java
 * Author: daniusdk.com
 */
private void InitAndSetConfig() {
    if (null == libPublisher)
        return;
    if (!stream_publisher_.empty())
        return;
    Log.i(TAG, "InitAndSetConfig video width: " + video_width_ + ", height" + video_height_ + " imageRotationDegree:" + cameraImageRotationDegree_);
    int audio_opt = 1;
    long handle = libPublisher.SmartPublisherOpen(context_, audio_opt, 3,  video_width_, video_height_);
    if (0==handle) {
        Log.e(TAG, "sdk open failed!");
        return;
    }
    Log.i(TAG, "publisherHandle=" + handle);
    int fps = 25;
    int gop = fps * 3;
    initialize_publisher(libPublisher, handle, video_width_, video_height_, fps, gop);
    stream_publisher_.set(libPublisher, handle);
}

image.gif

对应的initialize_publisher()实现如下,设置软硬编码、帧率、关键帧间隔等。

private boolean initialize_publisher(SmartPublisherJniV2 lib_publisher, long handle, int width, int height, int fps, int gop) {
    if (null == lib_publisher) {
        Log.e(TAG, "initialize_publisher lib_publisher is null");
        return false;
    }
    if (0 == handle) {
        Log.e(TAG, "initialize_publisher handle is 0");
        return false;
    }
    if (videoEncodeType == 1) {
        int kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, true);
        Log.i(TAG, "h264HWKbps: " + kbps);
        int isSupportH264HWEncoder = lib_publisher.SetSmartPublisherVideoHWEncoder(handle, kbps);
        if (isSupportH264HWEncoder == 0) {
            lib_publisher.SetNativeMediaNDK(handle, 0);
            lib_publisher.SetVideoHWEncoderBitrateMode(handle, 1); // 0:CQ, 1:VBR, 2:CBR
            lib_publisher.SetVideoHWEncoderQuality(handle, 39);
            lib_publisher.SetAVCHWEncoderProfile(handle, 0x08); // 0x01: Baseline, 0x02: Main, 0x08: High
            // lib_publisher.SetAVCHWEncoderLevel(handle, 0x200); // Level 3.1
            // lib_publisher.SetAVCHWEncoderLevel(handle, 0x400); // Level 3.2
            // lib_publisher.SetAVCHWEncoderLevel(handle, 0x800); // Level 4
            lib_publisher.SetAVCHWEncoderLevel(handle, 0x1000); // Level 4.1 多数情况下,这个够用了
            //lib_publisher.SetAVCHWEncoderLevel(handle, 0x2000); // Level 4.2
            // lib_publisher.SetVideoHWEncoderMaxBitrate(handle, ((long)h264HWKbps)*1300);
            Log.i(TAG, "Great, it supports h.264 hardware encoder!");
        }
    } else if (videoEncodeType == 2) {
        int kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, false);
        Log.i(TAG, "hevcHWKbps: " + kbps);
        int isSupportHevcHWEncoder = lib_publisher.SetSmartPublisherVideoHevcHWEncoder(handle, kbps);
        if (isSupportHevcHWEncoder == 0) {
            lib_publisher.SetNativeMediaNDK(handle, 0);
            lib_publisher.SetVideoHWEncoderBitrateMode(handle, 1); // 0:CQ, 1:VBR, 2:CBR
            lib_publisher.SetVideoHWEncoderQuality(handle, 39);
            // libPublisher.SetVideoHWEncoderMaxBitrate(handle, ((long)hevcHWKbps)*1200);
            Log.i(TAG, "Great, it supports hevc hardware encoder!");
        }
    }
    boolean is_sw_vbr_mode = true;
    //H.264 software encoder
    if (is_sw_vbr_mode) {
        int is_enable_vbr = 1;
        int video_quality = LibPublisherWrapper.estimate_video_software_quality(width, height, true);
        int vbr_max_kbps = LibPublisherWrapper.estimate_video_vbr_max_kbps(width, height, fps);
        lib_publisher.SmartPublisherSetSwVBRMode(handle, is_enable_vbr, video_quality, vbr_max_kbps);
    }
    if (is_pcma_) {
        lib_publisher.SmartPublisherSetAudioCodecType(handle, 3);
    } else {
        lib_publisher.SmartPublisherSetAudioCodecType(handle, 1);
    }
    lib_publisher.SetSmartPublisherEventCallbackV2(handle, new EventHandlerPublisherV2().set(handler_, record_executor_));
    lib_publisher.SmartPublisherSetSWVideoEncoderProfile(handle, 3);
    lib_publisher.SmartPublisherSetSWVideoEncoderSpeed(handle, 2);
    lib_publisher.SmartPublisherSetGopInterval(handle, gop);
    lib_publisher.SmartPublisherSetFPS(handle, fps);
    // lib_publisher.SmartPublisherSetSWVideoBitRate(handle, 600, 1200);
    boolean is_noise_suppression = true;
    lib_publisher.SmartPublisherSetNoiseSuppression(handle, is_noise_suppression ? 1 : 0);
    boolean is_agc = false;
    lib_publisher.SmartPublisherSetAGC(handle, is_agc ? 1 : 0);
    int echo_cancel_delay = 0;
    lib_publisher.SmartPublisherSetEchoCancellation(handle, 1, echo_cancel_delay);
    return true;
}

image.gif

发布RTSP流成功后,会回调上来可供拉流的RTSP URL:

private static class EventHandlerPublisherV2 implements NTSmartEventCallbackV2 {
    @Override
    public void onNTSmartEventCallbackV2(long handle, int id, long param1, long param2, String param3, String param4, Object param5) {
        switch (id) {
            ...
            case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:
                publisher_event = "RTSP服务URL: " + param3;
                break;
        }
    }
}

image.gif

获取RTSP Session会话数:

//获取RTSP会话数
class ButtonGetRtspSessionNumbersListener implements View.OnClickListener {
    public void onClick(View v) {
        if (rtsp_server_.is_running()) {
            int session_numbers = rtsp_server_.get_client_session_number();
            Log.i(TAG, "GetRtspSessionNumbers: " + session_numbers);
            PopRtspSessionNumberDialog(session_numbers);
        }
    }
}

image.gif

对应的Wrapper封装代码如下:

public int get_client_session_number() {
    if (!is_running())
        return 0;
    if (null == lib_publisher_)
        return 0;
    long handle = native_handle_.get();
    if (0 == handle)
        return 0;
    try {
        int ret = lib_publisher_.GetRtspServerClientSessionNumbers(handle);
        return ret;
    } catch (Exception e) {
        Log.e(TAG, "RTSPServer.Handle.get_client_session_number Exception:", e);
        return 0;
    }
}

image.gif

数据投递如下(以Camera2采集为例,如果是其他视频格式,也可以正常对接):

@Override
public void onCameraImageData(Image image) {
    ....
    for (LibPublisherWrapper i : publisher_array_)
        i.PostLayerImageYUV420888ByteBuffer(0, 0, 0,
            planes[0].getBuffer(), y_offset, planes[0].getRowStride(),
            planes[1].getBuffer(), u_offset, planes[1].getRowStride(),
            planes[2].getBuffer(), v_offset, planes[2].getRowStride(), planes[1].getPixelStride(),
            w, h, 0, 0,
            scale_w, scale_h, scale_filter_mode, rotation_degree);
}

image.gif

音频采集投递设计如下:

void startAudioRecorder() {
    if (audio_recorder_ != null)
        return;
    audio_recorder_ = new NTAudioRecordV2(this);
    Log.i(TAG, "startAudioRecorder call audio_recorder_.start()+++...");
    audio_recorder_callback_ = new NTAudioRecordV2CallbackImpl(stream_publisher_, null);
    audio_recorder_.AddCallback(audio_recorder_callback_);
    if (!audio_recorder_.Start(is_pcma_ ? 8000 : 44100, 1) ) {
        audio_recorder_.RemoveCallback(audio_recorder_callback_);
        audio_recorder_callback_ = null;
        audio_recorder_ = null;
        Log.e(TAG, "startAudioRecorder start failed.");
    }
    else {
        Log.i(TAG, "startAudioRecorder call audio_recorder_.start() OK---...");
    }
}
void stopAudioRecorder() {
    if (null == audio_recorder_)
        return;
    Log.i(TAG, "stopAudioRecorder+++");
    audio_recorder_.Stop();
    if (audio_recorder_callback_ != null) {
        audio_recorder_.RemoveCallback(audio_recorder_callback_);
        audio_recorder_callback_ = null;
    }
    audio_recorder_ = null;
    Log.i(TAG, "stopAudioRecorder---");
}

image.gif

回调Audio数据的地方,直接投递出去:

private static class NTAudioRecordV2CallbackImpl implements NTAudioRecordV2Callback {
    private WeakReference<LibPublisherWrapper> publisher_0_;
    private WeakReference<LibPublisherWrapper> publisher_1_;
    public NTAudioRecordV2CallbackImpl(LibPublisherWrapper publisher_0) {
        if (publisher_0 != null)
            publisher_0_ = new WeakReference<>(publisher_0);
    }
    private final LibPublisherWrapper get_publisher_0() {
        if (publisher_0_ !=null)
            return publisher_0_.get();
        return null;
    }
    @Override
    public void onNTAudioRecordV2Frame(ByteBuffer data, int size, int sampleRate, int channel, int per_channel_sample_number) {
        LibPublisherWrapper publisher_0 = get_publisher_0();
        if (publisher_0 != null)
            publisher_0.OnPCMData(data, size, sampleRate, channel, per_channel_sample_number);
    }
}

image.gif

onDestroy() 的时候,调用deinitialize_sdk()即可:

@Override
protected void onDestroy() {
    Log.i(TAG, "activity destory!");
    stopAudioRecorder();
    stopRtspPublisher();
    stream_publisher_.release();
    rtsp_server_.reset();
    //如已启用内置服务功能(InitRtspServer),调用UnInitRtspServer, 注意,即便是启动多个RTSP服务,也只需调用UnInitRtspServer一次
    LibPublisherWrapper.RTSPServer.deinitialize_sdk(libPublisher);
    stopLayerPostThread();
    if (camera2Helper != null) {
        camera2Helper.release();
    }
    super.onDestroy();
}

image.gif

总结

Android平台轻量级RTSP服务模块二次封装版,相对接口版,对接更方便。开发者无需关注底层实现,可以更方便的对接到自己的业务系统。感兴趣的开发者,可以单独跟我们探讨。

相关文章
|
2月前
|
存储 编解码 监控
Android平台GB28181执法记录仪技术方案与实现
本文介绍了大牛直播SDK的SmartGBD在执法记录仪场景中的应用。GB28181协议作为视频监控联网的国家标准,为设备互联互通提供规范。SmartGBD专为Android平台设计,支持音视频采集、编码与传输,具备自适应算法和多功能扩展优势。文章分析了执法记录仪的需求,如实时音视频传输、设备管理及数据安全,并详细阐述了基于SmartGBD的技术实现方案,包括环境准备、SDK集成、设备注册、音视频处理及功能扩展等步骤。最后展望了SmartGBD在未来智慧物联领域的广阔应用前景。
92 13
|
2月前
|
存储 编解码 开发工具
Android平台毫秒级低延迟HTTP-FLV直播播放器技术探究与实现
本文详细探讨了在Android平台上实现HTTP-FLV播放器的过程。首先介绍了FLV格式的基础,包括文件头和标签结构。接着分析了HTTP-FLV传输原理,通过分块传输实现流畅播放。然后重点讲解了播放器的实现步骤,涵盖网络请求、数据解析、音视频解码与渲染,以及播放控制功能的设计。文章还讨论了性能优化和网络异常处理的方法,并总结了HTTP-FLV播放器的技术价值,尤其是在特定场景下的应用意义。
142 11
|
2月前
|
监控 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) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
5月前
|
IDE 开发工具 Android开发
移动应用开发之旅:探索Android和iOS平台
在这篇文章中,我们将深入探讨移动应用开发的两个主要平台——Android和iOS。我们将了解它们的操作系统、开发环境和工具,并通过代码示例展示如何在这两个平台上创建一个简单的“Hello World”应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧,帮助你更好地理解和掌握移动应用开发。
156 17
|
Android开发 数据安全/隐私保护 开发工具
|
2月前
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
409 76
|
3月前
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
105 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
3月前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
253 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
3月前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
89 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
4月前
|
缓存 前端开发 Android开发
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
222 12
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程