字幕简介

我们在观看电影或短视频时,一般在视频下发会出现字幕,有些视频还会配中英双语字幕。字幕可以帮助观看人更好的了解视频内容。字幕分为以下三种类型:
外挂字幕:独立的字幕文件,播放视频时把视频和字幕放入同一路径下,也可以在播放器中选择外挂字幕。这样在播放视频时就可以看到字幕内容。常见字幕格式srt、vtt、ass等。
软字幕:也叫内挂字幕、封装字幕、字幕流等。通过某种技术将外挂字幕与视频文件打包在一起成一个文件。视频文件也可以同时封装多个字幕文件,播放时通过播放器选择所需字幕或不显示字幕。在需要时,还可以将字幕分离出来,修改后再打包进去。
硬字幕:将字幕内容覆盖叠加到视频画面上。这种字幕与视频画面溶于一体,具有最佳兼容性,只要能播放视频就能显示字幕。缺点是字幕占据视频画面,无法隐藏,破坏原有视频内容。且不可编辑更改。

FFMPEG命令行添加字幕

添加软字幕

ffmpeg -i demo.mp4 -i ass=subtitle.ass -c copy output.mkv
#demo.mp4 原始视频
#subtitle.ass 字幕文件
#output.mkv 输出视频

添加硬字幕

ffmpeg -i demo.mp4 -vf ass=subtitle.ass output.mp4

字幕格式转换

ffmpeg -i src.srt out.vtt
ffmpeg -i src.srt out.ass

代码方式添加字幕

添加软字幕

QString m_videoPath;    //原始视频文件
QString m_srtPath;      //字幕文件
QString m_destPath;     //输出视频文件
AVFormatContext* vfmt = NULL;   //源
AVFormatContext* sfmt = NULL;   //字幕
AVFormatContext* ofmt = NULL;   //输出
void SoftSubtitle()
{av_register_all();  //初始化avcodec_register_all(); //注册编解码器int ret = 0;// 打开视频流if (avformat_open_input(&vfmt, m_videoPath.toStdString().c_str(), NULL, NULL) < 0) {printf("avformat_open_input failed");return;}//读取媒体文件的数据包以获取流信息  查看是否有流信息if (avformat_find_stream_info(vfmt, NULL) < 0) {printf("avformat_find_stream_info");releaseInternal();return;}//分配输出的AVFormatContextif ((avformat_alloc_output_context2(&ofmt, NULL, NULL, m_destPath.toStdString().c_str())) < 0) {printf("avformat_alloc_output_context2() failed");releaseInternal();return;}int in_video_index = -1, in_audio_index = -1;   //源文件视频/音频流索引int ou_video_index = -1, ou_audio_index = -1;for (int i = 0; i < vfmt->nb_streams; i++) {AVStream* stream = vfmt->streams[i];if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {in_video_index = i;AVStream* newstream = avformat_new_stream(ofmt, NULL);  //创建一个流对象avcodec_parameters_copy(newstream->codecpar, stream->codecpar); //把源文件流复制给输出文件流newstream->codecpar->codec_tag = 0;ou_video_index = newstream->index;}else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {AVStream* newstream = avformat_new_stream(ofmt, NULL);avcodec_parameters_copy(newstream->codecpar, stream->codecpar);newstream->codecpar->codec_tag = 0;in_audio_index = i;ou_audio_index = newstream->index;}}if (!(ofmt->oformat->flags & AVFMT_NOFILE)) {if (avio_open(&ofmt->pb, m_destPath.toStdString().c_str(), AVIO_FLAG_WRITE) < 0) {printf("avio_open failed");releaseInternal();return;}}// 打开字幕流/** 遇到问题:调用avformat_open_input()时提示"avformat_open_input failed -1094995529(Invalid data found when processing input)"*  分析原因:编译ffmpeg库是没有将对应的字幕解析器添加进去比如(ff_ass_demuxer,ff_ass_muxer)*  解决方案:添加对应的编译参数*/if ((ret = avformat_open_input(&sfmt, m_srtPath.toStdString().c_str(), NULL, NULL)) < 0) {char errorBuf[256] = { 0 };av_strerror(ret, errorBuf, sizeof(errorBuf));printf("avformat_open_input failed %d(%s)", ret, errorBuf);return;}if ((ret = avformat_find_stream_info(sfmt, NULL)) < 0) {char errorBuf[256] = { 0 };av_strerror(ret, errorBuf, sizeof(errorBuf));printf("avformat_find_stream_info %d(%s)", ret, errorBuf);releaseInternal();return;}//获取字幕文件字幕流索引int subTitleIndex = av_find_best_stream(sfmt, AVMEDIA_TYPE_SUBTITLE, -1, -1, NULL, 0);if (subTitleIndex < 0) {printf("not find subtitle stream 0");releaseInternal();return;}//创建一个输出字幕流 并把源字幕流复制给它AVStream* nstream = avformat_new_stream(ofmt, NULL);ret = avcodec_parameters_copy(nstream->codecpar, sfmt->streams[subTitleIndex]->codecpar);nstream->codecpar->codec_tag = 0;int ou_subtitle_index = nstream->index; //输出字幕流索引值//写头文件if (avformat_write_header(ofmt, NULL) < 0) {printf("avformat_write_header failed");releaseInternal();return;}//打印媒体信息  将 AVFormatContext 中的媒体信息转存到输出av_dump_format(ofmt, 0, m_destPath.toStdString().c_str(), 1);/** 遇到问题:封装后生成的mkv文件字幕无法显示,封装时提示"[matroska @ 0x10381c000] Starting new cluster due to timestamp"*  分析原因:通过和ffmpeg.c中源码进行比对,后发现mvk对字幕写入的顺序有要求*  解决方案:将字幕写入放到音视频之前*///创建AVPacketAVPacket* inpkt2 = av_packet_alloc();while (av_read_frame(sfmt, inpkt2) >= 0) {  //读一帧字幕数据AVStream* srcstream = sfmt->streams[0];AVStream* dststream = ofmt->streams[ou_subtitle_index];//用于将AVPacket中各种时间值从一种时间基转换为另一种时间基av_packet_rescale_ts(inpkt2, srcstream->time_base, dststream->time_base);   inpkt2->stream_index = ou_subtitle_index;inpkt2->pos = -1;printf("pts %d", inpkt2->pts);if (av_write_frame(ofmt, inpkt2) < 0) { //输出一帧字幕数据printf("subtitle av_write_frame failed");releaseInternal();av_packet_unref(inpkt2);return;}}//释放AVPacketav_packet_unref(inpkt2);AVPacket* inpkt = av_packet_alloc();while (av_read_frame(vfmt, inpkt) >= 0) {if (inpkt->stream_index == in_video_index) {    //读的是视频数据AVStream* srcstream = vfmt->streams[in_video_index];AVStream* dststream = ofmt->streams[ou_video_index];av_packet_rescale_ts(inpkt, srcstream->time_base, dststream->time_base);inpkt->stream_index = ou_video_index;printf("inpkt %d", inpkt->pts);if (av_write_frame(ofmt, inpkt) < 0) {  //写视频数据到printf("video av_write_frame failed");av_packet_unref(inpkt);releaseInternal();return;}}else if (inpkt->stream_index == in_audio_index) {   //读的是音频数据AVStream* srcstream = vfmt->streams[in_audio_index];AVStream* dststream = ofmt->streams[ou_audio_index];av_packet_rescale_ts(inpkt, srcstream->time_base, dststream->time_base);inpkt->stream_index = ou_audio_index;if (av_write_frame(ofmt, inpkt) < 0) {  //写视频数据到printf("audio av_write_frame failed");av_packet_unref(inpkt);releaseInternal();return;}}}av_packet_unref(inpkt);//字幕、视音频数据全部读写完毕//输出文件尾av_write_trailer(ofmt);if (vfmt) {avformat_close_input(&vfmt);avformat_free_context(vfmt);}if (sfmt) {avformat_close_input(&sfmt);avformat_free_context(sfmt);}if (ofmt) {avformat_close_input(&ofmt);avformat_free_context(ofmt);}
}

添加硬字幕

QString m_videoPath;
QString m_srtPath;
QString m_fontsConf;
QString m_destPath;
AVFormatContext* vfmt = NULL;   //源文件
AVFormatContext* sfmt = NULL;   //字幕文件
AVFormatContext* ofmt = NULL;   //输出文件
int in_video_index, in_audio_index;
int ou_video_index, ou_audio_index;
AVCodecContext* de_video_ctx = NULL;
AVCodecContext* en_video_ctx = NULL;
AVFrame* de_frame = NULL;
AVFilterGraph* graph = NULL;
AVFilterContext* src_filter_ctx = NULL;
AVFilterContext* sink_filter_ctx = NULL;//编码
void doEncodec(AVFrame* frame)
{int ret = avcodec_send_frame(en_video_ctx, frame);while (true) {AVPacket* pkt = av_packet_alloc();ret = avcodec_receive_packet(en_video_ctx, pkt);if (ret < 0) {av_packet_unref(pkt);break;}// 写入数据av_packet_rescale_ts(pkt, en_video_ctx->time_base, ofmt->streams[ou_video_index]->time_base);pkt->stream_index = ou_video_index;//printf("video pts %d(%s)", pkt->pts, av_ts2timestr(pkt->pts, &ofmt->streams[ou_video_index]->time_base));av_write_frame(ofmt, pkt);av_packet_unref(pkt);}
}
//解码
void doDecodec(AVPacket* pkt)
{if (!de_frame) {de_frame = av_frame_alloc();}int ret = avcodec_send_packet(de_video_ctx, pkt);while (true) {ret = avcodec_receive_frame(de_video_ctx, de_frame);if (ret == AVERROR_EOF) {// 说明已经没有数据了;清空//解码成功送入滤镜进行处理if ((ret = av_buffersrc_add_frame_flags(src_filter_ctx, NULL, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) {//printf("av_buffersrc_add_frame_flags failed");break;}break;}else if (ret < 0) {break;}//解码成功送入滤镜进行处理if ((ret = av_buffersrc_add_frame_flags(src_filter_ctx, de_frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) {//printf("av_buffersrc_add_frame_flags failed");break;}while (true) {AVFrame* enframe = av_frame_alloc();ret = av_buffersink_get_frame(sink_filter_ctx, enframe);    //从FilterGraph中取出一个AVFrameif (ret == AVERROR_EOF) {// 说明结束了//printf("avfilter endeof");// 清空编码器doEncodec(NULL);// 释放内存av_frame_unref(enframe);}else if (ret < 0) {// 释放内存av_frame_unref(enframe);break;}// 进行重新编码doEncodec(enframe);// 释放内存av_frame_unref(enframe);}}
}
//初始化过滤器
bool initFilterGraph(QString srtPath)
{graph = avfilter_graph_alloc();     //为FilterGraph分配内存int ret = 0;AVStream* stream = vfmt->streams[in_video_index]; //源视频解码器// 输入滤镜const AVFilter* src_filter = avfilter_get_by_name("buffer");char desc[400];sprintf(desc, "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", stream->codecpar->width, stream->codecpar->height, stream->codecpar->format, stream->time_base.num, stream->time_base.den, stream->codecpar->sample_aspect_ratio.num, stream->codecpar->sample_aspect_ratio.den);ret = avfilter_graph_create_filter(&src_filter_ctx, src_filter, "in", desc, NULL, graph);  //创建并向FilterGraph中添加一个Filterif (ret < 0) {//printf("init src filter failed");return false;}// 输出滤镜enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };AVBufferSinkParams* buffersink_params;buffersink_params = av_buffersink_params_alloc();buffersink_params->pixel_fmts = pix_fmts;const AVFilter* sink_filter = avfilter_get_by_name("buffersink");ret = avfilter_graph_create_filter(&sink_filter_ctx, sink_filter, "out", NULL, buffersink_params, graph);//av_free(buffersink_params);if (ret < 0) {char errorBuf[256] = { 0 };av_strerror(ret, errorBuf, sizeof(errorBuf));//printf("buffersink init failed");return false;}// 滤镜描述符char filter_des[400];//sprintf(filter_des, "drawbox=x=100:y=100:w=100:h=100:color=pink@0.5");    //加一个方框 可以运行//sprintf(filter_des, "drawtext=fontfile=arial.ttf:fontcolor=white:fontsize=30:text='Aispeech'");sprintf(filter_des, "subtitles=filename='123.srt'");        //只能使用当前路径的srt文件srtPath.replace(":", "\\:");//QString srtPath = QString("D\\:\\123.srt");//sprintf(filter_des, "subtitles=filename='%s'", srtPath.toStdString().c_str());        //只能存在当前路径AVFilterInOut* inputs = avfilter_inout_alloc();AVFilterInOut* ouputs = avfilter_inout_alloc();inputs->name = av_strdup("out");inputs->filter_ctx = sink_filter_ctx;inputs->next = NULL;inputs->pad_idx = 0;ouputs->name = av_strdup("in");ouputs->filter_ctx = src_filter_ctx;ouputs->next = NULL;ouputs->pad_idx = 0;ret = avfilter_graph_parse_ptr(graph, filter_des, &inputs, &ouputs, NULL);  //将一串通过字符串描述的Graph添加到FilterGraph中if (ret < 0) {char errorBuf[256] = { 0 };av_strerror(ret, errorBuf,sizeof(errorBuf));return false;}av_buffersink_set_frame_size(sink_filter_ctx, en_video_ctx->frame_size);// 初始化滤镜if (avfilter_graph_config(graph, NULL) < 0) {   //检查FilterGraph的配置//printf("avfilter_graph_config failed");return false;}avfilter_inout_free(&inputs);avfilter_inout_free(&ouputs);return true;
}
void HardSubtitle()
{av_register_all();avcodec_register_all();const char* ver = av_version_info();int ret = 0;// 打开视频流if (avformat_open_input(&vfmt, m_videoPath.toStdString().c_str(), NULL, NULL) < 0) {printf("avformat_open_input failed");return;}//判断文件是否有音视频流if (avformat_find_stream_info(vfmt, NULL) < 0) {printf("avformat_find_stream_info");releaseInternal();return;}//分配输出的AVFormatContextif ((ret = avformat_alloc_output_context2(&ofmt, NULL, NULL, m_destPath.toStdString().c_str())) < 0) {printf("avformat_alloc_output_context2 failed");return;}for (int i = 0; i < vfmt->nb_streams; i++) {AVStream* sstream = vfmt->streams[i];if (sstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {in_video_index = i;// 添加新的视频流AVStream* nstream = avformat_new_stream(ofmt, NULL);ou_video_index = nstream->index;// 由于视频需要添加字幕,所以需要重新编解码,但是编码信息和源文件中一样AVCodec* codec = avcodec_find_decoder(sstream->codecpar->codec_id);if (!codec) {printf("not surport codec!");releaseInternal();return;}de_video_ctx = avcodec_alloc_context3(codec);if (!de_video_ctx) {printf("avcodec_alloc_context3 failed");releaseInternal();return;}// 设置解码参数,从源文件拷贝avcodec_parameters_to_context(de_video_ctx, sstream->codecpar);// 初始化解码器上下文if (avcodec_open2(de_video_ctx, codec, NULL) < 0) {printf("avcodec_open2 failed");releaseInternal();return;}// 创建编码器AVCodec* encodec = avcodec_find_encoder(sstream->codecpar->codec_id);if (!encodec) {printf("not surport encodec!");releaseInternal();return;}en_video_ctx = avcodec_alloc_context3(encodec);if (!en_video_ctx) {printf("avcodec_alloc_context3 failed");releaseInternal();return;}// 设置编码相关参数/** 遇到问题:生成视频前面1秒钟是灰色的*  分析原因:直接从源视频流拷贝编码参数到新的编码上下文中(即通过avcodec_parameters_to_context(en_video_ctx, sstream->codecpar);)而部分重要编码参数(如帧率,时间基)并不在codecpar*  中,所以导致参数缺失*  解决方案:额外设置时间基和帧率参数*/avcodec_parameters_to_context(en_video_ctx, sstream->codecpar);// 设置帧率int fps = sstream->r_frame_rate.num;//en_video_ctx->framerate = (AVRational){ fps,1 };en_video_ctx->framerate.num = fps;en_video_ctx->framerate.den = 1;// 设置时间基;en_video_ctx->time_base = sstream->time_base;// I帧间隔,决定了压缩率en_video_ctx->gop_size = 12;if (ofmt->oformat->flags & AVFMT_GLOBALHEADER) {en_video_ctx->flags = AV_CODEC_FLAG_GLOBAL_HEADER;}// 初始化编码器上下文if (avcodec_open2(en_video_ctx, encodec, NULL) < 0) {printf("avcodec_open2 failed");releaseInternal();return;}// 设置视频流相关参数avcodec_parameters_from_context(nstream->codecpar, en_video_ctx);nstream->codecpar->codec_tag = 0;}else if (sstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {// 音频直接进行流拷贝in_audio_index = i;AVStream* nstream = avformat_new_stream(ofmt, NULL);avcodec_parameters_copy(nstream->codecpar, sstream->codecpar);ou_audio_index = nstream->index;nstream->codecpar->codec_tag = 0;}}if (in_video_index == -1) {printf("not has video stream");releaseInternal();return;}if (!(ofmt->flags & AVFMT_NOFILE)) {if (avio_open(&ofmt->pb, m_destPath.toStdString().c_str(), AVIO_FLAG_WRITE) < 0) {printf("avio_open() failed");releaseInternal();return;}}//打印媒体信息  将 AVFormatContext 中的媒体信息转存到输出av_dump_format(ofmt, -1, m_destPath.toStdString().c_str(), 1);// 写入头文件if (avformat_write_header(ofmt, NULL) < 0) {printf("avformat_write_header failed");releaseInternal();return;}// 初始化过滤器if (!initFilterGraph(m_srtPath)) {printf("");releaseInternal();return;}AVPacket* inpkt = av_packet_alloc();while (av_read_frame(vfmt, inpkt) >= 0) {if (inpkt->stream_index == in_video_index) {doDecodec(inpkt);}else if (inpkt->stream_index == in_audio_index) {// 进行时间基的转换av_packet_rescale_ts(inpkt, vfmt->streams[in_audio_index]->time_base, ofmt->streams[ou_audio_index]->time_base);inpkt->stream_index = ou_audio_index;//printf("audio pts %d(%s)", inpkt->pts, av_ts2timestr(inpkt->pts, &ofmt->streams[ou_audio_index]->time_base));av_write_frame(ofmt, inpkt);}}av_packet_unref(inpkt);printf("finish !");doDecodec(NULL);av_write_trailer(ofmt);if (vfmt) {avformat_close_input(&vfmt);avformat_free_context(vfmt);}if (sfmt) {avformat_close_input(&sfmt);avformat_free_context(sfmt);}if (ofmt) {avformat_close_input(&ofmt);avformat_free_context(ofmt);}releaseInternal();
}

注意:“:”在FFMPEG中有其他用途,因此在传入路径时使用相对路径或者加转义字符“\”
链接: 源码下载

通过FFMPEG给视频加字幕相关推荐

  1. mac自动给视频加字幕(ffmpeg,autosub)

    mac python3.8 自动给视频加字幕(ffmpeg,autosub实现) 安装ffmpeg 安装autosub 网课没字幕听起来不习惯,所以尝试给mp4视频文件加上字幕,内容参考了不同教程进行 ...

  2. 20221204使用ffmpeg给视频加硬字幕

    20221204使用ffmpeg给视频加硬字幕 2022/12/4 18:17 测试: 中文字幕的编码格式如果是ANSI格式的,肯定出错!^_ 可选UTF-8或者UNICODE格式. [Parsed_ ...

  3. ffmpeg用drawtext filter 给视频加字幕,代码实现

    简介:利用drawtext filter给视频加字幕,忽略音频,只处理一个视频. 流程:openinput->openout->readpacket->decode->push ...

  4. 给视频加字幕HTML代码,一键添加字幕的软件推荐,几分钟学会给短视频加字幕,自媒体人都在用...

    很多人觉得给短视频加字幕很辛苦? 今天给大家介绍几款给短视频添加字幕用到的软件. 1.字幕通: 字幕通是一款全新模式的智能视频翻译软件,将繁琐的视频字幕翻译制作工作最大程度的便捷化,成功实现从切分时间 ...

  5. html+视频添加字幕,给视频加滚动字幕,给视频加字幕制作mv 录制的视频配背景音乐...

    给视频添加滚动字幕方法其实很简单,像我们下载的电影,歌曲,用手机录制的视频都可以加字幕,或者滚动字幕,也可以加背景音乐或其它声音,给视频开头或结尾加一张图片或多张图片等等都是可以实现的,而这一切都是那 ...

  6. android视频添加字幕,视频加字幕手机app

    视频加字幕手机app是一款为用户朋友们打造的视频制作神器,这款视频加字幕手机能够帮助用户朋友们剪辑视频,同时它还支持很多的视频编辑功能,使用起来很简单,用户朋友们快来下载使用吧! 视频加字幕手机app ...

  7. html给字添加音频,如何给视频加字幕并与语音同步?方法用得好就是这么简单!...

    原标题:如何给视频加字幕并与语音同步?方法用得好就是这么简单! 大家好,今天要分享的内容是:如何用爱剪辑给视频加字幕并与语音同步. 在电影.电视剧和一些短视频中,字幕和语音都是同时出现同时消失的.那么 ...

  8. 新手给视频加字幕 包括制作字幕文件

    本人新手,一时头脑发热,想给自己录的视频加点字幕处理下什么的.感觉现在市场上相关软件很多,根据个人经验,觉得以下方式不错. 1.使用人人影视的时间机器 (TIME MACHINE)软件制作字幕文件,再 ...

  9. 什么软件可以给视频加字幕?这些软件值得收藏

    不知道是不是有小伙伴们和我一样,平时喜欢拍摄一些视频来记录自己的生活,在剪辑视频的时候,我们通常还会为视频加上一些字幕,这样可以方便视频的观看.不过现在可能还有一部分小伙伴没有给视频加过字幕或者是不知 ...

最新文章

  1. react中使用scss_我如何将CSS模块和SCSS集成到我的React应用程序中
  2. 百度CTO王海峰阐释AI融合创新,降低门槛,按下产业智能化加速键
  3. 解决minikube启动时若干问题
  4. Java基础:成员变量的继承与覆盖
  5. html二级导航栏随一级居中,html – 1.在css中链接不起作用2.如何垂直居中导航栏并在每个导航栏上添加填充...
  6. 编程珠玑 15.3生成文本
  7. 单E1光端机分类及技术指标详解
  8. 软件测试白皮书-等价类
  9. c语言字符串未初始化strcat,C语言中字符串常用函数strcat与strcpy的用法介绍
  10. 嵌入式IAP开发笔记之一:面向STM32的BootLoader程序
  11. SpringMVC @Transactional注解方式事务失效的解决办法
  12. JSP基础之 C标签中的 varStatues属性
  13. python 读取邮件内容_Python 如何提取邮件内容
  14. 初步认识lodash.js
  15. java8-谓词(predicate)
  16. C++ 鼠标乱动整人代码
  17. vfp报表打印到PDF文件中不用输入文件名
  18. 洛谷——P1375 小猫
  19. python程序下载腾讯企业邮箱附件_python批量下载邮件附件
  20. cf1102F. Elongated Matrix(状压dp)

热门文章

  1. 制作网页版简易计算器(Calculator)
  2. QT+OpenCV实现一个标注工具(图像处理、边缘检测)
  3. 老师不会玩网络,全国优秀也枉然
  4. SQL原生语句、SQL表关联修改数据、关联数据表修改数据
  5. 深度剖析apache 2.4web服务器(史上最全)
  6. JS学习辑录(4):push()、pop()、shift()、unshift() 方法整理
  7. 鲁滨逊的英灵与世长存
  8. 社保照片修饰软件_照片修饰:在Photoshop中消除热点
  9. 扒出一个Word神技巧:给所有图片自动编号
  10. 用超级弹弓把飞船射上月球