-
-
Notifications
You must be signed in to change notification settings - Fork 921
Expand file tree
/
Copy pathBaseSender.kt
More file actions
207 lines (181 loc) · 6.6 KB
/
Copy pathBaseSender.kt
File metadata and controls
207 lines (181 loc) · 6.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package com.pedro.common.base
import android.util.Log
import com.pedro.common.BitrateManager
import com.pedro.common.ConnectChecker
import com.pedro.common.FrameLifecycleListener
import com.pedro.common.QueueSnapshot
import com.pedro.common.StreamBlockingQueue
import com.pedro.common.TransportEvent
import com.pedro.common.frame.MediaFrame
import com.pedro.common.onMainThread
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.nio.ByteBuffer
abstract class BaseSender(
protected val connectChecker: ConnectChecker,
protected val TAG: String
) {
@Volatile
protected var running = false
// Tracks actual queue capacity; updated when resizeCache() replaces the queue.
// getCacheSize() reads this field — it must stay in sync with the live queue object.
private var cacheSize = 400
@Volatile
protected var queue = StreamBlockingQueue(cacheSize)
protected var audioFramesSent: Long = 0
protected var videoFramesSent: Long = 0
var droppedAudioFrames: Long = 0
protected set
var droppedVideoFrames: Long = 0
protected set
private val bitrateManager: BitrateManager = BitrateManager(connectChecker)
protected var isEnableLogs = true
private var job: Job? = null
protected val scope = CoroutineScope(Dispatchers.IO)
@Volatile
var bytesSend = 0L
protected set
@Volatile
protected var bytesSendPerSecond = 0L
/**
* Lifecycle callback invoked after the sender's dispatch thread has fully consumed a
* [MediaFrame]. Register a [FrameLifecycleListener] to receive notifications when the
* frame's [MediaFrame.data] buffer may safely be returned to a pool.
*
* Called from the sender's IO coroutine; implementations must not block.
*/
var frameLifecycleListener: FrameLifecycleListener? = null
abstract fun setVideoInfo(sps: ByteBuffer, pps: ByteBuffer?, vps: ByteBuffer?)
abstract fun setAudioInfo(sampleRate: Int, isStereo: Boolean)
protected abstract suspend fun onRun()
protected abstract suspend fun stopImp(clear: Boolean = true)
fun sendMediaFrame(mediaFrame: MediaFrame) {
if (running && !queue.trySend(mediaFrame)) {
when (mediaFrame.type) {
MediaFrame.Type.VIDEO -> {
Log.i(TAG, "Video frame discarded (queue full)")
droppedVideoFrames++
}
MediaFrame.Type.AUDIO -> {
Log.i(TAG, "Audio frame discarded (queue full)")
droppedAudioFrames++
}
}
}
}
/**
* Called by subclass [onRun] implementations after a [MediaFrame] has been fully
* dispatched to the network socket. Notifies [frameLifecycleListener] so callers can
* recycle the frame's buffer.
*/
protected fun notifyFrameConsumed(frame: MediaFrame) {
frameLifecycleListener?.onFrameConsumed(frame)
}
fun start() {
bitrateManager.reset()
queue.clear()
running = true
job = scope.launch {
val bitrateTask = async {
while (scope.isActive && running) {
//bytes to bits
bitrateManager.calculateBitrate(bytesSendPerSecond * 8)
bytesSendPerSecond = 0
delay(timeMillis = 1000)
}
}
val frameEventTask = async {
while (scope.isActive && running) {
val event = TransportEvent.QueueOverflow(
droppedVideo = droppedVideoFrames,
droppedAudio = droppedAudioFrames,
queueCapacity = cacheSize,
queueSize = queue.getSize(),
)
onMainThread { connectChecker.onTransportEvent(event) }
delay(timeMillis = 1500)
}
}
onRun()
}
}
suspend fun stop(clear: Boolean = true) {
running = false
stopImp(clear)
resetSentAudioFrames()
resetSentVideoFrames()
resetDroppedAudioFrames()
resetDroppedVideoFrames()
resetBytesSend()
job?.cancelAndJoin()
job = null
queue.clear()
}
@Throws(IllegalArgumentException::class)
fun hasCongestion(percentUsed: Float = 20f): Boolean {
if (percentUsed !in 0.0..100.0) throw IllegalArgumentException("the value must be in range 0 to 100")
val size = queue.getSize().toFloat()
val remaining = queue.remainingCapacity().toFloat()
val capacity = size + remaining
return size >= capacity * (percentUsed / 100f)
}
fun resizeCache(newSize: Int) {
if (newSize < queue.getSize() - queue.remainingCapacity()) {
throw RuntimeException("Can't fit current cache inside new cache size")
}
val tempQueue = StreamBlockingQueue(newSize)
queue.drainTo(tempQueue)
queue = tempQueue
cacheSize = newSize // keep getCacheSize() in sync with the new queue capacity
}
/**
* Returns the current maximum capacity of the sender frame queue.
* Reflects the value passed to the most recent [resizeCache] call.
*/
fun getCacheSize(): Int = cacheSize
fun getItemsInCache(): Int = queue.getSize()
/**
* Returns a point-in-time snapshot of the sender queue state.
* Thread-safe; values are read atomically from the backing [StreamBlockingQueue].
*/
fun getQueueSnapshot(): QueueSnapshot = QueueSnapshot(
capacity = cacheSize,
items = queue.getSize(),
)
fun clearCache() {
queue.clear()
}
fun getSentAudioFrames(): Long = audioFramesSent
fun getSentVideoFrames(): Long = videoFramesSent
fun resetSentAudioFrames() {
audioFramesSent = 0
}
fun resetSentVideoFrames() {
videoFramesSent = 0
}
fun resetDroppedAudioFrames() {
droppedAudioFrames = 0
}
fun resetDroppedVideoFrames() {
droppedVideoFrames = 0
}
fun setLogs(enable: Boolean) {
isEnableLogs = enable
}
fun setBitrateExponentialFactor(factor: Float) {
bitrateManager.exponentialFactor = factor
}
fun getBitrateExponentialFactor() = bitrateManager.exponentialFactor
fun setDelay(delay: Long) {
queue.setCacheTime(delay)
}
fun resetBytesSend() {
bytesSend = 0
}
}