学习目标:
H264推流拉流实现(硬编解码)学习内容:
当前实现的是在区域网内的H264编解码,在启动的时候我们需要先启动推流端(服务端),然后再启动拉流端(客户端)。本例采用的是webSocket的方式实现的手机投屏到另一个手机的功能,接下来上代码。
推流端
推流端项目代码链接
推流端的代码主要实现方式就是通过录屏将数据通过MediaCodec编码成H264编码推送出去。
下面就是推流的时候关键代码,具体代码请看上面项目链接。
//处理并且发送录屏的数据
private void dealFrame(ByteBuffer bb, MediaCodec.BufferInfo bufferInfo) {
//跳过分隔符
int offset = 4;
if (bb.get(2) == 0x01) {
offset = 3;
}
//当前帧类型
int type = bb.get(offset) & 0x1F;
if (type == NAL_SPS) {
vps_sps_pps_buf = new byte[bufferInfo.size];
bb.get(vps_sps_pps_buf);
} else if (type == NAL_I) {
final byte[] bytes = new byte[bufferInfo.size];
bb.get(bytes);
byte[] newBuf = new byte[vps_sps_pps_buf.length + bytes.length];
System.arraycopy(vps_sps_pps_buf, 0, newBuf, 0, vps_sps_pps_buf.length);
System.arraycopy(bytes, 0, newBuf, vps_sps_pps_buf.length, bytes.length);
this.socketLive.sendData(newBuf);
} else {
final byte[] bytes = new byte[bufferInfo.size];
bb.get(bytes);
this.socketLive.sendData(bytes);
}
// 以下是当推流H265的时候,代码示例如下:
// public static final int NAL_I = 19;
// public static final int NAL_VPS = 32;
// private byte[] vps_sps_pps_buf;
// int type = (bb.get(offset) & 0x7E) >> 1;
// if (type == NAL_VPS) {
// vps_sps_pps_buf = new byte[bufferInfo.size];
// bb.get(vps_sps_pps_buf);
// } else if (type == NAL_I) {
// final byte[] bytes = new byte[bufferInfo.size];
// bb.get(bytes);
// byte[] newBuf = new byte[vps_sps_pps_buf.length + bytes.length];
// System.arraycopy(vps_sps_pps_buf, 0, newBuf, 0, vps_sps_pps_buf.length);
// System.arraycopy(bytes, 0, newBuf, vps_sps_pps_buf.length, bytes.length);
// this.socketLive.sendData(newBuf);
// } else {
// final byte[] bytes = new byte[bufferInfo.size];
// bb.get(bytes);
// this.socketLive.sendData(bytes);
// }
}
大家可以看到,上面的代码的注释部分是对于H265的进行处理的,博主之所以注释掉,是因为我第一次的时候本来是想推送H265的,没想到手机不争气(不支持h265),所以勉为其难就写了推送H264的。
拉流端
拉流端项目代码链接
推流端就是把推送过来的数据通过MediaCodec解析出来渲染到surfaceview上。下面就是推流的时候关键代码,具体代码请看上面项目链接。
public void callBack(byte[] data) {
Log.i(TAG, "接收到消息: " + data.length);
int index = mediaCodec.dequeueInputBuffer(100000);
//索引
if (index >= 0) {
ByteBuffer inputBuffer = mediaCodec.getInputBuffer(index);
inputBuffer.clear();
inputBuffer.put(data, 0, data.length);
//通知dsp芯片解码
mediaCodec.queueInputBuffer(index, 0, data.length, System.currentTimeMillis(), 0);
}
//获取数据
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 100000);
while (outputBufferIndex > 0) {
mediaCodec.releaseOutputBuffer(outputBufferIndex, true);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
}
ps: 运行项目时,ip地址根据服务器的地址做调整,并且推流拉流端的port也需要在每次运行的时候重新更换一个新的端口号(会出现端口号占用)。最后,一定要先运行推流端,在运行拉流端
。