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

news2024/9/22 23:36:48

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

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

就封装格式而言,目前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/181916.html

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

相关文章

数据中心Spine/Leaf+VXLAN的结构

大家过年好&#xff0c;我是技福的小咖老师。今天我们继续聊聊网络架构。 随着业务系统对IT基础设备灵活度要求的不断提升&#xff0c;云计算、大数据以及虚拟化等技术在新型数据中心的建设中发挥着重要作用。如何更好地满足数据中心计算资源灵活调配以及服务扩展&#xff0c;…

C语言块级变量

所谓代码块&#xff0c;就是由{ }包围起来的代码。代码块在C语言中随处可见&#xff0c;例如函数体、选择结构、循环结构等。不包含代码块的C语言程序根本不能运行&#xff0c;即使最简单的C语言程序&#xff08;上节已经进行了展示&#xff09;也要包含代码块。C语言允许在代码…

不就是Java吗之 认识异常

认识异常一、异常的概念与体系结构1.1 异常的概念1.2 异常的体系结构1.3 异常的分类1.3.1 编译时异常(受查异常)1.3.2 运行时异常(非受查异常)二、异常的处理2.1 防御型编程2.1.1 LBYL2.1.2 EAFP2.2 异常的抛出2.3 异常的捕获2.3.1 异常的声明2.3.2 try-catch捕获并处理2.3.3 f…

linux gui版本控制工具对比

linux gui版本控制工具对比qgitungitgitgsublime mergegitKrakengitAhead tkdiff之前一直用windows开发, 最近想用linux开发, 选版本控制工具的时候考察了以下几款可以在linux平台上使用的版本控制工具, 还是觉得tortoise好用. 记录下它们的优缺点. 想起以前一位同事说过的话,…

Elasticsearch:将数据从 Elasticsearch 和 Kibana 导出到 Pandas Dataframe

在这篇文章中&#xff0c;我们将看到如何从 Elasticsearch 索引和 Kibana 的 CSV 报告中导出数据 - post-url 到 pandas 数据帧。 数据的可视化可以在 Kibana 中完成&#xff0c;但如果你想对数据进行更精细的分析并创建更动态的可视化&#xff0c;将数据导出到 pandas datafra…

LeetCode 55. 跳跃游戏 45. 跳跃游戏 II 22. 括号生成 53. 最大子数组和

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 55. 跳跃游戏 一、力扣示例 二、解决办法 三、代码实现 45. 跳跃游戏 II 一、力扣示例 二、解决办法 三、代码实现 22. 括号生成 一、力扣示例 二、解决办法 三、代码实现 53. 最大子数组和 一、力扣示例 …

WebAssembly编译之(3)-WASM编译实战之C/C++导出asm.js及wasm库

引言 上一节我们介绍了Ubuntu下的WASM的编译环境快速搭建。这一节我们继续WASM编译相关的介绍——如何导出C/C编写的函数库 WASM 相关文档&#xff1a; WebAssembly编译之(1)-asm.js及WebAssembly原理介绍 WebAssembly编译之(2)-Ubuntu搭建WASM编译环境 单个C文件(*.cpp)的导出…

每日学术速递1.28

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 今天带来的arXiv上最新发表的3篇AI论文。 Subjects: cs.AI、cs.Cv 1.Revisiting Temporal Modeling for CLIP-based Image-to-Video Knowledge Transferring 标题&#xff1a;重新审视基于CLIP的图像-视…

计讯物联5G工业级路由器在智慧消防的功能解析

据悉&#xff0c;国务院安全生产委员会印发《“十四五”国家消防工作规划》&#xff08;以下简称《规划》&#xff09;&#xff0c;对“十四五”时期消防改革发展作出全面部署。《规划》提出&#xff0c;坚持防消一体、防救并重&#xff1b;加强改革创新&#xff0c;加快消防“…

11 Day : 编写操作系统中断程序,加快时钟

前言&#xff1a;昨天学习了中断&#xff0c;今天就废话不多说&#xff0c;直接编写程序吧 内容更新&#xff1a;之前有朋友说看不太懂我的代码写的是啥&#xff0c;能不能详细讲讲&#xff0c;所以本期开始我会详细讲解代码&#xff0c;也会同步更新之前的博客&#xff0c;大多…

java基础巩固-宇宙第一AiYWM:为了维持生计,做项目经验之~高速项目大数据及机器学习算法方面的思路总结~整起

原始项目可能主要的功能是接收下位机传送来的很多参数&#xff0c;然后将参数以不同形式表达出来&#xff0c;在此过程中会涉及到文件上传下载、excel表格导出…等&#xff0c;但是呢&#xff0c;这么多数据不玩一下岂不是太浪费。于是&#xff0c;额们决定这样来: 项目中有一个…

Metasploit工具使用(上)

Metasploit工具使用1.Metasploit简介1.1.Metasploit下载1.2.Metasploit框架结构1.2.1.框架路径1.2.2.框架内容介绍1.2.2.1.data目录文件1.2.2.2.modules目录文件1.2.2.3.scripts目录文件1.2.2.4.tools目录文件1.2.2.5.plugins目录文件1.3.Metasploit更新2.MSF中数据库设置2.1.数…

动态与静态函数库的的使用 和 区别 及 优缺点

这里写目录标题初识静态库与动态库静态函数库动态函数库初识静态库与动态库 静态函数库与动态函数库的使用中&#xff0c;有人也把他称为程序的静态链接及动态链接。 静态链接&#xff1a;指程序链接时使用静态库的链接方式&#xff0c;把所有需要的库函数加入&#xff08;拷贝…

Vue3商店后台管理系统设计文稿篇(七)

记录使用vscode构建Vue3商店后台管理系统&#xff0c;这是第七篇&#xff0c;主要记录系统登录页面的创建过程&#xff0c;包含完整vue登录页面代码&#xff1b;Vuex的相关知识以及具体的使用&#xff0c;对state中值得获取&#xff0c;修改&#xff0c;异步修改&#xff0c;分…

Gradle学习笔记之Hook生命周期

简介 Gradle生命周期中的hook&#xff08;钩子&#xff09;函数是由gradle自动回调的&#xff0c;可以用来帮助我们实现一些功能&#xff1a; Gradle在生命周期各个阶段都提供了用于回调的钩子函数: Gradle初始化阶段: 在settings.gradle执行完后&#xff0c;会回调Gradle对…

2022爱分析・智能客服厂商全景报告 | 爱分析报告

报告编委 张扬 爱分析联合创始人&首席分析师 文鸿伟 爱分析高级分析师 王鹏 爱分析分析师 目录 研究范围定义厂商全景地图市场分析与厂商评估入选厂商列表研究范围定义 研究范围 在数字化快速发展的大背景下&#xff0c;随着消费人群及其消费意识的转变&#xff0c;客户对…

亚马逊云科技凭借多年云业务经验,协同合作伙伴快速展开生态化创新

在过去的两周里&#xff0c;ChatGPT的热度居高不下&#xff0c;引发全网讨论。虽然AlphaGo这类AI产品也曾引起热议&#xff0c;但是在应用层面终究还是离用户太远了。而ChatGPT更像是「民用级」的产品&#xff0c;真正意义上让AI技术跨入广泛破圈应用时代。在当下&#xff0c;机…

大数据-Hive

第1章 Hive入门 1.1 什么是Hive 1&#xff09;Hive简介 Hive是由Facebook开源&#xff0c;基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张表&#xff0c;并提供类SQL查询功能。 2&#xff09;Hive本质 Hive是一个Hadoop客户端&#xff0c;用于…

springboot项目解决@ResponseBody注解返回xml格式数据而不是json格式的问题

目录 1.说明 2.解决 1.说明 一般情况下&#xff0c;RestController中的接口默认响应数据格式都是 json 格式的数据&#xff0c;但有时候使用某些依赖包&#xff0c;会影响ResponseBody的响应数据类型为xml格式&#xff0c; 例&#xff1a; 2.解决 但我们希望响应数据格式是…

使用腾讯云服务器+Nonebot2+go-cqhttp搭建QQ聊天机器人

文章目录一、查看conda版本二、查看系统版本三、配置go-cqhttp1.请切换至同一网络下扫码2.打包Docker镜像四、创建NoneBot环境安装脚手架一、查看conda版本 二、查看系统版本 uname -a arch getconf LONG_BIT三、配置go-cqhttp 下载go-cqhttp 这里有不同版本的cqhttp,并且对…