Qt+FFmpeg仿VLC接收RTSP流并播放
關鍵詞:Qt FFmpeg C++ RTSP RTP VLC 內存泄漏 摘要認證 花屏 源碼 UDP
本系列原文地址。
下載直接可運行的源碼,在原文頂部。
效果
產生RTSP流
比播放文件復雜一點是,為了接收RTSP流,我們需要產生RTSP流。簡單搭建一個RTSP推流環境:
用EasyDarwin開啟RTSP服務作為RTSP服務器。
用ffmpeg命令行作為客戶端,向EasyDarwin循環推送一個視頻文件。
./ffmpeg.exe -re -stream_loop -1 -i test.mp4 -c copy -f rtsp rtsp://127.0.0.1/stream
這樣就可以從EasyDarwin接收RTSP流了。
我們用vlc接收RTSP流看看。
成功接收。
FFmepg接收RTSP流代碼
用FFmpeg接收RTSP流并播放的流程和播放mp4文件的流程差不多,只不過播放mp4文件時,文件作為播放源,而接收RTSP流時,RTSP流作為了播放源:
我們依舊看下流程中的關鍵代碼:
if (avformat_open_input(&fileFmtCtx, url.toStdString().c_str(), nullptr, nullptr) != 0) {
qDebug() << "avformat_open_input() failed";
return;
}
用于打開一個RTSP地址,跟打開一個文件相比,不僅要查找流信息,還需要和RTSP服務器建立連接,讓RTSP服務器開始推流。
接收上述RTSP流后,我們打印AVFormatContext的相關屬性:
qDebug() << "stream name: " << streamFmtCtx->url;
qDebug() << "stream iformat: " << streamFmtCtx->iformat->name;
qDebug() << "stream duration: " << streamFmtCtx->duration << " microseconds";
qDebug() << "stream bit_rate: " << streamFmtCtx->bit_rate;
/*
stream name: rtsp://127.0.0.1/stream
stream iformat: rtsp
stream duration: -9223372036854775808 microseconds
stream bit_rate: 0
*/
這次由于是RTSP流,并不能獲取準確的duration。繼續打印流相關的信息:
qDebug() << "nb_streams:";
for (unsigned int i = 0; i < streamFmtCtx->nb_streams; i++) {
AVStream *stream = streamFmtCtx->streams[i];
qDebug() << "Stream " << i + 1 << ":";
qDebug() << " Codec: " << avcodec_get_name(stream->codecpar->codec_id);
qDebug() << " Duration: " << stream->duration << " microseconds";
}
/*
nb_streams:
Stream 1 :
Codec: h264
Duration: -9223372036854775808 microseconds
Stream 2 :
Codec: aac
Duration: -9223372036854775808 microseconds
*/
可以看到和上次直接讀取文件的結果一樣,包括1個H264視頻流和1個AAC音頻流。
swsCtx = sws_getContext(decoderCtx->width, decoderCtx->height, decoderCtx->pix_fmt,
decoderCtx->width, decoderCtx->height, FMT_PIC_SHOW,
SWS_BICUBIC, NULL, NULL, NULL);
qDebug() << "decoderCtx->pix_fmt:" << av_get_pix_fmt_name(decoderCtx->pix_fmt);
//decoderCtx->pix_fmt: yuv420p
sws_getContext()用于將RTSP流格式轉換為將要顯示的格式,這里是yuv420p=>AV_PIX_FMT_RGB24。
int numBytes = av_image_get_buffer_size(FMT_PIC_SHOW, decoderCtx->width, decoderCtx->height, 1);
showBuffer = (unsigned char*)av_malloc(static_cast<unsigned long long>(numBytes) * sizeof(unsigned char));
if(av_image_fill_arrays(showFrame->data, showFrame->linesize,
showBuffer, FMT_PIC_SHOW, decoderCtx->width, decoderCtx->height, 1) < 0)
{
qDebug() << "av_image_fill_arrays() failed";
return;
}
av_image_get_buffer_size計算了計算圖像數據的緩沖區大小。av_malloc分配了1個內存塊給showBuffer。av_image_fill_arrays用圖像參數和showBuffer初始化AVFrame的data和linesize成員,并且讓AVFrame和showBuffer關聯。
while(av_read_frame(streamFmtCtx, packet) >= 0){
if(packet->stream_index == nVideoIndex){
if(avcodec_send_packet(decoderCtx, packet)>=0){
while((ret = avcodec_receive_frame(decoderCtx, decodedFrame)) >= 0){
//...
}
}
}
}
和播放mp4文件類似的解碼步驟,從RTSP流中讀取一個數據包AVPacket,將AVPacket送入解碼器進行解碼,嘗試從解碼器中接收已解碼的視頻幀,并將接收到的幀數據存儲在decodedFrame中。
經過上述基本步驟,我們的代碼已經可以和VLC一樣,從RTSP服務器接收RTSP流并播放了。
RTSP協議簡述及驗證
FFmpeg內部將RTSP連接建立處理得很好,但我們有必要進一步學習一下RTSP協議。RTSP全稱Real Time Sreaming Protocol,是TCP/IP協議體系中的一個應用層協議。數據傳輸由RTP/RTCP完成,底層通過TCP/UDP實現。
一個標準的RTSP的收流協議層的交互流程如下:
話不多說,我們直接在上面的推流環境下(由于EasyDarwin似乎加密了某些信息,我們選擇了一個其他的RTSP服務器,效果是一樣的),用VLC收流,并用wireshark抓包看看協議流程是不是這樣的:
直接看看每條信息都是什么:
client => server
Real Time Streaming Protocol
Request: OPTIONS rtsp://127.0.0.1:554/stream RTSP/1.0\r\n
Method: OPTIONS
URL: rtsp://127.0.0.1:554/stream
CSeq: 2\r\n
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
\r\n
client發送OPTIONS向rtsp://127.0.0.1:554/stream詢問server支持哪些RTSP方法。
server=> client
Real Time Streaming Protocol
Response: RTSP/1.0 200 OK\r\n
Status: 200
CSeq: 2\r\n
Session: 4J_bOCNSg
Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS, ANNOUNCE, RECORD\r\n
\r\n
server回復支持DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS, ANNOUNCE, RECORD
client => server
Real Time Streaming Protocol
Request: DESCRIBE rtsp://127.0.0.1:554/stream RTSP/1.0\r\n
Method: DESCRIBE
URL: rtsp://127.0.0.1:554/stream
CSeq: 3\r\n
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
Accept: application/sdp\r\n
\r\n
client請求媒體描述文件,格式為application/sdp。
一般server會進行用戶認證,如果未攜帶Authorization鑒權信息,或者認證失敗,server會返回錯誤號為401的響應,client接收到401響應時,需要根據已知的用戶鑒權信息,生成Authorization,再次發送DESCRIBE,如果認證成功,服務器返回攜帶有SDP的響應信息。
是否進行認證和RTSP服務器有關,這里我們沒有為EasyDarwin設置認證。
server=> client
Real Time Streaming Protocol
Response: RTSP/1.0 200 OK\r\n
CSeq: 3\r\n
Session: _ZLZ7_NSR
Content-type: application/sdp
Content-length: 511
\r\n
Session Description Protocol
Session Description Protocol Version (v): 0
Owner/Creator, Session Id (o): - 0 0 IN IP4 127.0.0.1
Session Name (s): No Name
Connection Information (c): IN IP4 127.0.0.1
Time Description, active time (t): 0 0
Session Attribute (a): tool:libavformat 58.76.100
Media Description, name and address (m): video 0 RTP/AVP 96
Bandwidth Information (b): AS:1894
Media Attribute (a): rtpmap:96 H264/90000
Media Attribute (a): fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAKqwspQFAFumoCAgKAAADAAIAAAMAYcTAAc/YABW+f4xwEA==,aOkJNSU=; profile-level-id=64002A
Media Attribute (a): control:streamid=0
Media Description, name and address (m): audio 0 RTP/AVP 97
Bandwidth Information (b): AS:317
Media Attribute (a): rtpmap:97 MPEG4-GENERIC/48000/2
Media Attribute (a): fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=1190
Media Attribute (a): control:streamid=1
server返回SDP信息,告訴client當前有哪些音視頻流和屬性,sdp協議不做展開。這里我們要關注的比較重要的信息是:server可以發送streamid=0的H264視頻流和streamid=1的AAC音頻流。
client => server
Real Time Streaming Protocol
Request: SETUP rtsp://127.0.0.1:554/stream/streamid=0 RTSP/1.0\r\n
Method: SETUP
URL: rtsp://127.0.0.1:554/stream/streamid=0
CSeq: 4\r\n
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
Transport: RTP/AVP;unicast;client_port=52024-52025
\r\n
client發送SETUP告訴server需要建立streamid=0即視頻流的連接,這里RTP/AVP表示通過UDP傳輸,unicast表示單播,client_port=52024-52025需要單獨解釋一下,前面說到RTSP協議數據傳輸通過RTP+RTCP完成。RTP和RTCP都是建立在UDP之上的,RTP默認使用1個偶數端口號,而RTCP則默認使用RTP端口的下1個奇數端口號,就是這里的52024和52025。
server => client
Real Time Streaming Protocol
Response: RTSP/1.0 200 OK\r\n
Status: 200
CSeq: 4\r\n
Session: 4J_bOCNSg
Transport: RTP/AVP;unicast;client_port=52024-52025
\r\n
server向client返回確認。
client => server
Real Time Streaming Protocol
Request: SETUP rtsp://127.0.0.1:554/stream/streamid=1 RTSP/1.0\r\n
Method: SETUP
URL: rtsp://127.0.0.1:554/stream/streamid=1
CSeq: 5\r\n
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
Transport: RTP/AVP;unicast;client_port=52028-52029
Session: 4J_bOCNSg
\r\n
client告訴server需要建立streamid=1的音頻流的連接,RTP和RTCP的端口分別在52028和52029。
server => client
Real Time Streaming Protocol
Response: RTSP/1.0 200 OK\r\n
Status: 200
Transport: RTP/AVP;unicast;client_port=52028-52029
CSeq: 5\r\n
Session: 4J_bOCNSg
\r\n
server向client返回確認。
client=>server
Real Time Streaming Protocol
Request: PLAY rtsp://127.0.0.1:554/stream RTSP/1.0\r\n
Method: PLAY
URL: rtsp://127.0.0.1:554/stream
CSeq: 6\r\n
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
Session: 4J_bOCNSg
Range: npt=0.000-\r\n
\r\n
client發送PLAY告訴server開始傳輸,Range代表媒體播放時間,server會根據Range的值播放指定段的數據流,對于實時流,一般只會指定起點,即Range: npt=0.000-
server=>client
Real Time Streaming Protocol
Response: RTSP/1.0 200 OK\r\n
Status: 200
CSeq: 6\r\n
Session: 4J_bOCNSg
Range: npt=0.000-\r\n
\r\n
server返回確認,使用同一Session。
client=>server
Real Time Streaming Protocol
Request: TEARDOWN rtsp://127.0.0.1:554/stream RTSP/1.0\r\n
Method: TEARDOWN
URL: rtsp://127.0.0.1:554/stream
CSeq: 7\r\n
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
Session: 4J_bOCNSg
\r\n
client發送TEARDOWN發起停止流傳輸請求。
server=>client
Real Time Streaming Protocol
Response: RTSP/1.0 200 OK\r\n
Status: 200
CSeq: 7\r\n
Session: 4J_bOCNSg
\r\n
server返回確認,使用同一Session,停止流傳輸。
搭建摘要認證環境
上面說到了server可能會進行用戶認證,那我們現在得創造一個需要認證的環境,直接看看EasyDarwin能不能直接選擇認證,打開easydarwin.ini:
[http]
port=10008
default_username=admin
default_password=admin
#...
;是否使能向服務器推流或者從服務器播放時驗證用戶名密碼. [注意] 因為服務器端并不保存明文密碼,所以推送或者播放時,客戶端應該輸入密碼的md5后的值。
;password should be the hex of md5(original password)
authorization_enable=0
#...
可以看到authorization_enable變量是控制認證的,把它的值改為1,重新啟動服務。這時候發現原來的ffmpeg命令推流不成功了。
那就是說,向EasyDarwin推流的時候,也需要進行認證。從注釋上來看,需要加入用戶名和密碼的md5值,我們用正確的參數再推流(下面mad5ofpassword換成你密碼的md5):
./ffmpeg.exe -re -stream_loop -1 -i test.mp4 -c copy -f rtsp rtsp://admin:mad5ofpassword@127.0.0.1/stream
成功了:
這時候用vlc接收試試,果然要進行認證,要求輸入用戶名和密碼:
注意這里密碼也要輸入md5后的值。輸入正確的密碼后,vlc可以接收RTSP流了:
同樣地,用wireshark抓包看看帶有認證的流程是什么樣的:
client=>server
Real Time Streaming Protocol
Request: DESCRIBE rtsp://127.0.0.1:554/stream RTSP/1.0\r\n
Method: DESCRIBE
URL: rtsp://127.0.0.1:554/stream
CSeq: 6\r\n
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
Accept: application/sdp\r\n
\r\n
首先client同樣發起DESCRIBE
server=>client
Real Time Streaming Protocol
Response: RTSP/1.0 401 Unauthorized\r\n
Status: 401
CSeq: 6\r\n
Session: ayQBojNIg
WWW-Authenticate: Digest realm="EasyDarwin", nonce="539c6afee35b8edd354e983a6af947bf", algorithm="MD5"\r\n
\r\n
server返回401,WWW-Authenticate: Digest表示需要摘要認證,realm和nonce用于生成response,algorithm="MD5"表示需要md5算法生成response。
client=>server
Real Time Streaming Protocol
Request: DESCRIBE rtsp://127.0.0.1:554/stream RTSP/1.0\r\n
Method: DESCRIBE
URL: rtsp://127.0.0.1:554/stream
CSeq: 7\r\n
Authorization: Digest username="admin", realm="EasyDarwin", nonce="539c6afee35b8edd354e983a6af947bf", uri="rtsp://127.0.0.1:554/stream", response="d6a48b37f2010b3ddfad1eef18692648"\r\n
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
Accept: application/sdp\r\n
\r\n
client用對應算法生成response并返回給server,response的計算方法單獨再講。
server=>client
Real Time Streaming Protocol
Response: RTSP/1.0 200 OK\r\n
Status: 200
Content-length: 511
CSeq: 7\r\n
Session: ayQBojNIg
\r\n
Data (511 bytes)
server驗證response通過,則返回200。
這里其實和上面一樣返回了SDP信息(Data 511 bytes中的信息),但EasyDarwin是做了加密處理還是什么,是無法解析出來的。
之后的流程就和沒有摘要認證的過程是一樣的了。
完善代碼,處理摘要認證
既然可能會存在認證,那我們代碼中得處理server有認證的情況,否則肯定收不到RTSP流。首先我們定位server的返回在哪里被捕捉了,經過一番嘗試,發現在方法avformat_open_input中:
if ((ret = avformat_open_input(&streamFmtCtx, url.toStdString().c_str(), nullptr, nullptr)) != 0) {
qDebug() << "ret:" << ret;
}
//打印輸出
//ret: -825242872
//ffmpeg日志輸出
//[rtsp @ 000001d2d3940ec0] method DESCRIBE failed: 401 Unauthorized
在需要認證的情況下,avformat_open_input直接返回了一個負數。再結合ffmpeg的日志,大致可以斷定這是server返回Unauthorized時的情況。但我們需要更具體的確認,所以查看avformat_open_input的聲明:
//avformat.h
/*
* @return 0 on success, a negative AVERROR on failure.
*/
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);
返回值是一個int,注釋中寫到如果是失敗,則返回AVERROR,那么接下來,我們可以去ffmpeg的源碼中,找關于AVERROR的內容了。
如果編譯了ffmpeg源碼,直接debug就可以看到最終是如何返回的,但現在我們不想花額外的時間去編譯源碼,所以我們用宇宙第一IDE——Visual Studio,打開ffmpeg的源碼文件夾,直接搜索AVERROR,很方便找到了AVERROR的定義:
//error.h
#define AVERROR(e) (-(e)) ///< Returns a negative error code from a POSIX error code, to return from library functions.
可以看到AVERROR是用來取POSIX中標準錯誤相反數的宏,繼續追蹤沒有發現相關返回的地方。但我們在頭文件卻看見了Unauthorized的相關定義:
//error.h
#define AVERROR_HTTP_UNAUTHORIZED FFERRTAG(0xF8,'4','0','1')
#define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d))
//common.h
#define MKTAG(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
按照定義,AVERROR_HTTP_UNAUTHORIZED實際上是(0xF8,'4','0','1')組合的移位,按照定義計算后AVERROR_HTTP_UNAUTHORIZED確實等于-825242872。為了驗證,我們把宏定義從ffmpeg源碼中復制出來,直接在我們項目中打印:
//mainwindow.h
#define AVERROR_HTTP_UNAUTHORIZED FFERRTAG(0xF8,'4','0','1')
#define MKTAG(a, b, c, d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
#define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d))
//mainwindow.cpp
qDebug() << "AVERROR_HTTP_UNAUTHORIZED:" <<FFERRTAG(0xF8,'4','0','1');
//輸出
//AVERROR_HTTP_UNAUTHORIZED: -825242872
輸出和前面的日志輸出還有我們計算出來的結果都是一樣的,到這里我們確定報出了AVERROR_HTTP_UNAUTHORIZED錯誤。順手把error.h中其他宏定義打印出來,ffmpeg常用錯誤碼錯誤碼表如下:
| 錯誤碼宏定義 | 錯誤碼 | 錯誤說明 |
|---|---|---|
| AVERROR_BSF_NOT_FOUND | -1179861752 | Bitstream filter not found |
| AVERROR_BUG | -558323010 | Internal bug, also see AVERROR_BUG2 |
| AVERROR_BUFFER_TOO_SMALL | -1397118274 | Buffer too small |
| AVERROR_DECODER_NOT_FOUND | -1128613112 | Decoder not found |
| AVERROR_DEMUXER_NOT_FOUND | -1296385272 | Demuxer not found |
| AVERROR_ENCODER_NOT_FOUND | -1129203192 | Encoder not found |
| AVERROR_EOF | -541478725 | End of file |
| AVERROR_EXIT | -1414092869 | Immediate exit was requested; the called function should not be restarted |
| AVERROR_EXTERNAL | -542398533 | Generic error in an external library |
| AVERROR_FILTER_NOT_FOUND | -1279870712 | Filter not found |
| AVERROR_INVALIDDATA | -1094995529 | Invalid data found when processing input |
| AVERROR_MUXER_NOT_FOUND | -1481985528 | Muxer not found |
| AVERROR_OPTION_NOT_FOUND | -1414549496 | Option not found |
| AVERROR_PATCHWELCOME | -1163346256 | Not yet implemented in FFmpeg, patches welcome |
| AVERROR_PROTOCOL_NOT_FOUND | -1330794744 | Protocol not found |
| AVERROR_STREAM_NOT_FOUND | -1381258232 | Stream not found |
| AVERROR_BUG2 | -541545794 | |
| AVERROR_UNKNOWN | -1313558101 | |
| AVERROR_EXPERIMENTAL | -733130664 | |
| AVERROR_INPUT_CHANGED | -1668179713 | |
| AVERROR_OUTPUT_CHANGED | -1668179714 | |
| AVERROR_HTTP_BAD_REQUEST | -808465656 | |
| AVERROR_HTTP_UNAUTHORIZED | -825242872 | |
| AVERROR_HTTP_FORBIDDEN | -858797304 | |
| AVERROR_HTTP_NOT_FOUND | -875574520 | |
| AVERROR_HTTP_OTHER_4XX | -1482175736 | |
| AVERROR_HTTP_SERVER_ERROR | -1482175992 |
于是可以在代碼中增加Unauthorized情況的處理,如果Unauthorized則讓用戶輸入用戶名和密碼。
//ffmpegmanager.cpp
if ((ret = avformat_open_input(&streamFmtCtx, url.toStdString().c_str(), nullptr, nullptr)) != 0) {
if (ret == AVERROR_HTTP_UNAUTHORIZED)
{
//...
return;
}else{
//...
return;
}
}
vlc中,如果輸入的用戶名和密碼無法通過驗證,則會重新彈出驗證框(且用戶名不用重新輸入),直至輸入正確或取消輸入(效果看開頭)。所以我們也加入RTSP地址合法性的檢查等操作:
//ffmpegmanager.cpp
int rtspIndex = url.indexOf("rtsp://");
int atIndex = url.lastIndexOf("@");
if(rtspIndex != -1 && atIndex != -1){
QString couple = url.mid(rtspIndex + 7, atIndex - rtspIndex - 7);
username = couple;
if(couple.contains(':')){
username = couple.mid(0, couple.lastIndexOf(':'));
}
}
到這里,我們的代碼可以適配需要摘要認證的情況了。
增加錯誤窗口
vlc在無法打開RTSP地址的時候會彈出錯誤窗口。
我們也增加一個錯誤窗口,把所有錯誤都歸為無法打開地址,并打印出來。
解決內存泄漏
最然程序可以正常接收RTSP流了,但出現了之前沒出現的情況:內存持續增加。這種情況下一般是發生了內存泄露,之前讀取MP4文件沒有發現,可能是因為文件大小固定,現在持續收流,現象比較明顯,我們得排查我們的代碼。簡單定位之后,我們發現是下面的代碼塊發生泄露:
while(av_read_frame(streamFmtCtx, packet) >= 0){
if(packet->stream_index == nVideoIndex){
if(avcodec_send_packet(decoderCtx, packet)>=0){
while((ret = avcodec_receive_frame(decoderCtx, decodedFrame)) >= 0){
//...
}
}
}
}
接下來我們逐句排查,首先是av_read_frame,查看它的聲明:
//avformat.h
/**
*.....
* On success, the returned packet is reference-counted (pkt->buf is set) and
* valid indefinitely. The packet must be freed with av_packet_unref() when
* it is no longer needed.
*.....
*/
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
這里面有些有用的信息:pkt是reference-counted的,如果不av_packet_unref() ,則它將永久有效。繼續看它的定義,我們的目標是找出和pkt相關的進行reference-counted的語句:
//avformat.cpp
int av_read_frame(AVFormatContext *s, AVPacket *pkt){
//...
ret = read_frame_internal(s, pkt);
ret = avpriv_packet_list_put(&s->internal->packet_buffer,
&s->internal->packet_buffer_end,
pkt, NULL, 0);
//...
}
最終pkt都要執行這兩個函數,avpriv_packet_list_put就是我們要找的地方,繼續看它的聲明和定義:
//packet_internal.h
/**
* Append an AVPacket to the list.
*
* @param head List head element
* @param tail List tail element
* @param pkt The packet being appended. The data described in it will
* be made reference counted if it isn't already.
*/
int avpriv_packet_list_put(PacketList **head, PacketList **tail,
AVPacket *pkt,
int (*copy)(AVPacket *dst, const AVPacket *src),
int flags);
//avpacket.c
int avpriv_packet_list_put(PacketList **packet_buffer,
PacketList **plast_pktl,
AVPacket *pkt,
int (*copy)(AVPacket *dst, const AVPacket *src),
int flags)
{
//...
if (*packet_buffer)
(*plast_pktl)->next = pktl;
else
*packet_buffer = pktl;
*plast_pktl = pktl;
return 0;
}
最后pkt添加到了buffered packet中。其他細節我們可以不用深究,只需要知道pkt被添加到了一個list中,那么這里的確會產生內存泄漏。根據前面聲明中的提示,我們需要使用av_packet_unref()來釋放pkt的引用,那么直接在讀取和使用完1個AVPacket和結束時調用av_packet_unref()。
while(av_read_frame(streamFmtCtx, packet) >= 0){
//...
av_packet_unref(packet);
}
av_packet_unref(packet);
加上后發現,內存泄漏的問題被解決了,那就不再繼續向下排查了。
遺留問題
至此,一個簡單好用的RTSP收流功能就算是完成了,但別高興的太早,事情往往沒有我們想象的那么簡單——經過測試,接收高分辨率視頻一段時間后(甚至一開始),就會產生花屏現象:
考慮到篇幅原因,后面單獨篇章再去討論解決這個問題,依舊是需要從源碼切入:)
TO-DO
- 適配
BASE認證
總結
以上是生活随笔為你收集整理的Qt+FFmpeg仿VLC接收RTSP流并播放的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无用大棒
- 下一篇: 相同二叉树和镜面二叉树问题