精华内容
下载资源
问答
  •  数码音频系统是通过将声波波形转换成一连串二进制数据来再现原始声音(原始声音是模拟信号),实现这个步骤使用设备是模/数转换器(A/D转换器,或者ADC,或者analog to digital convert)。它以每秒上万次...
  • matlab处理音频信号 一、 问题的提出: 数字语音是信号的一种,我们处理数字语音信号,也就是一种信号的处理,那信号是什么呢? 信号是传递信息的函数。离散时间信号——序列——可以用图形来表示。 按信号特点的...
  • 几个术语和概念:1. 关于PCM的 PCM是Pulse code... Sampling rate:从模拟信号到数字信号,即从连续信号到离散信号的转换都是通过离散采样完成的,Sampling rate就是每秒种采样的个数。根据香农采样定理,要保证信号

    几个术语和概念:

    1.       关于PCM的

     

    PCM是Pulse code modulation的缩写,它是对波形最直接的编码方式。它在音频中的地位可能和BMP在图片中的地位有点类似吧。

                    

    Sampling rate:从模拟信号到数字信号,即从连续信号到离散信号的转换都是通过离散采样完成的,Sampling rate就是每秒种采样的个数。根据香农采样定理,要保证信号不失真,Sampling rate要大于信号最高频率的两倍。我们知道人的耳朵能听到的频率范围是20hz – 20khz,所以Sampling rate达到40k就够了,再多了也只是浪费。但是有时为了节省带宽和存储资源,可以降低Sampling rate而损失声音的质量,所以我们常常见到小于40k采样率的声音数据。

     

    Sample size:用来量化一个采样的幅度,一般为8 bits、16 bits和24 bits。8 bits只有早期的声卡支持,而24 bits只有专业的声卡才支持,我们用的一般都是16 bits的。

     

    Number of channels:声音通道个数,单声道为一个,立体声为两个,还有更多的(如8个声道的7.1格式)。一般来说,每个声道都来源于一个独立的mic,所以声道多效果会更好(更真实),当然代价也更大。

     

    Frame: Frame是指包含了所有通道的一次采样数据,比如对于16bits的双声道来说,一个frame的大小为4个字节(2 * 16)。

     

    一、数字音频

    音频信号是一种连续变化的模拟信号,但计算机只能处理和记录二进制的数字信号,由自然音源得到的音频信号必须经过一定的变换,成为数字音频信号之后,才能送到计算机中作进一步的处理。

    数字音频系统通过将声波的波型转换成一系列二进制数据,来实现对原始声音的重现,实现这一步骤的设备常被称为模/数转换器(A/D)。A/D转换器以每秒钟上万次的速率对声波进行采样,每个采样点都记录下了原始模拟声波在某一时刻的状态,通常称之为样本(sample),而每一秒钟所采样的数目则称为采样频率,通过将一串连续的样本连接起来,就可以在计算机中描述一段声音了。对于采样过程中的每一个样本来说,数字音频系统会分配一定存储位来记录声波的振幅,一般称之为采样分辩率或者采样精度,采样精度越高,声音还原时就会越细腻。

     

    数字音频涉及到的概念非常多,对于在Linux下进行音频编程的程序员来说,最重要的是理解声音数字化的两个关键步骤:采样和量化。采样就是每隔一定时间就读一次声音信号的幅度,而量化则是将采样得到的声音信号幅度转换为数字值,从本质上讲,采样是时间上的数字化,而量化则是幅度上的数字化。下面介绍几个在进行音频编程时经常需要用到的技术指标:

         采样频率

        采样频率是指将模拟声音波形进行数字化时,每秒钟抽取声波幅度样本的次数。采样频率的选择应该遵循奈奎斯特(Harry Nyquist)采样理论:如果对某一模拟信号进行采样,则采样后可还原的最高信号频率只有采样频率的一半,或者说只要采样频率高于输入信号最高频率的两倍,就能从采样信号系列重构原始信号。正常人听觉的频率范围大约在20Hz~20kHz之间,根据奈奎斯特采样理论,为了保证声音不失真,采样频率应该在40kHz左右。常用的音频采样频率有8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz等,如果采用更高的采样频率,还可以达到DVD的音质。 其中,8kHZ为电话的采样频率。

    量化位数

        量化位数是对模拟音频信号的幅度进行数字化,它决定了模拟信号数字化以后的动态范围,常用的有8位、12位和16位。量化位越高,信号的动态范围越大,数字化后的音频信号就越可能接近原始信号,但所需要的存贮空间也越大。

    声道数

    声道数是反映音频数字化质量的另一个重要因素,它有单声道和双声道之分。双声道又称为立体声,在硬件中有两条线路,音质和音色都要优于单声道,但数字化后占据的存储空间的大小要比单声道多一倍。

    二、声卡驱动

    出于对安全性方面的考虑,Linux下的应用程序无法直接对声卡这类硬件设备进行操作,而是必须通过内核提供的驱动程序才能完成。在Linux上进行音频编程的本质就是要借助于驱动程序,来完成对声卡的各种操作。

    对硬件的控制涉及到寄存器中各个比特位的操作,通常这是与设备直接相关并且对时序的要求非常严格,如果这些工作都交由应用程序员来负责,那么对声卡的编程将变得异常复杂而困难起来,驱动程序的作用正是要屏蔽硬件的这些底层细节,从而简化应用程序的编写。目前Linux下常用的声卡驱动程序主要有两种:OSS和ALSA。

    最早出现在Linux上的音频编程接口是OSS(Open Sound System),它由一套完整的内核驱动程序模块组成,可以为绝大多数声卡提供统一的编程接口。OSS出现的历史相对较长,这些内核模块中的一部分(OSS/Free)是与Linux内核源码共同免费发布的,另外一些则以二进制的形式由4Front Technologies公司提供。由于得到了商业公司的鼎力支持,OSS已经成为在Linux下进行音频编程的事实标准,支持OSS的应用程序能够在绝大多数声卡上工作良好。

    虽然OSS已经非常成熟,但它毕竟是一个没有完全开放源代码的商业产品,ALSA(AdvancedLinux Sound Architecture)恰好弥补了这一空白,它是在Linux下进行音频编程时另一个可供选择的声卡驱动程序。ALSA除了像OSS那样提供了一组内核驱动程序模块之外,还专门为简化应用程序的编写提供了相应的函数库,与OSS提供的基于ioctl的原始编程接口相比,ALSA函数库使用起来要更加方便一些。ALSA的主要特点有:

    • 支持多种声卡设备
    • 模块化的内核驱动程序
    • 支持SMP和多线程
    • 提供应用开发函数库
    • 兼容OSS应用程序

    ALSA和OSS最大的不同之处在于ALSA是由志愿者维护的自由项目,而OSS则是由公司提供的商业产品,因此在对硬件的适应程度上OSS要优于ALSA,它能够支持的声卡种类更多。ALSA虽然不及OSS运用得广泛,但却具有更加友好的编程接口,并且完全兼容于OSS,对应用程序员来讲无疑是一个更佳的选择。

    三、编程接口

    如何对各种音频设备进行操作是在Linux上进行音频编程的关键,通过内核提供的一组系统调用,应用程序能够访问声卡驱动程序提供的各种音频设备接口,这是在Linux下进行音频编程最简单也是最直接的方法。

    3.1 访问音频设备

    无论是OSS还是ALSA,都是以内核驱动程序的形式运行在Linux内核空间中的,应用程序要想访问声卡这一硬件设备,必须借助于Linux内核所提供的系统调用(systemcall)。从程序员的角度来说,对声卡的操作在很大程度上等同于对磁盘文件的操作:首先使用open系统调用建立起与硬件间的联系,此时返回的文件描述符将作为随后操作的标识;接着使用read系统调用从设备接收数据,或者使用write系统调用向设备写入数据,而其它所有不符合读/写这一基本模式的操作都可以由ioctl系统调用来完成;最后,使用close系统调用告诉Linux内核不会再对该设备做进一步的处理。

    • open系统调用
      系统调用open可以获得对声卡的访问权,同时还能为随后的系统调用做好准备,其函数原型如下所示:

    int open(const char *pathname, int flags, int mode);

    参数pathname是将要被打开的设备文件的名称,对于声卡来讲一般是/dev/dsp。参数flags用来指明应该以什么方式打开设备文件,它可以是O_RDONLY、O_WRONLY或者O_RDWR,分别表示以只读、只写或者读写的方式打开设备文件;参数mode通常是可选的,它只有在指定的设备文件不存在时才会用到,指明新创建的文件应该具有怎样的权限。
      如果open系统调用能够成功完成,它将返回一个正整数作为文件标识符,在随后的系统调用中需要用到该标识符。如果open系统调用失败,它将返回-1,同时还会设置全局变量errno,指明是什么原因导致了错误的发生。

    • read系统调用
      系统调用read用来从声卡读取数据,其函数原型如下所示:

    int read(int fd, char *buf, size_t count);


    参数fd是设备文件的标识符,它是通过之前的open系统调用获得的;参数buf是指向缓冲区的字符指针,它用来保存从声卡获得的数据;参数count则用来限定从声卡获得的最大字节数。如果read系统调用成功完成,它将返回从声卡实际读取的字节数,通常情况会比count的值要小一些;如果read系统调用失败,它将返回-1,同时还会设置全局变量errno,来指明是什么原因导致了错误的发生。

    • write系统调用
      系统调用write用来向声卡写入数据,其函数原型如下所示:

    size_t write(int fd, const char *buf, size_t count);


    系统调用write和系统调用read在很大程度是类似的,差别只在于write是向声卡写入数据,而read则是从声卡读入数据。参数fd同样是设备文件的标识符,它也是通过之前的open系统调用获得的;参数buf是指向缓冲区的字符指针,它保存着即将向声卡写入的数据;参数count则用来限定向声卡写入的最大字节数。
    如果write系统调用成功完成,它将返回向声卡实际写入的字节数;如果read系统调用失败,它将返回-1,同时还会设置全局变量errno,来指明是什么原因导致了错误的发生。无论是read还是write,一旦调用之后Linux内核就会阻塞当前应用程序,直到数据成功地从声卡读出或者写入为止。

    • ioctl系统调用
      系统调用ioctl可以对声卡进行控制,凡是对设备文件的操作不符合读/写基本模式的,都是通过ioctl来完成的,它可以影响设备的行为,或者返回设备的状态,其函数原型如下所示:

    int ioctl(int fd, int request, ...);


    参数fd是设备文件的标识符,它是在设备打开时获得的;如果设备比较复杂,那么对它的控制请求相应地也会有很多种,参数request的目的就是用来区分不同的控制请求;通常说来,在对设备进行控制时还需要有其它参数,这要根据不同的控制请求才能确定,并且可能是与硬件设备直接相关的。

    • close系统调用
      当应用程序使用完声卡之后,需要用close系统调用将其关闭,以便及时释放占用的硬件资源,其函数原型如下所示:

    int close(int fd);


    参数fd是设备文件的标识符,它是在设备打开时获得的。一旦应用程序调用了close系统调用,Linux内核就会释放与之相关的各种资源,因此建议在不需要的时候尽量及时关闭已经打开的设备。

    3.2 音频设备文件

    对于Linux应用程序员来讲,音频编程接口实际上就是一组音频设备文件,通过它们可以从声卡读取数据,或者向声卡写入数据,并且能够对声卡进行控制,设置采样频率和声道数目等等。

    • /dev/sndstat
      设备文件/dev/sndstat是声卡驱动程序提供的最简单的接口,通常它是一个只读文件,作用也仅仅只限于汇报声卡的当前状态。一般说来,/dev/sndstat是提供给最终用户来检测声卡的,不宜用于程序当中,因为所有的信息都可以通过ioctl系统调用来获得。 Linux提供的cat命令可以很方便地从/dev/sndstat获得声卡的当前状态: [xiaowp@linuxgam sound]$ cat /dev/sndstat
    • /dev/dsp

    声卡驱动程序提供的/dev/dsp是用于数字采样(sampling)和数字录音(recording)的设备文件,它对于Linux下的音频编程来讲非常重要:向该设备写数据即意味着激活声卡上的D/A转换器进行放音,而向该设备读数据则意味着激活声卡上的A/D转换器进行录音。目前许多声卡都提供有多个数字采样设备,它们在Linux下可以通过/dev/dsp1等设备文件进行访问。

    DSP是数字信号处理器(DigitalSignal Processor)的简称,它是用来进行数字信

    号处理的特殊芯片,声卡使用它来实现模拟信号和数字信号的转换。声卡中的DSP设备实际上包含两个组成部分:在以只读方式打开时,能够使用A/D转换器进行声音的输入;而在以只写方式打开时,则能够使用D/A转换器进行声音的输出。严格说来,Linux下的应用程序要么以只读方式打开/dev/dsp输入声音,要么以只写方式打开/dev/dsp输出声音,但事实上某些声卡驱动程序仍允许以读写的方式打开/dev/dsp,以便同时进行声音的输入和输出,这对于某些应用场合(如IP电话)来讲是非常关键的。

    在从DSP设备读取数据时,从声卡输入的模拟信号经过A/D转换器变成数字采样后的样本(sample),保存在声卡驱动程序的内核缓冲区中,当应用程序通过read系统调用从声卡读取数据时,保存在内核缓冲区中的数字采样结果将被复制到应用程序所指定的用户缓冲区中。需要指出的是,声卡采样频率是由内核中的驱动程序所决定的,而不取决于应用程序从声卡读取数据的速度。如果应用程序读取数据的速度过慢,以致低于声卡的采样频率,那么多余的数据将会被丢弃;如果读取数据的速度过快,以致高于声卡的采样频率,那么声卡驱动程序将会阻塞那些请求数据的应用程序,直到新的数据到来为止。

    在向DSP设备写入数据时,数字信号会经过D/A转换器变成模拟信号,然后产生出

    声音。应用程序写入数据的速度同样应该与声卡的采样频率相匹配,否则过慢的话会产生声音暂停或者停顿的现象,过快的话又会被内核中的声卡驱动程序阻塞,直到硬件有能力处理新的数据为止。与其它设备有所不同,声卡通常不会支持非阻塞(non-blocking)的I/O操作。无论是从声卡读取数据,或是向声卡写入数据,事实上都具有特定的格式(format,默认为8位无符号数据单声道8KHz采样率,如果默认值无法达到要求,可以通过ioctl系统调用来改变它们。通常说来,在应用程序中打开设备文件/dev/dsp之后,接下去就应该为其设置恰当的格式,然后才能从声卡读取或者写入数据。

    • /dev/audio
      /dev/audio类似于/dev/dsp,它兼容于Sun工作站上的音频设备,使用的是mu-law编码方式。如果声卡驱动程序提供了对/dev/audio的支持,那么在Linux上就可以通过cat命令,来播放在Sun工作站上用mu-law进行编码的音频文件:

    [xiaowp@linuxgam sound]$ cat audio.au > /dev/audio


    由于设备文件/dev/audio主要出于对兼容性的考虑,所以在新开发的应用程序中最好不要尝试用它,而应该以/dev/dsp进行替代。对于应用程序来说,同一时刻只能使用/dev/audio或者/dev/dsp其中之一,因为它们是相同硬件的不同软件接口。

    • /dev/mixer
      在声卡的硬件电路中,混音器(mixer)是一个很重要的组成部分,它的作用是将多个信号组合或者叠加在一起,对于不同的声卡来说,其混音器的作用可能各不相同。运行在Linux内核中的声卡驱动程序一般都会提供/dev/mixer这一设备文件,它是应用程序对混音器进行操作的软件接口。混音器电路通常由两个部分组成:输入混音器(input mixer)和输出混音器(output mixer)。
      输入混音器负责从多个不同的信号源接收模拟信号,这些信号源有时也被称为混音通道或者混音设备。模拟信号通过增益控制器和由软件控制的音量调节器后,在不同的混音通道中进行级别(level)调制,然后被送到输入混音器中进行声音的合成。混音器上的电子开关可以控制哪些通道中有信号与混音器相连,有些声卡只允许连接一个混音通道作为录音的音源,而有些声卡则允许对混音通道做任意的连接。经过输入混音器处理后的信号仍然为模拟信号,它们将被送到A/D转换器进行数字化处理。
      输出混音器的工作原理与输入混音器类似,同样也有多个信号源与混音器相连,并且事先都经过了增益调节。当输出混音器对所有的模拟信号进行了混合之后,通常还会有一个总控增益调节器来控制输出声音的大小,此外还有一些音调控制器来调节输出声音的音调。经过输出混音器处理后的信号也是模拟信号,它们最终会被送给喇叭或者其它的模拟输出设备。对混音器的编程包括如何设置增益控制器的级别,以及怎样在不同的音源间进行切换,这些操作通常来讲是不连续的,而且不会像录音或者放音那样需要占用大量的计算机资源。由于混音器的操作不符合典型的读/写操作模式,因此除了open和close两个系统调用之外,大部分的操作都是通过ioctl系统调用来完成的。与/dev/dsp不同,/dev/mixer允许多个应用程序同时访问,并且混音器的设置值会一直保持到对应的设备文件被关闭为止。
      为了简化应用程序的设计,Linux上的声卡驱动程序大多都支持将混音器的ioctl操作直接应用到声音设备上,也就是说如果已经打开了/dev/dsp,那么就不用再打开/dev/mixer来对混音器进行操作,而是可以直接用打开/dev/dsp时得到的文件标识符来设置混音器。
    • /dev/sequencer
      目前大多数声卡驱动程序还会提供/dev/sequencer这一设备文件,用来对声卡内建的波表合成器进行操作,或者对MIDI总线上的乐器进行控制,一般只用于计算机音乐软件中。

    四、应用框架

    在Linux下进行音频编程时,重点在于如何正确地操作声卡驱动程序所提供的各种设备文件,由于涉及到的概念和因素比较多,所以遵循一个通用的框架无疑将有助于简化应用程序的设计。

    4.1 DSP编程

    对声卡进行编程时首先要做的是打开与之对应的硬件设备,这是借助于open系统调用来完成的,并且一般情况下使用的是/dev/dsp文件。采用何种模式对声卡进行操作也必须在打开设备时指定,对于不支持全双工的声卡来说,应该使用只读或者只写的方式打开,只有那些支持全双工的声卡,才能以读写的方式打开,并且还要依赖于驱动程序的具体实现。Linux允许应用程序多次打开或者关闭与声卡对应的设备文件,从而能够很方便地在放音状态和录音状态之间进行切换,建议在进行音频编程时只要有可能就尽量使用只读或者只写的方式打开设备文件,因为这样不仅能够充分利用声卡的硬件资源,而且还有利于驱动程序的优化。下面的代码示范了如何以只写方式打开声卡进行放音(playback)操作:

    int handle = open("/dev/dsp", O_WRONLY);

    if (handle == -1) {

             perror("open /dev/dsp");

             return -1;

    }

     

    运行在Linux内核中的声卡驱动程序专门维护了一个缓冲区,其大小会影响到放音和录音时的效果,使用ioctl系统调用可以对它的尺寸进行恰当的设置。调节驱动程序中缓冲区大小的操作不是必须的,如果没有特殊的要求,一般采用默认的缓冲区大小也就可以了。但需要注意的是,缓冲区大小的设置通常应紧跟在设备文件打开之后,这是因为对声卡的其它操作有可能会导致驱动程序无法再修改其缓冲区的大小。下面的代码示范了怎样设置声卡驱动程序中的内核缓冲区的大小:

    int setting = 0xnnnnssss;

    int result = ioctl(handle, SNDCTL_DSP_SETFRAGMENT, &setting);

    if (result == -1) {

             perror("ioctl buffer size");

             return -1;

    }

    // 检查设置值的正确性

     

    在设置缓冲区大小时,参数setting实际上由两部分组成,其低16位标明缓冲区的尺寸,相应的计算公式为buffer_size = 2^ssss,即若参数setting低16位的值为16,那么相应的缓冲区的大小会被设置为65536字节。参数setting的高16位则用来标明分片(fragment)的最大序号,它的取值范围从2一直到0x7FFF,其中0x7FFF表示没有任何限制。

    接下来要做的是设置声卡工作时的声道(channel)数目,根据硬件设备和驱动程序的具体情况,可以将其设置为0(单声道,mono)或者1(立体声,stereo)。下面的代码示范了应该怎样设置声道数目:

    int channels = 0; // 0=mono 1=stereo

    int result = ioctl(handle, SNDCTL_DSP_STEREO, &channels);

    if ( result == -1 ) {

             perror("ioctl channel number");

             return -1;

    }

    if (channels != 0) {

             // 只支持立体声

    }

     

    采样格式和采样频率是在进行音频编程时需要考虑的另一个问题,声卡支持的所有采样格式可以在头文件soundcard.h中找到,而通过ioctl系统调用则可以很方便地更改当前所使用的采样格式。下面的代码示范了如何设置声卡的采样格式:

    int format = AFMT_U8;

    int result = ioctl(handle, SNDCTL_DSP_SETFMT, &format);

    if ( result == -1 ) {

             perror("ioctl sample format");

             return -1;

    }

    // 检查设置值的正确性

     

    声卡采样频率的设置也非常容易,只需在调用ioctl时将第二个参数的值设置为SNDCTL_DSP_SPEED,同时在第三个参数中指定采样频率的数值就行了。对于大多数声卡来说,其支持的采样频率范围一般为5kHz到44.1kHz或者48kHz,但并不意味着该范围内的所有频率都会被硬件支持,在Linux下进行音频编程时最常用到的几种采样频率是11025Hz、16000Hz、22050Hz、32000Hz和44100Hz。下面的代码示范了如何设置声卡的采样频率:

    int rate = 22050;

    int result = ioctl(handle, SNDCTL_DSP_SPEED, &rate);

    if ( result == -1 ) {

             perror("ioctl sample format");

             return -1;

    }

    // 检查设置值的正确性

     

    4.2 Mixer编程

    声卡上的混音器由多个混音通道组成,它们可以通过驱动程序提供的设备文件/dev/mixer进行编程。对混音器的操作是通过ioctl系统调用来完成的,并且所有控制命令都由SOUND_MIXER或者MIXER开头,表1列出了常用的几个混音器控制命令:

    名 称

    作 用

    SOUND_MIXER_VOLUME

    主音量调节

    SOUND_MIXER_BASS

    低音控制

    SOUND_MIXER_TREBLE

    高音控制

    SOUND_MIXER_SYNTH

    FM合成器

    SOUND_MIXER_PCM

    主D/A转换器

    SOUND_MIXER_SPEAKER

    PC喇叭

    SOUND_MIXER_LINE

    音频线输入

    SOUND_MIXER_MIC

    麦克风输入

    SOUND_MIXER_CD

    CD输入

    SOUND_MIXER_IMIX

    回放音量

    SOUND_MIXER_ALTPCM

    从D/A 转换器

    SOUND_MIXER_RECLEV

    录音音量

    SOUND_MIXER_IGAIN

    输入增益

    SOUND_MIXER_OGAIN

    输出增益

    SOUND_MIXER_LINE1

    声卡的第1输入

    SOUND_MIXER_LINE2

    声卡的第2输入

    SOUND_MIXER_LINE3

    声卡的第3输入


    表1 混音器命令

    对声卡的输入增益和输出增益进行调节是混音器的一个主要作用,目前大部分声卡采用的是8位或者16位的增益控制器,但作为程序员来讲并不需要关心这些,因为声卡驱动程序会负责将它们变换成百分比的形式,也就是说无论是输入增益还是输出增益,其取值范围都是从0到100。在进行混音器编程时,可以使用SOUND_MIXER_READ宏来读取混音通道的增益大小,例如在获取麦克风的输入增益时,可以使用如下的代码:

    int vol;

    ioctl(fd, SOUND_MIXER_READ(SOUND_MIXER_MIC), &vol);

    printf("Mic gain is at %d %%\n", vol);

     

    对于只有一个混音通道的单声道设备来说,返回的增益大小保存在低位字节中。而对于支持多个混音通道的双声道设备来说,返回的增益大小实际上包括两个部分,分别代表左、右两个声道的值,其中低位字节保存左声道的音量,而高位字节则保存右声道的音量。下面的代码可以从返回值中依次提取左右声道的增益大小:

    int left, right;

    left = vol & 0xff;

    right = (vol & 0xff00) >> 8;

    printf("Left gain is %d %%, Right gain is %d %%\n", left, right);

     

    类似地,如果想设置混音通道的增益大小,则可以通过SOUND_MIXER_WRITE宏来实现,此时遵循的原则与获取增益值时的原则基本相同,例如下面的语句可以用来设置麦克风的输入增益:

    vol = (right << 8) + left;

    ioctl(fd, SOUND_MIXER_WRITE(SOUND_MIXER_MIC), &vol);

     

    在编写实用的音频程序时,混音器是在涉及到兼容性时需要重点考虑的一个对象,这是因为不同的声卡所提供的混音器资源是有所区别的。声卡驱动程序提供了多个ioctl系统调用来获得混音器的信息,它们通常返回一个整型的位掩码(bitmask),其中每一位分别代表一个特定的混音通道,如果相应的位为1,则说明与之对应的混音通道是可用的。例如通过SOUND_MIXER_READ_DEVMASK返回的位掩码,可以查询出能够被声卡支持的每一个混音通道,而通过SOUND_MIXER_READ_RECMAS返回的位掩码,则可以查询出能够被当作录音源的每一个通道。下面的代码可以用来检查CD输入是否是一个有效的混音通道:

      ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);

    if (devmask & SOUND_MIXER_CD)

      printf("The CD input is supported");

     

     

    如果进一步还想知道其是否是一个有效的录音源,则可以使用如下语句:

    ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask);

    if (recmask & SOUND_MIXER_CD)

      printf("The CD input can be a recording source");

     

    目前大多数声卡提供多个录音源,通过SOUND_MIXER_READ_RECSRC可以查询出当前正在使用的录音源,同一时刻能够使用几个录音源是由声卡硬件决定的。类似地,使用SOUND_MIXER_WRITE_RECSRC可以设置声卡当前使用的录音源,例如下面的代码可以将CD输入作为声卡的录音源使用:

    devmask = SOUND_MIXER_CD;

    ioctl(fd, SOUND_MIXER_WRITE_DEVMASK, &devmask);

     

    此外,所有的混音通道都有单声道和双声道的区别,如果需要知道哪些混音通道提供了对立体声的支持,可以通过SOUND_MIXER_READ_STEREODEVS来获得。

    4.3 音频录放框架

    下面给出一个利用声卡上的DSP设备进行声音录制和回放的基本框架,它的功能是先录制几秒种音频数据,将其存放在内存缓冲区中,然后再进行回放,其所有的功能都是通过读写/dev/dsp设备文件来完成的:

    /*

     * sound.c

     */

    #include <unistd.h>

    #include <fcntl.h>

    #include <sys/types.h>

    #include <sys/ioctl.h>

    #include <stdlib.h>

    #include <stdio.h>

    #include <linux/soundcard.h>

    #define LENGTH 3    /* 存储秒数 */

    #define RATE 8000   /* 采样频率 */

    #define SIZE 8      /* 量化位数 */

    #define CHANNELS 1  /* 声道数目 */

    /* 用于保存数字音频数据的内存缓冲区 */

    unsigned char buf[LENGTH*RATE*SIZE*CHANNELS/8];

    int main()

    {

      int fd; /* 声音设备的文件描述符 */

      int arg;        /* 用于ioctl调用的参数 */

      int status;   /* 系统调用的返回值 */

      /* 打开声音设备 */

      fd = open("/dev/dsp", O_RDWR);

      if (fd < 0) {

        perror("open of /dev/dsp failed");

        exit(1);

      }

      /* 设置采样时的量化位数 */

      arg = SIZE;

      status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg);

      if (status == -1)

        perror("SOUND_PCM_WRITE_BITS ioctl failed");

      if (arg != SIZE)

        perror("unable to set sample size");

      /* 设置采样时的声道数目 */

      arg = CHANNELS;

      status = ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &arg);

      if (status == -1)

        perror("SOUND_PCM_WRITE_CHANNELS ioctl failed");

      if (arg != CHANNELS)

        perror("unable to set number of channels");

      /* 设置采样时的采样频率 */

      arg = RATE;

      status = ioctl(fd, SOUND_PCM_WRITE_RATE, &arg);

      if (status == -1)

        perror("SOUND_PCM_WRITE_WRITE ioctl failed");

      /* 循环,直到按下Control-C */

      while (1) {

        printf("Say something:\n");

        status = read(fd, buf, sizeof(buf)); /* 录音 */

        if (status != sizeof(buf))

          perror("read wrong number of bytes");

        printf("You said:\n");

        status = write(fd, buf, sizeof(buf)); /* 回放 */

        if (status != sizeof(buf))

          perror("wrote wrong number of bytes");

        /* 在继续录音前等待回放结束 */

        status = ioctl(fd, SOUND_PCM_SYNC, 0);

        if (status == -1)

          perror("SOUND_PCM_SYNC ioctl failed");

      }

    }

     

    4.4 混音器框架

    下面再给出一个对混音器进行编程的基本框架,利用它可以对各种混音通道的增益进行调节,其所有的功能都是通过读写/dev/mixer设备文件来完成的:

    /*

     * mixer.c

     */

    #include <unistd.h>

    #include <stdlib.h>

    #include <stdio.h>

    #include <sys/ioctl.h>

    #include <fcntl.h>

    #include <linux/soundcard.h>

    /* 用来存储所有可用混音设备的名称 */

    const char *sound_device_names[] = SOUND_DEVICE_NAMES;

    int fd;                  /* 混音设备所对应的文件描述符 */

    int devmask, stereodevs; /* 混音器信息对应的位图掩码 */

    char *name;

    /* 显示命令的使用方法及所有可用的混音设备 */

    void usage()

    {

      int i;

      fprintf(stderr, "usage: %s <device> <left-gain%%> <right-gain%%>\n"

               "       %s <device> <gain%%>\n\n"

               "Where <device> is one of:\n", name, name);

      for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++)

        if ((1 << i) & devmask) /* 只显示有效的混音设备 */

          fprintf(stderr, "%s ", sound_device_names[i]);

      fprintf(stderr, "\n");

      exit(1);

    }

    int main(int argc, char *argv[])

    {

      int left, right, level;  /* 增益设置 */

      int status;              /* 系统调用的返回值 */

      int device;              /* 选用的混音设备 */

      char *dev;               /* 混音设备的名称 */

      int i;

      name = argv[0];

      /* 以只读方式打开混音设备 */

      fd = open("/dev/mixer", O_RDONLY);

      if (fd == -1) {

        perror("unable to open /dev/mixer");

        exit(1);

      }

     

      /* 获得所需要的信息 */

      status = ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);

      if (status == -1)

        perror("SOUND_MIXER_READ_DEVMASK ioctl failed");

      status = ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs);

      if (status == -1)

        perror("SOUND_MIXER_READ_STEREODEVS ioctl failed");

      /* 检查用户输入 */

      if (argc != 3 && argc != 4)

        usage();

      /* 保存用户输入的混音器名称 */

      dev = argv[1];

      /* 确定即将用到的混音设备 */

      for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++)

        if (((1 << i) & devmask) && !strcmp(dev, sound_device_names[i]))

          break;

      if (i == SOUND_MIXER_NRDEVICES) { /* 没有找到匹配项 */

        fprintf(stderr, "%s is not a valid mixer device\n", dev);

        usage();

      }

      /* 查找到有效的混音设备 */

      device = i;

      /* 获取增益值 */

      if (argc == 4) {

        /* 左、右声道均给定 */

        left  = atoi(argv[2]);

        right = atoi(argv[3]);

      } else {

        /* 左、右声道设为相等 */

        left  = atoi(argv[2]);

        right = atoi(argv[2]);

      }

     

      /* 对非立体声设备给出警告信息 */

      if ((left != right) && !((1 << i) & stereodevs)) {

        fprintf(stderr, "warning: %s is not a stereo device\n", dev);

      }

     

      /* 将两个声道的值合到同一变量中 */

      level = (right << 8) + left;

     

      /* 设置增益 */

      status = ioctl(fd, MIXER_WRITE(device), &level);

      if (status == -1) {

        perror("MIXER_WRITE ioctl failed");

        exit(1);

      }

      /* 获得从驱动返回的左右声道的增益 */

      left  = level & 0xff;

      right = (level & 0xff00) >> 8;

      /* 显示实际设置的增益 */

      fprintf(stderr, "%s gain set to %d%% / %d%%\n", dev, left, right);

      /* 关闭混音设备 */

      close(fd);

      return 0;

    }

     

    编译好上面的程序之后,先不带任何参数执行一遍,此时会列出声卡上所有可用的混音通道:

    [xiaowp@linuxgam sound]$ ./mixer

    usage: ./mixer <device> <left-gain%> <right-gain%>

           ./mixer <device> <gain%>

     

    Where <device> is one of:

    vol pcm speaker line mic cd igain line1 phin video

     

    之后就可以很方便地设置各个混音通道的增益大小了,例如下面的命令就能够将CD输入的左、右声道的增益分别设置为80%和90%:

    [xiaowp@linuxgam sound]$ ./mixer cd 80 90

    cd gain set to 80% / 90%

    五、小结

    随着Linux平台下多媒体应用的逐渐深入,需要用到数字音频的场合必将越来越广泛。虽然数字音频牵涉到的概念非常多,但在Linux下进行最基本的音频编程却并不十分复杂,关键是掌握如何与OSS或者ALSA这类声卡驱动程序进行交互,以及如何充分利用它们提供的各种功能,熟悉一些最基本的音频编程框架和模式对初学者来讲大有裨益。

     

    展开全文
  • 音频采集基础知识

    2020-01-20 14:08:24
    抽样:对模拟信号进行周期性扫描,把时间上连续的信号变成时间上离散的信号,对应采样频率; 量化:用一组规定电平,把瞬时抽样值用最接近电平值来表示,通常是用二进制表示; 编码:用一组二进制码组来表示每一...

    自然界中的声音非常复杂,波形极其复杂,需要将模拟信号转换成数字信号,通常我们采用的是脉冲代码调制编码,即PCM编码。PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。 

    • 抽样:对模拟信号进行周期性扫描,把时间上连续的信号变成时间上离散的信号,对应采样频率;
    • 量化:用一组规定的电平,把瞬时抽样值用最接近的电平值来表示,通常是用二进制表示;
    • 编码:用一组二进制码组来表示每一个有固定电平的量化值,对应采样位数;

     

    (1)音频文件的组成:文件格式(或者音频容器)+数据格式(或者音频编码)

    • 文件格式是用于形容文件本身的格式,可以通过多种不同方法为真正的音频数据编码,例如CAF文件便是一种文件格式,它能够包含MP3格式,线性PCM以及其他数据格式音频
    • 线性PCM:这是表示线性脉冲编码机制,主要是描写用于将模拟声音数据转换成数组格式的技术,简单地说也就是未压缩的数据。因为数据是未压缩的,所以我们便可以最快速地播放出音频,而如果空间不是问题的话这便是iPhone 音频的优先代码选择

    (2).音频文件计算大小
    简述:声卡对声音的处理质量可以用三个基本参数来衡量,即采样频率,采样位数和声道数。  

    • 采样频率:单位时间内采样次数。采样频率越大,采样点之间的间隔就越小,数字化后得到的声音就越逼真,但相应的数据量就越大,声卡一般提供11.025kHz,22.05kHz和44.1kHz等不同的采样频率。

    • 采样位数:记录每次采样值数值大小的位数。采样位数通常有8bits或16bits两种,采样位数越大,所能记录的声音变化度就越细腻,相应的数据量就越大。

    • 声道数:处理的声音是单声道还是立体声。单声道在声音处理过程中只有单数据流,而立体声则需要左右声道的两个数据流。显然,立体声的效果要好,但相应数据量要比单声道数据量加倍。

    • 声音数据量的计算公式:数据量(字节 / 秒)=(采样频率(Hz)* 采样位数(bit)* 声道数)/ 8
      单声道的声道数为1,立体声的声道数为2. 字节B,1MB=1024KB = 1024*1024B

    • 一个采样率为44.1KHz,采样大小为16bit,双声道的PCM编码的WAV文件,它的数据速率=44.1K×16×2 bps=1411.2 Kbps= 176.4 KB/s。
      1秒的数据就这么大,这是不合适的,需要进行压缩编码,所以就会有aac,MP3,wma等等格式.

    struct AudioStreamBasicDescription
    {
       Float64             mSampleRate;        // 采样率 :Hz
       AudioFormatID       mFormatID;          // 采样数据的类型,PCM,AAC等
       AudioFormatFlags    mFormatFlags;       // 每种格式特定的标志,无损编码 ,0表示没有
       UInt32              mBytesPerPacket;    // 一个数据包中的字节数
       UInt32              mFramesPerPacket;   // 一个数据包中的帧数,每个packet的帧数。如果是未压缩的音频数据,值是1。动态帧率格式,这个值是一个较大的固定数字,比如说AAC的1024。如果是动态大小帧数(比如Ogg格式)设置为0。
       UInt32              mBytesPerFrame;     // 每一帧中的字节数
       UInt32              mChannelsPerFrame;  // 每一帧数据中的通道数,单声道为1,立体声为2
       UInt32              mBitsPerChannel;    // 每个通道中的位数,1byte = 8bit
       UInt32              mReserved;          // 8字节对齐,填0
    };
    typedef struct AudioStreamBasicDescription  AudioStreamBasicDescription;
    

    https://www.jianshu.com/p/a671f5b17fc1

    https://www.jianshu.com/p/e2d072b9e4d8 

    展开全文
  • 原理是用一个固定频率对模拟信号进行采样,采样后的信号波形上看就像一串连续的幅值不一脉冲(脉搏似短暂起伏电冲击),把这些脉冲幅值按一定精度进行量化,这些量化后数值被连续的输出、传输、处理或...

    PCM(Pulse Code Modulation),脉冲编码调制。人耳听到的是模拟信号,PCM是把声音从模拟信号转化为数字信号的技术。原理是用一个固定的频率对模拟信号进行采样,采样后的信号在波形上看就像一串连续的幅值不一的脉冲(脉搏似的短暂起伏的电冲击),把这些脉冲的幅值按一定精度进行量化,这些量化后的数值被连续的输出、传输、处理或记录到存储介质中,所有这些组成了数字音频的产生过程(抽样、量化、编码三个过程)。

    播放音乐时,应用程序从存储介质中读取音频数据(MP3、WMA、AAC…),经过解码后,最终送到音频驱动程序中的就是PCM数据,反过来,在录音时,音频驱动不停地把采样所得的PCM数据送回给应用程序,由应用程序完成压缩、存储等任务。

    下面我们通过ffmpeg音频转原生的PCM

    public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
        public void play(View view) {
            player();
        }
        private static String[] permissions = new String[]{
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_EXTERNAL_STORAGE
        };
        private void player(){
            String[] permissions1 = checkPermission(this);
            if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
                if(permissions1.length<=0){
                    MyPlayer myPlayer = new MyPlayer();
                    String input = new File(Environment.getExternalStorageDirectory(),"input.mp3").getAbsolutePath();
                    String output = new File(Environment.getExternalStorageDirectory(),"output.pcm").getAbsolutePath();
                    myPlayer.sound(input,output);
                }else{
                    ActivityCompat.requestPermissions(this, permissions, 100);
                }
            }else{//6.0以下不需要申请权限
                MyPlayer myPlayer = new MyPlayer();
                String input = new File(Environment.getExternalStorageDirectory(),"input.mp3").getAbsolutePath();
                String output = new File(Environment.getExternalStorageDirectory(),"output.pcm").getAbsolutePath();
                myPlayer.sound(input,output);
            }
        }
        public static String[] checkPermission(Context context){
            List<String> data = new ArrayList<>();//存储未申请的权限
            for (String permission : permissions) {
                int checkSelfPermission = ContextCompat.checkSelfPermission(context, permission);
                if(checkSelfPermission == PackageManager.PERMISSION_DENIED){//未申请
                    data.add(permission);
                }
            }
            return data.toArray(new String[data.size()]);
        }
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            if(requestCode == 100){
                boolean flag = true;
                for(int i :  grantResults){
                    if(i != PackageManager.PERMISSION_GRANTED){
                        flag = false;
                        break;
                    }
                }
                if(flag){
                    player();
                }else{
                    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
                }
            }else{
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }
    }
    public class MyPlayer {
        static{
            System.loadLibrary("avutil-54");
            System.loadLibrary("swresample-1");
            System.loadLibrary("avcodec-56");
            System.loadLibrary("avformat-56");
            System.loadLibrary("swscale-3");
            System.loadLibrary("postproc-53");
            System.loadLibrary("avfilter-5");
            System.loadLibrary("avdevice-56");
            System.loadLibrary("native-lib");
        }
        public native void sound(String input,String output);
    }

    核心代码

    #include <jni.h>
    #include <string>
    #include <android/log.h>
    #define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"twy",FORMAT,##__VA_ARGS__);
    #define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"twy",FORMAT,##__VA_ARGS__);
    
    #define MAX_AUDIO_FRME_SIZE 48000 * 4
    extern  "C"{
    //封装格式
    #include "libavformat/avformat.h"
    //解码
    #include "libavcodec/avcodec.h"
    //缩放
    #include "libswscale/swscale.h"
    //重采样
    #include "libswresample/swresample.h"
    };
    extern "C"
    {
        JNIEXPORT void JNICALL
        Java_com_dongnao_ffmpegmusicdemo_MyPlayer_sound(JNIEnv *env, jobject instance, jstring input_,
                                                           jstring output_) {
            const char *input = env->GetStringUTFChars(input_, 0);
            const char *output = env->GetStringUTFChars(output_, 0);
            av_register_all();
            AVFormatContext *pFormatCtx = avformat_alloc_context();
            //打开音频文件
            if(avformat_open_input(&pFormatCtx,input,NULL,NULL) != 0){
                LOGI("%s","无法打开音频文件");
                return;
            }
            //获取输入文件信息
            if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
                LOGI("%s","无法获取输入文件信息");
                return;
            }
            //获取音频流索引位置
            int i = 0, audio_stream_idx = -1;
            for(; i < pFormatCtx->nb_streams;i++){
                if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
                    audio_stream_idx = i;
                    break;
                }
            }
        //获取解码器
            AVCodecContext *codecCtx = pFormatCtx->streams[audio_stream_idx]->codec;
            AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
            //打开解码器
            if(avcodec_open2(codecCtx,codec,NULL) < 0){
                LOGI("%s","无法打开解码器");
                return;
            }
            //压缩数据
            AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
            //解压缩数据
            AVFrame *frame = av_frame_alloc();
            //frame->16bit 44100 PCM 统一音频采样格式与采样率
            SwrContext *swrContext = swr_alloc();
        //    音频格式  重采样设置参数
            AVSampleFormat in_sample = codecCtx->sample_fmt;
        //    输出采样格式
            AVSampleFormat out_sample=AV_SAMPLE_FMT_S16;
        // 输入采样率
            int in_sample_rate = codecCtx->sample_rate;
        //    输出采样
            int out_sample_rate=44100;
        //    输入声道布局
            uint64_t in_ch_layout=codecCtx->channel_layout;
        //    输出声道布局
            uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
            swr_alloc_set_opts(swrContext,out_ch_layout,out_sample,out_sample_rate
                    ,in_ch_layout,in_sample,in_sample_rate,0,NULL);
            swr_init(swrContext);
            int got_frame=0;
            int ret;
            int out_channerl_nb = av_get_channel_layout_nb_channels(out_ch_layout);
            LOGE("声道数量%d ",out_channerl_nb);
            int count=0;
        //    设置音频缓冲区间 16bit   44100  PCM数据
            uint8_t *out_buffer = (uint8_t *) av_malloc(2 * 44100);
            FILE *fp_pcm = fopen(output, "wb");
            while (av_read_frame(pFormatCtx, packet)>=0) {
        
                ret = avcodec_decode_audio4(codecCtx, frame, &got_frame, packet);
                LOGE("正在解码%d",count++);
                if (ret < 0) {
                    LOGE("解码完成");
                }
        //        解码一帧
                if (got_frame > 0) {
                    swr_convert(swrContext, &out_buffer, 2 * 44100,
                                (const uint8_t **) frame->data, frame->nb_samples);
                    int out_buffer_size=av_samples_get_buffer_size(NULL, out_channerl_nb, frame->nb_samples, out_sample, 1);
                    fwrite(out_buffer, 1, out_buffer_size,fp_pcm);
                }
                av_free_packet(packet);
            }
            fclose(fp_pcm);
            av_frame_free(&frame);
            av_free(out_buffer);
            swr_free(&swrContext);
            avcodec_close(codecCtx);
            avformat_close_input(&pFormatCtx);
        }
    }
    
    

     

    Native 层回调播放

    public class MyPlayer {
        static{
            System.loadLibrary("avutil-54");
            System.loadLibrary("swresample-1");
            System.loadLibrary("avcodec-56");
            System.loadLibrary("avformat-56");
            System.loadLibrary("swscale-3");
            System.loadLibrary("postproc-53");
            System.loadLibrary("avfilter-5");
            System.loadLibrary("avdevice-56");
            System.loadLibrary("native-lib");
        }
        private AudioTrack audioTrack;
        public native void sound(String input,String output);
    
        //    这个方法  是C进行调用  通道数
        public void createAudio(int sampleRateInHz,int nb_channals) {
    
            int channaleConfig;
            if (nb_channals == 1) {
                channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
            } else if (nb_channals == 2) {
                channaleConfig = AudioFormat.CHANNEL_OUT_STEREO;
            }else {
                channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
            }
            int buffersize= AudioTrack.getMinBufferSize(sampleRateInHz,
                    channaleConfig, AudioFormat.ENCODING_PCM_16BIT);
            audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,sampleRateInHz,channaleConfig,
                    AudioFormat.ENCODING_PCM_16BIT,buffersize,AudioTrack.MODE_STREAM);
            audioTrack.play();
        }
    
        //C传入音频数据
        public synchronized  void playTrack(byte[] buffer, int lenth) {
            if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                audioTrack.write(buffer, 0, lenth);//写入进来喇叭就直接播放了
            }
        }
    }
    #include <jni.h>
    #include <string>
    #include <android/log.h>
    #define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"twy",FORMAT,##__VA_ARGS__);
    #define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"twy",FORMAT,##__VA_ARGS__);
    
    #define MAX_AUDIO_FRME_SIZE 48000 * 4
    extern  "C"{
    //封装格式
    #include "libavformat/avformat.h"
    //解码
    #include "libavcodec/avcodec.h"
    //缩放
    #include "libswscale/swscale.h"
    //重采样
    #include "libswresample/swresample.h"
    };
    extern "C"
    {
        JNIEXPORT void JNICALL
        Java_com_dongnao_ffmpegmusicdemo_MyPlayer_sound(JNIEnv *env, jobject instance, jstring input_,
                                                           jstring output_) {
            const char *input = env->GetStringUTFChars(input_, 0);
            const char *output = env->GetStringUTFChars(output_, 0);
            av_register_all();
            AVFormatContext *pFormatCtx = avformat_alloc_context();
            //打开音频文件
            if(avformat_open_input(&pFormatCtx,input,NULL,NULL) != 0){
                LOGI("%s","无法打开音频文件");
                return;
            }
            //获取输入文件信息
            if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
                LOGI("%s","无法获取输入文件信息");
                return;
            }
            //获取音频流索引位置
            int i = 0, audio_stream_idx = -1;
            for(; i < pFormatCtx->nb_streams;i++){
                if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
                    audio_stream_idx = i;
                    break;
                }
            }
        //获取解码器
            AVCodecContext *codecCtx = pFormatCtx->streams[audio_stream_idx]->codec;
            AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
            //打开解码器
            if(avcodec_open2(codecCtx,codec,NULL) < 0){
                LOGI("%s","无法打开解码器");
                return;
            }
            //压缩数据
            AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
            //解压缩数据
            AVFrame *frame = av_frame_alloc();
            //音频转换上下文 frame->16bit 44100 PCM 统一音频采样格式与采样率
            SwrContext *swrContext = swr_alloc();
        //    音频格式  重采样设置参数
            AVSampleFormat in_sample = codecCtx->sample_fmt;
        //    输出采样格式
            AVSampleFormat out_sample=AV_SAMPLE_FMT_S16;
        // 输入采样率
            int in_sample_rate = codecCtx->sample_rate;
        //    输出采样
            int out_sample_rate=44100;
        //    输入声道布局
            uint64_t in_ch_layout=codecCtx->channel_layout;
        //    输出声道布局
            uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
            swr_alloc_set_opts(swrContext,out_ch_layout,out_sample,out_sample_rate
                    ,in_ch_layout,in_sample,in_sample_rate,0,NULL);
            swr_init(swrContext);
    
            //    获取通道数  2
            int out_channer_nb = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
            //    反射得到Class类型
            jclass david_player = env->GetObjectClass(instance);
            //    反射得到createAudio方法
            jmethodID createAudio = env->GetMethodID(david_player, "createAudio", "(II)V");
            //    反射调用createAudio
            env->CallVoidMethod(instance, createAudio, 44100, out_channer_nb);
    
            jmethodID audio_write = env->GetMethodID(david_player, "playTrack", "([BI)V");
    
            int got_frame=0;
            int ret;
            int out_channerl_nb = av_get_channel_layout_nb_channels(out_ch_layout);
            LOGE("声道数量%d ",out_channerl_nb);
            int count=0;
        //    设置音频缓冲区间 16bit   44100  PCM数据
            uint8_t *out_buffer = (uint8_t *) av_malloc(2 * 44100);
            FILE *fp_pcm = fopen(output, "wb");
            while (av_read_frame(pFormatCtx, packet)>=0) {
    
                ret = avcodec_decode_audio4(codecCtx, frame, &got_frame, packet);
                LOGE("正在解码%d",count++);
                if (ret < 0) {
                    LOGE("解码完成");
                }
        //        解码一帧
                if (got_frame > 0) {
                    swr_convert(swrContext, &out_buffer, 2 * 44100,
                                (const uint8_t **) frame->data, frame->nb_samples);
                    int out_buffer_size=av_samples_get_buffer_size(NULL, out_channerl_nb, frame->nb_samples, out_sample, 1);
                    //缓冲区的大小
                    int size = av_samples_get_buffer_size(NULL, out_channer_nb, frame->nb_samples,AV_SAMPLE_FMT_S16, 1);
                    jbyteArray audio_sample_array = env->NewByteArray(size);
                    env->SetByteArrayRegion(audio_sample_array, 0, size, (const jbyte *) out_buffer);
                    env->CallVoidMethod(instance, audio_write, audio_sample_array, size);
                    env->DeleteLocalRef(audio_sample_array);
                    //fwrite(out_buffer, 1, out_buffer_size,fp_pcm);
                }
                av_free_packet(packet);
            }
            fclose(fp_pcm);
            av_frame_free(&frame);
            av_free(out_buffer);
            swr_free(&swrContext);
            avcodec_close(codecCtx);
            avformat_close_input(&pFormatCtx);
        }
    }
    
    

    这样手机就能播放出来

     

     

     

     

     

    展开全文
  • 采样率是频率采样,采样精度是幅度采样,也称量化,采样率越高,越能还原原始的连续信号,量化精度是指可以将模拟信号分成多少个等级,量化精度越高,音乐的声压振幅越接近原音乐。 当精度越低,数字音频在...

    在自然界中声音的本质是振动,是一种能量波,有频率有振幅,频率高低就是音调,振幅大小就是音量;

    数字音频系统中,需要将声波波形信号通过ADC转换成计算机支持的二进制,进而保存成音频文件,这一过程叫做音频采样,采样是把连续的时间信号,变成离散的数字信号。采样率是指每秒钟采集多少个样本。采样率是对频率采样,采样精度是对幅度采样,也称量化,采样率越高,越能还原原始的连续信号,量化精度是指可以将模拟信号分成多少个等级,量化精度越高,音乐的声压振幅越接近原音乐。

    当精度越低,数字音频在纵轴上将会压缩,很多原本能细化区分开来的点将成为同样的量化数字,这样的音乐听起来就会有严重的沙哑声,这就是丢失了动态压缩范围,有了量化噪声。

    所谓音量调整,软件形式就是对已经量化后的数字音频数据进行放大和缩小处理。一般来说,对于一套系统,在数字处理领域只允许对原始信号进行缩小处理,也就是说最大音量就是使其保持原始幅度0db,因为放大了将会有溢出的风险,0db不增不减,调小音量就是在原始基础上减小相应的db了,当然这里说的都是解码后的pcm信号。

    其实简单说白了,声音在软件处理中就是一段数据,一块buffer,所有的处理都是在这这块buffer做文章,追踪定位问题也就是盯着buffer的处理逻辑就好了。大道至简。

     

    展开全文
  • 音频中PCM概念

    2014-08-25 16:10:00
    我们知道在现实生活中,人耳听到的声音是模拟信号,PCM就是要把声音从模拟转换成数字信号的一种技术,他的原理简单地说就是利用一个固定的频率对模拟信号进行采样,采样后的信号在波形上看就像一串连续的幅值不一的....
  • PCM就是要把声音从模拟转换成数字信号的一种技术,他的原理简单地说就是利用一个固定的频率对模拟信号进行采样,采样后的信号在波形上看就像一串连续的幅值不一的脉冲,把这些脉冲的幅值按一定的精度进行量化,这些...
  • 音频编码种类

    千次阅读 2013-11-20 13:50:34
    PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。 涉及到一些概念 1.采样率和采样大小 声音其实是一种能量波,因此也有频率和振幅特征,频率对应于时间轴线,振幅对应于电平轴线。波是...
  • 1.PCM(脉冲编码调制)技术及声波数字化我们知道声音是一种连续变化的波形,是模拟信号,而我们计算机或者说MCU存储及使用都是二进制数字信号,所以我们需要声音信号进行一些处理,这里便使用到了PCM技术,...
  • 音频PCM知识整理

    2020-02-25 10:19:51
    原理是用一个固定频率对模拟信号进行采样,采样后的信号波形上看就像一串连续的幅值不一脉冲(脉搏似短暂起伏电冲击),把这些脉冲幅值按一定精度进行量化,这些量化后数值被连续的输出、传输、处理或...
  • PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。 1、什么是采样率和采样大小(位/bit)?  声音其实是一种能量波,因此也有频率和振幅特征,频率对应于时间轴线,振幅对应于电平轴线...
  • 音频的采样率和采样大小

    万次阅读 2015-01-26 13:03:38
    PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。 抽样:在音频采集中叫做采样率。 由于声音其实是一种能量波,因此也有频率和振幅特征,频率对应于时间轴线,振幅对应于电平轴线。波是...
  • 音频基础知识汇总

    2021-01-20 03:15:59
     数码音频系统是通过将声波波形转换成一连串二进制数据来再现原始声音(原始声音是模拟信号),实现这个步骤使用设备是模/数转换器(A/D转换器,或者ADC,或者analog to digital convert)。它以每秒上万次...
  • PCM音频基础知识及采样数据处理 目录 PCM简介 ...原理是用一个固定频率对模拟信号进行采样,采样后的信号波形上看就像一串连续的幅值不一脉冲(脉搏似短暂起伏电冲击),把这些脉冲幅值按一
  • 音频相关知识

    2018-09-05 16:47:18
    我们知道在现实生活中,人耳听到的声音是模拟信号,PCM就是要把声音从模拟转换成数字信号的一种技术,他的原理简单地说就是利用一个固定的频率对模拟信号进行采样,采样后的信号在波形上看就像一串连续的幅值不一的...
  • PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。 ##1、什么是采样率和采样大小(位/bit)?  声音其实是一种能量波,因此也有频率和振幅特征,频率对应于时间轴线,振幅对应于电平轴线。波...
  • 语音信号采样与量化

    千次阅读 2020-11-09 07:22:47
    模拟声音的信号是个连续量,由许多具有不同振幅和频率的正弦波组成。实际声音信号的计算机获取...对模拟音频数字化过程涉及到音频的采样、量化和编码。 采样和量化的过程可由A/D转换器实现。A/D转换器以固定的频率...
  • 什么是音频的采样率和采样大小

    千次阅读 2013-08-01 13:21:28
    PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。 抽样:在音频采集中叫做采样率。 由于声音其实是一种能量波,因此也有频率和振幅特征,频率对应于时间轴线,振幅对应于电平轴线。波是...
  • 原理是用一个固定频率对模拟信号进行采样,采样后的信号波形上看就像一串连续的幅值不一脉冲(脉搏似短暂起伏电冲击),把这些脉冲幅值按一定精度进行量化,这些量化后数值被连续的输出、传输、处理或...
  • 音频基础概念及常见编码格式

    千次阅读 2018-09-11 23:17:33
    声音是由物体振动而产生 声波三要素 声波三要素是频率、振幅和波形,频率代表音阶高低,振幅代表...原理是用一个固定频率对模拟信号进行采样,采样后的信号波形上看就像一串连续的幅值不一脉冲(脉...
  • 嵌入式 PCM音频

    千次阅读 2013-12-02 14:51:30
    我们知道在现实生活中,人耳听到的声音是模拟信号,PCM就是要把声音从模拟转换成数字信号的一种技术,他的原理简单地说就是利用一个固定的频率对模拟信号进行采样,采样后的信号在波形上看就像一串连续的幅值不一的...
  • PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。 1、什么是采样率和采样大小(位/bit)?  声音其实是一种能量波,因此也有频率和振幅特征,频率对应于时间轴线,振幅对应于电平轴线...

空空如也

空空如也

1 2 3 4
收藏数 61
精华内容 24
关键字:

对模拟音频信号的连续波形