目錄
- 參考
- lswr功能介紹
- lswr使用說明
- 示例代碼
1. 參考
- [1] FFmpeg/Libswresample Documentation
- [2] FFmpeg/Libswresample Detailed Description
- [3] FFmpeg/doc/examples/resampling_audio.c
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使用說明
重采樣的處理流程:
- 創(chuàng)建上下文環(huán)境:重采樣過程上下文環(huán)境為SwrContext數(shù)據(jù)結(jié)構(gòu)杠输。
- 參數(shù)設(shè)置:轉(zhuǎn)換的參數(shù)設(shè)置到SwrContext中赎败。
- SwrContext初始化:swr_init()。
- 分配樣本數(shù)據(jù)內(nèi)存空間:使用av_samples_alloc_array_and_samples蠢甲、av_samples_alloc等工具函數(shù)僵刮。
- 開啟重采樣轉(zhuǎn)換:通過重復(fù)地調(diào)用swr_convert來完成。
- 重采樣轉(zhuǎn)換完成峡钓, 釋放相關(guān)資源:通過swr_free()釋放SwrContext妓笙。
下面是示例程序的一個(gè)流程圖:
函數(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的方式有兩種:
- swr_alloc() : 創(chuàng)建SwrContext之后再通過AVOptions的API來設(shè)置參數(shù)。
- 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è)置的方式有兩種:
- AVOptions的API
- 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ù):
- 重新進(jìn)行參數(shù)設(shè)置锯玛。
- 再次調(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);
說明:
- 如果沒有提供足夠的空間用于保存輸出數(shù)據(jù),采樣數(shù)據(jù)會(huì)緩存在swr中脱羡。可以通過 swr_get_out_samples()來獲取下一次調(diào)用swr_convert在給定輸入樣本數(shù)量下輸出樣本數(shù)量的上限却汉,來提供足夠的空間。
- 如果是采樣頻率轉(zhuǎn)換荷并,轉(zhuǎn)換完成后采樣數(shù)據(jù)可能會(huì)緩存在swr中合砂,它期待你提供更多的輸入數(shù)據(jù)。
- 如果實(shí)際上并不需要更多輸入數(shù)據(jù)源织,通過調(diào)用swr_convert()翩伪,其中參數(shù)in_count設(shè)置為0來獲取緩存在swr中的數(shù)據(jù)。
- 轉(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;
}