视频处理
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; }
}
|
还没有评论,来说两句吧...