Android平台实现RTSP拉流转发至轻量级RTSP服务

本文涉及的产品
视觉智能开放平台,视频资源包5000点
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,图像资源包5000点
简介: 为满足Android平台上从外部RTSP摄像头拉流并提供轻量级RTSP服务的需求,利用大牛直播SDK实现了相关功能。SDK支持开始与停止拉流、音频视频数据回调处理及RTSP服务的启动与发布等操作。拉流仅需将未解码数据回调,对性能影响小。音频和视频数据经由特定接口传递给发布端进行处理。此外,SDK还提供了获取RTSP会话数量的功能。此方案适用于监控和巡检等低延迟应用场景,并支持二次水印添加等功能。

技术背景

我们在做Android平台RTSP转发模块的时候,有公司提出来这样的技术需求,他们希望拉取外部RTSP摄像头的流,然后提供个轻量级RTSP服务,让内网其他终端过来拉流。实际上,这块,大牛直播SDK前几年就已经实现。

技术实现

拉流的话,很好理解,其实就是播放端,把未解码的数据,直接回调上来,如果需要预览,直接底层绘制即可。单纯的数据回调,对性能消耗不大。

回调上来的数据,可以作为轻量级RTSP服务的数据源(投递编码后数据),推送端,只要启动RTSP服务,然后发布RTSP流即可。

image.gif

先说拉流,开始拉流、停止拉流实现:

/*
 * SmartPlayer.java
 * Author: daniusdk.com
 */
private boolean StartPull()
{
    if ( isPulling )
        return false;
    if(!isPlaying)
    {
        if (!OpenPullHandle())
            return false;
    }
    libPlayer.SmartPlayerSetAudioDataCallback(player_handle_, new PlayerAudioDataCallback(stream_publisher_));
    libPlayer.SmartPlayerSetVideoDataCallback(player_handle_, new PlayerVideoDataCallback(stream_publisher_));
    int is_pull_trans_code  = 1;
    libPlayer.SmartPlayerSetPullStreamAudioTranscodeAAC(player_handle_, is_pull_trans_code);
    int startRet = libPlayer.SmartPlayerStartPullStream(player_handle_);
    if (startRet != 0) {
        Log.e(TAG, "Failed to start pull stream!");
        if(!isPlaying)
        {
            releasePlayerHandle();
        }
        return false;
    }
    isPulling = true;
    return true;
}
private void StopPull()
{
    if ( !isPulling )
        return;
    isPulling = false;
    if (null == libPlayer || 0 == player_handle_)
        return;
    libPlayer.SmartPlayerStopPullStream(player_handle_);
    if ( !isPlaying)
    {
        releasePlayerHandle();
    }
}

image.gif

音频回调处理:

class PlayerAudioDataCallback implements NTAudioDataCallback
{
    private WeakReference<LibPublisherWrapper> publisher_;
    private int audio_buffer_size = 0;
    private int param_info_size = 0;
    private ByteBuffer audio_buffer_ = null;
    private ByteBuffer parameter_info_ = null;
    public PlayerAudioDataCallback(LibPublisherWrapper publisher) {
        if (publisher != null)
            publisher_ = new WeakReference<>(publisher);
    }
    @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);
        if ( audio_buffer_ == null)
            return;
        LibPublisherWrapper publisher = publisher_.get();
        if (null == publisher)
            return;
        if (!publisher.is_publishing())
            return;
        audio_buffer_.rewind();
        publisher.PostAudioEncodedData(audio_codec_id, audio_buffer_, sample_size, is_key_frame, timestamp, parameter_info_, parameter_info_size);
    }
}

image.gif

视频回调数据:

class PlayerVideoDataCallback implements NTVideoDataCallback
{
    private WeakReference<LibPublisherWrapper> publisher_;
    private int video_buffer_size = 0;
    private ByteBuffer video_buffer_ = null;
    public PlayerVideoDataCallback(LibPublisherWrapper publisher) {
        if (publisher != null)
            publisher_ = new WeakReference<>(publisher);
    }
    @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 +
        //      ",presentation_timestamp:" + presentation_timestamp);
        if ( video_buffer_ == null)
            return;
        LibPublisherWrapper publisher = publisher_.get();
        if (null == publisher)
            return;
        if (!publisher.is_publishing())
            return;
        video_buffer_.rewind();
        publisher.PostVideoEncodedData(video_codec_id, video_buffer_, sample_size, is_key_frame, timestamp, presentation_timestamp);
    }
}

image.gif

启动RTSP服务:

//启动/停止RTSP服务
class ButtonRtspServiceListener implements View.OnClickListener {
    public void onClick(View v) {
        if (isRTSPServiceRunning) {
            stopRtspService();
            btnRtspService.setText("启动RTSP服务");
            btnRtspPublisher.setEnabled(false);
            isRTSPServiceRunning = false;
            return;
        }
        Log.i(TAG, "onClick start rtsp service..");
        rtsp_handle_ = libPublisher.OpenRtspServer(0);
        if (rtsp_handle_ == 0) {
            Log.e(TAG, "创建rtsp server实例失败! 请检查SDK有效性");
        } else {
            int port = 28554;
            if (libPublisher.SetRtspServerPort(rtsp_handle_, port) != 0) {
                libPublisher.CloseRtspServer(rtsp_handle_);
                rtsp_handle_ = 0;
                Log.e(TAG, "创建rtsp server端口失败! 请检查端口是否重复或者端口不在范围内!");
            }
            if (libPublisher.StartRtspServer(rtsp_handle_, 0) == 0) {
                Log.i(TAG, "启动rtsp server 成功!");
            } else {
                libPublisher.CloseRtspServer(rtsp_handle_);
                rtsp_handle_ = 0;
                Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!");
            }
            btnRtspService.setText("停止RTSP服务");
            btnRtspPublisher.setEnabled(true);
            isRTSPServiceRunning = 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_handle_);
        if (!stream_publisher_.StartRtspStream()) {
            stream_publisher_.try_release();
            Log.e(TAG, "调用发布rtsp流接口失败!");
            return;
        }
        btnRtspPublisher.setText("停止RTSP流");
        btnGetRtspSessionNumbers.setEnabled(true);
        btnRtspService.setEnabled(false);
    }
}

image.gif

获取RTSP Session会话数:

//获取RTSP会话数
class ButtonGetRtspSessionNumbersListener implements View.OnClickListener {
    public void onClick(View v) {
        if (libPublisher != null && rtsp_handle_ != 0) {
            int session_numbers = libPublisher.GetRtspServerClientSessionNumbers(rtsp_handle_);
            Log.i(TAG, "GetRtspSessionNumbers: " + session_numbers);
            PopRtspSessionNumberDialog(session_numbers);
        }
    }
}

image.gif

总结

因为RTSP外部拉流,不需要解码,配合大牛直播SDK的SmartPlayer播放器,延迟和直连的,差别不大,整体毫秒级,延迟非常低,巡检或监控类场景,都可以达到相应的技术指标。如果需要二次水印,也可以回调解码后的yuv或rgb数据,推送端添加二次文字或图片水印后,编码输出,这种在一些合成类场景,比如智慧煤矿、管廊隧道等行业,非常适用,感兴趣的开发者,可以单独跟我探讨。

相关文章
|
26天前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
97 1
|
2月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
13天前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
Android远程连接和登录FTPS服务代码(commons.net库)
15 1
|
17天前
|
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开发知识可参考相关书籍。
64 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
2月前
|
开发工具 Android开发 iOS开发
安卓与iOS开发环境对比:选择适合你的平台
【9月更文挑战第26天】在移动应用开发的广阔天地中,安卓和iOS是两大巨头。它们各自拥有独特的优势和挑战,影响着开发者的选择和决策。本文将深入探讨这两个平台的开发环境,帮助你理解它们的核心差异,并指导你根据个人或项目需求做出明智的选择。无论你是初学者还是资深开发者,了解这些平台的异同都至关重要。让我们一起探索,找到最适合你的那片开发天地。
|
2月前
|
Android开发 开发者
Android平台无纸化同屏如何实现实时录像功能
Android平台无纸化同屏,如果需要本地录像的话,实现难度不大,只要复用之前开发的录像模块的就可以,对我们来说,同屏采集这块,只是数据源不同而已,如果是自采集的其他数据,我们一样可以编码录像。
|
26天前
|
缓存 搜索推荐 Android开发
安卓开发中的自定义控件实践
【10月更文挑战第4天】在安卓开发的海洋中,自定义控件是那片璀璨的星辰。它不仅让应用界面设计变得丰富多彩,还提升了用户体验。本文将带你探索自定义控件的核心概念、实现过程以及优化技巧,让你的应用在众多竞争者中脱颖而出。
|
3天前
|
编解码 Java Android开发
通义灵码:在安卓开发中提升工作效率的真实应用案例
本文介绍了通义灵码在安卓开发中的应用。作为一名97年的聋人开发者,我在2024年Google Gemma竞赛中获得了冠军,拿下了很多项目竞赛奖励,通义灵码成为我的得力助手。文章详细展示了如何安装通义灵码插件,并通过多个实例说明其在适配国际语言、多种分辨率、业务逻辑开发和编程语言转换等方面的应用,显著提高了开发效率和准确性。
|
2天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
12 5
|
1天前
|
缓存 数据库 Android开发
安卓开发中的性能优化技巧
【10月更文挑战第29天】在移动应用的海洋中,性能是船只能否破浪前行的关键。本文将深入探讨安卓开发中的性能优化策略,从代码层面到系统层面,揭示如何让应用运行得更快、更流畅。我们将以实际案例和最佳实践为灯塔,引领开发者避开性能瓶颈的暗礁。
8 3

热门文章

最新文章

  • 1
    2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
    15
  • 2
    2024重生之回溯数据结构与算法系列学习(11)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
    9
  • 3
    2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    11
  • 4
    2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    13
  • 5
    2024重生之回溯数据结构与算法系列学习(8)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    14
  • 6
    2024重生之回溯数据结构与算法系列学习(7)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    11
  • 7
    2024重生之回溯数据结构与算法系列学习之王道第2.3章节之线性表精题汇总二(5)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    8
  • 8
    23
    7
  • 9
    2024重生之回溯数据结构与算法系列学习之单双链表精题(4)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    16
  • 10
    2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    10