当前位置: 首页 > news >正文

通过 RootEncoder 进行安卓直播 RTSP 推流

当前需要把安卓摄像头所生成的视频流通过 RTSP 协议传输到服务器上,也就是推流。最开始用 libstreaming,直接 source 引入,不知为何压根没有推流,故放弃。估计是太老 API 吧,都七年前最后更新的。于是再网上搜索下,结论是没啥好的安卓直播推流组件,要么就是老掉牙的。

最后 AI 推荐这款 RootEncoder https://github.com/pedroSG94/RootEncoder,持续更新的。但我先要批判它一番,因为问题确实多多,搞得我头发掉不少:

  • 模块众多,甚至有 iOS 版本,比较混乱,文档也说不清楚的样子
  • 没有文档,没有例子,要自己摸索,AI 的例子也跑不通
  • 依赖是在 jitpack 的,你要另外配置,——我是安卓新手,这个搞半天
  • API 混乱,差一个小版本就没了某个类,——作者重构的任意性太大,搞的例子都不通用
  • 它的名字也换来换去,搞得我不好搜索。早期叫 rtmp-rtsp-stream-client-java 后来改为 RootEncoder

虽然搞起来没有一帆风顺,但通过不懈的努力,在老外一篇文章帮助下,终于调通 RSTP 推流,于是写就此外——以飨读者!

添加依赖

依赖是在 jitpack 的,其他地方没有。操作是:打开工程根目录下的settings.gradle加入maven { url 'https://jitpack.io' }

加入maven { url 'https://jitpack.io' }依赖源。


保存然后打开app/build.gradle,加入rtmp-rtsp-stream-client的依赖,注意版本不能错。新版本 API 又不同,代码也不晓得怎么改(这货就是这样)。

implementation 'com.github.pedroSG94.rtmp-rtsp-stream-client-java:rtplibrary:2.2.4'

最后点击【File】菜单下面这里的才有效,执行网络远程下载相关的依赖。

安卓代码开发

添加权限

老操作了,对AndroidManifest.xml添加权限:

<uses-permissionandroid:name="android.permission.INTERNET"/><uses-permissionandroid:name="android.permission.RECORD_AUDIO"/><uses-permissionandroid:name="android.permission.CAMERA"/><uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/><!--Optional for play store--><uses-featureandroid:name="android.hardware.camera"android:required="false"/><uses-featureandroid:name="android.hardware.camera.autofocus"android:required="false"/>

添加布局文件

目录res/layout下新建activity_open_gl_rtsp.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".OpenGlRtspActivity"> <com.pedro.rtplibrary.view.OpenGlView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:orientation="vertical" android:padding="16dp"> <EditText android:id="@+id/et_rtp_url" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="RTSP URL" android:inputType="textUri" android:padding="8dp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="16dp"> <Button android:id="@+id/b_start_stop" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Start" android:layout_marginEnd="8dp" /> <Button android:id="@+id/switch_camera" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Switch Camera" /> </LinearLayout> <Button android:id="@+id/b_record" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Record" android:layout_marginTop="8dp" /> </LinearLayout> </RelativeLayout>

回到里面注册一下新布局:

<activityandroid:name=".OpenGlRtspActivity"android:exported="false"android:theme="@style/Theme.MyApplication"/>

页面逻辑

新建OpenGlRtspActivity.kt

importandroid.os.Bundleimportandroid.util.Logimportandroid.view.Menuimportandroid.view.MenuItemimportandroid.view.MotionEventimportandroid.view.SurfaceHolderimportandroid.view.Viewimportandroid.view.View.OnTouchListenerimportandroid.view.WindowManagerimportandroid.widget.Buttonimportandroid.widget.EditTextimportandroid.widget.Toastimportandroidx.activity.ComponentActivityimportcom.pedro.encoder.input.gl.SpriteGestureControllerimportcom.pedro.encoder.input.video.CameraOpenExceptionimportcom.pedro.rtplibrary.rtsp.RtspCamera1importcom.pedro.rtplibrary.view.OpenGlViewimportcom.pedro.rtsp.utils.ConnectCheckerRtspimportjava.io.FileclassOpenGlRtspActivity:ComponentActivity(),ConnectCheckerRtsp,View.OnClickListener,SurfaceHolder.Callback,OnTouchListener{privatevarrtspCamera1:RtspCamera1?=nullprivatelateinitvarbutton:ButtonprivatelateinitvarbRecord:ButtonprivatelateinitvaretUrl:EditTextprivatevarcurrentDateAndTime=""privatevarfolder:File?=nullprivatelateinitvaropenGlView:OpenGlViewprivatevalspriteGestureController=SpriteGestureController()overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)setContentView(R.layout.activity_open_gl_rtsp)openGlView=findViewById<OpenGlView>(R.id.surfaceView)button=findViewById<Button>(R.id.b_start_stop)button.setOnClickListener(this)bRecord=findViewById<Button>(R.id.b_record)bRecord.setOnClickListener(this)etUrl=findViewById<EditText>(R.id.et_rtp_url)etUrl.setHint("RTSP")etUrl.setText("")valswitchCamera=findViewById<Button>(R.id.switch_camera)switchCamera.setOnClickListener(this)rtspCamera1=RtspCamera1(openGlView,this)openGlView.holder.addCallback(this)openGlView.setOnTouchListener(this)}overridefunonCreateOptionsMenu(menu:Menu):Boolean{// I commented this line because I don't need this menu// menuInflater.inflate(R.menu.gl_menu, menu)returntrue}overridefunonOptionsItemSelected(item:MenuItem):Boolean{//Stop listener for image, text and gif stream objects.spriteGestureController.stopListener()returnfalse}overridefunonConnectionStartedRtsp(rtspUrl:String){}overridefunonConnectionSuccessRtsp(){runOnUiThread{Toast.makeText(this@OpenGlRtspActivity,"Connection success",Toast.LENGTH_SHORT).show()}}overridefunonConnectionFailedRtsp(reason:String){Log.e("RTTMA",reason)runOnUiThread{Toast.makeText(this@OpenGlRtspActivity,"Connection failed.$reason",Toast.LENGTH_SHORT).show()rtspCamera1!!.stopStream()button.setText("Start")}}overridefunonNewBitrateRtsp(bitrate:Long){}overridefunonDisconnectRtsp(){runOnUiThread{Toast.makeText(this@OpenGlRtspActivity,"Disconnected",Toast.LENGTH_SHORT).show()}}overridefunonAuthErrorRtsp(){runOnUiThread{Toast.makeText(this@OpenGlRtspActivity,"Auth error",Toast.LENGTH_SHORT).show()}}overridefunonAuthSuccessRtsp(){runOnUiThread{Toast.makeText(this@OpenGlRtspActivity,"Auth success",Toast.LENGTH_SHORT).show()}}overridefunonClick(view:View){when(view.id){R.id.b_start_stop->if(!rtspCamera1!!.isStreaming){if(rtspCamera1!!.isRecording||rtspCamera1!!.prepareAudio()&&rtspCamera1!!.prepareVideo()){button.text="Stop"rtspCamera1!!.startStream(etUrl!!.text.toString())}else{Toast.makeText(this,"Error preparing stream, This device cant do it",Toast.LENGTH_SHORT).show()}}else{button.text="Start"rtspCamera1!!.stopStream()}R.id.switch_camera->try{rtspCamera1!!.switchCamera()}catch(e:CameraOpenException){Toast.makeText(this,e.message,Toast.LENGTH_SHORT).show()}else->{}}}overridefunsurfaceCreated(surfaceHolder:SurfaceHolder){}overridefunsurfaceChanged(surfaceHolder:SurfaceHolder,i:Int,i1:Int,i2:Int){rtspCamera1!!.startPreview()}overridefunsurfaceDestroyed(surfaceHolder:SurfaceHolder){if(rtspCamera1!!.isStreaming){rtspCamera1!!.stopStream()button.text="Start"}rtspCamera1!!.stopPreview()}overridefunonTouch(view:View,motionEvent:MotionEvent):Boolean{if(spriteGestureController.spriteTouched(view,motionEvent)){spriteGestureController.moveSprite(view,motionEvent)spriteGestureController.scaleSprite(motionEvent)returntrue}returnfalse}}

最后制作一个按钮作为入口:

Button(onClick={valintent=Intent(context,OpenGlRtspActivity::class.java)context.startActivity(intent)},modifier=Modifier.padding(top=16.dp)){Text(text="RootEncoder推流")}

搞定~

其他开源

  • Android端RTMP推流实现- RtmpPublishKit,挺不错的,作者有文章专门介绍实现思路,可惜的是不再开源; 博客文章《深度解析RTMP直播协议:从保姆级入门到高级优化!》
  • 编写一个简单的RTSP协议-主流程 https://github.com/ImSjt/RtspServer
  • ZLMediaKit-Android-Stream
  • 播放器,不是推流 https://github.com/alexeyvasilyev/rtsp-client-android
http://www.jsqmd.com/news/534128/

相关文章:

  • 2026雅思考前冲刺机考模考网站,在线全真模拟系统备考平台 - 品牌2026
  • Qwen3-0.6B-FP8运维日志分析实战:从海量数据中智能定位故障
  • ChatGPT对话时间监控:从原理到实践的AI辅助开发指南
  • 萤石开放平台二次开发:哪些非摄像头设备也能轻松接入?
  • 钉钉智能客服机器人开发实战:从零搭建到生产环境部署
  • Ubuntu 20.04下rMATS 4.1.2环境配置避坑指南(附GSL 2.5安装详解)
  • 裂隙煤体注浆模拟:当浆液遇上变质量渗流
  • RTX 4060笔记本也能玩转大模型?实测DeepSeek-R1-8B本地推理速度与显存占用
  • 蜜雪年营收336亿:净利59亿 门店59823家 张红甫卸任CEO
  • 2026雅思机考软件哪个好?带精准口语评分的备考工具实测 - 品牌2026
  • UE:如何管理打包时的配置文件排除
  • 避开施工陷阱!市政管网非开挖靠谱企业怎么选? - 品牌推荐大师1
  • 实时目标检测开源模型DAMO-YOLO效果展示:小目标手机精准框选案例
  • AnyDesk v9.6.12 | 高速免费远程桌面控制工具
  • YOLO11环境搭建避坑指南:快速解决部署中的常见问题
  • 张雪峰收入
  • 2025-2026-2 《网络攻防实践》第2次作业
  • OCRmyPDF性能优化指南:从效率瓶颈到极速处理的7个关键突破
  • 2026年2月卡套接头厂家实力推荐:不锈钢/穿板/弯通/直通/铜/出口/三通/四通/中间接头,精选耐用流体连接方案! - 呼呼拉呼
  • DeepSeek-OCR-2性能测试:不同硬件平台上的推理速度对比
  • Fish Speech 1.5语音合成效果展示:医疗科普内容+专业术语准确输出
  • 实战指南:使用Docker GPU部署CosyVoice 2的避坑与优化
  • ChatTTS 使用教程:从零构建高效语音合成工作流
  • 查看openclaw所有版本
  • 2026年原型工具选型指南:打破偏见,Axure和墨刀的真实定位
  • Cordriver在走廊场景下的端到端自动驾驶安全优化实践
  • 5个颠覆性技巧:Blender置换贴图让你的3D模型细节提升10倍
  • UE:如何自动规范项目资产命名
  • 突破Unity与Arduino实时通信瓶颈:WRMHL亚毫秒级响应方案深度解析
  • OpenClaw飞书机器人深度配置:GLM-4.7-Flash对话触发任务详解