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] = 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占用两个字节,它会描述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 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、通道数为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测试播放

  1. vlc打开工程目录下的play_audio.sdp/play_audio_auheader.sdp 。
  2. 启动Android应用指定运行vlc的电脑的ip地址。
  3. 开始录制,如何vlc打开的是play_audio_auheader.sdp,那么在开始录制前需要选中auHeader check box

总结

  1. AudioRecord的设置信息与MediaCodec的配置信息必须一致。
  2. AudioRecord采用block的方式读取数据,这样我们可以直接使用系统时间来配置encode时间戳。
  3. 是否需要添加au header与sdp配置有关,vlc播放器会按照sdp配置解析au header。
  4. sdp中的config需要按照实际的音频配置信息计算得出,否则不能正常播放。

工程git地址

https://github.com/mjlong123123/AudioRecorder

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

(0)

相关推荐

  • Android Studio实现简单音乐播放功能的示例代码

    项目要求 基于Broadcast,BroadcastReceiver等与广播相关的知识实现简单的音乐播放功能,包括音乐的播放.暂停.切换.进度选择.音量调整. 设计效果 (进度条时间刷新功能还没有实现) 实现思路 音乐服务端负责播放音乐和收发广播的功能.当音乐服务端作为接收器时,只能接收到主页面广播的控制消息:作为发送器时,向主页面发送歌曲信息更新的消息 主页面负责进度条以及音量按钮的监听,同时也有收发广播的功能.当主页面作为接收器时,只能接收到音乐服务端广播的歌曲信息更新的消息:作为发送器时,

  • Android实现文字滚动播放效果的代码

    在开发时,我们会遇到文字过长,TextView不能完全展示,但又不想文字换行展示,这样有时会影响美观.这时我们就需要文字滚动播放,让用户可以看到所有的文字. 话不多说,直接上代码: import android.content.Context; import android.util.AttributeSet; import android.widget.TextView; public class MarqueTextView extends TextView { public MarqueT

  • Android10.0实现本地音乐播放(附源码下载)

    1.概述 本篇文章仅是Android小白在写一个小程序,内容仅供参考,有很多不足之处希望各位大神指出,文章末尾有整个项目的下载,不需要币,只求帮你们解决到问题的同时收获到一颗小小的赞.这个项目中还有很多不足的地方,如:在按键中设置图片文字,这些正常的应该交给Handler处理,我只是粗略地完成这个项目.测试环境:Android10.0.实现:自动播放下一首,正常音乐的功能,全屏显示. Android10.0是内外分存了的,应用是没有权限读取内存的,需要在配置文件中application中加上属性

  • Android仿优酷视频的悬浮窗播放效果

    之前接了需求要让视频播放时可以像优酷视频那样在悬浮窗里播放,并且悬浮窗和主播放页面之间要实现无缝切换,项目中使用的是自封装的ijkplayer 这个要求就代表不能在悬浮窗中新建视频控件,所以需要在悬浮窗中复用主页面的视频控件,以达到无缝衔接的效果. 主页面对应的视频控件的父view <FrameLayout android:id="@+id/vw_live" android:layout_width="match_parent" android:layout_

  • Android下拉刷新ListView——RTPullListView(demo)

    下拉刷新在越来越多的App中使用,已经形成一种默认的用户习惯,遇到列表显示的内容时,用户已经开始习惯性的拉拉.在交互习惯上已经形成定性.之前在我的文章<IOS学习笔记34-EGOTableViewPullRefresh实现下拉刷新>中介绍过如何在IOS上实现下拉刷新的功能.今天主要介绍下在Android上实现下拉刷新的Demo,下拉控件参考自Github上开源项目PullToRefresh,并做简单修改.最终效果如下:                         工程结构如下: 使用过程中

  • Android项目实现视频播放器

    本文实例为大家分享了Android项目实现视频播放器的具体代码,供大家参考,具体内容如下 VideoView控件是播放视频用的,借助它可以完成一个简易的视频播放器. ①在activity_main.xml中编写相应的控件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/andro

  • Android GSYVideoPlayer视频播放器功能的实现

    GSYVideoPlayer GSYVideoPlayer官方地址 GSYVideoPlayer 一个基于IJkPlayer的播放器 支持调节声音亮度 边播边缓存 使用了AndroidVideoCache:ExoPlayer使用SimpleCache 支持多种协议h263\4\5.Https.concat.rtsp.hls.rtmp.crypto.mpeg等等 简单滤镜(马赛克.黑白.色彩过滤.高斯.模糊.模糊等等20多种).动画.(水印.画面多重播放等) 视频第一帧.视频帧截图功能,视频生成g

  • Android实现本地Service方法控制音乐播放

    问题现象描述:在Activity中控制播放时,按返回键退出应用后,音乐可在后台继续播放.重新进入app,音乐无法停止,重新点击开始播放音乐,出现重复的音乐同时播放的现象(多个同时播放).如何解决? 解决方法:使用本地Service的方式来控制音乐的播放,app返回退出了,重新进入App也可以正常终止. 1.主Activity控制音乐 的开始.暂停.停止.退出空能,(具体实现在下面MusicService.java中实现) /** * Activity播放广播,返回键返回后,重新进入无法停止 *

  • Android原生视频播放VideoView的使用

    本文实例为大家分享了Android原生视频播放VideoView的具体代码,供大家参考,具体内容如下 布局文件activity_video.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_par

  • java、android可用的rtp封包解包h264案例

    做直播,音视频通讯.经常需要通过rtp协议封装音视频数据来发送.网上找到的基本都是c或c++版本的,没有JAVA版本的.就算千辛万苦找到一篇java版本的,要么不能用,要么就是一些片段,要么有封包没解包. 很是蛋疼,本人也是这样,刚开始不太熟悉rtp协议,不太明白怎么封包组包,痛苦了几天,终于搞出来了,分享给有需要的朋友,希望对你们有所帮助. 直接看代码吧.不多说了. 首先看看关键类: package com.imsdk.socket.udp.codec; import android.os.S

  • Android Studio实现音乐播放器

    Androidstudio音乐播放器,供大家参考,具体内容如下 实现目的:利用广播在myapplication中原本button点我暂停按钮是灰色无法点击的,此时发送一条短信给自己的安卓手机,按钮变成黑色,音乐开始播放,此时点击 点我暂停 按钮,音乐停止. 结果截图: 发送短信给自己后:,点我暂停 按钮变成黑色,并开始播放音乐: 此时点击点我暂停按钮,音乐暂停 广播 Android 应用与 Android 系统和其他 Android 应用之间可以相互收发广播消息,这与发布-订阅设计模式相似.这些

随机推荐