Skip to content

Commit 6d685ae

Browse files
committed
refactor(*): introduce streamer pipeline to enable multiple output
1 parent 44c7631 commit 6d685ae

File tree

54 files changed

+3473
-986
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+3473
-986
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright (C) 2025 Thibault B.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.thibaultbee.streampack.core.elements.endpoints
17+
18+
import io.github.thibaultbee.streampack.core.configuration.mediadescriptor.MediaDescriptor
19+
import io.github.thibaultbee.streampack.core.elements.data.Frame
20+
import io.github.thibaultbee.streampack.core.elements.encoders.CodecConfig
21+
import kotlinx.coroutines.flow.MutableStateFlow
22+
import kotlinx.coroutines.flow.StateFlow
23+
24+
class DummyEndpoint : IEndpointInternal {
25+
private val _isOpenFlow = MutableStateFlow(false)
26+
override val isOpenFlow: StateFlow<Boolean> = _isOpenFlow
27+
28+
var numOfAudioFramesWritten = 0
29+
private set
30+
var numOfVideoFramesWritten = 0
31+
private set
32+
val numOfFramesWritten: Int
33+
get() = numOfAudioFramesWritten + numOfVideoFramesWritten
34+
35+
private val _isStreamingFlow = MutableStateFlow(false)
36+
val isStreamingFlow: StateFlow<Boolean> = _isStreamingFlow
37+
38+
override val info: IEndpoint.IEndpointInfo
39+
get() = TODO("Not yet implemented")
40+
41+
override fun getInfo(type: MediaDescriptor.Type): IEndpoint.IEndpointInfo {
42+
TODO("Not yet implemented")
43+
}
44+
45+
override val metrics: Any
46+
get() = TODO("Not yet implemented")
47+
48+
override suspend fun open(descriptor: MediaDescriptor) {
49+
_isOpenFlow.emit(true)
50+
}
51+
52+
override suspend fun close() {
53+
_isOpenFlow.emit(false)
54+
}
55+
56+
override suspend fun write(frame: Frame, streamPid: Int) {
57+
when {
58+
frame.isAudio -> numOfAudioFramesWritten++
59+
frame.isVideo -> numOfVideoFramesWritten++
60+
}
61+
}
62+
63+
override fun addStreams(streamConfigs: List<CodecConfig>): Map<CodecConfig, Int> {
64+
return streamConfigs.associateWith { it.hashCode() }
65+
}
66+
67+
override fun addStream(streamConfig: CodecConfig): Int {
68+
return streamConfig.hashCode()
69+
}
70+
71+
override suspend fun startStream() {
72+
_isStreamingFlow.emit(true)
73+
}
74+
75+
override suspend fun stopStream() {
76+
_isStreamingFlow.emit(false)
77+
}
78+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (C) 2025 Thibault B.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.thibaultbee.streampack.core.pipelines
17+
18+
import org.junit.Assert.*
19+
20+
class StreamerPipelineTest {
21+
22+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (C) 2025 Thibault B.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.thibaultbee.streampack.core.pipelines.outputs
17+
18+
import io.github.thibaultbee.streampack.core.elements.encoders.IEncoderInternal
19+
import io.github.thibaultbee.streampack.core.logger.Logger
20+
import kotlinx.coroutines.flow.MutableStateFlow
21+
import kotlinx.coroutines.flow.StateFlow
22+
23+
class DummyVideoPipelineOutput : DummyPipelineOutput(hasAudio = false, hasVideo = true), IVideoPipelineOutputInternal {
24+
companion object {
25+
private const val TAG = "DummyVideoPipelineOutput"
26+
}
27+
28+
override var targetRotation: Int = 0
29+
override val surface: StateFlow<SurfaceWithSize?>
30+
get() = TODO("Not yet implemented")
31+
override var videoSourceTimestampOffset: Long
32+
get() = TODO("Not yet implemented")
33+
set(value) {}
34+
override var videoFrameRequestedListener: IEncoderInternal.IByteBufferInput.OnFrameRequestedListener?
35+
get() = TODO("Not yet implemented")
36+
set(value) {}
37+
}
38+
39+
open class DummyPipelineOutput(override val hasAudio: Boolean, override val hasVideo: Boolean) :
40+
IPipelineOutputInternal {
41+
42+
override var streamListener: IPipelineOutputInternal.Listener? = null
43+
44+
private val _throwable = MutableStateFlow<Throwable?>(null)
45+
override val throwableFlow: StateFlow<Throwable?> = _throwable
46+
47+
private val _isStreaming = MutableStateFlow(false)
48+
override val isStreamingFlow: StateFlow<Boolean> = _isStreaming
49+
50+
override suspend fun startStream() {
51+
Logger.i(TAG, "Start stream")
52+
_isStreaming.emit(true)
53+
}
54+
55+
override suspend fun stopStream() {
56+
Logger.i(TAG, "Stop stream")
57+
_isStreaming.emit(false)
58+
}
59+
60+
override suspend fun release() {
61+
Logger.i(TAG, "Release")
62+
_isStreaming.emit(false)
63+
}
64+
65+
companion object {
66+
private const val TAG = "DummyPipelineOutput"
67+
}
68+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (C) 2025 Thibault B.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.thibaultbee.streampack.core.pipelines.outputs.encoding
17+
18+
import android.content.Context
19+
import androidx.test.platform.app.InstrumentationRegistry
20+
import io.github.thibaultbee.streampack.core.configuration.mediadescriptor.UriMediaDescriptor
21+
import io.github.thibaultbee.streampack.core.elements.endpoints.DummyEndpoint
22+
import junit.framework.TestCase.assertTrue
23+
import kotlinx.coroutines.test.runTest
24+
import org.junit.Assert.assertFalse
25+
import org.junit.Test
26+
27+
class EncodingPipelineOutputTest {
28+
private val context: Context = InstrumentationRegistry.getInstrumentation().context
29+
30+
@Test
31+
fun testOpenClose() = runTest {
32+
val output = EncodingPipelineOutput(context, endpointInternal = DummyEndpoint())
33+
output.open(UriMediaDescriptor("file://test.mp4"))
34+
assertTrue(output.isOpenFlow.value)
35+
output.close()
36+
assertFalse(output.isOpenFlow.value)
37+
}
38+
}

core/src/androidTest/java/io/github/thibaultbee/streampack/core/utils/ConfigurationUtils.kt renamed to core/src/androidTest/java/io/github/thibaultbee/streampack/core/pipelines/utils/ConfigurationUtils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package io.github.thibaultbee.streampack.core.utils
16+
package io.github.thibaultbee.streampack.core.pipelines.utils
1717

1818
import android.media.MediaFormat
1919
import android.util.Size

core/src/androidTest/java/io/github/thibaultbee/streampack/core/utils/FileUtils.kt renamed to core/src/androidTest/java/io/github/thibaultbee/streampack/core/pipelines/utils/FileUtils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package io.github.thibaultbee.streampack.core.utils
16+
package io.github.thibaultbee.streampack.core.pipelines.utils
1717

1818
import androidx.test.platform.app.InstrumentationRegistry
1919
import java.io.File
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright (C) 2025 Thibault B.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.thibaultbee.streampack.core.pipelines.utils
17+
18+
import android.media.MediaFormat
19+
import android.util.Size
20+
import io.github.thibaultbee.streampack.core.elements.encoders.VideoCodecConfig
21+
import org.junit.Assert.assertEquals
22+
import org.junit.Assert.fail
23+
import org.junit.Test
24+
25+
class SourceConfigUtilsTest {
26+
27+
@Test
28+
fun videoSourceConfigFromEmpty() {
29+
try {
30+
SourceConfigUtils.buildVideoSourceConfig(emptySet())
31+
fail("Video codec configs must not be empty")
32+
} catch (_: Throwable) {
33+
}
34+
}
35+
36+
@Test
37+
fun buildVideoSourceConfigWithSimple() {
38+
// Given
39+
val videoCodecConfigs = setOf(
40+
VideoCodecConfig(
41+
MediaFormat.MIMETYPE_VIDEO_AVC,
42+
resolution = Size(1280, 720),
43+
fps = 30
44+
),
45+
VideoCodecConfig(MediaFormat.MIMETYPE_VIDEO_AVC, resolution = Size(1280, 720), fps = 30)
46+
)
47+
48+
// When
49+
val videoSourceConfig = SourceConfigUtils.buildVideoSourceConfig(videoCodecConfigs)
50+
51+
// Then
52+
assertEquals(1280, videoSourceConfig.resolution.width)
53+
assertEquals(720, videoSourceConfig.resolution.height)
54+
assertEquals(30, videoSourceConfig.fps)
55+
}
56+
57+
@Test
58+
fun buildVideoSourceConfigWithDifferentResolution() {
59+
// Given
60+
val videoCodecConfigs = setOf(
61+
VideoCodecConfig(MediaFormat.MIMETYPE_VIDEO_AVC, resolution = Size(1280, 720)),
62+
VideoCodecConfig(MediaFormat.MIMETYPE_VIDEO_AVC, resolution = Size(1920, 1080))
63+
)
64+
65+
// When
66+
val videoSourceConfig = SourceConfigUtils.buildVideoSourceConfig(videoCodecConfigs)
67+
68+
// Then
69+
assertEquals(1920, videoSourceConfig.resolution.width)
70+
assertEquals(1080, videoSourceConfig.resolution.height)
71+
}
72+
73+
@Test
74+
fun videoSourceConfigWithDifferentFps() {
75+
// Given
76+
val videoCodecConfigs = setOf(
77+
VideoCodecConfig(MediaFormat.MIMETYPE_VIDEO_AVC, fps = 30),
78+
VideoCodecConfig(MediaFormat.MIMETYPE_VIDEO_AVC, fps = 25)
79+
)
80+
81+
// When
82+
try {
83+
SourceConfigUtils.buildVideoSourceConfig(videoCodecConfigs)
84+
fail("All video codec configs must have the same fps")
85+
} catch (e: IllegalArgumentException) {
86+
assertEquals("All video codec configs must have the same fps", e.message)
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)