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流并播放的更多相关文章
- ffmpeg接收rtsp流问题
项目使用mingw环境g++5.3,C++调用ffmpeg接收rtsp流,再通过C#显示.结构上是C#调用C++的so文件,读取得到的视频帧(RGB888格式),通过图片控件显示. 一开始是使用ope ...
- 使用VLC发送TS流与播放TS流
使用VLC发送TS流与播放TS流 一.如何使用VLC发送TS流 1.添加一个文件至VLC 2.选择串流,继续 3.选择UDP,点击添加 4.输入地址及端口 5.选择h.264+mp3(TS) 6.ne ...
- nginx+ffmpeg搭建rtmp转播rtsp流的flash服务器
本文概要: nginx是非常优秀的开源服务器,用它来做hls或者rtmp流媒体服务器是非常不错的选择.本文介绍了一种简易方法快速搭建rtmp流媒体服务器,也叫rtsp转播,数据源不是读取文件,而是采用 ...
- 使用ffmpeg向crtmpserver发布rtsp流
ffmpeg的调用命令如下: ffmpeg -re -i xxx.mp4 -vcodec copy -acodec copy -f rtsp rtsp://127.0.0.1/live/mystre ...
- [工具]利用EasyRTSPClient工具检查摄像机RTSP流不能播放原因以及排查音视频数据无法播放问题
出现问题 我们在做流媒体开发的过程中,进程会出现摄像机RTSP流莫名其妙无法播放的问题,而我们常用的vlc经常是直接弹出一个无法播放的提示框就完事了,没有说明出错的原因,或者在vlc的消息里面能看到日 ...
- 使HTML5支持RTSP流 微信直播RTSP流 微信播放RTSP直播流(HTML5播放rtsp,web播放rtsp,微信支持rtsp)
一.大家都知道HTML5的VIDEO可以播放视频,但是H5不支持RTSP播放,所以需要中间件! 二.我们经理长年的努力,开发了HTML5支持RTSP的中间件,使HTML5支持RTSP直播! 三.不卡顿 ...
- RTSP 流相关工具介绍
RTSP (Real Time Streaming Protocol),实时流协议,是一种应用层协议,专为流媒体使用.本文将介绍 GStreamer, VLC, FFmpeg 这几个工具,如何发送.接 ...
- Onvif设备Rtsp地址解析和播放
今天把Onvif搜索以及Rtsp流这一块的功能集成了下, 主要包含以下功能: 1. onvif设备的搜索 2. 设备rtsp地址的解析 3. Rtsp流的播放 4. 建立Rtsp流服务器, 使用vlc ...
- centos下用ffmpeg推流宇视科技摄像头rtsp流到前端播放(无flash)
严禁垃圾中文技术网站复制粘贴 流程:安装SRS服务接收ffmpeg的推流,SRS会提供一个flv的播放地址,前端通过fls.js播放即可,无需flash. 1.安装ffmpeg 提供两个版本,都能推流 ...
- 调用Live555接收RTSP直播流,转换为Http Live Streaming(iOS直播)协议
Live555接收RTSP直播流,转换Http Live Streaming(iOS直播)协议 RTSP协议也是广泛使用的直播/点播流媒体协议,之前实现过一个通过live555接收RTSP协议,然后转 ...
随机推荐
- c++中的宏#define用途
宏的一些作用,包括但不限于这些 定义一个变量.字符串.类型 定义一个函数.条件表达式 条件编译.调试信息,异常类 定义结构体.命名空间 定义模版.枚举.函数对象 #define宏定义在C++中用于定义 ...
- Django框架项目之课程主页——课程页页面、课程表分析、课程表数据、课程页面、课程接口、前台、后台
文章目录 1-课程页页面 课程组件 2 课程主页之课程表分析 课程表分析 免费课案例 创建models:course/models.py 注册models:course/adminx.py 数据库迁移 ...
- MySQL系列之优化——1.优化哲学、2. 优化工具的使用、3. 优化思路分解、4. MySQL参数优化测试、5.1 参数优化、6. 参数优化结果、7. 锁的监控及处理、8. 主从优化
文章目录 1.优化哲学 1.1 为什么优化? 1.2 优化风险 1.3 谁参与优化 1.4 优化方向 1.5 优化的范围及思路 优化效果和成本的评估: 2. 优化工具的使用 2.1 系统层面的 2.1 ...
- Python基础——数字类型int与float、字符串、列表、元组、字典、集合、可变类型与不可变类型、数据类型总结
文章目录 一 引子 二 数字类型int与float 2.1 定义 2.2 类型转换 2.3 使用 三 字符串 3.1 定义: 3.2 类型转换 3.3 使用 3.3.1 优先掌握的操作 3.3.2 需 ...
- C#学习笔记--变量类型的转换
变量类型的转化: 转换原则 同类型的大的可以装小的,小类型的装大的就需要强制转换. 隐式转换: 同种类型的转换: //有符号 long-->int-->short-->sbyte l ...
- Update 1.82.1: The update addresses this security issue.
August 2023 (version 1.82) 更新后显示发行说明 Update 1.82.1: The update addresses this security issue. Welcom ...
- 使用 Kubernetes 简化平台工程
平台工程在现代应用程序开发和部署中发挥的作用至关重要.随着软件应用程序变得越来越复杂和分散,对稳健且可扩展的基础设施的需求变得越来越重要.这就是平台工程的作用所在,它是支持整个软件开发生命周期的支柱. ...
- DPDK丢包那些事
本文来自博客园,作者:T-BARBARIANS,博文严禁转载,转载必究! 一.前言 DPDK技术原理相关的文章不胜枚举,但从实战出发,针对DPDK丢包这一类问题进行系统分析的文章还是凤毛麟角. 刚好最 ...
- Sentinel源码改造,实现Nacos双向通信!
Sentinel Dashboard(控制台)默认情况下,只能将配置规则保存到内存中,这样就会导致 Sentinel Dashboard 重启后配置规则丢失的情况,因此我们需要将规则保存到某种数据源中 ...
- 面向生产的 LLM 优化
注意 : 本文同时也是 Transformers 的文档. 以 GPT3/4.Falcon 以及 LLama 为代表的大语言模型 (Large Language Model,LLM) 在处理以人为中心 ...