明天咱们的实战内容是将音频解码成PCM,并将PCM重采样成特定的采样率,而后输入到本地文件进行播放。

什么是重采样

所谓重采样,一句话总结就是扭转音频的三元素,也就是通过重采样扭转音频的采样率、采样格局或者声道数。

例如音频A是采样率48000hz、采样格局为f32le、声道数为1,通过重采样能够将音频A的采样率变更为采样率44100hz、采样格局为s16le、声道数为2等。

为什么须要重采样

个别进行重采样有两个起因,一是播放设施须要,二是音频合并、或编码器等须要。

例如有些声音设施只能播放44100hz的采样率、16位采样格局的音频数据,因而如果音频不是这些格局的,就须要进行重采样能力失常播放了。

例如FFmpeg默认的AAC编码器输出的PCM格局为:AV_SAMPLE_FMT_FLTP,如果须要应用FFMpeg默认的AAC编码器则须要进行重采样了。又比有些须要进行混音的业务需要,须要保障PCM三要素雷同能力进行失常混音。

如何进行音频重采样

在重采样的过程中咱们要坚守一个准则就是音频通过重采样后它的播放工夫是不变的,如果一个10s的音频通过重采样后变成了15,那必定就是不行的。

影响音频播放时长的因素是每帧的采样数和采样率,上面举一个例子简略介绍下音频播放时长的问题:

如果现有mp3,它的采样率是采样率48000,mp3每帧采样点数是1152,那么每帧mp3的播放时长就是 1152/48000,每一个采样点的播放时长就是1/48000。
如果现有mp3,它的采样率是采样率44100,aac每帧采样点数是1024,那么每帧aac的播放时长就是 1024/44100,每个采样点的播放时长就是1/44100。

从下面的例子中咱们能够看出,对于采样率不同的两个音频,不可能1帧mp3转换出1帧aac,它们的比例不是1:1的,对于下面的例子,那么1帧mp3能重采样出多少个aac的采样点呢?
以工夫不变为根底,能够有这样的一个公式:

1152 / 48000 = 指标采样点数 / 44100也就是说:指标采样点数 = 1152 * 44100 / 48000

这条公式能够用FFmpeg中的函数av_rescale_rnd来实现...

有了计算公式,上面咱们说说FFmpeg重采样的步骤:

1、调配SwrContext并配置音频输入输入参数

这里能够间接应用函数swr_alloc_set_opts实现,也能够应用swr_alloc、av_opt_set_channel_layout、av_opt_set_int、av_opt_set_sample_fmt等组合函数分步实现,

2、初始化SwrContext

调配好SwrContext 后,通过函数swr_init进行重采样上下文初始化。

3、swr_convert重采样

FFmpeg真正进行重采样的函数是swr_convert。它的返回值就是重采样输入的点数。应用FFmpeg进行重采样时外部是有缓存的,而外部缓存了多少个采样点,能够用函数swr_get_delay获取。
也就是说调用函数swr_convert时你传递进去的第三个参数示意你心愿输入的采样点数,然而函数swr_convert的返回值才是真正输入的采样点数,这个返回值肯定是小于或等于你心愿输入的采样点数。

上面是残缺代码:

#ifndef AUDIO_TARGET_SAMPLE#define AUDIO_TARGET_SAMPLE 48000#endif#include <iostream>extern "C" {#include "libavformat/avformat.h"#include <libswresample/swresample.h>#include <libavcodec/avcodec.h>#include <libavutil/frame.h>#include <libavutil/opt.h>#include <libavutil/channel_layout.h>}class AudioResample {public:    // 将PCM数据重采样    void decode_audio_resample(const char *media_path, const char *pcm_path) {        avFormatContext = avformat_alloc_context();        int ret = avformat_open_input(&avFormatContext, media_path, nullptr, nullptr);        if (ret < 0) {            std::cout << "输出关上失败" << std::endl;            return;        }        // 寻找视频流        int audio_index = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);        if (audio_index < 0) {            std::cout << "没有可用的音频流" << std::endl;            return;        }        // 配置解码相干        const AVCodec *avCodec = avcodec_find_decoder(avFormatContext->streams[audio_index]->codecpar->codec_id);        avCodecContext = avcodec_alloc_context3(avCodec);        avcodec_parameters_to_context(avCodecContext, avFormatContext->streams[audio_index]->codecpar);        ret = avcodec_open2(avCodecContext, avCodec, nullptr);        if (ret < 0) {            std::cout << "解码器关上失败" << std::endl;            return;        }        // 调配包和帧数据结构        avPacket = av_packet_alloc();        avFrame = av_frame_alloc();        // 关上yuv输入文件        pcm_out = fopen(pcm_path, "wb");        // 读取数据解码        while (true) {            ret = av_read_frame(avFormatContext, avPacket);            if (ret < 0) {                std::cout << "音频包读取结束" << std::endl;                break;            } else {                if (avPacket->stream_index == audio_index) {                    // 只解决音频包                    ret = avcodec_send_packet(avCodecContext, avPacket);                    if (ret < 0) {                        std::cout << "发送解码包失败" << std::endl;                        return;                    }                    while (true) {                        ret = avcodec_receive_frame(avCodecContext, avFrame);                        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {                            break;                        } else if (ret < 0) {                            std::cout << "获取解码数据失败" << std::endl;                            return;                        } else {                            std::cout << "重采样解码数据" << std::endl;                            resample();                        }                    }                }            }            av_packet_unref(avPacket);        }    }    ~AudioResample() {        // todo 开释资源    }private:    AVFormatContext *avFormatContext = nullptr;    AVCodecContext *avCodecContext = nullptr;    AVPacket *avPacket = nullptr;    AVFrame *avFrame = nullptr;    FILE *pcm_out = nullptr;    SwrContext *swrContext = nullptr;    AVFrame *out_frame = nullptr;    int64_t max_dst_nb_samples;    /**     * 重采样并输入到文件     */    void resample() {        if (nullptr == swrContext) {            /**             * 以下能够应用 swr_alloc、av_opt_set_channel_layout、av_opt_set_int、av_opt_set_sample_fmt             * 等API设置,更加灵便             */            swrContext = swr_alloc_set_opts(nullptr, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_FLTP, AUDIO_TARGET_SAMPLE,                                            avFrame->channel_layout, static_cast<AVSampleFormat>(avFrame->format),                                            avFrame->sample_rate, 0, nullptr);            swr_init(swrContext);        }        // 进行音频重采样        int src_nb_sample = avFrame->nb_samples;        // 为了放弃从采样后 dst_nb_samples / dest_sample = src_nb_sample / src_sample_rate        max_dst_nb_samples = av_rescale_rnd(src_nb_sample, AUDIO_TARGET_SAMPLE, avFrame->sample_rate, AV_ROUND_UP);        // 从采样器中会缓存一部分,获取缓存的长度        int64_t delay = swr_get_delay(swrContext, avFrame->sample_rate);        int64_t dst_nb_samples = av_rescale_rnd(delay + avFrame->nb_samples, AUDIO_TARGET_SAMPLE, avFrame->sample_rate,                                                AV_ROUND_UP);        if(nullptr == out_frame){            init_out_frame(dst_nb_samples);        }        if (dst_nb_samples > max_dst_nb_samples) {            // 须要重新分配buffer            std::cout << "须要重新分配buffer" << std::endl;            init_out_frame(dst_nb_samples);            max_dst_nb_samples = dst_nb_samples;        }        // 重采样        int ret = swr_convert(swrContext, out_frame->data, dst_nb_samples,                              const_cast<const uint8_t **>(avFrame->data), avFrame->nb_samples);        if(ret < 0){            std::cout << "重采样失败" << std::endl;        } else{            // 每帧音频数据量的大小            int data_size = av_get_bytes_per_sample(static_cast<AVSampleFormat>(out_frame->format));            std::cout << "重采样胜利:" << ret << "----dst_nb_samples:" << dst_nb_samples  << "---data_size:" << data_size << std::endl;            // 交织模式放弃写入            // 留神不要用 i < out_frame->nb_samples, 因为重采样进去的点数不肯定就是out_frame->nb_samples            for (int i = 0; i < ret; i++) {                for (int ch = 0; ch < out_frame->channels; ch++) {                    // 须要贮存为pack模式                    fwrite(out_frame->data[ch] + data_size * i, 1, data_size, pcm_out);                }            }        }    }    void init_out_frame(int64_t dst_nb_samples){        av_frame_free(&out_frame);        out_frame = av_frame_alloc();        out_frame->sample_rate = AUDIO_TARGET_SAMPLE;        out_frame->format = AV_SAMPLE_FMT_FLTP;        out_frame->channel_layout = AV_CH_LAYOUT_STEREO;        out_frame->nb_samples = dst_nb_samples;        // 调配buffer        av_frame_get_buffer(out_frame,0);        av_frame_make_writable(out_frame);    }};

应用ffplay播放以下重采样后的PCM文件是否失常,播放命令是:

// -ar 示意采样率// -ac 示意音频通道数// -f 示意 pcm 格局,sample_fmts + le(小端)或者 be(大端)  f32le示意的是 AV_SAMPLE_FMT_FLTP 的小端模式// sample_fmts能够通过ffplay -sample_fmts来查问// -i 示意输出文件,这里就是 pcm 文件ffplay -ar 44100 -ac 2 -f f32le -i pcm文件门路

系列举荐

FFmpeg连载1-开发环境搭建
FFmpeg连载2-拆散视频和音频
FFmpeg连载3-视频解码
FFmpeg连载4-音频解码
FFmpeg连载5-音视频编码

关注我,一起提高,人生不止coding!!!