如何用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();
关键改进点说明
- 过滤字幕流:先判断数据包属于字幕流才处理,避免无效的视频/音频数据包干扰
- 时间轴格式化:把FFmpeg返回的毫秒级时间转成SRT要求的标准时间格式
- ASS转纯文本:简单清理ASS的样式标签,提取干净的字幕内容(如果想保留ASS格式,直接保存
rect->ass并改文件后缀为.ass即可) - 严格遵循SRT结构:按照「序号→时间轴→字幕文本」的顺序写入,生成的文件直接可用
- 资源管理:每次处理完字幕和数据包后,调用FFmpeg的API释放资源,避免内存泄漏
内容的提问来源于stack exchange,提问作者user155
相关产品推荐
相关产品推荐

