首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >RTP中的AMR译码

RTP中的AMR译码
EN

Stack Overflow用户
提问于 2022-08-17 10:40:36
回答 1查看 139关注 0票数 4

我正在接受一些RTP流,我只知道它的AMR八位组对齐了每包100毫秒。一些第三方可以接收到同样的流和它的“可听到的”,所以它是适当的。现在我收到这些数据并试图破译,没有运气.

init:

代码语言:javascript
运行
复制
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()

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

代码语言:javascript
运行
复制
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)
}

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

代码语言:javascript
运行
复制
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) +when,但是我得到了类似于上面的日志,但是使用了0x8000100b

在另一台设备上-- Pixel上的Android 12 --我正在变得类似

代码语言:javascript
运行
复制
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,FT,Q等。,直接到译码器,但我已经把它写得不太好了?还是我的decode方法被错误地实现了?简而言之:如何正确解码(播放)从RTP流获得的AMR?

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

EN

回答 1

Stack Overflow用户

发布于 2022-08-30 09:03:35

原来,我必须“解包”,也是AMR头和“重新打包”数据到AMR帧。所讨论的有效负载的第一个字节是ToC列表。

F0是CMR,并且可能是完整的,启动pos 1我们可以在msb (或作为int >= 128或十六进制第一char >= 8) +1上计算1的连续字节数,所以如果payload1以0(十六进制)开头,那么ToC大小是1,有效负载是一个帧,我们可以将它传递给解码器(不要忘记跳过第一个CMR字节!)。在我的示例ToC大小为5,所以我必须除以剩余的有效载荷和交错与ToC字节,其中“框架”=一个字节的ToC +帧-有效载荷。

我的整个有效载荷有91个字节-1用于cmr -5 ToCs给出了5个帧(toc大小)的85个字节(toc大小),它给出了5个帧(toc字节)+ 17 ( 85 /5 amr负载)大小。

我们只需除以有效负载的其余部分,但通过检查每个帧在每个ToC字节中传递的比特率模式并与固定的每个比特率帧大小进行比较(请参阅下面代码中的index ),确保该大小是值得的。

代码语言:javascript
运行
复制
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
}

返回的数据可能会被使用,而不是rtpPacket.payloaddecode方法中张贴了问题(好吧,译码器本身的代码可能有一点改进,因为最后一行是无法到达的,但即使在这种形式下也是有效的)

在我的例子中,amr_frame_sizes是const数组,其中100 ms的AMR被划分为5帧。这些大小根据index (“可变”比特率)调整到这样的情况--20 to帧和位置。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73387051

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档