数字音频滤波器的简单使用。
音视频处理都是比较复杂的,本章主要讲解javacv转码视频的时候,如果使得音频仅保留人声段,去除不必要的声音,其实很简单,不需要其他peak滤波器,只需要一组低通、高通滤波器即可(lowpass、highpass),接着往下看
图我手绘的有点丑,大概意思就是这样吧,lowpass只通过1000hz以内,highpass只通过800以上,一旦这两家伙合体,就进化成了黑色阴影部分,也就是声音只有800-1000hz,其他频率全部被抛弃了。
接下来直接看代码
这是其中的两个数字音频滤波算法,自由组合,可以用来切除声音
/**
* 双二次滤波器之低通、高通
*
* @author ZJ
*
*/
public class AudioFilter {
double b0, b1, b2, a0, a1, a2;
double x1, x2, y, y1, y2;
/**
* 采样率
*/
private float sample_rate = 44100f;
/**
* 中心频率
*/
private double center_freq = 0;
/**
* Q值
*/
private double Q = 1;
public AudioFilter(float sample_rate, double center_freq) {
super();
this.sample_rate = sample_rate;
this.center_freq = center_freq;
}
public AudioFilter(float sample_rate, double center_freq, double q) {
super();
this.sample_rate = sample_rate;
this.center_freq = center_freq;
Q = q;
}
/**
* 低通滤波
*/
public void initLowpass() {
Q = (Q == 0) ? 1e-9 : Q;
double ov = 2 * Math.PI * center_freq / sample_rate;
double sn = Math.sin(ov);
double cs = Math.cos(ov);
double alpha = sn / (2 * Q);
b0 = (1 - cs) / 2;
b1 = 1 - cs;
b2 = (1 - cs) / 2;
a0 = 1 + alpha;
a1 = -2 * cs;
a2 = 1 - alpha;
gcompute();
}
/**
* 高通滤波
*/
public void initHighpass() {
Q = (Q == 0) ? 1e-9 : Q;
double ov = 2 * Math.PI * center_freq / sample_rate;
double sn = Math.sin(ov);
double cs = Math.cos(ov);
double alpha = sn / (2 * Q);
b0 = (1 + cs) / 2;
b1 = -(1 + cs);
b2 = (1 + cs) / 2;
a0 = 1 + alpha;
a1 = -2 * cs;
a2 = 1 - alpha;
gcompute();
}
/**
* 计算
*/
private void gcompute() {
// a0归一化为1
b0 /= a0;
b1 /= a0;
b2 /= a0;
a1 /= a0;
a2 /= a0;
}
/**
* 过滤
*
* @param x
* @return
*/
public double filter(double x) {
y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
x2 = x1;
x1 = x;
y2 = y1;
y1 = y;
return y;
}
在javacv中使用filter过滤
/**
* java视频转码之音频滤波实现
*
* @author ZJ
*
*/
public class JavacvStreamAudioFilter {
static int sampleFormat;
static int sampleRate;
static int audioChannels;
public static void start() throws IOException, ExecutionException, LineUnavailableException {
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("d:/flv/file.mp4");
grabber.start();
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder("d:/flv/out.flv",
grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
// 设置flv格式
recorder.setFormat("flv");
recorder.setFrameRate(25);// 设置帧率
recorder.setGopSize(25);// 设置gop
recorder.setVideoQuality(1);
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);// 这种方式也可以
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);// 设置音频编码
recorder.start();
CanvasFrame canvas = new CanvasFrame("视频预览");// 新建一个窗口
canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Frame frame = null;
sampleFormat = grabber.getSampleFormat();
sampleRate = grabber.getSampleRate();
audioChannels = grabber.getAudioChannels();
// 初始化扬声器
final AudioFormat audioFormat = new AudioFormat(grabber.getSampleRate(), 16, grabber.getAudioChannels(), true,
true);
final DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
final SourceDataLine soundLine = (SourceDataLine) AudioSystem.getLine(info);
soundLine.open(audioFormat);
soundLine.start();
//线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
// 低通滤波
AudioFilter lowpass = new AudioFilter(sampleRate, 1000);
lowpass.initLowpass();
// 高通
AudioFilter highpass = new AudioFilter(sampleRate, 800);
highpass.initHighpass();
// 只抓取图像画面
for (; (frame = grabber.grab()) != null;) {
// 判断是否是音频帧
if (frame.samples != null) {
// 一般音频都是16位非平面型左右声道在一个buffer中,如果不是得自己解析
ShortBuffer channelSamples = (ShortBuffer) frame.samples[0];
// 扬声器采样数据
channelSamples.rewind();
ByteBuffer bufferToSourceDataLine = ByteBuffer.allocate(channelSamples.capacity() * 2);
// recorder采样数据
channelSamples.rewind();
ShortBuffer bufferToRecorder = ShortBuffer.allocate(channelSamples.capacity());
for (int i = 0; i < channelSamples.capacity(); i++) {
short val = channelSamples.get(i);
/**
* 滤波计算 可能是javacv的缘故,重新编码声音貌似有bug,偶尔会有噪音,我直接用java自己解析就不会,算法是没有问题的
*/
val = (short) lowpass.filter(val);
val = (short) highpass.filter(val);
bufferToSourceDataLine.putShort(val);
bufferToRecorder.put(val);
}
/**
* 写入到扬声器 并准备下一次读取
*/
try {
executor.submit(new Runnable() {
public void run() {
soundLine.write(bufferToSourceDataLine.array(), 0, bufferToSourceDataLine.capacity());
bufferToSourceDataLine.clear();
}
}).get();
} catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
}
bufferToRecorder.rewind();
recorder.recordSamples(sampleRate, audioChannels, bufferToRecorder);
bufferToRecorder.clear();
} else {
// 画面帧直接录制/推流
recorder.record(frame);
// 显示画面
canvas.showImage(frame);
}
}
recorder.close();// close包含stop和release方法。录制文件必须保证最后执行stop()方法,才能保证文件头写入完整,否则文件损坏。
grabber.close();// close包含stop和release方法
canvas.dispose();
soundLine.close();
System.exit(0);
}
public static void main(String[] args) throws IOException, ExecutionException, LineUnavailableException {
start();
}
至此简单的音频滤波器功能实现了。
如果觉得可以,请随手点赞,Thanks♪(・ω・)ノ