Android平台轻量级RTSP服务模块技术对接说明

一、技术背景

随着内网无纸化办公、电子教室等应用场景对超低延迟音视频传输需求的日益增长,为避免用户或开发者单独部署 RTSP 或 RTMP 服务,大牛直播 SDK 推出了轻量级 RTSP 服务 SDK。该 SDK 能够将本地音视频数据(如摄像头、麦克风等)进行编码后,汇聚到内置 RTSP 服务中,对外提供可供拉流的 RTSP URL,适用于内网环境下对并发要求不高的场景。

二、技术特点

(一)支持的编码格式

  • 视频编码:支持 H.264/H.265(Android H.265 硬编码)。

  • 音频编码:支持 G.711 A 律、AAC。

(二)功能特性

  • 协议支持:支持 RTSP 协议。

  • 音量调节:Android 平台采集端支持实时音量调节。

  • 视频编码:支持 H.264 特定机型硬编码及 H.265 特定机型硬编码。

  • 音视频类型:支持纯音频、纯视频及音视频组合。

  • 摄像头切换:支持采集过程中前后摄像头实时切换。

  • 参数设置:支持帧率、关键帧间隔(GOP)、码率(bit-rate)设置。

  • 水印功能:支持动态文字水印、png 水印。

  • 快照功能:支持实时快照。

  • 降噪处理:支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD 检测。

  • 外部数据对接:支持 YUV 数据(外部编码前视频数据)、PCM 数据(外部编码前音频数据)、外部 H.264、H.265 数据(外部编码后视频数据)以及外部 AAC 数据(外部编码后音频数据)对接。

  • 录像功能:支持与录像 SDK 组合使用,实现录像相关功能。

  • 其他:支持 RTSP 端口设置、RTSP 鉴权用户名及密码设置、获取当前 RTSP 服务会话连接数,兼容 Android 5.1 及以上版本。

三、技术对接

(一)系统要求

  • SDK 支持版本:Android 5.1 及以上版本。

  • 支持的 CPU 架构:armv7、arm64、x86、x86_64。

(二)准备工作

  1. 文件放置:确保 SmartPublisherJniV2.java 放置于 com.daniulive.smartpublisher 包名下(可在其他包名下调用)。

  2. 库文件添加:将 smartavengine.jar 添加至工程中,并拷贝 libSmartPublisher.so 至工程目录。

  3. 权限配置:在 AndroidManifest.xml 中添加相关权限,具体如下:

    XML 复制代码
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-feature android:name="android.hardware.camera.autofocus" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
    <uses-permission android:name="android.permission.VIBRATE" />
  4. 加载 so 库

    java 复制代码
    static {
        System.loadLibrary("SmartPublisher");
    }
  5. 配置 32/64 位库:在 build.gradle 中进行如下配置:

    java 复制代码
    splits {
        abi {
            enable true
            reset()
            include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
            universalApk true
        }
    }
  6. 修改 app-name:如需集成至自有系统进行测试,可使用大牛直播 SDK 的 app name,授权版则按照授权 app name 正常使用。修改 app-name 可在 strings.xml 中进行如下操作:

    xml复制

    <string name="app_name">SmartPublisherSDKDemo</string>
    

(三)接口设计

1. SmartRTSPServerSDK 接口
调用描述 接口 接口描述
初始化 RTSP Server InitRtspServer 初始化 RTSP 服务器(与 UnInitRtspServer 配对使用,启动多个 RTSP 服务也只需调用一次,需在 OpenRtspServer 之前调用)
创建一个 rtsp server OpenRtspServer 创建一个 RTSP 服务器,返回 RTSP 服务器句柄
设置端口 SetRtspServerPort 设置 RTSP 服务器监听端口,在 StartRtspServer 之前必须设置
设置鉴权用户名、密码 SetRtspServerUserNamePassword 设置 RTSP 服务器鉴权用户名和密码,可选设置
获取 rtsp server 当前会话数 GetRtspServerClientSessionNumbers 获取 RTSP 服务器当前的客户会话数,此接口必须在 StartRtspServer 之后调用
启动 rtsp server StartRtspServer 启动 RTSP 服务器
停止 rtsp server StopRtspServer 停止 RTSP 服务器
关闭 rtsp server CloseRtspServer 关闭 RTSP 服务器
UnInit rtsp server UnInitRtspServer 反初始化 RTSP 服务器(与 InitRtspServer 配对使用,启动多个 RTSP 服务也只需调用一次)
2. SmartRTSPServerSDK 供 Publisher 调用的接口
调用描述 接口 接口描述
设置 rtsp 的流名称 SetRtspStreamName 设置 RTSP 的流名称
给要发布的 rtsp 流设置 rtsp server AddRtspStreamServer 给要发布的 RTSP 流设置 RTSP 服务器,一个流可发布到多个 RTSP 服务器上,服务器的创建启动参考 OpenRtspServer 和 StartRtspServer 接口
清除设置的 rtsp server ClearRtspStreamServer 清除设置的 RTSP 服务器
启动 rtsp 流 StartRtspStream 启动 RTSP 流
停止 rtsp 流 StopRtspStream 停止 RTSP 流

(四)接口调用详解

1. 初始化 SDK

在应用的 onCreate() 方法中,调用 LibPublisherWrapperinitialize_sdk() 方法进行 SDK 初始化:

java 复制代码
@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_);
}

封装代码如下:

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

具体实现逻辑:

java 复制代码
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_;
    }
}
2. 启动与停止 RTSP 服务

通过按钮点击事件启动或停止 RTSP 服务:

java 复制代码
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);
    }
}
3. 发布与停止 RTSP 流

同样通过按钮点击事件控制 RTSP 流的发布与停止:

java 复制代码
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);
    }
}

停止 RTSP 流的实现:

java 复制代码
private void stopRtspPublisher() {
    stream_publisher_.StopRtspStream();
    stream_publisher_.try_release();
    if (!stream_publisher_.is_publishing()) {
        stopAudioRecorder();
    }
}
4. 配置与初始化

在发布 RTSP 流之前,需要进行相关配置与初始化:

java 复制代码
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);
}

初始化编码参数等设置:

java 复制代码
private boolean initialize_publisher(SmartPublisherJniV2 lib_publisher, long handle, int width, int height, int fps, int gop) {
    // 编码类型设置
    if (videoEncodeType == 1) {
        // H.264 硬件编码设置
    } else if (videoEncodeType == 2) {
        // HEVC 硬件编码设置
    }
    // 软件编码可变比特率模式设置
    boolean is_sw_vbr_mode = true;
    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);
    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;
}
5. 获取 RTSP 会话数

提供获取当前 RTSP 会话数的功能:

java 复制代码
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);
        }
    }
}

封装实现:

java 复制代码
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;
    }
}
6. 数据投递

以 Camera2 采集为例,进行数据投递:

java 复制代码
@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);
    }
}

音频采集与投递:

java 复制代码
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---");
}

回调音频数据投递:

java 复制代码
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);
    }
}
7. 释放资源

onDestroy() 方法中,释放相关资源:

java 复制代码
@Override
protected void onDestroy() {
    Log.i(TAG, "activity destory!");
    stopAudioRecorder();
    stopRtspPublisher();
    stream_publisher_.release();
    rtsp_server_.reset();
    LibPublisherWrapper.RTSPServer.deinitialize_sdk(libPublisher);
    stopLayerPostThread();
    if (camera2Helper != null) {
        camera2Helper.release();
    }
    super.onDestroy();
}

四、总结

以上为 Android 平台轻量级 RTSP 服务模块的详细技术对接说明。该模块不仅支持编码前音视频数据的对接,还支持编码后音视频数据的对接,并可与本地录像、快照等功能组合使用,以满足多样化的应用场景需求。开发者可根据实际需求进行集成与开发,如有任何疑问或需要进一步探讨,欢迎与我们联系。

相关推荐
李少兄17 分钟前
MySQL中的UNION操作符
android·数据库·mysql
在成都搬砖的鸭鸭20 分钟前
【MySQL】表连接原理
android·mysql
缘来的精彩22 分钟前
Android Jetpack常用组件‌
android·android jetpack
事业运财运爆棚2 小时前
比较RPC和RESTful API的优缺点
android
江上清风山间明月3 小时前
一周掌握Flutter开发--4、导航与路由
android·flutter·路由·导航·ongenerateroute·navigator.push·navigator.pop
Mr.pyZhang5 小时前
Android构建系统 - 02 初始化编译环境,添加产品
android·linux
计算机毕设定制辅导-无忧学长6 小时前
Maven 依赖的深入理解(二)
android·java·maven
JabamiLight17 小时前
Lineageos 22.1(Android 15)Launcer打开Taskbar
android·android 15·taskbar·lineageos 22.1
IT猿手19 小时前
2025最新高维多目标优化:基于城市场景下无人机三维路径规划的导航变量的多目标粒子群优化算法(NMOPSO),MATLAB代码
android·开发语言·算法·机器学习·matlab·无人机