LiveMedia视频中间件概述
LiveMedia视频中间件是安防视频监控行业中的一套基于H5技术开发的视频基础平台。
支持国内外不同品牌、不同协议、不同设备类型监控产品的统一接入管理;
可多协议(RTSP/RTMP/GB28181/海康ISUP/Ehome/大华、海康SDK等)的设备视频接入、采集、处理、分发等服务;
同时有标准的H5播放接口供其他应用平台快速对接,可快速拥有视频集成能力。
不受开发环境、跨系统跨平台等条件影响,具有全兼容、全平台支持、纯WEB、免插件、低延时、安全等功能特点,为安防视频播放提供最稳定、可靠、便捷的解决方案。
实时抓图的使用场景
由于GB28181的标准规范中没有规定实时抓图功能如何实现,导致基于GB28181的视频设备都没实现实时抓图(不是在播放器上实现抓图),但是在客户很多使用场景下需要用到抓图功能,如智能AI分析定时抓取图片、报警联动实时抓图等都需要,因此我们在提供抓图功能接口的同时广泛适配了所有接入的前端抓图,对于集成应用APP对于调用抓图的实现内里逻辑是无感的。
我们对于GB28181设备实时抓图的是采用打开实时视频,获取I帧,再通过ffmpeg转码成图片的方式实现,转码成功后关闭实时视频流。具体逻辑是
1. 应用调用抓图API向视频中间请求抓图
2.信令服务判断设备是否是GB28181设备
3.如果是海康、大华等设备,直接调用厂家SDK获取实时抓图
3.1 设备返回抓图数据
3.2 平台向应用返回图片数据
4.是GB28181设备,则向流媒体服务请求实时视频
4.1 流媒体服务判断当前设备是否已打开视频,没有的话就向GB28181服务发送请求实时视频
4.2 GB28181向设备发送Invite请求视频
4.3 设备返回实时视频数据
4.4 流媒体服务抓取视频流中的I帧,转换成jpeg图片
4.5 图片数据返回信令服务,同时向GB28181服务发送关闭视频请求
4.6 信令服务向应用返回图片数据
综上完成整个抓图的逻辑
ffmpeg有I帧转换jpg部分代码
打开输入流:
int Frame2JPG::openInput(const char* frame, int len)
{
BufferBlock bf;
bf.append(frame, len);
_inCtx = std::make_unique<DecodeContext>();
_inCtx->_ctx = avformat_alloc_context();
_inCtx->create_read_io_context(&bf);
if (avformat_open_input(&_inCtx->_ctx, nullptr, nullptr, nullptr) < 0)
{
return -1;
}
if (avformat_find_stream_info(_inCtx->_ctx, nullptr) < 0)
{
return -2;
}
if (_inCtx->_ctx->nb_streams <=0 )
{
return -3;
}
AVStream* st = _inCtx->_ctx->streams[0];
AVCodecParameters* par = st->codecpar;
AVCodec* ic = avcodec_find_decoder(st->codecpar->codec_id);
_inCtx->_avctx = avcodec_alloc_context3(ic);
avcodec_parameters_to_context(_inCtx->_avctx, par);
if (avcodec_open2(_inCtx->_avctx, ic, nullptr) < 0)
{
printf("avcodec_open2 failed\n");
return -4;
}
//read
AVPacket pkt;
_inCtx->_av_frame = av_frame_alloc();
while (0 == av_read_frame(_inCtx->_ctx, &pkt))
{
//decode
int ret = avcodec_send_packet(_inCtx->_avctx, &pkt);
if (ret >= 0)
{
ret = avcodec_receive_frame(_inCtx->_avctx, _inCtx->_av_frame);
if (ret == AVERROR(EAGAIN))
continue;
if (ret == 0 && _inCtx->_av_frame->pict_type == AV_PICTURE_TYPE_I)
return 0;
else if (ret < 0) {
fprintf(stderr, "Error during decoding\n");
return -7;
}
}
}
return -5;
}
打开输出(jpg)
int Frame2JPG::openOutput()
{
auto instream = _inCtx->_ctx->streams[0];
_outCtx = std::make_unique<DecodeContext>();
_outCtx->_ctx = avformat_alloc_context();
_outCtx->_ctx->oformat = av_guess_format("mjpeg", nullptr, nullptr);
_outCtx->create_write_io_context(&_dst);
auto outstream = avformat_new_stream(_outCtx->_ctx, nullptr);
AVCodec* ic = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
_outCtx->_avctx = avcodec_alloc_context3(ic);
_outCtx->_avctx->width = instream->codecpar->width;
_outCtx->_avctx->height = instream->codecpar->height;
_outCtx->_avctx->pix_fmt = AV_PIX_FMT_YUVJ420P;
_outCtx->_avctx->time_base = { 1,25 };
if (avcodec_open2(_outCtx->_avctx, ic, nullptr) < 0)
{
return -1;
}
avcodec_parameters_from_context(outstream->codecpar, _outCtx->_avctx);
avformat_write_header(_outCtx->_ctx, nullptr);
AVPacket encpkt = { 0 };
av_init_packet(&encpkt);
int ret = avcodec_send_frame(_outCtx->_avctx, _inCtx->_av_frame);
while (ret >= 0)
{
ret = avcodec_receive_packet(_outCtx->_avctx, &encpkt);
if (ret < 0)
{
break;
}
ret = av_interleaved_write_frame(_outCtx->_ctx, &encpkt);
}
av_write_trailer(_outCtx->_ctx);
return 0;
}
转换函数:
int Frame2JPG::decode2JPG(const char* frame, int len)
{
if (openInput(frame, len) < 0 )
{
return -1;
}
return openOutput();
}