音视频-PCM重采样
2021-07-05 本文已影响0人
li_礼光
相关函数
swr_alloc_set_opts
swr_init
av_samples_alloc_array_and_samples
swr_convert
重采样逻辑
源文件 ==》输入缓冲区 ==》输出缓冲区 ==》输入文件
也就是
- 1.创建上下文
- 2.初始化上下文
- 3.创建输入缓冲区
- 4.创建输出缓冲区
- 5.重采样
重采样 swr_convert
/** Convert audio 转换音频。
*
* in 和 in_count 可以设置为 0 以在最后刷新最后几个样本。
*
* 如果提供的输入多于输出空间,则输入将被缓冲。 您可以通过使用 swr_get_out_samples() 为给定数量的输入样本检索所需输出样本数量的上限来避免这种缓冲。 只要有可能,转换将直接运行而无需复制。
*
* @param s 分配的 Swr 上下文,带参数设置
* @param out 输出缓冲区,在打包音频的情况下只需要设置第一个
* @param out_count 一个通道的样本中可用于输出的空间量
* @param in 输入缓冲区,在打包音频的情况下只需要设置第一个
* @param in_count 一个通道中可用的输入样本数
*
* @return 每个通道输出的样本数,错误时为负值
*/
int swr_convert(struct SwrContext *s,
uint8_t **out,
int out_count,
const uint8_t **in ,
int in_count);
SwrContext 上下文结构体
/**
* libswresample 上下文。 与 libavcodec 和 libavformat 不同,这种结构是不透明的。
* 这意味着如果你想设置选项,你必须使用@ref avoptions API,并且不能直接为结构的成员设置值。
*/
typedef struct SwrContext SwrContext;
重点:SwrContext这种结构是不透明的, 这意味着如果你想设置选项,你必须使用@ref avoptions API
生成并配置SwrContext 上下文swr_alloc_set_opts
/**
* 根据需要分配 SwrContext 并设置/重置通用参数。
*
* 此函数不需要使用 swr_alloc() 分配 s。 另一方面,swr_alloc() 可以使用 swr_alloc_set_opts() 在分配的上下文中设置参数。
*
* @param s 现有的 Swr 上下文(如果可用),否则为 NULL
* @param out_ch_layout 输出通道布局 (AV_CH_LAYOUT_*)
* @param out_sample_fmt 输出样本格式 (AV_SAMPLE_FMT_*)。
* @param out_sample_rate 输出采样率(频率以赫兹为单位)
* @param in_ch_layout 输入通道布局 (AV_CH_LAYOUT_*)
* @param in_sample_fmt 输入样本格式 (AV_SAMPLE_FMT_*)。
* @param in_sample_rate 输入采样率(以Hz为单位的频率)
* @param log_offset 日志级别偏移
* @param log_ctx 父日志上下文,可以为 NULL
*
* @参见 swr_init(), swr_free()
* @return NULL 出错,否则分配上下文
*/
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);
这里也可以拆分为 swr_alloc 和 swr_set_opts
初始化上下文swr_init
/**
* 设置用户参数后初始化上下文。
* @note 必须使用 AVOption API 配置上下文。
*
* @see av_opt_set_int()
* @see av_opt_set_dict()
*
* @param[in,out] s 要初始化的 Swr 上下文
* @return AVERROR 失败时的错误代码。
*/
int swr_init(struct SwrContext *s);
输入输出缓冲区av_samples_alloc_array_and_samples
/**
* 分配一个数据指针数组,为 nb_samples 采样缓冲区样本,并相应地填充数据指针和线宽。
*
* 这与 av_samples_alloc() 相同,但也分配数据指针数组。
*
* @see av_samples_alloc()
*/
int av_samples_alloc_array_and_samples(uint8_t ***audio_data,
int *linesize,
int nb_channels,
int nb_samples,
enum AVSampleFormat sample_fmt,
int align);
参照av_samples_alloc()
/**
* 为 nb_samples 样本分配一个样本缓冲区,并相应地填充数据指针和 linesize。
* 分配的样本缓冲区可以通过使用 av_freep(&audio_data[0]) 释放分配的数据将被初始化为静音。
*
* @see enum AVSampleFormat
* AVSampleFormat 的文档描述了数据布局。
*
* @param [out] audio_data 数组要填充每个通道的指针
* @param [out] linesize 对齐的音频缓冲区大小,可能为 NULL
* @param nb_channels 音频通道数
* @param nb_samples 每个通道的样本数
* @param sample_fmt 采样格式
* @param align 缓冲区大小对齐(0 = 默认,1 = 无对齐)
* @return >=0 成功或失败时返回负错误代码
* @todo 如果在下一次开辟内存空间成功,则返回已分配缓冲区的大小
* @see av_samples_fill_arrays()
* @see av_samples_alloc_array_and_samples()
*/
int av_samples_alloc(uint8_t **audio_data,
int *linesize,
int nb_channels,
int nb_samples,
enum AVSampleFormat sample_fmt,
int align);
代码实现 Demo
typedef struct {
const char *filename;
int sampleRate;
AVSampleFormat sampleFmt;
int chLayout;
} ResampleAudioSpec;
#ifdef Q_OS_WIN
#define IN_PCM_FILEPATH "G:/Resource/record_to_pcm.pcm"
#define OUT_PCM_FILEPATH "G:/Resource/record_to_pcm2.pcm"
#else
#define IN_PCM_FILEPATH "/Users/liliguang/Desktop/record_to_pcm.pcm"
#define OUT_PCM_FILEPATH "/Users/liliguang/Desktop/record_to_pcm2.pcm"
#endif
void ResampleThread::run() {
// 源文件 ==》输入缓冲区 ==》 输出缓冲区 ==》 输出文件
//输入音频文件上下文
ResampleAudioSpec inSpec;
inSpec.filename = IN_PCM_FILEPATH;
inSpec.sampleFmt = AV_SAMPLE_FMT_S16;
inSpec.sampleRate = 44100;
inSpec.chLayout = AV_CH_LAYOUT_STEREO;
//输出音频文件上下文
ResampleAudioSpec outSpec;
outSpec.filename = OUT_PCM_FILEPATH;
outSpec.sampleFmt = AV_SAMPLE_FMT_S16;
outSpec.sampleRate = 48000;
outSpec.chLayout = AV_CH_LAYOUT_STEREO;
audio_resampleS(inSpec, outSpec);
}
void audio_resampleS (ResampleAudioSpec inSpec, ResampleAudioSpec outSpec) {
// 前置变量声明 (因为goto语句后面不能定义变量)
// 初始化上下文ret
int initRet = 0;
// 缓冲区大小
int inBufferSize = 0;
int outBufferSize = 0;
// 文件操作
QFile inFile(inSpec.filename);
QFile outFile(outSpec.filename);
int inOpenFileRet = 0;
int outOpenFileRet = 0;
// 读取输出文件数据的大小
int inDataLen = 0;
// 每个样本的总大小
int inBytesPerSample = ( av_get_channel_layout_nb_channels(inSpec.chLayout) * av_get_bytes_per_sample(inSpec.sampleFmt) ) ;
int outBytesPerSample = (av_get_channel_layout_nb_channels(outSpec.chLayout) * av_get_bytes_per_sample(outSpec.sampleFmt)) ;
// 重采样ret
int swrConvertRet = 0;
//重采样目标 44100 ==> 48000
/*
inSampleRate (48000) in_nb_samples (输入缓冲区大小)
------------------------ = ------------------------
outSampleRate (44100) out_nb_samples (输出缓冲区大小)
输出缓冲区大小计算公式 out_nb_samples = outSampleRate * in_nb_samples / inSampleRate
输入缓冲区大小计算公式 in_nb_samples = inSampleRate * out_nb_samples / outSampleRate
*/
//输入缓冲区
uint8_t **inData = nullptr;
int inlinesize = 0;
int in_nb_channels = av_get_channel_layout_nb_channels(inSpec.chLayout);
int in_nb_samples = 1024 ;
enum AVSampleFormat in_sample_fmt = inSpec.sampleFmt;
int in_align = 0;
//输出缓冲区
uint8_t **outData = nullptr;
int outlinesize = 0;
int out_nb_channels = av_get_channel_layout_nb_channels(outSpec.chLayout);
int out_nb_samples = av_rescale_rnd(outSpec.sampleRate, in_nb_samples, inSpec.sampleRate, AV_ROUND_UP);
enum AVSampleFormat out_sample_fmt = outSpec.sampleFmt;
int out_align = 0;
qDebug() << "输入缓冲区" << inSpec.sampleRate << in_nb_samples;
qDebug() << "输出缓冲区" << outSpec.sampleRate << out_nb_samples;
//===================================================================
//===================================================================
//=========================== 主要步骤 ===========================
//===================================================================
//===================================================================
// 1. 创建上下文
SwrContext *swrCtx = swr_alloc_set_opts(nullptr,
outSpec.chLayout,
outSpec.sampleFmt,
outSpec.sampleRate,
inSpec.chLayout,
inSpec.sampleFmt,
inSpec.sampleRate,
0,
nullptr
);
CHECK_IF_ERROR_BUF_END(!swrCtx, "swr_alloc_set_opts");
// 2. 初始化上下文
initRet = swr_init(swrCtx);
CHECK_IF_ERROR_BUF_END(initRet, "swr_init");
// 3. 输入缓冲区
qDebug() << "输入outBufferRet" << inData << inlinesize << in_nb_channels << in_nb_samples << in_sample_fmt;
inBufferSize = av_samples_alloc_array_and_samples (&inData,
&inlinesize,
in_nb_channels,
in_nb_samples,
in_sample_fmt,
in_align);
CHECK_IF_ERROR_BUF_END(inBufferSize < 0, "av_samples_alloc_array_and_samples inBufferSize");
// 4. 输出缓冲区
qDebug() << "输出outBufferRet" << outData << outlinesize << out_nb_channels << out_nb_samples << out_sample_fmt;
outBufferSize = av_samples_alloc_array_and_samples ( &outData,
&outlinesize,
out_nb_channels,
out_nb_samples,
out_sample_fmt,
out_align);
CHECK_IF_ERROR_BUF_END(outBufferSize < 0, "av_samples_alloc_array_and_samples outBufferSize");
// 文件操作
inOpenFileRet = !inFile.open(QFile::ReadOnly);
outOpenFileRet = !outFile.open(QFile::WriteOnly);
CHECK_IF_ERROR_BUF_END(inOpenFileRet, "inFile.open inSpec.filename");
CHECK_IF_ERROR_BUF_END(outOpenFileRet, "outFile.open outSpec.filename");
qDebug() << "av_get_channel_layout_nb_channels(outSpec.chLayout)" << av_get_channel_layout_nb_channels(outSpec.chLayout) ;
qDebug() << "av_get_bytes_per_sample(outSpec.sampleFmt)" << av_get_bytes_per_sample(outSpec.sampleFmt);
// 5.重采样
while ((inDataLen = inFile.read((char *) inData[0], inlinesize)) > 0) {
// 读取的样本数量
qDebug() << "in_nb_samples" << in_nb_samples << "out_nb_samples" << out_nb_samples ;
qDebug() << "inDataLen" << (inDataLen / inBytesPerSample) ;
// 有的代码Demo用的是inDataLen / inBytesPerSample 来计算swr_convert中的in_count,我发现目前和in_nb_samples 是一样的大小的
// swrConvertRet:每个通道输出的样本数,错误时为负值
swrConvertRet = swr_convert(swrCtx,
outData,
out_nb_samples,
(const uint8_t **) inData,
in_nb_samples);
// qDebug() << "swrConvertRet" << outDataLen << swrConvertRet << out_nb_samples << in_nb_samples;
CHECK_IF_ERROR_BUF_END(swrConvertRet <= 0, "swr_convert");
// 写入文件, 转换得出swrConvertRet每个通道样本数, 写入数据 = swrConvertRet * 每个样本的总大小, 写入到outData中
outFile.write((char *) outData[0], swrConvertRet * outBytesPerSample);
}
// 检查一下输出缓冲区是否还有残留的样本(已经重采样过的,转换过的)
while ((swrConvertRet = swr_convert(swrCtx, outData, out_nb_samples, nullptr, 0)) > 0) {
outFile.write((char *) outData[0], swrConvertRet * 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(&swrCtx);
}
