项目里用到了h264/h265+aac封装成rtmp流,然后在PC客户端播放,画面和声音都非常正常和稳定。
现在由于项目需要,rtmp推送的音视频流,需要直接在网页端播放,发现网页播放时只有画面,没有声音
查了很多资料和数据包,发现是音频头部引起的原因。因为PC有很多库可以调用,即使AAC音频头部不严谨也可以解析出来,但到了网页就不行了
科普下AAC头部Adts信息:ADTS是Audio Data Transport Stream的简称,感谢前辈的辛勤付出,链接为:https://blog.csdn.net/jay100500/article/details/52955232/
废话少说,直接贴出修改后,浏览器播放正常时的代码:
这里是把采集到的pcm音频转为AAC:
/**
* 编码PCM数据
* @param data
* @param len
*/
public void encodePCM(byte[] data, int len) {
try {
if (!isRunning) {
Log.i("encodePCM", "audio is no longer running...");
return;
}
int inputIndex;
inputIndex = mMediaCodec.dequeueInputBuffer(ENCODE_TIMEOUT);
if (inputIndex >= 0) {
ByteBuffer buffer = mInputBuffers[inputIndex];
buffer.clear();
if (len < 0) {
mMediaCodec.queueInputBuffer(inputIndex, 0, 0, (long) System.nanoTime() / 1000, 0);
} else {
mTotalBytesRead += len;
buffer.put(data, 0, len);
mMediaCodec.queueInputBuffer(inputIndex, 0, len, (long) System.nanoTime() / 1000, 0);
}
}
int outputIndex = 0;
while (outputIndex != MediaCodec.INFO_TRY_AGAIN_LATER) {
outputIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 0);
if (outputIndex >= 0) {
ByteBuffer encodedData = mOutputBuffers[outputIndex];
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0 && mBufferInfo.size != 0) {
encodedData.position(mBufferInfo.offset);
encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
onHevcStateListener.aacBuffer(encodedData, mBufferInfo);//这里是调到rtmp封装方法
mMediaCodec.releaseOutputBuffer(outputIndex, false);
} else {
onHevcStateListener.aacBuffer(encodedData, mBufferInfo);//这里是调到rtmp封装方法
mMediaCodec.releaseOutputBuffer(outputIndex, false);
}
} else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
mMediaFormat = mMediaCodec.getOutputFormat();
Log.d(TAG, "encoder output format changed Audio: " + mMediaFormat.getString(MediaFormat.KEY_MIME));
onHevcStateListener.onRecordStart(MediaType.AUDIO, mMediaFormat);//这里是开始rtmp推流的一些方法回调过去
}
}
}catch (Exception e){
e.printStackTrace();
}
}
这里是使用硬编码,在网页播放时,一定要选择低复杂度规格LC:
MediaCodecInfo.CodecProfileLevel.AACObjectLC
/**
* 准备编码器
* @throws Exception
*/
public void prepare() throws Exception {
mMediaFormat = MediaFormat.createAudioFormat(AUDIO_MIME_TYPE, mSampleRate, mChannelCount);
mMediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
mMediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, mBitrate);
mMediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, mBufferSize);
mMediaCodec = MediaCodec.createEncoderByType(AUDIO_MIME_TYPE);
mMediaCodec.configure(mMediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mMediaCodec.start();
mInputBuffers = mMediaCodec.getInputBuffers();
mOutputBuffers = mMediaCodec.getOutputBuffers();
mBufferInfo = new MediaCodec.BufferInfo();
}
然后回调到封前rtmp前,需要把音频关键帧的同步包也分割出来,同时需要加上AAC的头部ADTS信息:
public void aacBuffer(@NonNull ByteBuffer bb, @NonNull MediaCodec.BufferInfo aBufferInfo){
if (aBufferInfo.size == 2) {
byte[] bytes = new byte[aBufferInfo.size + 7];
//添加ADTS头部
addADTStoPacket(bytes, aBufferInfo.size + 7);
bb.get(bytes, 7, aBufferInfo.size);
outputAudioSpecConfig(bytes, aBufferInfo.size + 7);
} else {
byte[] bytes = new byte[aBufferInfo.size];
bb.get(bytes);
calculateAudioTimeUs(aBufferInfo);
outputAudioData(bytes, bytes.length, mLastTimeStamp + 10 * 1000);
}
}
/**
* 给编码出的aac裸流添加adts头字段
*
* @param packet 要空出前7个字节,否则会搞乱数据
* @param packetLen
*/
private void addADTStoPacket(byte[] packet, int packetLen) {
int profile = 2; //AAC LC
int freqIdx = 4; //44.1KHz
int chanCfg = 2; //CPE
packet[0] = (byte) 0xFF;
packet[1] = (byte) 0xF9;
packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
packet[6] = (byte) 0xFC;
}
这样在网页播放就能有声音了