您的位置:首页技术文章
文章详情页

Android手机通过rtp发送aac数据给vlc播放的实现步骤

【字号: 日期:2022-09-19 14:24:52浏览:3作者:猪猪
截屏

Android手机通过rtp发送aac数据给vlc播放的实现步骤

AudioRecord音频采集

private val sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) private val channelCount = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) private val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, if (channelCount == 1) CHANNEL_IN_MONO else CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);runInBackground { audioRecord = AudioRecord(MediaRecorder.AudioSource.MIC,sampleRate,if (channelCount == 1) CHANNEL_IN_MONO else CHANNEL_IN_STEREO,AudioFormat.ENCODING_PCM_16BIT,2 * minBufferSize ) audioRecord.startRecording()}

音频采集时需要设置采集参数,设置的这些参数需要与创建MediaCodec时的参数一致。

sampleRate是采样率:44100 channelCount是通道数:1 单个采样数据大小格式:AudioFormat.ENCODING_PCM_16BIT 最小数据buffer:AudioRecord.getMinBufferSize()计算获取

override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {try { codec.getInputBuffer(index)?.let { bb ->var startTime = System.currentTimeMillis();var readSize = audioRecord.read(bb, bb.capacity())log { 'read time ${System.currentTimeMillis() - startTime} read size $readSize' }if (readSize < 0) { readSize = 0}codec.queueInputBuffer(index, 0, readSize, System.nanoTime() / 1000, 0) }}catch (e:Exception){ e.printStackTrace()} }

这里采用的阻塞的方式采集数据,所以AudioRecord依据设置的采样频率生成数据的,我们可以直接把当前的时间设置为录制的时间戳。

MediaCodec编码音频数据

val mediaFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC,audioSampleRate,audioChannelCount)mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, audioBitRate)mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,MediaCodecInfo.CodecProfileLevel.AACObjectLC)mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, audioMaxBufferSize)

为MediaCodec创建MediaFormat并设置参数,这里设置的音频参数必须与AudioRecord一致。

MIME_TYPE:'audio/mp4a-latm' 采样频率与AudioRecord一致:44100 通道数与AudioRecord一致:1 KEY_AAC_PROFILE配置为低带宽要求类型:AACObjectLC KEY_BIT_RATE设置的大小影响编码压缩率:128 * 1024

override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {try { codec.getInputBuffer(index)?.let { bb ->var startTime = System.currentTimeMillis();var readSize = audioRecord.read(bb, bb.capacity())log { 'read time ${System.currentTimeMillis() - startTime} read size $readSize' }if (readSize < 0) { readSize = 0}codec.queueInputBuffer(index, 0, readSize, System.nanoTime() / 1000, 0) }}catch (e:Exception){ e.printStackTrace()} }

给MediaCodec传数据的时候设置的时间戳是当前的系统时间,由于我们使用rtp发送实时数据,所以flag不需要设置结束标志。

audioCodec = object : AudioEncodeCodec(mediaFormat) { override fun onOutputBufferAvailable( codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo ) {try { val buffer = codec.getOutputBuffer(index) ?: return if (lastSendAudioTime == 0L) {lastSendAudioTime = info.presentationTimeUs; } val increase = (info.presentationTimeUs - lastSendAudioTime) * audioSampleRate / 1000 / 1000 if (hasAuHeader) {buffer.position(info.offset)buffer.get(bufferArray, 4, info.size)auHeaderLength.apply { bufferArray[0] = this[0] bufferArray[1] = this[1]}auHeader(info.size).apply { bufferArray[2] = this[0] bufferArray[3] = this[1]}audioRtpWrapper?.sendData(bufferArray, info.size + 4, 97, true, increase.toInt()) } else {buffer.position(info.offset)buffer.get(bufferArray, 0, info.size)audioRtpWrapper?.sendData(bufferArray, info.size, 97, true, increase.toInt()) } lastSendAudioTime = info.presentationTimeUs codec.releaseOutputBuffer(index, false)} catch (e: Exception) { e.printStackTrace()} } }

从MediaCodec读出的是aac原始的数据,我们可以根据具体的需求来决定是否添加au header发送。这里实现了有au header和没有 au header两种方案。没有au header的情况我们直接把MediaCode读出的数据通过rtp发送出去。有au header的情况我们需要在原始的aac数据前面追加4个字节的au header。是否有au header与vlc播放的sdp内容有关。后面会详解介绍sdp内容的设置。

private val auHeaderLength = ByteArray(2).apply {this[0] = 0this[1] = 0x10 } private fun auHeader(len: Int): ByteArray {return ByteArray(2).apply { this[0] = (len and 0x1fe0 shr 5).toByte() this[1] = (len and 0x1f shl 3).toByte()} } au header length占用两个字节,它会描述au header的大小,这里设置为2. au header 占用两个字节,它描述了aac原始数据的大小,这里需要根据MediaCodec返回的aac原始数据大小进行设置。 Rtp发送数据

我们使用jrtplib库来发送数据,这里对库进行简单的封装并提供了java封装类RtpWrapper。

public class RtpWrapper { private long nativeObject = 0; private IDataCallback callback; public RtpWrapper() {init(); } @Override protected void finalize() throws Throwable {release();super.finalize(); } public void setCallback(IDataCallback callback) {this.callback = callback; } void receivedData(byte[] buffer, int len) {if(this.callback != null)this.callback.onReceivedData(buffer, len); } public interface IDataCallback {void onReceivedData(byte[] buffer, int len); } static {try { System.loadLibrary('rtp-lib'); initLib();} catch (Throwable e) { e.printStackTrace();} } private native static void initLib(); private native boolean init(); private native boolean release(); public native boolean open(int port, int payloadType, int sampleRate); public native boolean close(); /** * @param ip '192.168.1.1' * @return */ public native boolean addDestinationIp(String ip); public native int sendData(byte[] buffer, int len, int payloadType, boolean mark, int increase);}

open方法要指定发送数据使用的端口,payloadType设置载体类型,sampleRate是采样率。addDestinationIp用于添加接收端ip地址,地址格式: '192.168.1.1'。sendData方法用于发送数据,increase是时间间隔,时间单位是 sampleRate/秒

override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {audioRtpWrapper = RtpWrapper()audioRtpWrapper?.open(audioRtpPort, audioPayloadType, audioSampleRate)audioRtpWrapper?.addDestinationIp(ip) }

MediaCodec返回format的时候创建rtp连接并指定目的地址。

try { val buffer = codec.getOutputBuffer(index) ?: return if (lastSendAudioTime == 0L) {lastSendAudioTime = info.presentationTimeUs; } val increase = (info.presentationTimeUs - lastSendAudioTime) * audioSampleRate / 1000 / 1000 if (hasAuHeader) {buffer.position(info.offset)buffer.get(bufferArray, 4, info.size)auHeaderLength.apply { bufferArray[0] = this[0] bufferArray[1] = this[1]}auHeader(info.size).apply { bufferArray[2] = this[0] bufferArray[3] = this[1]}audioRtpWrapper?.sendData(bufferArray, info.size + 4, 97, true, increase.toInt()) } else {buffer.position(info.offset)buffer.get(bufferArray, 0, info.size)audioRtpWrapper?.sendData(bufferArray, info.size, 97, true, increase.toInt()) } lastSendAudioTime = info.presentationTimeUs codec.releaseOutputBuffer(index, false)} catch (e: Exception) { e.printStackTrace()}

发送数据的时候需要指定payloadType,距离上次发送数据的时间间隔等信息。(info.presentationTimeUs - lastSendAudioTime)计算的是以微妙为单位的时间间隔。(info.presentationTimeUs - lastSendAudioTime) * audioSampleRate / 1000 / 1000转换成sampleRate/秒为单位的时间间隔。rtp发送aac数据使用的payloadType为97。

SDP文件配置

vlc播放器播放rtp音频数据时需要指定sdp文件,它通过读取sdp文件中的信息可以了解rpt接收端口、payloadType类型、音频的格式等信息用于接收数据流并解码播放。这里有两种配置方式用于支持有au header和没有au header的情况。

有au header

m=audio 40020 RTP/AVP 97a=rtpmap:97 mpeg4-generic/44100/1a=fmtp: 97 streamtype=5;config=1208;sizeLength=13; indexLength=3 没有au header

m=audio 40020 RTP/AVP 97a=rtpmap:97 mpeg4-generic/44100/1a=fmtp: 97 streamtype=5;config=1208

sdp文件配置了端口号为40020, Rtp payload type为97,音频的采样率为44100、通道数为1。

音频config配置计算方式:

Android手机通过rtp发送aac数据给vlc播放的实现步骤

比较有au header和没有au header的两个版本,发现它们的区别在于是否配置了sizeLength和indexLength。

我这里的au header是两个字节的,sizeLength为13代表占用了13bit,indexLength为3代表占用3bit。配合发送数据时添加au header的代码就容易理解了。

private fun auHeader(len: Int): ByteArray {return ByteArray(2).apply { this[0] = (len and 0x1fe0 shr 5).toByte() this[1] = (len and 0x1f shl 3).toByte()} }vlc测试播放 vlc打开工程目录下的play_audio.sdp/play_audio_auheader.sdp 。 启动Android应用指定运行vlc的电脑的ip地址。 开始录制,如何vlc打开的是play_audio_auheader.sdp,那么在开始录制前需要选中auHeader check box 总结 AudioRecord的设置信息与MediaCodec的配置信息必须一致。 AudioRecord采用block的方式读取数据,这样我们可以直接使用系统时间来配置encode时间戳。 是否需要添加au header与sdp配置有关,vlc播放器会按照sdp配置解析au header。 sdp中的config需要按照实际的音频配置信息计算得出,否则不能正常播放。 工程git地址

https://github.com/mjlong123123/AudioRecorder

以上就是Android手机通过rtp发送aac数据给vlc播放的实现步骤的详细内容,更多关于Android rtp发送aac数据给vlc播放的资料请关注好吧啦网其它相关文章!

标签: Android
相关文章: