我正在接受一些RTP流,我只知道它的AMR八位组对齐了每包100毫秒。一些第三方可以接收到同样的流和它的“可听到的”,所以它是适当的。现在我收到这些数据并试图破译,没有运气.
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)
}
遗憾的是,我正在使用一些(干净的) 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)
我可能应该在一些dequeueOutputBuffer
+when
中打包while(true)
+when
,但是我得到了类似于上面的日志,但是使用了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)
我很明显地切断了RTP头(上面使用的payload
),但是没有做任何其他事情。我也应该识别有效载荷/AMR头吗?在它里面有例如FT -帧类型的索引,它决定了比特率,所以解码器应该在start()
调用之前得到这个参数,对吗?或者我可以将整个有效载荷传递给CMR,ToC,FT,Q等。,直接到译码器,但我已经把它写得不太好了?还是我的decode
方法被错误地实现了?简而言之:如何正确解码(播放)从RTP流获得的AMR?
编辑:值得一提的是,每个数据包的有效载荷都以F0 84 84 84 84 04
开头
发布于 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
),确保该大小是值得的。
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.payload
在decode
方法中张贴了问题(好吧,译码器本身的代码可能有一点改进,因为最后一行是无法到达的,但即使在这种形式下也是有效的)
在我的例子中,amr_frame_sizes
是const数组,其中100 ms的AMR被划分为5帧。这些大小根据index
(“可变”比特率)调整到这样的情况--20 to帧和位置。
https://stackoverflow.com/questions/73387051
复制相似问题