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加群免费领取~

下面是完整代码:

#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_ratemax_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) {// 需要重新分配bufferstd::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_samplesfor (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;// 分配bufferav_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-音频重采样 - 掘金 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/410522.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新增PostgreSQL数据库管理功能,1Panel开源面板v1.9.3发布

2024年1月15日&#xff0c;现代化、开源的Linux服务器运维管理面板1Panel正式发布v1.9.3版本。 在这一版本中&#xff0c;1Panel新增了PostgreSQL数据库管理功能&#xff0c;并且支持设置PHP运行环境扩展模版。此外&#xff0c;我们进行了30多项功能更新和问题修复。1Panel应用…

网站SEO优化方案

1&#xff0c;去各类搜索引擎里面&#xff0c;注册你的站点 解决方案&#xff1a;注册地址&#xff1a;https://seo.chinaz.com/chinaz.com 2&#xff0c;网站地址使用 https 会增加搜索排名 解决方案&#xff1a;https:www.xxx.com 3&#xff0c;官网每个页面的 meta 里面&a…

基于WebSocket双向通信技术实现-下单提醒和催单(后端)

学习复盘和总结项目亮点。 扩展&#xff1a;该功能能应用在&#xff0c;各种服务类项目中。&#xff08;例如&#xff1a;酒店、洗脚城等系ERP系中提醒类服务&#xff09; 4. 来单提醒 4.1 需求分析和设计 用户下单并且支付成功后&#xff0c;需要第一时间通知外卖商家。通…

[GN] nodejs16.13.0版本完美解决node-sass和sass-loader版本冲突问题

项目场景&#xff1a; npm install 运行vue项目时候 问题描述 项目场景&#xff1a;sass-loader &#xff0c;node-sass出错 ! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: smoore-mes-web1.4.0 npm ERR! Found: webpack3.12.0 npm ER…

GEE:随机森林分类器投票方法的优化与修改

作者:CSDN @ _养乐多_ 在随机森林中,每棵决策树都对输入数据进行分类或回归,并产生一个输出。对于分类问题,这个输出通常是一个类别标签,而对于回归问题,输出通常是一个连续的数值。在分类问题中,每棵决策树会为每个样本投票,然后采用众数来确定最终的类别。例如,如果…

数据结构奇妙旅程之二叉树初阶

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …

【SpringBoot框架篇】35.kafka环境搭建和收发消息

kafka环境搭建 kafka依赖java环境,如果没有则需要安装jdk yum install java-1.8.0-openjdk* -y1.下载安装kafka kafka3.0版本后默认自带了zookeeper&#xff0c;3.0之前的版本需要单独再安装zookeeper,我使用的最新的3.6.1版本。 cd /usr/local wget https://dlcdn.apache.…

洗地机怎么选?热门洗地机选购要点指南

洗地机的创新设计确实为清洁地面提供了更加高效和便捷的解决方案。相较于传统的清洁方式&#xff0c;洗地机具备自动化、深度清洁、消毒杀菌等功能&#xff0c;大大减轻了家庭清洁的负担。它不仅能够快速扫除灰尘和杂物&#xff0c;还能进行湿拖地操作&#xff0c;保持地面的清…

跟着cherno手搓游戏引擎【6】ImGui和ImGui事件

导入ImGui&#xff1a; 下载链接&#xff1a; GitHub - TheCherno/imgui: Dear ImGui: Bloat-free Immediate Mode Graphical User interface for C with minimal dependencies 新建文件夹&#xff0c;把下载好的文件放入对应路径&#xff1a; SRC下的premake5.lua文件&#…

好消息,Linux Kernel 6.7正式发布!

据有关资料显示&#xff0c;该版本是有史以来合并数最多的版本之一&#xff0c;包含 17k 个非合并 commit&#xff0c;实际合并的超过1K个。 那么该版本主要有哪边变化呢&#xff1f;下面我来一一列举一下&#xff1a; Bcachefs文件系统已被合并到主线内核&#xff0c;这是一款…

23111 网络编程 day4

思维导图 #include<myhead.h> #define SER_PORT 69 #define SER_IP "192.168.125.180"int do_download(int cfd,struct sockaddr_in sin) {//向服务器发送下载请求char buf[516]"";char fileName[40]"";printf("请输入文件名&#xf…

Simulink|双机并联自适应虚拟阻抗下垂控制仿真模型

目录 主要内容 模型研究 结果一览 下载链接 主要内容 风电高渗透率下&#xff0c;电力系统对风电场频率调节能力提出了技术要求。考虑风机惯性控制和变桨距控制的频率响应能力&#xff0c;提出将储能与风电自身调频手段相结合&#xff0c;参与系统频率调节。模型…