我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

    
    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播放的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录