1
SRT implementation with mediamtx
urko edited this page 2025-05-18 19:22:39 +01:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Overview

Add dual-pipeline recording & streaming (MediaMuxer ↔ local + SRT → MediaMTX) to your existing Android project using StreamPack.


1. Update Gradle Dependencies

File: app/build.gradle.kts

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    namespace = "com.urkob.wittrail_android"
    compileSdk = 34
    defaultConfig {
        applicationId = "com.urkob.wittrail_android"
        minSdk = 24
        targetSdk = 34
        versionCode = 2
        versionName = "2.0"
    }
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.0"
    }
}

dependencies {
    // Compose & Lifecycle
    implementation(platform("androidx.compose:compose-bom:2023.08.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.material3:material3")
    implementation("androidx.lifecycle:lifecycle-service:2.7.0")

    // CameraX
    implementation("androidx.camera:camera-camera2:1.2.0")
    implementation("androidx.camera:camera-lifecycle:1.2.0")
    implementation("androidx.camera:camera-view:1.2.0")

    // StreamPack for dual-record & SRT
    implementation("io.github.thibaultbee.streampack:streampack-core:3.0.0-RC")
    implementation("io.github.thibaultbee.streampack:streampack-ui:3.0.0-RC")
    implementation("io.github.thibaultbee.streampack:streampack-extension-srt:3.0.0-RC")

    // Other
    implementation("androidx.core:core-ktx:1.12.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

2. AndroidManifest Modifications

File: app/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.urkob.wittrail_android">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:theme="@style/Theme.WittrailAndroid">

        <service
            android:name=".service.StreamRecordingService"
            android:exported="false"
            android:foregroundServiceType="camera|microphone"
            android:enabled="true" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

3. Create StreamRecordingService

File: app/src/main/java/com/urkob/wittrail_android/service/StreamRecordingService.kt

package com.urkob.wittrail_android.service

import android.app.*
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.lifecycle.LifecycleService
import io.github.thibaultbee.streampack.Streamer
import io.github.thibaultbee.streampack.extension.srt.UriMediaDescriptor
import io.github.thibaultbee.streampack.ext.cameraDualStreamer
import io.github.thibaultbee.streampack.ext.setAudioConfig
import io.github.thibaultbee.streampack.ext.setVideoConfig
import io.github.thibaultbee.streampack.media.AudioConfig
import io.github.thibaultbee.streampack.media.VideoConfig
import android.util.Size
import java.io.File

class StreamRecordingService : LifecycleService() {
    private lateinit var streamer: Streamer
    private val channelId = "StreamRecordingChannel"
    private val notificationId = 1001

    override fun onCreate() {
        super.onCreate()
        createNotificationChannel()
        startForeground(notificationId, buildNotification("Initializing..."))

        streamer = cameraDualStreamer(context = this)
        streamer.setVideoConfig(
            VideoConfig(
                resolution = Size(1280, 720),
                fps = 30,
                startBitrate = 2_000_000
            )
        )
        streamer.setAudioConfig(
            AudioConfig(
                sampleRate = 44100,
                channelConfig = android.media.AudioFormat.CHANNEL_IN_STEREO,
                startBitrate = 128_000
            )
        )
        streamer.startPreview()

        val localFile = File(getExternalFilesDir(null),
            "session_${System.currentTimeMillis()}.mp4")
        streamer.startRecord(localFile.absolutePath)

        val srtUrl = "srt://YOUR_MTX_HOST:8890/camera?passphrase=mySecretKey&pbkeylen=16"
        streamer.startStream(UriMediaDescriptor(srtUrl))

        updateNotification("Streaming to MTX server...")
    }

    override fun onDestroy() {
        streamer.stopStream()
        streamer.stopRecord()
        streamer.release()
        super.onDestroy()
    }

    override fun onBind(intent: Intent): IBinder? {
        super.onBind(intent)
        return null
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId,
                "Stream Recording",
                NotificationManager.IMPORTANCE_LOW
            )
            getSystemService(NotificationManager::class.java)
                .createNotificationChannel(channel)
        }
    }

    private fun buildNotification(content: String): Notification {
        return NotificationCompat.Builder(this, channelId)
            .setContentTitle("Wittrail Recording")
            .setContentText(content)
            .setSmallIcon(R.drawable.ic_notification)
            .setOngoing(true)
            .build()
    }
}

4. MainActivity: Control Buttons

File: app/src/main/java/com/urkob/wittrail_android/MainActivity.kt

package com.urkob.wittrail_android

import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import com.urkob.wittrail_android.service.StreamRecordingService
import com.urkob.wittrail_android.ui.theme.WittrailAndroidTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            WittrailAndroidTheme {
                val context = LocalContext.current
                val isRecording = remember { mutableStateOf(false) }

                Button(onClick = {
                    Intent(context, StreamRecordingService::class.java).also { intent ->
                        if (!isRecording.value) {
                            context.startForegroundService(intent)
                        } else {
                            context.stopService(intent)
                        }
                        isRecording.value = !isRecording.value
                    }
                }) {
                    Text(if (!isRecording.value) "Start Recording" else "Stop Recording")
                }
            }
        }
    }
}

5. Architecture Diagram

flowchart TB
  subgraph Android_Client
    A[CameraX + Mic] -->|DualStreamer| B[StreamPack]
    B --> C[Local MP4 via MediaMuxer]
    B --> D[SRT → MediaMTX]
  end

  subgraph Server
    D --> E[MediaMTX SRT Ingest]
    E --> F[runOnPublish → FFmpeg MP4]
    F --> G[Storage + Audit]
  end

Next Steps

  1. Replace YOUR_MTX_HOST with your servers address in srtUrl.
  2. Grant permissions at runtime for camera, mic, and storage.
  3. Test via Start Recording button and verify local MP4 plus server-side recordings.
  4. Enhance: compute SHA256 on the MP4 chunks or on file completion and log for audit.
  5. Extend: add error callbacks (streamer.setStreamErrorCallback) to update UI.

This setup gives you a production-ready, dual-pipeline Android client—local backup + secure, low-latency SRT streaming to your MediaMTX server.