之前尝试写了一个基于来疯直播的点对点Tcp连接传输H264进行投屏直播的demo,
(1) Android之间互相的录屏直播 –点对点传输(tcp长连接发送h264)(一)
(2)Android之间互相的录屏相机直播-(增加声音直播)(二)
前段时间,有些小伙伴反馈了几个bug和新的需求,趁着最近周末无事,修正了这几个bug,
实现了一些新增需求,有以下几点改动:
- 1,增加了直播样例代码,使用来疯直播的直播组件,补充了一个成功的Camera直播样例.
- 2,解决直播快速移动时和使用小米手机情况下,播放端模糊.
- 3,增加了传输声音道播放端播放
1, 解决直播模糊
鉴于增加直播样例代码没什么好说的,我们一笔带过,直接说如何解决模糊,模糊应该是视频帧不清晰造成的,
首先先介绍一下MediaCodec的bitrate_mode三种模式 :
CQ 对应于 OMX_Video_ControlRateDisable,它表示完全不控制码率,尽最大可能保证图像质量.
CBR 对应于 OMX_Video_ControlRateConstant,它表示编码器会尽量把输出码率控制为设定值.
VBR 对应于 OMX_Video_ControlRateVariable,它表示编码器会根据图像内容的复杂度(实际上是帧间变化量的大小)来动态调整输出码率,图像复杂则码率高,图像简单则码率低;
所以,我们可以检查手机型号,只要是小米手机的话,就直接使用CQ模式去初始化MediaCodec的bitrate_mode.
因为CQ模式下,是最高图片质量,为了降低网络带宽,我们可以适当降低一下fps,并且提高关键帧的间隔(ifi),
用以减少局域网下带宽的占用.
//附上MediaCodec初始化的代码:
public static MediaCodec getVideoMediaCodec(VideoConfiguration videoConfiguration) {
int videoWidth = getVideoSize(videoConfiguration.width);
int videoHeight = getVideoSize(videoConfiguration.height);
if (Build.MANUFACTURER.equalsIgnoreCase("XIAOMI")) {
videoConfiguration.maxBps = 500;
videoConfiguration.fps = 10;
videoConfiguration.ifi = 3;
}
MediaFormat format = MediaFormat.createVideoFormat(videoConfiguration.mime, videoWidth, videoHeight);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, videoConfiguration.maxBps * 1024);
int fps = videoConfiguration.fps;
//设置摄像头预览帧率
if (BlackListHelper.deviceInFpsBlacklisted()) {
SopCastLog.d(SopCastConstant.TAG, "Device in fps setting black list, so set mediacodec fps 15");
fps = 15;
}
format.setInteger(MediaFormat.KEY_FRAME_RATE, fps);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, videoConfiguration.ifi);
format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / 45);
//------------------ 为解决MIUI9.5花屏而增加...-------------------------------
if (Build.MANUFACTURER.equalsIgnoreCase("XIAOMI")) {
format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ);
} else {
format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
}
format.setInteger(MediaFormat.KEY_COMPLEXITY, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);
MediaCodec mediaCodec = null;
try {
mediaCodec = MediaCodec.createEncoderByType(videoConfiguration.mime);
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (Exception e) {
e.printStackTrace();
if (mediaCodec != null) {
mediaCodec.stop();
mediaCodec.release();
mediaCodec = null;
}
}
return mediaCodec;
}
2,声音的采集,发送,接收,播放
声音直播相比较来说复杂:
因为我们的发送和播放视频代码都是传输的H264,H264是传输视频帧的协议.H264协议没有传输声音的片段.
为了快速出一个demo,我最后将声音AAC片段放在h264的头数据后面(0,0,0,1后面).
再收到每一个片段后,在播放端判断是视频帧还是AAC片段,然后将视频帧喂给配置有SurfaceHolder的MediaCodec,
将AAC片段喂给Audio MediaCodec,然后从AudioMediaCodec出来的片段再写入AudioTrack,由AudioTrack去播放声音.
(1)从AudioRecord中拿到pcm,
(2)通过AudioMediaCodec拿到不包括ADTS头的AAC片段
(3)给AAC片段加上ADTS的头信息,再加上h264头后发送{0,0,0,1}(加h264的头信息是我自己定义的,只是为了方便解析)
播放端流程:
(1)根据ADTS识别出AAC片段,去掉,后h264的头信息,
(2)将包括ADTS的AAC片段喂给AudioMediaCodec,获得解码后的片段.
(3)将解码后的片段传递给AudioTrack播放.
首先我们需要连接下AAC和ADTS:
AAC是高级音频编码(Advanced Audio Coding)的缩写,
ADTS:Audio Data Transport Stream 音频数据传输流。这种格式的特征是它是一个有同步字的比特流,
解码可以在这个流中任何位置开始。
每一个AAC片段前面都应该有ADTS,一般第一个片段不包括ADTS的话,只有两个字节,用于配置一些信息.
可以去这位大神的博客了解AAC和ADTS更多信息
接下来我们根据自己AudioRecord的配置信息,生成一段ADTS头信息,给每个AAC片段都加上
//获得ADTS头信息
private byte[] getADTSHeader(int packetLen) {
byte[] packet = new byte[7];
int profile = 2; //AAC LC
int freqIdx = 4; //16.0KHz
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;
return packet;
}
//给AAC片段添加ADTS
if (packetListener == null ) {
return;
}
if (!mSendAudio) {
return;
}
bb.position(bi.offset);
bb.limit(bi.offset + bi.size);
byte[] audio = new byte[bi.size];
bb.get(audio);
int length = 7 + audio.length;
ByteBuffer tempBb = ByteBuffer.allocate(length + 4);
//header是自定义逻辑,即为h264的{0,0,0,1}
tempBb.put(header);
tempBb.put(getADTSHeader(length));
tempBb.put(audio);
packetListener.onPacket(tempBb.array(), AUDIO);
播放端根据头信息,筛选出视频片段或者音频片段
筛分算法主要来自于来疯直播
private boolean isAudio(byte[] frame) {//是否是音频片段
if (frame.length < 5) {
return false;
}
return frame[4] == ((byte) 0xFF) && frame[5] == ((byte) 0xF9);
}
private boolean isSps(byte[] frame) {//是否是Sps
if (frame.length < 5) {
return false;
}
// 5bits, 7.3.1 NAL unit syntax,
// H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
// 7: SPS, 8: PPS, 5: I Frame, 1: P Frame
int nal_unit_type = (frame[4] & 0x1f);
return nal_unit_type == SPS;
}
private boolean isPps(byte[] frame) {//是否是Pps
if (frame.length < 5) {
return false;
}
// 5bits, 7.3.1 NAL unit syntax,
// H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
// 7: SPS, 8: PPS, 5: I Frame, 1: P Frame
int nal_unit_type = (frame[4] & 0x1f);
return nal_unit_type == PPS;
}
private boolean isKeyFrame(byte[] frame) {//是否是关键帧
if (frame.length < 5) {
return false;
}
// 5bits, 7.3.1 NAL unit syntax,
// H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
// 7: SPS, 8: PPS, 5: I Frame, 1: P Frame
int nal_unit_type = (frame[4] & 0x1f);
return nal_unit_type == IDR;
}
*将音频片段解码并播放
public class AudioPlay {
private static final String TAG = "AudioPlay";
private MediaCodec mAudioMediaCodec;
private AudioTrack mAudioTrack;
//用来记录解码失败的帧数
private int count = 0;
public AudioPlay() {
this.mAudioMediaCodec = AudioMediaCodec.getAudioMediaCodec();
this.mAudioTrack = AudioMediaCodec.getAudioTrack();
this.mAudioMediaCodec.start();
this.mAudioTrack.play();
}
public void playAudio(byte[] buf, int offset, int length) {
//输入ByteBuffer
ByteBuffer[] codecInputBuffers = mAudioMediaCodec.getInputBuffers();
//输出ByteBuffer
ByteBuffer[] codecOutputBuffers = mAudioMediaCodec.getOutputBuffers();
//等待时间,0->不等待,-1->一直等待
long kTimeOutUs = 0;
try {
//返回一个包含有效数据的input buffer的index,-1->不存在
int inputBufIndex = mAudioMediaCodec.dequeueInputBuffer(kTimeOutUs);
if (inputBufIndex >= 0) {
//获取当前的ByteBuffer
ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
//清空ByteBuffer
dstBuf.clear();
//填充数据
dstBuf.put(buf, offset, length);
//将指定index的input buffer提交给解码器
mAudioMediaCodec.queueInputBuffer(inputBufIndex, 0, length, 0, 0);
}
//编解码器缓冲区
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
//返回一个output buffer的index,-1->不存在
int outputBufferIndex = mAudioMediaCodec.dequeueOutputBuffer(info, kTimeOutUs);
if (outputBufferIndex < 0) {
//记录解码失败的次数
count++;
}
ByteBuffer outputBuffer;
while (outputBufferIndex >= 0) {
switch (outputBufferIndex) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
Log.e(TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
Log.e(TAG, "INFO_OUTPUT_FORMAT_CHANGE");
break;
case MediaCodec.INFO_TRY_AGAIN_LATER:
Log.e(TAG, "INFO_TRY_AGAIN_LATER");
break;
}
//---------------------------------------------------------------
//获取解码后的ByteBuffer
outputBuffer = codecOutputBuffers[outputBufferIndex];
//用来保存解码后的数据
byte[] outData = new byte[info.size];
outputBuffer.get(outData);
//清空缓存
outputBuffer.clear();
//播放解码后的数据
mAudioTrack.write(outData, 0, info.size);
// Log.e("DecodeThread", "buff length = " + info.size);
//释放已经解码的buffer
mAudioMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
//解码未解完的数据
outputBufferIndex = mAudioMediaCodec.dequeueOutputBuffer(info, kTimeOutUs);
//--------------------------------------------------------
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
//返回解码失败的次数
public int getCount() {
return count;
}
public void release() {
if (mAudioMediaCodec != null) {
mAudioMediaCodec.stop();
mAudioMediaCodec.release();
mAudioMediaCodec = null;
}
if (mAudioTrack != null) {
mAudioTrack.stop();
mAudioTrack.release();
mAudioTrack = null;
}
}
}