一、GB28181规范
尽管在国标GB28181中并没有对“平台”进行明确的定义,但在规范中却多次提到“系统平台”、“管理平台”等词汇,在具体项目中、网络上的交流学习中,平台概念也是无处不在。笔者认为,GB28181平台就是视频联网系统中的上级平台、中间平台或下级平台,用于实现信令、音视频数据的接收和转发。因此,在一个GB28181平台中至少应包含一个SIP信令服务器和一个流媒体服务器,这两个(类)服务器和摄像头、播放终端等设备一起组成一个监控域。对于绝大多数项目,平台中还应包括针对具体业务的业务平台,如指挥控制中心等,我这里只重点将信令服务器和流媒体服务器。
二、SIP信令服务器
SIP信令服务器负责对平台中的控制指令进行接收和转发,具有向SIP客户端、SIP设备、媒体服务器和网关等提供注册、路由选择以及逻辑控制等功能。具体指令包括注册、实时视频播放、历史视频播放、设备控制、信息查询、报警事件通知等。具体的实现遵循SIP协议规范,会话控制采用IETFRFC3261规定的Register、Invite等请求和响应方法实现,历史视音频回放控制采用SIP扩展协议IETFRFC2976规定的INFO 方法实现,前端设备控制、信息查询、报警事件通知和分发等应用的会话控制采用SIP扩展协议IETFRFC3428规定的Message方法实现。SIP消息的传输支持采用UDP和TCP两种传输协议。
三、流媒体服务器
流媒体服务器的作用是接收和转发音视频流,包括接收监控设备发送携带音视频的数据包,向播放终端或其他平台发送音视频包等。视音频传输协议要求采用RTP协议,视频推荐使用H264编码,音频G711/G723。传输层协议同样最好同时支持UDP协议和TCP协议,UDP协议带来更好的实时性和更低的延迟,TCP协议则提供更可靠的传输。
尽管标准对视频的上行和下行(播放)都明确定义要RTP协议,但是在实际应用中存在向各种终端(手机、浏览器)、各种业务平台转发音视频的需求,因此流媒体服务器的实现应当提供更多的灵活性,在采用RTP协议接收和转发音视频的同时,应当支持更多播放协议,以支持更广泛的终端集成播放需求。一些流媒体服务器平台,如云视睿博的NTV Media Server G3和SRS项目,都在支持RTP协议的同时提供了更多的播出协议支持。
四、GB28181协议在Android上的实现
笔者将相关实现代码封装到了一个单独的SDK中,此SDK包含信令通信、流媒体传输的实现接口,调用SDK的相关功能即可实现。下面的示例代码AvcEncoder就是一个调用示例,我们在App设置界面配置好参数,然后打开视频通话,接着App会注册到平台上,在平台上点击“播放实时”,就可以实时预览摄像头的画面了。
示例代码如下(具体实现代码可私聊我要哦!)
public class AvcEncoder {
public static final String VIDEO_MIME_TYPE = "video/avc"; // h264
private Context context;
private MediaCodec mediaCodec;
private MediaFormat mediaFormat;
private byte[] netPpsSps = null;
private byte[] outData = null;
private byte[] yv12data = null;
private int m_width;
private int m_height;
private byte[] m_info = null;
private byte[] yuv420 = null;
private byte csd0[] = new byte[]{0, 0, 0, 1, 0x67, 0x64, 0, 0x29,
(byte) 0xac, 0x1b, 0x1a, (byte) 0x80, 0x78, 0x02, 0x27,
(byte) 0xe5, (byte) 0x80, 0x78, 0x44, 0x22, (byte) 0x9c};
private byte csd1[] = new byte[]{0, 0, 0, 1, 0x68, (byte) 0xEA, 0X43, (byte) 0XCB};
private MediaCodecInfo selectCodecInfo() {
int numCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i < numCodecs; i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
if (!codecInfo.isEncoder()) {
continue;
}
String[] types = codecInfo.getSupportedTypes();
for (int j = 0; j < types.length; j++) {
if (types[j].equalsIgnoreCase(VIDEO_MIME_TYPE)) {
return codecInfo;
}
}
}
return null;
}
public AvcEncoder(Context context, int width, int height, int frameRate, int bitrate) {
this.context = context;
m_width = width;
m_height = height;
yuv420 = new byte[width * height * 3 / 2];
MediaCodecInfo mcif = selectCodecInfo();
try {
if (mcif != null) {
mediaCodec = MediaCodec.createByCodecName(mcif.getName());
}
} catch (IOException e) {
e.printStackTrace();
}
mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
// mediaFormat.setInteger(MediaFormat.KEY_ROTATION, 270);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(csd0));
mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(csd1));
try {
if (mediaCodec != null) {
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void yuvNV21ToYV12(byte[] NV21,int w,int h,byte[] YV12) {
System.arraycopy(NV21,0,YV12,0,w * h);
for(int i = 0, j = 0; i < w * h / 4; i++, j += 2) {
System.arraycopy(NV21, w*h+j ,YV12,w * h + i + w * h / 4,1);
System.arraycopy(NV21,w * h + j + 1,YV12,w * h + i,1);
}
}
public int offerEncoder(final byte[] input) {
int pos = 0;
if (mediaCodec == null) {
return pos;
}
if (yv12data == null || yv12data.length != input.length) {
yv12data = new byte[input.length];
}
yuvNV21ToYV12(input,m_width,m_height,yv12data);
long time = System.nanoTime();
long t1 = System.currentTimeMillis();
try {
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(10000000);
ByteBuffer inputBuffer;
if (inputBufferIndex >= 0) {
inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(yv12data);
mediaCodec.queueInputBuffer(inputBufferIndex, 0, yv12data.length, time, 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
final MediaFormat vFormat = mediaCodec.getOutputFormat(); // API >= 16
}
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
netPpsSps = new byte[bufferInfo.size];
outputBuffer.get(netPpsSps,0,bufferInfo.size);
outputBuffer.clear();
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
continue;
}
if((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { // I Frame
if (outData == null || outData.length < bufferInfo.size + netPpsSps.length) {
outData = new byte[bufferInfo.size + netPpsSps.length];
}
System.arraycopy(netPpsSps,0, outData,0,netPpsSps.length);
outputBuffer.get(outData,netPpsSps.length,bufferInfo.size);
GBlib.getInstance().PushAVData(0,GBlib.FRAME_TYPE_I, outData,
bufferInfo.size + netPpsSps.length,
System.currentTimeMillis(),(int)System.currentTimeMillis() / 1000);
} else {
if (outData == null || outData.length < bufferInfo.size) {
outData = new byte[bufferInfo.size ];
}
outputBuffer.get(outData,0,bufferInfo.size);
GBlib.getInstance().PushAVData(0,GBlib.FRAME_TYPE_P, outData,
bufferInfo.size,
System.currentTimeMillis(),(int)System.currentTimeMillis() / 1000);
}
outputBuffer.clear();
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
} catch (Throwable t) {
t.printStackTrace();
}
return pos;
}
public void close() {
try {
if (mediaCodec != null) {
mediaCodec.stop();
mediaCodec.release();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1、App的参数设置界面
2、App的摄像头预览界面
3、平台播放实时效果
具体实现代码可私聊我要哦!