从RTP进行AMR解码

4 人关注

我正在接收一些RTP流,我只知道它的AMR-WB八位数对齐,每包100ms。一些第三方可以接收相同的数据流,并且可以 "听到",所以是正确的。现在我正在接收这些数据并试图解码,但没有成功......

init:

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()

分别对每个数据包进行解码。

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)

可悲的是,我在一些(干净的)安卓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)

我也许应该把dequeueOutputBuffer+when打包在一些while(true)中,但这样我就会得到与上面类似的日志,但有0x8000100b

在另一台设备上--Pixel上的安卓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)

我明显地切断了RTP头(payload在上面使用),但没有做其他事情。我是否也应该识别有效载荷/AMR头?其中有FT--帧类型索引--它决定了比特率,所以解码器应该在调用start()之前得到这个参数,对吗?或者我可以传递整个有效载荷,用CMR, ToC with FT, Q etc.直达解码器,但我却把它引向了不那么好的地方?或者我的decode的方法在某种程度上是错误的?简而言之:如何正确解码(和播放)从RTP流得到的AMR-WB?

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

3 个评论
如果你使用高级语言,如TypeScript或想使用C#,那么请使用NAudio(在github上找到),它将帮助你解决这些问题。
它在任何其他设备上或与其他RTP流一起工作吗?
可悲的是,我不清楚,我没有机会接触到其他的AMR RTPs,但我很清楚,"我的流 "是正确的。
android
android-mediacodec
snachmsm
snachmsm
发布于 2022-08-17
1 个回答
snachmsm
snachmsm
发布于 2022-08-30
已采纳
0 人赞同

事实证明,我必须把AMR头 "解压",并把数据 "重新打包 "到AMR帧中。

F0 是CMR,可以省略,从位置1开始,我们可以计算ToC大小--msb上有1的连续字节数(或如 int >= 128 或如十六进制的第一个 char >= 8 )+1。因此,如果payload[1]以 0 (十六进制)开始,那么ToC大小为1,payload为一帧,我们可以将其传递给解码器(别忘了跳过第一个CMR字节!)。在我的例子中,ToC大小为5,所以我必须将payload的其余部分与ToC字节隔开,其中 "帧"=ToC的一个字节+帧-payload。

我的整个有效载荷有91个字节 -cmr为1 -5个ToCs 5个帧有85个字节(Toc大小)。 这样,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)