Add SRT implementation with mediamtx
parent
f95c8d7620
commit
6fb843a27a
252
SRT-implementation-with-mediamtx.md
Normal file
252
SRT-implementation-with-mediamtx.md
Normal file
@ -0,0 +1,252 @@
|
||||
### 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`
|
||||
```kotlin
|
||||
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`
|
||||
```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`
|
||||
```kotlin
|
||||
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`
|
||||
```kotlin
|
||||
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
|
||||
```mermaid
|
||||
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 server’s 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 SHA‑256 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.
|
Loading…
x
Reference in New Issue
Block a user