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

如何用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或解码。

正确步骤

  1. 确认目标流编码为AV_CODEC_ID_HDMV_PGS_SUBTITLE,避免处理其他类型字幕。
  2. 无需打开解码器(我们要做的是复制而非解码),使用av_parser_parse2将MKV容器中分割的字幕分片拼接成完整的PGS原始段。
  3. 将拼接后的完整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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 09:12:39