1.帧捕获
在前面的实现过程当中,采用了QVideoProbe捕获摄像头数据,在信号绑定以后,传输到QSmartVenc,编码模块是额外放在另一个线程处理的app
QVencParm param; param.width = 1280; param.height = 720; param.code = MPP_VIDEO_CodingAVC; param.fmt = MPP_FMT_YUV420P; m_venc.reset(new QSmartVenc(param)); QThread *vencThread = new QThread(this); m_venc->moveToThread(vencThread); vencThread->start();
将probe数据绑定到编码模块tcp
connect(m_probe.data(),&QVideoProbe::videoFrameProbed,m_venc.data(),&QSmartVenc::needHandleImage);
2.编码
编码是使用的瑞芯微MPP,参考历程mpi_enc_test,总体接口略显麻烦,基本能够跟着流程不动进行修改。其中须要注意一点的是stride在MPP中须要是16字节对齐,这里最好将输入分辨率也作16字节对齐,这样宽和高就恰好和stride匹配,避免后面填充的时候还有手动计算YUV份量位置.ide
MppFrame frame = NULL; MppPacket packet = NULL; void *buf = mpp_buffer_get_ptr(frm_buf); f.map(QAbstractVideoBuffer::ReadOnly); memcpy(buf,f.bits(),frame_size); f.unmap(); int ret = mpp_frame_init(&frame); if(ret) { qWarning("Init frame failed"); return; } mpp_frame_set_width(frame, par.width); mpp_frame_set_height(frame, par.height); mpp_frame_set_hor_stride(frame, hor_stride); mpp_frame_set_ver_stride(frame, ver_stride); mpp_frame_set_fmt(frame, par.fmt); mpp_frame_set_eos(frame, 0); mpp_frame_set_buffer(frame, frm_buf); ret = mpi->encode_put_frame(ctx, frame); if(ret) { qWarning( "encode_put_frame failed"); return; } ret = mpi->encode_get_packet(ctx, &packet); if (ret) { qWarning("mpp encode get packet failed\n"); } if(!packet) return; // write packet to file here void *ptr = mpp_packet_get_pos(packet); size_t len = mpp_packet_get_length(packet); encFile.write((char *)ptr,len); //p->pkt_eos = mpp_packet_get_eos(packet); { QMutexLocker locker(&encDataLock); if((encDataLen + len) > MAX_STREAM_BUFFER) { auto ba = encDataList.takeFirst(); //右值 encDataLen -= ba.length(); } qDebug() << "encode len " << len; encDataList.append(rtcpInfo + QByteArray((char *)ptr,len)); //右值,这里必定要加上sps,pps encDataLen += (len + rtcpInfo.size());; } mpp_packet_deinit(&packet); //qDebug("encoded frame %u size %lu\n", frame_count, len); frame_count++;
在memcpy的位置,若是宽高和stride不匹配的,就不能直接拷贝了,手动计算,麻烦得一逼。。函数
这里在编码完成以后,经过 QList<QByteArray>存储起来,须要作缓冲大小限制,这个大小限制和具体业务相关了。this
3.RTSP
RTSP的实现采用的是live555,根据livemedia进行的修改,目前跑多路视频流,1080P,没有问题,live555这里是通过修改过的和原生略有不一样。
首先提供码流信息和读取回调函数,live555是单独一个线程运行的,在合适的时候会调用到提供的函数读取视频流。
编码
int rtsp_sever_callback(void *data,unsigned int max,const char * des,int *fps); VIDEO_RTSP_INFO rtspServerInfo[] = { {PT_H264,"smartcam","smartcam",rtsp_sever_callback} };
经过此接口将信息注册到RTSP服务上,video_init_rtsp是本身实现的和live555交互的处理逻辑。spa
void QSmartVenc::init_rtsp_server(void) { MESSAGE msg; msg.pstInfo = rtspServerInfo; msg.size = sizeof(rtspServerInfo)/sizeof(rtspServerInfo[0]); video_init_rtsp(&msg); }
rtsp_sever_callback实现以下,这里须要注意的就是生产和消费关系了,若是每次取多了,可能会致使下次来取无数据,从而断流,若是取少了,会致使数据丢失,从而花屏。.net
int rtsp_sever_callback(void *data,unsigned int max,const char * des,int *fps) { //可经过des判断是哪一条流信息 QMutexLocker locker(&encDataLock); if(!encDataList.size()) //无数据 return -1; unsigned int remain = max; int num = 0; //while(!encDataList.isEmpty() && num++ < 2) //最多取两针,这里根据实际状况来作处理吧,协调好生产消费的关系,否则很容易掉帧,花屏 { auto ba = encDataList.takeFirst(); int readLen = ba.size() < remain ? ba.size() : remain; memcpy(data,ba.data(),readLen); if(readLen < ba.size()) //此帧没有读取完毕 { encDataList.insert(0,ba.remove(0,readLen)); } encDataLen -= readLen; remain -= readLen; // if(remain < 512) // break; } qDebug() << "left data "<< encDataLen; *fps = encDataLen < 2 * 1024 ? 10 : 30; return (max - remain); }
这里的实现也有个问题,没有处理I帧,会出现刚开始获取视频流时,有点花屏,过一会就行了,初步分析是由于第一次获取到的可能不是I帧,后面读到I帧以后,就恢复正常线程
执行文件和相关库文件在连接上code