@@ -124,8 +124,9 @@ class AudioRenderer(
124124 numberOfChannels : Int ,
125125 numberOfFrames : Int
126126 ): Map <String , Any >? {
127- if (bitsPerSample != 16 && bitsPerSample != 32 ) {
128- logDroppedFrame(" Unsupported bitsPerSample: $bitsPerSample " )
127+ // WebRTC AudioTrackSink always delivers 16-bit signed int16 PCM.
128+ if (bitsPerSample != 16 ) {
129+ logDroppedFrame(" Unsupported bitsPerSample: $bitsPerSample (expected 16)" )
129130 return null
130131 }
131132 if (numberOfChannels <= 0 ) {
@@ -137,7 +138,7 @@ class AudioRenderer(
137138 return null
138139 }
139140
140- val bytesPerSample = bitsPerSample / 8
141+ val bytesPerSample = 2 // 16-bit
141142 val bytesPerFrame = numberOfChannels * bytesPerSample
142143 if (bytesPerFrame <= 0 ) {
143144 logDroppedFrame(" Invalid bytesPerFrame: $bytesPerFrame " )
@@ -181,15 +182,15 @@ class AudioRenderer(
181182 when (targetFormat.commonFormat) {
182183 " int16" -> {
183184 result[" commonFormat" ] = " int16"
184- result[" data" ] = extractAsInt16Bytes(buffer, bitsPerSample, numberOfChannels, outChannels, frameLength)
185+ result[" data" ] = extractAsInt16Bytes(buffer, numberOfChannels, outChannels, frameLength)
185186 }
186187 " float32" -> {
187188 result[" commonFormat" ] = " float32"
188- result[" data" ] = extractAsFloat32Bytes(buffer, bitsPerSample, numberOfChannels, outChannels, frameLength)
189+ result[" data" ] = extractAsFloat32Bytes(buffer, numberOfChannels, outChannels, frameLength)
189190 }
190191 else -> {
191192 result[" commonFormat" ] = " int16"
192- result[" data" ] = extractAsInt16Bytes(buffer, bitsPerSample, numberOfChannels, outChannels, frameLength)
193+ result[" data" ] = extractAsInt16Bytes(buffer, numberOfChannels, outChannels, frameLength)
193194 }
194195 }
195196
@@ -203,97 +204,67 @@ class AudioRenderer(
203204 }
204205 }
205206
207+ /* *
208+ * Extracts int16 PCM bytes from an int16 source buffer.
209+ *
210+ * Fast path when channel counts match (direct copy).
211+ * Otherwise keeps only the first [outChannels] channels, interleaved.
212+ */
206213 private fun extractAsInt16Bytes (
207214 buffer : ByteBuffer ,
208- bitsPerSample : Int ,
209215 srcChannels : Int ,
210216 outChannels : Int ,
211217 numberOfFrames : Int
212218 ): ByteArray {
213- // Fast path for int16 with matching channel count.
214- if (bitsPerSample == 16 && srcChannels == outChannels) {
219+ // Fast path: matching channel count — bulk copy .
220+ if (srcChannels == outChannels) {
215221 val totalBytes = numberOfFrames * outChannels * 2
216222 val out = ByteArray (totalBytes)
217223 buffer.get(out , 0 , totalBytes.coerceAtMost(buffer.remaining()))
218224 return out
219225 }
220226
227+ // Channel reduction: keep first outChannels.
221228 val out = ByteArray (numberOfFrames * outChannels * 2 )
222229 val outBuf = ByteBuffer .wrap(out ).order(ByteOrder .LITTLE_ENDIAN )
223230
224- when (bitsPerSample) {
225- 16 -> {
226- for (frame in 0 until numberOfFrames) {
227- val srcOffset = frame * srcChannels * 2
228- for (ch in 0 until outChannels) {
229- val byteIndex = srcOffset + ch * 2
230- if (byteIndex + 1 < buffer.capacity()) {
231- buffer.position(byteIndex)
232- outBuf.putShort((frame * outChannels + ch) * 2 , buffer.short)
233- }
234- }
235- }
236- }
237- 32 -> {
238- for (frame in 0 until numberOfFrames) {
239- val srcOffset = frame * srcChannels * 4
240- for (ch in 0 until outChannels) {
241- val byteIndex = srcOffset + ch * 4
242- if (byteIndex + 3 < buffer.capacity()) {
243- buffer.position(byteIndex)
244- val sample16 = (buffer.int shr 16 ).toShort()
245- outBuf.putShort((frame * outChannels + ch) * 2 , sample16)
246- }
247- }
231+ for (frame in 0 until numberOfFrames) {
232+ val srcOffset = frame * srcChannels * 2
233+ for (ch in 0 until outChannels) {
234+ val byteIndex = srcOffset + ch * 2
235+ if (byteIndex + 1 < buffer.capacity()) {
236+ buffer.position(byteIndex)
237+ outBuf.putShort((frame * outChannels + ch) * 2 , buffer.short)
248238 }
249239 }
250240 }
251241
252242 return out
253243 }
254244
245+ /* *
246+ * Converts int16 PCM source to float32 bytes.
247+ *
248+ * Each int16 sample is scaled to the [-1.0, 1.0] range.
249+ * Only the first [outChannels] channels are kept.
250+ */
255251 private fun extractAsFloat32Bytes (
256252 buffer : ByteBuffer ,
257- bitsPerSample : Int ,
258253 srcChannels : Int ,
259254 outChannels : Int ,
260255 numberOfFrames : Int
261256 ): ByteArray {
262- // Fast path for float32 with matching channel count.
263- if (bitsPerSample == 32 && srcChannels == outChannels) {
264- val totalBytes = numberOfFrames * outChannels * 4
265- val out = ByteArray (totalBytes)
266- buffer.get(out , 0 , totalBytes.coerceAtMost(buffer.remaining()))
267- return out
268- }
269-
270257 val out = ByteArray (numberOfFrames * outChannels * 4 )
271258 val outBuf = ByteBuffer .wrap(out ).order(ByteOrder .LITTLE_ENDIAN )
272259
273- when (bitsPerSample) {
274- 16 -> {
275- for (frame in 0 until numberOfFrames) {
276- val srcOffset = frame * srcChannels * 2
277- for (ch in 0 until outChannels) {
278- val byteIndex = srcOffset + ch * 2
279- if (byteIndex + 1 < buffer.capacity()) {
280- buffer.position(byteIndex)
281- val sampleFloat = buffer.short.toFloat() / Short .MAX_VALUE
282- outBuf.putFloat((frame * outChannels + ch) * 4 , sampleFloat)
283- }
284- }
285- }
286- }
287- 32 -> {
288- for (frame in 0 until numberOfFrames) {
289- val srcOffset = frame * srcChannels * 4
290- for (ch in 0 until outChannels) {
291- val byteIndex = srcOffset + ch * 4
292- if (byteIndex + 3 < buffer.capacity()) {
293- buffer.position(byteIndex)
294- outBuf.putFloat((frame * outChannels + ch) * 4 , buffer.float)
295- }
296- }
260+ for (frame in 0 until numberOfFrames) {
261+ val srcOffset = frame * srcChannels * 2
262+ for (ch in 0 until outChannels) {
263+ val byteIndex = srcOffset + ch * 2
264+ if (byteIndex + 1 < buffer.capacity()) {
265+ buffer.position(byteIndex)
266+ val sampleFloat = buffer.short.toFloat() / Short .MAX_VALUE
267+ outBuf.putFloat((frame * outChannels + ch) * 4 , sampleFloat)
297268 }
298269 }
299270 }
0 commit comments