拉取RTSP流后的几个去向探讨(播放|转RTMP|轻量级RTSP服务|本地录制|GB28181)

本文涉及的产品
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,图像资源包5000点
视觉智能开放平台,视频资源包5000点
简介: 本文汇总了大牛直播SDK在Android平台上拉取RTSP流后的多种应用方向,包括本地播放、转推至RTMP服务器、轻量级RTSP服务、GB28181平台及录像等功能。提供了详细的实现方法与示例代码,旨在帮助开发者高效利用RTSP流数据,实现低延迟、稳定且灵活的应用场景。

 RTSP流的几个去处

写了很多关于RTSP播放和转发的blog了,今天我们做个简单的汇总,以大牛直播SDK的Android平台为例,拉取到RTSP流,除了本地播放,还有几个流向:

image.gif

功能实现:

1. 拉流:通过RTSP直播播放SDK的数据回调接口,拿到音视频数据;

2. 转推:通过RTMP直播推送SDK的编码后数据输入接口,把回调上来的数据,传给RTMP直播推送模块,实现RTSP数据流到RTMP服务器的转发,同时也可以转发到轻量级RTSP服务和GB28181平台;

3. 录像:如果需要录像,借助RTSP直播播放SDK,拉到音视频数据后,直接存储MP4文件即可;

4. 快照:如果需要实时快照,拉流后,解码调用播放端快照接口,生成快照,因为快照涉及到video数据解码,如无必要,可不必开启,不然会额外消耗性能。

5. 拉流预览:如需预览拉流数据,只要调用播放端的播放接口,即可实现拉流数据预览;

6. 数据转AAC后转发:考虑到好多监控设备出来的音频可能是PCMA/PCMU的,如需要更通用的音频格式,可以转AAC后,在通过RTMP推送;

7. 转推实时静音:只需要在传audio数据的地方,加个判断即可;

8. 拉流速度反馈:通过RTSP播放端的实时码率反馈event,拿到实时带宽占用即可;

9. 整体网络状态反馈:考虑到有些摄像头可能会临时或异常关闭,RTMP服务器亦是,可以通过推拉流的event回调状态,查看那整体网络情况,如此界定:是拉不到流,还是推不到RTMP服务器。

先说拉取数据,拉取RTSP流的时候,设置音视频数据回调。

/*
     * SmartRelayDemo.java
     * Author: daniusdk.com
     * WeChat: xinsheng120
     */
    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;
    }

image.gif

对应的OpenPullHandle()实现如下:

private boolean OpenPullHandle()
    {
        //playbackUrl可自定义
        //playbackUrl = "rtsp://admin:daniulive12345@192.168.0.120:554/h264/ch1/main/av_stream";
        if (playbackUrl == null) {
            Log.e(TAG, "playback URL is null...");
            return false;
        }
        player_handle_ = libPlayer.SmartPlayerOpen(context_);
        if (player_handle_ == 0) {
            Log.e(TAG, "playerHandle is null..");
            return false;
        }
        libPlayer.SetSmartPlayerEventCallbackV2(player_handle_,
                new EventHandlePlayerV2());
        libPlayer.SmartPlayerSetBuffer(player_handle_, playBuffer);
        // set report download speed
        libPlayer.SmartPlayerSetReportDownloadSpeed(player_handle_, 1, 3);
        //设置RTSP超时时间
        int rtsp_timeout = 10;
        libPlayer.SmartPlayerSetRTSPTimeout(player_handle_, rtsp_timeout);
        //设置RTSP TCP/UDP模式自动切换
        int is_auto_switch_tcp_udp = 1;
        libPlayer.SmartPlayerSetRTSPAutoSwitchTcpUdp(player_handle_, is_auto_switch_tcp_udp);
        // It only used when playback RTSP stream..
        //libPlayer.SmartPlayerSetRTSPTcpMode(playerHandle, 1);
        libPlayer.SmartPlayerSetUrl(player_handle_, playbackUrl);
        return true;
    }

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

本地播放

private boolean StartPlay()
    {
        if(isPlaying)
            return false;
        if(!isPulling)
        {
            if (!OpenPullHandle())
                return false;
        }
        // 如果第二个参数设置为null,则播放纯音频
        libPlayer.SmartPlayerSetSurface(player_handle_, sSurfaceView);
        //libPlayer.SmartPlayerSetSurface(player_handle_, null);
        libPlayer.SmartPlayerSetRenderScaleMode(player_handle_, 1);
        libPlayer.SmartPlayerSetFastStartup(player_handle_, isFastStartup ? 1 : 0);
        libPlayer.SmartPlayerSetAudioOutputType(player_handle_, 1);
        if (isMute) {
            libPlayer.SmartPlayerSetMute(player_handle_, isMute ? 1 : 0);
        }
        if (isHardwareDecoder)
        {
            int isSupportH264HwDecoder = libPlayer.SetSmartPlayerVideoHWDecoder(player_handle_, 1);
            int isSupportHevcHwDecoder = libPlayer.SetSmartPlayerVideoHevcHWDecoder(player_handle_, 1);
            Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder);
        }
        libPlayer.SmartPlayerSetLowLatencyMode(player_handle_, isLowLatency ? 1 : 0);
        libPlayer.SmartPlayerSetRotation(player_handle_, rotate_degrees);
        int iPlaybackRet = libPlayer.SmartPlayerStartPlay(player_handle_);
        if (iPlaybackRet != 0 && !isPulling) {
            Log.e(TAG, "StartPlay failed!");
            releasePlayerHandle();
            return false;
        }
        isPlaying = true;
        return true;
    }
    private void StopPlay()
    {
        if ( !isPlaying )
            return;
        isPlaying = false;
        if (null == libPlayer || 0 == player_handle_)
            return;
        libPlayer.SmartPlayerStopPlay(player_handle_);
    }

image.gif

转推RTMP

btnRTMPPusher.setOnClickListener(new Button.OnClickListener() {
            // @Override
            public void onClick(View v) {
                if (stream_publisher_.is_rtmp_publishing()) {
                    stopPush();
                    btnRTMPPusher.setText("推送RTMP");
                    return;
                }
                Log.i(TAG, "onClick start push rtmp..");
                InitAndSetConfig();
                //relayStreamUrl = "rtmp://192.168.0.108:1935/hls/stream1";
                if (!stream_publisher_.SetURL(relayStreamUrl))
                    Log.e(TAG, "Failed to set publish stream URL..");
                boolean start_ret = stream_publisher_.StartPublisher();
                if (!start_ret) {
                    stream_publisher_.try_release();
                    Log.e(TAG, "Failed to start push stream..");
                    return;
                }
                btnRTMPPusher.setText("停止推送");
            }
        });

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;
            }
        }
    }
    //发布/停止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);
        }
    }
    //当前RTSP会话数弹出框
    private void PopRtspSessionNumberDialog(int session_numbers) {
        final EditText inputUrlTxt = new EditText(this);
        inputUrlTxt.setFocusable(true);
        inputUrlTxt.setEnabled(false);
        String session_numbers_tag = "RTSP服务当前客户会话数: " + session_numbers;
        inputUrlTxt.setText(session_numbers_tag);
        AlertDialog.Builder builderUrl = new AlertDialog.Builder(this);
        builderUrl
                .setTitle("内置RTSP服务")
                .setView(inputUrlTxt).setNegativeButton("确定", null);
        builderUrl.show();
    }
    //获取RTSP会话数
    class ButtonGetRtspSessionNumbersListener implements 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后录像

btnStartStopRecorder.setOnClickListener(new Button.OnClickListener() {
            // @Override
            public void onClick(View v) {
                if (isRecording)
                {
                    StopRecorder();
                    btnStartStopRecorder.setText(" 开始录像");
                } else {
                    Log.i(TAG, "onClick StartRecorder..");
                    boolean startRet = StartRecorder();
                    if (!startRet) {
                        Log.e(TAG, "Failed to call StartRecorder().");
                        return;
                    }
                    btnStartStopRecorder.setText("停止录像");
                }
            }
        });

image.gif

转推GB28181平台

class ButtonGB28181AgentListener implements OnClickListener {
        public void onClick(View v) {
            stopGB28181Stream();
            destoryRTPSender();
            if (null == gb28181_agent_ ) {
                if( !initGB28181Agent() )
                    return;
            }
            if (gb28181_agent_.isRunning()) {
                gb28181_agent_.terminateAllPlays(true);// 目前测试下来,发送BYE之后,有些服务器会立即发送INVITE,是否发送BYE根据实际情况看
                gb28181_agent_.stop();
                btnGB28181Agent.setText("启动GB28181");
            }
            else {
                if ( gb28181_agent_.start() ) {
                    btnGB28181Agent.setText("停止GB28181");
                }
            }
        }
    }
    //停止GB28181 媒体流
    private void stopGB28181Stream() {
        stream_publisher_.StopGB28181MediaStream();
        stream_publisher_.try_release();
    }

image.gif

总结

一个好的转发模块,首先要低延迟!其次足够稳定、灵活、有状态反馈机制、资源占用低,如果可以跨平台,还能以SDK形式提供,会给开发者提供更大的便利!Android平台拉取RTSP流后,有了数据源,开发者可以在一个推送实例中,转推到不同的业务场景,实现高效率低延迟的数据转发。


相关文章
|
编解码 监控 网络协议
Android平台音视频推送选RTMP还是GB28181?
早在2015年,我们发布了RTMP直播推送模块,那时候音视频直播这块场景需求,还不像现在这么普遍,我们做这块的初衷,主要是为了实现移动单兵应急指挥系统的低延迟音视频数据传输。好多开发者可能会疑惑,走RTMP怎么可能低延迟?网上看到的RTMP推拉流延迟,总归要2-3秒起,如果是自己实现框架,RTMP推拉流逻辑自己实现的话,延迟确实可以控制在毫秒级,这个已无需赘述。
106 0
|
1月前
|
应用服务中间件 Linux nginx
FFmpeg学习笔记(一):实现rtsp推流rtmp以及ffplay完成拉流操作
这篇博客介绍了如何使用FFmpeg实现RTSP推流到RTMP服务器,并使用ffplay进行拉流操作,包括在Windows和Linux系统下的命令示例,以及如何通过HTML页面显示视频流。
392 0
|
3月前
|
开发工具 Android开发 开发者
Android平台如何不推RTMP|不发布RTSP流|不实时录像|不回传GB28181数据时实时快照?
本文介绍了一种在Android平台上实现实时截图快照的方法,尤其适用于无需依赖系统接口的情况,如在RTMP推送、RTSP服务或GB28181设备接入等场景下进行截图。通过底层模块(libSmartPublisher.so)实现了截图功能,封装了`SnapShotImpl.java`类来管理截图流程。此外,提供了关键代码片段展示初始化SDK实例、执行截图、以及在Activity销毁时释放资源的过程。此方案还考虑到了快照数据的灵活处理需求,符合GB/T28181-2022的技术规范。对于寻求更灵活快照机制的开发者来说,这是一个值得参考的设计思路。
|
2月前
|
Linux Android开发 iOS开发
Windows平台RTSP|RTMP播放器如何实现实时录像功能
Windows平台RTSP、RTMP播放器实时录像接口设计,实际上,除了Windows平台,我们Linux、Android、iOS平台也是一样的设计,单纯的录像模块,如果做的全面,也不是一两个接口可以搞定的
|
2月前
|
XML 编解码 开发工具
多路RTSP转RTMP推送方案的两个选择
RTSP转RTMP模块设计,可以用ffmpeg直接命令行转发,也可以用方案二的非常成熟的转发设计,ffmpeg转发,需要有一定的代码基础,有问题的话,bug修复需要对底层逻辑非常了解才行,方案二,技术成熟,二次开发难度不大,很容易集成到自己现有系统
|
3月前
|
编解码 开发工具 Android开发
iOS平台如何实现毫秒级延迟的RTMP|RTSP播放器
在我的blog里面,最近很少有提到iOS平台RTMP推送|轻量级RTSP服务和RTMP|RTSP直播播放模块,实际上,我们在2016年就发布了iOS平台直播推拉流、转发模块,只是因为传统行业,对iOS的需求比较少,所以一直没单独说明,本文主要介绍下,如何在iOS平台播放RTMP或RTSP流。
|
3月前
|
Linux 开发工具 图形学
Unity下实现跨平台的RTMP推流|轻量级RTSP服务|RTMP播放|RTSP播放低延迟解决方案
自2018年起,我们成功实现了Unity环境下的低延迟RTSP|RTMP播放,达到毫秒级延迟,获得业界广泛认可。现已覆盖Windows、Android、iOS与Linux平台的RTMP推送、轻量级RTSP服务及RTSP|RTMP播放。通过高效采集Unity窗口或摄像头数据,并利用原生SDK进行编码与推送,确保了数据传输的高速性。此外,播放器支持多路视频同时播放,适应不同分辨率,并保持长时间运行稳定。更多技术细节和技术博文,请参考相关链接。
216 1
|
3月前
|
监控 开发工具 Android开发
Android平台实现RTSP拉流转发至轻量级RTSP服务
为满足Android平台上从外部RTSP摄像头拉流并提供轻量级RTSP服务的需求,利用大牛直播SDK实现了相关功能。SDK支持开始与停止拉流、音频视频数据回调处理及RTSP服务的启动与发布等操作。拉流仅需将未解码数据回调,对性能影响小。音频和视频数据经由特定接口传递给发布端进行处理。此外,SDK还提供了获取RTSP会话数量的功能。此方案适用于监控和巡检等低延迟应用场景,并支持二次水印添加等功能。
|
3月前
|
监控 开发工具 数据安全/隐私保护
Windows平台如何实现多路RTSP|RTMP流合成后录像或转发RTMP服务
本文介绍了在Windows平台上实现多路RTSP/RTMP视频流的合并技术。主要应用场景包括驾考、全景摄像头以及多路会议录制等。技术实现上,文章详细展示了如何使用特定的SDK来解码并回调YUV或RGB数据,再将这些数据按照图层形式进行合成。示例代码中给出了初始化参数、设置视频帧回调函数、以及如何配置不同图层的具体步骤。最终,合成后的视频可以推送到RTMP服务器、注入到本地RTSP服务,或是直接录制为MP4文件。此外,还提供了添加实时文字水印的方法,并展示了四路视频流合成后的“四宫格”效果。
|
编解码 开发工具 Android开发
数据推送选择GB28181、RTSP还是RTMP?
国标GB/T28181协议全称《安全防范视频监控联网系统信息传输、交换、控制技术要求》,是一个定义视频联网传输和设备控制标准的白皮书,由公安部科技信息化局提出,该标准规定了城市监控报警联网系统中信息传输、交换、控制的互联结构、通信协议结构,传输、交换、控制的基本要求和安全性要求,以及控制、传输流程和协议接口等技术要求。解决了视频间互联互通,数据共享,以及设备控制的问题,这个问题从顶层解决了视频信息各自为战的问题,打通了视频联网的信息孤岛。
392 1

热门文章

最新文章