Linux下搭建基於ffmpeg的jave並且提取視訊中的音訊
1.安裝yasm-1.3.0
解壓
tar zxvf yasm-1.3.0.tar.gz
cd到解壓目錄下對進行:
./configure
make
make install
如果提示說缺少"automake-1.15",則在其原始碼目錄執行
autoreconf -ivf
2.安裝fdk-acc-0.1.6
過程同1.
3.安裝配置ffmpeg-4.0(這裡一定要使用jave2.0的jar 不然後續使用jave會一直出錯)
tar cd之後
執行
./configure --enable-shared --prefix=/usr/local/ffmpeg
make
make install
make時間較長需要耐心等待
檢驗是否安裝成功:
./configure --enable-shared --prefix=/usr/local/ffmpeg
會出現報錯:
/usr/local/ffmpeg/bin/ffmpeg: error while loading shared libraries: libavdevice.so.56: cannot open shared object file: No such file or directory
就是庫檔案檢索不到,因此需要手動修改連線庫檔案/etc/ld.so.conf ,具體為:
vim /etc/ld.so.conf
include ld.so.conf.d/*.conf
/usr/local/ffmpeg/lib/
在vim好如上命令之後,接下來使得命令需生效,使用的命令為:
ldconfig
配置環境變數Path,以使得其在所有目錄下都可以使用ffmpeg:
export PATH=/usr/local/ffmpeg/bin/:$PATH
檢測是否可以再任意目錄下進行:
ffmpeg --version
4.使用jave
呼叫:private boolean convertAmr2MP3(File src, File target) { AudioAttributes audio = new AudioAttributes(); audio.setCodec("libmp3lame"); Encoder encoder = new Encoder(); EncodingAttributes attrs = new EncodingAttributes(); attrs.setFormat("mp3"); attrs.setAudioAttributes(audio); try { encoder.encode(src, target, attrs); return true; } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InputFormatException e) { e.printStackTrace(); } catch (EncoderException e) { e.printStackTrace(); } return false; }
報錯:
it.sauronsoftware.jave.InputFormatException
at it.sauronsoftware.jave.Encoder.parseMultimediaInfo(Encoder.java:659)
at it.sauronsoftware.jave.Encoder.encode(Encoder.java:840)
at it.sauronsoftware.jave.Encoder.encode(Encoder.java:713)
at com.music.read.MusicFileParser.convertAmr2MP3(MusicFileParser.java:256)
at com.music.read.MusicFileParser.loadFile(MusicFileParser.java:75)
at com.music.read.MusicFileParser.access$000(MusicFileParser.java:24)
at com.music.read.MusicFileParser$1.run(MusicFileParser.java:43)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
繼續到官網找原因:
Using an alternative ffmpeg executable
JAVE is not pure Java: it acts as a wrapper around an ffmpeg (http://ffmpeg.mplayerhq.hu/) executable. ffmpeg is an open source and free software project entirely written in C, so its executables cannot be easily ported from a machine to another. You need a pre-compiled version of ffmpeg in order to run JAVE on your target machine. The JAVE distribution includes two pre-compiled executables of ffmpeg: a Windows one and a Linux one, both compiled for i386/32 bit hardware achitectures. This should be enough in most cases. If it is not enough for your specific situation, you can still run JAVE, but you need to obtain a platform specific ffmpeg executable. Check the Internet for it. You can even build it by yourself getting the code (and the documentation to build it) on the official ffmpeg site. Once you have obtained a ffmpeg executable suitable for your needs, you have to hook it in the JAVE library. That's a plain operation. JAVE gives you an abstract class called it.sauronsoftware.jave.FFMPEGLocator. Extend it. All you have to do is to define the following method:
public java.lang.String getFFMPEGExecutablePath()
This method should return a file system based path to your custom ffmpeg executable.
Once your class is ready, suppose you have called it MyFFMPEGExecutableLocator, you have to create an alternate encoder that uses it instead of the default locator:
Encoder encoder = new Encoder(new MyFFMPEGExecutableLocator())
You can use the same procedure also to switch to other versions of ffmpeg, even if you are on a platform covered by the executables bundled in the JAVE distribution.
Anyway be careful and test ever your application: JAVE it's not guaranteed to work properly with custom ffmpeg executables different from the bundled ones.
在官方文件上看了這一段,大意就是說要去ffmpeg的官網下載對應版本的ffmpeg。然後需要自己建立一個類去繼承FFMPEGLocator,實現抽象方法,將ffmpeg的路徑傳JAVE
使用類載入器,獲取ffmpeg的真實路經:
public class MyFFMPEGExecute extends FFMPEGLocator {
protected String getFFMPEGExecutablePath() {
String path = MyFFMPEGExecute.class.getResource("/res/ffmpeg").getPath();
return path;
}
}
呼叫方法改為:
private boolean convertAmr2MP3(File src, File target) {
AudioAttributes audio = new AudioAttributes();
audio.setCodec("libmp3lame");
Encoder encoder = new Encoder(new MyFFMPEGExecute());
EncodingAttributes attrs = new EncodingAttributes();
attrs.setFormat("mp3");
attrs.setAudioAttributes(audio);
try {
encoder.encode(src, target, attrs);
return true;
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InputFormatException e) {
e.printStackTrace();
} catch (EncoderException e) {
e.printStackTrace();
}
return false;
}
執行之後仍然報錯:
it.sauronsoftware.jave.EncoderException: Stream mapping:
at it.sauronsoftware.jave.Encoder.encode(Encoder.java:863)
at it.sauronsoftware.jave.Encoder.encode(Encoder.java:713)
at com.music.read.MusicFileParser.convertAmr2MP3(MusicFileParser.java:256)
at com.music.read.MusicFileParser.loadFile(MusicFileParser.java:75)
at com.music.read.MusicFileParser.access$000(MusicFileParser.java:24)
at com.music.read.MusicFileParser$1.run(MusicFileParser.java:43)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
這下就尷尬了,都是都是按照要求來實現的啊?怎麼就不行呢?
看來還是得翻牆,後來找到一個更新版本的jave的jar包。JAVE官網最新的版本釋出日期是2009年!所以可能是這個原因
加入新的jar包後重新編譯執行,仍然報錯。但這此報的異常又變了,感覺有希望
Caused by: java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 8 more
從異常來看,最新的jave依賴了commons-logging,加上這個:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
但是這方案不建議使用,使用maven新增依賴在非maven專案下比較麻煩,因此建議使用commons-logging-1.2.jar
此中間還出現了一個插曲,如果提示ClassDefNotFound,很有可能是你的路徑包含有中文名字。
最後根據實際專案需求寫了一個獲取目錄下所有的視訊檔案,在上一層目錄建立一個新的資料夾下,把原來要轉換的視訊轉換到這裡。
import it.sauronsoftware.jave.AudioAttributes;
import it.sauronsoftware.jave.Encoder;
import it.sauronsoftware.jave.EncodingAttributes;
import it.sauronsoftware.jave.FFMPEGLocator;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import banger.video.EncryptUtil;
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
String filePath="/Users/hackjeremy/Desktop/env/1/";
String videoType=".avi";
System.out.println(
Main.work(Main.getFileList(filePath,videoType), videoType));
}
public static String work(List<File>filelist,String fileType){
long startTime = System.currentTimeMillis(); // 獲取開始時間
String newFloderPath="";
if(filelist.size()>0){
String []temp=(filelist.get(0)+"").split("/");
for(int i=0;i<temp.length-1;i++){
if(temp[i].length()>0){
newFloderPath+=("/"+temp[i]);
}
}
}else{
return "";
}
newFloderPath+="_finish/";
List<String>filename=new ArrayList<String>();//目錄下所有要處理的檔名字 不含字尾名
for(int i=0;i<filelist.size();i++){
String []t=filelist.get(i).toString().split("/");
String []tt=t[t.length-1].split(fileType);
filename.add(tt[0]);
}
List<String>filename_cast=new ArrayList<String>();
if(createDir(newFloderPath)){
for(int i=0;i<filelist.size();i++){
try {
String castFilePath=newFloderPath+filename.get(i).toString()+"_cast"+fileType;
filename_cast.add(castFilePath);
EncryptUtil.encryFile(filelist.get(i).toString(),castFilePath);
File source = new File(filename_cast.get(i));
File target = new File(newFloderPath+filename.get(i).toString()+"_finish"+".wav");
AudioAttributes audio = new AudioAttributes();
audio.setCodec("pcm_s16le");
audio.setChannels(new Integer(2));
EncodingAttributes attrs = new EncodingAttributes();
attrs.setFormat("wav");
attrs.setAudioAttributes(audio);
Encoder encoder = new Encoder(new MyFFMPEGExecute());
encoder.encode(source, target, attrs);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("cast error");
}
}
for(int i=0;i<filename_cast.size();i++){//刪除轉換後的視訊
if(!deleteFile(filename_cast.get(i).toString())){
return "";
}
}
}else{
return "";
}
long endTime = System.currentTimeMillis(); // 獲取結束時間
System.out.println("run time:" + (endTime - startTime) + "ms");
return newFloderPath;
}
public static boolean deleteFile(String fileName) {
File file = new File(fileName);
// 如果檔案路徑所對應的檔案存在,並且是一個檔案,則直接刪除
if (file.exists() && file.isFile()) {
if (file.delete()) {
System.out.println("刪除單個檔案" + fileName + "成功!");
return true;
} else {
System.out.println("刪除單個檔案" + fileName + "失敗!");
return false;
}
} else {
System.out.println("刪除單個檔案失敗:" + fileName + "不存在!");
return false;
}
}
public static boolean createDir(String destDirName) {
File dir = new File(destDirName);
if (dir.exists()) {
System.out.println("建立目錄" + destDirName + "失敗,目標目錄已經存在");
return false;
}
if (!destDirName.endsWith(File.separator)) {
destDirName = destDirName + File.separator;
}
//建立目錄
if (dir.mkdirs()) {
System.out.println("建立目錄" + destDirName + "成功!");
return true;
} else {
System.out.println("建立目錄" + destDirName + "失敗!");
return false;
}
}
public static List<File> getFileList(String strPath,String fileType) {
File dir = new File(strPath);
List<File>filelist=new ArrayList<File>();
File[] files = dir.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
String fileName = files[i].getName();
if (files[i].isDirectory()) {
getFileList(files[i].getAbsolutePath(),fileType);
} else if (fileName.endsWith(fileType)) {
String strFileName = files[i].getAbsolutePath();
filelist.add(files[i]);
} else {
continue;
}
}
}
return filelist;
}
}
初試檔案為:控制檯輸出為:
執行之後的視訊轉換如下:
看某個視訊簡略圖:
完全沒有任何問題