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

FFmpeg写入带内嵌字幕的MP4时avcodec_encode_subtitle报错求助

解决MP4内嵌MOV_TEXT字幕编码错误(错误码-12)的问题

问题重现

已成功写入H264视频帧,但添加内嵌字幕时调用avcodec_encode_subtitle返回错误码-12。尝试将rect->type设为SUBTITLE_TEXT时,收到错误提示:[mov_text @ 0000000006df0040] Only SUBTITLE_ASS type supported.;改为SUBTITLE_ASS后仍报错。

原实现代码

AVSubtitle subtitle;
memset(&subtitle, 0, sizeof(AVSubtitle));

subtitle.format = 1;
subtitle.num_rects = 1;
subtitle.rects = (AVSubtitleRect**)av_mallocz(sizeof(AVSubtitleRect*));
subtitle.rects[0] = (AVSubtitleRect*)av_mallocz(sizeof(AVSubtitleRect));
AVSubtitleRect* rect = subtitle.rects[0];
int buf_size = 65536;
char* text = "Test subtitles";
rect->type = SUBTITLE_ASS; //dà errore
rect->ass = av_strdup(text);
subtitle.start_display_time = 0;
subtitle.end_display_time = 0;
subtitle.pts = mp4h->av_pkt.pts;

uint8_t* buf = (uint8_t*)av_malloc(buf_size);
if (!buf) {
    trace_error("Could not allocate buffer");
    return -1;
}

int encoded_size = avcodec_encode_subtitle(subtitleCtx, buf, buf_size, &subtitle);
if (encoded_size < 0) {
    char strE[256];
    av_strerror(encoded_size, strE, sizeof(strE));
    trace_error("Failed to encode subtitle with error: %s", strE);
}
else
{
    AVPacket* pkt = av_packet_alloc();
    if (av_packet_from_data(pkt, buf, encoded_size) < 0) {
        av_free(buf);
        av_packet_free(&pkt);
        trace_error("Error setting packet data");
    }
    else
    {
        pkt->stream_index = subtitleStream->index;
        pkt->pts = av_rescale_q(subtitle.pts, subtitleCtx->time_base, subtitleStream->time_base);

        pkt->pts = pkt->pts;
        pkt->dts = pkt->dts;
        pkt->duration = 0;

        ret = av_interleaved_write_frame(av_fmt_ctx_out, pkt);
        if (ret != 0) {
            char strE[256];
            av_strerror(ret, strE, sizeof(strE));
            trace_error("av_interleaved_write_frame returns %d - %s", ret, strE);
        }
    }
}

原subtitleCtx初始化代码

enum AVCodecID id = AV_CODEC_ID_MOV_TEXT;
subtitleCodec = avcodec_find_encoder(id);
if (subtitleCodec)
{
    subtitleStream = avformat_new_stream(av_fmt_ctx_out, subtitleCodec);
    subtitleCtx = avcodec_alloc_context3(subtitleCodec);
    if (subtitleCtx == NULL) {
        eth_trace_error("avcodec_alloc_context3 failed");
        goto FnExit;
    }
    if (subtitleCtx)
    {
        subtitleCtx->codec_id = id;
        subtitleCtx->codec_type = AVMEDIA_TYPE_SUBTITLE;
        subtitleCtx->width = par.width;
        subtitleCtx->height = par.height;
        subtitleCtx->framerate = av_make_q(1000, 1);
        subtitleCtx->time_base = av_make_q(1, 1000);
    }

    subtitleStream->id = 1;
    subtitleStream->codecpar->codec_id = id;
    subtitleStream->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
    subtitleStream->codecpar->width = par.width;
    subtitleStream->codecpar->height = par.height;
    subtitleStream->time_base = av_make_q(1, 1000);
    subtitleStream->avg_frame_rate = av_make_q(1000, 1);

    if (av_fmt_ctx_out->oformat->flags & AVFMT_GLOBALHEADER)
        subtitleCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

if (avformat_write_header(mp4h->av_fmt_ctx_out, &mp4h->dict) < 0) {
    trace_error("Fail to write the header of output file");
}

问题根源及修复步骤

1. 核心缺失:未打开编码器

初始化subtitleCtx后,未调用avcodec_open2打开编码器,这是编码失败的首要原因。必须在配置好上下文后完成编码器初始化,才能进行字幕编码。

2. ASS格式不符合规范

MOV_TEXT编码器要求传入的ASS文本必须严格遵循ASS脚本对话格式,不能直接传入普通文本。标准对话格式示例:Dialogue: 0,0:00:00.000,0:00:05.000,,Test subtitles,,0,0,0,,

3. 时间参数未正确配置

start_display_time、end_display_time需与时间基匹配设置合理值,同时AVPacket的duration也需要根据字幕显示时长正确赋值,否则会导致编码异常或字幕无法正常显示。

修正后的代码

修正后的subtitleCtx初始化代码(补充编码器打开及参数同步)

enum AVCodecID id = AV_CODEC_ID_MOV_TEXT;
subtitleCodec = avcodec_find_encoder(id);
if (subtitleCodec)
{
    subtitleStream = avformat_new_stream(av_fmt_ctx_out, subtitleCodec);
    subtitleCtx = avcodec_alloc_context3(subtitleCodec);
    if (subtitleCtx == NULL) {
        eth_trace_error("avcodec_alloc_context3 failed");
        goto FnExit;
    }
    if (subtitleCtx)
    {
        subtitleCtx->codec_id = id;
        subtitleCtx->codec_type = AVMEDIA_TYPE_SUBTITLE;
        subtitleCtx->width = par.width;
        subtitleCtx->height = par.height;
        subtitleCtx->framerate = av_make_q(1000, 1);
        subtitleCtx->time_base = av_make_q(1, 1000);
    }

    subtitleStream->id = 1;
    subtitleStream->codecpar->codec_id = id;
    subtitleStream->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
    subtitleStream->codecpar->width = par.width;
    subtitleStream->codecpar->height = par.height;
    subtitleStream->time_base = av_make_q(1, 1000);
    subtitleStream->avg_frame_rate = av_make_q(1000, 1);

    if (av_fmt_ctx_out->oformat->flags & AVFMT_GLOBALHEADER)
        subtitleCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    // 新增:打开字幕编码器
    if (avcodec_open2(subtitleCtx, subtitleCodec, NULL) < 0) {
        eth_trace_error("Failed to open subtitle codec");
        goto FnExit;
    }

    // 新增:将上下文参数同步到流的codecpar
    if (avcodec_parameters_from_context(subtitleStream->codecpar, subtitleCtx) < 0) {
        eth_trace_error("Failed to copy codec parameters to stream");
        goto FnExit;
    }

    if (avformat_write_header(mp4h->av_fmt_ctx_out, &mp4h->dict) < 0) {
        trace_error("Fail to write the header of output file");
        goto FnExit;
    }
}

修正后的字幕实现代码

AVSubtitle subtitle;
memset(&subtitle, 0, sizeof(AVSubtitle));

subtitle.format = 1;
subtitle.num_rects = 1;
subtitle.rects = (AVSubtitleRect**)av_mallocz(sizeof(AVSubtitleRect*));
subtitle.rects[0] = (AVSubtitleRect*)av_mallocz(sizeof(AVSubtitleRect));
AVSubtitleRect* rect = subtitle.rects[0];
int buf_size = 65536;

// 使用符合ASS规范的对话格式文本
char* ass_text = "Dialogue: 0,0:00:00.000,0:00:05.000,,Test subtitles,,0,0,0,,\r\n";
rect->type = SUBTITLE_ASS;
rect->ass = av_strdup(ass_text);

// 设置正确的显示时间(单位:时间基刻度,此处time_base为1/1000,5000代表5秒)
subtitle.start_display_time = 0;
subtitle.end_display_time = 5000;
subtitle.pts = mp4h->av_pkt.pts;

uint8_t* buf = (uint8_t*)av_malloc(buf_size);
if (!buf) {
    trace_error("Could not allocate buffer");
    // 释放已分配的字幕资源
    av_freep(&subtitle.rects[0]);
    av_freep(&subtitle.rects);
    return -1;
}

int encoded_size = avcodec_encode_subtitle(subtitleCtx, buf, buf_size, &subtitle);
if (encoded_size < 0) {
    char strE[256];
    av_strerror(encoded_size, strE, sizeof(strE));
    trace_error("Failed to encode subtitle with error: %s", strE);
    av_free(buf);
    av_freep(&subtitle.rects[0]);
    av_freep(&subtitle.rects);
    return -1;
}
else
{
    AVPacket* pkt = av_packet_alloc();
    if (av_packet_from_data(pkt, buf, encoded_size) < 0) {
        av_free(buf);
        av_packet_free(&pkt);
        av_freep(&subtitle.rects[0]);
        av_freep(&subtitle.rects);
        trace_error("Error setting packet data");
        return -1;
    }
    else
    {
        pkt->stream_index = subtitleStream->index;
        pkt->pts = av_rescale_q(subtitle.pts, subtitleCtx->time_base, subtitleStream->time_base);
        // 计算字幕显示时长对应的packet duration
        pkt->duration = av_rescale_q(subtitle.end_display_time - subtitle.start_display_time, 
                                    subtitleCtx->time_base, subtitleStream->time_base);
        pkt->dts = pkt->pts; // 字幕流通常dts与pts一致

        ret = av_interleaved_write_frame(av_fmt_ctx_out, pkt);
        if (ret != 0) {
            char strE[256];
            av_strerror(ret, strE, sizeof(strE));
            trace_error("av_interleaved_write_frame returns %d - %s", ret, strE);
        }
        // av_packet_from_data后缓冲区所有权转移给pkt,只需释放pkt
        av_packet_free(&pkt);
    }
}
// 释放subtitle相关资源
av_freep(&subtitle.rects[0]);
av_freep(&subtitle.rects);

额外注意事项

  • 所有动态分配的资源(如AVSubtitleRect、AVPacket、缓冲区)必须在使用完成后释放,避免内存泄漏。
  • MOV_TEXT对ASS格式的兼容性严格,对话行的字段顺序、分隔符不能出错,否则编码器会拒绝处理。
  • 确保subtitle.pts的时间基与subtitleCtx->time_base一致,避免时间戳转换错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 15:28:13