截图

开启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);
        }
    }

总结

git

https://github.com/mjlong123123/AudioPlayer.git


mjlong123
4 声望3 粉丝