FFmpeg系列(五)—— 音频重采样

为什么要重采样

从设备采集的音频数据与编码器要求的数据不一致
扬声器要求的音频数据与要播放的音频数据不一致
更方便运算(回音消除须使用单声道,需要先转换)

比如说语音识别,需要很低的采样率就可以了,高了增加了数据量,毫无用处,这时候就需要进行音频重采样,重采样可以改变音频采样值或采样格式。

完整代码

代码参考了FFmpeg示例,利用 fill_samples() 函数生成正弦波音频数据,然后,实现将48000采样值转换成44100的功能。代码一开始做了最基本的初始化,然后分配空间,计算采样值,最后进行采样值的转换,将转换后的数据写入本地文件。代码中的关键函数是 swr_convert(),采样值的转换就是靠他完成。
以下代码在Qt5.14.0中验证OK,代码如下:

#ifdef __cplusplus
extern "C"
{
#endif
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
#ifdef __cplusplus
}
#endif

#define IN_RATE  48000  // 输入采样率
#define OUT_RATE 44100  // 输出采样率

// 获取采样格式
static int get_format_from_sample_fmt(const char **fmt,
                                      enum AVSampleFormat sample_fmt)
{
    char *lettle_end = NULL;
    char *big_end    = NULL;
    switch ((int)sample_fmt) {  // 根据枚值获取音频编码格式字符串
        case AV_SAMPLE_FMT_U8:  big_end = "u8";    lettle_end = "u8";    break; // 大端模式和小端模式
        case AV_SAMPLE_FMT_S16: big_end = "s16be"; lettle_end = "s16le"; break;
        case AV_SAMPLE_FMT_S32: big_end = "s32be"; lettle_end = "s32le"; break;
        case AV_SAMPLE_FMT_FLT: big_end = "f32be"; lettle_end = "f32le"; break;
        case AV_SAMPLE_FMT_DBL: big_end = "f64be"; lettle_end = "f64le"; break;
        default: return AVERROR(EINVAL);
    }
    *fmt = AV_NE(big_end, lettle_end);  // 一般计算机都是小端模式,宏展开为 *fmt = lettle_end
    return 0;
}

// 交错模式生成输入源
static void fill_samples(double *dst, int nb_samples, int nb_channels, int sample_rate, double *t)
{
    int i, j;
    double tincr = 1.0 / sample_rate, *dstp = dst;
    const double c = 2 * M_PI * 440.0; // 440Hz

    // 产生440Hz重复通道的正弦信号
    for (i = 0; i < nb_samples; i++) {
        *dstp = sin(c * *t);
        for (j = 1; j < nb_channels; j++)
            dstp[j] = dstp[0];
        dstp += nb_channels;
        *t += tincr;
    }
}

int main()
{
    // 输入参数
    struct SwrContext   *swr_ctx        = swr_alloc();               // 重采样上下文
    enum AVSampleFormat src_sample_fmt  = AV_SAMPLE_FMT_DBL;         // 交错采样模式
    enum AVSampleFormat dst_sample_fmt  = AV_SAMPLE_FMT_S16;         // 有符号16位
    uint8_t             **src_data      = NULL;                      // 输入音频数据缓冲区
    uint8_t             **dst_data      = NULL;                      // 输出音频数据缓冲区
    int                 src_nb_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); // 输入通道数
    int                 dst_nb_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); // 输出通道数
    int                 src_linesize;                                // 输入音频行数据大小,取决于声道数和采样格式是否是交错模式
    int                 dst_linesize;                                // 输出音频行数据大小
    int                 src_nb_samples  = 1024;                      // 输入固定按1024个采样点
    int                 dst_nb_samples;                              // 当前输出数据包的真实采样数量
    int                 max_dst_nb_samples;                          // 最大的采样数量
    int                 dst_bufsize;                                 // 当前数据包采样值大小
    const char          *dst_filename   = "out.pcm";                 // 保存输出的pcm到本地,然后播放验证
    FILE                *dst_file       = fopen(dst_filename, "wb");
    const char          *fmt;

    double              t = 0.0;
    int                 ret = 0;

    if (!dst_file) { exit(1);  } // 输出文件打开失败
    if (!swr_ctx)  { goto end; } // 重采样上下文创建失败

    // 输入参数
    av_opt_set_int(swr_ctx, "in_channel_layout",  AV_CH_LAYOUT_STEREO, 0); // 输入立体声
    av_opt_set_int(swr_ctx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0); // 输出立体声
    av_opt_set_int(swr_ctx, "in_sample_rate",     IN_RATE, 0);             // 输入采样率48000
    av_opt_set_int(swr_ctx, "out_sample_rate",    OUT_RATE, 0);            // 输出采样率44100
    av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt",  src_sample_fmt, 0);
    av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);

    // 初始化重采样
    if (swr_init(swr_ctx) < 0) { goto end; }

    // 给输入源分配内存空间
    ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize, src_nb_channels, src_nb_samples, src_sample_fmt, 0);
    if (ret < 0) { goto end; }

    // 计算输出采样数量
    max_dst_nb_samples = dst_nb_samples = av_rescale_rnd(src_nb_samples, OUT_RATE, IN_RATE, AV_ROUND_UP);

    // 分配输出缓存内存
    ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels, dst_nb_samples, dst_sample_fmt, 0);
    if (ret < 0) { goto end; }

    do {
        fill_samples((double *)src_data[0], src_nb_samples, src_nb_channels, IN_RATE, &t); // 生成输入源

        // 计算采样数量
        int64_t delay = swr_get_delay(swr_ctx, IN_RATE); // swr_convert可能有没转换的数据,这里需要获取缓存
        // 计算新的采样数量 dst_nb_samples = (delay + src_nb_samples) * OUT_RATE / IN_RATE,结果向上取整
        dst_nb_samples = av_rescale_rnd(delay + src_nb_samples, OUT_RATE, IN_RATE, AV_ROUND_UP);
        if (dst_nb_samples > max_dst_nb_samples) {       // 采样数量不够大,重新分配
            av_freep(&dst_data[0]);
            ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels, dst_nb_samples, dst_sample_fmt, 1);
            if (ret < 0) { break; }              // 空间分配失败
            max_dst_nb_samples = dst_nb_samples; // 更新最大采样数量
        }

        // 音频重采样
        if ((ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, (const uint8_t **)src_data, src_nb_samples)) < 0) { goto end; }
        if ((dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels, ret, dst_sample_fmt, 1)) < 0) { goto end; }

        printf("t:%f in:%d out:%dn", t, src_nb_samples, ret);
        fwrite(dst_data[0], 1, dst_bufsize, dst_file);  // 将pcm数据写入本地文件
    } while (t < 10);

    // ========== 刷新最后几个样本 ==========
    if (swr_convert(swr_ctx, dst_data, dst_nb_samples, NULL, 0) < 0) { goto end; }
    if ((dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels, ret, dst_sample_fmt, 1)) < 0) { goto end; }

    printf("flush in:%d out:%dn", 0, ret);  // 最后有几个样本,非常小
    fwrite(dst_data[0], 1, dst_bufsize, dst_file);  // 将最后一点pcm数据写入本地文件

    if (get_format_from_sample_fmt(&fmt, dst_sample_fmt) < 0) { goto end; }
    fprintf(stderr, "ffplay -f %s -channel_layout %"PRId64" -channels %d -ar %d %sn",   // 转换成功,打印音频播放命令
            fmt, (int64_t)AV_CH_LAYOUT_STEREO, dst_nb_channels, OUT_RATE, dst_filename);

end:  // 结束并释放空间
    fclose(dst_file);

    if (src_data)
        av_freep(&src_data[0]);
    av_freep(&src_data);

    if (dst_data)
        av_freep(&dst_data[0]);
    av_freep(&dst_data);

    swr_free(&swr_ctx);
    return 0;
}

运行结果

编译运行后会在输出目录看到一个out.pcm文件,使用ffplay -f s16le -channel_layout 3 -channels 2 -ar 44100 out.pcm命令即可播放,声音为一个连续的但音调。
在这里插入图片描述

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>