android vlc 参数,Android手机通过rtp发送aac数据给vlc播放

截屏

bVcRa0d

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 * 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] = 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 headerm=audio 40020 RTP/AVP 97

a=rtpmap:97 mpeg4-generic/44100/1

a=fmtp: 97 streamtype=5;config=1208;sizeLength=13; indexLength=3没有au headerm=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配置计算方式:

bVcRbRZ

比较有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地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值