1. 添加teardown
1、解决方法就是在调用ijkmp_stop之前通过avformat_preclose_input 调用这个方法:
ijkplayer.c
//20241025
int ffp_pre_stop_l(FFPlayer *ffp)
{
assert(ffp);
VideoState *is = ffp->is;
if (is){
avformat_pre_close_input(&is->ic);
}
return 0;
}
2. ijkplayer.c 的ijkmp_stop_l 方法里的ffp_stop_l 方法前添加如下方法, 以此为准
ffp_pre_stop_l(mp->ffplayer);
3. ijkplayer/extra/ffmpeg/libavformat/rtspdec.c修改(所有涉及的rtspdec.c文件都需要有改动):
57行
//20241025
static int rtsp_read_pre_close(AVFormatContext *s)
{
RTSPState *rt = s->priv_data;
if (!(rt->rtsp_flags & RTSP_FLAG_LISTEN))
ff_rtsp_send_cmd_async(s,"TEARDOWN", rt->control_uri, NULL);
return 0;
}
最后.read_close = rtsp_read_close之前 增加:
.read_pre_close = rtsp_read_pre_close,
4. 修改 ijkplayer/extra/ffmpeg/libavformat/avformat.h
(1)增加方法read_pre_close 接口
723
/**20241025
* The stream is going to be closed. The AVFormatContext and AVStreams are not
* freed by this function
*/
int (*read_pre_close)(struct AVFormatContext *);
(2)增加方法avformat_pre_close_input接口
2499
/** 20241025
* Call before closing input AVFormatContext.
*/
void avformat_pre_close_input(AVFormatContext **s);
5、修改 ijkplayer/extra/ffmpeg/libavformat/utils.c ,实现pre_close_input
4205
//20241025
void avformat_pre_close_input(AVFormatContext **ps)
{
AVFormatContext *s;
if (!ps || !*ps)
return;
s = *ps;
if (s->iformat)
if (s->iformat->read_pre_close)
s->iformat->read_pre_close(s);
}
2. 卡顿修改示列
原因分析:
ffmpeg 解析服务器rtsp 返回数据时,通过”$“和 ”return_on_interleaved_data“字段 来区分rtsp控制信息和音视
频数据包的开始字段。而读取数据是按照一个一个字符来进行的,当网络、服务器数据等原因造成数据包不完整
时,”ff_rtsp_read_reply“方法就会出现卡顿的现象
解决方案:
修改extra/ffmpeg/libavformat/rtsp.c
int ff_rtsp_read_reply(AVFormatContext *s, RTSPMessageHeader *reply,
unsigned char **content_ptr,
int return_on_interleaved_data, const char *method)
{
RTSPState *rt = s->priv_data;
char buf[4096], buf1[1024], *q;
unsigned char ch;
const char *p;
int ret, content_length, line_count = 0, request = 0;
unsigned char *content = NULL;
start:
line_count = 0;
request = 0;
content = NULL;
memset(reply, 0, sizeof(*reply));
/* parse reply (XXX: use buffers) */
rt->last_reply[0] = '\0';
for (;;) {
q = buf;
for (;;) {
ret = ffurl_read_complete(rt->rtsp_hd, &ch, 1);
av_log(s, AV_LOG_TRACE, "ret=%d c=%02x [%c]\n", ret, ch, ch);
if (ret != 1)
return AVERROR_EOF;
if (ch == '\n')
break;
//if (ch == '$' && q == buf) { magical note
if (ch == '$') { //magical add
if (return_on_interleaved_data) {
return 1;
} else
ff_rtsp_skip_packet(s);
} else if (ch != '\r') {
if ((q - buf) < sizeof(buf) - 1)
*q++ = ch;
}
}
*q = '\0';
av_log(s, AV_LOG_TRACE, "line='%s'\n", buf);
/* test if last line */
if (buf[0] == '\0')
break;
p = buf;
if (line_count == 0) {
/* get reply code */
get_word(buf1, sizeof(buf1), &p);
if (!strncmp(buf1, "RTSP/", 5)) {
get_word(buf1, sizeof(buf1), &p);
reply->status_code = atoi(buf1);
av_strlcpy(reply->reason, p, sizeof(reply->reason));
} else {
av_strlcpy(reply->reason, buf1, sizeof(reply->reason)); // method
get_word(buf1, sizeof(buf1), &p); // object
request = 1;
}
} else {
//--------------------magical-s---------------
if (return_on_interleaved_data) {
av_log(s, AV_LOG_WARNING, "ADD ADD ------------ RETURN return_on_interleaved_data='%d',line_count = %d --------------ADD ADD.\n", return_on_interleaved_data, line_count);
return 0;
}
//----------------------e--------------
ff_rtsp_parse_line(s, reply, p, rt, method);
av_strlcat(rt->last_reply, p, sizeof(rt->last_reply));
av_strlcat(rt->last_reply, "\n", sizeof(rt->last_reply));
}
line_count++;
}
if (rt->session_id[0] == '\0' && reply->session_id[0] != '\0' && !request)
av_strlcpy(rt->session_id, reply->session_id, sizeof(rt->session_id));
content_length = reply->content_length;
if (content_length > 0) {
/* leave some room for a trailing '\0' (useful for simple parsing) */
content = av_malloc(content_length + 1);
if (!content)
return AVERROR(ENOMEM);
ffurl_read_complete(rt->rtsp_hd, content, content_length);
content[content_length] = '\0';
}
if (content_ptr)
*content_ptr = content;
else
av_freep(&content);
if (request) {
//----------magical -----s--------------
//add 20240220 If normal data packets arrive here, it indicates that abnormal data is parsed, and it directly returns 0, not request the server
if (return_on_interleaved_data) {
av_log(s, AV_LOG_WARNING, "ADD ADD ------------ request RETURN return_on_interleaved_data='%d' --------------ADD ADD.\n", return_on_interleaved_data);
return 0;
}
//-----------------------------
char buf[1024];
char base64buf[AV_BASE64_SIZE(sizeof(buf))];
const char* ptr = buf;
if (!strcmp(reply->reason, "OPTIONS")) {
snprintf(buf, sizeof(buf), "RTSP/1.0 200 OK\r\n");
if (reply->seq)
av_strlcatf(buf, sizeof(buf), "CSeq: %d\r\n", reply->seq);
if (reply->session_id[0])
av_strlcatf(buf, sizeof(buf), "Session: %s\r\n",
reply->session_id);
} else {
snprintf(buf, sizeof(buf), "RTSP/1.0 501 Not Implemented\r\n");
}
av_strlcat(buf, "\r\n", sizeof(buf));
if (rt->control_transport == RTSP_MODE_TUNNEL) {
av_base64_encode(base64buf, sizeof(base64buf), buf, strlen(buf));
ptr = base64buf;
}
ffurl_write(rt->rtsp_hd_out, ptr, strlen(ptr));
rt->last_cmd_time = av_gettime_relative();
/* Even if the request from the server had data, it is not the data
* that the caller wants or expects. The memory could also be leaked
* if the actual following reply has content data. */
if (content_ptr)
av_freep(content_ptr);
/* If method is set, this is called from ff_rtsp_send_cmd,
* where a reply to exactly this request is awaited. For
* callers from within packet receiving, we just want to
* return to the caller and go back to receiving packets. */
if (method)
goto start;
return 0;
}
if (rt->seq != reply->seq) {
av_log(s, AV_LOG_WARNING, "CSeq %d expected, %d received.\n",
rt->seq, reply->seq);
}
/* EOS */
if (reply->notice == 2101 /* End-of-Stream Reached */ ||
reply->notice == 2104 /* Start-of-Stream Reached */ ||
reply->notice == 2306 /* Continuous Feed Terminated */) {
rt->state = RTSP_STATE_IDLE;
} else if (reply->notice >= 4400 && reply->notice < 5500) {
return AVERROR(EIO); /* data or server error */
} else if (reply->notice == 2401 /* Ticket Expired */ ||
(reply->notice >= 5500 && reply->notice < 5600) /* end of term */ )
return AVERROR(EPERM);
return 0;
}
3. 花屏解决实例
1. h264_parse.c
add
#include "libavformat/avformat.h"
gFrameErorr
gFrameErorr
2. h264_slice.c
add
#include "libavformat/avformat.h"
gFrameErorr
3. avformat.h
gFrameErorr
4.rtpdec.c
gFrameErorr
5. udp.c
UDP_MAX_PKT_SIZE
6. ff_ffplay.c
static int packet_queue_get_or_buffering(FFPlayer *ffp, PacketQueue *q, AVPacket *pkt, int *serial, int *finished)
{
assert(finished);
if (!ffp->packet_buffering)
return packet_queue_get(q, pkt, 1, serial);
while (1) {
int new_packet = packet_queue_get(q, pkt, 0, serial);
if (new_packet < 0)
return -1;
else if (new_packet == 0) {
// if (q->is_buffer_indicator && !*finished)
// ffp_toggle_buffering(ffp, 1);
av_log(NULL, AV_LOG_ERROR, "%s magical update packet_queue_get_or_buffering\n", __func__);
new_packet = packet_queue_get(q, pkt, 1, serial);
if (new_packet < 0)
return -1;
}
if (*finished == *serial) {
av_packet_unref(pkt);
continue;
}
else
break;
}
return 1;
}
static double vp_duration(VideoState *is, Frame *vp, Frame *nextvp) {
/*
if (vp->serial == nextvp->serial) {
double duration = nextvp->pts - vp->pts;
if (isnan(duration) || duration <= 0 || duration > is->max_frame_duration)
return vp->duration;
else
return duration;
} else {
return 0.0;
}*/
return vp->duration; //magical add
}
delay = 0
截屏2024-10-30 14.33.22.png
截屏2024-10-30 14.33.57.png
截屏2024-10-30 14.34.52.png
设置丢弃 GOP 的标志
ff_ffplay_def.h
discard_gop
static void decoder_init(Decoder *d, AVCodecContext *avctx, PacketQueue *queue, SDL_cond *empty_queue_cond) {
memset(d, 0, sizeof(Decoder));
d->avctx = avctx;
d->queue = queue;
d->empty_queue_cond = empty_queue_cond;
d->start_pts = AV_NOPTS_VALUE;
d->first_frame_decoded_time = SDL_GetTickHR();
d->first_frame_decoded = 0;
// d->avctx->flags |= CODEC_FLAG_LOW_DELAY; //magical add
SDL_ProfilerReset(&d->decode_profiler, -1);
}
截屏2024-10-30 14.40.36.png
调用
/*
//设置使用HEVC(High Efficiency Video Coding)硬件解码器。HEVC是一种视频编码标准,允许更高的视频质量和更低的比特率。
player.setOption(FijkOption.playerCategory, "mediacodec-hevc", 1);
//启用帧丢弃,允许在视频渲染过程中丢弃一些帧,以减少延迟。 视频帧处理不过来的时候丢弃一些帧达到同步的效果
player.setOption(FijkOption.playerCategory, "framedrop", 2);
//禁用OpenSL ES音频渲染器。
player.setOption(FijkOption.playerCategory, "opensles", 0);
// packet-buffering: 禁用数据包缓冲。
player.setOption(FijkOption.playerCategory, "packet-buffering", 0);
// mediacodec-auto-rotate: 禁用自动旋转。
player.setOption(FijkOption.playerCategory, "mediacodec-auto-rotate", 0);
//设置最小帧数。可能与视频渲染有关。
player.setOption(FijkOption.playerCategory, "min-frames", 2);
//max_cached_duration: 设置最大缓存持续时间。
player.setOption(FijkOption.playerCategory, "max_cached_duration", 0); //300
//启用无限缓冲。
player.setOption(FijkOption.playerCategory, "infbuf", 0);
//设置重新连接次数。
player.setOption(FijkOption.playerCategory, "reconnect", 5);
//设置RTSP传输方式为TCP。
player.setOption(FijkOption.formatCategory, "rtsp_transport", 'tcp');
//设置RTSP标志为"prefer_tcp",首选使用TCP传输。
player.setOption(FijkOption.formatCategory, "rtsp_flags", "prefer_tcp");
//禁用HTTP检测范围支持
player.setOption(FijkOption.formatCategory, "http-detect-range-support", 0);
//设置分析持续时间。
player.setOption(FijkOption.formatCategory, "analyzeduration", 1);
player.setOption(FijkOption.formatCategory, "analyzemaxduration", 100);
//设置缓冲区大小。
player.setOption(FijkOption.formatCategory, "buffer_size", 1024 * 2);
//设置最大帧速率。
player.setOption(FijkOption.formatCategory, "max-fps", 0);
//清除DNS缓存
player.setOption(FijkOption.formatCategory, "dns_cache_clear", 1);
//启用刷新数据包。
player.setOption(FijkOption.formatCategory, "flush_packets", 1);
//设置最大缓冲区大小。
player.setOption(FijkOption.formatCategory, "max-buffer-size", 0);
//设置文件标志为"nobuffer"
player.setOption(FijkOption.formatCategory, "fflags", "nobuffer");
// 设置探测文件的大小。播放前的探测Size,默认是1M, 改小一点会出画面更快
player.setOption(FijkOption.formatCategory, "probesize", 100);
//设置跳过环路滤波器 设置是否开启环路过滤: 0开启,画面质量高,解码开销大,48关闭,画面质量差点,解码开销小
player.setOption(FijkOption.codecCategory, "skip_loop_filter", 48);
//设置跳过帧
player.setOption(FijkOption.codecCategory, "skip_frame", 1);
// 需要准备好后自动播放
player.setOption(FijkOption.playerCategory, "start-on-prepared", 1);
// 不额外优化(使能非规范兼容优化,默认值0 )
player.setOption(FijkOption.playerCategory, "fast", 1);
*/
void setPlayerOptions() {
player.setOption(FijkOption.playerCategory, "mediacodec-hevc", 1);
player.setOption(FijkOption.playerCategory, "framedrop", 1);
player.setOption(FijkOption.playerCategory, "start-on-prepared", 0);
player.setOption(FijkOption.playerCategory, "opensles", 0);
player.setOption(FijkOption.playerCategory, "mediacodec", 0);
player.setOption(FijkOption.playerCategory, "start-on-prepared", 1);
player.setOption(FijkOption.playerCategory, "packet-buffering", 0);
player.setOption(FijkOption.playerCategory, "mediacodec-auto-rotate", 0);
player.setOption(
FijkOption.playerCategory, "mediacodec-handle-resolution-change", 0);
player.setOption(FijkOption.playerCategory, "min-frames", 2);
player.setOption(FijkOption.playerCategory, "max_cached_duration", 3);
player.setOption(FijkOption.playerCategory, "infbuf", 1);
player.setOption(FijkOption.playerCategory, "reconnect", 5);
player.setOption(FijkOption.playerCategory, "framedrop", 5);
player.setOption(FijkOption.formatCategory, "rtsp_transport", 'tcp');
player.setOption(FijkOption.formatCategory, "http-detect-range-support", 0);
player.setOption(FijkOption.formatCategory, "analyzeduration", 1);
player.setOption(FijkOption.formatCategory, "rtsp_flags", "prefer_tcp");
player.setOption(FijkOption.formatCategory, "buffer_size", 1024 * 5);
player.setOption(FijkOption.formatCategory, "max-fps", 0);
player.setOption(FijkOption.formatCategory, "analyzemaxduration", 20);
// player.setOption(FijkOption.formatCategory, "analyzemaxduration", 50);
player.setOption(FijkOption.formatCategory, "dns_cache_clear", 1);
player.setOption(FijkOption.formatCategory, "flush_packets", 1);
player.setOption(FijkOption.formatCategory, "max-buffer-size", 0);
player.setOption(FijkOption.formatCategory, "fflags", "nobuffer");
// player.setOption(FijkOption.formatCategory, "probesize", 200);
player.setOption(FijkOption.formatCategory, "probesize", 100);
player.setOption(FijkOption.formatCategory, "http-detect-range-support", 0);
player.setOption(FijkOption.codecCategory, "skip_loop_filter", 48);
player.setOption(FijkOption.codecCategory, "skip_frame", 0);
}