本系列介绍了如何逐步实现MP4视频的语音对话,自动转换为文本,并输出到word文档中。这里的第一篇首先完成视频-音频处理。本项目的所有代码也全部公开在代码云上,可以直接下载试用版。
总体技术架构
下图是整个转换过程。
首先,MP4视频文件通过ffmpeg工具库大量转换为基于PCM音频文件(语音识别服务仅支持此格式)的基于云的技术,将PCM文件上传到百度对象存储BOS,并将日志等内容写入本地MySQL数据库。Pcm文件上载完成后,调用免费语音识别(录音转换)服务来创建离线录音传输作业。查询成功转换的作业,并将相关写入结果保存到本地MySQL库中。基于Docx4j库,将数据库的录制结果导出到标准化的word文档中。
转换结果示例
我们将《托马斯和他的朋友们第18季》 20次MP4视频转换为word故事文档。
以下是第一集具体对话文本表。
视频转音频
视频音频传输基于ffmpeg库。Ffmpeg是一个功能强大的跨平台音频和视频录制和转换程序(官网:a complete、cross-platform solution to record、convert and stream audio and video)
Ffmpeg主要以命令行模式进行音频和视频转换和处理。此处实现的功能包括:
从Mp4文件中删除片头和片尾音乐,并修剪中间片段。将剪裁的MP4文件转换为PCM文件。根据Ffplay检查PCM是否可播放。
用于截断Mp4文件中间部分的命令的默认格式如下:
FFM peg-ss[start]-I[input]-t[duration]-c copy[output]
FFM peg-ss[start]-I[input]-to[end]-c copy[output]
#例如,以下是将30秒至524秒剪切的文件另存为c1-1801.mp4文件。
Ffmpeg-y-ss30-I-to 524-c复制C1-1801.mp4
将Mp4文件转换为PCM音频文件命令参数:
-i输入文件
-移除an音频流
-删除vn视频流
-acodec音频编码设置
-f强制指定输入或输出文件的编码
-设置交流音频轨道数
-设置ar音频采用频率
-y不确认,直接复盖同名文件
#例如,以下是删除视频流并将音频编码为pcm_s16le的文件。输出文件也编码为s16le,轨迹为1,采样频率为16000。
FFM peg-I-VN-acodecpcm _ s 16le-fs 16le-AC 1-ar 16000
使用Ffplay播放PCM文件:
Ffplay -ar 16000 -ac 1 -f s16le -i使用更多ffmpeg命令,请参阅正式文档:
Java音视频处理
以上只是在命令行模式下基于ffmpeg验证基本音频和视频操作。需要以编程方式调用ffmpeg进行批量处理。
基于Org.bytedeco的ffmpeg和ffmpeg-platform使用Java实现ffmpeg调用。虽然每集视频的片头和片尾曲长度基本是固定的,但由于每集视频的总时间不同,可以通过org.mp4parser的isoparser库读取每集的总时间,并动态执行装配转换命令。引进的基本相依性如下:
!-实施视频文件读取-
Dependency
gt; <groupId>org.mp4parser</groupId> <artifactId>isoparser</artifactId> <version>1.9.41</version> </dependency> <!--实现对ffmpeg的操作--> <dependency> <groupId>org.bytedeco</groupId> <artifactId>ffmpeg</artifactId> <version>4.2.2-1.5.3</version> </dependency> <dependency> <groupId>org.bytedeco</groupId> <artifactId>ffmpeg-platform</artifactId> <version>4.2.2-1.5.3</version> </dependency>以下是基于isoparser,读取MP4文件的总时长(秒数):
public long readDuration(Path mp4Path) {
if (mp4Path) || !Files.isReadable(mp4Path)) {
log.warn("文件路径不存在或不可读 {}", mp4Path);
return 0;
}
try {
IsoFile isoFile = new IsoFile());
long duration = i().getMovieHeaderBox().getDuration();
long timescale = i().getMovieHeaderBox().getTimescale();
return duration / timescale;
} catch (IOException e) {
log.error("读取MP4文件时长出错", e);
return 0;
}
}
以下是将MP4文件进行截取,并转换为PCM文件:
/**
* 将单个PM4文件进行片头和片尾歌曲删除后,转换为PCM文件
*
* @param mp4Path
* @param pcmDir
* @return 转换完成后的pcm文件路径
*/
public Optional<String> convertMP4toPCM(Path mp4Path, Path pcmDir) {
long seconds = readDuration(mp4Path);
if (seconds == 0) {
log.warn("文件总时长为0");
return O();
}
String ffmpeg = Loader.load);
String endTime = S(seconds - 100 - 30);
File src = m();
//在当前源mp4文件目录下生成临时文件
String mp4TempFile = () + "\\" + Sy() + ".mp4";
//基于ffmpeg进行截取
ProcessBuilder cutBuilder = new ProcessBuilder(ffmpeg, "-ss", "30", "-i", m().toString(),
"-to", endTime, "-c", "copy", mp4TempFile);
try {
cu().start().waitFor();
} catch (InterruptedException | IOException e) {
log.error("ffmpeg截取MP4文件出错", e);
return O();
}
// 基于ffmpeg进行pcm转换
// 基于输入路径的md5值来命名,也可以基于系统时间戳来命名
String pcmFile = ()) + ".pcm").toString();
ProcessBuilder pcmBuilder = new ProcessBuilder(ffmpeg, "-y", "-i", mp4TempFile, "-vn", "-acodec", "pcm_s16le",
"-f", "s16le", "-ac", "1", "-ar", "16000", pcmFile);
try {
//inheritIO是指将 子流程的IO与当前java流程的IO设置为相同
().start().waitFor();
} catch (InterruptedException | IOException e) {
log.error("ffmpeg将mp4转换为pcm时出错", e);
return O();
}
// 删除MP4临时文件
try {
Files.deleteIfExi(mp4TempFile));
} catch (IOException e) {
log.error("删除mp4临时文件出错", e);
}
//返回pcm文件路径
return O(pcmFile);
}
调用上述单个文件的处理方法,实现批量文件处理和转换:
/**
* 批量将MP4文件转换为PCM文件
*
* @param rootDir
* @param pcmDir
* @return 成功转换的PCM文件数
*/
public int batchConvertMP4toPCM(Path rootDir, Path pcmDir) {
if (rootDir) || !Files.isDirectory(rootDir)) {
log.warn("mp4文件目录{}不存在", rootDir);
return 0;
}
if (pcmDir)) {
//级联创建目录
try {
Files.createDirectories(pcmDir);
} catch (IOException e) {
log.error("创建文件夹出错", e);
}
}
AtomicInteger pcmCount = new AtomicInteger(0);
//遍历rootdir,获取所有目录下子目录和文件
try {
Files.list(rootDir).forEach(path -> {
if (path)) {
//递归遍历下级目录
(batchConvertMP4toPCM(path, pcmDir));
}
if (path) && Files.isReadable(path) && ()
.toString()
.endsWith("mp4")) {
Optional<String> pcmFile = (path, pcmDir);
if ()) {
();
}
}
});
} catch (IOException e) {
log.error("批量将MP4文件转换为PCM文件出错", e);
}
return ();
}
单个文件转换调用测试:
@Test
void cutTest() {
String file = "D:\\dev2\\project\\thomas\\local\\videos\\";
String pcmdir = "D:\\dev2\\project\\thomas\\local\\videos\\pcm";
Path path = Pa(file);
u(path, Pa(pcmdir));
}
批量文件转换测试:
@Test
void batchTest() {
Path root = Pa("D:\\dev2\\project\\thomas\\local\\videos\\第18季");
Path pcmDir = Pa("D:\\dev2\\project\\thomas\\local\\videos\\pcm");
int pcmFiles = u(root, pcmDir);
log.info("转换出PCM文件数{}", pcmFiles);
}
至此,读取mp4文件,转换为pcm文件并剔除片头和片尾,就基本完成了,接下来将为你介绍如何基于百度云SDK和API实现语音转录。