Android手機通過rtp發送aac數據給vlc播放的實現步驟
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 * 1024override 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 headerm=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配置計算方式:比較有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播放的資料請關注好吧啦網其它相關文章!
相關文章:
1. React+umi+typeScript創建項目的過程2. ASP中常用的22個FSO文件操作函數整理3. ASP.NET Core 5.0中的Host.CreateDefaultBuilder執行過程解析4. SharePoint Server 2019新特性介紹5. .Net core 的熱插拔機制的深入探索及卸載問題求救指南6. 解決ASP中http狀態跳轉返回錯誤頁的問題7. 讀大數據量的XML文件的讀取問題8. ASP編碼必備的8條原則9. 無線標記語言(WML)基礎之WMLScript 基礎第1/2頁10. ASP調用WebService轉化成JSON數據,附json.min.asp
