前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手帮你视频转文本(1-视频转音频)

手把手帮你视频转文本(1-视频转音频)

原创
作者头像
技术路漫漫
修改2020-06-11 10:14:15
2.2K0
修改2020-06-11 10:14:15
举报
文章被收录于专栏:技术路漫漫技术路漫漫

本系列将介绍如何一步步实现将mp4视频中的语音对话,自动转换为文本,并输出到word文档中。这里第一篇,先完成视频转音频处理。本项目全部代码也已经全部开源到码云(https://gitee.com/coolpine/thomas),可直接下载试用。

总体技术架构

下图是整体转换流程:

image-20200608233112583
image-20200608233112583
  1. 先将mp4视频文件,通过ffmpeg工具库,批量转换为pcm音频文件(语音识别服务仅支持该格式)
  2. 基于百度云的技术,将pcm文件上传到百度对象存储BOS中,并将日志等记录到本地mysql数据库。
  3. pcm文件上传完毕后,调用免费的语音识别(录音转写)服务,创建离线录音转写任务。
  4. 查询转写成功的任务,并将相关转写结果存储到本地mysql库中。
  5. 基于docx4j库,将数据库中的录音转写结果,导出为规范化的word文档。

转换结果示例

我们这里实现的是将 《托马斯和他的朋友们第18季》20集MP4视频,最终转换为一个word故事文档:

Image 1591634632
Image 1591634632

下面是第一集具体对话文本表格:

Image 1591634654
Image 1591634654

视频转音频

视频转音频基于ffmpeg库来实现。ffmpeg是一个强大的跨平台音视频记录、转换方案(官网说法:A complete, cross-platform solution to record, convert and stream audio and video)

ffmpeg主要是以命令行模式来实现音视频转换和处理,我们这里实现的功能有:

  • 将mp4文件中片头和片尾音乐剔除,截取中间片段。
  • 将截取后的mp4文件,转换为pcm文件。
  • 基于ffplay验证pcm可播放情况。

截取mp4文件中间片段的命令基本格式为:

代码语言:javascript
复制
ffmpeg -ss [start] -i [input] -t [duration] -c copy [output]
ffmpeg -ss [start] -i [input] -to [end] -c copy [output]
​
# 例如,以下是将t1801.mp4文件,截取从第30秒开始,截止到524秒,并保存为c1-1801.mp4文件:
ffmpeg -y -ss 30 -i t1801.mp4 -to 524 -c copy c1-1801.mp4

将mp4文件转换为pcm音频文件命令参数:

代码语言:javascript
复制
-i 输入文件
-an 去除音频流
-vn 去除视频流
-acodec 设置音频编码
-f 强制指定输入或输出文件的编码
-ac 设置音频轨道数
-ar 设置音频采用频率
-y 不经过确认,直接覆盖同名文件
​
# 例如,以下是将t1801.mp4文件,去除视频流并用pcm_s16le进行音频编码,输出文件也采用s16le编码,同时音轨为1且采样频率为16000:
ffmpeg -i t1801.mp4 -vn -acodec pcm_s16le -f s16le -ac 1 -ar 16000 t1801.pcm

用ffplay播放pcm文件:

代码语言:javascript
复制
ffplay -ar 16000 -ac 1 -f s16le -i t1801.pcm

更多ffmpeg命令使用,参见官方文档:https://ffmpeg.org/ffmpeg.html

Java音视频处理

以上只是验证了在命令行模式下,基于ffmpeg进行基本音视频操作。因为要进行批量处理,我们还需要用编程的方式来调用ffmpeg:

  1. 基于org.bytedeco的ffmpeg和ffmpeg-platform来实现用java调用ffmpeg。
  2. 因为每集视频的片头和片尾歌曲时长基本固定,但每集视频总时长不一样,通过org.mp4parser的isoparser库实现读取每集总时长,动态拼装转换命令。

以下是引入的基本依赖:

代码语言:javascript
复制
<!--实现对视频文件读取-->
<dependency>
    <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文件的总时长(秒数):

代码语言:javascript
复制
public long readDuration(Path mp4Path) {
    if (Files.notExists(mp4Path) || !Files.isReadable(mp4Path)) {
        log.warn("文件路径不存在或不可读 {}", mp4Path);
        return 0;
    }
    try {
        IsoFile isoFile = new IsoFile(mp4Path.toFile());
        long duration = isoFile.getMovieBox().getMovieHeaderBox().getDuration();
        long timescale = isoFile.getMovieBox().getMovieHeaderBox().getTimescale();
        return duration / timescale;
    } catch (IOException e) {
        log.error("读取MP4文件时长出错", e);
        return 0;
    }
}

以下是将MP4文件进行截取,并转换为PCM文件:

代码语言:javascript
复制
/**
 * 将单个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 Optional.empty();
    }
    String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);
    String endTime = String.valueOf(seconds - 100 - 30);
    File src = mp4Path.toFile();
    //在当前源mp4文件目录下生成临时文件
    String mp4TempFile = src.getParent() + "\\" + System.currentTimeMillis() + ".mp4";
    //基于ffmpeg进行截取
    ProcessBuilder cutBuilder = new ProcessBuilder(ffmpeg, "-ss", "30", "-i", mp4Path.toAbsolutePath().toString(),
            "-to", endTime, "-c", "copy", mp4TempFile);
    try {
        cutBuilder.inheritIO().start().waitFor();
    } catch (InterruptedException | IOException e) {
        log.error("ffmpeg截取MP4文件出错", e);
        return Optional.empty();
    }
    // 基于ffmpeg进行pcm转换
    // 基于输入路径的md5值来命名,也可以基于系统时间戳来命名
    String pcmFile = pcmDir.resolve(DigestUtils.md5Hex(mp4Path.toString()) + ".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设置为相同
        pcmBuilder.inheritIO().start().waitFor();
    } catch (InterruptedException | IOException e) {
        log.error("ffmpeg将mp4转换为pcm时出错", e);
        return Optional.empty();
    }
    // 删除MP4临时文件
    try {
        Files.deleteIfExists(Paths.get(mp4TempFile));
    } catch (IOException e) {
        log.error("删除mp4临时文件出错", e);
    }
    //返回pcm文件路径
    return Optional.of(pcmFile);
}

调用上述单个文件的处理方法,实现批量文件处理和转换:

代码语言:javascript
复制
/**
 * 批量将MP4文件转换为PCM文件
 *
 * @param rootDir
 * @param pcmDir
 * @return 成功转换的PCM文件数
 */
public int batchConvertMP4toPCM(Path rootDir, Path pcmDir) {
    if (Files.notExists(rootDir) || !Files.isDirectory(rootDir)) {
        log.warn("mp4文件目录{}不存在", rootDir);
        return 0;
    }
​
    if (Files.notExists(pcmDir)) {
        //级联创建目录
        try {
            Files.createDirectories(pcmDir);
        } catch (IOException e) {
            log.error("创建文件夹出错", e);
        }
    }
    AtomicInteger pcmCount = new AtomicInteger(0);
    //遍历rootdir,获取所有目录下子目录和文件
    try {
        Files.list(rootDir).forEach(path -> {
            if (Files.isDirectory(path)) {
                //递归遍历下级目录
                pcmCount.getAndAdd(batchConvertMP4toPCM(path, pcmDir));
            }
            if (Files.isRegularFile(path) && Files.isReadable(path) && path.getFileName()
                    .toString()
                    .endsWith("mp4")) {
                Optional<String> pcmFile = this.convertMP4toPCM(path, pcmDir);
                if (pcmFile.isPresent()) {
                    pcmCount.getAndIncrement();
                }
            }
        });
    } catch (IOException e) {
        log.error("批量将MP4文件转换为PCM文件出错", e);
    }
​
    return pcmCount.get();
}

单个文件转换调用测试:

代码语言:javascript
复制
@Test
void cutTest() {
    String file = "D:\\dev2\\project\\thomas\\local\\videos\\t1801.mp4";
    String pcmdir = "D:\\dev2\\project\\thomas\\local\\videos\\pcm";
    Path path = Paths.get(file);
    util.convertMP4toPCM(path, Paths.get(pcmdir));
}

批量文件转换测试:

代码语言:javascript
复制
@Test
void batchTest() {
    Path root = Paths.get("D:\\dev2\\project\\thomas\\local\\videos\\第18季");
    Path pcmDir = Paths.get("D:\\dev2\\project\\thomas\\local\\videos\\pcm");
    int pcmFiles = util.batchConvertMP4toPCM(root, pcmDir);
    log.info("转换出PCM文件数{}", pcmFiles);
}

至此,读取mp4文件,转换为pcm文件并剔除片头和片尾,就基本完成了,接下来将为你介绍如何基于百度云SDK和API实现语音转录。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总体技术架构
  • 转换结果示例
  • 视频转音频
  • Java音视频处理
相关产品与服务
语音识别
腾讯云语音识别(Automatic Speech Recognition,ASR)是将语音转化成文字的PaaS产品,为企业提供精准而极具性价比的识别服务。被微信、王者荣耀、腾讯视频等大量业务使用,适用于录音质检、会议实时转写、语音输入法等多个场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档