ffmpeg为mkv封装格式的音视频文件添加字幕

news2024/12/28 18:31:25

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

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

就封装格式而言,目前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
};
#endif

class 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
};
#endif

CFfmpegAddAss::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多秒,故最后一段视频上,没字幕。
下面是字幕通道。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/181349.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ZYNQ IP核之RAM

随机存取存储器&#xff08;Random Access Memory&#xff0c;RAM&#xff09;&#xff0c;可以随时把数据写入任一指定地址的存储单元&#xff0c;也可以随时从任一指定地址中读出数据&#xff0c;其读写速度是由时钟频率决定的&#xff0c;主要用来存放程序及程序执行过程中产…

C++结构(OOP的基石)

写在前面 面向初学者撰写专栏&#xff0c;个人原创的学习C/C笔记&#xff08;干货&#xff09;所作源代码输出内容为中文&#xff0c;便于理解如有错误之处请各位读者指正请读者评论回复、参与投票&#xff0c;反馈给作者&#xff0c;我会获得持续更新笔记干货的动力。致粉丝&a…

vulnhub之VENOM:1

1.信息收集 输入arp-scan 192.168.239.0/24探测存活的IP 使用nmap对192.168.239.166进行扫描&#xff0c;发现IP端口21、80、139、443、445。 在浏览器上访问http://192.168.239.166/&#xff0c;查看源码发现类似md5加密。 解密md5可以得到为hostinger。 输入enum4linux …

Linux | 软件包管理器——yum的使用

文章目录一、什么是软件包二、有关软件使用的背景讲解1、如何去下一个软件呢&#xff1f;过程是怎样的&#xff1f;2、软件的商业利益分析3、Linux开源社区的生态链4、Linux中软件的分类和选择三、yum的使用1、说在前面【注意事项】2、如何查看软件包3、软件的安装和卸载四、yu…

WPF鼠标、键盘、拖拽事件、用行为封装事件

WPF鼠标、键盘、拖拽事件、用行为封装事件 本文主要介绍了WPF中常用的鼠标事件、键盘事件以及注意事项&#xff0c;同时使用一个案例讲解了拓展事件。除此之外&#xff0c;本文还讲述如何用行为&#xff08;Behavior&#xff09;来封装事件。 Windows中的事件通过消息机制来完…

新手利用Git上传项目代码到Gitee仓库

【本文发布于https://blog.csdn.net/Stack_/article/details/128770678&#xff0c;未经许可禁止转载&#xff0c;转载须注明出处】 一、安装git工具 【git下载】 【git插件以及插件汉化包下载】 官网下载很慢&#xff0c;已上传CSDN。跳转 或者百度云 链接&#xff1a;https:…

下拉列表支持多选

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>下拉列表支持多选</title> </head> <body bgcolor"antiquewhite"> <center> <…

【算法基础】二分查找算法

一、二分查找算法原理 1. 算法介绍 首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后…

github codespace进行npm start运行开发服的时候无法显示页面(白屏)

github codespace进行npm start运行开发服的时候无法显示页面解决方案 先说答案 必须要指定host为0.0.0.0&#xff0c;因为一般前端开发的时候都是本地访问的&#xff0c;所以npm start 默认启动一个开发服&#xff0c;监听127.0.0.1的请求&#xff0c;也就是localhost。但是…

《Spring揭秘》读书笔记 3:Spring MVC

22 迈向Spring MVC的旅程 【参考】Java Web开发历程。 1) Servlet独行天下的时代。 一个Servlet对应处理一个Web请求。Servlet什么都做。 2) 繁盛一时的JSP时代。 将Servlet中的视图渲染逻辑以独立的单元抽取出来&#xff0c;JSP成为Java Web应用程序事实上的模板化视图标准…

Golang 使用Gin创建Restful API

Golang 使用Gin创建Restful API 今天学习下Go语言如何集成Gin框架编写Restful Web API的基本操作。Gin框架简化了Go原生语言构建Web应用程序的复杂度&#xff0c;在今天的学习中&#xff0c;将学会使用Gin构建路由请求、数据检索、JSON响应封装等最简单的Web服务。 基本要求 …

将本地txt文件上传至mysql

文章目录单个文件导入1.创建数据库和数据表2.使用cmd各种情况说明如果报错方案一方案二方案三mysql重启命令重启&#xff1a;图形页面重启当txt文件中的数据量太多时&#xff0c;不再适合用sql语句插入&#xff0c;这里介绍讲本地txt文件导入到mysql中 单个文件导入 如图所示…

【Maven】私服

目录 1. 私服介绍 问题导入 1.1 介绍 1.2 Nexus安装与启动 1.3 私服资源操作流程分析 2. 私服仓库分类 问题导入 3. 资源上传与下载 问题导入 3.1 从私服中下载依赖 3.2 上传依赖到私服中 1. 私服介绍 问题导入 这里的私服和平时我们听的国服、体验服、欧服等等有…

《深入浅出计算机组成原理》学习笔记 Day12

数据通路&#xff08;上&#xff09;1. 指令周期&#xff08;Instruction Cycle&#xff09;2. 建立数据通路3. CPU 所需要的硬件电路4. 总结参考1. 指令周期&#xff08;Instruction Cycle&#xff09; 计算机每执行一条指令的过程&#xff0c;可以分为这几个步骤&#xff1a…

MySQL三大日志

MySQL三大日志包括&#xff1a;undolog&#xff0c;redo log&#xff0c;binlog&#xff0c;它们分别有以下作用&#xff1a; undolog&#xff1a;是Innodb存储引擎生成的日志。用于事务的回滚和MVCC&#xff0c;保证了事务的原子性。 redo log&#xff1a;是Innodb存储引擎生…

【汇编】深入浅出地讲解使用六条汇编指令解决大部分汇编代码

开发环境说明 本文采用的IDE为keil5&#xff0c;核心板为STM32F103ZE。 在keil中添加以下两条语句即可生成汇编代码&#xff1a; 在上图界面中添加下述两句代码即可生成汇编代码&#xff1a;fromelf --bin --outputtest.bin Objects\test.axffromelf --text -a -c --outputtest…

使用Facebook分析跟踪SEO进展

Facebook是一个强大的社交平台&#xff0c;在这里你可以获取关于你的业务或客户的各种信息。它为你提供了一个巨大的渠道来展示你在过去一年中对行业或公司做出的贡献&#xff0c;以及你如何改进和优化我们所做的事情。如果不知道如何利用 Facebook来获得更多关于 SEO工作的信息…

高级数据结构:线段树入门(c++实现 + 详解)

文章目录线段树概念区间最值问题辅助函数创建线段树查询更新懒惰修改&#xff08;查询&#xff09;问题tag标记设计改进的更新函数关于本节的全部源代码线段树概念 线段树是一种高级数据结构&#xff0c;与树状数组一样&#xff0c;被用来处理区间查询&#xff0c;修改问题&am…

DDOS渗透与攻防(一)之拒绝服务攻击概念介绍

DDOS渗透与攻防 前言 DOS(Denial of Service&#xff0c;拒绝服务攻击)&#xff0c;它的原理很简单&#xff0c;就是用我们手里的机器去给服务器发请求&#xff0c;如果我们手头的服务器各方面性能都比服务器的主机的性能好&#xff0c;那么当我们发送大量请求给服务器&#…

Day871.行锁 -MySQL实战

行锁 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于行锁的内容。 MySQL 的 行锁 是在引擎层由各个引擎自己实现的。 但并不是所有的引擎都支持行锁&#xff0c;比如 MyISAM 引擎就不支持行锁。 不支持行锁意味着并发控制只能使用表锁&#xff0c;对于这种引擎的…