【秒懂音视频开发】13_音频重采样

什么叫音频重采样

音频重采样(Audio Resample):将音频A转换成音频B,并且音频A、B的参数(采样率、采样格式、声道数)并不完全相同。比如:

音频A的参数

采样率:48000

采样格式:f32le

声道数:1

音频B的参数

采样率:44100

采样格式:s16le

声道数:2

为什么需要音频重采样

这里列举一个音频重采样的经典用途。

有些音频编码器对输入的原始PCM数据是有特定参数要求的,比如要求必须是44100_s16le_2。但是你提供的PCM参数可能是48000_f32le_1。这个时候就需要先将48000_f32le_1转换成44100_s16le_2,然后再使用音频编码器对转换后的PCM进行编码。

音频重采样

命令行

通过下面的命令行可以将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 编程

音频重采样需要用到2个库:

swresample

avutil

函数声明

为了让音频重采样功能更加通用,设计成以下函数:

// 音频参数 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); } 函数调用 // 输入参数 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); 函数实现 变量定义

为了简化释放资源的代码,函数中用到了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; 创建重采样上下文 // 创建重采样上下文 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; } 初始化重采样上下文 // 初始化重采样上下文 int ret = swr_init(ctx); if (ret < 0) { ERROR_BUF(ret); qDebug() << "swr_init error:" << errbuf; goto end; } 创建缓冲区 // 创建输入缓冲区 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; } // 将转换后的数据写入到输出文件中 // 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);

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpwxfy.html