1. 如何使用命令行对 PCM 文件进行重采样?
ffmpeg -ar 44100 -ac 2 -f s16le -i 44100_s16le_2.pcm -ar 44100 -ac 1 -f f32le 44100_f32le_1.pcm
复制代码
2. ffmepg在描述采样格式时,比如 s16le、f32le
中的 s
、u
、f
、le
、be
分别代表什么意思?
s
:有符号(Signed)u
:无符号(Unsigned)f
:浮点型(Float)le
:小端模式(Little-Endian)be
:大端模式(Big-Endian)
3. 在 SDL 中描述采样格式时,比如 AUDIO_S16LSB、AUDIO_F32MSB
中的 LSB
、MSB
分别代表什么意思?
LSB
:小端模式(Least Significant Bit/Byte,最低有效位/字节)MSB
:大端模式(Most Significant Bit/Byte,最高有效位/字节)
4. 什么是音频重采样?为什么需要音频重采样?
- 音频重采样(Audio Resample):将音频 A 转成音频 B,并且音频 A、B 的参数(采样率、采样格式、声道数)并不完全相同。
- 有些音频编码器对输入的原始 PCM 数据是有特点参数要求的。
5. 简述音频代码重采样的主流程?
- in.pcm → 输入缓冲区 → 输出缓冲区 → out.pcm
6. 在.pro 文件中,LIBS
同时添加 FFmpeg 和 SDL 的库时要注意什么?
-L
和-l
后面都不能有空格
7. int a[] = {1, 2, 3, 4, 5}
中 a[0]
和 a[3]
,本质上是什么意思?
a[0]
等价于*(a+0)
等价于*a
值等于1
a[3]
等价于*(a+3)
值等于4
8. 在 C 语言中,指针和数据是可以相互切换的。如何理解这句话?(非常重要)
int a[] = {1, 2, 3, 4, 5};
int *p = a;
printf("%d, %d, %d, \n", *a, *(a + 1), a[1]); // 这两句代码,a 和 p 的使用几乎完全一致
printf("%d, %d, %d, \n", *p, *(p + 1), p[1]); // 这两句代码,a 和 p 的使用几乎完全一致
复制代码
9. 输入输出流的内存结构图(有助于代码理解)
10. 音频重采样关键代码
#include "ffmpegs.h"
#include <QFile>
#include <QDebug>
extern "C" {
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
}
#define ERROR_BUF(ret) \
char errbuf[1024]; \
av_strerror(ret, errbuf, sizeof (errbuf));
FFmpegs::FFmpegs()
{
}
void FFmpegs::resampleAudio(ResampleAudioSpec &in,
ResampleAudioSpec &out) {
resampleAudio(in.filename, in.sampleRate, in.sampleFmt, in.chLayout,
out.filename, out.sampleRate, out.sampleFmt, out.chLayout);
}
void FFmpegs::resampleAudio(const char *inFilename,
int inSampleRate,
AVSampleFormat inSampleFmt,
int inChLayout,
const char *outFilename,
int outSampleRate,
AVSampleFormat outSampleFmt,
int outChLayout) {
// 文件名
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;
}
// 创建输入缓冲区
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;
}
// 读取文件数据
// intData[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);
// void *ptr = malloc(100);
// freep(&ptr);
// free(ptr);
// ptr = nullptr;
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END