如何開發(fā)一個(gè)app建設(shè)一個(gè)網(wǎng)站關(guān)鍵詞搜索熱度查詢
我不生產(chǎn)代碼,我只是代碼的搬運(yùn)工,相信我,看完這個(gè)文章你的圖片一定能變成流媒體推出去。
訴求:使用opencv拉流,轉(zhuǎn)成bgr數(shù)據(jù),需要把處理后的數(shù)據(jù)(BGR)編碼成264,然后推流推出去,相當(dāng)于直播(實(shí)時(shí)編碼)
播放器
超低延遲的RTSP播放器
https://github.com/tsingsee/EasyPlayer-RTSP-Win
青犀的一個(gè)播放器,直接下他的EasyPlayer-RTSP-Win用來測(cè)試就行。劃重點(diǎn),超低延時(shí),我整體方案的延時(shí)大概是600-700ms,使用??迪鄼C(jī),rtsp拉流,做了yolo處理,再推出去,有編碼,有解碼,vlc的延時(shí)設(shè)置低了就回卡幀,Gop已經(jīng)改成5了還是卡幀,沒有測(cè)試Gop改成1的情況,但是vlc的延時(shí)和流暢,整體看是不太兼容的。ffmpeg使用nobuffer也會(huì)卡幀。直觀感受卡的就是Gop的P幀。
服務(wù)器
live555 方案
如果你不著急的話。。。 可以試試這個(gè)方案,這方面的參考文獻(xiàn)給列下面了,因?yàn)榇_實(shí)正經(jīng)研究了幾天還看了不少代碼,認(rèn)真想了應(yīng)該怎么處理,但是確實(shí)不太想寫,而且對(duì)我的需求來講live555冗余了很多功能,再加上網(wǎng)上確實(shí)沒有寫好的,我又很著急要結(jié)果,確定方案能用,所以也沒有用這個(gè)方案。
官方demo
live555自己的測(cè)試文件是有推流demo的,主要是根據(jù)實(shí)時(shí)需求推264文件,以及無腦做一個(gè)推264文件的服務(wù)器,當(dāng)時(shí)看代碼的時(shí)候一頭霧水加上著急,也沒太認(rèn)真看,主要在live555\testProgs下面,testOnDemandRTSPServer,testH264VideoStreamer ,第二個(gè)是無腦推,第一個(gè)是你來一個(gè)請(qǐng)求,我從頭開始給你播放一次視頻文件。
這個(gè)東西的底層是向一個(gè)fTo指針里面拷貝264碼流。
如果你Cpp、coding能力強(qiáng)的話,應(yīng)該是能看懂直接改的,也就不用往后看了。
參考demo 零聲 usb相機(jī)推流
網(wǎng)上基本上和我需求最接近的live555方案下的代碼是國內(nèi)的做音視頻開發(fā)教學(xué)的一個(gè)零聲出的視頻還有他們傳的這個(gè)代碼。
主要功能是 v4l2相機(jī)讀取mjpeg,然后ffmpeg的avcodec相關(guān)庫編碼,然后送live555,然后推實(shí)時(shí)流,像是改的testOnDemandRTSPServer,結(jié)構(gòu)很清晰,除了不能用我也找不到原因外都挺好的。另外他的課是5K的,有點(diǎn)貴。
視頻的話去B站帶關(guān)鍵詞,基本都能搜到,這個(gè)代碼我加了那個(gè)聯(lián)系QQ要到的。但是在我本地沒有推成功,我也不確定是哪里的問題,編譯過了,放在這里
https://gitee.com/qingfuliao/v4l2_ipc_live555?_from=gitee_search
在我這是下面這個(gè)demo 實(shí)現(xiàn)了一個(gè)相似的功能,編譯實(shí)測(cè)是可以讀取usb相機(jī)然后推流成功的。但是代碼結(jié)構(gòu)沒有上面那個(gè)清晰。
參考demo
https://github.com/mpromonet/v4l2rtspserver
這個(gè)功能是基于linux的v4l2,使用264的方式讀取相機(jī)視頻流(如果你的usb相機(jī)不支持264輸出,會(huì)驅(qū)動(dòng)失敗),然后直接拆幀把流發(fā)出去
需要自己下一個(gè)libv4l2cpp的代碼放進(jìn)來就能編譯了
這個(gè)現(xiàn)在看稍微改一改就能用了,不過當(dāng)時(shí)對(duì)整體沒有概念,改了一陣子不知道怎么下手把我自己的碼流變成demo的輸入,碼流送進(jìn)去了但是沒有推成功,定位了一會(huì)兒很難定位問題,也就擱置了。有興趣的可以基于這個(gè)改一改。
https://blog.csdn.net/qq_43418269/article/details/122488866
這個(gè)方案我是成功了的,不過延時(shí)不太能滿足我的需求,這個(gè)復(fù)現(xiàn)很快。他是用一個(gè)管道文件做的,我把編碼之后264文件直接寫到live555的testOnDemandRTSPServer.cpp這邊的讀取文件里面,然后邏輯是live555這邊接收到請(qǐng)求,創(chuàng)建管道,相機(jī)程序初始化后阻塞住,管道被創(chuàng)建后往管道里寫,然后另一邊就開始播。。
這個(gè)就相當(dāng)于是運(yùn)行在一個(gè)demo里的兩個(gè)程序。流倒是推出來了,只不過,我這樣實(shí)現(xiàn),延時(shí)很大,他說的百毫秒量級(jí)我做不到,我是2s左右。感覺也可能是我操作不當(dāng)。
https://blog.csdn.net/lifexx/article/details/52823777
live555讀文件改為內(nèi)存讀取實(shí)現(xiàn),確實(shí)C++不太行,這個(gè)文章對(duì)我理解Live555,還有改成內(nèi)存中的數(shù)據(jù)方向給了很大啟發(fā),但是沒有按照他的做,而且他的參考代碼無法運(yùn)行。對(duì)我理解另一個(gè)推相機(jī)的demo有幫助
其他開源服務(wù)器框架
這個(gè)也是一個(gè)很容易就編譯成功的服務(wù)器,可以使用這個(gè)做服務(wù)器,然后調(diào)用ffmpeg推流,在RK3588上也推成功了,基本沒改make相關(guān)的配置,需要按照他給的快速開始流程使用git下附加庫,功能很強(qiáng)大,但是對(duì)我的需求來講,這個(gè)功能我進(jìn)行二次開發(fā)比較慢。不排除我太菜。
https://github.com/ZLMediaKit/ZLMediaKit/tree/master
這里寫這個(gè)的主要原因是他的一些文章對(duì)我的啟發(fā)和快速上手有很大的參考意義,比如下面這個(gè)。
https://github.com/ZLMediaKit/ZLMediaKit/wiki/%E6%80%8E%E4%B9%88%E6%B5%8B%E8%AF%95ZLMediaKit%E7%9A%84%E5%BB%B6%E6%97%B6%EF%BC%9F
RK3399 參考
網(wǎng)上有瑞芯微其他方案的rtsp推流,我只能幫忙排除錯(cuò)誤選項(xiàng)
https://t.rock-chips.com/forum.php?mod=viewthread&tid=749&extra=page%3D1
如果你是在看這個(gè)帖子,可以不用看了,這個(gè)貓頭的代碼雖然推出去了,但是他的Rtsp是調(diào)庫,這個(gè)庫是閉源的,3588沒有,這個(gè)網(wǎng)站注冊(cè)要兩三天才能通過,不必等這個(gè),用不了。
rtsp推流
在github上搜索rtsp,排名最高的那個(gè)結(jié)果,就是那個(gè)小烏龜,
https://github.com/PHZ76/RtspServer
這個(gè)功能比較單一,但是足夠滿足我的需求了,他還使用他自己的庫做了一個(gè)windows下的應(yīng)用,windows上編譯成功了,但是不太好用,不過對(duì)我理解他的Demo運(yùn)行有一定幫助。因?yàn)橛玫氖且惶讕?。另外他主頁還有一個(gè)rtmp ,我沒進(jìn)去看也沒試能不能用。
https://github.com/PHZ76/DesktopSharing
下載安裝
下載下來直接在3588上面make就可以,編譯的是RtspServer-master/example里面的main 文件,這里的rtsp_h264_file.cpp是可以直接推運(yùn)行推264文件的,一般不需要修改就能直接用,如果不能用,有可能是554端口被占用,改一個(gè)大點(diǎn)的就好了
std::string suffix = "live";std::string ip = "127.0.0.1";std::string port = "5543";// 改這里 不要改那個(gè)"0.0.0.0" 那個(gè)是對(duì)的不用改std::string rtsp_url = "rtsp://" + ip + ":" + port + "/" + suffix;
然后rtsp_pusher.cpp還有rtsp_server.cpp都把發(fā)送文件的部分注釋掉了,需要結(jié)合h264那個(gè)文件來對(duì)比把264碼流寫進(jìn)去。
運(yùn)行測(cè)試
編譯出來之后運(yùn)行 ./rtsp_h264_file ./test.264 就能推出來了
工程已經(jīng)被我魔改過了,重新生成一個(gè)sample 來演示下結(jié)果
這是RK3588 服務(wù)器
這是VLC的界面IP
下面的是顯示的結(jié)果
改cmake 支持opencv 、rknn,mpp
因?yàn)槲沂褂玫腞KNN之間是在Qt里面編譯的,工程使用的都是Cmake的cmakelists,他的makefile也不難改,主要的問題是我自己寫的解碼器,在使用makefile指定mpp庫之后編譯出來的mpp庫運(yùn)行不正常,具體報(bào)錯(cuò)找不到了,然后我qt上用是沒問題的,定位到是makefile沒寫好,改成cmakelist就可以正常編譯了
-g是為了支持gdb調(diào)試,配合我的vscode 調(diào)試配置文件,可以單步調(diào)試,全程在板上編譯,沒配置交叉編譯環(huán)境
cmake_minimum_required(VERSION 3.5)
project(rtspserver)
set(CMAKE_INCLUDE_CURRENT_DIR ON)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSOCKLEN_T=socklen_t -g ")find_package(OpenCV 4.5.5 REQUIRED)
find_package(OpenSSL REQUIRED)# MPP
set(MPP_PATH /home/orangepi/code/mpp-develop/inc)
set(MPP_LIBS /home/orangepi/code/mpp-develop/mpp/librockchip_mpp.so)
include_directories(${MPP_PATH})# OSAL
set(OSAL_PATH /home/orangepi/code/mpp-develop/osal/inc/ /home/orangepi/code/mpp-develop/utils)
set(OSAL_LIBS /home/orangepi/code/mpp-develop/osal/libosal.a /home/orangepi/code/mpp-develop/utils/libutils.a)
include_directories(${OSAL_PATH})# RKNN lib
set(RKNN_API_PATH ${CMAKE_SOURCE_DIR}/lib)
set(RKNN_RT_LIB ${RKNN_API_PATH}/aarch64/librknnrt.so)
include_directories(${RKNN_API_PATH}/include)
include_directories(${CMAKE_SOURCE_DIR}/3rdparty)# RGA
set(RGA_PATH ${CMAKE_SOURCE_DIR}/3rdparty/rga/RK3588)
set(RGA_LIB ${RGA_PATH}/lib/Linux/aarch64/librga.so)
include_directories(${RGA_PATH}/include)aux_source_directory(src/xop SOURCE1)
aux_source_directory(src/net SOURCE2)include_directories( src src/xopsrc/netsrc/3rdpart)# add_executable(rtsp
# example/rtsp_server.cpp
# ${SOURCE1}
# ${SOURCE2}
# src/3rdpart/md5/md5.hpp
# )
# target_link_libraries(rtsp ${MPP_LIBS} ${OSAL_LIBS} ${OpenCV_LIBS} ${RKNN_RT_LIB} ${RGA_LIB} OpenSSL::SSL OpenSSL::Crypto )
# add_executable(rh264
# example/rtsp_h264_file.cpp
# ${SOURCE1}
# ${SOURCE2}
# src/3rdpart/md5/md5.hpp
# )
# target_link_libraries(rh264 ${MPP_LIBS} ${OSAL_LIBS} ${OpenCV_LIBS} OpenSSL::SSL OpenSSL::Crypto )
add_executable(sample
example/sample.cpp
${SOURCE1}
${SOURCE2}
src/3rdpart/md5/md5.hpp
)
target_link_libraries(sample ${MPP_LIBS} ${OSAL_LIBS} ${OpenCV_LIBS} ${RKNN_RT_LIB} ${RGA_LIB} OpenSSL::SSL OpenSSL::Crypto )
代碼修改
代碼是基于他的rtsp_server來修改的,主要修改的內(nèi)容是sendFrameThread,大概思路是這樣的,還差一個(gè)問題,怎么把你的原始mat圖像轉(zhuǎn)成264碼流
bool IsKeyFrame(const char* data, uint32_t size)
{if (size > 4) {//0x67:sps ,0x65:IDR, 0x6: SEIif (data[4] == 0x67 || data[4] == 0x65 || data[4] == 0x6 || data[4] == 0x27) {return true;}}return false;
}void SendFrameThread(xop::RtspServer* rtsp_server, xop::MediaSessionId session_id, int& clients)
{ encoder e;// encoder相關(guān) 內(nèi)存拷貝int size = 0;char* buffer ;// 編碼標(biāo)志位int i = 0;// 生成圖像int width = 1920;int height = 1080;cv::Mat colorBar= cv::Mat::zeros(height, width, CV_8UC3);// 設(shè)置彩條的寬度int barWidth = width / 8; // 8個(gè)彩條,你可以根據(jù)需要調(diào)整// 生成彩條for (int i = 0; i < 8; ++i) {// 計(jì)算彩條的起始和結(jié)束位置int startX = i * barWidth;int endX = (i + 1) * barWidth;// 設(shè)置彩條顏色(BGR格式)cv::Vec3b color;if (i % 2 == 0) {color = cv::Vec3b(255, 0, 155); // 藍(lán)色} else {color = cv::Vec3b(0, 255, 0); // 綠色}// 在colorBar上畫出彩條colorBar(cv::Rect(startX, 0, barWidth, height)) = color;}while(1){if(clients > 0) /* 會(huì)話有客戶端在線, 發(fā)送音視頻數(shù)據(jù) */{{ xop::AVFrame videoFrame = {0};// printf("width is %d, height is %d",colorBar.rows,colorBar.cols);// 編碼 發(fā)包if(0==i){// 第一幀有sps信息 給他兩幀拼一起char *buffer1;int size1;e.init(buffer1,size1);videoFrame.size = size1;e.postAframe(colorBar,buffer,size);videoFrame.size += size; videoFrame.buffer.reset(new uint8_t[videoFrame.size]);memcpy(videoFrame.buffer.get(), buffer1, size1);memcpy(videoFrame.buffer.get()+size1, buffer, size);i++;} else{e.postAframe(colorBar,buffer,size);videoFrame.size = size; // 視頻幀大小 videoFrame.buffer.reset(new uint8_t[videoFrame.size]);memcpy(videoFrame.buffer.get(), buffer, videoFrame.size);}videoFrame.type = IsKeyFrame(buffer, size) ? xop::VIDEO_FRAME_I : xop::VIDEO_FRAME_P;// videoFrame.type = 0; // 建議確定幀類型。I幀(xop::VIDEO_FRAME_I) P幀(xop::VIDEO_FRAME_P)videoFrame.timestamp = xop::H264Source::GetTimestamp(); // 時(shí)間戳, 建議使用編碼器提供的時(shí)間戳// writeCharPointerToFile((char *)videoFrame.buffer.get(), videoFrame.size, "filename.txt"); rtsp_server->PushFrame(session_id, xop::channel_0, videoFrame); //送到服務(wù)器進(jìn)行轉(zhuǎn)發(fā), 接口線程安全/*//獲取一幀 H264, 打包xop::AVFrame videoFrame = {0};videoFrame.type = 0; // 建議確定幀類型。I幀(xop::VIDEO_FRAME_I) P幀(xop::VIDEO_FRAME_P)videoFrame.size = video frame size; // 視頻幀大小 videoFrame.timestamp = xop::H264Source::GetTimestamp(); // 時(shí)間戳, 建議使用編碼器提供的時(shí)間戳videoFrame.buffer.reset(new uint8_t[videoFrame.size]); memcpy(videoFrame.buffer.get(), video frame data, videoFrame.size); rtsp_server->PushFrame(session_id, xop::channel_0, videoFrame); //送到服務(wù)器進(jìn)行轉(zhuǎn)發(fā), 接口線程安全*/}{ /*//獲取一幀 AAC, 打包xop::AVFrame audioFrame = {0};audioFrame.type = xop::AUDIO_FRAME;audioFrame.size = audio frame size; /* 音頻幀大小 audioFrame.timestamp = xop::AACSource::GetTimestamp(44100); // 時(shí)間戳audioFrame.buffer.reset(new uint8_t[audioFrame.size]); memcpy(audioFrame.buffer.get(), audio frame data, audioFrame.size);rtsp_server->PushFrame(session_id, xop::channel_1, audioFrame); // 送到服務(wù)器進(jìn)行轉(zhuǎn)發(fā), 接口線程安全*/} }// xop::Timer::Sleep(20); /* 實(shí)際使用需要根據(jù)幀率計(jì)算延時(shí)! 我這里處理延時(shí)很大,就不人工延遲了*/}videocapture->release();e.deinit((MPP_RET)0);yolo5.deinit();}
rk3588編碼
這方面網(wǎng)上的文章不太多,但是官方給了demo,都是中文,認(rèn)真看看,功能都是能用的。
主要參考他的mpp-develop/test/mpi_enc_test 以及Rk3588-linux-v002\linux\docs\Linux\Multimedia\Rockchip_Developer_Guide_MPP_CN.pdf進(jìn)行配置和使用。這相關(guān)的東西我之后再另開一個(gè)文章單獨(dú)說,總之參考這部分可以做一個(gè)BGR888轉(zhuǎn)成264存儲(chǔ)的demo。
這里重點(diǎn)說一個(gè)概念,I、P、B幀
這個(gè)東西是264流編碼的一個(gè)概念,正常你每一幀的圖像都很大,比如一個(gè)1920*1080,每個(gè)像素點(diǎn)存一個(gè)BGR888的話,就是1920*1080*3 Byte =6220800 Byte ≈ 6M ,然后一秒30幀的話,一秒就要傳180M,局域網(wǎng)或許勉強(qiáng)可以,但是對(duì)帶寬壓力也很大。所以這里就涉及到了壓縮,264、265就是壓縮標(biāo)準(zhǔn),壓縮中需要做兩種壓縮 幀內(nèi)壓縮和 幀間壓縮。
-
幀內(nèi)壓縮:使用一定方法使用盡量小的空間存一幀數(shù)據(jù)。
-
幀間壓縮:利用幀和前后幀的關(guān)聯(lián)來進(jìn)一步的壓縮視頻。
這里我們重點(diǎn)關(guān)注幀間壓縮。給一個(gè)參考文獻(xiàn)
https://zhuanlan.zhihu.com/p/409527359
說的挺透徹的,這里我粗略說一下,看下面這個(gè)圖
I幀 ,是幀間壓縮里面的一幀完整圖像,P幀是前向預(yù)測(cè)幀,B幀是雙向預(yù)測(cè)幀
IDR幀是特殊的I幀,在編解碼時(shí)候P、B幀可以參考I幀前面的幀進(jìn)行復(fù)原,但是不能參考IDR幀前面的幀進(jìn)行復(fù)原
而一個(gè)上面這樣的循環(huán),被編碼成一組,我們指定了h264的gop大小,就確定了多少幀中有一個(gè)I幀
而我們?cè)谧鲋辈?#xff0c;就導(dǎo)致為了低延時(shí),不要B幀,gop 也要盡量的不要太大,如果要解碼P幀,前面就一定要緩存I幀,
監(jiān)測(cè)工具
wireshark, 這個(gè)感覺還是蠻必要的,至少你能看見客戶端和服務(wù)器之間說沒說話。如果你有耐心開RTSP 或者網(wǎng)絡(luò)協(xié)議握手說明的話,你甚至能看到他們之間的握手流程。
雜記
中間遇到了一個(gè)問題,因?yàn)槲疫@個(gè)地方?jīng)]有公網(wǎng),所以只能自己給開一個(gè)熱點(diǎn)給電腦,PC和RK3588之間通過路由器連接,然后使用前面的某個(gè)demo的時(shí)候遇到的,主機(jī)向服務(wù)器(RK3588)發(fā)出視頻請(qǐng)求時(shí),服務(wù)器并沒有直接給客戶端發(fā)rtcp包,而是給一個(gè)200多的地址發(fā)包,而這個(gè)包在客戶端收不到,但是他能收到rtp的協(xié)議包,所以vlc這邊也不提示打不開,就是沒有圖像顯示。
后來使用的方法是,和板子通過路由器網(wǎng)線連接,然后電腦PC wifi 連接路由器,然后路由器沒有公網(wǎng),再使用一個(gè)手機(jī)usb給電腦共享網(wǎng)絡(luò),讓我的調(diào)試環(huán)境穩(wěn)定可以接受到3588的流,和路由器網(wǎng)線連接的時(shí)候接受不到包,感覺是因?yàn)槁酚善鳑]有網(wǎng),使用網(wǎng)線連接時(shí)候被屏蔽了服務(wù)器功能,所以交給路由器的包轉(zhuǎn)發(fā)請(qǐng)求沒有被PC識(shí)別到,但是wifi連接的話,就算他沒有網(wǎng),也是不能忽略的。 目前是這樣理解的。