欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時間:2021年04月14日 09:08:52   作者:mjlong123  
這篇文章主要介紹了Android手機通過rtp發(fā)送aac數(shù)據(jù)給vlc播放的實現(xiàn)步驟,幫助大家更好的理解和學習使用Android,感興趣的朋友可以了解下

截屏

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ù)需要與創(chuàng)建MediaCodec時的參數(shù)一致。

  • sampleRate是采樣率:44100
  • channelCount是通道數(shù):1
  • 單個采樣數(shù)據(jù)大小格式:AudioFormat.ENCODING_PCM_16BIT
  • 最小數(shù)據(jù)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()
        }
    }

這里采用的阻塞的方式采集數(shù)據(jù),所以AudioRecord依據(jù)設(shè)置的采樣頻率生成數(shù)據(jù)的,我們可以直接把當前的時間設(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è)置的時間戳是當前的系統(tǒng)時間,由于我們使用rtp發(fā)送實時數(shù)據(jù),所以flag不需要設(shè)置結(jié)束標志。

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

    private val auHeaderLength = ByteArray(2).apply {
        this[0] = 0
        this[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占用兩個字節(jié),它會描述au header的大小,這里設(shè)置為2.
  • au header 占用兩個字節(jié),它描述了aac原始數(shù)據(jù)的大小,這里需要根據(jù)MediaCodec返回的aac原始數(shù)據(jù)大小進行設(shè)置。

Rtp發(fā)送數(shù)據(jù)

我們使用jrtplib庫來發(fā)送數(shù)據(jù),這里對庫進行簡單的封裝并提供了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是時間間隔,時間單位是 sampleRate/秒

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

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

SDP文件配置

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

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

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

音頻config配置計算方式:

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

我這里的au header是兩個字節(jié)的,sizeLength為13代表占用了13bit,indexLength為3代表占用3bit。配合發(fā)送數(shù)據(jù)時添加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測試播放

  1. vlc打開工程目錄下的play_audio.sdp/play_audio_auheader.sdp 。
  2. 啟動Android應(yīng)用指定運行vlc的電腦的ip地址。
  3. 開始錄制,如何vlc打開的是play_audio_auheader.sdp,那么在開始錄制前需要選中auHeader check box

總結(jié)

  1. AudioRecord的設(shè)置信息與MediaCodec的配置信息必須一致。
  2. AudioRecord采用block的方式讀取數(shù)據(jù),這樣我們可以直接使用系統(tǒng)時間來配置encode時間戳。
  3. 是否需要添加au header與sdp配置有關(guān),vlc播放器會按照sdp配置解析au header。
  4. sdp中的config需要按照實際的音頻配置信息計算得出,否則不能正常播放。

工程git地址

https://github.com/mjlong123123/AudioRecorder

以上就是Android手機通過rtp發(fā)送aac數(shù)據(jù)給vlc播放的實現(xiàn)步驟的詳細內(nèi)容,更多關(guān)于Android rtp發(fā)送aac數(shù)據(jù)給vlc播放的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論