WAV音频重采样的python实现 音频重采样原理
转载
1 什么叫音频重采样
音频重采样(Audio Resample):将音频A转换成音频B,并且音频A、B的参数(采样率、采样格式、声道数)并不完全相同。比如:
2 为什么需要音频重采样
这里列举一个音频重采样的经典用途。
有些音频编码器对输入的原始PCM数据是有特定参数要求的,比如要求必须是44100_s16le_2。但是你提供的PCM参数可能是48000_f32le_1。这个时候就需要先将48000_f32le_1转换成44100_s16le_2,然后再使用音频编码器对转换后的PCM进行编码。
音频重采样
3 命令行
通过下面的命令行可以将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
|
4 编程
音频重采样需要用到2个库:
4.1 函数声明
为了让音频重采样功能更加通用,设计成以下函数:
| // 音频参数
|
| typedef struct {
|
| const char *filename;
|
| int sampleRate;
|
| AVSampleFormat sampleFmt;
|
| int chLayout;
|
| } ResampleAudioSpec;
|
| |
| class FFmpegs {
|
| public:
|
| static void resampleAudio(ResampleAudioSpec &in,
|
| ResampleAudioSpec &out);
|
| |
| static void resampleAudio(const char *inFilename,
|
| int inSampleRate,
|
| AVSampleFormat inSampleFmt,
|
| int inChLayout,
|
| |
| const char *outFilename,
|
| int outSampleRate,
|
| AVSampleFormat outSampleFmt,
|
| int outChLayout);
|
| };
|
| |
| // 导入头文件
|
| extern "C" {
|
| #include <libswresample/swresample.h>
|
| #include <libavutil/avutil.h>
|
| }
|
| |
| // 处理错误码
|
| #define ERROR_BUF(ret) \
|
| char errbuf[1024]; \
|
| av_strerror(ret, errbuf, sizeof (errbuf));
|
| |
| void FFmpegs::resampleAudio(ResampleAudioSpec &in,
|
| ResampleAudioSpec &out) {
|
| resampleAudio(in.filename, in.sampleRate, in.sampleFmt, in.chLayout,
|
| out.filename, out.sampleRate, out.sampleFmt, out.chLayout);
|
| }
|
4.2 函数调用
| // 输入参数
|
| ResampleAudioSpec in;
|
| in.filename = "F:/44100_s16le_2.pcm";
|
| in.sampleFmt = AV_SAMPLE_FMT_S16;
|
| in.sampleRate = 44100;
|
| in.chLayout = AV_CH_LAYOUT_STEREO;
|
| |
| // 输出参数
|
| ResampleAudioSpec out;
|
| out.filename = "F:/48000_f32le_1.pcm";
|
| out.sampleFmt = AV_SAMPLE_FMT_FLT;
|
| out.sampleRate = 48000;
|
| out.chLayout = AV_CH_LAYOUT_MONO;
|
| |
| // 进行音频重采样
|
| FFmpegs::resampleAudio(in, out);
|
4.3 函数实现
4.3.1 变量定义
为了简化释放资源的代码,函数中用到了goto语句,所以把需要用到的变量都定义到了前面。
| // 文件名
|
| 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);
|
| // 缓冲区的样本数量(AV_ROUND_UP是向上取整)
|
| int outSamples = av_rescale_rnd(outSampleRate, inSamples, inSampleRate, AV_ROUND_UP);
|
| |
| /*
|
| inSampleRate inSamples
|
| ------------- = -----------
|
| outSampleRate outSamples
|
| |
| outSamples = outSampleRate * inSamples / inSampleRate
|
| */
|
| |
| // 返回结果
|
| int ret = 0;
|
4.3.2 创建重采样上下文
| // 创建重采样上下文
|
| 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;
|
| }
|
4.3.3 初始化重采样上下文
| // 初始化重采样上下文
|
| int ret = swr_init(ctx);
|
| if (ret < 0) {
|
| ERROR_BUF(ret);
|
| qDebug() << "swr_init error:" << errbuf;
|
| goto end;
|
| }
|
4.3.4 创建缓冲区
| // 创建输入缓冲区
|
| 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;
|
| }
|
4.3.5 读取文件数据
| // 打开文件
|
| 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;
|
| }
|
| |
| // 将转换后的数据写入到输出文件中
|
| // outData[0] == *outData
|
| outFile.write((char *) outData[0], ret * outBytesPerSample);
|
| }
|
4.3.6 刷新输出缓冲区
| // 检查一下输出缓冲区是否还有残留的样本(已经重采样过的,转换过的)
|
| while ((ret = swr_convert(ctx,
|
| outData, outSamples,
|
| nullptr, 0)) > 0) {
|
| outFile.write((char *) outData[0], ret * outBytesPerSample);
|
| }
|
4.3.7 回收释放资源
| 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);
|
本文章为转载内容,我们尊重原作者对文章享有的著作权。如有内容错误或侵权问题,欢迎原作者联系我们进行内容更正或删除文章。