推送
流程:初始化、串連伺服器、發送資料
這一部分主要用的是librtmp,由於是使用別人封裝好的庫,總的來說比較簡單 初始化
簡單地調用一下函數
rtmp = RTMP_Alloc();RTMP_Init(rtmp);
串連伺服器
四個函數分別代表:設定URL、使自己成為發送端、建立NetConnection,建立NetStream
RTMP_SetupURL(rtmp, url);RTMP_EnableWrite(rtmp);RTMP_Connect(rtmp, NULL);RTMP_ConnectStream(mRTMP, 0);
發送資料
不斷從隊列中取出RTMP包,加上時間戳記和流ID,然後往伺服器發送,幀率的控制位於編碼線程,所以這裡不用計算時間,直接加上時間戳記發送即可,而且由於這裡也不好計算時間,還有就是這裡的隊列是阻塞隊列
while (true) { RTMPPacket& packet = queue.front(); packet.m_nInfoField2 = rtmp->m_stream_id; packet.m_nTimeStamp = RTMP_GetTime(); RTMP_SendPacket(rtmp, &packet, 1); queue.pop();}
主程式
主要分為:視頻編碼線程、音頻編碼線程、主線程(發送線程) 視頻編碼線程
根據幀率算出間隔時間,然後把視頻中繼資料的rtmp包存下來以便多次發送,從視頻源擷取幀,判斷是否是主要畫面格,如果是則先發送中繼資料,經過編碼封裝後發送資料,最後比較花費時間與間隔時間決定是否需要等待
const int interval = 1000 / fps;int bytes;char *buf, *frame;RTMPPacket packet, meta;H264RTMPPackager packager;std::chrono::milliseconds duration;std::pair<int, char*> result;meta = encoder.getMetadata();buf = pool.get(1024);packet = packager.metadata(buf, result.second, result.first);auto last = std::chrono::system_clock::now();while ((frame = source.getNextFrame()) != NULL) { result = source.encode(frame); if (H264RTMPPackager::isKeyFrame(result.second)) { queue.push(meta, true); } bytes = packager.getBodyLength(result.first); buf = pool.get(bytes); packet = packager.pack(buf, result.second, result.first) queue.push(packet); duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - last); if (duration < interval) { msleep((interval - duration).count()); } last = std::chrono::system_clock::now();}
音頻編碼線程
步驟與視頻編碼線程相似,只是這裡無需等待,因為音頻源是根據採樣率來輸出幀的,這裡由於中繼資料只需要發送一次,所以可以不用儲存下來
int bytes;char *buf;RTMPPacket packet;AACRTMPPackager packager;std::pair<int, char*> frame, result;result = encoder.getMetadata();buf = pool.get(128);packet = packager.metadata(buf, result.second, result.first);queue.push(packet, true);while ((frame = source.getNextFrames()).second != NULL) { result = encoder.encode(frame.first, frame.second); if (result.first != 0) { bytes = packager.getBodyLength(result.first); buf = pool.get(bytes); packet = packager.pack(buf, result.second, result.first); queue.push(packet); }}
主線程
建立H264流以及AAC流,然後開啟視頻編碼線程和音頻編碼線程
PacketQueue queue;MemoryPool pool;V4L2Source v4l2(width, height);PCMSource pcm;YUY2Converter converter(YUY2_CVT_RGB24);MotionDetector detector;FilteredVideoSource videoSource(v4l2);videoSource.addFilter(&converter);videoSource.addFilter(&detector);RTMPPublisher publisher(queue, pool);publisher.connect(url);H264Stream videoStream(videoSource, queue, pool, fps, bitrate);AACStream audioStream(pcm, queue, pool);std::thread videoEncodeThread(&H264Stream::run, &videoStream);std::thread audioEncodeThread(&AACStream::run, &audioStream);publisher.run();
後記
至此,簡單的推送程式就完成了,項目整體比較簡單,基本上也就是在調用第三方庫,但是總的來說學到的東西不少,對於以前不懂的視音頻編碼,也有了一些瞭解,而且也是第一次遇到生產者-消費者的情景,雖然這裡的情景比較簡單,只有一位消費者
自己的測試環境是電腦Ubuntu和樹莓派Raspbian,關於使用到的庫,opencv3應該是需要編譯安裝,樹莓派上libfaac可能也需要編譯安裝
rtmp伺服器用的是SRS,配置方法再網上可以很簡單地搜尋到