现在好莱坞的电影,都是全球看,一个地区的人看电影时,电影屏幕上应该展示对应的本地区语言字幕。故电影画面在不同的地区,需要配置不同的语言字幕。故视频画面里面的字幕应该可以拆出来,不能像老版三国演义,每到经典处,展示出文字,如下所示:

这种文字是直接嵌入到视频画面,无法拆出来,这种字幕也叫内嵌字幕。

本文要讲的是内挂字幕,字幕在视频文件里面,但是是独立的通道,可以独立拆出来。当然,还有一种外挂字幕,是在视频文件外面,播放器播放时,可以选择本地的字幕文件。

就封装格式而言,目前mkv对字幕支持的最好,读者可以先准备下字幕文件,字幕文件,读者可以网上下载现有的,也可以自己制作,本文准备的字幕文件ts.ass的内容如下:

[Script Info]
Title: Untitled
ScriptType: v4.00+
PlayResX:1280
PlayResY:720
WrapStyle: 0
ScaledBorderAndShadow: yes[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 2,0:00:00.22,0:00:31.93,Default,,0,0,0,,就算身处 流逝的时光里
Dialogue: 0,0:00:32.02,0:00:36.18,Default,,0,0,0,,也只有倦怠 在原地打转不停
Dialogue: 0,0:00:36.19,0:00:38.85,Default,,0,0,0,,从我身边 渐行渐远的心
Dialogue: 0,0:00:39.03,0:00:43.15,Default,,0,0,0,,再也模糊不清 你明白吗
Dialogue: 0,0:00:43.20,0:00:45.67,Default,,0,0,0,,我的身体 已经动弹不得
Dialogue: 0,0:00:45.79,0:00:50.16,Default,,0,0,0,,在时间的狭缝里 随波逐流
Dialogue: 0,0:00:50.17,0:00:53.26,Default,,0,0,0,,周围的一切 都与我无关
Dialogue: 0,0:00:53.39,0:00:57.07,Default,,0,0,0,,我就是我 仅·此·而·已
Dialogue: 0,0:00:57.18,0:01:00.04,Default,,0,0,0,,我在做梦吗?什么都没在看
Dialogue: 0,0:01:00.18,0:01:03.56,Default,,0,0,0,,出口也是枉然 自怜自艾的废话
Dialogue: 0,0:01:03.66,0:01:07.08,Default,,0,0,0,,悲伤什么的 只会徒增疲倦啊
Dialogue: 0,0:01:07.21,0:01:10.52,Default,,0,0,0,,干脆就这样 在麻木中度日吧
Dialogue: 0,0:01:10.62,0:01:13.95,Default,,0,0,0,,就算被灌以 喧嚣的闲言碎语
Dialogue: 0,0:01:14.09,0:01:17.45,Default,,0,0,0,,我的心也已经 不再起一丝涟漪
Dialogue: 0,0:01:17.56,0:01:21.03,Default,,0,0,0,,如果我能够 驱使自己的话
Dialogue: 0,0:01:21.13,0:01:24.39,Default,,0,0,0,,就让这一切 被黑暗所吞没吧
Dialogue: 0,0:01:24.50,0:01:28.06,Default,,0,0,0,,这样的我 还有未来可言吗
Dialogue: 0,0:01:28.19,0:01:31.46,Default,,0,0,0,,这种世界 允许我的存在吗
Dialogue: 0,0:01:31.56,0:01:34.85,Default,,0,0,0,,此刻感到窒息吗?此刻觉得悲伤吗
Dialogue: 0,0:01:34.98,0:01:38.45,Default,,0,0,0,,就连自己的事 也根本搞不懂啊
Dialogue: 0,0:01:38.55,0:01:41.94,Default,,0,0,0,,就算走下去 也只是徒增疲倦
Dialogue: 0,0:01:42.06,0:01:45.32,Default,,0,0,0,,对他人的一切 完全无法理解
Dialogue: 0,0:01:45.42,0:01:48.64,Default,,0,0,0,,这样的我 如果还能改变
Dialogue: 0,0:01:48.78,0:01:52.14,Default,,0,0,0,,还能改变的话 可以化为空白吗
Dialogue: 0,0:02:06.79,0:02:09.33,Default,,0,0,0,,就算身处 流逝的时光里
Dialogue: 0,0:02:09.48,0:02:13.64,Default,,0,0,0,,也只有倦怠 在原地打转不停
Dialogue: 0,0:02:13.77,0:02:16.24,Default,,0,0,0,,从我身边 渐行渐远的心
Dialogue: 0,0:02:16.37,0:02:20.54,Default,,0,0,0,,再也模糊不清 你明白吗
Dialogue: 0,0:02:20.67,0:02:23.26,Default,,0,0,0,,我的身体 已经动弹不得
Dialogue: 0,0:02:23.36,0:02:27.67,Default,,0,0,0,,在时间的狭缝里 随波逐流
Dialogue: 0,0:02:27.68,0:02:31.19,Default,,0,0,0,,周围的一切 都与我无关
Dialogue: 0,0:02:31.28,0:02:34.31,Default,,0,0,0,,我就是我 仅·此·而·已
Dialogue: 0,0:02:34.49,0:02:37.50,Default,,0,0,0,,我在做梦吗?什么都没在看
Dialogue: 0,0:02:37.62,0:02:40.93,Default,,0,0,0,,出口也是枉然 自怜自艾的废话
Dialogue: 0,0:02:41.03,0:02:44.30,Default,,0,0,0,,悲伤什么的 只会徒增疲倦啊
Dialogue: 0,0:02:44.43,0:02:47.91,Default,,0,0,0,,干脆就这样 在麻木中度日吧
Dialogue: 0,0:02:47.99,0:02:51.61,Default,,0,0,0,,就算被灌以 喧嚣的闲言碎语
Dialogue: 0,0:02:51.72,0:02:54.73,Default,,0,0,0,,我的心也已经 不再起一丝涟漪
Dialogue: 0,0:02:54.82,0:02:58.30,Default,,0,0,0,,如果我能够 驱使自己的话
Dialogue: 0,0:02:58.39,0:03:02.04,Default,,0,0,0,,就让这一切 被黑暗所吞没吧
Dialogue: 0,0:03:02.05,0:03:05.39,Default,,0,0,0,,如果任我驱使 驱使自己的话
Dialogue: 0,0:03:05.47,0:03:08.92,Default,,0,0,0,,一切都会毁灭 一切都会毁灭啊
Dialogue: 0,0:03:09.03,0:03:12.36,Default,,0,0,0,,被悲伤笼罩 被悲伤笼罩的话
Dialogue: 0,0:03:12.47,0:03:15.74,Default,,0,0,0,,我的心还能够 化为空白吗
Dialogue: 0,0:03:15.85,0:03:19.25,Default,,0,0,0,,不论你的存在 还是我的存在
Dialogue: 0,0:03:19.35,0:03:22.67,Default,,0,0,0,,这一切的真实 我都一无所知
Dialogue: 0,0:03:22.79,0:03:26.18,Default,,0,0,0,,如果在此睁开 这沉重的双眼
Dialogue: 0,0:03:26.36,0:03:29.88,Default,,0,0,0,,一切都会毁灭 被黑暗所吞没

读者可以清晰的看到,哪段时间至哪段时间,界面需要展示的文字,比如最后一个Dialogue显示在03:26.36到0:03:29.88这段时间,界面应该展示一切都会毁灭 被黑暗所吞没。

可以通过如下的ffmpeg命令降此字幕内挂到视频文件中

ffmpeg -i TAEYEON-Weekend.mkv -i ts.ass -c copy output.mkv

下面用ffmpeg代码的方式展示如何实现。
首先,需要说明的是,字幕跟音频,视频一样,有自己的通道,有自己的time_base,其读取方法也是av_read_frame。这点跟内嵌字幕不一样,在一个视频中,添加内嵌文字,可以通过滤镜drawtext实现,有解码,滤镜运算,编码过程,很费时,内挂不一样,没有这三个费时的计算,故往视频文件中添加内挂字幕很快。

其次,本人通过两个队列m_vecMediaPacket和m_vecAssPacket来存储读取的packet,然后在一个线程里面按照写入时间顺序分别写入m_vecMediaPacket和m_vecAssPacket的数据。

std::deque<AVPacket *> m_vecMediaPacket;
std::deque<AVPacket *> m_vecAssPacket;

此处,本人在av_read_frame,得到AVPacket后,没有直接调用av_interleaved_write_frame写文件,最主要的原因是av_interleaved_write_frame里面会对AVPacket的时间(相对各自的AVStream)进行排序,若视频文件比较大,则可能里面需要分配的空间也越来越大,最终由于内存不足导致崩溃。
故本人将读取的音视频packet和字幕packet分别存入队列,然后按照音视频播放同步的原理,调用av_write_frame依次写入m_vecMediaPacket和m_vecAssPacket里面的内容。也就是音视频的packet和字幕的packet,由自己编码判断谁先写(代码中av_compare_ts部分),而不是交由av_interleaved_write_frame处理。

再次,本人讲解下代码的大致结构:
1.用avformat_open_input分别打开媒体文件和字幕文件
2.avformat_alloc_output_context2构建输出文件context后,用avformat_new_stream分别往里面添加媒体流和字幕流,代码如下:

int iStreamNum = m_pFormatCtx_MediaFile->nb_streams;
for (int i = 0; i < iStreamNum; i++)
{AVCodec* pCodecEncode_Media = (AVCodec *)avcodec_find_encoder(m_pFormatCtx_MediaFile->streams[i]->codecpar->codec_id);AVStream *pMediaStream = avformat_new_stream(m_pFormatCtx_Out, pCodecEncode_Media);if (!pCodecEncode_Media){break;}avcodec_parameters_copy(pMediaStream->codecpar, m_pFormatCtx_MediaFile->streams[i]->codecpar);pMediaStream->codecpar->codec_tag = 0;
}{AVCodec* pCodecEncode_Ass = (AVCodec *)avcodec_find_encoder(m_pFormatCtx_AssFile->streams[0]->codecpar->codec_id);AVStream *pAssStream = avformat_new_stream(m_pFormatCtx_Out, pCodecEncode_Ass);if (!pAssStream){break;}avcodec_parameters_copy(pAssStream->codecpar, m_pFormatCtx_AssFile->streams[0]->codecpar);pAssStream->codecpar->codec_tag = 0;
}

3.创建三个线程,如下所示:

m_hMediaFileReadThread = CreateThread(NULL, 0, MediaFileReadProc, this, 0, NULL);
m_hAssFileReadThread = CreateThread(NULL, 0, AssFileReadProc, this, 0, NULL);
m_hWriteThread = CreateThread(NULL, 0, WriteProc, this, 0, NULL);

前两个线程分别读取媒体流和字幕流,然后塞入队列,第三个线程读取两个队列中的数据,然后按照时间顺序写入packet。
这里说明下,对于字幕流而言,av_read_frame,本人调用到了48次,这48次,其实也是上面的ass文件中,Dialogue节点的数量。

最后,是代码,文件结构如下:

其中FfmpegMkvTest.cpp内容如下:

#include <iostream>
#include "FfmpegAddAss.h"int main()
{CFfmpegAddAss cFfmpegAddAss;std::string strMediaFile = "D:/learn/ffmpeg/FfmpegConvert/x64/Release/TAEYEON-Weekend.mkv";std::string strAssFile = "D:/learn/ffmpeg/FfmpegConvert/x64/Release/ts.ass";std::string strOutFile = "D:/learn/ffmpeg/FfmpegConvert/x64/Release/TAEYEON-Weekend_ass.mkv";cFfmpegAddAss.StartAddAss(strMediaFile, strAssFile, strOutFile);cFfmpegAddAss.WaitFinish();return 0;
}

其中WaitFinish函数在文件处理结束后,会返回。

FfmpegAddAss.h的内容如下:

#pragma once#include <string>
#include <Windows.h>
#include <deque>#define MAX_PACKET_NUM 200#ifdef  __cplusplus
extern "C"
{#endif
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavdevice/avdevice.h"
#include "libavutil/audio_fifo.h"
#include "libavutil/avutil.h"
#include "libavutil/fifo.h"
#include "libavutil/frame.h"
#include "libavutil/imgutils.h"#include "libavfilter/avfilter.h"
#include "libavfilter/buffersink.h"
#include "libavfilter/buffersrc.h"#ifdef __cplusplus
};
#endifclass CFfmpegAddAss
{public:CFfmpegAddAss();~CFfmpegAddAss();
public:int StartAddAss(std::string strMediaFile, std::string strAssFile, std::string strOutFile);void WaitFinish();
private:int OpenMediaFile(std::string strMediaFile);int OpenAssFile(std::string strAssFile);int OpenOutFile(std::string strOutFile);
private:static DWORD WINAPI MediaFileReadProc(LPVOID lpParam);void MediaFileRead();static DWORD WINAPI AssFileReadProc(LPVOID lpParam);void AssFileRead();static DWORD WINAPI WriteProc(LPVOID lpParam);void Write();
private:AVFormatContext *m_pFormatCtx_MediaFile = NULL;AVFormatContext *m_pFormatCtx_AssFile = NULL;AVFormatContext *m_pFormatCtx_Out = NULL;int m_iAssStreamIndex = -1;HANDLE m_hMediaFileReadThread = NULL;HANDLE m_hAssFileReadThread = NULL;HANDLE m_hWriteThread = NULL;std::deque<AVPacket *> m_vecMediaPacket;std::deque<AVPacket *> m_vecAssPacket;CRITICAL_SECTION m_csMediaSection;CRITICAL_SECTION m_csAssSection;bool m_bStart = false;
};

FfmpegAddAss.cpp内容如下:

#include "FfmpegAddAss.h"#ifdef    __cplusplus
extern "C"
{#endif#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avdevice.lib")
#pragma comment(lib, "avfilter.lib")
#pragma comment(lib, "postproc.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "swscale.lib")#ifdef __cplusplus
};
#endifCFfmpegAddAss::CFfmpegAddAss()
{InitializeCriticalSection(&m_csMediaSection);InitializeCriticalSection(&m_csAssSection);
}CFfmpegAddAss::~CFfmpegAddAss()
{DeleteCriticalSection(&m_csMediaSection);DeleteCriticalSection(&m_csAssSection);
}int CFfmpegAddAss::StartAddAss(std::string strMediaFile, std::string strAssFile, std::string strOutFile)
{int ret = 0;do{ret = OpenMediaFile(strMediaFile);if (ret < 0){break;}ret = OpenAssFile(strAssFile);if (ret < 0){break;}ret = OpenOutFile(strOutFile);if (ret < 0){break;}m_bStart = true;m_hMediaFileReadThread = CreateThread(NULL, 0, MediaFileReadProc, this, 0, NULL);m_hAssFileReadThread = CreateThread(NULL, 0, AssFileReadProc, this, 0, NULL);m_hWriteThread = CreateThread(NULL, 0, WriteProc, this, 0, NULL);} while (0);return ret;
}void CFfmpegAddAss::WaitFinish()
{DWORD dw = 0;for (int i = 0; i < 10000; i++){if (m_hMediaFileReadThread == NULL && m_hAssFileReadThread == NULL){break;}if (m_hMediaFileReadThread != NULL){dw = WaitForSingleObject(m_hMediaFileReadThread, 1000);if (dw == WAIT_OBJECT_0){CloseHandle(m_hMediaFileReadThread);m_hMediaFileReadThread = NULL;}}if (m_hAssFileReadThread != NULL){dw = WaitForSingleObject(m_hAssFileReadThread, 1000);if (dw == WAIT_OBJECT_0){CloseHandle(m_hAssFileReadThread);m_hAssFileReadThread = NULL;}}}while (m_vecMediaPacket.size() > 0 && m_vecAssPacket.size() > 0){Sleep(1000);}Sleep(1000);m_bStart = false;WaitForSingleObject(m_hWriteThread, INFINITE);CloseHandle(m_hWriteThread);m_hWriteThread = NULL;
}int CFfmpegAddAss::OpenMediaFile(std::string strMediaFile)
{int ret = -1;do{if ((ret = avformat_open_input(&m_pFormatCtx_MediaFile, strMediaFile.c_str(), 0, 0)) < 0) {break;}if ((ret = avformat_find_stream_info(m_pFormatCtx_MediaFile, 0)) < 0) {break;}ret = 0;} while (0);return ret;
}int CFfmpegAddAss::OpenAssFile(std::string strAssFile)
{int ret = -1;do{if ((ret = avformat_open_input(&m_pFormatCtx_AssFile, strAssFile.c_str(), 0, 0)) < 0) {break;}if ((ret = avformat_find_stream_info(m_pFormatCtx_AssFile, 0)) < 0) {break;}ret = 0;} while (0);return ret;
}int CFfmpegAddAss::OpenOutFile(std::string strOutFile)
{int iRet = -1;do{avformat_alloc_output_context2(&m_pFormatCtx_Out, NULL, NULL, strOutFile.c_str());int iStreamNum = m_pFormatCtx_MediaFile->nb_streams;for (int i = 0; i < iStreamNum; i++){AVCodec* pCodecEncode_Media = (AVCodec *)avcodec_find_encoder(m_pFormatCtx_MediaFile->streams[i]->codecpar->codec_id);AVStream *pMediaStream = avformat_new_stream(m_pFormatCtx_Out, pCodecEncode_Media);if (!pCodecEncode_Media){break;}avcodec_parameters_copy(pMediaStream->codecpar, m_pFormatCtx_MediaFile->streams[i]->codecpar);pMediaStream->codecpar->codec_tag = 0;}{AVCodec* pCodecEncode_Ass = (AVCodec *)avcodec_find_encoder(m_pFormatCtx_AssFile->streams[0]->codecpar->codec_id);AVStream *pAssStream = avformat_new_stream(m_pFormatCtx_Out, pCodecEncode_Ass);if (!pAssStream){break;}avcodec_parameters_copy(pAssStream->codecpar, m_pFormatCtx_AssFile->streams[0]->codecpar);pAssStream->codecpar->codec_tag = 0;}if (!(m_pFormatCtx_Out->oformat->flags & AVFMT_NOFILE)){if (avio_open(&m_pFormatCtx_Out->pb, strOutFile.c_str(), AVIO_FLAG_WRITE) < 0){break;}}if (avformat_write_header(m_pFormatCtx_Out, NULL) < 0){break;}m_iAssStreamIndex = iStreamNum;iRet = 0;} while (0);if (iRet != 0){if (m_pFormatCtx_Out != NULL){avformat_free_context(m_pFormatCtx_Out);m_pFormatCtx_Out = NULL;}}return iRet;
}DWORD WINAPI CFfmpegAddAss::MediaFileReadProc(LPVOID lpParam)
{CFfmpegAddAss *pFfmpegAddAss = (CFfmpegAddAss *)lpParam;if (pFfmpegAddAss != NULL){pFfmpegAddAss->MediaFileRead();}return 0;
}void CFfmpegAddAss::MediaFileRead()
{AVPacket packet = { 0 };int ret = 0;while (1){av_packet_unref(&packet);ret = av_read_frame(m_pFormatCtx_MediaFile, &packet);if (ret == AVERROR(EAGAIN)){continue;}else if (ret == AVERROR_EOF){break;}else if (ret < 0){break;}while (1){int iPacketNum = m_vecMediaPacket.size();if (iPacketNum >= MAX_PACKET_NUM){Sleep(10);continue;}else{AVPacket *pPacket = av_packet_clone(&packet);if (pPacket != NULL){EnterCriticalSection(&m_csMediaSection);m_vecMediaPacket.push_back(pPacket);LeaveCriticalSection(&m_csMediaSection);}}break;}}
}DWORD WINAPI CFfmpegAddAss::AssFileReadProc(LPVOID lpParam)
{CFfmpegAddAss *pFfmpegAddAss = (CFfmpegAddAss *)lpParam;if (pFfmpegAddAss != NULL){pFfmpegAddAss->AssFileRead();}return 0;
}void CFfmpegAddAss::AssFileRead()
{AVPacket packet = { 0 };int ret = 0;while (1){av_packet_unref(&packet);ret = av_read_frame(m_pFormatCtx_AssFile, &packet);if (ret == AVERROR(EAGAIN)){continue;}else if (ret == AVERROR_EOF){break;}else if (ret < 0){break;}while (1){int iPacketNum = m_vecAssPacket.size();if (iPacketNum >= MAX_PACKET_NUM){Sleep(10);continue;}else{AVPacket *pPacket = av_packet_clone(&packet);if (pPacket != NULL){EnterCriticalSection(&m_csAssSection);m_vecAssPacket.push_back(pPacket);LeaveCriticalSection(&m_csAssSection);}}break;}}
}DWORD WINAPI CFfmpegAddAss::WriteProc(LPVOID lpParam)
{CFfmpegAddAss *pFfmpegAddAss = (CFfmpegAddAss *)lpParam;if (pFfmpegAddAss != NULL){pFfmpegAddAss->Write();}return 0;
}void CFfmpegAddAss::Write()
{int ret = 0;int cur_pts_media = 0;int cur_pts_ass = 0;AVPacket packet = { 0 };int iPicCount = 0;int iMediaIndex = 0;while (m_bStart){if (av_compare_ts(cur_pts_media, m_pFormatCtx_Out->streams[iMediaIndex]->time_base,cur_pts_ass, m_pFormatCtx_Out->streams[m_iAssStreamIndex]->time_base) <= 0){int iPacketNum = m_vecMediaPacket.size();if (iPacketNum >= 1){AVPacket *pPacket = NULL;EnterCriticalSection(&m_csMediaSection);if (!m_vecMediaPacket.empty()){pPacket = m_vecMediaPacket.front();m_vecMediaPacket.pop_front();}LeaveCriticalSection(&m_csMediaSection);pPacket->pts = av_rescale_q_rnd(pPacket->pts, m_pFormatCtx_MediaFile->streams[pPacket->stream_index]->time_base, m_pFormatCtx_Out->streams[m_iAssStreamIndex]->time_base, AVRounding(1));pPacket->dts = av_rescale_q_rnd(pPacket->dts, m_pFormatCtx_MediaFile->streams[pPacket->stream_index]->time_base, m_pFormatCtx_Out->streams[m_iAssStreamIndex]->time_base, AVRounding(1));pPacket->duration = av_rescale_q_rnd(pPacket->duration, m_pFormatCtx_MediaFile->streams[pPacket->stream_index]->time_base, m_pFormatCtx_Out->streams[m_iAssStreamIndex]->time_base, AVRounding(1));cur_pts_media = pPacket->pts;iMediaIndex = pPacket->stream_index;ret = av_write_frame(m_pFormatCtx_Out, pPacket);av_packet_free(&pPacket);}else{Sleep(1);if (m_hMediaFileReadThread == NULL){break;}}}else{int iPacketNum = m_vecAssPacket.size();if (iPacketNum >= 1){AVPacket *pPacket = NULL;EnterCriticalSection(&m_csAssSection);if (!m_vecAssPacket.empty()){pPacket = m_vecAssPacket.front();m_vecAssPacket.pop_front();}LeaveCriticalSection(&m_csAssSection);pPacket->pts = av_rescale_q_rnd(pPacket->pts, m_pFormatCtx_AssFile->streams[0]->time_base, m_pFormatCtx_Out->streams[m_iAssStreamIndex]->time_base, AVRounding(1));pPacket->dts = av_rescale_q_rnd(pPacket->dts, m_pFormatCtx_AssFile->streams[0]->time_base, m_pFormatCtx_Out->streams[m_iAssStreamIndex]->time_base, AVRounding(1));pPacket->duration = av_rescale_q_rnd(pPacket->duration, m_pFormatCtx_AssFile->streams[0]->time_base, m_pFormatCtx_Out->streams[m_iAssStreamIndex]->time_base, AVRounding(1));pPacket->stream_index = m_iAssStreamIndex;cur_pts_ass = pPacket->pts;ret = av_write_frame(m_pFormatCtx_Out, pPacket);av_packet_free(&pPacket);}else{Sleep(1);if (m_hAssFileReadThread == NULL){break;}}}}while (m_hMediaFileReadThread != NULL || m_vecMediaPacket.size() >= 1){AVPacket *pPacket = NULL;EnterCriticalSection(&m_csMediaSection);if (!m_vecMediaPacket.empty()){pPacket = m_vecMediaPacket.front();m_vecMediaPacket.pop_front();}else{LeaveCriticalSection(&m_csMediaSection);continue;}LeaveCriticalSection(&m_csMediaSection);pPacket->pts = av_rescale_q_rnd(pPacket->pts, m_pFormatCtx_MediaFile->streams[pPacket->stream_index]->time_base, m_pFormatCtx_Out->streams[m_iAssStreamIndex]->time_base, AVRounding(1));pPacket->dts = av_rescale_q_rnd(pPacket->dts, m_pFormatCtx_MediaFile->streams[pPacket->stream_index]->time_base, m_pFormatCtx_Out->streams[m_iAssStreamIndex]->time_base, AVRounding(1));pPacket->duration = av_rescale_q_rnd(pPacket->duration, m_pFormatCtx_MediaFile->streams[pPacket->stream_index]->time_base, m_pFormatCtx_Out->streams[m_iAssStreamIndex]->time_base, AVRounding(1));cur_pts_media = pPacket->pts;iMediaIndex = pPacket->stream_index;ret = av_write_frame(m_pFormatCtx_Out, pPacket);av_packet_free(&pPacket);}while (m_hAssFileReadThread != NULL || m_vecAssPacket.size() >= 1){AVPacket *pPacket = NULL;EnterCriticalSection(&m_csAssSection);if (!m_vecAssPacket.empty()){pPacket = m_vecAssPacket.front();m_vecAssPacket.pop_front();}else{LeaveCriticalSection(&m_csAssSection);continue;}LeaveCriticalSection(&m_csAssSection);pPacket->pts = av_rescale_q_rnd(pPacket->pts, m_pFormatCtx_AssFile->streams[0]->time_base, m_pFormatCtx_Out->streams[m_iAssStreamIndex]->time_base, AVRounding(1));pPacket->dts = av_rescale_q_rnd(pPacket->dts, m_pFormatCtx_AssFile->streams[0]->time_base, m_pFormatCtx_Out->streams[m_iAssStreamIndex]->time_base, AVRounding(1));pPacket->duration = av_rescale_q_rnd(pPacket->duration, m_pFormatCtx_AssFile->streams[0]->time_base, m_pFormatCtx_Out->streams[m_iAssStreamIndex]->time_base, AVRounding(1));pPacket->stream_index = m_iAssStreamIndex;cur_pts_ass = pPacket->pts;ret = av_write_frame(m_pFormatCtx_Out, pPacket);av_packet_free(&pPacket);}Sleep(100);av_write_trailer(m_pFormatCtx_Out);avio_close(m_pFormatCtx_Out->pb);
}

运行效果如下:

本视频4分钟7秒,而ass文件只有3分30多秒,故最后一段视频上,没字幕。
下面是字幕通道。

ffmpeg为mkv封装格式的音视频文件添加内挂字幕相关推荐

  1. 如何把视频文件添加中英文对照字幕

    如何把视频文件添加中英文对照字幕 在这里用到了subtitle edit的软件,首先我们先安装好subtitle和vlc软件,见上一篇文章下载Subtitle edit和vlc 1,点击视频,打开视频 ...

  2. C++实现flv封装格式解析(音视频学习笔记三)

    这篇博文使用C++解析一个flv文件信息,对其中一些重要的信息进行log输出,对flv的数据封装格式信息不清楚的可以去看这篇博文-FLV 封装格式解析,里面详细说明了flv文件的结构信息.这篇博文参考 ...

  3. 07-----给音视频文件添加字幕流

    添加字幕流可以使用ACHTIME软件去添加. 一 下载ACHTIME软件 在下载之前我们需要注册一个账号.然后才能下载. http://arctime.cn/download.html 二 添加字幕流 ...

  4. 音视频基础之封装格式与音视频同步

    封装格式的概念 封装格式(也叫容器)就是将已经编码压缩好的视频流.音频流及字幕按照一定的方案放到一个文件中,便于播放软件播放. 一般来说,视频文件的后缀名就是它的封装格式. 封装的格式不一样,后缀名也 ...

  5. 前端教程:浏览器不兼容嵌入的音视频文件怎么办?

    虽然HTML5支持ogg.mpeg4和webm的视频格式以及ogg.mp3和wav的音频格式,但并不是所有的浏览器都支持这些格式,因此我们在嵌入视频音频文件格式时,就要考虑浏览器的兼容性问题.表1列举 ...

  6. 常见音视频文件的编码和封装格式详解

    常见的AVI.RMVB.MKV.ASF.WMV.MP4.3GP.FLV等⽂件其实只能算是⼀种封装标准. ⼀个完整的视频⽂件是由⾳频和视频2部分组成的.H264.Xvid等就是视频编码格式,MP3.AA ...

  7. ffmpeg音视频文件音视频流抽取,初步尝试人声分离

    文章目录 ffmpeg抽取音视频文件中的音频流 音频流类型 AAC与m4a的区别 AAC与mp3的区别 用ffmpeg查看视频的信息 用ffmpeg抽取AAC音频流 从AAC文件中获取音轨 音轨是什么 ...

  8. ffmpeg学习(11)音视频文件muxer(2)多输入混流

    在 ffmpeg学习(3)编码.解码的流程介绍 和 ffmpeg学习(9)音视频文件demuxer中介绍了媒体文件的解封装.本文记录Ffmpeg封装格式另一种处理与与demuxer相反方式–视音频复用 ...

  9. 音视频文件的容器格式和编码格式

    转自:http://blog.chinaunix.net/uid-25885064-id-3338166.html 音视频文件的容器格式和编码格式 视频和音频以视频文件格式的形式放在一个文件中,方便同 ...

最新文章

  1. iis服务器文件上传速度慢,windows 2008 R2 上传速度慢
  2. 百度搜索自动提示搜索相关内容----模拟实现
  3. AutoLayout框架之序言
  4. silverlight textblock 自动换行
  5. SAP Commerce Cloud 里的 Solr 架构简介
  6. pdf做成翻页电子书_软网推荐:文档秒变3D翻页电子书
  7. 苹果将30天无条件退还iPhone改为14天
  8. 关于计算机的CPU的发展历史,计算机CPU的全部发展历史
  9. 杨幂晒七月孕肚揭怀孕只胖baby不胖身材的女星
  10. 使用python的matplotlib(pyplot)画折线图和柱状图
  11. el-table根据数据某个属性不同,做斑马纹,设置表格行样式
  12. 51nod3241 小明和他的同学们
  13. Kubernetes安装系列之kubctl exec权限设定
  14. curl_exec函数
  15. “墨子号”实现无中继千公里量子保密通信
  16. Hadoop安装教程_单机/伪分布式配置_CentOS6.4/Hadoop2.6.0
  17. 程序学3DMax之自动展UV
  18. 不完美的青春才最美!
  19. 内推 | 字节提前批快开始了。。。。
  20. OSError: cannot identify image file 三种解决方法

热门文章

  1. 人工智能数学基础2:指数、方根及对数运算公式
  2. js9331模块板openwrt刷固件刷usb声卡插件
  3. spark 小demo
  4. HTML5+CSS网页设计作业——传统节日-春节(8页) 学生网站模板
  5. KEIL5安装与使用。
  6. AR!!!高通Vuforia-iOS-SDK 和官方Demo 集成到iOS 项目中所需要注意的几点.
  7. 这11个网络工程师必备实用软件,别说老杨藏私不告诉你
  8. 「数据架构」实体关系模型溯源
  9. geo 读取单细胞csv表达矩阵 单细胞 改列名 seurat
  10. Win10配置IIS与 C#/.net项目的发布与IIS部署