文章目录
- 前言
- 一、方案
- 二、编程批量调整同步
- 总结
前言
最近下载了10多个G的学习视频,结果一播放,发现声音和画面不同步,原本以为大脑能自动调整画面和和画面,勉强能看下去,结果一分钟不到崩溃了,因为声音比画面快了20秒😓;
原本想删了算了,但是下载了几个礼拜才下好,所以只能想想办法
一、方案
方案一:众所周知,声音的传播速度是 340m/s,找跟长的线,只要把音响放的足够远,理论上是能达到声音和画面同步;果然是学好数理化,走遍天下都不怕啊;实际体验效果我就不尝试了,哈哈🤣
方案二:如果是电脑,同时打开两个视频播放器,一个只听声音,一个静音只看画面;如果是手机,没办法同时打开两个视频播放器,那就只能两个手机了;亲测可用😎
方案三:之前接触过ffmpeg,专门处理音视频流的,查了下果然可以,相关命令如下:
// 音频相对于视频后移20秒(00:00:20),如果前移就是(-00:00:20)
ffmpeg -y -itsoffset 00:00:20 -i 源视频.mp4 -i 源视频.mp4 -map 0:a -map 1:v -vcodec copy -acodec copy -f mp4 -threads 2 -v warning 调整后的视频.mp4
// 用得到的参数:
// -y 可覆盖,如果文件已存在强制替换;
// -itsoffset offset 设置以秒为基准的时间偏移,该选项影响所有后面的输入文件。该偏移被加到输入文件的时戳,定义一个正偏移意味着相应的流被延迟了offset秒。 [-]hh:mm:ss[.xxx]的格式也支持
// -f fmt 强迫采用格式fmt
// -v 调试信息级别(quiet、panic、fatal、error、warning、info、verbose、debug)
// 选择媒体流,AVI,mkv,mp4等,可以包含不同种类的多个流
二、编程批量调整同步
每个视频都手动敲一行命令太麻烦了,写代码批量生成岂不快哉;废话不多说,直接上代码:
#include <string>
#include <iostream>
#include <vector>
#include <thread>
#include <windows.h>
#include <string.h>
#include <io.h>
#include <direct.h>
#include <shlwapi.h>
#pragma comment(lib,"shlwapi.lib")
using namespace std;
vector<string> g_vecVideoPaths;
#define ERR_NO_FIND -1
int FindFile(const char* pszPath)
{
int iRet = 0;
char szFindPath[MAX_PATH] = { 0 };
DWORD dwFileAttributes;
string strFileName;
WIN32_FIND_DATA wfd;
sprintf(szFindPath, "%s*.*", pszPath);
HANDLE hFindFile = ::FindFirstFile(szFindPath, &wfd);
if (hFindFile == INVALID_HANDLE_VALUE)
{
// 没有找到任何文件
return ERR_NO_FIND;
}
// 找到文件,开始遍历
strFileName = wfd.cFileName;
while (strFileName.size() > 0)
{
// 过滤 . 和 ..
if (strFileName != "." && strFileName != "..")
{
dwFileAttributes = wfd.dwFileAttributes;
if (dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY) // 目录
{
// 如果是目录,则继续递归查找
char szSubFindPath[MAX_PATH] = { 0 };
sprintf(szSubFindPath, "%s%s\\", pszPath, strFileName.c_str());
iRet = FindFile(szSubFindPath);
if (iRet != 0)
{
break;
}
}
else if (dwFileAttributes == FILE_ATTRIBUTE_ARCHIVE) // 文件
{
// 检查文件是否为指定的类型,测试只能判断mp4文件
//bool bRet = PathIsContentType(strFileName.c_str(), "video/mp4");
// 根据文件后缀判断文件是否是视频文件,我只用到*.mp4、*.mkv、*.rmvb
// 获取文件路径后缀
const char* pExtension = PathFindExtension(strFileName.c_str());
if(pExtension != nullptr &&
(stricmp(pExtension, ".mp4") == 0 || stricmp(pExtension, ".mkv") == 0 || stricmp(pExtension, ".rmvb") == 0))
{
// 视频文件找到了,插入video路径
g_vecVideoPaths.push_back(string(pszPath) + strFileName);
}
}
}
// 查找下一个文件
if (!::FindNextFile(hFindFile, &wfd))
{
break;
}
strFileName = wfd.cFileName;
}
::FindClose(hFindFile);
return iRet;
}
void GenerateVideo(const char* pPath)
{
if (g_vecVideoPaths.size() == 0)
{
cout << "查找视频资源失败" << endl;
return;
}
cout << "--------------------开始调整-------------------------" << endl;
int iAdjustTime = 0;
cout << "请输入要调整的时间(单位:秒),音频后移为正数,前移为负数:";
cin >> iAdjustTime;
char szSavePath[MAX_PATH] = { 0 };
char szFileName[MAX_PATH] = { 0 };
char szSaveFileName[MAX_PATH] = { 0 };
char szCommand[1024] = { 0 };
char szAdjustTime[32] = {0};
char* pPrefix = "";
if (iAdjustTime < 0)
{
pPrefix = "-";
iAdjustTime = -iAdjustTime;
}
int iHour = iAdjustTime / 3600;
int iMinute = (iAdjustTime % 3600) / 60;
int iSecond = iAdjustTime % 60;
sprintf(szAdjustTime, "%s%02d:%02d:%02d", pPrefix, iHour, iMinute, iSecond);
// 执行ffmpeg函数
auto funAdjust = [](string strCommand)
{
system(strCommand.c_str());
};
for (int i = 0; i < g_vecVideoPaths.size(); i++)
{
// 跳过空
if (g_vecVideoPaths[i].size() == 0)
{
continue;
}
memset(szSavePath, 0, sizeof(szSavePath));
strcpy(szSavePath, g_vecVideoPaths[i].c_str());
// 获得路径中的文件名
const char* pFileName = PathFindFileName(szSavePath);
// PathFindFileName返回的是指向传入参数szSavePath字符串中文件名所在的地址值,必须用其他变量保存
memset(szFileName, 0, sizeof(szFileName));
strcpy(szFileName, pFileName);
// 删除路径后面的文件名以及\\,可以得到一个文件的路径
PathRemoveFileSpec(szSavePath);
strcat(szSavePath, "\\output");
// 创建保存文件夹
if (_access(szSavePath, 0) == -1) // 如果文件夹不存在
{
_mkdir(szSavePath);
}
// 保存文件名
sprintf(szSaveFileName, "%s\\%s", szSavePath, szFileName);
// 组装命令
// ffmpeg -y -itsoffset 00:00:20 -i 源视频.mp4 -i 源视频.mp4 -map 0:a -map 1:v -vcodec copy -acodec copy -f mp4 -threads 2 -v warning 调整后的视频.mp4
sprintf(szCommand, "%sffmpeg.exe -y -itsoffset %s -i %s -i %s -map 0:a -map 1:v -vcodec copy -acodec copy -f mp4 -threads 2 -v warning %s",
pPath, szAdjustTime, g_vecVideoPaths[i].c_str(), g_vecVideoPaths[i].c_str(), szSaveFileName);
cout << szCommand << endl;
// 使用线程处理
std::thread t(funAdjust, szCommand);
t.join();
cout << i << " --- " << "输出:" << szSaveFileName << endl;
}
}
int main()
{
// 获取当前模块(exe)所在路径
char szModuleFileName[MAX_PATH] = { 0 };
::GetModuleFileName(NULL, szModuleFileName, MAX_PATH);
// 查找目录
char szFindPath[MAX_PATH] = { 0 };
strcpy(szFindPath, szModuleFileName);
char *pPos = strrchr(szFindPath, '\\');
if (pPos == NULL)
{
return -1;
}
// 截断文件名
pPos[1] = '\0';
FindFile(szFindPath);
GenerateVideo(szFindPath);
system("pause");
return 0;
}