FFmpeg音頻重采樣API(libswresample)

目錄

  1. 參考
  2. lswr功能介紹
  3. lswr使用說明
  4. 示例代碼

1. 參考

2. lswr功能介紹

FFmpeg中重采樣的功能由libswresample(后面簡寫為lswr)提供缀壤。
lswr提供了高度優(yōu)化的轉(zhuǎn)換音頻的采樣頻率、聲道格式或樣本格式的功能。

功能說明:

  • 采樣頻率轉(zhuǎn)換:對音頻的采樣頻率進(jìn)行轉(zhuǎn)換的處理,例如把音頻從一個(gè)高的44100Hz的采樣頻率轉(zhuǎn)換到8000Hz习贫。從高采樣頻率到低采樣頻率的音頻轉(zhuǎn)換是一個(gè)有損的過程柜候。API提供了多種的重采樣選項(xiàng)和算法。
  • 聲道格式轉(zhuǎn)換:對音頻的聲道格式進(jìn)行轉(zhuǎn)換的處理参萄,例如立體聲轉(zhuǎn)換為單聲道。當(dāng)輸入通道不能映射到輸出流時(shí)酣倾,這個(gè)過程是有損的舵揭,因?yàn)樗婕安煌脑鲆嬉蛩睾突旌稀?/li>
  • 樣本格式轉(zhuǎn)換:對音頻的樣本格式進(jìn)行轉(zhuǎn)換的處理,例如把s16的PCM數(shù)據(jù)轉(zhuǎn)換為s8格式或者f32的PCM數(shù)據(jù)躁锡。此外提供了Packed和Planar包裝格式之間相互轉(zhuǎn)換的功能午绳,Packed和Planar的區(qū)別見FFmpeg中Packed和Planar的PCM數(shù)據(jù)區(qū)別

此外映之,還提供了一些其他音頻轉(zhuǎn)換的功能如拉伸和填充拦焚,通過專門的設(shè)置來啟用。

3. lswr使用說明

重采樣的處理流程:

  1. 創(chuàng)建上下文環(huán)境:重采樣過程上下文環(huán)境為SwrContext數(shù)據(jù)結(jié)構(gòu)杠输。
  2. 參數(shù)設(shè)置:轉(zhuǎn)換的參數(shù)設(shè)置到SwrContext中赎败。
  3. SwrContext初始化:swr_init()。
  4. 分配樣本數(shù)據(jù)內(nèi)存空間:使用av_samples_alloc_array_and_samples蠢甲、av_samples_alloc等工具函數(shù)僵刮。
  5. 開啟重采樣轉(zhuǎn)換:通過重復(fù)地調(diào)用swr_convert來完成。
  6. 重采樣轉(zhuǎn)換完成峡钓, 釋放相關(guān)資源:通過swr_free()釋放SwrContext妓笙。

下面是示例程序的一個(gè)流程圖:


resampling_audio.png

函數(shù)說明:

  • swr_alloc() :創(chuàng)建SwrContext對象若河。
  • av_opt_set_*():設(shè)置輸入和輸出音頻的信息能岩。
  • swr_init(): 初始化SwrContext。
  • av_samples_alloc_array_and_samples:根據(jù)音頻格式分配相應(yīng)大小的內(nèi)存空間萧福。
  • av_samples_alloc:根據(jù)音頻格式分配相應(yīng)大小的內(nèi)存空間拉鹃。用于轉(zhuǎn)換過程中對輸出內(nèi)存大小進(jìn)行調(diào)整。
  • swr_convert:進(jìn)行重采樣轉(zhuǎn)換鲫忍。

3.1 創(chuàng)建上下文環(huán)境

重采樣過程上下文環(huán)境為SwrContext數(shù)據(jù)結(jié)構(gòu)(SwrContext的定義沒有對外暴露)膏燕。

創(chuàng)建SwrContext的方式有兩種:

  1. swr_alloc() : 創(chuàng)建SwrContext之后再通過AVOptions的API來設(shè)置參數(shù)。
  2. swr_alloc_set_opts():在創(chuàng)建SwrContext的同時(shí)設(shè)置必要的參數(shù)悟民。

兩個(gè)函數(shù)的定義如下:

struct SwrContext* swr_alloc()

struct SwrContext* swr_alloc_set_opts(struct SwrContext *   s, //如果為NULL則創(chuàng)建一個(gè)新的SwrContext坝辫,否則對已有的SwrContext進(jìn)行參數(shù)設(shè)置
                                      int64_t               out_ch_layout, //輸出的聲道格式,AV_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 
)       

3.2 參數(shù)設(shè)置

參數(shù)設(shè)置的方式有兩種:

  1. AVOptions的API
  2. swr_alloc_set_opts():如果第一個(gè)參數(shù)為NULL則創(chuàng)建一個(gè)新的SwrContext射亏,否則對已有的SwrContext進(jìn)行參數(shù)設(shè)置近忙。

假定要進(jìn)行如下的重采樣轉(zhuǎn)換:

“f32le格式、采樣頻率48kHz智润、5.1聲道格式”的PCM數(shù)據(jù)
轉(zhuǎn)換為
“s16le格式及舍、采樣頻率44.1kHz、立體聲格式”的PCM數(shù)據(jù)

swr_alloc()的使用方式如下所示:

SwrContext *swr = swr_alloc();
av_opt_set_channel_layout(swr, "in_channel_layout", AV_CH_LAYOUT_5POINT1, 0);
av_opt_set_channel_layou(swr, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(swr, "in_sample_rate", 48000, 0);
av_opt_set_int(swr, "out_sample_rate", 44100, 0);
av_opt_set_sample_fmt(swr, "in_sample_fmt", AV_SAMPLE_FMT_FLPT, 0);
av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);

swr_alloc_set_opts()的使用方式如下所示:

SwrContext *swr = swr_alloc_set_opts(NULL,  // we're allocating a new context
                      AV_CH_LAYOUT_STEREO,  // out_ch_layout
                      AV_SAMPLE_FMT_S16,    // out_sample_fmt
                      44100,                // out_sample_rate
                      AV_CH_LAYOUT_5POINT1, // in_ch_layout
                      AV_SAMPLE_FMT_FLTP,   // in_sample_fmt
                      48000,                // in_sample_rate
                      0,                    // log_offset
                      NULL);                // log_ctx

3.3 SwrContext初始化:swr_init()

參數(shù)設(shè)置好之后必須調(diào)用swr_init()對SwrContext進(jìn)行初始化窟绷。

如果需要修改轉(zhuǎn)換的參數(shù):

  1. 重新進(jìn)行參數(shù)設(shè)置锯玛。
  2. 再次調(diào)用swr_init()。

3.4 分配樣本數(shù)據(jù)內(nèi)存空間

轉(zhuǎn)換之前需要分配內(nèi)存空間用于保存重采樣的輸出數(shù)據(jù)兼蜈,內(nèi)存空間的大小跟通道個(gè)數(shù)攘残、樣本格式需要拙友、容納的樣本個(gè)數(shù)都有關(guān)系。libavutil中的samples處理API提供了一些函數(shù)方便管理樣本數(shù)據(jù)歼郭,例如av_samples_alloc()函數(shù)用于分配存儲(chǔ)sample的buffer献宫。

av_sample_alloc()的定義如下:

/**
 * @param[out] audio_data  輸出數(shù)組,每個(gè)元素是指向一個(gè)通道的數(shù)據(jù)的指針实撒。
 * @param[out] linesize    aligned size for audio buffer(s), may be NULL
 * @param nb_channels      通道的個(gè)數(shù)姊途。
 * @param nb_samples       每個(gè)通道的樣本個(gè)數(shù)。
 * @param align            buffer size alignment (0 = default, 1 = no alignment)
 * @return                 成功返回大于0的數(shù)知态,錯(cuò)誤返回負(fù)數(shù)捷兰。
 */
int av_samples_alloc(uint8_t **audio_data, int *linesize, int nb_channels,
                     int nb_samples, enum AVSampleFormat sample_fmt, int align);

3.5 開啟重采樣轉(zhuǎn)換

重采樣轉(zhuǎn)換是通過重復(fù)地調(diào)用swr_convert()來完成的。

swr_convert()函數(shù)的定義如下:

 * @param out            輸出緩沖區(qū)负敏,當(dāng)PCM數(shù)據(jù)為Packed包裝格式時(shí)贡茅,只有out[0]會(huì)填充有數(shù)據(jù)。
 * @param out_count      每個(gè)通道可存儲(chǔ)輸出PCM數(shù)據(jù)的sample數(shù)量其做。
 * @param in             輸入緩沖區(qū)顶考,當(dāng)PCM數(shù)據(jù)為Packed包裝格式時(shí),只有in[0]需要填充有數(shù)據(jù)妖泄。
 * @param in_count       輸入PCM數(shù)據(jù)中每個(gè)通道可用的sample數(shù)量驹沿。
 *
 * @return               返回每個(gè)通道輸出的sample數(shù)量,發(fā)生錯(cuò)誤的時(shí)候返回負(fù)數(shù)。
 */
int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
                                const uint8_t **in , int in_count);

說明:

  1. 如果沒有提供足夠的空間用于保存輸出數(shù)據(jù),采樣數(shù)據(jù)會(huì)緩存在swr中脱羡。可以通過 swr_get_out_samples()來獲取下一次調(diào)用swr_convert在給定輸入樣本數(shù)量下輸出樣本數(shù)量的上限却汉,來提供足夠的空間。
  2. 如果是采樣頻率轉(zhuǎn)換荷并,轉(zhuǎn)換完成后采樣數(shù)據(jù)可能會(huì)緩存在swr中合砂,它期待你提供更多的輸入數(shù)據(jù)。
  3. 如果實(shí)際上并不需要更多輸入數(shù)據(jù)源织,通過調(diào)用swr_convert()翩伪,其中參數(shù)in_count設(shè)置為0來獲取緩存在swr中的數(shù)據(jù)。
  4. 轉(zhuǎn)換結(jié)束之后需要沖刷swr_context的緩沖區(qū)雀鹃,通過調(diào)用swr_convert()幻工,其中參數(shù)in設(shè)置為NULL,參數(shù)in_count設(shè)置為0黎茎。

下面的代碼演示了重采樣轉(zhuǎn)換處理的流程囊颅,其中假定依照上面的參數(shù)設(shè)置、get_input()和handle_output()已經(jīng)定義好。

uint8_t **input;
int in_samples;
while (get_input(&input, &in_samples)) {
    uint8_t *output;
    int out_samples = av_rescale_rnd(swr_get_delay(swr, 48000) +
                                     in_samples, 44100, 48000, AV_ROUND_UP);
    av_samples_alloc(&output, NULL, 2, out_samples,
                     AV_SAMPLE_FMT_S16, 0);
    out_samples = swr_convert(swr, &output, out_samples,
                                     input, in_samples);
    handle_output(output, out_samples);
    av_freep(&output);
}

3.6 重采樣轉(zhuǎn)換完成踢代, 釋放相關(guān)資源

轉(zhuǎn)換結(jié)束之后盲憎,需要調(diào)用av_freep(&audio_data[0])來釋放內(nèi)存。

4. 示例代碼

[3] 示例的代碼胳挎。

/**
 * @example resampling_audio.c
 * libswresample API use example.
 */

#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>

static int get_format_from_sample_fmt(const char **fmt,
                                      enum AVSampleFormat sample_fmt)
{
    int i;
    struct sample_fmt_entry {
        enum AVSampleFormat sample_fmt; const char *fmt_be, *fmt_le;
    } sample_fmt_entries[] = {
        { AV_SAMPLE_FMT_U8,  "u8",    "u8"    },
        { AV_SAMPLE_FMT_S16, "s16be", "s16le" },
        { AV_SAMPLE_FMT_S32, "s32be", "s32le" },
        { AV_SAMPLE_FMT_FLT, "f32be", "f32le" },
        { AV_SAMPLE_FMT_DBL, "f64be", "f64le" },
    };
    *fmt = NULL;

    for (i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++) {
        struct sample_fmt_entry *entry = &sample_fmt_entries[i];
        if (sample_fmt == entry->sample_fmt) {
            *fmt = AV_NE(entry->fmt_be, entry->fmt_le);
            return 0;
        }
    }

    fprintf(stderr,
            "Sample format %s not supported as output format\n",
            av_get_sample_fmt_name(sample_fmt));
    return AVERROR(EINVAL);
}

/**
 * Fill dst buffer with nb_samples, generated starting from t.
 */
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;

    /* generate sin tone with 440Hz frequency and duplicated channels */
    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(int argc, char **argv)
{
    int64_t src_ch_layout = AV_CH_LAYOUT_STEREO, dst_ch_layout = AV_CH_LAYOUT_SURROUND;
    int src_rate = 48000, dst_rate = 44100;
    uint8_t **src_data = NULL, **dst_data = NULL;
    int src_nb_channels = 0, dst_nb_channels = 0;
    int src_linesize, dst_linesize;
    int src_nb_samples = 1024, dst_nb_samples, max_dst_nb_samples;
    enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_DBL, dst_sample_fmt = AV_SAMPLE_FMT_S16;
    const char *dst_filename = NULL;
    FILE *dst_file;
    int dst_bufsize;
    const char *fmt;
    struct SwrContext *swr_ctx;
    double t;
    int ret;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s output_file\n"
                "API example program to show how to resample an audio stream with libswresample.\n"
                "This program generates a series of audio frames, resamples them to a specified "
                "output format and rate and saves them to an output file named output_file.\n",
            argv[0]);
        exit(1);
    }
    dst_filename = argv[1];

    dst_file = fopen(dst_filename, "wb");
    if (!dst_file) {
        fprintf(stderr, "Could not open destination file %s\n", dst_filename);
        exit(1);
    }

    /* create resampler context */
    swr_ctx = swr_alloc();
    if (!swr_ctx) {
        fprintf(stderr, "Could not allocate resampler context\n");
        ret = AVERROR(ENOMEM);
        goto end;
    }

    /* set options */
    av_opt_set_int(swr_ctx, "in_channel_layout",    src_ch_layout, 0);
    av_opt_set_int(swr_ctx, "in_sample_rate",       src_rate, 0);
    av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);

    av_opt_set_int(swr_ctx, "out_channel_layout",    dst_ch_layout, 0);
    av_opt_set_int(swr_ctx, "out_sample_rate",       dst_rate, 0);
    av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);

    /* initialize the resampling context */
    if ((ret = swr_init(swr_ctx)) < 0) {
        fprintf(stderr, "Failed to initialize the resampling context\n");
        goto end;
    }

    /* allocate source and destination samples buffers */

    src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
    ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize, src_nb_channels,
                                             src_nb_samples, src_sample_fmt, 0);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate source samples\n");
        goto end;
    }

    /* compute the number of converted samples: buffering is avoided
     * ensuring that the output buffer will contain at least all the
     * converted input samples */
    max_dst_nb_samples = dst_nb_samples =
        av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);

    /* buffer is going to be directly written to a rawaudio file, no alignment */
    dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
    ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels,
                                             dst_nb_samples, dst_sample_fmt, 0);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate destination samples\n");
        goto end;
    }

    t = 0;
    do {
        /* generate synthetic audio */
        fill_samples((double *)src_data[0], src_nb_samples, src_nb_channels, src_rate, &t);

        /* compute destination number of samples */
        dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, src_rate) +
                                        src_nb_samples, dst_rate, src_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;
        }

        /* convert to destination format */
        ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, (const uint8_t **)src_data, src_nb_samples);
        if (ret < 0) {
            fprintf(stderr, "Error while converting\n");
            goto end;
        }
        dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels,
                                                 ret, dst_sample_fmt, 1);
        if (dst_bufsize < 0) {
            fprintf(stderr, "Could not get sample buffer size\n");
            goto end;
        }
        printf("t:%f in:%d out:%d\n", t, src_nb_samples, ret);
        fwrite(dst_data[0], 1, dst_bufsize, dst_file);
    } while (t < 10);

    if ((ret = get_format_from_sample_fmt(&fmt, dst_sample_fmt)) < 0)
        goto end;
    fprintf(stderr, "Resampling succeeded. Play the output file with the command:\n"
            "ffplay -f %s -channel_layout %"PRId64" -channels %d -ar %d %s\n",
            fmt, dst_ch_layout, dst_nb_channels, dst_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 ret < 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饼疙,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子慕爬,更是在濱河造成了極大的恐慌窑眯,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件医窿,死亡現(xiàn)場離奇詭異磅甩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)姥卢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門卷要,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人独榴,你說我怎么就攤上這事僧叉。” “怎么了棺榔?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵瓶堕,是天一觀的道長。 經(jīng)常有香客問我掷豺,道長捞烟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任当船,我火速辦了婚禮,結(jié)果婚禮上默辨,老公的妹妹穿的比我還像新娘德频。我一直安慰自己,他們只是感情好缩幸,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布壹置。 她就那樣靜靜地躺著,像睡著了一般表谊。 火紅的嫁衣襯著肌膚如雪钞护。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天爆办,我揣著相機(jī)與錄音难咕,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛余佃,可吹牛的內(nèi)容都是我干的暮刃。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼爆土,長吁一口氣:“原來是場噩夢啊……” “哼椭懊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起步势,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤氧猬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后坏瘩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狂窑,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年桑腮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泉哈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡破讨,死狀恐怖丛晦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情提陶,我是刑警寧澤烫沙,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站隙笆,受9級特大地震影響锌蓄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜撑柔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一瘸爽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧铅忿,春花似錦剪决、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至峻凫,卻和暖如春渗鬼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背荧琼。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工譬胎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留差牛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓银择,卻偏偏與公主長得像多糠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子浩考,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容