目录截屏AudioRecord音频采集MediaCodec编码音频数据Rtp发送数据SDP文件配置音频config配置计算方式:vlc测试播放总结工程git地址截屏 AudioRe
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时的参数一致。
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依据设置的采样频率生成数据的,我们可以直接把当前的时间设置为录制的时间戳。
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一致。
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()
}
}
我们使用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。
vlc播放器播放rtp音频数据时需要指定sdp文件,它通过读取sdp文件中的信息可以了解rpt接收端口、payloadType类型、音频的格式等信息用于接收数据流并解码播放。这里有两种配置方式用于支持有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
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。
比较有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()
}
}
https://GitHub.com/mjlong123123/AudioRecorder
以上就是Android手机通过rtp发送aac数据给vlc播放的实现步骤的详细内容,更多关于Android rtp发送aac数据给vlc播放的资料请关注编程网其它相关文章!
--结束END--
本文标题: Android手机通过rtp发送aac数据给vlc播放的实现步骤
本文链接: https://lsjlt.com/news/123514.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-01-21
2023-10-28
2023-10-28
2023-10-27
2023-10-27
2023-10-27
2023-10-27
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0