You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用avcodec_decode_subtitle2从MKV等视频提取并保存字幕?

解决MKV字幕提取并保存为SRT的问题

我来帮你搞定这个字幕提取的事儿!你的现有代码有几个关键问题,导致要么输出一堆无关内容,要么生成的文件根本不是正确的字幕格式,咱们一步步修正:

先说说你代码里的核心问题

  • 常量判断错误:你写的if (rect.x == SUBTITLE_TEXT)完全不对,应该检查rect.type字段来判断字幕类型
  • 忽略SRT格式要求:SRT需要固定的结构(序号、时间轴、字幕文本),你现在只打印文本内容,没法直接用
  • 错误写入原始数据包:之前尝试的out.write((char*)pkt.data, pkt.size)是写入压缩的字幕数据,不是解码后的文本,所以文件才会和视频一样大
  • ASS字幕处理缺失:如果字幕是ASS格式,需要转成SRT兼容的纯文本,或者直接保存成ASS文件(看你需求)

修改后的完整代码

下面是调整后的代码,能正确解码字幕并保存为标准SRT格式:

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <iomanip>

// 假设你已经正确初始化了pFormatCtx、aCodecCtx,且已获取到subtitle_stream_index字幕流索引
std::ofstream out("/path/to/extracted/subtitles.srt");
int subtitle_index = 1; // SRT的序号,从1开始

while (av_read_frame(pFormatCtx, &pkt) == 0) {
    // 只处理字幕流的数据包,过滤掉视频/音频流
    if (pkt.stream_index != subtitle_stream_index) {
        av_packet_unref(&pkt);
        continue;
    }

    int got_frame = 0;
    AVSubtitle subtitle = {0};
    int ret = avcodec_decode_subtitle2(aCodecCtx, &subtitle, &got_frame, &pkt);

    if (ret >= 0 && got_frame) {
        // 计算字幕的开始和结束时间,转成SRT要求的HH:MM:SS,mmm格式
        double start_time = subtitle.start_display_time / 1000.0;
        double end_time = (subtitle.start_display_time + subtitle.end_display_time) / 1000.0;

        // 封装时间格式化函数
        auto format_time = [](double seconds) -> std::string {
            int hours = static_cast<int>(seconds) / 3600;
            int minutes = (static_cast<int>(seconds) % 3600) / 60;
            int secs = static_cast<int>(seconds) % 60;
            int millis = static_cast<int>((seconds - static_cast<int>(seconds)) * 1000);
            std::stringstream ss;
            ss << std::setw(2) << std::setfill('0') << hours << ":"
               << std::setw(2) << std::setfill('0') << minutes << ":"
               << std::setw(2) << std::setfill('0') << secs << ","
               << std::setw(3) << std::setfill('0') << millis;
            return ss.str();
        };

        std::string start_str = format_time(start_time);
        std::string end_str = format_time(end_time);

        // 遍历所有字幕矩形
        for (int i = 0; i < subtitle.num_rects; i++) {
            AVSubtitleRect* rect = subtitle.rects[i];
            if (!rect) continue;

            std::string subtitle_text;
            if (rect->type == SUBTITLE_ASS) {
                // 简单处理ASS格式:去掉所有{...}内的样式标签,提取纯文本
                std::string ass_str(rect->ass);
                size_t pos;
                while ((pos = ass_str.find('{')) != std::string::npos) {
                    size_t end_pos = ass_str.find('}', pos);
                    if (end_pos != std::string::npos) {
                        ass_str.erase(pos, end_pos - pos + 1);
                    } else {
                        break;
                    }
                }
                subtitle_text = ass_str;
            } else if (rect->type == SUBTITLE_TEXT) {
                subtitle_text = rect->text;
            } else {
                // 忽略图片等非文本类型的字幕
                continue;
            }

            // 按照SRT标准格式写入内容
            out << subtitle_index++ << "\n";
            out << start_str << " --> " << end_str << "\n";
            out << subtitle_text << "\n\n";
        }

        // 释放字幕资源,避免内存泄漏
        avsubtitle_free(&subtitle);
    }

    // 释放数据包资源
    av_packet_unref(&pkt);
}

out.close();

关键改进点说明

  1. 过滤字幕流:先判断数据包属于字幕流才处理,避免无效的视频/音频数据包干扰
  2. 时间轴格式化:把FFmpeg返回的毫秒级时间转成SRT要求的标准时间格式
  3. ASS转纯文本:简单清理ASS的样式标签,提取干净的字幕内容(如果想保留ASS格式,直接保存rect->ass并改文件后缀为.ass即可)
  4. 严格遵循SRT结构:按照「序号→时间轴→字幕文本」的顺序写入,生成的文件直接可用
  5. 资源管理:每次处理完字幕和数据包后,调用FFmpeg的API释放资源,避免内存泄漏

内容的提问来源于stack exchange,提问作者user155

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:09:07