如何用libav编写等效ffmpeg的MKV容器PGS/SUP字幕提取C代码
问题:提取PGS字幕原始数据的FFmpeg API实现
我想要编写一段等效于以下ffmpeg命令的C代码:
ffmpeg -i video.mkv -map 0:"$STREAM_INDEX" -c:s copy subtitles.sup目前已成功读取媒体容器、定位字幕流并根据元数据筛选出目标流,但仅能获取到位图数据,无法得到完整的PGS头部及段信息。
尝试将packet->data写入文件,期望包含完整段数据,但该数据并未以预期的PGS魔术头0x5047开头;使用avcodec_decode_subtitle2也仅能得到位图数据,无法获取完整的PGS头部及段信息。
请问在找到目标字幕流后,需执行哪些步骤才能提取出与上述ffmpeg命令输出完全一致的原始数据?
示例代码:
while(av_read_frame(container_ctx, packet) == 0) { if(packet->stream_index != i) { continue; } // Try 1 - dump packet data to file fwrite(packet->data, 1, packet->size, fout); // Try 2 - decode subtitles int bytes_read = 0, success = 0; AVSubtitle sub; if ((bytes_read = avcodec_decode_subtitle2(codec_context, &sub, &success, packet)) < 0) { fprintf(stderr, "Failed to decode subtitles %s", av_err2str(bytes_read)); goto end; } fprintf(stdout, "Success! Status:%d - BytesRead:%d NumRects:%d\n", success, bytes_read, sub.num_rects); if (success) { // Only managed to extract bitmap data here via sub.rects } av_packet_unref(packet); }
解决方案
核心原因
你直接写入的packet->data是MKV容器封装后的字幕分片数据,并非原始的PGS字幕段(0x5047是原始PGS段的标识);而avcodec_decode_subtitle2是把PGS解码成位图,和-c:s copy的"原样复制原始流"逻辑完全相悖。要得到和命令行一致的输出,需要解封装还原完整的PGS原始段,而非直接写容器packet或解码。
正确步骤
- 确认目标流编码为
AV_CODEC_ID_HDMV_PGS_SUBTITLE,避免处理其他类型字幕。 - 无需打开解码器(我们要做的是复制而非解码),使用
av_parser_parse2将MKV容器中分割的字幕分片拼接成完整的PGS原始段。 - 将拼接后的完整PGS段写入输出文件。
修改后的代码示例
#include <libavformat/avformat.h> #include <libavcodec/avcodec.h> int main(int argc, char *argv[]) { AVFormatContext *container_ctx = NULL; AVPacket *packet = av_packet_alloc(); AVCodecParserContext *parser = NULL; FILE *fout = fopen("subtitles.sup", "wb"); int target_stream_idx = atoi(argv[2]); // 假设第二个参数是目标流索引 int ret; // 初始化FFmpeg组件 av_register_all(); avformat_network_init(); // 打开输入文件 if ((ret = avformat_open_input(&container_ctx, argv[1], NULL, NULL)) < 0) { fprintf(stderr, "无法打开输入文件: %s\n", av_err2str(ret)); goto cleanup; } // 读取流信息 if ((ret = avformat_find_stream_info(container_ctx, NULL)) < 0) { fprintf(stderr, "无法获取流信息: %s\n", av_err2str(ret)); goto cleanup; } // 验证目标流为PGS字幕 AVStream *stream = container_ctx->streams[target_stream_idx]; if (stream->codecpar->codec_id != AV_CODEC_ID_HDMV_PGS_SUBTITLE) { fprintf(stderr, "目标流不是PGS字幕\n"); goto cleanup; } // 创建PGS解析器,用于拼接分片数据 parser = av_parser_init(stream->codecpar->codec_id); if (!parser) { fprintf(stderr, "无法初始化字幕解析器\n"); goto cleanup; } // 循环读取并处理字幕包 while (av_read_frame(container_ctx, packet) == 0) { if (packet->stream_index != target_stream_idx) { av_packet_unref(packet); continue; } uint8_t *output_data = NULL; int output_size = 0; // 解析分片数据,还原完整PGS段 ret = av_parser_parse2(parser, NULL, &output_data, &output_size, packet->data, packet->size, packet->pts, packet->dts, packet->pos); if (ret < 0) { fprintf(stderr, "解析出错: %s\n", av_err2str(ret)); av_packet_unref(packet); continue; } // 验证是完整PGS段(以PG开头)后写入文件 if (output_size > 0 && output_data[0] == 'P' && output_data[1] == 'G') { fwrite(output_data, 1, output_size, fout); } av_packet_unref(packet); } cleanup: if (parser) av_parser_close(parser); if (container_ctx) avformat_close_input(&container_ctx); if (packet) av_packet_free(&packet); if (fout) fclose(fout); return ret >= 0 ? 0 : 1; }
关键说明
av_parser_parse2负责将MKV容器中拆分的小字幕包拼接成符合PGS规范的完整段,这是还原原始数据的核心步骤。- 全程不需要解码操作,解码会丢失PGS的头部和段结构信息,只保留位图数据,不符合
-c:s copy的需求。 - 必须验证流编码类型,避免处理非PGS字幕导致输出错误。
内容的提问来源于stack exchange,提问作者Elia
相关产品推荐
相关产品推荐

