FFmpeg连载6-音频重采样

news/2024/2/24 7:44:56/文章来源:https://blog.csdn.net/qq_52703909/article/details/135633516

今天我们的实战内容是将音频解码成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.luyixian.cn/news_show_926088.aspx

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

相关文章

【docker-compose】【nginx】内网环境https配置

目录 1、openssl生成自签名证书和私钥2、nginx.conf配置ssl3、docker-compose挂载 1、openssl生成自签名证书和私钥 在部署服务器上&#xff0c;新建cert目录&#xff0c;执行以下指令&#xff0c;然后生成.crt和.key文件 openssl req -newkey rsa:2048 -nodes -keyout rsa_pri…

成都力寰璨泓科技有限公司:抖小店新势力

在当下这个信息爆炸的时代&#xff0c;科技的发展日新月异&#xff0c;电商行业更是风起云涌。在这场没有硝烟的战争中&#xff0c;成都力寰璨泓科技有限公司以其敏锐的商业洞察和卓越的技术实力&#xff0c;成功抢滩抖音小店市场&#xff0c;成为一股不可忽视的新生力量。 成都…

ADA-YOLO:YOLOv8+注意力+Adaptive Head,mAP提升3%

生物医学图像分析中的目标检测和定位至关重要&#xff0c;尤其是在血液学领域&#xff0c;检测和识别血细胞对于诊断和治疗决策至关重要。虽然基于注意力的方法在各个领域中目标检测方面取得了显著的进展&#xff0c;但由于医学影像数据集的独特挑战&#xff0c;其在医学目标检…

论文浅尝 | 记忆力强还是健忘?深入探究语言模型的知识记忆机制

笔记整理&#xff1a;方润楠&#xff0c;浙江大学硕士&#xff0c;研究方向为自然语言处理 链接&#xff1a;https://arxiv.org/abs/2305.09144 摘要 近年来&#xff0c;大规模预训练语言模型展示出惊人的记忆能力&#xff0c;相比之下&#xff0c;未经预训练的普通神经网络存在…

Web - Angular 的 AJAX 与 REST

AJAX AJAX 是用于描述网页与网络服务器互动的一系列技术的术语。它不是一项新技术&#xff0c;而是对长时间以来已存在技术的应用。随着主要网站&#xff08;例如谷歌&#xff09;展示其优势&#xff0c;它作为一种开发技术变得流行。AJAX 这个术语被创造出来&#xff0c;用以描…

深度解析JVM类加载器与双亲委派模型

概述 Java虚拟机&#xff08;JVM&#xff09;是Java程序运行的核心&#xff0c;其中类加载器和双亲委派模型是JVM的重要组成部分。本文将深入讨论这两个概念&#xff0c;并解释它们在实际开发中的应用。 1. 什么是类加载器&#xff1f; 类加载器是JVM的一部分&#xff0c;负…

鸿蒙APP闪退的问题

解决鸿蒙&#xff08;HarmonyOS&#xff09;应用闪退的问题通常需要进行一系列的调查和分析。以下是一些建议的步骤&#xff0c;可以帮助你定位和解决鸿蒙应用闪退的原因&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff…

还在质疑js的性能,人家都干到过第一了。

前两天看到一个 just.js&#xff0c;感到了 Techempower 排名第一&#xff0c;甚至打败了 asp.net core&#xff0c;还有drogn&#xff0c;不知道背后什么原因&#xff0c;反正挺震撼的。 现在开始慢慢验证Atwood定律&#xff1a;任何能够用JavaScript实现的应用系统&#xf…

如何在 Python3 中使用变量

介绍 变量是一个重要的编程概念&#xff0c;值得掌握。它们本质上是在程序中用于表示值的符号。 本教程将涵盖一些变量基础知识&#xff0c;以及如何在您创建的 Python 3 程序中最好地使用它们。 理解变量 从技术角度来说&#xff0c;变量是将存储位置分配给与符号名称或标…

java自动化将用例和截图一起执行测试放入world中直接生成测试报告【搬代码】

1.首先我们得用例写好之后放入文档中&#xff0c;把不用的案例类型、前置条件去掉之后&#xff0c;如图&#xff1a; 放到桌面后&#xff0c;先看执行结果&#xff1a; 首先&#xff0c;我们先创建一个时间&#xff0c;这个时间主要是给图片创建名称&#xff0c;并且要在插入…

【LabVIEW FPGA入门】没有CompactRIO时进行编程测试

1.新建一个空白项目。 2.新建cRIO终端。 要添加仿真的远程实时目标&#xff0c;请选择项目名称&#xff0c;右击并选择新建>>目标和设备(Targets and Devices)。 3.新建终端和设备&#xff0c;选一个cRIO型号 接下来&#xff0c;当添加目标和设备窗口出现时&#xff0c;请…

【数据结构】常见八大排序算法总结

目录 前言 1.直接插入排序 2.希尔排序 3.选择排序 4.堆排序 5.冒泡排序 6.快速排序 6.1Hoare版本 6.2挖坑法 6.3前后指针法 6.4快速排序的递归实现 6.5快速排序的非递归实现 7.归并排序 8.计数排序&#xff08;非比较排序&#xff09; 9.补充:基数排序 10.总结…

Jmeter 性能-监控服务器

Jmeter监控Linux需要三个文件 JMeterPlugins-Extras.jar (包&#xff1a;JMeterPlugins-Extras-1.4.0.zip) JMeterPlugins-Standard.jar (包&#xff1a;JMeterPlugins-Standard-1.4.0.zip) ServerAgent-2.2.3.zip 1、Jemter 安装插件 在插件管理中心的搜索Servers Perform…

散列函数,哈希表hash table

附上一句话&#xff1a;我知道大家可能曾经了解过这个散列表了&#xff0c;我发现&#xff0c;如果多看几个相关的视频&#xff0c;从不同的表述方式和不同的理解角度来理解这个问题&#xff0c;我会明白的更透彻&#xff0c;也有更多新的收获&#xff0c;尤其是对这个算法的应…

宁夏银行关键系统基于OceanBase的创新实践

宁夏银行成立于 1998 年&#xff0c;是宁夏第一家“宁”字号地方商业银行&#xff0c;西部地区第一家以省级行政区命名的地方商业银行。2016 年&#xff0c;被中国人民银行评为宁夏地区系统性重要银行。目前&#xff0c;全行设分支机构 97 家&#xff0c;其中总行营业部 1 家&a…

制造工厂ERP系统:从数字销售-生产到财务管理,掌握企业数字化十大核心!

在快速发展的数字化时代&#xff0c;企业&#xff08;尤其是传统生产制造行业&#xff09;面临着诸多挑战与机遇。无论是客户体验、供应链管理还是内部流程优化&#xff0c;数字化都在发挥着关键作用。为了更好地应对数字化带来的挑战和机遇为了更好地应对市场变化和提高竞争力…

打造高品质家具的必选!数控开料机为何备受推崇?

随着科技的不断进步&#xff0c;数控开料机已经成为了木材加工行业中的首选设备。 一、数控开料机在木材加工行业中的优势 高效、精准的加工效果 数控开料机采用高精度的数控技术和高功率的机械传动系统&#xff0c;可以实现对木材的精确开料和高效加工。与传统的手工操作相…

【RabbitMQ】RabbitMQ高级:死信队列和延迟队列

目录 设置TTL&#xff08;过期时间&#xff09;概述RabbitMQ使用TTL原生API案例springboot案例 死信队列概述原生API案例springboot案例 延迟队列概述插件实现延迟队列安装插件代码 TTL实现延迟队列实现延迟队列优化 设置TTL&#xff08;过期时间&#xff09; 概述 在电商平台…

51单片机-电子密码锁

实物演示效果&#xff1a; https://www.bilibili.com/video/BV1xh4y1K7uV/?vd_source6ff7cd03af95cd504b60511ef9373a1d 电子密码锁的主要功能 1.按键设置6位密码&#xff0c;输入密码若密码正确&#xff0c;则锁打开。显示open&#xff01; 2.密码可以自己修改&#xff0…

Ubuntu 22.04安装使用easyconnect

EasyConnect 百度百科&#xff0c;EasyConnect能够帮助您在办公室之外使用公司内网的所有系统及应用。在您的公司部署深信服远程应用发布解决方案后&#xff0c;您的公司所有业务系统及应用都可以轻松迁移至移动互联网上。您可以通过手机、PAD等智能移动终端随时随地开展您的业…