You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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模式配置

  1. 打开OBS,点「设置」-「推流」
  2. 服务选「自定义」
  3. 服务器填:srt://你手机的局域网IP:8888?mode=caller&latency=1000000&transtype=live
    • 把你手机的局域网IP换成当前手机在WiFi里的实际IP(手机设置-WLAN-点当前WiFi就能看)
    • 端口8888要和Android代码里的LISTEN_PORT一致
    • latency参数建议和Android端保持一样,不然容易音画不同步
  4. 流密钥空着就行,确定后点「开始推流」

五、许可证说明

  1. libsrt库:用的是MPL 2.0许可证,闭源商用没问题。只要你没改libsrt的源码,不用开源你自己的代码;要是改了libsrt,就得把修改后的部分开源。
  2. FFmpeg(如果用的话):默认是LGPL v2.1或GPL v2。动态链接FFmpeg的话,你的应用可以闭源;静态链接的话,得遵循GPL规则,开源和FFmpeg交互的相关代码。

六、踩坑提示

  • 确保手机和电脑在同一个WiFi下,路由器别屏蔽了用的端口
  • 要是连接断了,自己加个重连逻辑,比如在receiveStream里捕获异常后重新调用startListening
  • Android 8+后台限制,要后台收流就得开前台服务,还要弹通知
  • 调试的时候开SRT的日志(loglevel设7),方便找问题

内容的提问来源于stack exchange,提问作者online internaut

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 04:37:17