记ffmpeg subtitles滤镜切换字幕卡顿

1.subtitles卡顿

偷懒在项目中使用ffmpeg的subtitles滤镜进行字幕渲染。后来发现,使用滤镜切换字幕时会出现卡顿。经过进一步测试与代码调式确认,在播放一个时长在一个小时以上的视频文件的内挂字幕时,滤镜初始化花费了较长的时间。

使用ffplay + subtitles滤镜播放该文件并显示字幕存在同样的问题。(播放窗口出现前需执行较长时间)(ps:好,第三方开源库问题,下班喽)
注:该问题在ffmpeg5.1存在。6.1不记得了,回头再测下看看。

2.调试ffmpeg滤镜

ffmpeg代码不是很熟,好在手里有跑通的源码可以用来调试。大致定位了下,找到了vf_subtitles.c这个文件,subtitles滤镜的相关代码都在这个文件内。接下来就好找了,我们阅读下代码或者直接调试都可以确定,花费了大量时间的是init_subtitles函数的以下代码:

while (av_read_frame(fmt, &pkt) >= 0) {
        int i, got_subtitle;
        AVSubtitle sub = {0};

        if (pkt.stream_index == sid) {
            ret = avcodec_decode_subtitle2(dec_ctx, &sub, &got_subtitle, &pkt);
            if (ret < 0) {
                av_log(ctx, AV_LOG_WARNING, "Error decoding: %s (ignored)\n",
                       av_err2str(ret));
            } else if (got_subtitle) {
                const int64_t start_time = av_rescale_q(sub.pts, AV_TIME_BASE_Q, av_make_q(1, 1000));
                const int64_t duration   = sub.end_display_time;
                for (i = 0; i < sub.num_rects; i++) {
                    char *ass_line = sub.rects[i]->ass;
                    if (!ass_line)
                        break;
                    ass_process_chunk(ass->track, ass_line, strlen(ass_line),
                                      start_time, duration);
                }
            }
        }
        av_packet_unref(&pkt);
        avsubtitle_free(&sub);
    }

这段代码很简单,调用的ffmpeg api解码字幕并将字幕文本传入libass。但是再往上找能看到以下代码:

ret = avformat_open_input(&fmt, ass->filename, NULL, NULL);
    if (ret < 0) {
        av_log(ctx, AV_LOG_ERROR, "Unable to open %s\n", ass->filename);
        goto end;
    }
    ret = avformat_find_stream_info(fmt, NULL);
    if (ret < 0)
        goto end;

    /* Locate subtitles stream */
    if (ass->stream_index < 0)
        ret = av_find_best_stream(fmt, AVMEDIA_TYPE_SUBTITLE, -1, -1, NULL, 0);
    else {
        ret = -1;
        if (ass->stream_index < fmt->nb_streams) {
            for (j = 0; j < fmt->nb_streams; j++) {
                if (fmt->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
                    if (ass->stream_index == k) {
                        ret = j;
                        break;
                    }
                    k++;
                }
            }
        }
    }

这里对字幕文件解封装,如果是针对外挂字幕文件,这两段代码是可行的。但如果是内挂字幕,解码前的av_read_frame方法会遍历输入文件的所有包,包括视频流与音频流的包。耗费了大量的时间。

3.解决方法

代码修改如下:

/* Locate subtitles stream */
    if (ass->stream_index < 0)
        ret = av_find_best_stream(fmt, AVMEDIA_TYPE_SUBTITLE, -1, -1, NULL, 0);
    else {
        ret = -1;
        if (ass->stream_index < fmt->nb_streams) {
            for (j = 0; j < fmt->nb_streams; j++) {
                if (fmt->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
                    if (ass->stream_index == k) {
                        ret = j;
                        // break;
                    }
                    k++;
                }
            }
        }
    }

删除找到字幕流后的的break,并设置除字幕流外每一条流的flag为discard_all,避免遍历非选定字幕流的包导致耗时。然后重新编译ffmpeg即可。

if (ass->stream_index < 0)
        ret = av_find_best_stream(fmt, AVMEDIA_TYPE_SUBTITLE, -1, -1, NULL, 0);
    else {
        ret = -1;
        if (ass->stream_index < fmt->nb_streams) {
            for (j = 0; j < fmt->nb_streams; j++) {
                auto discard = fmt->streams[j]->discard;
                fmt->streams[j]->discard = AVDISCARD_ALL;
                if (fmt->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
                    if (ass->stream_index == k) {
                        ret = j;
                        // break;
                        fmt->streams[j]->discard = discard;
                    }
                    k++;
                }
            }
        }
    }

posted @ 2024-09-27 23:34  愤怒的蒲公英  阅读(40)  评论(0编辑  收藏  举报