Android端以Listener模式接收Caller模式推送的SRT流实现求助
Android以Listener模式接收OBS Caller模式SRT流实现方案
一、先配好权限
在AndroidManifest.xml里添加必要的网络权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- Android 12+ 要后台收流的话,得加前台服务权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
如果目标SDK是Android 13+,还要动态申请POST_NOTIFICATIONS权限,不然前台服务启动不了。
二、集成SRT库
推荐用官方libsrt的Android预编译包,或者集成带SRT支持的FFmpeg。用gradle引依赖的话,比如JitPack上的版本:
dependencies { implementation 'com.github.haivision:srt:v1.5.3' }
三、Android端Listener模式核心代码(Kotlin)
1. 初始化SRT并监听连接
import android.util.Log import java.net.InetAddress import java.net.InetSocketAddress import java.nio.ByteBuffer import kotlin.concurrent.thread class SRTListener { private val TAG = "SRTListener" private val LISTEN_PORT = 8888 // 自定义端口,要和OBS配置一致 private var srtSocket: Long = 0L lateinit var playerPipeSink: java.io.PipedOutputStream // 给播放器传流用的管道 fun startListening() { thread(start = true) { // 初始化SRT库 if (srt_startup() != 0) { Log.e(TAG, "SRT初始化崩了") return@thread } // 创建SRT Socket srtSocket = srt_socket(AF_INET, SOCK_DGRAM, 0) if (srtSocket == -1L) { Log.e(TAG, "创建SRT Socket失败") srt_cleanup() return@thread } // 设为Listener模式 srt_setloglevel(7) // 调试开日志,上线改3就行 srt_setsockopt(srtSocket, 0, SRTO_MODE, SRT_MODE_LISTENER, 4) // 绑定端口 val bindAddr = InetSocketAddress("0.0.0.0", LISTEN_PORT) if (srt_bind(srtSocket, bindAddr.address, LISTEN_PORT) != 0) { Log.e(TAG, "绑端口失败: ${srt_getlasterror_str()}") srt_close(srtSocket) srt_cleanup() return@thread } // 开始监听 if (srt_listen(srtSocket, 1) != 0) { Log.e(TAG, "监听失败: ${srt_getlasterror_str()}") srt_close(srtSocket) srt_cleanup() return@thread } Log.i(TAG, "已开始监听SRT连接,端口: $LISTEN_PORT") // 等OBS主动连过来 val clientSocket = srt_accept(srtSocket, null, null) if (clientSocket == -1L) { Log.e(TAG, "接连接失败: ${srt_getlasterror_str()}") srt_close(srtSocket) srt_cleanup() return@thread } Log.i(TAG, "OBS Caller已连上") // 读流数据给播放器 receiveStream(clientSocket) } } private fun receiveStream(clientSocket: Long) { val buffer = ByteBuffer.allocate(1024 * 1024) // 开1MB缓冲区够用来 while (true) { val readLen = srt_recv(clientSocket, buffer.array(), buffer.capacity(), 0) if (readLen <= 0) { Log.e(TAG, "流断了或者读失败: ${srt_getlasterror_str()}") break } // 把数据写进管道,给ExoPlayer读 try { playerPipeSink.write(buffer.array(), 0, readLen) } catch (e: Exception) { Log.e(TAG, "写管道失败: ${e.message}") break } buffer.clear() } // 收尾资源 srt_close(clientSocket) srt_close(srtSocket) srt_cleanup() } // SRT的JNI方法,根据你用的库调整 external fun srt_startup(): Int external fun srt_cleanup(): Int external fun srt_socket(domain: Int, type: Int, protocol: Int): Long external fun srt_setsockopt(socket: Long, level: Int, optname: Int, optval: Int, optlen: Int): Int external fun srt_bind(socket: Long, addr: InetAddress, port: Int): Int external fun srt_listen(socket: Long, backlog: Int): Int external fun srt_accept(socket: Long, addr: InetAddress?, port: IntArray?): Long external fun srt_recv(socket: Long, buf: ByteArray, len: Int, flags: Int): Int external fun srt_close(socket: Long): Int external fun srt_getlasterror_str(): String external fun srt_setloglevel(level: Int): Int companion object { init { System.loadLibrary("srt") // 加载libsrt.so } private const val AF_INET = 2 private const val SOCK_DGRAM = 2 private const val SRTO_MODE = 17 private const val SRT_MODE_LISTENER = 1 } }
2. 对接ExoPlayer播放
用管道把SRT的流传给ExoPlayer,这样能实时播放:
import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.PipeDataSource import java.io.PipedInputStream import java.io.PipedOutputStream class SRTPlayerActivity : AppCompatActivity() { private lateinit var exoPlayer: ExoPlayer private lateinit var srtListener: SRTListener override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_player) // 建管道,SRT收的流往这里写,ExoPlayer从这里读 val pipeIn = PipedInputStream() val pipeOut = PipedOutputStream(pipeIn) srtListener = SRTListener().apply { playerPipeSink = pipeOut } // 初始化ExoPlayer exoPlayer = ExoPlayer.Builder(this).build() val playerView = findViewById<com.google.android.exoplayer2.ui.PlayerView>(R.id.player_view) playerView.player = exoPlayer // 自定义数据源,用Pipe接SRT流 val dataSourceFactory = DataSource.Factory { PipeDataSource(pipeIn) } val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory) .createMediaSource(MediaItem.fromUri("srt://local")) exoPlayer.setMediaSource(mediaSource) exoPlayer.prepare() exoPlayer.playWhenReady = true // 启动SRT监听 srtListener.startListening() } override fun onDestroy() { super.onDestroy() exoPlayer.release() try { srtListener.playerPipeSink.close() } catch (e: Exception) { e.printStackTrace() } } }
四、OBS端Caller模式配置
- 打开OBS,点「设置」-「推流」
- 服务选「自定义」
- 服务器填:
srt://你手机的局域网IP:8888?mode=caller&latency=1000000&transtype=live- 把
你手机的局域网IP换成当前手机在WiFi里的实际IP(手机设置-WLAN-点当前WiFi就能看) - 端口
8888要和Android代码里的LISTEN_PORT一致 latency参数建议和Android端保持一样,不然容易音画不同步
- 把
- 流密钥空着就行,确定后点「开始推流」
五、许可证说明
- libsrt库:用的是MPL 2.0许可证,闭源商用没问题。只要你没改libsrt的源码,不用开源你自己的代码;要是改了libsrt,就得把修改后的部分开源。
- FFmpeg(如果用的话):默认是LGPL v2.1或GPL v2。动态链接FFmpeg的话,你的应用可以闭源;静态链接的话,得遵循GPL规则,开源和FFmpeg交互的相关代码。
六、踩坑提示
- 确保手机和电脑在同一个WiFi下,路由器别屏蔽了用的端口
- 要是连接断了,自己加个重连逻辑,比如在
receiveStream里捕获异常后重新调用startListening - Android 8+后台限制,要后台收流就得开前台服务,还要弹通知
- 调试的时候开SRT的日志(loglevel设7),方便找问题
内容的提问来源于stack exchange,提问作者online internaut
相关产品推荐
相关产品推荐

