1
+ package com.cloudwebrtc.webrtc
2
+
3
+ import android.util.Log
4
+ import org.webrtc.*
5
+ import java.util.concurrent.*
6
+
7
+ /*
8
+ Copyright 2017, Lyo Kato <lyo.kato at gmail.com> (Original Author)
9
+ Copyright 2017-2021, Shiguredo Inc.
10
+
11
+ Licensed under the Apache License, Version 2.0 (the "License");
12
+ you may not use this file except in compliance with the License.
13
+ You may obtain a copy of the License at
14
+
15
+ http://www.apache.org/licenses/LICENSE-2.0
16
+
17
+ Unless required by applicable law or agreed to in writing, software
18
+ distributed under the License is distributed on an "AS IS" BASIS,
19
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ See the License for the specific language governing permissions and
21
+ limitations under the License.
22
+ */
23
+ internal class SimulcastVideoEncoderFactoryWrapper (
24
+ sharedContext : EglBase .Context ? ,
25
+ enableIntelVp8Encoder : Boolean ,
26
+ enableH264HighProfile : Boolean
27
+ ) : VideoEncoderFactory {
28
+
29
+ /* *
30
+ * Factory that prioritizes software encoder.
31
+ *
32
+ * When the selected codec can't be handled by the software encoder,
33
+ * it uses the hardware encoder as a fallback. However, this class is
34
+ * primarily used to address an issue in libwebrtc, and does not have
35
+ * purposeful usecase itself.
36
+ *
37
+ * To use simulcast in libwebrtc, SimulcastEncoderAdapter is used.
38
+ * SimulcastEncoderAdapter takes in a primary and fallback encoder.
39
+ * If HardwareVideoEncoderFactory and SoftwareVideoEncoderFactory are
40
+ * passed in directly as primary and fallback, when H.264 is used,
41
+ * libwebrtc will crash.
42
+ *
43
+ * This is because SoftwareVideoEncoderFactory does not handle H.264,
44
+ * so [SoftwareVideoEncoderFactory.createEncoder] returns null, and
45
+ * the libwebrtc side does not handle nulls, regardless of whether the
46
+ * fallback is actually used or not.
47
+ *
48
+ * To avoid nulls, we simply pass responsibility over to the HardwareVideoEncoderFactory.
49
+ * This results in HardwareVideoEncoderFactory being both the primary and fallback,
50
+ * but there aren't any specific problems in doing so.
51
+ */
52
+ private class Fallback (private val hardwareVideoEncoderFactory : VideoEncoderFactory ) :
53
+ VideoEncoderFactory {
54
+
55
+ private val softwareVideoEncoderFactory: VideoEncoderFactory = SoftwareVideoEncoderFactory ()
56
+
57
+ override fun createEncoder (info : VideoCodecInfo ): VideoEncoder ? {
58
+ val softwareEncoder = softwareVideoEncoderFactory.createEncoder(info)
59
+ val hardwareEncoder = hardwareVideoEncoderFactory.createEncoder(info)
60
+ return if (hardwareEncoder != null && softwareEncoder != null ) {
61
+ VideoEncoderFallback (hardwareEncoder, softwareEncoder)
62
+ } else {
63
+ softwareEncoder ? : hardwareEncoder
64
+ }
65
+ }
66
+
67
+ override fun getSupportedCodecs (): Array <VideoCodecInfo > {
68
+ val supportedCodecInfos: MutableList <VideoCodecInfo > = mutableListOf ()
69
+ supportedCodecInfos.addAll(softwareVideoEncoderFactory.supportedCodecs)
70
+ supportedCodecInfos.addAll(hardwareVideoEncoderFactory.supportedCodecs)
71
+ return supportedCodecInfos.toTypedArray()
72
+ }
73
+
74
+ }
75
+
76
+ /* *
77
+ * Wraps each stream encoder and performs the following:
78
+ * - Starts up a single thread
79
+ * - When the width/height from [initEncode] doesn't match the frame buffer's,
80
+ * scales the frame prior to encoding.
81
+ * - Always calls the encoder on the thread.
82
+ */
83
+ private class StreamEncoderWrapper (private val encoder : VideoEncoder ) : VideoEncoder {
84
+
85
+ val executor: ExecutorService = Executors .newSingleThreadExecutor()
86
+ var streamSettings: VideoEncoder .Settings ? = null
87
+
88
+ override fun initEncode (
89
+ settings : VideoEncoder .Settings ,
90
+ callback : VideoEncoder .Callback ?
91
+ ): VideoCodecStatus {
92
+ streamSettings = settings
93
+ val future = executor.submit(Callable {
94
+ // LKLog.i {
95
+ // """initEncode() thread=${Thread.currentThread().name} [${Thread.currentThread().id}]
96
+ // | streamSettings:
97
+ // | numberOfCores=${settings.numberOfCores}
98
+ // | width=${settings.width}
99
+ // | height=${settings.height}
100
+ // | startBitrate=${settings.startBitrate}
101
+ // | maxFramerate=${settings.maxFramerate}
102
+ // | automaticResizeOn=${settings.automaticResizeOn}
103
+ // | numberOfSimulcastStreams=${settings.numberOfSimulcastStreams}
104
+ // | lossNotification=${settings.capabilities.lossNotification}
105
+ // """.trimMargin()
106
+ // }
107
+ return @Callable encoder.initEncode(settings, callback)
108
+ })
109
+ return future.get()
<
E377
code>110 + }
111
+
112
+ override fun release (): VideoCodecStatus {
113
+ val future = executor.submit(Callable { return @Callable encoder.release() })
114
+ return future.get()
115
+ }
116
+
117
+ override fun encode (
118
+ frame : VideoFrame ,
119
+ encodeInfo : VideoEncoder .EncodeInfo ?
120
+ ): VideoCodecStatus {
121
+ val future = executor.submit(Callable {
122
+ // LKLog.d { "encode() buffer=${frame.buffer}, thread=${Thread.currentThread().name} " +
123
+ // "[${Thread.currentThread().id}]" }
124
+ if (streamSettings == null ) {
125
+ return @Callable encoder.encode(frame, encodeInfo)
126
+ } else if (frame.buffer.width == streamSettings!! .width) {
127
+ return @Callable encoder.encode(frame, encodeInfo)
128
+ } else {
129
+ // The incoming buffer is different than the streamSettings received in initEncode()
130
+ // Need to scale.
131
+ val originalBuffer = frame.buffer
132
+ // TODO: Do we need to handle when the scale factor is weird?
133
+ val adaptedBuffer = originalBuffer.cropAndScale(
134
+ 0 , 0 , originalBuffer.width, originalBuffer.height,
135
+ streamSettings!! .width, streamSettings!! .height
136
+ )
137
+ val adaptedFrame = VideoFrame (adaptedBuffer, frame.rotation, frame.timestampNs)
138
+ val result = encoder.encode(adaptedFrame, encodeInfo)
139
+ adaptedBuffer.release()
140
+ return @Callable result
141
+ }
142
+ })
143
+ return future.get()
144
+ }
145
+
146
+ override fun setRateAllocation (
147
+ allocation : VideoEncoder .BitrateAllocation ? ,
148
+ frameRate : Int
149
+ ): VideoCodecStatus {
150
+ val future = executor.submit(Callable {
151
+ return @Callable encoder.setRateAllocation(
152
+ allocation,
153
+ frameRate
154
+ )
155
+ })
156
+ return future.get()
157
+ }
158
+
159
+ override fun getScalingSettings (): VideoEncoder .ScalingSettings {
160
+ val future = executor.submit(Callable { return @Callable encoder.scalingSettings })
161
+ return future.get()
162
+ }
163
+
164
+ override fun getImplementationName (): String {
165
+ val future = executor.submit(Callable { return @Callable encoder.implementationName })
166
+ return future.get()
167
+ }
168
+ }
169
+
170
+ private class StreamEncoderWrapperFactory (private val factory : VideoEncoderFactory ) :
171
+ VideoEncoderFactory {
172
+ override fun createEncoder (videoCodecInfo : VideoCodecInfo ? ): VideoEncoder ? {
173
+ val encoder = factory.createEncoder(videoCodecInfo)
174
+ if (encoder == null ) {
175
+ return null
176
+ }
177
+ return StreamEncoderWrapper (encoder)
178
+ }
179
+
180
+ override fun getSupportedCodecs (): Array <VideoCodecInfo > {
181
+ return factory.supportedCodecs
182
+ }
183
+ }
184
+
185
+
186
+ private val primary: VideoEncoderFactory
187
+ private val fallback: VideoEncoderFactory
188
+ private val native: SimulcastVideoEncoderFactory
189
+
190
+ init {
191
+ val hardwareVideoEncoderFactory = HardwareVideoEncoderFactory (
192
+ sharedContext, enableIntelVp8Encoder, enableH264HighProfile
193
+ )
194
+ primary = StreamEncoderWrapperFactory (hardwareVideoEncoderFactory)
195
+ fallback = StreamEncoderWrapperFactory (Fallback (primary))
196
+ native = SimulcastVideoEncoderFactory (primary, fallback)
197
+ }
198
+
199
+ override fun createEncoder (info : VideoCodecInfo ? ): VideoEncoder ? {
200
+ return native.createEncoder(info)
201
+ }
202
+
203
+ override fun getSupportedCodecs (): Array <VideoCodecInfo > {
204
+ return native.supportedCodecs
205
+ }
206
+
207
+ }
0 commit comments