最近在做无人机项目时,需要用到pcm和opus格式的音频文件作为喊话器的输入文件,故需要将常用的音频文件(mp3/acc/wav)等转化为指定格式。最终经过调研,决定采用java+ffmpeg的形式开发一个音频转换服务供其他服务使用,主要提供的功能为常见音频格式的转换。
由于ffmpeg本身不支持opus格式文件的处理,需要自编译将相关的libopus的动态库打包进去,故采用源码形式编译,先去官网下载对应版本,博主使用centos系统,使用的ffmpeg版本为4.4.6
shell展开代码wget https://ffmpeg.org/releases/ffmpeg-4.4.6.tar.xz sudo tar -xJvf ffmpeg-4.4.6.tar.xz -C /opt
shell展开代码//安装基本库 yum install autoconf automake bzip2 bzip2-devel cmake freetype-devel gcc gcc-c++ git libtool make pkgconfig zlib-devel //安装nasm yum install nasm //安装yasm yum install yasm //安装pkg-config yum install pkg-config
这里注意,如果编译时出现找不到pkg-config,则需配置下环境变量
shell展开代码export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH
接下来安装libopus库,采用源码安装模式,去libopus官网下载,版本1.4即可,解压编译后在/usr/lib内即出现对应的库
shell展开代码wget https://link.zhihu.com/?target=https%3A//ftp.osuosl.org/pub/xiph/releases/opus/opus-1.4.tar.gz //解压 sudo tar -xzvf opus-1.4.tar.gz -C /opt //进入目录进行编译 cd opus-1.4 ./configure make & make install
如果要用fflay相关的功能,需下载sdl相关的库(注意服务器必须有声卡才能使用ffplay相关功能,博主服务器没有声卡故该功能没有验证)
shell展开代码wget https://github.com/libsdl-org/SDL/releases/download/release-2.32.6/SDL2-2.32.6.tar.gz tar -xvzf SDL2-2.32.6.tar.gz -C /opt cd SDL2-2.32.6 ./configure make & make install
为支持mp3,需安装lame库
shell展开代码wget http://sourceforge.net/projects/lame/files/latest/download -O lame-3.100.tar.gz tar -xzvf lame-3.100.tar.gz -C /opt cd lame-3.100 ./configure make & make install
首先进入ffmpeg安装目录,执行以下命令
shell展开代码./configure --prefix=/usr/local/ffmpeg/4.4.6 --enable-shared --enable-libmp3lame --enable-ffplay --enable-libopus --enable-encoder=libopus --enable-encoder=opus make & make install
名词解释
shell展开代码export PATH=$PATH:/usr/local/ffmpeg/4.4.6/bin
说到音频转换,先提一下音频几个相关的概念
以上概念基本够用,接下来执行ffmpeg命令
shell展开代码ffmpeg -f sl6le -i inputi.mp3 -f s16le -ar 16000 -ac 1 output.pcm
解释:
shell展开代码ffmpeg -f sl6le -i inputi.pcm -f s16le -ar 16000 -ac 1 -c:a libopus -b:a 24k output.opus
解释:
shell展开代码ffplay -f s16le oupput.pcm ffplay -f s16le oupput.opus
注:一定要指定 -f s16le 不然无法成功
java展开代码public String convertPcmToOpus(String pcmUrl) {
// 确保输出目录存在
File outputDirectory = new File(OUTPUT_DIR);
if (!outputDirectory.exists()) {
outputDirectory.mkdirs();
}
//生成文件唯一id
String uuid = UuidUtil.getUUID();
String tempFilePath = OUTPUT_DIR + "/" + uuid + ".pcm";
String outputFilePath = OUTPUT_DIR + "/" + uuid + ".opus";
try {
// 下载PCM文件到临时位置
try (var in = new URL(pcmUrl).openStream()) {
Files.copy(in, Paths.get(tempFilePath), StandardCopyOption.REPLACE_EXISTING);
}
// 构建FFmpeg命令
String[] command = {
FFMPEG_PATH,
"-f", "s16le",
"-i", tempFilePath, //pcm文件路径
"-f", "s16le", // PCM格式为16位小端
"-ar", "16000", // 采样率16kHz
"-ac", "1", // 单声道
"-c:a", "libopus", // Opus编码器
"-b:a", "24k", // 比特率24kbps
// "-vbr", "on", // 启用可变比特率
// "-compression_level", "10", // 最高压缩级别
outputFilePath //输出文件路径
};
// 执行命令
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
// 读取输出流
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("FFmpeg处理失败,退出码: " + exitCode);
}
return minioOpusUploader.uploadFileToMinio(outputFilePath,"opus");
} catch (Exception e) {
log.error("转化失败,{}", e);
throw new RuntimeException("PCM转Opus失败", e);
} finally {
// 删除临时文件
try {
Files.deleteIfExists(Paths.get(tempFilePath));
Files.deleteIfExists(Paths.get(outputFilePath));
} catch (Exception e) {
log.error("删除临时文件失败,{}", e);
}
}
}
java展开代码public String convertToPcm(String url, String fileFormat) {
// 确保输出目录存在
File outputDirectory = new File(OUTPUT_DIR);
if (!outputDirectory.exists()) {
outputDirectory.mkdirs();
}
//生成文件唯一id
String uuid = UuidUtil.getUUID();
String tempFilePath = OUTPUT_DIR + "/" + uuid + "." + fileFormat;
String outputFilePath = OUTPUT_DIR + "/" + uuid + ".pcm";
try {
// 下载PCM文件到临时位置
try (var in = new URL(url).openStream()) {
Files.copy(in, Paths.get(tempFilePath), StandardCopyOption.REPLACE_EXISTING);
}
// 构建FFmpeg命令
String[] command = {
FFMPEG_PATH,
"-f", "s16le",
"-i", tempFilePath, //文件路径
"-f", "s16le", // PCM格式为16位小端
"-ar", "16000", // 采样率16kHz
"-ac", "1", // 单声道
outputFilePath //输出文件路径
};
// 执行命令
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
// 读取输出流
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("FFmpeg处理失败,退出码: " + exitCode);
}
return minioOpusUploader.uploadFileToMinio(outputFilePath,"pcm");
} catch (Exception e) {
log.error("转化失败,{}", e);
throw new RuntimeException(fileFormat+"转pcm失败", e);
} finally {
// 删除临时文件
try {
Files.deleteIfExists(Paths.get(tempFilePath));
Files.deleteIfExists(Paths.get(outputFilePath));
} catch (Exception e) {
log.error("删除临时文件失败,{}", e);
}
}
}
上传minio的代码
java展开代码public String uploadFileToMinio(String filePath,String format) {
String objectName = "/wayline/audio/sample_" + System.currentTimeMillis() + "."+format;
try {
// 检查存储桶是否存在
boolean found = minioClient.bucketExists(BucketExistsArgs.builder()
.bucket(BUCKET_NAME)
.build());
if (!found) {
minioClient.makeBucket(MakeBucketArgs.builder()
.bucket(BUCKET_NAME)
.build());
}
// 上传文件
minioClient.uploadObject(
UploadObjectArgs.builder()
.bucket(BUCKET_NAME)
.object(objectName)
.filename(filePath)
.build());
System.out.println("文件上传成功: " + filePath);
// 获取7天有效期的下载URL
String url = minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(BUCKET_NAME)
.object(objectName)
.expiry(60 * 60 * 24 * 7)
.build());
System.out.println("下载URL: " + url);
return url;
} catch (MinioException | InvalidKeyException | NoSuchAlgorithmException | IOException e) {
System.err.println("上传失败: " + e.getMessage());
throw new RuntimeException("上传minio失败:"+e.getMessage());
}
}
本文作者:刘涛
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!