文章目录
一、引言
因为工作需要,在网上找了许多方法来实现,七拼八凑总算是把接口搞通了,肯定还有更好的方法,但下面这个方法亲测有效!
二、添加依赖
在pom.xml配置文件中添加如下依赖:
<!-- mp3文件支持-->
<dependency>
<groupId>org</groupId>
<artifactId>jaudiotagger</artifactId>
<version>2.0.1</version>
</dependency>
<!-- mp4文件支持-->
<dependency>
<groupId>com.googlecode.mp4parser</groupId>
<artifactId>isoparser</artifactId>
<version>1.1.22</version>
</dependency>
三、添加工具类
以工具类包装更为方便
注意:这里会出现1秒的文件获取时长为0。
可参考六、问题解决----1秒的时长在未出错的情况下读出来是0秒,因为是我自己的解决方案,可能有更适合的,所以没有在这里直接加上!
3.1 视频文件工具类
package com.blcup.cledu.teach.utils;
import com.coremedia.iso.IsoFile;
import java.io.IOException;
public class VideoUtil {
/**
* 获取视频文件的播放长度(mp4、mov格式)
* @param videoPath
* @return 单位为毫秒
*/
public static long getMp4Duration(String videoPath) throws IOException {
IsoFile isoFile = new IsoFile(videoPath);
long lengthInSeconds =
isoFile.getMovieBox().getMovieHeaderBox().getDuration() /
isoFile.getMovieBox().getMovieHeaderBox().getTimescale();
return lengthInSeconds;
}
/**
* 得到语音或视频文件时长,单位秒
* @param filePath
* @return
* @throws IOException
*/
public static long getDuration(String filePath) throws IOException {
String format = getVideoFormat(filePath);
long result = 0;
if("wav".equals(format)){
result = AudioUtil.getDuration(filePath).intValue();
}else if("mp3".equals(format)){
result = AudioUtil.getMp3Duration(filePath).intValue();
}else if("m4a".equals(format)) {
result = VideoUtil.getMp4Duration(filePath);
}else if("mov".equals(format)){
result = VideoUtil.getMp4Duration(filePath);
}else if("mp4".equals(format)){
result = VideoUtil.getMp4Duration(filePath);
}
return result;
}
/**
* 得到语音或视频文件时长,单位秒
* @param filePath
* @return
* @throws IOException
*/
public static long getDuration(String filePath,String format) throws IOException {
long result = 0;
if("wav".equals(format)){
result = AudioUtil.getDuration(filePath).intValue();
}else if("mp3".equals(format)){
result = AudioUtil.getMp3Duration(filePath).intValue();
}else if("m4a".equals(format)) {
result = VideoUtil.getMp4Duration(filePath);
}else if("mov".equals(format)){
result = VideoUtil.getMp4Duration(filePath);
}else if("mp4".equals(format)){
result = VideoUtil.getMp4Duration(filePath);
}
return result;
}
/**
* 得到文件格式
* @param path
* @return
*/
public static String getVideoFormat(String path){
return path.toLowerCase().substring(path.toLowerCase().lastIndexOf(".") + 1);
}
}
3.2 音频文件工具类
package com.blcup.cledu.teach.utils;
import com.coremedia.iso.IsoFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.mp3.MP3AudioHeader;
import org.jaudiotagger.audio.mp3.MP3File;
import java.io.File;
import java.io.IOException;
/**
* @Author xxx
**/
public class AudioUtil {
/**
* 获取mp3语音文件播放时长(秒) mp3
* @param filePath
* @return
*/
public static Float getMp3Duration(String filePath){
try {
File mp3File = new File(filePath);
MP3File f = (MP3File) AudioFileIO.read(mp3File);
MP3AudioHeader audioHeader = (MP3AudioHeader)f.getAudioHeader();
return Float.parseFloat(audioHeader.getTrackLength()+"");
} catch(Exception e) {
e.printStackTrace();
return 0f;
}
}
/**
* 获取mp3语音文件播放时长(秒)
* @param mp3File
* @return
*/
public static Float getMp3Duration(File mp3File){
try {
//File mp3File = new File(filePath);
MP3File f = (MP3File) AudioFileIO.read(mp3File);
MP3AudioHeader audioHeader = (MP3AudioHeader)f.getAudioHeader();
return Float.parseFloat(audioHeader.getTrackLength()+"");
} catch(Exception e) {
e.printStackTrace();
return 0f;
}
}
/**
* 得到pcm文件的毫秒数
*
* pcm文件音频时长计算
* 同图像bmp文件一样,pcm文件保存的是未压缩的音频信息。 16bits 编码是指,每次采样的音频信息用2个字节保存。可以对比下bmp文件用分别用2个字节保存RGB颜色的信息。 16000采样率 是指 1秒钟采样 16000次。常见的音频是44100HZ,即一秒采样44100次。 单声道: 只有一个声道。
*
* 根据这些信息,我们可以计算: 1秒的16000采样率音频文件大小是 2*16000 = 32000字节 ,约为32K 1秒的8000采样率音频文件大小是 2*8000 = 16000字节 ,约为 16K
*
* 如果已知录音时长,可以根据文件的大小计算采样率是否正常。
* @param filePath
* @return
*/
public static long getPCMDurationMilliSecond(String filePath) {
File file = new File(filePath);
//得到多少秒
long second = file.length() / 32000 ;
long milliSecond = Math.round((file.length() % 32000) / 32000.0 * 1000 ) ;
return second * 1000 + milliSecond;
}
}
四、亲测成功
我这里是通过资源id获取资源文件的详细信息的实现类,其中包含对音频时长的处理,注释里有详细标注
@Override
public TeachExerciseFileVO getById(Long id) {
//通过id获取资源详情
TeachExerciseFile teachExerciseFile = baseMapper.selectById(id);
TeachExerciseFileVO teachExerciseFileVO = new TeachExerciseFileVO();
//因为我这里在数据库表中并没有时长字段,所以先把实体类其他信息复制到VO中,再单独插入时长
BeanUtils.copyProperties(teachExerciseFile,teachExerciseFileVO);
// --------------------------------------------------------------------------------------------------开始处理音频时长
//这里的path是一个文件的远程地址,我先获取到文件再存到本地进行操作---如果没有从远程获取文件的前提,本身就在本地的话可以略过
String path = fileProperties.getOss().getEndpoint() + fileProperties.getBucketName()+"/"+teachExerciseFileVO.getPath();
try {
//通过远程url地址建立连接
URL url = new URL(path);
URLConnection connection = url.openConnection();
InputStream inputStream = connection.getInputStream();
//此处的 System.getProperty("java.io.tmpdir" 指的是本地临时文件路径,因为正常开发环境下要适应Windows和Linux等不同环境,所以一般不写死,这里是提前配置过的临时文件路径
FileOutputStream outputStream = new FileOutputStream(System.getProperty("java.io.tmpdir")+File.separator+"a.mp3");
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.close();
inputStream.close();
//-------------------------------------如果文件直接就在本地的话,直接跳到这里,不需要上面的从远程读写操作
//上面的音频工具类中有不同的获取音频时长的方法,此处使用的是通过本地文件路径获取
Float duration = AudioUtil.getMp3Duration(System.getProperty("java.io.tmpdir")+File.separator+"a.mp3");
//----------------------------------------------------------------------------------------------------处理音频时长结束
//将时长插入到VO中
teachExerciseFileVO.setDuration(duration.longValue());
//用完文件后把本地的文件删除掉,正常生产环境需要删,自己研究可以不删
File file = new File(System.getProperty("java.io.tmpdir")+File.separator+"a.mp3");
file.delete();
} catch (Exception e) {
throw new RuntimeException(e);
}
//返回VO
return teachExerciseFileVO;
}
五、No audio header found withina.mp3 错误解决-----可能未报错但是时长为0
1)场景:我有一个接口是通过传Base64编码的方式上传的,我直接把Base64编码解密后使用文件流上传了。
2)原因:Base64 编码是一种将二进制数据转换成文本格式的编码方式,它仅仅是对原始数据的编码,并不包含音频文件的元数据,例如音频头信息、采样率和播放时长等。创文件的不包含读的时候自然读不到!!!
3)解决方案:在不是通过文件形式(MultipartFile类型或者File类型)上传的地方,先写入到本地文件,再以文件形式上传就好了,用完文件删除本地文件即可。
4)修改前代码:
5)修改后代码:
代码版:
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(System.getProperty("java.io.tmpdir") + File.separator + name + "." + suffix);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1){
fileOutputStream.write(buffer,0,bytesRead);
}
fileOutputStream.close();
inputStream.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
六、问题解决----1秒的时长在未出错的情况下读出来是0秒
本人猜测:因为测试了其他文件读取时间都是对的,只有1秒时被读成了0秒,猜测是获取文件时长所需的时间超过1秒,所以还未获取到时长文件就结束了,所以为0秒,这里我是手动设置成了1秒。代码如下:
public static Float getMp3Duration(String filePath){
try {
File mp3File = new File(filePath);
MP3File f = (MP3File) AudioFileIO.read(mp3File);
MP3AudioHeader audioHeader = (MP3AudioHeader)f.getAudioHeader();
int trackLength = audioHeader.getTrackLength();
//原代码是直接获取后返回,我这里手动增加了判断,如果读取没问题却读成了0,就手动设置为1返回
if (trackLength==0){
trackLength = 1;
}
return Float.parseFloat(trackLength+"");
} catch(Exception e) {
e.printStackTrace();
return 0f;
}
}
七、结束
路漫漫其修远兮,吾将上下而求索!
任何疑问欢迎私信指教!