截图
开启RTP端口监听
我们在发送端向端口40020发送数据,所以这里监听的是40020端口,payload type是97与发送端设置的一致。
audioRtpWrapper.open(40020, 97, 1000);
设置rtp callback用于接收aac原始数据。
audioRtpWrapper.setCallback { data, len ->
if (len < 4) return@setCallback
val index = indexArray.take()
if (currentTime == 0L) {
currentTime = System.currentTimeMillis()
}
val buffer = audioDecodeCodec.mediaCodec.getInputBuffer(index)
val time = (System.currentTimeMillis() - currentTime) * 1000
if (hasAuHeader) {
buffer?.position(0)
buffer?.put(data, 4, len - 4);
buffer?.position(0)
audioDecodeCodec.mediaCodec.queueInputBuffer(index, 0, len - 4, time, 1)
} else {
buffer?.position(0)
buffer?.put(data, 0, len);
buffer?.position(0)
audioDecodeCodec.mediaCodec.queueInputBuffer(index, 0, len, time, 1)
}
};
接收到aac数据后直接写入MediaCodec的inputbuffer,这里针对是否有au header来处理数据。如果有au header,那么跳过au header数据。au header length 和 au header共占用4 bytes。在通过index获取inputbuffer的时候要格外注意index的有效性,即index必须是mediacodec释放出来的buffer的index,否则会发送buffer写入错误。
创建MediaCodec解码aac数据
创建MediaCodec的时候要指定采样率、通道数、格式等信息,这些信息需要与发送端保持一致。
val sampleRate = 44100
val audioFormat = MediaFormat.createAudioFormat(
MediaFormat.MIMETYPE_AUDIO_AAC,
sampleRate,
audioChannelCount
)
audioFormat.setByteBuffer("csd-0", audioSpecificConfig)
var currentTime = 0L
indexArray.clear()
audioDecodeCodec = object : AudioDecodeCodec(audioFormat) {
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
indexArray.put(index)
}
}
这里的"csd-0"参数要格外的关注,因为这个参数不设置或是设置错误都会在解码过程中发生buffer的读写错误。audioSpecificConfig这个变量的生成规则可以参考下面这段代码:
private val audioChannelCount = 1;
private val audioProfile = 1
/**
* 97000, 88200, 64000, 48000,44100, 32000, 24000, 22050,16000, 12000, 11025, 8000,7350, 0, 0, 0
*/
private val audioIndex = 4
private val audioSpecificConfig = ByteArray(2).apply {
this[0] = ((audioProfile + 1).shl(3).and(0xff)).or(audioIndex.ushr(1).and(0xff)).toByte()
this[1] = ((audioIndex.shl(7).and(0xff)).or(audioChannelCount.shl(3).and(0xff))).toByte()
}.let {
val buffer = ByteBuffer.allocate(2)
buffer.put(it)
buffer.position(0)
buffer
}
当然如果不设置csd-0这个参数,那么从rtp返回的数据中有config数据也是可以的。但是rtp是广播形式的,它并不知道什么时候需要广播config数据,所以配置csd-0是比较好的选择。(注意:config数据指的是发送端的MediaCodec编码启动后生成的config 数据buffer)
使用AudioTrack播放解码后的音频
首先根据mediaformat信息创建AudioTrack并进行播放,这里设置的参数要与MediaCodec设置的一致,同事这种流模式播放(MODE_STREAM)
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
val sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
val channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, if (channelCount == 1) AudioFormat.CHANNEL_IN_MONO else AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
audioTrack = AudioTrack(AudioManager.STREAM_VOICE_CALL, sampleRate, channelCount, AudioFormat.ENCODING_PCM_16BIT, minBufferSize, MODE_STREAM);
audioTrack?.play();
}
从MediaCodec读取解码后的数据并写入AudioTrack,这里采用的是block方式写入。
override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
Log.d("audio_dragon","onOutputBufferAvailable $index")
kotlin.runCatching {
val buffer = codec.getOutputBuffer(index) ?: return;
buffer.position(info.offset);
audioTrack?.write(buffer, info.size, WRITE_BLOCKING);
codec.releaseOutputBuffer(index, false);
}
}
总结
- rtp发送aac参考另一篇文章
- 。
- csd-0参数很关键,一定要留意它的设置是否正确。
- 接收的aac数据需要按照是否有au header区分处理。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。