alsa编程 linux

2018-01-06 19:24:40 su1041168096 阅读数 555

今天要在linux下搞音频编程,一般使用ALSA音频编程。ALSA是Advanced Linux Sound Architecture的缩写,高级Linux声音架构的简称,它在Linux操作系统上提供了音频和MIDI(Musical Instrument Digital Interface,音乐设备数字化接口)的支持。
首先介绍一下一些关于ALSA编程的知识:

1、GNU/Linux 系统下三大主流声卡驱动程序集:
Linux 有三个主流的声卡驱动程序集:OSS/Lite(也称为OSS/Free)、OSS/Full
(商业软件)、ALSA(自由软件)。

OSS/Lite 是现在linux内核中自带的声卡驱动程序集,最初由 Hannu Savolainen
开发。后来 Hannu 跑去开发 Open Sound System(也就是上面所说的OSS/Full)。
由于 Hannu 的“逃跑”,RH 资助 Alan Cox 增强 Hannu 开发的驱动程序,并使它们
完全模块化。现在 Alan Cox 是内核声卡驱程集的维护人。OSS/Lite 从kernel-2.0开
始并入内核,现在大家使用的声卡驱程默认都是OSS/Lite中的。

OSS/Full 是由 4Front Technologies 开发并销售的商业软件。它可以驱动很多
声卡并且可以用在很多 UNIX 系统中。OSS/Full 完全兼容以前基于 OSS/Lite 开发
的应用程序。作为一个商业软件,你虽然可以使用它,但是你得不到它的源代码,并且
你必须为此而付钱。

ALSA 是linux内核的下一代标准声卡驱动集。开始时 Jaroslav Kysela 等人为
Gravis UltraSound Card 开发驱动程序,后来该计划改名为 ALSA「先进的linux音频
体系」,因为他们认为 ALSA 比原来内核中的 OSS/Lite 驱动程序集更优秀,完全可以
代替 OSS/Lite。他们是对的,ALSA 支持的声卡比 OSS/Lite 多,完全兼容以前基于
OSS 开发的程序,SMP(多处理器) 与 线程安全设计,并且从 2.5 分支的内核开始,
ALSA 的驱动程序集开始并入内核,大家可以在今年的 2.6 版本的内核中看到使用它们。

2、为什么要使用 ALSA 开发音频程序
首先,ALSA 是 linux 以后声卡驱动程序的标准,OSS/Lite 迟早会从内核中去除。
开发基于 ALSA 的音频程序可以保证以后的兼容。
其次,我们简单比较一下开发基于 OSS 与 ALSA 的方法。
OSS 向应用程序提供了一系列的系统接口。开发基于 OSS 的应用程序需要使用
open,close,ioctl,read,write 等低级系统调用来完成音频设备的控制、音频流的输入
输出。
而 ALSA 则为应用程序开发人员提供了一个优秀的音频库。利用该音频库,开发
人员可以方便快捷地开发出自己的应用程序,细节则留给音频库内部处理。当然 ALSA
也提供了类似于 OSS 的系统接口,不过 ALSA 的开发者建议应用程序开发者使用音频
库而不是驱程API。

3、那么我该从何开始呢
第一步当然是安装 ALSA 驱动程序与音频库。
当前 ALSA 有两个分支,一个是以前的0.5版本,一个是现在的0.9。ALSA的开发者
已经不支持0.5版本了,所以我们要使用0.9。大家可以在 ALSA 的主页
www.alsa-project.org 上下载安装。这个页面上的信息对大家安装很有用:
www.alsa-project.org/alsa-doc ,建议先浏览一下。

第二步是参考文档与例子。
在 ALSA 的文档页面上有两篇为应用程序开发者提供的文章:
ALSA 0.9.0 HOWTO [ http://www.suse.de/~mana/alsa090_howto.html ]
Howto use the ALSA API [ http://www.op.net/~pbd/alsa-audio.html ]
当然,还有音频库API的在线参考:
http://www.alsa-project.org/alsa-doc/alsa-lib

在开发过程中大家肯定会遇到问题或者困难,这时请教跟讨论就少不了了。所以要
做的第三步就是订阅 ALSA 的邮件列表了: alsa-devel@lists.sourceforge.net 。或者
你 E 文不好的话,可以在这里跟别人讨论一下。不过不要期望过高,国内开发 ALSA
音频程序的人本来就少,能来这里的人就更少了。

要是你已经完成以上几步的话,那么你就应该开始开发了。

  1. ALSA设备文件结构
    我们从alsa在linux中的设备文件结构开始我们的alsa之旅. 看看我的电脑中的alsa驱动的设备文件结构:

cd/dev/snd ls -l

crw-rw—-+ 1 root audio 116, 8 2011-02-23 21:38 controlC0
crw-rw—-+ 1 root audio 116, 4 2011-02-23 21:38 midiC0D0
crw-rw—-+ 1 root audio 116, 7 2011-02-23 21:39 pcmC0D0c
crw-rw—-+ 1 root audio 116, 6 2011-02-23 21:56 pcmC0D0p
crw-rw—-+ 1 root audio 116, 5 2011-02-23 21:38 pcmC0D1p
crw-rw—-+ 1 root audio 116, 3 2011-02-23 21:38 seq
crw-rw—-+ 1 root audio 116, 2 2011-02-23 21:38 timer
$

我们可以看到以下设备文件:

controlC0 –> 用于声卡的控制,例如通道选择,混音,麦克风的控制等
midiC0D0 –> 用于播放midi音频
pcmC0D0c –〉 用于录音的pcm设备
pcmC0D0p –〉 用于播放的pcm设备
seq –〉 音序器
timer –〉 定时器
其中,C0D0代表的是声卡0中的设备0,pcmC0D0c最后一个c代表capture,pcmC0D0p最后一个p代表playback,这些都是alsa-driver中的命名规则。从上面的列表可以看出,我的声卡下挂了6个设备,根据声卡的实际能力,驱动实际上可以挂上更多种类的设备,在include/sound/core.h中,定义了以下设备类型:

#define SNDRV_DEV_TOPLEVEL  ((__force snd_device_type_t) 0)  
#define SNDRV_DEV_CONTROL   ((__force snd_device_type_t) 1)  
#define SNDRV_DEV_LOWLEVEL_PRE  ((__force snd_device_type_t) 2)  
#define SNDRV_DEV_LOWLEVEL_NORMAL ((__force snd_device_type_t) 0x1000)  
#define SNDRV_DEV_PCM       ((__force snd_device_type_t) 0x1001)  
#define SNDRV_DEV_RAWMIDI   ((__force snd_device_type_t) 0x1002)  
#define SNDRV_DEV_TIMER     ((__force snd_device_type_t) 0x1003)  
#define SNDRV_DEV_SEQUENCER ((__force snd_device_type_t) 0x1004)  
#define SNDRV_DEV_HWDEP     ((__force snd_device_type_t) 0x1005)  
#define SNDRV_DEV_INFO      ((__force snd_device_type_t) 0x1006)  
#define SNDRV_DEV_BUS       ((__force snd_device_type_t) 0x1007)  
#define SNDRV_DEV_CODEC     ((__force snd_device_type_t) 0x1008)  
#define SNDRV_DEV_JACK          ((__force snd_device_type_t) 0x1009)  
#define SNDRV_DEV_LOWLEVEL  ((__force snd_device_type_t) 0x2000) 

通常,我们更关心的是pcm和control这两种设备。

5.一些例子,这些例子在官方文档也有,请自行查阅:

1.)显示一些PCM的类型和格式:

#include <iostream>
#include <alsa/asoundlib.h>

int main()
{
       std::cout << "ALSA library version: " << SND_LIB_VERSION_STR << std::endl;

       std::cout << "PCM stream types: " << std::endl;

       for (int val=0; val <= SND_PCM_STREAM_LAST; ++val)
              std::cout << snd_pcm_stream_name((snd_pcm_stream_t)val) << std::endl;
       std::cout << std::endl;

       std::cout << "PCM access types: " << std::endl;
       for (int val=0; val <= SND_PCM_ACCESS_LAST; ++val)
              std::cout << snd_pcm_access_name((snd_pcm_access_t)val) << std::endl;
       std::cout << std::endl;

       std::cout << "PCM subformats: " << std::endl;
       for (int val=0; val <= SND_PCM_SUBFORMAT_LAST; ++val)
              std::cout << snd_pcm_subformat_name((snd_pcm_subformat_t)val) << " (" << snd_pcm_subformat_description((snd_pcm_subformat_t)val) << ")" << std::endl;
       std::cout << std::endl;

       std::cout << "PCM states: " << std::endl;
       for (int val=0; val <= SND_PCM_STATE_LAST; ++val)
              std::cout << snd_pcm_state_name((snd_pcm_state_t)val) << std::endl;
       std::cout << std::endl;


       std::cout << "PCM formats: " << std::endl;
       for (int val=0; val <= SND_PCM_FORMAT_LAST; ++val)
              std::cout << snd_pcm_format_name((snd_pcm_format_t)val) << " (" << snd_pcm_format_description((snd_pcm_format_t)val) << ")" << std::endl;
       std::cout << std::endl;

}

2.)打开PCM设备和设置参数


    #include <iostream>
    #include <alsa/asoundlib.h>

int main()
{
       int                               rc;
       snd_pcm_t*                         handle;
       snd_pcm_hw_params_t*      params;
       unsigned int                  val, val2;
       int                               dir;
       snd_pcm_uframes_t             frames;

       if ( (rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0)
       {
              std::cerr << "unable to open pcm devices: " << snd_strerror(rc) << std::endl;
              exit(1);
       }

       snd_pcm_hw_params_alloca(&params);

       snd_pcm_hw_params_any(handle, params);

       snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);

       snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);

       snd_pcm_hw_params_set_channels(handle, params, 2);

       val = 44100;

       snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);

       if ( (rc = snd_pcm_hw_params(handle, params)) < 0)
       {
              std::cerr << "unable to set hw parameters: " << snd_strerror(rc) << std::endl;
              exit(1);
       }

       std::cout << "PCM handle name = " << snd_pcm_name(handle) << std::endl;

       std::cout << "PCM state = " << snd_pcm_state_name(snd_pcm_state(handle)) << std::endl;

       snd_pcm_hw_params_get_access(params, (snd_pcm_access_t *)&val);

       std::cout << "access type = " << snd_pcm_access_name((snd_pcm_access_t)val) << std::endl;

       snd_pcm_hw_params_get_format(params, (snd_pcm_format_t*)(&val));

       std::cout << "format = '" << snd_pcm_format_name((snd_pcm_format_t)val) << "' (" << snd_pcm_format_description((snd_pcm_format_t)val) << ")" << std::endl;

      snd_pcm_hw_params_get_subformat(params, (snd_pcm_subformat_t *)&val);
      std::cout << "subformat = '" <<
    snd_pcm_subformat_name((snd_pcm_subformat_t)val) << "' (" << snd_pcm_subformat_description((snd_pcm_subformat_t)val) << ")" << std::endl;

      snd_pcm_hw_params_get_channels(params, &val);
      std::cout << "channels = " << val << std::endl;

      snd_pcm_hw_params_get_rate(params, &val, &dir);
      std::cout << "rate = " << val << " bps" << std::endl;

       snd_pcm_hw_params_get_period_time(params, &val, &dir);
      std::cout << "period time = " << val << " us" << std::endl;

      snd_pcm_hw_params_get_period_size(params, &frames, &dir);
      std::cout << "period size = " << static_cast<int>(frames) << " frames" << std::endl;

       snd_pcm_hw_params_get_buffer_time(params, &val, &dir);
      std::cout << "buffer time = " << val << " us" << std::endl;

       snd_pcm_hw_params_get_buffer_size(params, (snd_pcm_uframes_t *) &val);
      std::cout << "buffer size = " << val << " frames" << std::endl;

      snd_pcm_hw_params_get_periods(params, &val, &dir);
      std::cout << "periods per buffer = " << val << " frames" << std::endl;

       snd_pcm_hw_params_get_rate_numden(params, &val, &val2);
      std::cout << "exact rate = " << val/val2 << " bps" << std::endl;

      val = snd_pcm_hw_params_get_sbits(params);
      std::cout << "significant bits = " << val << std::endl;

      snd_pcm_hw_params_get_tick_time(params, &val, &dir);
      std::cout << "tick time = " << val << " us" << std::endl;

      val = snd_pcm_hw_params_is_batch(params);
      std::cout << "is batch = " << val << std::endl;

      val = snd_pcm_hw_params_is_block_transfer(params);
      std::cout << "is block transfer = " << val << std::endl;

      val = snd_pcm_hw_params_is_double(params);
      std::cout << "is double = " << val << std::endl;

       val = snd_pcm_hw_params_is_half_duplex(params);
      std::cout << "is half duplex = " << val << std::endl;

       val = snd_pcm_hw_params_is_joint_duplex(params);
      std::cout << "is joint duplex = " << val << std::endl;

       val = snd_pcm_hw_params_can_overrange(params);
      std::cout << "can overrange = " << val << std::endl;

      val = snd_pcm_hw_params_can_mmap_sample_resolution(params);
      std::cout << "can mmap = " << val << std::endl;

      val = snd_pcm_hw_params_can_pause(params);
      std::cout << "can pause = " << val << std::endl;

       val = snd_pcm_hw_params_can_resume(params);
      std::cout << "can resume = " << val << std::endl;

      val = snd_pcm_hw_params_can_sync_start(params);
      std::cout << "can sync start = " << val << std::endl;

      snd_pcm_close(handle);

      return 0;
}

3.)一个简单的声音播放程序

#include <iostream>
#include <alsa/asoundlib.h>

int main()
{
       long                             loops;
       int                               rc;
       int                                       size;
       snd_pcm_t*                         handle;
       snd_pcm_hw_params_t*      params;
       unsigned int                  val;
       int                               dir;
       snd_pcm_uframes_t             frames;
       char*                                  buffer;

       if ( (rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0)
       {
              std::cerr << "unable to open pcm device: " << snd_strerror(rc) << std::endl;
              exit(1);
       }

       snd_pcm_hw_params_alloca(&params);

       snd_pcm_hw_params_any(handle, params);

       snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);

       snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);

       snd_pcm_hw_params_set_channels(handle, params, 2);

       val = 44100;

       snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);

       frames = 32;
       snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);

       if ( (rc = snd_pcm_hw_params(handle, params)) < 0)
       {
              std::cerr << "unable to set hw paramseters: " << snd_strerror(rc) << std::endl;
              exit(1);
       }

       snd_pcm_hw_params_get_period_size(params, &frames, &dir);
       size = frames * 4;
       buffer = new char[size];

       snd_pcm_hw_params_get_period_time(params, &val, &dir);

       loops = 5000000 / val;

       while (loops > 0) {
              loops--;
              if ( (rc = read(0, buffer, size)) == 0)
              {
                     std::cerr << "end of file on input" << std::endl;
                     break;
              }
              else if (rc != size)
                     std::cerr << "short read: read " << rc << " bytes" << std::endl;

              if ( (rc = snd_pcm_writei(handle, buffer, frames)) == -EPIPE)
              {
                     std::cerr << "underrun occurred" << std::endl;
                     snd_pcm_prepare(handle);
              }
              else if (rc < 0)
                     std::cerr << "error from writei: " << snd_strerror(rc) << std::endl;
              else if (rc != (int)frames)
                     std::cerr << "short write, write " << rc << " frames" << std::endl;
       }

       snd_pcm_drain(handle);
       snd_pcm_close(handle);
       free(buffer);

       return 0;
}

4.)一个简单的记录声音的程序

#include <iostream>
#include <alsa/asoundlib.h>

int main()
{
       long                             loops;
       int                                       rc;
       int                                       size;
       snd_pcm_t*                         handle;
       snd_pcm_hw_params_t*      params;
       unsigned int                  val;
       int                                       dir;
       snd_pcm_uframes_t             frames;
       char*                                  buffer;

       if ( (rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0)
       {
              std::cerr << "unable to open pcm device: " << snd_strerror(rc) << std::endl;
              exit(1);
       }

       snd_pcm_hw_params_alloca(&params);

       snd_pcm_hw_params_any(handle, params);

       snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);

       snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);

       snd_pcm_hw_params_set_channels(handle, params, 2);

       val = 44100;
       snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);

       if ( (rc = snd_pcm_hw_params(handle, params)) < 0)
       {
              std::cerr << "unable to set hw parameters: " << snd_strerror(rc) << std::endl;
              exit(1);
       }

       snd_pcm_hw_params_get_period_size(params, &frames, &dir);

       size = frames * 4;
       buffer = new char[size];

       snd_pcm_hw_params_get_period_time(params, &val, &dir);

       loops = 5000000 / val;

       while (loops > 0)
       {
              loops --;
              rc = snd_pcm_readi(handle, buffer, frames);
              if (rc == -EPIPE)
              {
                     std::cerr << "overrun occurred" << std::endl;
                     snd_pcm_prepare(handle);
              }
              else if (rc < 0)
                     std::cerr << "error from read: " << snd_strerror(rc) << std::endl;
              else if ( rc != (int)frames)
                     std::cerr << "short read, read " << rc << " frames" << std::endl;
              rc = write(1, buffer, size);
              if (rc != size)
                     std::cerr << "short write: wrote " << rc << " bytes" << std::endl;
       }

       snd_pcm_drain(handle);
       snd_pcm_close(handle);
       free(buffer);

       return 0;
}

注意:编译的时候记得加上参数,g++ xxx.cpp -o xxx -lasound;如果编译时出现如下错误:alsa/asoundlib.h: No such file or directory
缺少一个库:

apt-get install libasound2-dev

OK!

2012-12-12 10:15:58 zd394071264 阅读数 662

一. 介绍

      ALSA 标准是一个先进的linux声音体系。它包含内核驱动集合,API库和工具对Linux声音进行支持。ALSA包含一系列内核驱动对不同的声卡进行支持,还提供了libasound的API库。用这些进行写程序不需要打开设备等操作,所以编程人员在写程序的时候不会被底层的东西困扰。与此相反OSS/Free 驱动在内核层次调用,需要指定设备名和调用ioctl。为提供向后兼容, ALSA 提供内核模块模仿 OSS/Free 驱动,所以大多数的程序不需要改动。 ALSA 拥有调用插件的能力对新设备提供扩展,包括那些用软件模拟出来的虚拟设备。 ALSA 还提供一组命令行工具包括  mixer, sound file player 和工具控制一些特别的声卡的特别的作用。

 

二.ALSA 体系:

ALSA API 被主要分为以下几种接口:

l         控制接口:提供灵活的方式管理注册的声卡和对存在的声卡进行查询。

l         PCM接口:提供管理数字音频的捕捉和回放。

l         原始 MIDI 接口: 支持 MIDI (Musical Instrument Digital Interface), 一种标准电子音乐指令集。 这些 API 提供访问声卡上的 MIDI 总线。这些原始借口直接工作在 The  MIDI 事件上,程序员只需要管理协议和时间。

l         记时接口: 为支持声音的同步事件提供访问声卡上的定时器。

l         音序器接口:一个比原始MIDI接口高级的MIDI编程和声音同步高层接口。它可以处理很多的MIDI协议和定时器。

l         混音器接口:控制发送信号和控制声音大小的声卡上的设备。

 

三.声卡的缓存和数据的传输:

      一块声卡有一个声卡内存用来存储记录的样本。当它被写满时就产生中断。内核驱动就使用DMA将数据传输到内存中。同样地,当在播放时就将内存中的声音样本使用DMA传到声卡的内存中!

      声卡的缓存是环状的,这里只讨论应用程序中的内存结构:ALSA将数据分成连续的片段然后传到按单元片段传输。

 

四:典型的声音程序结构:

        open interface for capture or playback

        set hardware parameters

        (access mode, data format, channels, rate, etc.)

        while there is data to be processed:

        read PCM data (capture)

        or write PCM data (playback)

        close interface

 

五.一些例子:

1.显示一些PCM的类型和格式:

 

#include <iostream>

#include <alsa/asoundlib.h>

 

int main()

{

       std::cout << "ALSA library version: " << SND_LIB_VERSION_STR << std::endl;

 

       std::cout << "PCM stream types: " << std::endl;

 

       for (int val=0; val <= SND_PCM_STREAM_LAST; ++val)

              std::cout << snd_pcm_stream_name((snd_pcm_stream_t)val) << std::endl;

       std::cout << std::endl;

 

       std::cout << "PCM access types: " << std::endl;

       for (int val=0; val <= SND_PCM_ACCESS_LAST; ++val)

              std::cout << snd_pcm_access_name((snd_pcm_access_t)val) << std::endl;

       std::cout << std::endl;

 

       std::cout << "PCM subformats: " << std::endl;

       for (int val=0; val <= SND_PCM_SUBFORMAT_LAST; ++val)

              std::cout << snd_pcm_subformat_name((snd_pcm_subformat_t)val) << " (" << snd_pcm_subformat_description((snd_pcm_subformat_t)val) << ")" << std::endl;

       std::cout << std::endl;

 

       std::cout << "PCM states: " << std::endl;

       for (int val=0; val <= SND_PCM_STATE_LAST; ++val)

              std::cout << snd_pcm_state_name((snd_pcm_state_t)val) << std::endl;

       std::cout << std::endl;

 

 

       std::cout << "PCM formats: " << std::endl;

       for (int val=0; val <= SND_PCM_FORMAT_LAST; ++val)

              std::cout << snd_pcm_format_name((snd_pcm_format_t)val) << " (" << snd_pcm_format_description((snd_pcm_format_t)val) << ")" << std::endl;

       std::cout << std::endl;

      

}

 

2.打开PCM设备和设置参数

 

#include <iostream>

#include <alsa/asoundlib.h>

 

int main()

{

       int                               rc;

       snd_pcm_t*                         handle;

       snd_pcm_hw_params_t*      params;

       unsigned int                  val, val2;

       int                               dir;

       snd_pcm_uframes_t             frames;

 

       if ( (rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0)

       {

              std::cerr << "unable to open pcm devices: " << snd_strerror(rc) << std::endl;

              exit(1);

       }

 

       snd_pcm_hw_params_alloca(&params);

 

       snd_pcm_hw_params_any(handle, params);

 

       snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);

 

       snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);

 

       snd_pcm_hw_params_set_channels(handle, params, 2);

 

       val = 44100;

 

       snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);

 

       if ( (rc = snd_pcm_hw_params(handle, params)) < 0)

       {

              std::cerr << "unable to set hw parameters: " << snd_strerror(rc) << std::endl;

              exit(1);

       }

 

       std::cout << "PCM handle name = " << snd_pcm_name(handle) << std::endl;

 

       std::cout << "PCM state = " << snd_pcm_state_name(snd_pcm_state(handle)) << std::endl;

 

       snd_pcm_hw_params_get_access(params, (snd_pcm_access_t *)&val);

 

       std::cout << "access type = " << snd_pcm_access_name((snd_pcm_access_t)val) << std::endl;

 

       snd_pcm_hw_params_get_format(params, (snd_pcm_format_t*)(&val));

      

       std::cout << "format = '" << snd_pcm_format_name((snd_pcm_format_t)val) << "' (" << snd_pcm_format_description((snd_pcm_format_t)val) << ")" << std::endl;

 

      snd_pcm_hw_params_get_subformat(params, (snd_pcm_subformat_t *)&val);

      std::cout << "subformat = '" <<

    snd_pcm_subformat_name((snd_pcm_subformat_t)val) << "' (" << snd_pcm_subformat_description((snd_pcm_subformat_t)val) << ")" << std::endl;

 

      snd_pcm_hw_params_get_channels(params, &val);

      std::cout << "channels = " << val << std::endl;

 

      snd_pcm_hw_params_get_rate(params, &val, &dir);

      std::cout << "rate = " << val << " bps" << std::endl;

 

       snd_pcm_hw_params_get_period_time(params, &val, &dir);

      std::cout << "period time = " << val << " us" << std::endl;

 

      snd_pcm_hw_params_get_period_size(params, &frames, &dir);

      std::cout << "period size = " << static_cast<int>(frames) << " frames" << std::endl;

 

       snd_pcm_hw_params_get_buffer_time(params, &val, &dir);

      std::cout << "buffer time = " << val << " us" << std::endl;

      

       snd_pcm_hw_params_get_buffer_size(params, (snd_pcm_uframes_t *) &val);

      std::cout << "buffer size = " << val << " frames" << std::endl;

 

      snd_pcm_hw_params_get_periods(params, &val, &dir);

      std::cout << "periods per buffer = " << val << " frames" << std::endl;

 

       snd_pcm_hw_params_get_rate_numden(params, &val, &val2);

      std::cout << "exact rate = " << val/val2 << " bps" << std::endl;

      

      val = snd_pcm_hw_params_get_sbits(params);

      std::cout << "significant bits = " << val << std::endl;

 

      snd_pcm_hw_params_get_tick_time(params, &val, &dir);

      std::cout << "tick time = " << val << " us" << std::endl;

 

      val = snd_pcm_hw_params_is_batch(params);

      std::cout << "is batch = " << val << std::endl;

 

      val = snd_pcm_hw_params_is_block_transfer(params);

      std::cout << "is block transfer = " << val << std::endl;

 

      val = snd_pcm_hw_params_is_double(params);

      std::cout << "is double = " << val << std::endl;

 

       val = snd_pcm_hw_params_is_half_duplex(params);

      std::cout << "is half duplex = " << val << std::endl;

 

       val = snd_pcm_hw_params_is_joint_duplex(params);

      std::cout << "is joint duplex = " << val << std::endl;

 

       val = snd_pcm_hw_params_can_overrange(params);

      std::cout << "can overrange = " << val << std::endl;

 

      val = snd_pcm_hw_params_can_mmap_sample_resolution(params);

      std::cout << "can mmap = " << val << std::endl;

 

      val = snd_pcm_hw_params_can_pause(params);

      std::cout << "can pause = " << val << std::endl;

 

       val = snd_pcm_hw_params_can_resume(params);

      std::cout << "can resume = " << val << std::endl;

 

      val = snd_pcm_hw_params_can_sync_start(params);

      std::cout << "can sync start = " << val << std::endl;

 

      snd_pcm_close(handle);

 

      return 0;

}

 

3.一个简单的声音播放程序

 

#include <iostream>

#include <alsa/asoundlib.h>

 

int main()

{

       long                             loops;

       int                               rc;

       int                                       size;

       snd_pcm_t*                         handle;

       snd_pcm_hw_params_t*      params;

       unsigned int                  val;

       int                               dir;

       snd_pcm_uframes_t             frames;

       char*                                  buffer;

 

       if ( (rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0)

       {

              std::cerr << "unable to open pcm device: " << snd_strerror(rc) << std::endl;

              exit(1);

       }

 

       snd_pcm_hw_params_alloca(&params);

 

       snd_pcm_hw_params_any(handle, params);

 

       snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);

 

       snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);

 

       snd_pcm_hw_params_set_channels(handle, params, 2);

 

       val = 44100;

 

       snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);

 

       frames = 32;

       snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);

 

       if ( (rc = snd_pcm_hw_params(handle, params)) < 0)

       {

              std::cerr << "unable to set hw paramseters: " << snd_strerror(rc) << std::endl;

              exit(1);

       }

 

       snd_pcm_hw_params_get_period_size(params, &frames, &dir);

       size = frames * 4;

       buffer = new char[size];

 

       snd_pcm_hw_params_get_period_time(params, &val, &dir);

 

       loops = 5000000 / val;

 

       while (loops > 0) {

              loops--;

              if ( (rc = read(0, buffer, size)) == 0)

              {

                     std::cerr << "end of file on input" << std::endl;

                     break;

              }

              else if (rc != size)

                     std::cerr << "short read: read " << rc << " bytes" << std::endl;

 

              if ( (rc = snd_pcm_writei(handle, buffer, frames)) == -EPIPE)

              {

                     std::cerr << "underrun occurred" << std::endl;

                     snd_pcm_prepare(handle);

              }

              else if (rc < 0)

                     std::cerr << "error from writei: " << snd_strerror(rc) << std::endl;

              else if (rc != (int)frames)

                     std::cerr << "short write, write " << rc << " frames" << std::endl;

       }

 

       snd_pcm_drain(handle);

       snd_pcm_close(handle);

       free(buffer);

 

       return 0;

}

4.一个简单的记录声音的程序

 

#include <iostream>

#include <alsa/asoundlib.h>

 

int main()

{

       long                             loops;

       int                                       rc;

       int                                       size;

       snd_pcm_t*                         handle;

       snd_pcm_hw_params_t*      params;

       unsigned int                  val;

       int                                       dir;

       snd_pcm_uframes_t             frames;

       char*                                  buffer;

 

       if ( (rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0)

       {

              std::cerr << "unable to open pcm device: " << snd_strerror(rc) << std::endl;

              exit(1);

       }

 

       snd_pcm_hw_params_alloca(&params);

 

       snd_pcm_hw_params_any(handle, params);

 

       snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);

 

       snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);

 

       snd_pcm_hw_params_set_channels(handle, params, 2);

 

       val = 44100;

       snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);

 

       if ( (rc = snd_pcm_hw_params(handle, params)) < 0)

       {

              std::cerr << "unable to set hw parameters: " << snd_strerror(rc) << std::endl;

              exit(1);

       }

 

       snd_pcm_hw_params_get_period_size(params, &frames, &dir);

 

       size = frames * 4;

       buffer = new char[size];

 

       snd_pcm_hw_params_get_period_time(params, &val, &dir);

 

       loops = 5000000 / val;

 

       while (loops > 0)

       {

              loops --;

              rc = snd_pcm_readi(handle, buffer, frames);

              if (rc == -EPIPE)

              {

                     std::cerr << "overrun occurred" << std::endl;

                     snd_pcm_prepare(handle);

              }

              else if (rc < 0)

                     std::cerr << "error from read: " << snd_strerror(rc) << std::endl;

              else if ( rc != (int)frames)

                     std::cerr << "short read, read " << rc << " frames" << std::endl;

              rc = write(1, buffer, size);

              if (rc != size)

                     std::cerr << "short write: wrote " << rc << " bytes" << std::endl;

       }

 

       snd_pcm_drain(handle);

       snd_pcm_close(handle);

       free(buffer);

      

       return 0;

}

 

编译的参数:g++ xxx.cpp -o xxx -lasound

2019-02-08 15:28:27 Tang_Chuanlin 阅读数 992

1、Linux 系统下三大主流声卡驱动程序集

Linux 有三个主流的声卡驱动程序集:OSS/Lite(也称为OSS/Free)、OSS/Full (商业软件)、ALSA(自由软件)。

  • OSS/Lite 是现在linux内核中自带的声卡驱动程序集,最初由 Hannu Savolainen 开发。后来 Hannu 跑去开发 Open Sound System(也就是上面所说的OSS/Full)。 由于 Hannu 的“逃跑”,RH 资助 Alan Cox 增强 Hannu 开发的驱动程序,并使它们 完全模块化。现在 Alan Cox 是内核声卡驱程集的维护人。OSS/Lite 从kernel-2.0开始并入内核,现在大家使用的声卡驱程默认都是OSS/Lite中的。

  • OSS/Full 是由 4Front Technologies 开发并销售的商业软件。它可以驱动很多 声卡并且可以用在很多 UNIX 系统中。OSS/Full 完全兼容以前基于 OSS/Lite 开发 的应用程序。作为一个商业软件,你虽然可以使用它,但是你得不到它的源代码,并且你必须为此而付钱。

  • ALSA 是linux内核的下一代标准声卡驱动集。 ALSA表示高级Linux声音体系结构(Advanced Linux Sound Architecture)。它由一系列内核驱动,应用程序编译接口(API)以及支持Linux下声音的实用程序组成。开始时 Jaroslav Kysela 等人为 Gravis UltraSound Card 开发驱动程序,后来该计划改名为 ALSA「先进的linux音频 体系」,因为他们认为 ALSA 比原来内核中的 OSS/Lite 驱动程序集更优秀,完全可以 代替 OSS/Lite。他们是对的,ALSA 支持的声卡比 OSS/Lite 多,完全兼容以前基于OSS 开发的程序,SMP(多处理器) 与线程安全设计,并且从 2.5 分支的内核开始,ALSA 的驱动程序集开始并入内核,大家可以在今年的 2.6 版本的内核中看到使用它们。

2、为什么使用 ALSA 开发音频程序

首先,ALSA 是 linux 以后声卡驱动程序的标准,OSS/Lite 迟早会从内核中去除。 开发基于 ALSA 的音频程序可以保证以后的兼容。

其次,我们简单比较一下开发基于 OSS 与 ALSA 的方法。
OSS 向应用程序提供了一系列的系统接口。开发基于 OSS 的应用程序需要使用open,close,ioctl,read,write 等低级系统调用来完成音频设备的控制、音频流的输入输出。
而 ALSA 则为应用程序开发人员提供了一个优秀的音频库。利用该音频库,开发人员可以方便快捷地开发出自己的应用程序,细节则留给音频库内部处理。当然 ALSA 也提供了类似于 OSS 的系统接口,不过 ALSA 的开发者建议应用程序开发者使用音频库而不是驱动程序API。

ALSA由许多声卡的声卡驱动程序组成,同时它也提供一个称为libasound的API库。应用程序开发者应该使用libasound而不是内核中的 ALSA接口。因为libasound提供最高级并且编程方便的编程接口。并且提供一个设备逻辑命名功能,这样开发者甚至不需要知道类似设备文件这样的低层接口。相反,OSS/Free驱动是在内核系统调用级上编程,它要求开发者提供设备文件名并且利用ioctrl来实现相应的功能。

为了向后兼容,ALSA提供内核模块来模拟OSS,这样之前的许多在OSS基础上开发的应用程序不需要任何改动就可以在ALSA上运行。另外,libaoss库也可以模拟OSS,而它不需要内核模块。
ALSA包含插件功能,使用插件可以扩展新的声卡驱动,包括完全用软件实现的虚拟声卡。ALSA提供一系列基于命令行的工具集,比如混音器(mixer),音频文件播放器(aplay),以及控制特定声卡特定属性的工具。

3、从何开始呢

第一步当然是安装 ALSA 驱动程序与音频库。

 sudo apt-get install libasound2-dev

当前 ALSA 有两个分支,一个是以前的0.5版本,一个是现在的0.9。ALSA的开发者 已经不支持0.5版本了,所以我们要使用0.9。
大家可以在 ALSA 的主页 www.alsa-project.org 上下载安装。
这个页面上的信息对大家安装很有用:http://www.alsa-project.org/main/index.php/Documentation ,建议先浏览一下。

第二步是参考文档与例子。
在 ALSA 的文档页面上有两篇为应用程序开发者提供的文章:
ALSA 0.9.0 HOWTO [ http://www.suse.de/~mana/alsa090_howto.html ]
当然,还有音频库API的在线参考: http://www.alsa-project.org/alsa-doc/alsa-lib
音频库API中各个函数的使用说明:http://www.alsa-project.org/alsa-doc/alsa-lib/modules.html

4、ALSA体系结构

ALSA API可以分解成以下几个主要的接口:

  • 控制接口:提供管理声卡注册和请求可用设备的通用功能
  • PCM接口:管理数字音频回放(playback)和录音(capture)的接口。后续重点放在这个接口上,因为它是开发数字音频程序最常用到的接口。
  • Raw MIDI接口:支持MIDI(Musical Instrument Digital Interface),标准的电子乐器。这些API提供对声卡上MIDI总线的访问。这个原始接口基于MIDI事件工作,由程序员负责管理协议以及时间处理。
  • 定时器(Timer)接口:为同步音频事件提供对声卡上时间处理硬件的访问。
  • 时序器(Sequencer)接口
  • 混音器(Mixer)接口

5、设备命名

API库使用逻辑设备名而不是设备文件。设备名字可以是真实的硬件名字也可以是插件名字。硬件名字使用hw:i,j这样的格式。其中i是卡号,j是这块声卡上的设备号。

第一个声音设备是hw:0,0.这个别名默认引用第一块声音设备并且在本文示例中一真会被用到。

插件使用另外的唯一名字,比如 plughw:,表示一个插件,这个插件不提供对硬件设备的访问,而是提供像采样率转换这样的软件特性,硬件本身并不支持这样的特性。

6、声音缓存和数据传输

每个声卡都有一个硬件缓存区来保存记录下来的样本。当缓存区足够满时,声卡将产生一个中断。内核声卡驱动然后使用直接内存(DMA)访问通道将样本传送到内存中的应用程序缓存区。类似地,对于回放,任何应用程序使用DMA将自己的缓存区数据传送到声卡的硬件缓存区中。

这样硬件缓存区是环缓存。也就是说当数据到达缓存区末尾时将重新回到缓存区的起始位置。ALSA维护一个指针来指向硬件缓存以及应用程序缓存区中数据操作的当前位置。从内核外部看,我们只对应用程序的缓存区感兴趣,所以本文只讨论应用程序缓存区。

应用程序缓存区的大小可以通过ALSA库函数调用来控制。缓存区可以很大,一次传输操作可能会导致不可接受的延迟,我们把它称为延时(latency)。为了解决这个问题,ALSA将缓存区拆分成一系列周期(period)(OSS/Free中叫片断fragments).ALSA以period为单元来传送数据。

一个周期(period)存储一些帧(frames)。每一帧包含时间上一个点所抓取的样本。对于立体声设备,一个帧会包含两个信道上的样本。分解过程:一个缓存区分解成周期,然后是帧,然后是样本。左右信道信息被交替地存储在一个帧内。这称为交错 (interleaved)模式。在非交错模式中,一个信道的所有样本数据存储在另外一个信道的数据之后。

7、Over and Under Run

当一个声卡活动时,数据总是连续地在硬件缓存区和应用程序缓存区间传输。但是也有例外。在录音例子中,如果应用程序读取数据不够快,循环缓存区将会被新的数据覆盖。这种数据的丢失被称为over run.在回放例子中如果应用程序写入数据到缓存区中的速度不够快,缓存区将会"饿死"。这样的错误被称为"under run"。在ALSA文档中,有时将这两种情形统称为"XRUN"。适当地设计应用程序可以最小化XRUN并且可以从中恢复过来。

参考资料

1、【Linux&音频】Alsa音频编程【精华】 - 小田的专栏 - CSDN博客

2017-08-19 13:23:11 qingkongyeyue 阅读数 2518

转自http://www.cnblogs.com/lifan3a/articles/5481993.html

样本长度(sample):样本是记录音频数据最基本的单位,常见的有8位和16位。

通道数(channel):该参数为1表示单声道,2则是立体声。

桢(frame):桢记录了一个声音单元,其长度为样本长度与通道数的乘积
采样率(rate):每秒钟采样次数,该次数是针对桢而言
周期(period):音频设备一次处理所需要的桢数,对于音频设备的数据访问以及音频数据的存储,都是以此为单位。

交错模式(interleaved):是一种音频数据的记录方式,在交错模式下,数据以连续桢的形式存放,即首先记录完桢1的左声道样本和右声道样本(假设为立体声格式),再开始桢2的记录。而在非交错模式下,首先记录的是一个周期内所有桢的左声道样本,再记录右声道样本,数据是以连续通道的方式存储。不过多数情况下,我们只需要使用交错模式就可以了。

period(周期):硬件中中断间的间隔时间。它表示输入延时。

声卡接口中有一个指针来指示声卡硬件缓存区中当前的读写位置。只要接口在运行,这个指针将循环地指向缓存区中的某个位置
frame size = sizeof(one sample) * nChannels
alsa中配置的缓存(buffer)和周期(size)大小在runtime中是以帧(frames)形式存储的
period_bytes = frames_to_bytes(runtime, runtime->period_size); 
bytes_to_frames()


一,ALSA声音编程介绍

ALSA表示高级Linux声音体系结构(Advanced Linux Sound Architecture)。它由一系列内核驱动,应用程序编译接口(API)以及支持Linux下声音的实用程序组成。这篇文章里,我将简单介绍 ALSA项目的基本框架以及它的软件组成。主要集中介绍PCM接口编程,包括您可以自动实践的程序示例。

您使用ALSA的原因可能就是因为它很新,但它并不是唯一可用的声音API。如果您想完成低级的声音操作,以便能够最大化地控制声音并最大化地提高性能,或者如果您使用其它声音API没有的特性,那么ALSA是很好的选择。如果您已经写了一个音频程序,你可能想要为ALSA声卡驱动添加本地支持。如果您对音频不感兴趣,只是想播放音频文件,那么高级的API将是更好的选择,比如SDL,OpenAL以及那些桌面环境提供的工具集。另外,您只能在有ALSA 支持的Linux环境中使用ALSA。

二,ALSA历史

ALSA项目发起的起因是Linux下的声卡驱动(OSS/Free drivers)没有得到积极的维护。并且落后于新的声卡技术。Jaroslav Kysela早先写了一个声卡驱动,并由此开始了ALSA项目,随便,更多的开发者加入到开发队伍中,更多的声卡得到支持,API的结构也得到了重组。

Linux内核2.5在开发过程中,ALSA被合并到了官方的源码树中。在发布内核2.6后,ALSA已经内建在稳定的内核版本中并将广泛地使用。

三,数字音频基础

声音由变化的气压组成。它被麦克风这样的转换器转换成电子形式。模/数(ADC)转换器将模拟电压转换成离散的样本值。声音以固定的时间间隔被采样,采样的速率称为采样率。把样本输出到数/模(DAC)转换器,比如扩音器,最后转换成原来的模拟信号。

样本大小以位来表示。样本大小是影响声音被转换成数字信号的精确程度的因素之一。另一个主要的因素是采样率。奈奎斯特(Nyquist)理论中,只要离散系统的奈奎斯特频率高于采样信号的最高频率或带宽,就可以避免混叠现象。

四,ALSA基础

ALSA由许多声卡的声卡驱动程序组成,同时它也提供一个称为libasound的API库。应用程序开发者应该使用libasound而不是内核中的 ALSA接口。因为libasound提供最高级并且编程方便的编程接口。并且提供一个设备逻辑命名功能,这样开发者甚至不需要知道类似设备文件这样的低层接口。相反,OSS/Free驱动是在内核系统调用级上编程,它要求开发者提供设备文件名并且利用ioctrl来实现相应的功能。

为了向后兼容,ALSA提供内核模块来模拟OSS,这样之前的许多在OSS基础上开发的应用程序不需要任何改动就可以在ALSA上运行。另外,libaoss库也可以模拟OSS,而它不需要内核模块。

ALSA包含插件功能,使用插件可以扩展新的声卡驱动,包括完全用软件实现的虚拟声卡。ALSA提供一系列基于命令行的工具集,比如混音器(mixer),音频文件播放器(aplay),以及控制特定声卡特定属性的工具。

五,ALSA体系结构

ALSA API可以分解成以下几个主要的接口:

1 控制接口:提供管理声卡注册和请求可用设备的通用功能 

2 PCM接口:管理数字音频回放(playback)和录音(capture)的接口。本文后续总结重点放在这个接口上,因为它是开发数字音频程序最常用到的接口。

3 Raw MIDI接口:支持MIDI(Musical Instrument Digital Interface),标准的电子乐器。这些API提供对声卡上MIDI总线的访问。这个原始接口基于MIDI事件工作,由程序员负责管理协议以及时间处理。

4 定时器(Timer)接口:为同步音频事件提供对声卡上时间处理硬件的访问。

5 时序器(Sequencer)接口

6 混音器(Mixer)接口

六,设备命名

API库使用逻辑设备名而不是设备文件。设备名字可以是真实的硬件名字也可以是插件名字。硬件名字使用hw:i,j这样的格式。其中i是卡号,j是这块声卡上的设备号

第一个声音设备是hw:0,0.这个别名默认引用第一块声音设备并且在本文示例中一真会被用到。

插件使用另外的唯一名字,比如 plughw:,表示一个插件,这个插件不提供对硬件设备的访问,而是提供像采样率转换这样的软件特性,硬件本身并不支持这样的特性。

七,声音缓存和数据传输

每个声卡都有一个硬件缓存区来保存记录下来的样本。当缓存区足够满时,声卡将产生一个中断内核声卡驱动然后使用直接内存(DMA)访问通道将样本传送到内存中的应用程序缓存区。类似地,对于回放,任何应用程序使用DMA将自己的缓存区数据传送到声卡的硬件缓存区中
这样硬件缓存区是环缓存。也就是说当数据到达缓存区末尾时将重新回到缓存区的起始位置。ALSA维护一个指针来指向硬件缓存以及应用程序缓存区中数据操作的当前位置。从内核外部看,我们只对应用程序的缓存区感兴趣,所以本文只讨论应用程序缓存区。

 

应用程序缓存区的大小可以通过ALSA库函数调用来控制。缓存区可以很大,一次传输操作可能会导致不可接受的延迟,我们把它称为延时(latency)。为了解决这个问题,ALSA将缓存区拆分成一系列周期(period)(OSS/Free中叫片断fragments).ALSA以period为单元来传送数据

一个周期(period)存储一些帧(frames)。每一帧包含时间上一个点所抓取的样本。对于立体声设备,一个帧会包含两个信道上的样本。分解过程:一个缓存区分解成周期,然后是帧,然后是样本。左右信道信息被交替地存储在一个帧内。这称为交错 (interleaved)模式。在非交错模式中,一个信道的所有样本数据存储在另外一个信道的数据之后。

八,Over and Under Run

当一个声卡活动时,数据总是连续地在硬件缓存区应用程序缓存区间传输。但是也有例外。在录音例子中,如果应用程序读取数据不够快,循环缓存区将会被新的数据覆盖。这种数据的丢失被称为over run.在回放例子中,如果应用程序写入数据到缓存区中的速度不够快,缓存区将会"饿死"。这样的错误被称为"under run"。在ALSA文档中,有时将这两种情形统称为"XRUN"。适当地设计应用程序可以最小化XRUN并且可以从中恢复过来。

九,一个典型的声音程序

使用PCM的程序通常类似下面的伪代码:

打开回放或录音接口

设置硬件参数(访问模式,数据格式,信道数,采样率,等等)

while 有数据要被处理:

读PCM数据(录音)

或 写PCM数据(回放)

关闭接口

 

和本文相关的所有实例清单可以从FTP中获取:ftp.ssc.com/pub/lj/listings/issue126/6735.tgz。

Listing 1. Display Some PCM Types and Formats

  1. #include <alsa/asoundlib.h>
  2. int main()
  3. {
  4. int val;
  5. printf("ALSA library version: %s\n",
  6. SND_LIB_VERSION_STR);
  7. printf("\nPCM stream types:\n");
  8. for (val = 0; val <= SND_PCM_STREAM_LAST; val++)
  9. printf(" %s\n",
  10. snd_pcm_stream_name((snd_pcm_stream_t)val));
  11. printf("\nPCM access types:\n");
  12. for (val = 0; val <= SND_PCM_ACCESS_LAST; val++)
  13. {
  14. printf(" %s\n",
  15. snd_pcm_access_name((snd_pcm_access_t)val));
  16. }
  17. printf("\nPCM formats:\n");
  18. for (val = 0; val <= SND_PCM_FORMAT_LAST; val++)
  19. {
  20. if (snd_pcm_format_name((snd_pcm_format_t)val)!= NULL)
  21. {
  22. printf(" %s (%s)\n",
  23. snd_pcm_format_name((snd_pcm_format_t)val),
  24. snd_pcm_format_description(
  25. (snd_pcm_format_t)val));
  26. }
  27. }
  28. printf("\nPCM subformats:\n");
  29. for (val = 0; val <= SND_PCM_SUBFORMAT_LAST;val++)
  30. {
  31. printf(" %s (%s)\n",
  32. snd_pcm_subformat_name((
  33. snd_pcm_subformat_t)val),
  34. snd_pcm_subformat_description((
  35. snd_pcm_subformat_t)val));
  36. }
  37. printf("\nPCM states:\n");
  38. for (val = 0; val <= SND_PCM_STATE_LAST; val++)
  39. printf(" %s\n",
  40. snd_pcm_state_name((snd_pcm_state_t)val));
  41. return 0;
  42. }
[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. #include <alsa/asoundlib.h>  
  2.   
  3. int main()   
  4. {  
  5.     int val;  
  6.   
  7.     printf("ALSA library version: %s\n",  
  8.                        SND_LIB_VERSION_STR);  
  9.   
  10.     printf("\nPCM stream types:\n");  
  11.     for (val = 0; val <= SND_PCM_STREAM_LAST; val++)  
  12.             printf(" %s\n",  
  13.                 snd_pcm_stream_name((snd_pcm_stream_t)val));  
  14.   
  15.     printf("\nPCM access types:\n");  
  16.     for (val = 0; val <= SND_PCM_ACCESS_LAST; val++)  
  17.     {  
  18.             printf(" %s\n",  
  19.                 snd_pcm_access_name((snd_pcm_access_t)val));  
  20.     }  
  21.   
  22.     printf("\nPCM formats:\n");  
  23.     for (val = 0; val <= SND_PCM_FORMAT_LAST; val++)  
  24.         {  
  25.         if (snd_pcm_format_name((snd_pcm_format_t)val)!= NULL)  
  26.         {  
  27.                 printf(" %s (%s)\n",  
  28.                     snd_pcm_format_name((snd_pcm_format_t)val),  
  29.                     snd_pcm_format_description(  
  30.                             (snd_pcm_format_t)val));  
  31.         }  
  32.     }  
  33.     printf("\nPCM subformats:\n");  
  34.     for (val = 0; val <= SND_PCM_SUBFORMAT_LAST;val++)  
  35.         {  
  36.         printf(" %s (%s)\n",  
  37.                 snd_pcm_subformat_name((  
  38.                 snd_pcm_subformat_t)val),  
  39.                 snd_pcm_subformat_description((  
  40.                 snd_pcm_subformat_t)val));  
  41.     }  
  42.     printf("\nPCM states:\n");  
  43.     for (val = 0; val <= SND_PCM_STATE_LAST; val++)  
  44.             printf(" %s\n",  
  45.                 snd_pcm_state_name((snd_pcm_state_t)val));  
  46.   
  47.     return 0;  
  48. }  



 

 

清单一显示了一些ALSA使用的PCM数据类型和参数。首先需要做的是包括头文件。这些头文件包含了所有库函数的声明。其中之一就是显示ALSA库的版本。

这个程序剩下的部分的迭代一些PCM数据类型,以流类型开始。ALSA为每次迭代的最后值提供符号常量名,并且提供功能函数以显示某个特定值的描述字符串。你将会看到,ALSA支持许多格式,在我的1.0.15版本里,支持多达36种格式。

这个程序必须链接到alsalib库,通过在编译时需要加上-lasound选项。有些alsa库函数使用dlopen函数以及浮点操作,所以您可能还需要加上-ldl,-lm选项。

编译:gcc -o main test.c -lasound

下面是该程序的Makefile:


Listing 2. Opening PCM Device and Setting Parameters

  1. /*
  2. This example opens the default PCM device, sets
  3. some parameters, and then displays the value
  4. of most of the hardware parameters. It does not
  5. perform any sound playback or recording.
  6. */
  7. /* Use the newer ALSA API */
  8. #define ALSA_PCM_NEW_HW_PARAMS_API
  9. /* All of the ALSA library API is defined
  10. * in this header */
  11. #include <alsa/asoundlib.h>
  12. int main() {
  13. int rc;
  14. snd_pcm_t *handle;
  15. snd_pcm_hw_params_t *params;
  16. unsigned int val, val2;
  17. int dir;
  18. snd_pcm_uframes_t frames;
  19. /* Open PCM device for playback. */
  20. rc = snd_pcm_open(&handle, "default",
  21. SND_PCM_STREAM_PLAYBACK, 0);
  22. if (rc 0) {
  23. fprintf(stderr,
  24. "unable to open pcm device: %s\n",
  25. snd_strerror(rc));
  26. exit(1);
  27. }
  28. /* Allocate a hardware parameters object. */
  29. snd_pcm_hw_params_alloca(¶ms);
  30. /* Fill it in with default values. */
  31. snd_pcm_hw_params_any(handle, params);
  32. /* Set the desired hardware parameters. */
  33. /* Interleaved mode */
  34. snd_pcm_hw_params_set_access(handle, params,
  35. SND_PCM_ACCESS_RW_INTERLEAVED);
  36. /* Signed 16-bit little-endian format */
  37. snd_pcm_hw_params_set_format(handle, params,
  38. SND_PCM_FORMAT_S16_LE);
  39. /* Two channels (stereo) */
  40. snd_pcm_hw_params_set_channels(handle, params, 2);
  41. /* 44100 bits/second sampling rate (CD quality) */
  42. val = 44100;
  43. snd_pcm_hw_params_set_rate_near(handle,
  44. params, &val, &dir);
  45. /* Write the parameters to the driver */
  46. rc = snd_pcm_hw_params(handle, params);
  47. if (rc 0) {
  48. fprintf(stderr,
  49. "unable to set hw parameters: %s\n",
  50. snd_strerror(rc));
  51. exit(1);
  52. }
  53. /* Display information about the PCM interface */
  54. printf("PCM handle name = '%s'\n",
  55. snd_pcm_name(handle));
  56. printf("PCM state = %s\n",
  57. snd_pcm_state_name(snd_pcm_state(handle)));
  58. snd_pcm_hw_params_get_access(params,
  59. (snd_pcm_access_t *) &val);
  60. printf("access type = %s\n",
  61. snd_pcm_access_name((snd_pcm_access_t)val));
  62. snd_pcm_hw_params_get_format(params, &val);
  63. printf("format = '%s' (%s)\n",
  64. snd_pcm_format_name((snd_pcm_format_t)val),
  65. snd_pcm_format_description(
  66. (snd_pcm_format_t)val));
  67. snd_pcm_hw_params_get_subformat(params,
  68. (snd_pcm_subformat_t *)&val);
  69. printf("subformat = '%s' (%s)\n",
  70. snd_pcm_subformat_name((snd_pcm_subformat_t)val),
  71. snd_pcm_subformat_description(
  72. (snd_pcm_subformat_t)val));
  73. snd_pcm_hw_params_get_channels(params, &val);
  74. printf("channels = %d\n", val);
  75. snd_pcm_hw_params_get_rate(params, &val, &dir);
  76. printf("rate = %d bps\n", val);
  77. snd_pcm_hw_params_get_period_time(params,
  78. &val, &dir);
  79. printf("period time = %d us\n", val);
  80. snd_pcm_hw_params_get_period_size(params,
  81. &frames, &dir);
  82. printf("period size = %d frames\n", (int)frames);
  83. snd_pcm_hw_params_get_buffer_time(params,
  84. &val, &dir);
  85. printf("buffer time = %d us\n", val);
  86. snd_pcm_hw_params_get_buffer_size(params,
  87. (snd_pcm_uframes_t *) &val);
  88. printf("buffer size = %d frames\n", val);
  89. snd_pcm_hw_params_get_periods(params, &val, &dir);
  90. printf("periods per buffer = %d frames\n", val);
  91. snd_pcm_hw_params_get_rate_numden(params,
  92. &val, &val2);
  93. printf("exact rate = %d/%d bps\n", val, val2);
  94. val = snd_pcm_hw_params_get_sbits(params);
  95. printf("significant bits = %d\n", val);
  96. snd_pcm_hw_params_get_tick_time(params,
  97. &val, &dir);
  98. printf("tick time = %d us\n", val);
  99. val = snd_pcm_hw_params_is_batch(params);
  100. printf("is batch = %d\n", val);
  101. val = snd_pcm_hw_params_is_block_transfer(params);
  102. printf("is block transfer = %d\n", val);
  103. val = snd_pcm_hw_params_is_double(params);
  104. printf("is double = %d\n", val);
  105. val = snd_pcm_hw_params_is_half_duplex(params);
  106. printf("is half duplex = %d\n", val);
  107. val = snd_pcm_hw_params_is_joint_duplex(params);
  108. printf("is joint duplex = %d\n", val);
  109. val = snd_pcm_hw_params_can_overrange(params);
  110. printf("can overrange = %d\n", val);
  111. val = snd_pcm_hw_params_can_mmap_sample_resolution(params);
  112. printf("can mmap = %d\n", val);
  113. val = snd_pcm_hw_params_can_pause(params);
  114. printf("can pause = %d\n", val);
  115. val = snd_pcm_hw_params_can_resume(params);
  116. printf("can resume = %d\n", val);
  117. val = snd_pcm_hw_params_can_sync_start(params);
  118. printf("can sync start = %d\n", val);
  119. snd_pcm_close(handle);
  120. return 0;
  121. }
[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. /*  
  2.   
  3. This example opens the default PCM device, sets  
  4. some parameters, and then displays the value  
  5. of most of the hardware parameters. It does not  
  6. perform any sound playback or recording.  
  7.   
  8. */  
  9.   
  10. /* Use the newer ALSA API */  
  11. #define ALSA_PCM_NEW_HW_PARAMS_API  
  12.   
  13. /* All of the ALSA library API is defined  
  14. * in this header */  
  15. #include <alsa/asoundlib.h>  
  16.   
  17. int main() {  
  18. int rc;  
  19. snd_pcm_t *handle;  
  20. snd_pcm_hw_params_t *params;  
  21. unsigned int val, val2;  
  22. int dir;  
  23. snd_pcm_uframes_t frames;  
  24.   
  25. /* Open PCM device for playback. */  
  26. rc = snd_pcm_open(&handle, "default",  
  27.                     SND_PCM_STREAM_PLAYBACK, 0);  
  28. if (rc 0) {  
  29.     fprintf(stderr,  
  30.             "unable to open pcm device: %s\n",  
  31.             snd_strerror(rc));  
  32.     exit(1);  
  33. }  
  34.   
  35. /* Allocate a hardware parameters object. */  
  36. snd_pcm_hw_params_alloca(¶ms);  
  37.   
  38. /* Fill it in with default values. */  
  39. snd_pcm_hw_params_any(handle, params);  
  40.   
  41. /* Set the desired hardware parameters. */  
  42.   
  43. /* Interleaved mode */  
  44. snd_pcm_hw_params_set_access(handle, params,  
  45.                       SND_PCM_ACCESS_RW_INTERLEAVED);  
  46.   
  47. /* Signed 16-bit little-endian format */  
  48. snd_pcm_hw_params_set_format(handle, params,  
  49.                               SND_PCM_FORMAT_S16_LE);  
  50.   
  51. /* Two channels (stereo) */  
  52. snd_pcm_hw_params_set_channels(handle, params, 2);  
  53.   
  54. /* 44100 bits/second sampling rate (CD quality) */  
  55. val = 44100;  
  56. snd_pcm_hw_params_set_rate_near(handle,  
  57.                                  params, &val, &dir);  
  58.   
  59. /* Write the parameters to the driver */  
  60. rc = snd_pcm_hw_params(handle, params);  
  61. if (rc 0) {  
  62.     fprintf(stderr,  
  63.             "unable to set hw parameters: %s\n",  
  64.             snd_strerror(rc));  
  65.     exit(1);  
  66. }  
  67.   
  68. /* Display information about the PCM interface */  
  69.   
  70. printf("PCM handle name = '%s'\n",  
  71.          snd_pcm_name(handle));  
  72.   
  73. printf("PCM state = %s\n",  
  74.          snd_pcm_state_name(snd_pcm_state(handle)));  
  75.   
  76. snd_pcm_hw_params_get_access(params,  
  77.                           (snd_pcm_access_t *) &val);  
  78. printf("access type = %s\n",  
  79.          snd_pcm_access_name((snd_pcm_access_t)val));  
  80.   
  81. snd_pcm_hw_params_get_format(params, &val);  
  82. printf("format = '%s' (%s)\n",  
  83.     snd_pcm_format_name((snd_pcm_format_t)val),  
  84.     snd_pcm_format_description(  
  85.                              (snd_pcm_format_t)val));  
  86.   
  87. snd_pcm_hw_params_get_subformat(params,  
  88.                         (snd_pcm_subformat_t *)&val);  
  89. printf("subformat = '%s' (%s)\n",  
  90.     snd_pcm_subformat_name((snd_pcm_subformat_t)val),  
  91.     snd_pcm_subformat_description(  
  92.                           (snd_pcm_subformat_t)val));  
  93.   
  94. snd_pcm_hw_params_get_channels(params, &val);  
  95. printf("channels = %d\n", val);  
  96.   
  97. snd_pcm_hw_params_get_rate(params, &val, &dir);  
  98. printf("rate = %d bps\n", val);  
  99.   
  100. snd_pcm_hw_params_get_period_time(params,  
  101.                                     &val, &dir);  
  102. printf("period time = %d us\n", val);  
  103.   
  104. snd_pcm_hw_params_get_period_size(params,  
  105.                                     &frames, &dir);  
  106. printf("period size = %d frames\n", (int)frames);  
  107.   
  108. snd_pcm_hw_params_get_buffer_time(params,  
  109.                                     &val, &dir);  
  110. printf("buffer time = %d us\n", val);  
  111.   
  112. snd_pcm_hw_params_get_buffer_size(params,  
  113.                          (snd_pcm_uframes_t *) &val);  
  114. printf("buffer size = %d frames\n", val);  
  115.   
  116. snd_pcm_hw_params_get_periods(params, &val, &dir);  
  117. printf("periods per buffer = %d frames\n", val);  
  118.   
  119. snd_pcm_hw_params_get_rate_numden(params,  
  120.                                     &val, &val2);  
  121. printf("exact rate = %d/%d bps\n", val, val2);  
  122.   
  123. val = snd_pcm_hw_params_get_sbits(params);  
  124. printf("significant bits = %d\n", val);  
  125.   
  126. snd_pcm_hw_params_get_tick_time(params,  
  127.                                   &val, &dir);  
  128. printf("tick time = %d us\n", val);  
  129.   
  130. val = snd_pcm_hw_params_is_batch(params);  
  131. printf("is batch = %d\n", val);  
  132.   
  133. val = snd_pcm_hw_params_is_block_transfer(params);  
  134. printf("is block transfer = %d\n", val);  
  135.   
  136. val = snd_pcm_hw_params_is_double(params);  
  137. printf("is double = %d\n", val);  
  138.   
  139. val = snd_pcm_hw_params_is_half_duplex(params);  
  140. printf("is half duplex = %d\n", val);  
  141.   
  142. val = snd_pcm_hw_params_is_joint_duplex(params);  
  143. printf("is joint duplex = %d\n", val);  
  144.   
  145. val = snd_pcm_hw_params_can_overrange(params);  
  146. printf("can overrange = %d\n", val);  
  147.   
  148. val = snd_pcm_hw_params_can_mmap_sample_resolution(params);  
  149. printf("can mmap = %d\n", val);  
  150.   
  151. val = snd_pcm_hw_params_can_pause(params);  
  152. printf("can pause = %d\n", val);  
  153.   
  154. val = snd_pcm_hw_params_can_resume(params);  
  155. printf("can resume = %d\n", val);  
  156.   
  157. val = snd_pcm_hw_params_can_sync_start(params);  
  158. printf("can sync start = %d\n", val);  
  159.   
  160. snd_pcm_close(handle);  
  161.   
  162. return 0;  
  163. }  


打开默认的PCM设备,设置一些硬件参数并且打印出最常用的硬件参数值。它并不做任何回放或录音的操作。

1)snd_pcm_open打开默认的PCM 设备并设置访问模式为PLAYBACK。这个函数返回一个句柄,这个句柄保存在第一个函数参数中。该句柄会在随后的函数中用到。像其它函数一样,这个函数返回一个整数。

int snd_pcm_open(snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t stream, int mode)

2)如果返回值小于0,则代码函数调用出错。如果出错,我们用snd_errstr打开错误信息并退出。

fprintf(stderr,"unable to open pcm device: %s\n",snd_strerror(rc)); // int rc ;

3)为了设置音频流的硬件参数,我们需要分配一个类型为snd_pcm_hw_param的变量。分配用到函数宏 snd_pcm_hw_params_alloca。

snd_pcm_hw_params_alloca(&params); //snd_pcm_uframes_t frames;

4)下一步,我们使用函数snd_pcm_hw_params_any来初始化这个变量,传递先前打开的 PCM流句柄。

snd_pcm_hw_params_any(handle, params);

5)接下来,我们调用API来设置我们所需的硬件参数。这些函数需要三个参数:PCM流句柄,参数类型,参数值。我们设置流为交错模式,16位的样本大小,2 个信道,44100bps的采样率。对于采样率而言,声音硬件并不一定就精确地支持我们所定的采样率,但是我们可以使用函数 snd_pcm_hw_params_set_rate_near来设置最接近我们指定的采样率的采样率。其实只有当我们调用函数 snd_pcm_hw_params后,硬件参数才会起作用。
6)程序的剩余部分获得并打印一些PCM流参数,包括周期和缓冲区大小。结果可能会因为声音硬件的不同而不同。
运行该程序后,做实验,改动一些代码。把设备名字改成hw:0,0,然后看结果是否会有变化。设置不同的硬件参数然后观察结果的变化。

Listing 3. Simple Sound Playback

 

  1. /*
  2. This example reads standard from input and writes
  3. to the default PCM device for 5 seconds of data.
  4. */
  5. /* Use the newer ALSA API */
  6. #define ALSA_PCM_NEW_HW_PARAMS_API
  7. #include <alsa/asoundlib.h>
  8. int main()
  9. {
  10. long loops;
  11. int rc;
  12. int size;
  13. snd_pcm_t *handle;
  14. snd_pcm_hw_params_t *params;
  15. unsigned int val;
  16. int dir;
  17. snd_pcm_uframes_t frames;
  18. char *buffer;
  19. /* Open PCM device for playback. */
  20. rc = snd_pcm_open(&handle, "default",
  21. SND_PCM_STREAM_PLAYBACK, 0);
  22. if (rc 0)
  23. {
  24. fprintf(stderr,"unable to open pcm device: %s\n",snd_strerror(rc));
  25. exit(1);
  26. }
  27. /* Allocate a hardware parameters object. */
  28. snd_pcm_hw_params_alloca(?ms);
  29. /* Fill it in with default values. */
  30. snd_pcm_hw_params_any(handle, params);
  31. /* Set the desired hardware parameters. */
  32. /* Interleaved mode */
  33. snd_pcm_hw_params_set_access(handle, params,
  34. SND_PCM_ACCESS_RW_INTERLEAVED);
  35. /* Signed 16-bit little-endian format */
  36. snd_pcm_hw_params_set_format(handle, params,
  37. SND_PCM_FORMAT_S16_LE);
  38. /* Two channels (stereo) */
  39. snd_pcm_hw_params_set_channels(handle, params, 2);
  40. /* 44100 bits/second sampling rate (CD quality) */
  41. val = 44100;
  42. snd_pcm_hw_params_set_rate_near(handle, params,
  43. &val, &dir);
  44. /* Set period size to 32 frames. */
  45. frames = 32;
  46. snd_pcm_hw_params_set_period_size_near(handle,
  47. params, &frames, &dir);
  48. /* Write the parameters to the driver */
  49. rc = snd_pcm_hw_params(handle, params);
  50. if (rc 0) {
  51. fprintf(stderr,
  52. "unable to set hw parameters: %s\n",
  53. snd_strerror(rc));
  54. exit(1);
  55. }
  56. /* Use a buffer large enough to hold one period */
  57. snd_pcm_hw_params_get_period_size(params,&frames,
  58. &dir);
  59. size = frames * 4; /* 2 bytes/sample, 2 channels */
  60. buffer = (char *) malloc(size);//一个缓冲区
  61. /* We want to loop for 5 seconds */
  62. snd_pcm_hw_params_get_period_time(params,&val, &dir);
  63. /* 5 seconds in microseconds divided by
  64. * period time */
  65. loops = 5000000 / val;(单位应该是us)
  66. while (loops > 0) //循环录音 5 s
  67. {
  68. loops--;
  69. rc = read(0, buffer, size);
  70. if (rc == 0) //没有读取到数据
  71. {
  72. fprintf(stderr, "end of file on input\n");
  73. break;
  74. }
  75. else if (rc != size)//实际读取 的数据 小于 要读取的数据
  76. {
  77. fprintf(stderr,"short read: read %d bytes\n", rc);
  78. }
  79. rc = snd_pcm_writei(handle, buffer,frames);//写入声卡 (放音)
  80. if (rc == -EPIPE)
  81. {
  82. /* EPIPE means underrun */
  83. fprintf(stderr, "underrun occurred\n");
  84. snd_pcm_prepare(handle);
  85. } else if (rc 0) {
  86. fprintf(stderr,"error from writei: %s\n",snd_strerror(rc));
  87. } else if (rc != (int)frames) {
  88. fprintf(stderr,"short write, write %d frames\n", rc);
  89. }
  90. }
  91. snd_pcm_drain(handle);
  92. snd_pcm_close(handle);
  93. free(buffer);
  94. return 0;
  95. }
[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. /*  
  2.   
  3. This example reads standard from input and writes  
  4. to the default PCM device for 5 seconds of data.  
  5.   
  6. */  
  7.   
  8. /* Use the newer ALSA API */  
  9. #define ALSA_PCM_NEW_HW_PARAMS_API  
  10.   
  11. #include <alsa/asoundlib.h>  
  12.   
  13. int main()   
  14. {  
  15.   long loops;  
  16.   int rc;  
  17.   int size;  
  18.   snd_pcm_t *handle;  
  19.   snd_pcm_hw_params_t *params;  
  20.   unsigned int val;  
  21.   int dir;  
  22.   snd_pcm_uframes_t frames;  
  23.   char *buffer;  
  24.   
  25.   /* Open PCM device for playback. */  
  26.   rc = snd_pcm_open(&handle, "default",  
  27.                     SND_PCM_STREAM_PLAYBACK, 0);  
  28.   if (rc 0)  
  29.   {  
  30.     fprintf(stderr,"unable to open pcm device: %s\n",snd_strerror(rc));  
  31.     exit(1);  
  32.   }  
  33.   
  34.   /* Allocate a hardware parameters object. */  
  35.   snd_pcm_hw_params_alloca(?ms);  
  36.   
  37.   /* Fill it in with default values. */  
  38.   snd_pcm_hw_params_any(handle, params);  
  39.   
  40.   /* Set the desired hardware parameters. */  
  41.   
  42.   /* Interleaved mode */  
  43.   snd_pcm_hw_params_set_access(handle, params,  
  44.                       SND_PCM_ACCESS_RW_INTERLEAVED);  
  45.   
  46.   /* Signed 16-bit little-endian format */  
  47.   snd_pcm_hw_params_set_format(handle, params,  
  48.                               SND_PCM_FORMAT_S16_LE);  
  49.   
  50.   /* Two channels (stereo) */  
  51.   snd_pcm_hw_params_set_channels(handle, params, 2);  
  52.   
  53.   /* 44100 bits/second sampling rate (CD quality) */  
  54.   val = 44100;  
  55.   snd_pcm_hw_params_set_rate_near(handle, params,  
  56.                                   &val, &dir);  
  57.   
  58.   /* Set period size to 32 frames. */  
  59.   frames = 32;  
  60.   snd_pcm_hw_params_set_period_size_near(handle,  
  61.                               params, &frames, &dir);  
  62.   
  63.   /* Write the parameters to the driver */  
  64.   rc = snd_pcm_hw_params(handle, params);  
  65.   if (rc 0) {  
  66.     fprintf(stderr,  
  67.             "unable to set hw parameters: %s\n",  
  68.             snd_strerror(rc));  
  69.     exit(1);  
  70.   }  
  71.   
  72.   /* Use a buffer large enough to hold one period */  
  73.   snd_pcm_hw_params_get_period_size(params, &frames,  
  74.                                     &dir);  
  75.   size = frames * 4; /* 2 bytes/sample, 2 channels */  
  76.   buffer = (char *) malloc(size);  
  77.   
  78.   /* We want to loop for 5 seconds */  
  79.   snd_pcm_hw_params_get_period_time(params,&val, &dir);  
  80.   /* 5 seconds in microseconds divided by  
  81.    * period time */  
  82.   loops = 5000000 / val; //求取循环次数
  83.   
  84.   while (loops > 0) //循环录音 5 s    
  85.   {  
  86.     loops--;  
  87.     rc = read(0, buffer, size);  //从标准输入读取输入
  88.     if (rc == 0) //没有读取到数据   
  89.     {  
  90.       fprintf(stderr, "end of file on input\n");  
  91.       break;  
  92.     }   
  93.     else if (rc != size)//实际读取 的数据 小于 要读取的数据   
  94.     {  
  95.       fprintf(stderr,"short read: read %d bytes\n", rc);  
  96.     }  
  97.       
  98.     rc = snd_pcm_writei(handle, buffer, frames);//写入声卡  (放音)   
  99.     if (rc == -EPIPE)   
  100.     {  
  101.       /* EPIPE means underrun */  
  102.       fprintf(stderr, "underrun occurred\n");  
  103.       snd_pcm_prepare(handle);  
  104.     } else if (rc 0) {  
  105.       fprintf(stderr,"error from writei: %s\n",snd_strerror(rc));  
  106.     }  else if (rc != (int)frames) {  
  107.       fprintf(stderr,"short write, write %d frames\n", rc);  
  108.     }  
  109.   }  
  110.   
  111.   snd_pcm_drain(handle);  
  112.   snd_pcm_close(handle);  
  113.   free(buffer);  
  114.   
  115.   return 0;  
  116. }  


 



清单3扩展了之前的示例。向声卡中写入了一些声音样本以实现声音回放。在这个例子中,我们从标准输入中读取数据,每个周期读取足够多的数据,然后将它们写入到声卡中,直到5秒钟的数据全部传输完毕。
这个程序的开始处和之前的版本一样---打开PCM设备、设置硬件参数。我们使用由ALSA自己选择的周期大小,申请该大小的缓冲区来存储样本然后我们找出周期时间,这样我们就能计算出本程序为了能够播放5秒钟,需要多少个周期。
在处理数据的循环中,我们从标准输入中读入数据,并往缓冲区中填充一个周期的样本。然后检查并处理错误,这些错误可能是由到达文件结尾,或读取的数据长度与我期望的数据长度不一致导致的。
我们调用snd_pcm_writei来发送数据。它操作起来很像内核的写系统调用,只是这里的大小参数是以帧来计算的。我们检查其返回代码值。返回值为EPIPE表明发生了underrun,使得PCM音频流进入到XRUN状态并停止处理数据。从该状态中恢复过来的标准方法是调用snd_pcm_prepare函数,把PCM流置于PREPARED状态,这样下次我们向该PCM流中数据时,它就能重新开始处理数据。如果我们得到的错误码不是EPIPE,我们把错误码打印出来,然后继续。最后,如果写入的帧数不是我们期望的,则打印出错误消息。 这个程序一直循环,直到5秒钟的帧全部传输完,或者输入流读到文件结尾。然后我们调用snd_pcm_drain把所有挂起没有传输完的声音样本传输完全,最后关闭该音频流,释放之前动态分配的缓冲区,退出。 我们可以看到这个程序没有什么用,除非标准输入被重定向到了其它其它的文件。尝试用设备/dev/urandom来运行这个程序,该设备产生随机数据: ./example3 </dev/urandom 随机数据会产生5秒钟的白色噪声。 然后,尝试把标准输入重定向到设备/dev/null和/dev/zero上,并比较结果。改变一些参数,例如采样率和数据格式,然后查看结果的变化。
Listing 4. Simple Sound Recording

 

  1. /*
  2. This example reads from the default PCM device
  3. and writes to standard output for 5 seconds of data.
  4. */
  5. /* Use the newer ALSA API */
  6. #define ALSA_PCM_NEW_HW_PARAMS_API
  7. #include <alsa/asoundlib.h>
  8. int main() {
  9. long loops;
  10. int rc;
  11. int size;
  12. snd_pcm_t *handle;
  13. snd_pcm_hw_params_t *params;
  14. unsigned int val;
  15. int dir;
  16. snd_pcm_uframes_t frames;
  17. char *buffer;
  18. /* Open PCM device for recording (capture). */
  19. rc = snd_pcm_open(&handle, "default",
  20. SND_PCM_STREAM_CAPTURE, 0);
  21. if (rc 0) {
  22. fprintf(stderr,
  23. "unable to open pcm device: %s\n",
  24. snd_strerror(rc));
  25. exit(1);
  26. }
  27. /* Allocate a hardware parameters object. */
  28. snd_pcm_hw_params_alloca(?ms);
  29. /* Fill it in with default values. */
  30. snd_pcm_hw_params_any(handle, params);
  31. /* Set the desired hardware parameters. */
  32. /* Interleaved mode */
  33. snd_pcm_hw_params_set_access(handle, params,
  34. SND_PCM_ACCESS_RW_INTERLEAVED);
  35. /* Signed 16-bit little-endian format */
  36. snd_pcm_hw_params_set_format(handle, params,
  37. SND_PCM_FORMAT_S16_LE);
  38. /* Two channels (stereo) */
  39. snd_pcm_hw_params_set_channels(handle, params, 2);
  40. /* 44100 bits/second sampling rate (CD quality) */
  41. val = 44100;
  42. snd_pcm_hw_params_set_rate_near(handle, params,
  43. &val, &dir);
  44. /* Set period size to 32 frames. */
  45. frames = 32;
  46. snd_pcm_hw_params_set_period_size_near(handle,
  47. params, &frames, &dir);
  48. /* Write the parameters to the driver */
  49. rc = snd_pcm_hw_params(handle, params);
  50. if (rc 0) {
  51. fprintf(stderr,
  52. "unable to set hw parameters: %s\n",
  53. snd_strerror(rc));
  54. exit(1);
  55. }
  56. /* Use a buffer large enough to hold one period */
  57. snd_pcm_hw_params_get_period_size(params,
  58. &frames, &dir);
  59. size = frames * 4; /* 2 bytes/sample, 2 channels */
  60. buffer = (char *) malloc(size);
  61. /* We want to loop for 5 seconds */
  62. snd_pcm_hw_params_get_period_time(params,
  63. &val, &dir);
  64. loops = 5000000 / val;
  65. while (loops > 0) {
  66. loops--;
  67. rc = snd_pcm_readi(handle, buffer, frames);
  68. if (rc == -EPIPE) {
  69. /* EPIPE means overrun */
  70. fprintf(stderr, "overrun occurred\n");
  71. snd_pcm_prepare(handle);
  72. } else if (rc 0) {
  73. fprintf(stderr,
  74. "error from read: %s\n",
  75. snd_strerror(rc));
  76. } else if (rc != (int)frames) {
  77. fprintf(stderr, "short read, read %d frames\n", rc);
  78. }
  79. rc = write(1, buffer, size);
  80. if (rc != size)
  81. fprintf(stderr,
  82. "short write: wrote %d bytes\n", rc);
  83. }
  84. snd_pcm_drain(handle);
  85. snd_pcm_close(handle);
  86. free(buffer);
  87. return 0;
  88. }
[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. /*  
  2.   
  3. This example reads from the default PCM device  
  4. and writes to standard output for 5 seconds of data.  
  5.   
  6. */  
  7.   
  8. /* Use the newer ALSA API */  
  9. #define ALSA_PCM_NEW_HW_PARAMS_API  
  10.   
  11. #include <alsa/asoundlib.h>  
  12.   
  13. int main() {  
  14. long loops;  
  15. int rc;  
  16. int size;  
  17. snd_pcm_t *handle;  
  18. snd_pcm_hw_params_t *params;  
  19. unsigned int val;  
  20. int dir;  
  21. snd_pcm_uframes_t frames;  
  22. char *buffer;  
  23.   
  24. /* Open PCM device for recording (capture). */  
  25. rc = snd_pcm_open(&handle, "default",  
  26.                     SND_PCM_STREAM_CAPTURE, 0);  
  27. if (rc 0) {  
  28.     fprintf(stderr,  
  29.             "unable to open pcm device: %s\n",  
  30.             snd_strerror(rc));  
  31.     exit(1);  
  32. }  
  33.   
  34. /* Allocate a hardware parameters object. */  
  35. snd_pcm_hw_params_alloca(?ms);  
  36.   
  37. /* Fill it in with default values. */  
  38. snd_pcm_hw_params_any(handle, params);  
  39.   
  40. /* Set the desired hardware parameters. */  
  41.   
  42. /* Interleaved mode */  
  43. snd_pcm_hw_params_set_access(handle, params,  
  44.                       SND_PCM_ACCESS_RW_INTERLEAVED);  
  45.   
  46. /* Signed 16-bit little-endian format */  
  47. snd_pcm_hw_params_set_format(handle, params,  
  48.                               SND_PCM_FORMAT_S16_LE);  
  49.   
  50. /* Two channels (stereo) */  
  51. snd_pcm_hw_params_set_channels(handle, params, 2);  
  52.   
  53. /* 44100 bits/second sampling rate (CD quality) */  
  54. val = 44100;  
  55. snd_pcm_hw_params_set_rate_near(handle, params,  
  56.                                   &val, &dir);  
  57.   
  58. /* Set period size to 32 frames. */  
  59. frames = 32;  
  60. snd_pcm_hw_params_set_period_size_near(handle,  
  61.                               params, &frames, &dir);  
  62.   
  63. /* Write the parameters to the driver */  
  64. rc = snd_pcm_hw_params(handle, params);  
  65. if (rc 0) {  
  66.     fprintf(stderr,  
  67.             "unable to set hw parameters: %s\n",  
  68.             snd_strerror(rc));  
  69.     exit(1);  
  70. }  
  71.   
  72. /* Use a buffer large enough to hold one period */  
  73. snd_pcm_hw_params_get_period_size(params,  
  74.                                       &frames, &dir);  
  75. size = frames * 4; /* 2 bytes/sample, 2 channels */  
  76. buffer = (char *) malloc(size);  
  77.   
  78. /* We want to loop for 5 seconds */  
  79. snd_pcm_hw_params_get_period_time(params,  
  80.                                          &val, &dir);  
  81. loops = 5000000 / val;  
  82.   
  83. while (loops > 0) {  
  84.     loops--;  
  85.     rc = snd_pcm_readi(handle, buffer, frames);  
  86.     if (rc == -EPIPE) {  
  87.       /* EPIPE means overrun */  
  88.       fprintf(stderr, "overrun occurred\n");  
  89.       snd_pcm_prepare(handle);  
  90.     } else if (rc 0) {  
  91.       fprintf(stderr,  
  92.               "error from read: %s\n",  
  93.               snd_strerror(rc));  
  94.     } else if (rc != (int)frames) {  
  95.       fprintf(stderr, "short read, read %d frames\n", rc);  
  96.     }  
  97.     rc = write(1, buffer, size);  
  98.     if (rc != size)  
  99.       fprintf(stderr,  
  100.               "short write: wrote %d bytes\n", rc);  
  101. }  
  102.   
  103. snd_pcm_drain(handle);  
  104. snd_pcm_close(handle);  
  105. free(buffer);  
  106.   
  107. return 0;  
  108. }  

清单4类似于清单3中的程序,除了这里的程序时做声音的抓取(录音)。当打开PCM设备时我们指定打开模式为 SND_PCM_STREAM_CPATURE。在主循环中,我们调用snd_pcm_readi从声卡中读取数据,并把它们写入到标准输出。同样地,我们检查是否有overrun,如果存在,用与前例中相同的方式处理。
运行清单4的程序将录制将近5秒钟的声音数据,并把它们发送到标准输出。你也可以重定向到某个文件。如果你有一个麦克风连接到你的声卡,可以使用某个混音程序(mixer)设置录音源和级别。同样地,你也可以运行一个CD播放器程序并把录音源设成CD。尝
运行程序4并把输出定向到某个文件,然后运行程序 3播放该文件里的声音数据:
./listing4 > sound.raw
./listing3 < sound.raw
如果你的声卡支持全双工,你可以通过管道把两个程序连接起来,这样就可以从声卡中听到录制的声音:
./listing4 | ./listing3
同样地,您可以做实验,看看采样率和样本格式的变化会产生什么影响。


高级特性
在前面的例子中,PCM流是以阻塞模式操作的,也就是说,直到数据已经传送完,PCM接口调用才会返回。在事件驱动的交互式程序中,这样会长时间阻塞应用程序,通常是不能接受的。ALSA支持以非阻塞模式打开音频流,这样读写函数调用后立即返回。如果数据传输被挂起,调用不能被处理,ALSA就是返回一个 EBUSY的错误码。
许多图形应用程序使用回调来处理事件。ALSA支持以异步的方式打开一个PCM音频流。这使得当某个周期的样本数据被传输完后,某个已注册的回调函数将会调用。
这里用到的snd_pcm_readi和snd_pcm_writei调用和Linux下的读写系统调用类似。字母i表示处理的帧是交错式 (interleaved)的。ALSA中存在非交互模式的对应的函数。Linux下的许多设备也支持mmap系统调用,这个调用将设备内存映射到主内存,这样数据就可以用指针来维护。ALSA也运行以mmap模式打开一个PCM信道,这允许有效的零拷贝(zero copy)方式访问声音数据。


总结
我希望这篇文章能够激励你尝试编写某些ALSA程序。伴随着2.6内核在Linux发布版本(distributions)中被广泛地使用,ALSA也将被广泛地采用。它的高级特征将帮助Linux音频程序更好地向前发

2013-10-19 17:05:30 jsh13417 阅读数 5479

[Loong]:之前写过基于ALSA的WAV播放录音程序,见http://blog.csdn.net/sepnic/archive/2011/01/14/6140824.aspx。现在本想好好整理一下ALSA的编程思想,但Google了一下,发现已经有同道做了类似的工作,故将其转载过来,并添加一些本人的疑问以及补充(将会继续补充,原文很多重要的ALSA参数没有提到)。

原文:http://blogold.chinaunix.net/u3/112227/showart_2251390.html

 

一. 编程细节

 

按照上面的流程,其中有许多细节我们可以加以控制,这里仅仅指出应用程序需要关心的:

 

1.1 设备层次

在alsa驱动这一层,目前为止,抽象出了4层设备:

一是hw:0,0;

二是plughw:0,0;

三是default:0;

四是default。

至于一是清楚了,二和二以上可以做数据转换,以支持一个动态的范围,比如你要播放7000hz的东西,那么就可以用二和二以上的。而你用7000hz作为参数,去设置一,就会报错。三和四,支持软件混音。我觉得default:0表示对第一个声卡软件混音,default表示对整个系统软件混音。

这里提出两点:

1.1.1 一般为了让所有的程序都可以发音,为使用更多的默认策略,我们选用三和四,这样少一些控制权,多一些方便。

1.1.2 对不同的层次的设备,相同的函数,结果可能是不一样的。比如,设置Hardware Parameters里的period和buffer size,这个是对硬件的设置,所以,default和default:0这两种设备是不能设置的。

如果直接操作hw:0,0,那么snd_pcm_writei只能写如8的倍数的frame,比如16、24等,否则就会剩下一点不写入而退回,而 default,就可以想写多少就写多少,我们也不必要关心里面具体的策略。

[Loong]:之前都是使用了default,还真没留意过这些设备有何区别。

 

1.2 Hardware Parameters

说明:之所以叫做Hardware Parameters,是因为alsa这一层api是较为底层的,它允许用户对audio interface和alsa-core两层都做设置。其中对alsa-core设置,叫做Software Parameters,而对audio interface的设置叫做Hardware Parameters。(当然要设置hardware parameters,也肯定是通过alsa驱动来完成,只不过哪些参数是指导硬件的,哪些是指导alsa-core的,分开设置了)

1.2.1 Sample rate: 采样率

1.2.2 Sample format: 采用格式

1.2.3 Number of channels: 声道数

1.2.4 Data access and layout:

简单点说,在一个period以内,数据是按照channel1排完了再排channel2呢,还是一个frame一个frame的来排(frame在alsa里指的是一次采样时间内,两个channel的数据放一块儿就是一个frame)。默认是第二种。

1.2.5 Interrupt interval:

中断间隔,就是靠periods决定的,有函数来设置periods,也就是说这个hardware buffer在一次遍历之内,要中断多少次,来通知alsa-driver来写入或读走数据。比如buffer是8192个frame大,而 period设为4个frame大,那么比如playback,则每当有4个frame大的hardware buffer空间空出,就会中断,通知内核(alsa驱动)来写如数据。这个是影响实时效果的关键。一般不用调整。

1.2.6 Buffer size:

hardware buffer的大小,如果alsa整套体系主要靠这个来做缓冲,那么这个的大小,将影响缓冲效果,但是一般也不调整。

[Loong]:缺少buffer time、peroid time、peroid size等参数说明,这些参数一般情况下都要设置的。


1.3 Software Parameters

1.3.1 snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, 4096)

这个仅用在interrupt-driven模式。这个模式是alsa驱动层的,不是硬件interrupt。它的意思是,用户使用 snd_pcm_wait()时,这个实际封装的是系统的poll调用,表示用户在等待,那么在等待什么呢?对于playback来讲,就是等待下面的声卡的hardware buffer里有一定数量的空间,可以放入新的数据了,对于record来讲,就是等待下面声卡新采集的数据达到了一定数量了。这个一定数量,就是用 snd_pcm_sw_params_set_avail_min来设置,单位是frame。实际运作,没读驱动代码,不是很清楚,可能是alsa驱动根据用户设的这个参数,来设置Hardware Parameters里面的period,也可能是不改变硬件的period,每次硬件中断还是copy到自己的空间,然后数据积累到一定数量再 interrupt应用程序,使之从wait()出来。我不知道,也不必深究。

这种模式的使用,需要用户在snd_pcm_wait()出来以后,调用一个平常的wirtei或readi函数,来写入或读取一定数量的数据。如果用户不用interrupt-driven模式,那么这个函数不必使用。

[Loong]:什么是interrupt-driven模式?

 

1.3.2 snd_pcm_sw_params_set_start_threshold (playback_handle, sw_params, 0U)

这个函数指导什么时候开启audio interface的AD/DA,就是什么时候启动声卡。

对于playback,假设第三个参数设为320,那么就是说,当用户调用writei,写入的数据,将暂时存在alsa驱动空间里,当这个数据量达到 320帧时,alsa驱动才开始将数据写入hardware buffer,并启动DA转换。对于record,当用户调用readi,这个数据量达到320帧时,alsa驱动才开始启动AD转换,捕捉数据。我一般把它设为0,我没试过非0,如果是非0, 我想第一次的writei和readi一定得够数量才行,否则设备不启动。

这个对实时效果是需要的,将第三个参数设置为0,保证声卡的立即启动。

 

1.4 what to do about xruns

xrun指的是,声卡period一到,引发一个中断,告诉alsa驱动,要填入数据,或读走数据,但是,问题在于alsa的读取和写入操作必须用户调用writei和readi才会发生的,它不会去缓存数据。如果上层没有用户调用writei和readi,那么就会产生 overrun(录制时,数据都满了,还没被alsa驱动读走)和underrun(需要数据来播放,alsa驱动却不写入数据),统称为xrun。

这个东西,需要用一些函数来设置,比如snd_pcm_sw_params_set_silence_threshold(),是针对playback 的,就是设置当xxx的情况下,就用silence来写入hardware buffer。至于xxx情况,以及写入多少silence,我都不是很清楚,还有,比如xrun到什么情况下,可以停止这个设备等等函数。一般情况下用alsa驱动的默认的xrun处理策略。

但是关于xrun,最好这样写:

while ((pcmreturn = snd_pcm_writei(pcm_handle, data, frames)) < 0) {

        snd_pcm_prepare(pcm_handle);

        fprintf(stderr, "<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>/n");

}

就是说,如果这次读/写距离上次读/写,时间可能过长,那么这次去读/写的时候,device已经xrun了,在不知道alsa驱动对xrun的默认策略的情况下,最好调用snd_pcm_prepare()来重新准备好设备,然后再开始下一次读写。

 

1.5 transfer chunk size

这个应该是用不上的,我没找到文档里有用这个的。

[Loong]:这个其实是非常重要的,如果snd_pcm_writei/ snd_pcm_readi不是每次写入chunk size数据的话,那么放音/录音不是你所期望的声音。详细见:http://alsa-project.org/main/index.php/FramesPeriods

配置音量 
# cd  /home/work/alsa-utils-1.0.4
# alsamixer 
(此时出现图形界面,可以通过方向键来选取和修改音量,通过m键决定是否静音) 
# alsactl store 
(保存音量配置)
设置系统启动时自动加载: 
第一种方法(最简单,往系统加入alsasound服务,使用alsa本身的脚本进行控制,第一推荐):
执行:
chkconfig --add alsasound 
之后重启即可。 
第二种方法(也很简单):
编辑/etc/rc.d/rc.local 
加上这一行:
alsactl restore 
至此盼望已久的美妙音乐终于出现。


ALSA声音编程介绍

阅读数 12443