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
相关产品推荐
相关产品推荐

