FFmpeg连载6-音频重采样

这篇具有很好参考价值的文章主要介绍了FFmpeg连载6-音频重采样。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

今天我们的实战内容是将音频解码成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的返回值才是真正输出的采样点数,这个返回值一定是小于或等于你希望输出的采样点数。

【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击788280672加群免费领取~

FFmpeg连载6-音频重采样,ffmpeg,音视频

下面是完整代码:

#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连载6-音频重采样 - 掘金 文章来源地址https://www.toymoban.com/news/detail-797935.html

到了这里,关于FFmpeg连载6-音频重采样的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FFmpeg连载6-音频重采样

    今天我们的实战内容是将音频解码成PCM,并将PCM重采样成特定的采样率,然后输出到本地文件进行播放。 什么是重采样? 所谓重采样,一句话总结就是改变音频的三元素,也就是通过重采样改变音频的采样率、采样格式或者声道数。 例如音频A是采样率48000hz、采样格式为f

    2024年01月17日
    浏览(34)
  • ffmpeg@音视频工具@音视频合并

    FFmpeg中文网 (github.net.cn) FFmpeg 是一款强大的开源跨平台音视频处理工具集,它包含了一系列命令行工具以及用于音频和视频编码解码、格式转换、抓取、流化等功能的库。FFmpeg 支持多种视频、音频格式和编解码器,能够进行音视频的压缩、封装、转码、分割、合并、过滤、抓

    2024年03月17日
    浏览(79)
  • 音视频 FFmpeg音视频处理流程

    推荐一个零声学院项目课,个人觉得老师讲得不错,分享给大家: 零声白金学习卡(含基础架构/高性能存储/golang云原生/音视频/Linux内核) https://xxetb.xet.tech/s/VsFMs

    2024年02月12日
    浏览(69)
  • 音视频 ffmpeg命令提取音视频数据

    保留封装格式 提取视频 提取音频 推荐一个零声学院项目课,个人觉得老师讲得不错,分享给大家: 零声白金学习卡(含基础架构/高性能存储/golang云原生/音视频/Linux内核) https://xxetb.xet.tech/s/VsFMs

    2024年02月10日
    浏览(62)
  • 音视频 ffmpeg视频裁剪

    将输入视频帧的宽度和高度从x和y值表示的位置裁剪到指定的宽度和高度;x和y是输出的左上角坐标,协调系统的中心是输入视频帧的左上角。 如果使用了可选的keep_aspect参数,将会改变输出SAR(样本宽比)以补偿新的DAR(显示长宽比) 推荐一个零声学院项目课,个人觉得老师讲得不

    2024年02月10日
    浏览(52)
  • ffmpeg系列学习——FFmpeg的音视频处理

    1.音视频的采样率、采样位深度和声道数 音频和视频的采样率、采样位深度和声道数是媒体文件中的重要参数,它们会直接影响到音视频的质量和文件大小。下面对它们进行详细解释: 采样率 采样率指音频每秒钟采样的次数,用赫兹(Hz)表示。采样率越高,音频的还原度越

    2024年02月04日
    浏览(64)
  • [一]ffmpeg音视频解码

    (1)下载FFmpeg源码(v3.3.6) 下载地址( http://www.ffmpeg.org/download.html#releases ) (2)下载NDK(r14b) 下载地址( https://developer.android.google.cn/ndk/downloads/index.html) (3)编写Android编译脚本 (1)用ftp上传到Ubuntu中 (2)解压Ffmpeg(tar -zxvf ffmpeg-3.3.6.tar.gz) (3)解压NDK(unzip andro

    2024年01月19日
    浏览(267)
  • 音视频 ffmpeg命令视频录制(Windows)

    先安装dshow软件 Screen Capturer Recorder, 项目地址:https://sourceforge.net/projects/screencapturer/files/ 然后查看可用设备名字:ffmpeg -list_devices true -f dshow -i dummy 录制视频(默认参数) 录制声音(默认参数) 同时录制声音和视频(默认参数) 查看视频录制的可选参数 查看视频录制的可

    2024年02月10日
    浏览(71)
  • 音视频 ffmpeg命令转封装

    保持编码格式: 改变编码格式: 修改帧率: 修改视频码率: 修改视频码率: 修改音频码率: 修改音视频码率: 修改视频分辨率: 修改音频采样率: 推荐一个零声学院项目课,个人觉得老师讲得不错,分享给大家: 零声白金学习卡(含基础架构/高性能存储/golang云原生/音

    2024年02月10日
    浏览(55)
  • 音视频 ffmpeg命令参数说明

    主要参数: -i 设定输入流 -f 设定输出格式(format) -ss 开始时间 -t 时间长度 音频参数: -aframes 设置要输出的音频帧数 -b:a 音频码率 -ar 设定采样率 -ac 设定声音的Channel数 -acodec 设定声音编解码器,如果用copy表示原始编解码数据必须被拷贝。 -an 不处理音频 -af 音频过滤器 视频

    2024年02月10日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包