zoukankan      html  css  js  c++  java
  • FFmpeg进行视频帧提取&音频重采样-Process.waitFor()引发的阻塞超时

      由于产品需要对视频做一系列的解析操作,利用FFmpeg命令来完成视频的音频提取、第一帧提取作为封面图片、音频重采样、字幕压缩等功能;

      前一篇文章已经记录了FFmpeg在JAVA中的使用-音频提取&字幕压缩 ,就不再重复说明,今天简单介绍下如何实现第一帧提取、重采样,然后再分享下Process.waitFor()引发的阻塞问题。

      一、首先,分享两个命令:

    1. 视频第一帧提取:ffmpeg -i [videofile]  -vframes 1 -q:v 2 -f image2  [imagefile]
    2. 音频重采样:ffmpeg -i [audiofile]  -ar 16000  [audiofile]

      说明:

    • -vframes 表示提取的第几帧,获取第一桢则后面的值为1,如果后面的值大于1,那么最后的[imagefile]不能指定一个文件,不然会报错,如下

      

      指定了输出的文件名为“1.jpeg”,报错:不能从1.jpeg文件中获取第二帧的文件名,因为-vframes只要大于1,则会提取出每一帧的图片,建议使用如%03d.jpeg来作为文件名,那么它解析的结果便是001.jpeg,002.jpeg,...依次编号往后;

    • -q:v 2 q代表质量quality, v代表视频流,2是控制质量的参数;-f指定输出的格式是image2;
    • 除了使用-vframes来获取视频帧,还有使用-ss参数来获取,-ss后的时间参数是自行设定,并且在视频的有效时间内(格式为00:00:00),使用-ss时,如果没有使用%03d.jpeg来作为文件名,则获取的是-ss参数后那个时间的帧;
    • -ar表示使用新的采样率,常用的有8,000Hz、16,000Hz、32,000Hz、44,100Hz;

      具体的代码依然使用Process类实现,可以参照之前的文章FFmpeg在JAVA中的使用-音频提取&字幕压缩

      二、接下来,重点讲一下在使用process.waitFor()发生的阻塞问题

      先介绍一下,这个功能由于11月6日需要演示,所以花了11月3日和4日两天开发完成的,主要流程是添加视频-音频提取-第一帧提取-语音识别-字幕处理-中英翻译,开发完成测试后功能ok,于是开开心心过周末。

      11月5日,周日下午产品经理打电话告诉我,4点开始的视频都没法正确处理了,于是我让他把视频发给我(当时他说这个视频是通过别的视频压缩工具压缩过的)。

      我测试了一下,通过查看log,发现在提取第一帧的时候,卡住了。但是我再次测试以前测试成功的那些视频,都是很顺利地处理完成,于是猜测,视频在压缩的过程中损坏,因为一开始他发视频给我后,我并没有播放,直接测试了,后来我双击播放了一下,竟然全是黑屏,只有声音???

      那么,到现在可以确定,问题确实出现在视频的本身,无法正确提取第一帧。

      可是无法提取第一帧,不应该是提取图片为空吗?为什么卡在那边不动了呢?暂时只能建议产品经理周一演示的时候,不要使用这些压缩损坏的视频,等周一去了之后再看看问题出现在哪。

      11月6日周一上午,到了公司,回想昨天的问题,首先看一下内存使用率(这是公司内部的监控系统,是不是很棒!哈哈哈哈!)

      

      内存图可以展示两个情况:

      1. 11月5日下午4点开始,内存使用率逐步上升,说明那段时间测试了视频,线程一直处于堵塞状态;

      2. 查看了一下dump文件(jmap -dump:live,format=b,file=heap.dmp PID),发现有很多线程在extractVideoFirstFrame方法时阻塞;

        

      3. 11月6日10点半左右,重启了一下应用,内存立马就下来了,可以断定,这就是因为内存使用率上升的原因,如果一直这样下去,肯定会导致OOM。 

      三、最后,如何解决process.waitFor()发生的阻塞问题呢?

       一般需要调用系统命令时,使用Runtime.getRuntime().exec(command)返回一个process对象,再调用process.waitFor()来等待命令执行结束,获取执行结果。

      查看waitFor()源码可以发现,其实调用的是Object类中的,wait()方法,并且未指定等待时间,那么如果一直不返回,则会一直阻塞。

      并且查看了JDK的帮助文档,如下

      

      但是,我把执行提取视频第一帧的命令单独拿出来执行,虽然图片未提取成功,但是命令仍然执行结束了,按道理不应该一直堵塞啊???

      

      然后又重新看了一遍JDK的API,看到如下一段话:

      

      因此,可以得出结论:如果外部程序不断在向标准输出流(对于jvm来说就是输入流)标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitFor()这里。

      解决方法:

      1. 在waitFor()之前,利用单独两个线程,分别处理process的getInputStream()和getErrorSteam(),防止缓冲区被撑满,导致阻塞;
      2. 记得释放文件流资源,以免导致“Too many open files”的错误;

      代码如下:

        /**
         * 处理process输出流和错误流,防止进程阻塞
         * 在process.waitFor();前调用
         * @param process
         */
        private static void dealStream(Process process) {
            if (process == null) {
                return;
            }
            // 处理InputStream的线程
            new Thread() {
                @Override
                public void run() {
                    BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
                    String line = null;
                    try {
                        while ((line = in.readLine()) != null) {
                            logger.info("output: " + line);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            in.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
            // 处理ErrorStream的线程
            new Thread() {
                @Override
                public void run() {
                    BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                    String line = null;
                    try {
                        while ((line = err.readLine()) != null) {
                            logger.info("err: " + line);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            err.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }
  • 相关阅读:
    随笔35 内联函数
    随笔32 内部类,外部类,局部内部类
    随笔31 Spring的依赖注入的三种方式
    随笔30 抽象类与接口
    随笔29 Statement对象
    随笔28 Spring中的事务
    随笔27 面向对象的五大基本原则
    随笔26 java中的泛型
    html5学习笔记——HTML5 web存储
    html5学习笔记——HTML 5 视频
  • 原文地址:https://www.cnblogs.com/handsomeye/p/7792035.html
Copyright © 2011-2022 走看看