RTP 的 AMR 解码

sna*_*msm 5 android android-mediacodec

我正在接收一些 RTP 流,我只知道每个数据包的 AMR-WB 八位字节对齐 100 毫秒。某些第 3 方可以接收相同的流及其“可听”,因此这是正确的。现在我正在接收这些数据并尝试解码,但没有运气......

在里面:

val sampleRate = 16000
val mc = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AMR_WB)
val mf = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AMR_WB, sampleRate, 1)
mf.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate) // is it needed?
mc.configure(mf, null, null, 0)
mc.start()
Run Code Online (Sandbox Code Playgroud)

分别解码每个数据包:

private fun decode(decoder: MediaCodec, mediaFormat: MediaFormat, rtpPacket: RtpPacket): ByteArray {
    var outputBuffer: ByteBuffer
    var outputBufferIndex: Int

    val inputBuffers: Array<ByteBuffer> = decoder.inputBuffers
    var outputBuffers: Array<ByteBuffer> = decoder.outputBuffers

    // input
    val inputBufferIndex = decoder.dequeueInputBuffer(-1L)
    if (inputBufferIndex >= 0) {
        val inputBuffer = inputBuffers[inputBufferIndex]
        inputBuffer.clear()
        inputBuffer.put(rtpPacket.payload)
        // native ACodec/MediaCodec crash in here (log below)
        decoder.queueInputBuffer(inputBufferIndex, 0, rtpPacket.payload.size, System.nanoTime()/1000, 0)
    }

    // output
    val bufferInfo: MediaCodec.BufferInfo = MediaCodec.BufferInfo()
    outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, -1L)
    Timber.i("outputBufferIndex: ${outputBufferIndex}")
    when (outputBufferIndex) {
        MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> {
            Timber.d("INFO_OUTPUT_BUFFERS_CHANGED")
            outputBuffers = decoder.outputBuffers
        }
        MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
            val format: MediaFormat = decoder.outputFormat
            Timber.d("INFO_OUTPUT_FORMAT_CHANGED $format")
            audioTrack.playbackRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
        }
        MediaCodec.INFO_TRY_AGAIN_LATER -> Timber.d("INFO_TRY_AGAIN_LATER")
        else -> {
            val outBuffer = outputBuffers[outputBufferIndex]
            outBuffer.position(bufferInfo.offset);
            outBuffer.limit(bufferInfo.offset + bufferInfo.size);

            val chunk = ByteArray(bufferInfo.size)
            outBuffer[chunk]
            outBuffer.clear()
            audioTrack.write(
                chunk,
                bufferInfo.offset,
                bufferInfo.offset + bufferInfo.size
            )
            decoder.releaseOutputBuffer(outputBufferIndex, false)
            Timber.v("chunk size:${chunk.size}")
            return chunk
        }
    }

    // All decoded frames have been rendered, we can stop playing now
    if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
        Timber.d("BUFFER_FLAG_END_OF_STREAM")
    }
    return ByteArray(0)
}
Run Code Online (Sandbox Code Playgroud)

遗憾的是我正在使用一些(干净的)Android 10

E/ACodec: [OMX.google.amrwb.decoder] ERROR(0x80001001)
E/ACodec: signalError(omxError 0x80001001, internalError -2147483648)
E/MediaCodec: Codec reported err 0x80001001, actionCode 0, while in state 6
E/RtpReceiver: java.lang.IllegalStateException
    at android.media.MediaCodec.native_dequeueInputBuffer(Native Method)
    at android.media.MediaCodec.dequeueInputBuffer(MediaCodec.java:2727)
Run Code Online (Sandbox Code Playgroud)

我可能应该在某些中打包dequeueOutputBuffer+ ,但是随后我得到了与上面类似的日志,但是带有whenwhile(true)0x8000100b

在另一台设备上 - Pixel 上的 Android 12 - 我越来越相似

D/BufferPoolAccessor2.0: bufferpool2 0xb400007067901978 : 4(32768 size) total buffers - 4(32768 size) used buffers - 0/5 (recycle/alloc) - 0/0 (fetch/transfer)
D/CCodecBufferChannel: [c2.android.amrwb.decoder#471] work failed to complete: 14
E/MediaCodec: Codec reported err 0xe, actionCode 0, while in state 6/STARTED
E/RtpReceiver: java.lang.IllegalStateException
    at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
    at android.media.MediaCodec.dequeueOutputBuffer(MediaCodec.java:3535)
Run Code Online (Sandbox Code Playgroud)

我显然切断了 RTP 标头(payload上面使用的),但没有做其他任何事情。我还应该识别有效负载/AMR 标头吗?在它的内部有例如 FT - 帧类型索引 - 它决定比特率,所以解码器应该在start()调用之前获取这个参数,对吗?或者我可以将整个有效负载(包括CMR、ToC 和 FT、Q 等)直接传递给解码器,但我初始化得不太好?或者我的decode方法以某种方式错误地实现了?简而言之:如何正确解码(和播放)从 RTP 流获得的 AMR-WB?

F0 84 84 84 84 04编辑:值得一提的是,每个数据包的有效负载都以

sna*_*msm 2

事实证明,我还必须“解包”AMR 标头并将数据“重新打包”到 AMR 帧中。有问题的有效负载的第一个字节是目录列表。

F0是 CMR 并且可以被省略,从 pos 1 开始,我们可以计算 ToC 大小 - msb 上为 1 的连续字节数(或者int >= 128首先作为或作为十六进制char >= 8)+ 1。因此,如果有效负载 [1] 以0(十六进制)开头,则 ToC 大小为1 并且有效负载是一帧,我们可以将其传递给解码器(不要忘记跳过第一个 CMR 字节!)。在我的示例中,ToC 大小为 5,因此我必须将其余有效负载划分并与 ToC 字节交错,其中“帧”= ToC + 帧有效负载的一个字节。

我的整个有效负载有 91 个字节 -1 对于 cmr -5 ToCs 为 5 个帧(toc 大小)提供 85 个字节,这为 5 个帧提供 1(toc 字节)+ 17(85/5 amrpayload)大小

我们可以划分其余的有效负载,但值得通过检查每个帧的每个 ToC 字节中传递的比特率模式并与每个比特率的固定帧大小进行比较来确保大小(请查看下面的index代码)

fun decode(rtpPacket: RtpPacket): ByteArray {
    var outData = ByteArray(0)
    var position = 0
    position++ // skip payload header, ignore CMR - rtpPacket.payload[0]

    var tocLen = 0
    while (getBit(rtpPacket.payload[position].toInt(), 7)) {
        //first byte has 1 at msb
        position++
        tocLen++
    }
    if (tocLen > 0) { // if there is any toc detected
        // first byte which has NOT 1 at msb also belongs to ToC
        position++
        tocLen++
    }
    //Timber.i("decoded tocListSize: $tocLen")

    if (tocLen > 0) {
        // starting from 1 because this is first ToC byte position after ommiting CMR
        for (i in 1 until (tocLen + 1)) {
            val index = rtpPacket.payload[i].toInt() shr 3 and 0xf
            if (index >= 9) {
                Timber.w("Bad AMR ToC, index=$index")
                break
            }
            val amr_frame_sizes = intArrayOf(17, 23, 32, 36, 40, 46, 50, 58, 60, 5)
            val frameSize = amr_frame_sizes[index]
            //Timber.i("decoded i:$i index:$index frameSize:frameSize position:$position")
            if (position + frameSize > rtpPacket.payloadLength) {
                Timber.w("Truncated AMR frame")
                break
            }
            val frame = ByteArray(1 + frameSize)
            frame[0] = rtpPacket.payload[i]
            System.arraycopy(rtpPacket.payload, position, frame, 1, frameSize)

            outData = outData.plus(decode(frame))
            position += frameSize
        }
    } else { // single frame case, NOT TESTED!!
        outData = ByteArray(rtpPacket.payloadLength - 1) // without CMR
        System.arraycopy(rtpPacket.payload, 1, outData, 0, outData.size)
        outData = decode(outData)
    }
    return outData
}
Run Code Online (Sandbox Code Playgroud)

rtpPacket.payload可以使用返回的数据而不是在有问题的方法中使用decode(嗯,解码器本身的代码可能会有所改进,因为最后几行无法访问,但即使在这种形式下也可以工作)

amr_frame_sizes对于我的情况来说是 const 数组,其中 100 毫秒的 AMR 被分为 5 帧。这些大小根据这种情况进行调整 - 20ms 帧 - 和位置根据index(“可变”比特率)