【流媒体】FFmpeg转换音视频格式
2025-09-18
音视频学习
00
请注意,本文编写于 44 天前,最后修改于 37 天前,其中某些信息可能已经过时。

目录

一、ffmpeg的安装
1、安装依赖库
2、编译ffmpeg
3、配置环境变量
二、Ffmpeg的使用
1、将MP3转化为pcm格式文件
2、将pcm转化为opus
3、测试是否转化成功
三、java代码实现

最近在做无人机项目时,需要用到pcm和opus格式的音频文件作为喊话器的输入文件,故需要将常用的音频文件(mp3/acc/wav)等转化为指定格式。最终经过调研,决定采用java+ffmpeg的形式开发一个音频转换服务供其他服务使用,主要提供的功能为常见音频格式的转换。

一、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

1、安装依赖库

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

2、编译ffmpeg

首先进入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

名词解释

  • --prefix=/usr/local/ffmpeg/4.4.6‌ 指定安装路径为 /usr/local/ffmpeg/4.4.6,编译后的二进制文件、库和头文件将安装到此目录。
  • --enable-shared 启用动态链接库(.so 文件)的编译,允许 FFmpeg 以共享库形式被其他程序调用。
  • --enable-libmp3lame 启用 MP3 编码支持(依赖 libmp3lame 库),允许 FFmpeg 处理 MP3 音频格式。
  • --enable-ffplay 编译 FFmpeg 自带的简易播放器 ffplay,用于测试和播放音视频文件。
  • --enable-libopus 启用 Opus 音频编解码支持(依赖 libopus 库),支持 Opus 格式的编解码。
  • --enable-encoder=libopus 和 --enable-encoder=opus‌ 显式启用 Opus 编码器,确保编译时包含 Opus 音频编码功能(部分系统可能需要明确指定)。

3、配置环境变量

shell
展开代码
export PATH=$PATH:/usr/local/ffmpeg/4.4.6/bin

二、Ffmpeg的使用

说到音频转换,先提一下音频几个相关的概念

  • 量化位数(Bit Depth):每个样本的二进制位数,决定动态范围和信噪比。位数越高,声音细节越细腻‌,常见值8bit(256级量化)、16bit(CD标准,65536级)、24bit/32bit(专业录音)‌
  • 声道数(Channels):音频信号的独立通道数,如单声道(1)、立体声(2)或多声道(5.1环绕声)‌。声道数越多,空间感越强,但数据量也越大‌。
  • 比特率(Bitrate):单位时间传输的二进制数据量(kbps),计算公式为:采样频率 × 量化位数 × 声道数‌。直接影响音质与文件大小,如MP3常用128-320kbps‌
  • 采样频率(Sampling Rate):秒采集声音样本的次数,单位为Hz。根据奈奎斯特采样理论,采样频率需至少为音频信号最高频率的两倍(如20kHz人耳上限需40kHz采样)‌。8kHz(电话语音)、44.1kHz(CD音质)、48kHz(专业音频)以及96kHz/192kHz(高解析度音频)‌

以上概念基本够用,接下来执行ffmpeg命令

1、将MP3转化为pcm格式文件

shell
展开代码
ffmpeg -f sl6le -i inputi.mp3 -f s16le -ar 16000 -ac 1 output.pcm

解释:

  • -f sl6le 量化位数16bit
  • -ar 16000 采样频率16kHz
  • -ac 1 单通道

2、将pcm转化为opus

shell
展开代码
ffmpeg -f sl6le -i inputi.pcm -f s16le -ar 16000 -ac 1 -c:a libopus -b:a 24k output.opus

解释:

  • -f sl6le 量化位数16bit
  • -ar 16000 采样频率16kHz
  • -ac 1 单通道
  • -c:a libopus 指定编码器
  • -b:a 24k 指定比特率为24kbps

3、测试是否转化成功

shell
展开代码
ffplay -f s16le oupput.pcm ffplay -f s16le oupput.opus

注:一定要指定 -f s16le 不然无法成功

三、java代码实现

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 许可协议。转载请注明出处!