1.帧捕获
在前面的实现过程中,采用了QVideoProbe捕获摄像头数据,在信号绑定之后,传输到QSmartVenc,编码模块是额外放在另外一个线程处理的
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数据绑定到编码模块
connect(m_probe.data(),&QVideoProbe::videoFrameProbed,m_venc.data(),&QSmartVenc::needHandleImage);
2.编码
编码是使用的瑞芯微MPP,参考历程mpi_enc_test,整体接口略显麻烦,基本可以跟着流程不动进行修改。其中需要注意一点的是stride在MPP中需要是16字节对齐,这里最好将输入分辨率也做16字节对齐,这样宽和高就刚好和stride匹配,避免后面填充的时候还有手动计算YUV分量位置.
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>存储起来,需要做缓冲大小限制,这个大小限制和具体业务相关了。
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交互的处理逻辑。
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实现如下,这里需要注意的就是生产和消费关系了,如果每次取多了,可能会导致下次来取无数据,从而断流,如果取少了,会导致数据丢失,从而花屏。
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帧之后,就恢复正常
执行文件和相关库文件在链接上
https://download.csdn.net/download/huahuang1508/12694057
来源:oschina
链接:https://my.oschina.net/u/4267086/blog/4479959