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