av一区二区在线观看_亚洲男人的天堂网站_日韩亚洲视频_在线成人免费_欧美日韩精品免费观看视频_久草视

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

Android手機(jī)通過(guò)rtp發(fā)送aac數(shù)據(jù)給vlc播放的實(shí)現(xiàn)步驟

瀏覽:2日期:2022-09-19 14:24:52
截屏

Android手機(jī)通過(guò)rtp發(fā)送aac數(shù)據(jù)給vlc播放的實(shí)現(xiàn)步驟

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()}

音頻采集時(shí)需要設(shè)置采集參數(shù),設(shè)置的這些參數(shù)需要與創(chuàng)建MediaCodec時(shí)的參數(shù)一致。

sampleRate是采樣率:44100 channelCount是通道數(shù):1 單個(gè)采樣數(shù)據(jù)大小格式:AudioFormat.ENCODING_PCM_16BIT 最小數(shù)據(jù)buffer:AudioRecord.getMinBufferSize()計(jì)算獲取

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()} }

這里采用的阻塞的方式采集數(shù)據(jù),所以AudioRecord依據(jù)設(shè)置的采樣頻率生成數(shù)據(jù)的,我們可以直接把當(dāng)前的時(shí)間設(shè)置為錄制的時(shí)間戳。

MediaCodec編碼音頻數(shù)據(jù)

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創(chuàng)建MediaFormat并設(shè)置參數(shù),這里設(shè)置的音頻參數(shù)必須與AudioRecord一致。

MIME_TYPE:'audio/mp4a-latm' 采樣頻率與AudioRecord一致:44100 通道數(shù)與AudioRecord一致:1 KEY_AAC_PROFILE配置為低帶寬要求類型:AACObjectLC KEY_BIT_RATE設(shè)置的大小影響編碼壓縮率: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傳數(shù)據(jù)的時(shí)候設(shè)置的時(shí)間戳是當(dāng)前的系統(tǒng)時(shí)間,由于我們使用rtp發(fā)送實(shí)時(shí)數(shù)據(jù),所以flag不需要設(shè)置結(jié)束標(biāo)志。

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原始的數(shù)據(jù),我們可以根據(jù)具體的需求來(lái)決定是否添加au header發(fā)送。這里實(shí)現(xiàn)了有au header和沒(méi)有 au header兩種方案。沒(méi)有au header的情況我們直接把MediaCode讀出的數(shù)據(jù)通過(guò)rtp發(fā)送出去。有au header的情況我們需要在原始的aac數(shù)據(jù)前面追加4個(gè)字節(jié)的au header。是否有au header與vlc播放的sdp內(nèi)容有關(guān)。后面會(huì)詳解介紹sdp內(nèi)容的設(shè)置。

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占用兩個(gè)字節(jié),它會(huì)描述au header的大小,這里設(shè)置為2. au header 占用兩個(gè)字節(jié),它描述了aac原始數(shù)據(jù)的大小,這里需要根據(jù)MediaCodec返回的aac原始數(shù)據(jù)大小進(jìn)行設(shè)置。 Rtp發(fā)送數(shù)據(jù)

我們使用jrtplib庫(kù)來(lái)發(fā)送數(shù)據(jù),這里對(duì)庫(kù)進(jìn)行簡(jiǎn)單的封裝并提供了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方法要指定發(fā)送數(shù)據(jù)使用的端口,payloadType設(shè)置載體類型,sampleRate是采樣率。addDestinationIp用于添加接收端ip地址,地址格式: '192.168.1.1'。sendData方法用于發(fā)送數(shù)據(jù),increase是時(shí)間間隔,時(shí)間單位是 sampleRate/秒

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

MediaCodec返回format的時(shí)候創(chuàng)建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()}

發(fā)送數(shù)據(jù)的時(shí)候需要指定payloadType,距離上次發(fā)送數(shù)據(jù)的時(shí)間間隔等信息。(info.presentationTimeUs - lastSendAudioTime)計(jì)算的是以微妙為單位的時(shí)間間隔。(info.presentationTimeUs - lastSendAudioTime) * audioSampleRate / 1000 / 1000轉(zhuǎn)換成sampleRate/秒為單位的時(shí)間間隔。rtp發(fā)送aac數(shù)據(jù)使用的payloadType為97。

SDP文件配置

vlc播放器播放rtp音頻數(shù)據(jù)時(shí)需要指定sdp文件,它通過(guò)讀取sdp文件中的信息可以了解rpt接收端口、payloadType類型、音頻的格式等信息用于接收數(shù)據(jù)流并解碼播放。這里有兩種配置方式用于支持有au header和沒(méi)有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 沒(méi)有au header

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

sdp文件配置了端口號(hào)為40020, Rtp payload type為97,音頻的采樣率為44100、通道數(shù)為1。

音頻config配置計(jì)算方式:

Android手機(jī)通過(guò)rtp發(fā)送aac數(shù)據(jù)給vlc播放的實(shí)現(xiàn)步驟

比較有au header和沒(méi)有au header的兩個(gè)版本,發(fā)現(xiàn)它們的區(qū)別在于是否配置了sizeLength和indexLength。

我這里的au header是兩個(gè)字節(jié)的,sizeLength為13代表占用了13bit,indexLength為3代表占用3bit。配合發(fā)送數(shù)據(jù)時(shí)添加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測(cè)試播放 vlc打開(kāi)工程目錄下的play_audio.sdp/play_audio_auheader.sdp 。 啟動(dòng)Android應(yīng)用指定運(yùn)行vlc的電腦的ip地址。 開(kāi)始錄制,如何vlc打開(kāi)的是play_audio_auheader.sdp,那么在開(kāi)始錄制前需要選中auHeader check box 總結(jié) AudioRecord的設(shè)置信息與MediaCodec的配置信息必須一致。 AudioRecord采用block的方式讀取數(shù)據(jù),這樣我們可以直接使用系統(tǒng)時(shí)間來(lái)配置encode時(shí)間戳。 是否需要添加au header與sdp配置有關(guān),vlc播放器會(huì)按照sdp配置解析au header。 sdp中的config需要按照實(shí)際的音頻配置信息計(jì)算得出,否則不能正常播放。 工程git地址

https://github.com/mjlong123123/AudioRecorder

以上就是Android手機(jī)通過(guò)rtp發(fā)送aac數(shù)據(jù)給vlc播放的實(shí)現(xiàn)步驟的詳細(xì)內(nèi)容,更多關(guān)于Android rtp發(fā)送aac數(shù)據(jù)給vlc播放的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Android
相關(guān)文章:
主站蜘蛛池模板: 国产精品久久久久久久7777 | 色婷婷亚洲国产女人的天堂 | 亚洲一区二区在线播放 | 九九99九九精彩46 | 中文字幕在线免费观看 | 欧洲成人午夜免费大片 | 精品国产女人 | 中文字幕一区二区三区乱码在线 | 日韩久久久久 | 欧美色综合一区二区三区 | 超碰97免费在线 | 国产精品欧美一区二区三区不卡 | 亚洲欧洲在线看 | 91久久久久 | 久久久久成人精品免费播放动漫 | 国产精品免费av | 成人av网站在线观看 | 亚洲男女激情 | 无码一区二区三区视频 | 91视频在线看 | 日本国产一区二区 | 91久久北条麻妃一区二区三区 | 免费xxxx大片国产在线 | 日日摸夜夜添夜夜添精品视频 | 亚洲美女一区二区三区 | 毛片日韩 | 亚洲精品视频免费看 | 国产成人精品一区二区在线 | 色综合久 | 国产精品成人一区 | 国产成人精品一区二区三区视频 | 毛片久久久 | 九色在线视频 | 欧美综合视频在线 | 日韩有码在线观看 | 九九热在线免费视频 | 91视视频在线观看入口直接观看 | 亚洲高清视频在线观看 | 国产精品视频97 | 亚洲人成人一区二区在线观看 | 精品国产乱码一区二区三 |