FFmpeg视频处理

1 篇文章 0 订阅

视频处理

 1、什么是视频编码

视频上传成功后需要对视频进行转码处理。

什么是视频编码?查阅百度百科如下:

详情参考 :百度百科-验证

首先我们要分清文件格式和编码格式:

文件格式:是指.mp4、.avi、.rmvb等 这些不同扩展名的视频文件的文件格式  ,视频文件的内容主要包括视频和音频,其文件格式是按照一 定的编码格式去编码,并且按照该文件所规定的封装格式将视频、音频、字幕等信息封装在一起,播放器会根据它们的封装格式去提取出编码,然后由播放器解码,最终播放音视频。

音视频编码格式:通过音视频的压缩技术,将视频格式转换成另一种视频格式,通过视频编码实现流媒体的传输。比如:一个.avi的视频文件原来的编码是a,通过编码后编码格式变为b,音频原来为c,通过编码后变为d。

音视频编码格式各类繁多,主要有几下几类:

1.1.MPEG系列

(由ISO[国际标准组织机构]下属的MPEG[运动图象专家组]开发 )视频编码方面主要是Mpeg1(vcd用的就是它)、Mpeg2(DVD使用)、Mpeg4(的DVDRIP使用的都是它的变种,如:divx,xvid等)、Mpeg4 AVC(正热门);音频编码方面主要是MPEG Audio Layer 1/2、MPEG Audio Layer 3(大名鼎鼎的mp3)、MPEG-2 AAC 、MPEG-4 AAC等等。注意:DVD音频没有采用Mpeg的。

1.1. H.26X系列

(由ITU[国际电传视讯联盟]主导,侧重网络传输,注意:只是视频编码)

包括H.261、H.262、H.263、H.263+、H.263++、H.264(就是MPEG4 AVC-合作的结晶)

目前最常用的编码标准是视频H.264,音频AAC。

提问:

H.264是编码格式还是文件格式?

mp4是编码格式还是文件格式?

 2.FFmpeg 的基本使用

我们将视频录制完成后,使用视频编码软件对视频进行编码,本项目 使用FFmpeg对视频进行编码 。

FFmpeg被许多开源项目采用,QQ影音、暴风影音、VLC等。

下载:FFmpeg Download FFmpeg

测试是否正常:cmd运行 ffmpeg -v

安装成功,作下简单测试

将一个.avi文件转成mp4、mp3、gif等。

比如我们将nacos.avi文件转成mp4,运行如下命令:

D:\soft\ffmpeg\ffmpeg.exe -i 1.avi 1.mp4

转码成功:

 

 

可以将ffmpeg.exe配置到环境变量path中,进入视频目录直接运行:ffmpeg.exe -i 1.avi 1.mp4

转成mp3:ffmpeg -i nacos.avi nacos.mp3

转成gif:ffmpeg -i nacos.avi nacos.gif

官方文档(英文):ffmpeg Documentation

3. 视频处理工具类

将课程资料目录中的util.zip解压,将解压出的工具类拷贝至base工程。

其中Mp4VideoUtil类是用于将视频转为mp4格式,是我们项目要使用的工具类。

下边看下这个类的代码,并进行测试。

我们要通过ffmpeg对视频转码,Java程序调用ffmpeg,使用java.lang.ProcessBuilder去完成,具体在Mp4VideoUtil类的63行,下边进行简单的测试,下边的代码运行本机安装的QQ软件。

3.1. java操作:

Java

//启动QQ
ProcessBuilder builder = new ProcessBuilder();
builder.command("D:\\QQ\\Bin\\QQScLauncher.exe");
//将标准输入流和错误输入流合并,通过标准输入流程读取信息
builder.redirectErrorStream(true);
Process p = builder.start();

对Mp4VideoUtil类需要学习使用方法,下边代码将一个avi视频转为mp4视频,如下:

Java

public static void main(String[] args) throws IOException {

        //转换文件
        //ffmpeg的路径
        String ffmpeg_path = "D:\\software\\ffmpeg\\ffmpeg.exe";//ffmpeg的安装位置
        //源avi视频的路径
        String video_path = "D:\\software\\Test\\xuecheng\\video\\11.avi";
        //转换后mp4文件的名称
        String mp4_name = "1.mp4";
        //转换后mp4文件的路径
        String mp4_path = "D:\\software\\Test\\xuecheng\\video\\1.mp4";
        //创建工具类对象
        Mp4VideoUtil videoUtil = new Mp4VideoUtil(ffmpeg_path, video_path, mp4_name, mp4_path);
        //开始视频转换,成功将返回success
        String s = videoUtil.generateMp4();
        System.out.println(s);
    }

执行main方法,最终在控制台输出 success 表示执行成功。

4. 任务类

视频采用并发处理,每个视频使用一个线程去处理,每次处理的视频数量不要超过cpu核心数。

所有视频处理完成结束本次执行,为防止代码异常出现无限期等待则添加超时设置,到达超时时间还没有处理完成仍结束任务。

定义任务类VideoTask 如下:

Java

package com.xuecheng.media.service.jobhander;

import com.xuecheng.base.utils.Mp4VideoUtil;
import com.xuecheng.media.model.po.MediaProcess;
import com.xuecheng.media.service.MediaFileProcessService;
import com.xuecheng.media.service.MediaFileService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


/**
 * 视频采用并发处理,每个视频使用一个线程去处理,每次处理的视频数量不要超过cpu核心数。
 * 所有视频处理完成结束本次执行,为防止代码异常出现无限期等待则添加超时设置,到达超时时间还没有处理完成仍结束任务。
 */
@Component
@Slf4j
public class VideoTask {

    @Autowired
    MediaFileProcessService mediaFileProcessService;

    @Autowired
    MediaFileService mediaFileService;

    @Value("${videoprocess.ffmpegpath}")
    String ffmpegpath;

    /**
     * 视频处理任务
     */
    @XxlJob("videoJobHander")
    public void videoJobHander() throws Exception {

        // 分片参数
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();

        //查询待处理任务,一次处理的任务数要和cup核心一样
        List<MediaProcess> mediaProcessList = mediaFileProcessService.getMediaProcessList(shardIndex, shardTotal, 2);

        if (mediaProcessList == null || mediaProcessList.size() <= 0) {
            log.debug("查询到的待处理的视频为0");
            return;
        }
        //要处理的任务数
        int size = mediaProcessList.size();

        //启动size个线程的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(size);

        //计数器
        CountDownLatch countDownLatch = new CountDownLatch(size);


        //遍历mediaProcessList,将处理任务加入线程池
        mediaProcessList.forEach(mediaProcess -> {
            threadPool.execute(() -> {
                //视频处理状态
                String status = mediaProcess.getStatus();
                //保证幂等性
                if ("2".equals(status)) {
                    log.debug("视频已经处理不用再次处理,视频信息:{}", mediaProcessList);
                    return;
                }
                //桶
                String bucket = mediaProcess.getBucket();
                //存储路径
                String filePath = mediaProcess.getFilePath();
                //原始视频的md5值
                String fileId = mediaProcess.getFileId();
                //原始文件名称
                String filename = mediaProcess.getFilename();

                //将要处理的文件下载到服务器上
                File originalFile = null;
                //处理结束的视频文件
                File mp4File = null;

                try {
                    originalFile = File.createTempFile("original", null);
                    mp4File = File.createTempFile("mp4", ".mp4");
                } catch (IOException e) {
                    log.error("处理视频前创建临时文件失败");
                    countDownLatch.countDown();//计数器减1
                    return;
                }

                try {
                    //将原始视频下载到本地
                    mediaFileService.downloadFileFromMinIO(originalFile, bucket, filePath);
                } catch (Exception e) {
                    log.error("下载源始文件过程出错:{},文件信息:{}", e.getMessage(), mediaProcess);
                    countDownLatch.countDown();//计数器减1
                    return;
                }

                //调用工具类将avi转成mp4

                //转换后mp4文件的名称
                String mp4_name = fileId + ".mp4";
                //转换后mp4文件的路径
                String mp4_path = mp4File.getAbsolutePath();
                //创建工具类对象
                Mp4VideoUtil videoUtil = new Mp4VideoUtil(ffmpegpath, originalFile.getAbsolutePath(), mp4_name, mp4_path);
                //开始视频转换,成功将返回success
                String result = videoUtil.generateMp4();
                String statusNew = "3";
                String url = null; // 最终访问路径
                if ("success".equals(result)) {
                    //转换成功
                    //上传到minion的路径
                    String objectName = getFilePath(fileId, ".mp4");
                    try {
                        //上传到minion
                        mediaFileService.addMediaFilesToMinIO(mp4_path, bucket, objectName);
                    } catch (Exception e) {
                        log.debug("上传文件出错:{}", e.getMessage());
                        countDownLatch.countDown();//计数器减1
                        return;
                    }
                    statusNew = "2"; //处理成功
                    url = "/" + bucket + "/" + objectName;
                }


                try {
                    //记录任务处理的结果
                    mediaFileProcessService.saveProcessFinishStatus(mediaProcess.getId(), statusNew, fileId, url, result);
                } catch (Exception e) {
                    log.debug("保存任务处理结果出错:{}", e.getMessage());
                    countDownLatch.countDown();//计数器减1
                    return;
                }
                //计数器减去1
                countDownLatch.countDown();
            });
        });

        //阻塞到任务执行完成,当countDownLatch计数器归零,这里的阻塞解除
        //等待,给一个充裕的超时时间,防止无限等待,到达超时时间还没有处理完成则结束任务
        countDownLatch.await(30, TimeUnit.MINUTES);


    }

    //生成路径
    private String getFilePath(String fileMd5, String fileExt) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + fileMd5 + fileExt;
    }


}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值