前言
音频重采样的经典用途:
有些音频编码器对输入的原始PCM数据是有特定参数要求的,比如要求必须是44100_s16le_2。但是你提供的PCM参数可能是48000_f32le_1。这个时候就需要先将48000_f32le_1转换成44100_s16le_2,然后再使用音频编码器对转换后的PCM进行编码。
测试环境:
- ffmpeg的shared版本
- windows环境
- qt5.12
- sdl2.0.22(mingw编译器)
命令行实现音频重采样:
通过下面的命令行可以将44100_s16le_2转换成48000_f32le_1
ffmpeg -ar 44100 -ac 2 -f s16le -i 44100_s16le_2.pcm -ar 48000 -ac 1 -f f32le 48000_f32le_1.pcm
编程实现:
重采样的主要思路是看创建重采样上下文的方法
struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
int64_t in_ch_layout, enum AVSampleFormat in_sample_fmt, int in_sample_rate,
int log_offset, void *log_ctx);
完整代码:(我这里是将功能封装在线程里,其中还考虑了线程的关闭问题)
audiothread_resample.h
#ifndef AUDIOSTARTTHREAD_H
#define AUDIOSTARTTHREAD_H
#include <QObject>
#include <QThread>
#include <SDL2/SDL.h>
class AudioStartThread : public QThread
{
Q_OBJECT
public:
explicit AudioStartThread(QObject *parent = nullptr);
~AudioStartThread();
private:
void run() override;
signals:
};
#endif // AUDIOSTARTTHREAD_H
audiothread_resample.cpp
#include "audiothread_resample.h"
#include <QDebug>
#include <QFile>
AudioThreadResample::AudioThreadResample(QObject *parent) : QThread(parent) {
// 当监听到线程结束时(finished),就调用deleteLater回收内存
connect(this, &AudioThreadResample::finished,
this, &AudioThreadResample::deleteLater);
}
AudioThreadResample::~AudioThreadResample() {
// 断开所有的连接
disconnect();
// 内存回收之前,正常结束线程
requestInterruption();
// 安全退出
quit();
wait();
qDebug() << this << "析构(内存被回收)";
}
void AudioThreadResample::resampleAudio(ResampleAudioSpec &in, ResampleAudioSpec &out)
{
resampleAudio(in.filename, in.sampleRate, in.sampleFmt, in.chLayout,
out.filename, out.sampleRate, out.sampleFmt, out.chLayout);
}
void AudioThreadResample::resampleAudio(const char *inFilename, int inSampleRate, AVSampleFormat inSampleFmt, int inChLayout, const char *outFilename, int outSampleRate, AVSampleFormat outSampleFmt, int outChLayout)
{
// 向下取整,AV_ROUND_DOWN(2.66) = 2
// qDebug() << av_rescale_rnd(8, 1, 3, AV_ROUND_DOWN);
// 向上取整,AV_ROUND_UP(1.25) = 2
// qDebug() << av_rescale_rnd(5, 1, 4, AV_ROUND_UP);
// 文件名
QFile inFile(inFilename);
QFile outFile(outFilename);
// 输入缓冲区
// 指向缓冲区的指针
uint8_t **inData = nullptr;
// 缓冲区的大小
int inLinesize = 0;
// 声道数
int inChs = av_get_channel_layout_nb_channels(inChLayout);
// 一个样本的大小
int inBytesPerSample = inChs * av_get_bytes_per_sample(inSampleFmt);
// 缓冲区的样本数量
int inSamples = 1024;
// 读取文件数据的大小
int len = 0;
// 输出缓冲区
// 指向缓冲区的指针
uint8_t **outData = nullptr;
// 缓冲区的大小
int outLinesize = 0;
// 声道数
int outChs = av_get_channel_layout_nb_channels(outChLayout);
// 一个样本的大小
int outBytesPerSample = outChs * av_get_bytes_per_sample(outSampleFmt);
// 缓冲区的样本数量
int outSamples = av_rescale_rnd(outSampleRate, inSamples, inSampleRate, AV_ROUND_UP);
/*
inSampleRate inSamples
------------- = -----------
outSampleRate outSamples
outSamples = outSampleRate * inSamples / inSampleRate
*/
qDebug() << "输入缓冲区" << inSampleRate << inSamples;
qDebug() << "输出缓冲区" << outSampleRate << outSamples;
// 返回结果
int ret = 0;
// 创建重采样上下文
SwrContext *ctx = swr_alloc_set_opts(nullptr,
// 输出参数
outChLayout, outSampleFmt, outSampleRate,
// 输入参数
inChLayout, inSampleFmt, inSampleRate,
0, nullptr);
if (!ctx) {
qDebug() << "swr_alloc_set_opts error";
goto end;
}
// 初始化重采样上下文
ret = swr_init(ctx);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "swr_init error:" << errbuf;
goto end;
}
/* 指针类型(64bit,8个字节)
int *;
double *;
void *;
int **;
int ***;
int ******;
*/
// int *p;
// *(p + i) == p[i]
// *(p + 0) == p[0]
// *p == p[0]
// int *p = new int[15];
// int *p = av_calloc(15, sizeof (int));
// int **pp = av_calloc(7, sizeof (int *));
// uint8_t **inData = av_calloc(1, sizeof(uint8_t *));
// 创建输入缓冲区
ret = av_samples_alloc_array_and_samples(
&inData,
&inLinesize,
inChs,
inSamples,
inSampleFmt,
1);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "av_samples_alloc_array_and_samples error:" << errbuf;
goto end;
}
// 创建输出缓冲区
ret = av_samples_alloc_array_and_samples(
&outData,
&outLinesize,
outChs,
outSamples,
outSampleFmt,
1);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "av_samples_alloc_array_and_samples error:" << errbuf;
goto end;
}
// 打开文件
if (!inFile.open(QFile::ReadOnly)) {
qDebug() << "file open error:" << inFilename;
goto end;
}
if (!outFile.open(QFile::WriteOnly)) {
qDebug() << "file open error:" << outFilename;
goto end;
}
// 读取文件数据
// inData[0] == *inData
while ((len = inFile.read((char *) inData[0], inLinesize)) > 0) {
// 读取的样本数量
inSamples = len / inBytesPerSample;
// 重采样(返回值转换后的样本数量)
ret = swr_convert(ctx,
outData, outSamples,
(const uint8_t **) inData, inSamples
);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "swr_convert error:" << errbuf;
goto end;
}
// int size = av_samples_get_buffer_size(nullptr, outChs, ret, outSampleFmt, 1);
// outFile.write((char *) outData[0], size);
// 将转换后的数据写入到输出文件中
// outData[0] == *outData
outFile.write((char *) outData[0], ret * outBytesPerSample);
}
// 检查一下输出缓冲区是否还有残留的样本(已经重采样过的,转换过的)
while ((ret = swr_convert(ctx,
outData, outSamples,
nullptr, 0)) > 0) {
outFile.write((char *) outData[0], ret * outBytesPerSample);
}
end:
// 释放资源
// 关闭文件
inFile.close();
outFile.close();
// 释放输入缓冲区
if (inData) {
av_freep(&inData[0]);
}
av_freep(&inData);
// 释放输出缓冲区
if (outData) {
av_freep(&outData[0]);
}
av_freep(&outData);
// 释放重采样上下文
swr_free(&ctx);
// void *ptr = malloc(100);
// freep(&ptr);
// free(ptr);
// ptr = nullptr;
}
//void freep(void **ptr) {
// free(*ptr);
// *ptr = nullptr;
//}
void AudioThreadResample::run() {
// 44100_s16le_2 -> 48000_f32le_2 -> 48000_s32le_1 -> 44100_s16le_2
ResampleAudioSpec ras1;
ras1.filename = "E:/media/44100_s16le_2.pcm";
ras1.sampleFmt = AV_SAMPLE_FMT_S16;
ras1.sampleRate = 44100;
ras1.chLayout = AV_CH_LAYOUT_STEREO;
ResampleAudioSpec ras2;
ras2.filename = "E:/media/48000_f32le_1.pcm";
ras2.sampleFmt = AV_SAMPLE_FMT_FLT;
ras2.sampleRate = 48000;
ras2.chLayout = AV_CH_LAYOUT_MONO;
ResampleAudioSpec ras3;
ras3.filename = "E:/media/48000_s32le_1.pcm";
ras3.sampleFmt = AV_SAMPLE_FMT_S32;
ras3.sampleRate = 48000;
ras3.chLayout = AV_CH_LAYOUT_MONO;
ResampleAudioSpec ras4 = ras1;
ras4.filename = "E:/media/44100_s16le_2_new.pcm";
resampleAudio(ras1, ras2);
resampleAudio(ras2, ras3);
resampleAudio(ras3, ras4);
}
注意:该代码对可能出现的问题都进行了一定的优化,读者使用时只需关注关键代码
线程调用
void MainWindow::on_pushButton_resample_clicked()
{
audioThreadResample = new AudioThreadResample(this);
audioThreadResample->start();
}
注意:mainwindow.h文件中提前声明了以下全局变量
AudioThreadResample *audioThreadResample = nullptr;
注意:本文为个人记录,新手照搬可能会出现各种问题,请谨慎使用
码字不易,如果这篇博客对你有帮助,麻烦点赞收藏,非常感谢!有不对的地方