最近在做华为摄像头相关的项目,里边需要实现语音广播和语音对讲,涉及到音频数据编码成g711a格式发送.参考了网上不少文章,最终通过javacv 调用ffmgeg实现转码.
先附上命令行转码的方式:
# 转换wav文件为G711A
ffmpeg -i "input.wav" -bitexact -c:a pcm_alaw output.wav
# 转换wav文件为G711U
ffmpeg -i "input.wav" -bitexact -c:a pcm_mulaw output.wav
通过java方式实现如下:
1 maven依赖
<!-- 媒体只用到以下两个,javacv、ffmpeg -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.6</version>
</dependency>
<!-- ffmpeg全平台引入 -->
<!-- <dependency>-->
<!-- <groupId>org.bytedeco</groupId>-->
<!-- <artifactId>ffmpeg-platform</artifactId>-->
<!-- <version>4.4-1.5.6</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>4.4-1.5.6</version>
<classifier>windows-x86_64</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>4.4-1.5.6</version>
<classifier>linux-x86_64</classifier>
</dependency>
2 转换工具类
package com.hisql.iot.javacv.util;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.*;
import org.bytedeco.javacv.FrameRecorder.Exception;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
/**
* 音频参数转换(包含采样率、编码,位数,通道数)
*
* @author fmi110
*/
public class AudioConvertUtil {
private static Logger log = LoggerFactory.getLogger(AudioConvertUtil.class);
/**
* 转换音频数据为g711a格式
*
* @param inputFile
* @return
*/
public static byte[] convert2G711a(byte[] inputFile) {
return convert(inputFile, avcodec.AV_CODEC_ID_PCM_ALAW, 8000, 64000, 1);
}
/**
* 通用音频格式参数转换
*
* @param inputFile -导入音频文件
* @param audioCodec -音频编码
* @param sampleRate -音频采样率
* @param audioBitrate -音频比特率
*/
public static byte[] convert(byte[] inputFile, int audioCodec, int sampleRate, int audioBitrate,
int audioChannels) {
Frame audioSamples = null;
// 音频录制(输出地址,音频通道)
FFmpegFrameRecorder recorder = null;
ByteArrayInputStream inputStream = new ByteArrayInputStream(inputFile);
//抓取器
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputStream);
// 开启抓取器
if (!start(grabber)) {
return null;
}
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
recorder = new FFmpegFrameRecorder(outStream, audioChannels);
recorder.setAudioOption("crf", "0");
recorder.setAudioCodec(audioCodec);
recorder.setAudioBitrate(audioBitrate);
recorder.setAudioChannels(audioChannels);
recorder.setSampleRate(sampleRate);
recorder.setAudioQuality(0);
recorder.setAudioOption("aq", "10");
recorder.setFormat("wav"); // 不指定格式无法写入内存,会报错
// 开启录制器
if (!start(recorder)) {
return null;
}
try {
// 抓取音频
while ((audioSamples = grabber.grab()) != null) {
recorder.setTimestamp(grabber.getTimestamp());
recorder.record(audioSamples);
}
log.info("转换音频成功");
} catch (Exception | org.bytedeco.javacv.FrameGrabber.Exception e) {
log.error("[{}]转换音频失败:{}", inputFile, e.getMessage());
return null;
} finally {
stop(grabber);
stop(recorder);
}
return outStream.toByteArray();
}
/**
* 通用音频格式参数转换
*
* @param inputFile -导入音频文件
* @param outputFile -导出音频文件
* @param audioCodec -音频编码
* @param sampleRate -音频采样率
* @param audioBitrate -音频比特率
*/
public static boolean convert(String inputFile, String outputFile, int audioCodec, int sampleRate, int audioBitrate,
int audioChannels) {
Frame audioSamples = null;
// 音频录制(输出地址,音频通道)
FFmpegFrameRecorder recorder = null;
//抓取器
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile);
// 开启抓取器
if (!start(grabber)) {
return false;
}
recorder = new FFmpegFrameRecorder(outputFile, audioChannels);
recorder.setAudioOption("crf", "0");
recorder.setAudioCodec(audioCodec);
recorder.setAudioBitrate(audioBitrate);
recorder.setAudioChannels(audioChannels);
recorder.setSampleRate(sampleRate);
recorder.setAudioQuality(0);
recorder.setAudioOption("aq", "10");
// 开启录制器
if (!start(recorder)) {
return false;
}
try {
// 抓取音频
while ((audioSamples = grabber.grab()) != null) {
recorder.setTimestamp(grabber.getTimestamp());
recorder.record(audioSamples);
}
log.info("转换音频成功:\r\n\tinput:{}\r\n\toutput:{}", inputFile, outputFile);
} catch (Exception | org.bytedeco.javacv.FrameGrabber.Exception e) {
log.error("[{}]转换音频失败:{}", inputFile, e.getMessage());
return false;
} finally {
stop(grabber);
stop(recorder);
}
return true;
}
public static boolean start(FrameGrabber grabber) {
try {
grabber.start();
return true;
} catch (org.bytedeco.javacv.FrameGrabber.Exception e2) {
try {
log.error("首次打开抓取器失败,准备重启抓取器...");
grabber.restart();
return true;
} catch (org.bytedeco.javacv.FrameGrabber.Exception e) {
try {
log.error("重启抓取器失败,正在关闭抓取器...");
grabber.stop();
} catch (org.bytedeco.javacv.FrameGrabber.Exception e1) {
log.error("停止抓取器失败!");
}
}
}
return false;
}
public static boolean start(FrameRecorder recorder) {
try {
recorder.start();
return true;
} catch (Exception e2) {
try {
log.error("首次打开录制器失败!准备重启录制器...");
recorder.stop();
recorder.start();
return true;
} catch (Exception e) {
try {
log.error("重启录制器失败!正在停止录制器...");
recorder.stop();
} catch (Exception e1) {
log.error("关闭录制器失败!");
}
}
}
return false;
}
public static boolean stop(FrameGrabber grabber) {
try {
grabber.flush();
grabber.stop();
return true;
} catch (org.bytedeco.javacv.FrameGrabber.Exception e) {
return false;
} finally {
try {
grabber.stop();
} catch (org.bytedeco.javacv.FrameGrabber.Exception e) {
log.error("关闭抓取器失败");
}
}
}
public static boolean stop(FrameRecorder recorder) {
try {
recorder.stop();
recorder.release();
return true;
} catch (Exception e) {
return false;
} finally {
try {
recorder.stop();
} catch (Exception e) {
}
}
}
public static void main(String[] args) throws IOException {
//convert("d:\\audio.wav","d:\\audio.mp3", avcodec.AV_CODEC_ID_MP3,8000,16,1); // 转成map3
//convert("d:\\audio.wav", "d:\\audio_g711a.wav", avcodec.AV_CODEC_ID_PCM_ALAW, 8000, 8, 1); // 转成g711a a-law 编码的文件
FileInputStream inputStream = new FileInputStream("D:\\rec-13500ms-16kbps-48000hz.wav");
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
byte[] g711aData = convert(bytes, avcodec.AV_CODEC_ID_PCM_ALAW, 8000, 64000, 1);
new FileOutputStream("d:\\g711a.wav").write(g711aData);
}
}