最近在做一个音频可视化的业务,网上有Java层的实现方法,但是业务需要用C实现,从原理出发其实很简单,先对音频进行解码,再计算分贝。这比把大象放进冰箱还简单。本文从音频可视化的业务为依托,以FFmpeg为基础实现解码,计算,绘制。
解码流程大致分为以下三个部分,以FFmpge源码下的ffmpeg\doc\examples\decode_audio.c为参考。

avformat_open_input负责打开需要解码的音频文件,如果文件打开成功的话会初始化AVFormatContext,avformat_find_stream_info开启音频流遍历,av_find_best_stream找到最合适解析数据的帧,解析完后我们可以通过返回的AVStream获取到我们需要用的解码器id、通道数、采样率、位深、音频时长、数据排列结构。拿到解码器id我们通过解码器id获取解码器avcodec_find_decoder,有些解码器并不是FFmpeg内置的,所以有些需要在编译时就加进去,我之前的文章也有讲过AAC和MP3编解码第三方库。如果找到了解码器,下一步就是avcodec_alloc_context3对解码器上下文AVCodecContext进行初始化,初始化完成后avcodec_parameters_to_context将解码器参数设置给解码器上下文,例如通道数,采样率,采样位深等等信息。如果未设置可能会出现invalid argument的错误,导致后续无法继续。最后通过avcodec_open2打开解码器,如果打开成功我们就可以开始对音频数据进行读取。
我们解码的目的就是为了拿到底层播放器能播的PCM数据。既然我们已经获取到了解码器,那么下面就是一帧一帧的读取解码器解析出来的数据。首先我们需要av_packet_alloc初始化包对象AVPacket,包对象是未解码的数据,原始的音频数据被打包成一个一个的包,然后送给解码器去把包打开,变成帧对象,所以我们又需要通过av_frame_alloc初始化帧对象AVFrame,把它送给解码器,解码器用数据把它装满后返回回来。av_read_frame就是从打开的文件读取一个数据包,对于AAC/MP3来说他们是未解码的压缩数据。然后通过avcodec_send_packet把数据包送给解码器,返回0表示解码器解包成功,接下来就可以从解码器读数据,这时的数据就是以帧的形式存在,avcodec_receive_frame读取帧,因为一个包可能有几个帧,所以需要循环读取,当avcodec_receive_frame返回0时表示读取成功,可以进行下一步操作,当返回值是AVERROR_EOF表示读取完毕可以跳出循环了,返回AVERROR(EAGAIN)表示解码器输出已经是不可用的状态,必须向解码器送新包来激活输出,同样也可以跳出读取和解析帧的循环。
我们的PCM数据就在frame的data里,但是我们并不能直接拿,首先我们得知道拿多少,怎么拿。拿多少取决于采样位数,通道数和帧里面的样本数。例如44100HZ的话一秒就有44100*通道数个样本。那一个帧里面就一共有 采样位数/8*通道数*样本数个字节数据。怎么拿取决于音频数据的存储方式,音频存储方式有两种:
data[0]:LLLLLLLLLLLLLLLL
data[1]:RRRRRRRRRRRRR
data[0]:LRLRLRLRLRLRLRLR
最终拿到的数据都是以LRLRLRLRLR的方式排列,到这里我们可以把它送给播放器或者在送给播放器前加一些我们自己的音频算法,全部解码完成后,最后记得释放掉相关资源。在这里我们简单点,计算它的分贝,实现音频可视化的功能。
我们音频的分贝往往不需要计算每一个样本的分贝数,第一计算密度太大超出人眼感知对显示没有益处,二是计算量太大会导致我们的计算时间大大延长。因为声音具有一定的延续性,所以我们可以计算一个时间段内的平均值来获得一段音频范围的分布值,这样既减小了工作量又达到了合理可视化的效果。首先是获取平均值,假设我们每秒想获取10个分贝值,那么我们需要计算采样率*通道数*采样位数/8/10个字节数据的平均值,我们不妨自己把它叫dB采样区间样本数,一个16bit位深的音频每两个字节组成一个样本,将区间内样本相加再除以样本数取平均值即可。接下来就是dB的计算,dB其实并不特指分贝,它只是在音频描述领域。它描述的是音频的增益关系,如果想详细了解db是什么可以看看《什么是dB》。分贝的计算公式是
20*log10(value)
所以声音的分贝描述的并不是线性关系而是指数关系,例如70db比50db的声音大了20倍,例如16bit可以描述的音频范围为0-65535那么它的最大dB值在96.3左右,32bit可以描述音频范围在0-4294967296,那么它的最大dB值在192.6。把我们刚才计算的平均值带入value就能获得我们的区间的分贝,把它存起来解析完一起返回或者逐个回调都可以,看你的业务需求。下面是计算16bit采样位数的分贝的方法,32bit的处理方法类似,主要注意值的大小,和每次位移的byte步长。拿到了了分贝我们就可以将它们变成条变成块的绘制到屏幕了。
void getPcmDB16(const unsigned char *pcmdata, size_t size) {
int db = 0;
short int value = 0;
double sum = 0;
for(int i = 0; i < size; i += bit_format/8)
{
memcpy(&value, pcmdata+i, bit_format/8); //获取2个字节的大小(值)
sum += abs(value); //绝对值求和
}
sum = sum / (size / (bit_format/8)); //求平均值(2个字节表示一个振幅,所以振幅个数为:size/2个)
if(sum > 0)
{
db = (int)(20.0*log10(sum));
}
memcpy(wave_buffer+wave_index,(char*)&db,1);
wave_index++;
}
需要注意的是我们在解码时ffmpeg的音频格式类型除了packet和planar两个大类外,对于32位的音频又区分了32位整形和32位浮点型。
enum AVSampleFormat {
AV_SAMPLE_FMT_NONE = -1,
AV_SAMPLE_FMT_U8, ///< unsigned 8 bits
AV_SAMPLE_FMT_S16, ///< signed 16 bits
AV_SAMPLE_FMT_S32, ///< signed 32 bits
AV_SAMPLE_FMT_FLT, ///< float
AV_SAMPLE_FMT_DBL, ///< double
AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar
AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar
AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar
AV_SAMPLE_FMT_FLTP, ///< float, planar
AV_SAMPLE_FMT_DBLP, ///< double, planar
AV_SAMPLE_FMT_S64, ///< signed 64 bits
AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar
AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking dynamically
};
浮点型的取值范围在-1到1的区间,所以我们在计算时需要乘以0x7fff来获得和16位同比例的数据,达到同样的显示效果。
void getPcmDBFloat(const unsigned char *pcmdata, size_t size) {
int db = 0;
float value = 0;
double sum = 0;
for(int i = 0; i < size; i += bit_format/8)
{
memcpy(&value, pcmdata+i, bit_format/8); //获取4个字节的大小(值)
sum += abs(value*0x7fff); //绝对值求和
}
sum = sum / (size / (bit_format/8));
if(sum > 0)
{
db = (int)(20.0*log10(sum));
}
memcpy(wave_buffer+wave_index,(char*)&db,1);
wave_index++;
}

欢迎大家交流讨论,批评指正。完整代码我会阶段性的推到我的GitHub上Audio项目上,如果需要解析音频的单个C文件可以私聊我发送给大家。
更新:资源已经上传到CSDN,0积分下载,有兴趣的可以下载下来看一看。解码只针对ffmpeg,计算db的方法可通用。音频解码,分贝计算 资源下载
https://download.csdn.net/download/qq_37841321/85203912?spm=1001.2014.3001.5503
我即将开始一个将录制和编辑音频文件的项目,我正在寻找一个好的库(最好是Ruby,但会考虑Java或.NET以外的任何库)以进行实时可视化波形。有人知道我应该从哪里开始搜索吗? 最佳答案 要流入浏览器的数据量很大。Flash或Flex图表可能是唯一能提高内存效率的解决方案。Javascript图表往往会因大型数据集而崩溃。 关于ruby-Ruby中的波形可视化,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.c
2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p
我正在尝试使用以下代码通过将ffmpeg实用程序作为子进程运行并获取其输出并解析它来确定视频分辨率:IO.popen'ffmpeg-i'+path_to_filedo|ffmpegIO|#myparsegoeshereend...但是ffmpeg输出仍然连接到标准输出并且ffmepgIO.readlines是空的。ffmpeg实用程序是否需要一些特殊处理?或者还有其他方法可以获得ffmpeg输出吗?我在WinXP和FedoraLinux下测试了这段代码-结果是一样的。 最佳答案 要跟进mouviciel的评论,您需要使用类似pope
Unity数据可视化图表插件XCharts3.0发布历时8个多月,业余时间,断断续续,XCharts3.0总算发布了。如果要打个满意度,我给3.0版本来个80分。对于代码框架结构设计的调整改动,基本符合预期,甚是满意。相比之前的1.0和2.0版本,我认为3.0才是一个拿得出手给广大开发者使用的版本。1.0发布的时候,很兴奋,从0.1到1.0,也磨了一年,真的等不及想给大家试用了,还特地写过一篇文章以示庆祝。那个时候,1.0虽然还还不够完善,功能也不够丰富,但它是XCharts的开始,没有1.0,也就没有后面的2.0和3.0。后面的2.0发布,做了很多改进和优化,随着版本迭代,慢慢的发现有不少硬
本人是音乐爱好者,从小就特别喜欢那个随着音乐跳动的方框效果,就是这个:arduino上一大把对,我忍你很久了,我就想用mpy做,全网没有,行我自己研究。果然兴趣是最好的老师,我之前有篇博客专门讲音频,有兴趣的可以回顾一下。提到可视化频谱,必然绕不开fft,大学学过这玩意,当时一心玩,老师讲的一个字都么听进去,网上教程简略扫了一下,大该就是把时域转频域的工具,我大mpy居然没有fft函数,奶奶的,先放着。音频信息如何收集?第一种傻瓜式的ADC,模拟转数字,原始粗暴,第二种,I2S库,我之前博客有讲过,数据是PCM编码。然后又去学PCM编码,一学豁然开朗,舒服,以代码为例:audio_in=I2S
我以前在Laravel4上工作过,它有一个很棒的日志查看器工具laravellogviewer查看demo我正在寻找与Rubyonrails4.2非常相似的东西,如果你们知道Rails4.2的任何好的可视化日志记录GEM,请告诉我..从代码我需要记录不同的日志级别,这个工具应该直观地组织我的日志,谢谢.. 最佳答案 这应该可以帮助您入门https://github.com/shadabahmed/logstasher如其所说Thisgemisheavilyinspiredfromlograge,butit'sfocusedonone
我有一个使用postgresql的Rails4应用程序。我还有一个backbone.js应用程序,可将JSON推送到Rails4应用程序。这是我的Controller:defcreate@product=Product.new(ActiveSupport::JSON.decodeproduct_params)respond_todo|format|if@product.saveformat.json{renderaction:'show',status::created,location:@product}elseformat.json{renderjson:@product.erro
我运行的是OSX,对视频转换一无所知。但我有大约200个视频都是mp4格式,无法在Firefox中播放。我需要将它们转换为ogg才能使用html5视频标签。这些文件位于一个文件夹结构中,这使得一次一个地处理一个文件变得困难。我希望bash命令或Ruby命令遍历所有子文件夹并找到所有.mp4并转换它们。我找到了一份关于如何使用Google执行此操作的引用资料:http://athmasagar.wordpress.com/2011/05/12/a-bash-script-to-convert-mp4-files-to-oggogv/#!/bin/bashforfin$(ls*mp4|se
目录需求基于JavaCV跨平台执行ffmpeg命令[^1]坑一内存不足坑二多个ffmpeg进程并行导致IO负载大,进而导致ioerror?坑三使用Java操作ffmpeg时,有时会卡死坑四Process的waitFor死锁问题及解决办法需求给透明背景的视频自动叠加一张背景图片基于JavaCV跨平台执行ffmpeg命令1我测试发现的本需求的最小依赖:dependency>groupId>org.bytedecogroupId>artifactId>ffmpeg-platform-gplartifactId>version>5.0-1.5.7version>dependency>核心代码:Stri
我正在尝试序列化和反序列化哈希。当散列被反序列化时,键被去符号化;例如不是更多:一个,而是“一个”。从Rails控制台:>>h={:one=>1,:two=>"two"}{:one=>1,:two=>"two"}>>j=ActiveSupport::JSON.encode(h)"{\"one\":1,\"two\":\"two\"}">>h2=ActiveSupport::JSON.decode(j){"one"=>1,"two"=>"two"}>>h2[:one]nil>>h[:one]1我现在已经切换到使用Marshal.dump/load。但是,我想把它扔出去看看是否有办法将它保