Java使用FFmpeg处理视频文件的方法教程
前言
本文主要讲述如何使用Java+FFmpeg实现对视频文件的信息提取、码率压缩、分辨率转换等功能;
之前在网上浏览了一大圈Java使用FFmpeg处理音视频的文章,大多都讲的比较简单,楼主在实操过程中踩了很多坑也填了很多坑,希望这份详细的踩坑&填坑指南能帮助到大家;
1.什么是FFmpeg
点我了解
2.开发前准备
在使用Java调用FFmpeg处理音视频之前,需要先安装FFmpeg,安装方法分为两种:
- 引入封装了FFmpeg的开源框架
- 在系统中手动安装FFmpeg
2.1引入封装了FFmpeg的开源框架
JAVE.jar(官网点我)是一个封装了FFmpeg的Java框架,在项目中能直接调用它的API来处理音视频文件;
优点:使用方便,直接在项目中引入JAVE.jar即可处理媒体文件,且开发完成后可以随工程一起打包发布,不需要在目标运行环境内手动安装FFmpeg相关的类库
缺点:JAVE.jar最后一次更新是2009年,其封装的FFmpeg版本是09年或更早前的版本,比较老旧,无法使用一些新特性
(当然也可以看看有没有其他比较新的封装了FFmpeg的框架)
Maven坐标如下:
org.ffmpeg sdk 1.0.2
2.2在系统中手动安装FFmpeg
在运行环境中手动安装FFmpeg稍微有一些麻烦,可以百度windows/mac安装FFmpeg这样的关键字,根据网上的安装教程将FFmpeg安装到系统中;
懒人链接:Windows安装教程Mac安装教程
优点:可以直接调用FFmpeg的相关API处理音视频,FFmpeg版本可控
缺点:手动安装较为麻烦,开发环境与目标运行环境都需要先安装好FFmpeg
3.使用FFmpeg处理音视频
使用JAVE.jar进行开发与直接使用FFmpeg开发的代码有一些不同,这里以直接使用FFmpeg进行开发的代码进行讲解(开发环境MacOS);(使用JAVE的代码、直接使用FFmpeg的代码都会附在文末供大家下载参考)
通过MediaUtil.java类及其依赖的类,你将可以实现:
- 解析源视频的基本信息,包括视频格式、时长、码率等;
- 解析音频、图片的基本信息;
- 将源视频转换成不同分辨率、不同码率、带或不带音频的新视频;
- 抽取源视频中指定时间点的帧画面,来生成一张静态图;
- 抽取源视频中指定时间段的帧画面,来生成一个GIF动态图;
- 截取源视频中的一段来形成一个新视频;
- 抽取源视频中的音频信息,生成单独的MP3文件;
- 对音视频等媒体文件执行自定义的FFmpeg命令;
3.1代码结构梳理
MediaUtil.java是整个解析程序中的核心类,封装了各种常用的解析方法供外部调用;
MetaInfo.java定义了多媒体数据共有的一些属性,VideoMetaInfo.javaMusicMetaInfo.javaImageMetaInfo.java都继承自MetaInfo.java,分别定义了视频、音频、图片数据相关的一些属性;
AnimatedGifEncoder.javaLZWEncoder.javaNeuQuant.java在抽取视频帧数、制作GIF动态图的时候会使用到;
CrfValueEnum.java定义了三种常用的FFmpeg压缩视频时使用到的crf值,PresetVauleEnum.java定义了FFmpeg压缩视频时常用的几种压缩速率值;
有关crf、preset的延伸阅读点我
3.2MediaUtil.java主程序类解析
3.2.1使用前需要注意的几点
1、指定正确的FFmpeg程序执行路径
MacOS安装好FFmpeg后,可以在控制台中通过whichffmpeg命令获取FFmpeg程序的执行路径,在调用MediaUtil.java前先通过其setFFmpegPath()方法设置好FFmpeg程序在系统中的执行路径,然后才能顺利调用到FFmpeg去解析音视频;
Windows系统下该路径理论上应设置为:FFmpeg可执行程序在系统中的绝对路径(实际情况有待大家补充)
2、指定解析音视频信息时需要的正则表达式
因项目需要解析后缀格式为.MP4.WMV.AAC的视频和音频文件,所以我研究了JAVE.jar底层调用FFmpeg时的解析逻辑后,在MediaUtil.java中设置好了匹配这三种格式的正则表达式供解析时使用(参考程序中的durationRegexvideoStreamRegexmusicStreamRegex这三个表达式值);
注意:如果你需要解析其他后缀格式如.MKV.MP3这样的媒体文件时,你很可能需要根据实际情况修改durationRegexvideoStreamRegexmusicStreamRegex这三个正则表达式的值,否则可能无法解析出正确的信息;
3、程序中的很多默认值你可以根据实际需要修改,比如视频帧抽取的默认宽度或高度值、时长等等;
3.2.2MediaUtil.java代码
packagemedia; importlombok.extern.slf4j.Slf4j; importmedia.domain.ImageMetaInfo; importmedia.domain.MusicMetaInfo; importmedia.domain.VideoMetaInfo; importmedia.domain.gif.AnimatedGifEncoder; importorg.apache.commons.collections4.CollectionUtils; importorg.apache.commons.io.FileUtils; importorg.apache.commons.lang3.StringUtils; importjavax.imageio.ImageIO; importjava.awt.image.BufferedImage; importjava.io.*; importjava.sql.Time; importjava.util.ArrayList; importjava.util.Arrays; importjava.util.LinkedList; importjava.util.List; importjava.util.regex.Matcher; importjava.util.regex.Pattern; /** *基于FFmpeg内核来编解码音视频信息; *使用前需手动在运行环境中安装FFmpeg运行程序,然后正确设置FFmpeg运行路径后MediaUtil.java才能正常调用到FFmpeg程序去处理音视频; * *Author:dreamer-1 * *version:1.0 * */ @Slf4j publicclassMediaUtil{ /** *可以处理的视频格式 */ publicfinalstaticString[]VIDEO_TYPE={"MP4","WMV"}; /** *可以处理的图片格式 */ publicfinalstaticString[]IMAGE_TYPE={"JPG","JPEG","PNG","GIF"}; /** *可以处理的音频格式 */ publicfinalstaticString[]AUDIO_TYPE={"AAC"}; /** *视频帧抽取时的默认时间点,第10s(秒) *(Time类构造参数的单位:ms) */ privatestaticfinalTimeDEFAULT_TIME=newTime(0,0,10); /** *视频帧抽取的默认宽度值,单位:px */ privatestaticintDEFAULT_WIDTH=320; /** *视频帧抽取的默认时长,单位:s(秒) */ privatestaticintDEFAULT_TIME_LENGTH=10; /** *抽取多张视频帧以合成gif动图时,gif的播放速度 */ privatestaticintDEFAULT_GIF_PLAYTIME=110; /** *FFmpeg程序执行路径 *当前系统安装好ffmpeg程序并配置好相应的环境变量后,值为ffmpeg可执行程序文件在实际系统中的绝对路径 */ privatestaticStringFFMPEG_PATH="/usr/bin/ffmpeg";///usr/bin/ffmpeg /** *视频时长正则匹配式 *用于解析视频及音频的时长等信息时使用; * *(.*?)表示:匹配任何除\r\n之外的任何0或多个字符,非贪婪模式 * */ privatestaticStringdurationRegex="Duration:(\\d*?):(\\d*?):(\\d*?)\\.(\\d*?),start:(.*?),bitrate:(\\d*)kb\\/s.*"; privatestaticPatterndurationPattern; /** *视频流信息正则匹配式 *用于解析视频详细信息时使用; */ privatestaticStringvideoStreamRegex="Stream#\\d:\\d[\\(]??\\S*[\\)]??:Video:(\\S*\\S$?)[^\\,]*,(.*?),(\\d*)x(\\d*)[^\\,]*,(\\d*)kb\\/s,(\\d*[\\.]??\\d*)fps"; privatestaticPatternvideoStreamPattern; /** *音频流信息正则匹配式 *用于解析音频详细信息时使用; */ privatestaticStringmusicStreamRegex="Stream#\\d:\\d[\\(]??\\S*[\\)]??:Audio:(\\S*\\S$?)(.*),(.*?)Hz,(.*?),(.*?),(\\d*)kb\\/s";; privatestaticPatternmusicStreamPattern; /** *静态初始化时先加载好用于音视频解析的正则匹配式 */ static{ durationPattern=Pattern.compile(durationRegex); videoStreamPattern=Pattern.compile(videoStreamRegex); musicStreamPattern=Pattern.compile(musicStreamRegex); } /** *获取当前多媒体处理工具内的ffmpeg的执行路径 *@return */ publicstaticStringgetFFmpegPath(){ returnFFMPEG_PATH; } /** *设置当前多媒体工具内的ffmpeg的执行路径 *@paramffmpeg_pathffmpeg可执行程序在实际系统中的绝对路径 *@return */ publicstaticbooleansetFFmpegPath(Stringffmpeg_path){ if(StringUtils.isBlank(ffmpeg_path)){ log.error("---设置ffmpeg执行路径失败,因为传入的ffmpeg可执行程序路径为空!---"); returnfalse; } FileffmpegFile=newFile(ffmpeg_path); if(!ffmpegFile.exists()){ log.error("---设置ffmpeg执行路径失败,因为传入的ffmpeg可执行程序路径下的ffmpeg文件不存在!---"); returnfalse; } FFMPEG_PATH=ffmpeg_path; log.info("---设置ffmpeg执行路径成功---当前ffmpeg可执行程序路径为:"+ffmpeg_path); returntrue; } /** *测试当前多媒体工具是否可以正常工作 *@return */ publicstaticbooleanisExecutable(){ FileffmpegFile=newFile(FFMPEG_PATH); if(!ffmpegFile.exists()){ log.error("---工作状态异常,因为传入的ffmpeg可执行程序路径下的ffmpeg文件不存在!---"); returnfalse; } Listcmds=newArrayList<>(1); cmds.add("-version"); StringffmpegVersionStr=executeCommand(cmds); if(StringUtils.isBlank(ffmpegVersionStr)){ log.error("---工作状态异常,因为ffmpeg命令执行失败!---"); returnfalse; } log.info("---工作状态正常---"); returntrue; } /** *执行FFmpeg命令 *@paramcommonds要执行的FFmpeg命令 *@returnFFmpeg程序在执行命令过程中产生的各信息,执行出错时返回null */ publicstaticStringexecuteCommand(List commonds){ if(CollectionUtils.isEmpty(commonds)){ log.error("---指令执行失败,因为要执行的FFmpeg指令为空!---"); returnnull; } LinkedList ffmpegCmds=newLinkedList<>(commonds); ffmpegCmds.addFirst(FFMPEG_PATH);//设置ffmpeg程序所在路径 log.info("---待执行的FFmpeg指令为:---"+ffmpegCmds); Runtimeruntime=Runtime.getRuntime(); Processffmpeg=null; try{ //执行ffmpeg指令 ProcessBuilderbuilder=newProcessBuilder(); builder.command(ffmpegCmds); ffmpeg=builder.start(); log.info("---开始执行FFmpeg指令:---执行线程名:"+builder.toString()); //取出输出流和错误流的信息 //注意:必须要取出ffmpeg在执行命令过程中产生的输出信息,如果不取的话当输出流信息填满jvm存储输出留信息的缓冲区时,线程就回阻塞住 PrintStreamerrorStream=newPrintStream(ffmpeg.getErrorStream()); PrintStreaminputStream=newPrintStream(ffmpeg.getInputStream()); errorStream.start(); inputStream.start(); //等待ffmpeg命令执行完 ffmpeg.waitFor(); //获取执行结果字符串 Stringresult=errorStream.stringBuffer.append(inputStream.stringBuffer).toString(); //输出执行的命令信息 StringcmdStr=Arrays.toString(ffmpegCmds.toArray()).replace(",",""); StringresultStr=StringUtils.isBlank(result)?"【异常】":"正常"; log.info("---已执行的FFmepg命令:---"+cmdStr+"已执行完毕,执行结果:"+resultStr); returnresult; }catch(Exceptione){ log.error("---FFmpeg命令执行出错!---出错信息:"+e.getMessage()); returnnull; }finally{ if(null!=ffmpeg){ ProcessKillerffmpegKiller=newProcessKiller(ffmpeg); //JVM退出时,先通过钩子关闭FFmepg进程 runtime.addShutdownHook(ffmpegKiller); } } } /** *视频转换 * *注意指定视频分辨率时,宽度和高度必须同时有值; * *@paramfileInput源视频路径 *@paramfileOutPut转换后的视频输出路径 *@paramwithAudio是否保留音频;true-保留,false-不保留 *@paramcrf指定视频的质量系数(值越小,视频质量越高,体积越大;该系数取值为0-51,直接影响视频码率大小),取值参考:CrfValueEnum.code *@parampreset指定视频的编码速率(速率越快压缩率越低),取值参考:PresetVauleEnum.presetValue *@paramwidth视频宽度;为空则保持源视频宽度 *@paramheight视频高度;为空则保持源视频高度 */ publicstaticvoidconvertVideo(FilefileInput,FilefileOutPut,booleanwithAudio,Integercrf,Stringpreset,Integerwidth,Integerheight){ if(null==fileInput||!fileInput.exists()){ thrownewRuntimeException("源视频文件不存在,请检查源视频路径"); } if(null==fileOutPut){ thrownewRuntimeException("转换后的视频路径为空,请检查转换后的视频存放路径是否正确"); } if(!fileOutPut.exists()){ try{ fileOutPut.createNewFile(); }catch(IOExceptione){ log.error("视频转换时新建输出文件失败"); } } Stringformat=getFormat(fileInput); if(!isLegalFormat(format,VIDEO_TYPE)){ thrownewRuntimeException("无法解析的视频格式:"+format); } List commond=newArrayList (); commond.add("-i"); commond.add(fileInput.getAbsolutePath()); if(!withAudio){//设置是否保留音频 commond.add("-an");//去掉音频 } if(null!=width&&width>0&&null!=height&&height>0){//设置分辨率 commond.add("-s"); Stringresolution=width.toString()+"x"+height.toString(); commond.add(resolution); } commond.add("-vcodec");//指定输出视频文件时使用的编码器 commond.add("libx264");//指定使用x264编码器 commond.add("-preset");//当使用x264时需要带上该参数 commond.add(preset);//指定preset参数 commond.add("-crf");//指定输出视频质量 commond.add(crf.toString());//视频质量参数,值越小视频质量越高 commond.add("-y");//当已存在输出文件时,不提示是否覆盖 commond.add(fileOutPut.getAbsolutePath()); executeCommand(commond); } /** *视频帧抽取 *默认抽取第10秒的帧画面 *抽取的帧图片默认宽度为300px * *转换后的文件路径以.gif结尾时,默认截取从第10s开始,后10s以内的帧画面来生成gif * *@paramvideoFile源视频路径 *@paramfileOutPut转换后的文件路径 */ publicstaticvoidcutVideoFrame(FilevideoFile,FilefileOutPut){ cutVideoFrame(videoFile,fileOutPut,DEFAULT_TIME); } /** *视频帧抽取(抽取指定时间点的帧画面) *抽取的视频帧图片宽度默认为320px * *转换后的文件路径以.gif结尾时,默认截取从指定时间点开始,后10s以内的帧画面来生成gif * *@paramvideoFile源视频路径 *@paramfileOutPut转换后的文件路径 *@paramtime指定抽取视频帧的时间点(单位:s) */ publicstaticvoidcutVideoFrame(FilevideoFile,FilefileOutPut,Timetime){ cutVideoFrame(videoFile,fileOutPut,time,DEFAULT_WIDTH); } /** *视频帧抽取(抽取指定时间点、指定宽度值的帧画面) *只需指定视频帧的宽度,高度随宽度自动计算 * *转换后的文件路径以.gif结尾时,默认截取从指定时间点开始,后10s以内的帧画面来生成gif * *@paramvideoFile源视频路径 *@paramfileOutPut转换后的文件路径 *@paramtime指定要抽取第几秒的视频帧(单位:s) *@paramwidth抽取的视频帧图片的宽度(单位:px) */ publicstaticvoidcutVideoFrame(FilevideoFile,FilefileOutPut,Timetime,intwidth){ if(null==videoFile||!videoFile.exists()){ thrownewRuntimeException("源视频文件不存在,请检查源视频路径"); } if(null==fileOutPut){ thrownewRuntimeException("转换后的视频路径为空,请检查转换后的视频存放路径是否正确"); } VideoMetaInfoinfo=getVideoMetaInfo(videoFile); if(null==info){ log.error("---未能解析源视频信息,视频帧抽取操作失败---源视频:"+videoFile); return; } intheight=width*info.getHeight()/info.getWidth();//根据宽度计算适合的高度,防止画面变形 cutVideoFrame(videoFile,fileOutPut,time,width,height); } /** *视频帧抽取(抽取指定时间点、指定宽度值、指定高度值的帧画面) * *转换后的文件路径以.gif结尾时,默认截取从指定时间点开始,后10s以内的帧画面来生成gif * *@paramvideoFile源视频路径 *@paramfileOutPut转换后的文件路径 *@paramtime指定要抽取第几秒的视频帧(单位:s) *@paramwidth抽取的视频帧图片的宽度(单位:px) *@paramheight抽取的视频帧图片的高度(单位:px) */ publicstaticvoidcutVideoFrame(FilevideoFile,FilefileOutPut,Timetime,intwidth,intheight){ if(null==videoFile||!videoFile.exists()){ thrownewRuntimeException("源视频文件不存在,请检查源视频路径"); } if(null==fileOutPut){ thrownewRuntimeException("转换后的视频路径为空,请检查转换后的视频存放路径是否正确"); } Stringformat=getFormat(fileOutPut); if(!isLegalFormat(format,IMAGE_TYPE)){ thrownewRuntimeException("无法生成指定格式的帧图片:"+format); } StringfileOutPutPath=fileOutPut.getAbsolutePath(); if(!"GIF".equals(StringUtils.upperCase(format))){ //输出路径不是以.gif结尾,抽取并生成一张静态图 cutVideoFrame(videoFile,fileOutPutPath,time,width,height,1,false); }else{ //抽取并生成一个gif(gif由10张静态图构成) Stringpath=fileOutPut.getParent(); Stringname=fileOutPut.getName(); //创建临时文件存储多张静态图用于生成gif StringtempPath=path+File.separator+System.currentTimeMillis()+"_"+name.substring(0,name.indexOf(".")); Filefile=newFile(tempPath); if(!file.exists()){ file.mkdir(); } try{ cutVideoFrame(videoFile,tempPath,time,width,height,DEFAULT_TIME_LENGTH,true); //生成gif Stringimages[]=file.list(); for(inti=0;i info.getDuration()){ thrownewRuntimeException("开始截取视频帧的时间点不合法:"+time.toString()+",因为截取时间点晚于视频的最后时间点"); } if(width<=20||height<=20){ thrownewRuntimeException("截取的视频帧图片的宽度或高度不合法,宽高值必须大于20"); } try{ List commond=newArrayList (); commond.add("-ss"); commond.add(time.toString()); if(isContinuty){ commond.add("-t"); commond.add(timeLength+""); }else{ commond.add("-vframes"); commond.add("1"); } commond.add("-i"); commond.add(videoFile.getAbsolutePath()); commond.add("-an"); commond.add("-f"); commond.add("image2"); if(isContinuty){ commond.add("-r"); commond.add("3"); } commond.add("-s"); commond.add(width+"*"+height); if(isContinuty){ commond.add(path+File.separator+"foo-%03d.jpeg"); }else{ commond.add(path); } executeCommand(commond); }catch(Exceptione){ log.error("---视频帧抽取过程出错---错误信息:"+e.getMessage()); } } /** *截取视频中的某一段,生成新视频 * *@paramvideoFile源视频路径 *@paramoutputFile转换后的视频路径 *@paramstartTime开始抽取的时间点(单位:s) *@paramtimeLength需要抽取的时间段(单位:s,需小于源视频最大时长);例如:该参数值为10时即抽取从startTime开始之后10秒内的视频作为新视频 */ publicstaticvoidcutVideo(FilevideoFile,FileoutputFile,TimestartTime,inttimeLength){ if(videoFile==null||!videoFile.exists()){ thrownewRuntimeException("视频文件不存在:"); } if(null==outputFile){ thrownewRuntimeException("转换后的视频路径为空,请检查转换后的视频存放路径是否正确"); } VideoMetaInfoinfo=getVideoMetaInfo(videoFile); if(null==info){ thrownewRuntimeException("未解析到视频信息"); } if(startTime.getTime()+timeLength>info.getDuration()){ thrownewRuntimeException("截取时间不合法:"+startTime.toString()+",因为截取时间大于视频的时长"); } try{ if(!outputFile.exists()){ outputFile.createNewFile(); } List commond=newArrayList (); commond.add("-ss"); commond.add(startTime.toString()); commond.add("-t"); commond.add(""+timeLength); commond.add("-i"); commond.add(videoFile.getAbsolutePath()); commond.add("-vcodec"); commond.add("copy"); commond.add("-acodec"); commond.add("copy"); commond.add(outputFile.getAbsolutePath()); executeCommand(commond); }catch(IOExceptione){ log.error("---视频截取过程出错---"); } } /** *抽取视频里的音频信息 *只能抽取成MP3文件 *@paramvideoFile源视频文件 *@paramaudioFile从源视频提取的音频文件 */ publicstaticvoidgetAudioFromVideo(FilevideoFile,FileaudioFile){ if(null==videoFile||!videoFile.exists()){ thrownewRuntimeException("源视频文件不存在:"); } if(null==audioFile){ thrownewRuntimeException("要提取的音频路径为空:"); } Stringformat=getFormat(audioFile); if(!isLegalFormat(format,AUDIO_TYPE)){ thrownewRuntimeException("无法生成指定格式的音频:"+format+"请检查要输出的音频文件是否是AAC类型"); } try{ if(!audioFile.exists()){ audioFile.createNewFile(); } List commond=newArrayList (); commond.add("-i"); commond.add(videoFile.getAbsolutePath()); commond.add("-vn");//novideo,去除视频信息 commond.add("-y"); commond.add("-acodec"); commond.add("copy"); commond.add(audioFile.getAbsolutePath()); executeCommand(commond); }catch(Exceptione){ log.error("---抽取视频中的音频信息的过程出错---错误信息:"+e.getMessage()); } } /** *解析视频的基本信息(从文件中) * *解析出的视频信息一般为以下格式: *Input#0,mov,mp4,m4a,3gp,3g2,mj2,from'6.mp4': *Duration:00:00:30.04,start:0.000000,bitrate:19031kb/s *Stream#0:0(eng):Video:h264(Main)(avc1/0x31637661),yuv420p(tv,bt709),1920x1080,18684kb/s,25fps,25tbr,25ktbn,50tbc(default) *Stream#0:1(eng):Audio:aac(LC)(mp4a/0x6134706D),48000Hz,stereo,fltp,317kb/s(default) * *注解: *Duration:00:00:30.04【视频时长】,start:0.000000【视频开始时间】,bitrate:19031kb/s【视频比特率/码率】 *Stream#0:0(eng):Video:h264【视频编码格式】(Main)(avc1/0x31637661),yuv420p(tv,bt709),1920x1080【视频分辨率,宽x高】,18684【视频比特率】kb/s,25【视频帧率】fps,25tbr,25ktbn,50tbc(default) *Stream#0:1(eng):Audio:aac【音频格式】(LC)(mp4a/0x6134706D),48000【音频采样率】Hz,stereo,fltp,317【音频码率】kb/s(default) * *@paramvideoFile源视频路径 *@return视频的基本信息,解码失败时返回null */ publicstaticVideoMetaInfogetVideoMetaInfo(FilevideoFile){ if(null==videoFile||!videoFile.exists()){ log.error("---解析视频信息失败,因为要解析的源视频文件不存在---"); returnnull; } VideoMetaInfovideoInfo=newVideoMetaInfo(); StringparseResult=getMetaInfoFromFFmpeg(videoFile); MatcherdurationMacher=durationPattern.matcher(parseResult); MatchervideoStreamMacher=videoStreamPattern.matcher(parseResult); MatchervideoMusicStreamMacher=musicStreamPattern.matcher(parseResult); Longduration=0L;//视频时长 IntegervideoBitrate=0;//视频码率 StringvideoFormat=getFormat(videoFile);//视频格式 LongvideoSize=videoFile.length();//视频大小 StringvideoEncoder="";//视频编码器 IntegervideoHeight=0;//视频高度 IntegervideoWidth=0;//视频宽度 FloatvideoFramerate=0F;//视频帧率 StringmusicFormat="";//音频格式 Longsamplerate=0L;//音频采样率 IntegermusicBitrate=0;//音频码率 try{ //匹配视频播放时长等信息 if(durationMacher.find()){ longhours=(long)Integer.parseInt(durationMacher.group(1)); longminutes=(long)Integer.parseInt(durationMacher.group(2)); longseconds=(long)Integer.parseInt(durationMacher.group(3)); longdec=(long)Integer.parseInt(durationMacher.group(4)); duration=dec*100L+seconds*1000L+minutes*60L*1000L+hours*60L*60L*1000L; //StringstartTime=durationMacher.group(5)+"ms"; videoBitrate=Integer.parseInt(durationMacher.group(6)); } //匹配视频分辨率等信息 if(videoStreamMacher.find()){ videoEncoder=videoStreamMacher.group(1); Strings2=videoStreamMacher.group(2); videoWidth=Integer.parseInt(videoStreamMacher.group(3)); videoHeight=Integer.parseInt(videoStreamMacher.group(4)); Strings5=videoStreamMacher.group(5); videoFramerate=Float.parseFloat(videoStreamMacher.group(6)); } //匹配视频中的音频信息 if(videoMusicStreamMacher.find()){ musicFormat=videoMusicStreamMacher.group(1);//提取音频格式 //Strings2=videoMusicStreamMacher.group(2); samplerate=Long.parseLong(videoMusicStreamMacher.group(3));//提取采样率 //Strings4=videoMusicStreamMacher.group(4); //Strings5=videoMusicStreamMacher.group(5); musicBitrate=Integer.parseInt(videoMusicStreamMacher.group(6));//提取比特率 } }catch(Exceptione){ log.error("---解析视频参数信息出错!---错误信息:"+e.getMessage()); returnnull; } //封装视频中的音频信息 MusicMetaInfomusicMetaInfo=newMusicMetaInfo(); musicMetaInfo.setFormat(musicFormat); musicMetaInfo.setDuration(duration); musicMetaInfo.setBitRate(musicBitrate); musicMetaInfo.setSampleRate(samplerate); //封装视频信息 VideoMetaInfovideoMetaInfo=newVideoMetaInfo(); videoMetaInfo.setFormat(videoFormat); videoMetaInfo.setSize(videoSize); videoMetaInfo.setBitRate(videoBitrate); videoMetaInfo.setDuration(duration); videoMetaInfo.setEncoder(videoEncoder); videoMetaInfo.setFrameRate(videoFramerate); videoMetaInfo.setHeight(videoHeight); videoMetaInfo.setWidth(videoWidth); videoMetaInfo.setMusicMetaInfo(musicMetaInfo); returnvideoMetaInfo; } /** *获取视频的基本信息(从流中) * *@paraminputStream源视频流路径 *@return视频的基本信息,解码失败时返回null */ publicstaticVideoMetaInfogetVideoMetaInfo(InputStreaminputStream){ VideoMetaInfovideoInfo=newVideoMetaInfo(); try{ Filefile=File.createTempFile("tmp",null); if(!file.exists()){ returnnull; } FileUtils.copyInputStreamToFile(inputStream,file); videoInfo=getVideoMetaInfo(file); file.deleteOnExit(); returnvideoInfo; }catch(Exceptione){ log.error("---从流中获取视频基本信息出错---错误信息:"+e.getMessage()); returnnull; } } /** *获取音频的基本信息(从文件中) *@parammusicFile音频文件路径 *@return音频的基本信息,解码失败时返回null */ publicstaticMusicMetaInfogetMusicMetaInfo(FilemusicFile){ if(null==musicFile||!musicFile.exists()){ log.error("---无法获取音频信息,因为要解析的音频文件为空---"); returnnull; } //获取音频信息字符串,方便后续解析 StringparseResult=getMetaInfoFromFFmpeg(musicFile); Longduration=0L;//音频时长 IntegermusicBitrate=0;//音频码率 Longsamplerate=0L;//音频采样率 StringmusicFormat="";//音频格式 LongmusicSize=musicFile.length();//音频大小 MatcherdurationMacher=durationPattern.matcher(parseResult); MatchermusicStreamMacher=musicStreamPattern.matcher(parseResult); try{ //匹配音频播放时长等信息 if(durationMacher.find()){ longhours=(long)Integer.parseInt(durationMacher.group(1)); longminutes=(long)Integer.parseInt(durationMacher.group(2)); longseconds=(long)Integer.parseInt(durationMacher.group(3)); longdec=(long)Integer.parseInt(durationMacher.group(4)); duration=dec*100L+seconds*1000L+minutes*60L*1000L+hours*60L*60L*1000L; //StringstartTime=durationMacher.group(5)+"ms"; musicBitrate=Integer.parseInt(durationMacher.group(6)); } //匹配音频采样率等信息 if(musicStreamMacher.find()){ musicFormat=musicStreamMacher.group(1);//提取音频格式 //Strings2=videoMusicStreamMacher.group(2); samplerate=Long.parseLong(musicStreamMacher.group(3));//提取采样率 //Strings4=videoMusicStreamMacher.group(4); //Strings5=videoMusicStreamMacher.group(5); musicBitrate=Integer.parseInt(musicStreamMacher.group(6));//提取比特率 } }catch(Exceptione){ log.error("---解析音频参数信息出错!---错误信息:"+e.getMessage()); returnnull; } //封装视频中的音频信息 MusicMetaInfomusicMetaInfo=newMusicMetaInfo(); musicMetaInfo.setFormat(musicFormat); musicMetaInfo.setDuration(duration); musicMetaInfo.setBitRate(musicBitrate); musicMetaInfo.setSampleRate(samplerate); musicMetaInfo.setSize(musicSize); returnmusicMetaInfo; } /** *获取音频的基本信息(从流中) *@paraminputStream源音乐流路径 *@return音频基本信息,解码出错时返回null */ publicstaticMusicMetaInfogetMusicMetaInfo(InputStreaminputStream){ MusicMetaInfomusicMetaInfo=newMusicMetaInfo(); try{ Filefile=File.createTempFile("tmp",null); if(!file.exists()){ returnnull; } FileUtils.copyInputStreamToFile(inputStream,file); musicMetaInfo=getMusicMetaInfo(file); file.deleteOnExit(); returnmusicMetaInfo; }catch(Exceptione){ log.error("---从流中获取音频基本信息出错---错误信息:"+e.getMessage()); returnnull; } } /** *获取图片的基本信息(从流中) * *@paraminputStream源图片路径 *@return图片的基本信息,获取信息失败时返回null */ publicstaticImageMetaInfogetImageInfo(InputStreaminputStream){ BufferedImageimage=null; ImageMetaInfoimageInfo=newImageMetaInfo(); try{ image=ImageIO.read(inputStream); imageInfo.setWidth(image.getWidth()); imageInfo.setHeight(image.getHeight()); imageInfo.setSize(Long.valueOf(String.valueOf(inputStream.available()))); returnimageInfo; }catch(Exceptione){ log.error("---获取图片的基本信息失败---错误信息:"+e.getMessage()); returnnull; } } /** *获取图片的基本信息(从文件中) * *@paramimageFile源图片路径 *@return图片的基本信息,获取信息失败时返回null */ publicstaticImageMetaInfogetImageInfo(FileimageFile){ BufferedImageimage=null; ImageMetaInfoimageInfo=newImageMetaInfo(); try{ if(null==imageFile||!imageFile.exists()){ returnnull; } image=ImageIO.read(imageFile); imageInfo.setWidth(image.getWidth()); imageInfo.setHeight(image.getHeight()); imageInfo.setSize(imageFile.length()); imageInfo.setFormat(getFormat(imageFile)); returnimageInfo; }catch(Exceptione){ log.error("---获取图片的基本信息失败---错误信息:"+e.getMessage()); returnnull; } } /** *检查文件类型是否是给定的类型 *@paraminputFile源文件 *@paramgivenFormat指定的文件类型;例如:{"MP4","AVI"} *@return */ publicstaticbooleanisGivenFormat(FileinputFile,String[]givenFormat){ if(null==inputFile||!inputFile.exists()){ log.error("---无法检查文件类型是否满足要求,因为要检查的文件不存在---源文件:"+inputFile); returnfalse; } if(null==givenFormat||givenFormat.length<=0){ log.error("---无法检查文件类型是否满足要求,因为没有指定的文件类型---"); returnfalse; } Stringfomat=getFormat(inputFile); returnisLegalFormat(fomat,givenFormat); } /** *使用FFmpeg的"-i"命令来解析视频信息 *@paraminputFile源媒体文件 *@return解析后的结果字符串,解析失败时为空 */ publicstaticStringgetMetaInfoFromFFmpeg(FileinputFile){ if(inputFile==null||!inputFile.exists()){ thrownewRuntimeException("源媒体文件不存在,源媒体文件路径:"); } List commond=newArrayList (); commond.add("-i"); commond.add(inputFile.getAbsolutePath()); StringexecuteResult=MediaUtil.executeCommand(commond); returnexecuteResult; } /** *检测视频格式是否合法 *@paramformat *@paramformats *@return */ privatestaticbooleanisLegalFormat(Stringformat,Stringformats[]){ for(Stringitem:formats){ if(item.equals(StringUtils.upperCase(format))){ returntrue; } } returnfalse; } /** *创建gif * *@paramimage多个jpg文件名(包含路径) *@paramoutputPath生成的gif文件名(包含路径) *@paramplayTime播放的延迟时间,可调整gif的播放速度 */ privatestaticvoidcreateGifImage(Stringimage[],StringoutputPath,intplayTime){ if(null==outputPath){ thrownewRuntimeException("转换后的GIF路径为空,请检查转换后的GIF存放路径是否正确"); } try{ AnimatedGifEncoderencoder=newAnimatedGifEncoder(); encoder.setRepeat(0); encoder.start(outputPath); BufferedImagesrc[]=newBufferedImage[image.length]; for(inti=0;i 3.2.3踩坑&填坑
1、在Linux等服务器上部署Java程序进行视频压缩时,多注意一下运行账号的权限问题,有时候可能是由于运行程序没有足够的文件操作权限,导致压缩过程失败;
2、第一版程序上线后,偶尔会出现这样的问题:
调用MediaUtil.java进行视频压缩过程中,整个程序突然“卡住”,后台也没有日志再打印出来,此时整个压缩过程还没有完成,像是线程突然阻塞住了;
经过多番查找,发现Java调用FFmpeg时,实际是在JVM里产生一个子进程来执行压缩过程,这个子进程与JVM建立三个通道链接(包括标准输入、标准输出、标准错误流),在压缩过程中,实际会不停地向标准输出和错误流中写入信息;
因为本地系统对标准输出及错误流提供的缓冲区大小有限,当写入标准输出和错误流的信息填满缓冲区时,执行压缩的进程就会阻塞住;
所以在压缩过程中,需要单独创建两个线程不停读取标准输出及错误流中的信息,防止整个压缩进程阻塞;(参考MediaUtil.java中的executeCommand()方法中的errorStream和inputStream这两个内部类实例的操作)
3.3在CentOS服务器安装FFmpeg指南
因项目最后部署在CentOS服务器上,需提前在服务器上安装好FFmpeg程序,这过程中也踩了不少坑,针对此写了另一篇总结文章,参考这里点我哦
4.源码下载
这里提供两种版本的源码供大家下载参考:
- 引入封装了FFmpeg的开源框架Jave.jar的版本点我下载
- 在系统中手动安装FFmpeg的版本点我下载
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。