ffmpeg音频重采样代码抽取

本篇博文针对的是ffmpeg4.0.1版本进行(音频重采样代码都能分离出来,其他部分不是肯定可以嘛,往后ffmpeg就可以分而食之,我们可以把它变成我们自己的代码)

ffmpeg解压缩之后会有一个doc目录,此目录中有示例程序,就在doc/examples。
要想编译examples中的测试程序,可以先编译整个ffmpeg(生成主要的库,如:libavcodec.a libavdevice.a libavfilter.a libavformat.a libavutil.a libswresample.a libswscale.a。当然动态库静态库都是可以配置的,我选择默认静态库编译),然后在ffmpeg主目录中执行make examples即可(make examplesclean可以清除编译文件)。


针对于resampleaudio.c这个文件,编译完了之后,直接执行./resampleaudio out即可(out文件查看代码可以知道它的采样率为44100,channel为3,格式为signed16bit),使用audacity工具导入查看如下:
在这里插入图片描述其中前两个通道左右声道都是由正玄波产生,正玄波是代码中写好的src_data数据,查看resampleaudio.c的fill_samples函数就能知道,如下:

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;
    }
}

你刚开始看上面的波形可能会觉得那里是正玄波,一点儿也看不出来,其实放大之后就可以看到了,如下:
在这里插入图片描述
数据时间为10s,代码中是通过do{} while(t<10)来控制的,为什么t<10就是10s,你仔细看fill_samples函数里面代码实现就能知道,对于这个示例程序,程序中写死了最终输出数据的采样率为44100,也就是说1s秒钟会有44100个采样点,tincr = 1.0 / sample_rate,什么时候t会等于1(也就是1s),那就要t自加sample_rate(44100)次才是一秒的数据量。由于nb_samples程序中写死了是1024(其实就是1024个采样点),1024对应于src_date采样率48000的数据量,通过程序执行时的打印看出*t=0重复出现了46次,其它都一样(*t=1,2,3…),我们计算一下就知道了48000/1024=46(46.875),而每次输入1024个采样点同样会输出941个采样点,这941个采样点对应的是dst_data的44100采样率数据,再次计算,44100/941=46(46.865037194473963868225292242295),这就对上了。


程序中核心函数,也就是实际进行采样率转换的函数就是swr_convert函数,我们只需要会使用这个函数就行,与之相关的函数还有如下:
(1)swr_alloc 创建resample context
(2)av_opt_set_int av_opt_set_sample_fmt 设置相关属性(通道,采样率,格式)
(3)swr_init 初始化resample context
(4)av_get_channel_layout_nb_channels av_samples_alloc_array_and_samples 分配内存
(5)fill_samples 填充数据
(6)swr_convert 转换采样率
(7)av_samples_get_buffer_size 获取转换之后的buffer大小,之后就是写入数据到文件中

非常符合我们日常实际开发中的代码逻辑:创建,初始化,设置参数,分配内存,进行数据处理,结果输出这个过程。

好了,代码也看了,也能正常运行,下面就是修改代码了,来个简单的,我要输入数据为48000,格式为signed16bit,通道为1(单声道),输出采样率为44100,格式为signed16bit,通道为1的数据,怎么搞?很简单,就是上面的fill_samples函数,你把数据填到里面就可以了,具体修改如下:

static int fill_samples(short *dst, int nb_samples, int nb_channels, int src_sample_rate, int dst_sample_rate, double *t, FILE* src_file)
{
    int i, j;
	int ret = 0;
    double tincr = 1.0 / src_sample_rate;
	short* 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;
    }*/
	
	
	ret = fread(dst, 2, nb_samples, src_file);
	if (ret == 0) {
		printf("read complete, end of file...\n");
		return 0;
	} else if (ret < 0) {
		printf("read error...\n");
		return -1;
	}
	printf("---- ret = %d\n", ret);
	//memcpy(dst, );
	for (j = 1; j < nb_channels; j++)
		dstp[j] = dstp[0];
	
	return ret;
}

int main(int argc, char **argv)
{
    int64_t src_ch_layout = AV_CH_LAYOUT_MONO, dst_ch_layout = AV_CH_LAYOUT_MONO;
    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_S16, dst_sample_fmt = AV_SAMPLE_FMT_S16;
    const char *dst_filename = NULL;
	const char *src_filename = NULL;
    FILE *dst_file;
	FILE *src_file;
    int dst_bufsize;
    const char *fmt;
    struct SwrContext *swr_ctx;
    double t;
    int ret;

    if (argc != 3) {
        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];
    src_filename = argv[2];

    dst_file = fopen(dst_filename, "wb");
    if (!dst_file) {
        fprintf(stderr, "Could not open destination file %s\n", dst_filename);
        exit(1);
    }
	
	src_file = fopen(src_filename, "rb");
    if (!src_file) {
        fprintf(stderr, "Could not open destination file %s\n", src_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 */
        if (fill_samples((short *)src_data[0], src_nb_samples, src_nb_channels, src_rate, dst_rate, &t, src_file) <= 0)
			break;

        /* 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 (1);

    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);
    fclose(src_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;
}
修改之后编译运行:
./resampleaudio out say_48.pcm  (say_48.pcm为采样率为48000,signed16bit,单声道格式)

结果如下,上图:
在这里插入图片描述

这个能实现,其他的所有格式转换都可以,直接修改代码即可,那怎么进行二次开发(此处就针对于音频重采样代码),单独编译resampleaudio.c文件如下:
gcc resampling_audio.c -o resampling_audio -I …/…/_install_gcc/include/ -L …/…/_install_gcc/lib/ -lswresample -lavutil -lm -lrt
…/…/_install_gcc/include/为编译ffmpeg之后的头文件安装目录,…/…/_install_gcc/lib/为编译ffmpeg之后的库文件安装目录,由此可以看出要编译resampling_audio.c需要用到libavutil和libswresample的库,好,那下面就把这一块儿代码直接从程序中抽出来,让我们能单独编译resampling_audio.c这个文件,而不用依赖库文件。
。。。。。。
。。。。。
。。。。。。

经过了一天的折腾,最终把这块儿代码抽了出来,成功编译通过运行通过,如下为编译指令(Makefile有时间再写):
目录如下:
在这里插入图片描述

gcc -std=c99 -s -O2 resampling_audio-me.c libavutil/samplefmt.c libavutil/mem.c libavutil/log.c libavutil/bprint.c  libswresample/options.c  libavutil/opt.c libavutil/parseutils.c libavutil/rational.c libavutil/intmath.c libavutil/log2_tab.c  libavutil/time.c libavutil/avstring.c libavutil/dict.c libavutil/pixdesc.c libavutil/channel_layout.c libavutil/eval.c libavutil/reverse.c libavutil/mathematics.c libavutil/error.c libswresample/swresample.c libswresample/audioconvert.c libswresample/resample.c libavutil/cpu.c libavutil/x86/cpu.c libswresample/resample_dsp.c libswresample/x86/resample_init.c libswresample/rematrix.c libswresample/dither.c libavutil/random_seed.c libavutil/sha.c -o resampling_audio-me -I ../../../_install_gcc/include/  -lm

运行如下:

./resampling_audio-me-modify out.pcm say48.pcm

往后要用到音频重采样,直接把这一块儿代码编译成库再使用即可。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行走在软件开发路上的人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值