掘金 人工智能 2024年07月08日
Android平台实现RTSP拉流转发至轻量级RTSP服务
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文介绍了在Android平台上实现RTSP转发模块的技术细节,包括拉流、音频和视频回调处理、启动RTSP服务及发布RTSP流等步骤。通过使用大牛直播SDK,实现了高效的流媒体处理和低延迟的直播体验。

🔄 拉流实现:文章详细描述了如何在Android平台上实现RTSP拉流功能。通过调用SmartPlayer类的StartPull和StopPull方法,可以控制流的开始和停止。这些方法通过设置音频和视频数据回调,将未解码的数据直接回调上来,从而实现高效的流处理。

🎵 音频和视频回调处理:文章进一步解释了音频和视频数据的回调处理机制。通过实现PlayerAudioDataCallback和PlayerVideoDataCallback类,可以处理音频和视频数据,并将这些数据作为轻量级RTSP服务的数据源。这种设计使得数据处理更加灵活和高效。

🚀 启动和发布RTSP流:文章还介绍了如何启动RTSP服务和发布RTSP流。通过ButtonRtspServiceListener和ButtonRtspPublisherListener类,可以控制RTSP服务的启动和停止,以及流的发布和停止。这些步骤确保了流媒体服务的稳定运行和高效管理。

技术背景

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

技术实现

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

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

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

/* * 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();}}

音频回调处理:

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);}@Overridepublic 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_;}@Overridepublic 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);}}

视频回调数据:

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);}@Overridepublic 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);}}

启动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流:

//发布/停止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 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);}}}

总结

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

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

RTSP Android 流媒体
相关文章