关于怎么使用ALSA API教程

  这份文档帮助对ALSA API使用入门。不是一个完整的ALSA API参考手册(详细参考手册地址是:http://www.alsa-project.org/alsa-doc/alsa-lib/),并且不包括复杂软件需要处理的细节部分。而它为有编程经验的但对这些API是新手的程序员提供充足的背景知识,以写出一个简单的程序。在这份文档中所有的代码的许可证遵守GNU Public License的,如果你打算在其他许可证下用ALSA写软件,我建议你去发现其他的文档。  主要内容:

        (1)理解音频接口

        (2)典型音频应用是什么样子的

        (3)一个最小回放程序

        (4)一个最小采集程序

        (5)一个最小基于中断驱动的程序

                   (6)写程序过程

                打开设备

                设置参数

                接受和发送数据

理解音频接口

我们首先回顾音频接口的基本设计。作为一个应用程序程序员,实际你没有必要为操作层担忧,它由ALSA提供的一个插件设备驱动全部考录,但是如果你想要些一个有效率并且灵活的软件,你必须在概念上理解操作层都做了些什么。

(音频接口、音频设备、声卡。在此处是一个概念。)

 

音频接口是一个设备(译者注:声卡),允许计算机从外界接受和向外界发送音频数据。在计算机内部,音频数据像其他任何数据一样代表了比特流。然而,声音接口可以发送数字声音信号和接受模拟声音信号。这两种情况下,计算机使用的代表声音的比特流需要被转化,在被送到外界之前,同样,被音频接口接受的外部信号也需要被转化,使之变为对计算机有用的数据(可用的数据)。这两个转化就是音频接口存在的目的

在音频接口中,有个区域被称为“硬件缓冲区”,当一个外界音频信号到达后,这个设备转换这个模拟信号为计算机可以使用的比特流,并且存储这些数据在硬件缓冲区,被用来传递给计算机。当在硬件缓冲区采集到了足够多的数据,音频接口对计算机产生一个中断,以告诉计算机数据准备好了,可以从硬件缓冲区,取出了。相反,对于数据从计算机内部送到外界时,有一个和以上有个相似的过程。设备产生对计算机产生中断,告诉计算机,硬件缓冲区有空间,计算机可以存数据在硬件缓冲区。设备然后转化这些比特流为外界需要的无论什么格式,并且发送它。理解设备使用这个缓冲区作为一个循环缓冲区是很重要的。当到达了缓冲区的末尾,指针回到头,继续开始。

为了这个过程可以正确的运行,一些变量需要被配置,他们包括:

(1)当在计算机使用的比特流和外界使用的信号之间的转换时,设备应该使用什么格式?

(2)采样样本以多少的比特率在设备和计算机直接传递?

(3)设备对计算机产生中断时,硬件缓冲区应该有多少数据(接受)或者空间(发送)?

(4)硬件缓冲区应该多大?

头两个问题是音频数据质量调节的基础。后两个问题是影响音频信号的延时,这个延时指的是在这些之间的延时:

1.输入延时:由设备从外界采集到的音频数据到达计算机之间。

2.输出延时:由计算机发送的数据,到达外界时之间的延时。

这两者对于很多的音频软件是很重要的,尽管一些程序不需要关心这些事情。

最小的播放程序

这个程序用播放模式打开一个音频设备,配置为立体音,16bit(位深),采样频率44.1KHZ,交错模式,常见的read/write存取模式。然后,发送一块任意数据到设备中,最后退出程序。

[cpp] view plain copy
 
  1. <span style="font-size:12px;">#include <stdio.h>  
  2.     #include <stdlib.h>  
  3.     #include <alsa/asoundlib.h>  
  4.             
  5.     main (int argc, char *argv[])  
  6.     {  
  7.         int i;  
  8.         int err;  
  9.         short buf[128];  
  10.         snd_pcm_t *playback_handle;  
  11.         snd_pcm_hw_params_t *hw_params;  
  12.       
  13.         if ((err = snd_pcm_open (&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)) < 0) {  
  14.             fprintf (stderr, "cannot open audio device %s (%s)\n",   
  15.                  argv[1],  
  16.                  snd_strerror (err));  
  17.             exit (1);  
  18.         }  
  19.              
  20.         if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {  
  21.             fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",  
  22.                  snd_strerror (err));  
  23.             exit (1);  
  24.         }  
  25.                    
  26.         if ((err = snd_pcm_hw_params_any (playback_handle, hw_params)) < 0) {  
  27.             fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",  
  28.                  snd_strerror (err));  
  29.             exit (1);  
  30.         }  
  31.       
  32.         if ((err = snd_pcm_hw_params_set_access (playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {  
  33.             fprintf (stderr, "cannot set access type (%s)\n",  
  34.                  snd_strerror (err));  
  35.             exit (1);  
  36.         }  
  37.       
  38.         if ((err = snd_pcm_hw_params_set_format (playback_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {  
  39.             fprintf (stderr, "cannot set sample format (%s)\n",  
  40.                  snd_strerror (err));  
  41.             exit (1);  
  42.         }  
  43.       
  44.         if ((err = snd_pcm_hw_params_set_rate_near (playback_handle, hw_params, 44100, 0)) < 0) {  
  45.             fprintf (stderr, "cannot set sample rate (%s)\n",  
  46.                  snd_strerror (err));  
  47.             exit (1);  
  48.         }  
  49.       
  50.         if ((err = snd_pcm_hw_params_set_channels (playback_handle, hw_params, 2)) < 0) {  
  51.             fprintf (stderr, "cannot set channel count (%s)\n",  
  52.                  snd_strerror (err));  
  53.             exit (1);  
  54.         }  
  55.       
  56.         if ((err = snd_pcm_hw_params (playback_handle, hw_params)) < 0) {  
  57.             fprintf (stderr, "cannot set parameters (%s)\n",  
  58.                  snd_strerror (err));  
  59.             exit (1);  
  60.         }  
  61.       
  62.         snd_pcm_hw_params_free (hw_params);  
  63.       
  64.         if ((err = snd_pcm_prepare (playback_handle)) < 0) {  
  65.             fprintf (stderr, "cannot prepare audio interface for use (%s)\n",  
  66.                  snd_strerror (err));  
  67.             exit (1);  
  68.         }  
  69.       
  70.         for (i = 0; i < 10; ++i) {  
  71.             if ((err = snd_pcm_writei (playback_handle, buf, 128)) != 128) {  
  72.                 fprintf (stderr, "write to audio interface failed (%s)\n",  
  73.                      snd_strerror (err));  
  74.                 exit (1);  
  75.             }  
  76.         }  
  77.       
  78.         snd_pcm_close (playback_handle);  
  79.         exit (0);  
  80.     }  
  81. </span>  


 

最小采集程序:

为了采集音频数据,程序打开一个音频设备,配置为立体音,位深16bit,采集频率为44.1KHz,交错模式,常见的read/write存取数据,然后读取一块任意数据从硬件缓冲区,最后退出程序。

[cpp] view plain copy
 
  1. <span style="font-size:12px;">#include <stdio.h>  
  2.     #include <stdlib.h>  
  3.     #include <alsa/asoundlib.h>  
  4.             
  5.     main (int argc, char *argv[])  
  6.     {  
  7.         int i;  
  8.         int err;  
  9.         short buf[128];  
  10.         snd_pcm_t *capture_handle;  
  11.         snd_pcm_hw_params_t *hw_params;  
  12.       
  13.         if ((err = snd_pcm_open (&capture_handle, argv[1], SND_PCM_STREAM_CAPTURE, 0)) < 0) {  
  14.             fprintf (stderr, "cannot open audio device %s (%s)\n",   
  15.                  argv[1],  
  16.                  snd_strerror (err));  
  17.             exit (1);  
  18.         }  
  19.              
  20.         if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {  
  21.             fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",  
  22.                  snd_strerror (err));  
  23.             exit (1);  
  24.         }  
  25.                    
  26.         if ((err = snd_pcm_hw_params_any (capture_handle, hw_params)) < 0) {  
  27.             fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",  
  28.                  snd_strerror (err));  
  29.             exit (1);  
  30.         }  
  31.       
  32.         if ((err = snd_pcm_hw_params_set_access (capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {  
  33.             fprintf (stderr, "cannot set access type (%s)\n",  
  34.                  snd_strerror (err));  
  35.             exit (1);  
  36.         }  
  37.       
  38.         if ((err = snd_pcm_hw_params_set_format (capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {  
  39.             fprintf (stderr, "cannot set sample format (%s)\n",  
  40.                  snd_strerror (err));  
  41.             exit (1);  
  42.         }  
  43.       
  44.         if ((err = snd_pcm_hw_params_set_rate_near (capture_handle, hw_params, 44100, 0)) < 0) {  
  45.             fprintf (stderr, "cannot set sample rate (%s)\n",  
  46.                  snd_strerror (err));  
  47.             exit (1);  
  48.         }  
  49.       
  50.         if ((err = snd_pcm_hw_params_set_channels (capture_handle, hw_params, 2)) < 0) {  
  51.             fprintf (stderr, "cannot set channel count (%s)\n",  
  52.                  snd_strerror (err));  
  53.             exit (1);  
  54.         }  
  55.       
  56.         if ((err = snd_pcm_hw_params (capture_handle, hw_params)) < 0) {  
  57.             fprintf (stderr, "cannot set parameters (%s)\n",  
  58.                  snd_strerror (err));  
  59.             exit (1);  
  60.         }  
  61.       
  62.         snd_pcm_hw_params_free (hw_params);  
  63.       
  64.         if ((err = snd_pcm_prepare (capture_handle)) < 0) {  
  65.             fprintf (stderr, "cannot prepare audio interface for use (%s)\n",  
  66.                  snd_strerror (err));  
  67.             exit (1);  
  68.         }  
  69.       
  70.         for (i = 0; i < 10; ++i) {  
  71.             if ((err = snd_pcm_readi (capture_handle, buf, 128)) != 128) {  
  72.                 fprintf (stderr, "read from audio interface failed (%s)\n",  
  73.                      snd_strerror (err));  
  74.                 exit (1);  
  75.             }  
  76.         }  
  77.       
  78.         snd_pcm_close (capture_handle);  
  79.         exit (0);  
  80.     }  
  81. </span>  


程序用播放模式,打开一个音频设备,配置为立体音,位深16bit,采集频率为44.1KHz,交错模式,常见的read/write存取数据。程序一直等待,直到设备准备好了接受回放数据,准备好同时,发送任意数据到硬件缓冲区,这个设计允许你的程序被移植到依靠回调函数驱动的机制,例如,jack,ladspa,coreaudio,vst 和其他。

[cpp] view plain copy
 
  1. <span style="font-size:14px;">     </span><span style="font-size:12px;"> #include <stdio.h>  
  2.     #include <stdlib.h>  
  3.     #include <errno.h>  
  4.     #include <poll.h>  
  5.     #include <alsa/asoundlib.h>  
  6.             
  7.     snd_pcm_t *playback_handle;  
  8.     short buf[4096];  
  9.       
  10.     int  
  11.     playback_callback (snd_pcm_sframes_t nframes)  
  12.     {  
  13.         int err;  
  14.       
  15.         printf ("playback callback called with %u frames\n", nframes);  
  16.       
  17.         /* ... fill buf with data ... */  
  18.       
  19.         if ((err = snd_pcm_writei (playback_handle, buf, nframes)) < 0) {  
  20.             fprintf (stderr, "write failed (%s)\n", snd_strerror (err));  
  21.         }  
  22.       
  23.         return err;  
  24.     }  
  25.             
  26.     main (int argc, char *argv[])  
  27.     {  
  28.       
  29.         snd_pcm_hw_params_t *hw_params;  
  30.         snd_pcm_sw_params_t *sw_params;  
  31.         snd_pcm_sframes_t frames_to_deliver;  
  32.         int nfds;  
  33.         int err;  
  34.         struct pollfd *pfds;  
  35.       
  36.         if ((err = snd_pcm_open (&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)) < 0) {  
  37.             fprintf (stderr, "cannot open audio device %s (%s)\n",   
  38.                  argv[1],  
  39.                  snd_strerror (err));  
  40.             exit (1);  
  41.         }  
  42.              
  43.         if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {  
  44.             fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",  
  45.                  snd_strerror (err));  
  46.             exit (1);  
  47.         }  
  48.                    
  49.         if ((err = snd_pcm_hw_params_any (playback_handle, hw_params)) < 0) {  
  50.             fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",  
  51.                  snd_strerror (err));  
  52.             exit (1);  
  53.         }  
  54.       
  55.         if ((err = snd_pcm_hw_params_set_access (playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {  
  56.             fprintf (stderr, "cannot set access type (%s)\n",  
  57.                  snd_strerror (err));  
  58.             exit (1);  
  59.         }  
  60.       
  61.         if ((err = snd_pcm_hw_params_set_format (playback_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {  
  62.             fprintf (stderr, "cannot set sample format (%s)\n",  
  63.                  snd_strerror (err));  
  64.             exit (1);  
  65.         }  
  66.       
  67.         if ((err = snd_pcm_hw_params_set_rate_near (playback_handle, hw_params, 44100, 0)) < 0) {  
  68.             fprintf (stderr, "cannot set sample rate (%s)\n",  
  69.                  snd_strerror (err));  
  70.             exit (1);  
  71.         }  
  72.       
  73.         if ((err = snd_pcm_hw_params_set_channels (playback_handle, hw_params, 2)) < 0) {  
  74.             fprintf (stderr, "cannot set channel count (%s)\n",  
  75.                  snd_strerror (err));  
  76.             exit (1);  
  77.         }  
  78.       
  79.         if ((err = snd_pcm_hw_params (playback_handle, hw_params)) < 0) {  
  80.             fprintf (stderr, "cannot set parameters (%s)\n",  
  81.                  snd_strerror (err));  
  82.             exit (1);  
  83.         }  
  84.       
  85.         snd_pcm_hw_params_free (hw_params);  
  86.       
  87.         /* tell ALSA to wake us up whenever 4096 or more frames 
  88.            of playback data can be delivered. Also, tell 
  89.            ALSA that we'll start the device ourselves. 
  90.         */  
  91.       
  92.         if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) {  
  93.             fprintf (stderr, "cannot allocate software parameters structure (%s)\n",  
  94.                  snd_strerror (err));  
  95.             exit (1);  
  96.         }  
  97.         if ((err = snd_pcm_sw_params_current (playback_handle, sw_params)) < 0) {  
  98.             fprintf (stderr, "cannot initialize software parameters structure (%s)\n",  
  99.                  snd_strerror (err));  
  100.             exit (1);  
  101.         }  
  102.         if ((err = snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, 4096)) < 0) {  
  103.             fprintf (stderr, "cannot set minimum available count (%s)\n",  
  104.                  snd_strerror (err));  
  105.             exit (1);  
  106.         }  
  107.         if ((err = snd_pcm_sw_params_set_start_threshold (playback_handle, sw_params, 0U)) < 0) {  
  108.             fprintf (stderr, "cannot set start mode (%s)\n",  
  109.                  snd_strerror (err));  
  110.             exit (1);  
  111.         }  
  112.         if ((err = snd_pcm_sw_params (playback_handle, sw_params)) < 0) {  
  113.             fprintf (stderr, "cannot set software parameters (%s)\n",  
  114.                  snd_strerror (err));  
  115.             exit (1);  
  116.         }  
  117.       
  118.         /* the interface will interrupt the kernel every 4096 frames, and ALSA 
  119.            will wake up this program very soon after that. 
  120.         */  
  121.       
  122.         if ((err = snd_pcm_prepare (playback_handle)) < 0) {  
  123.             fprintf (stderr, "cannot prepare audio interface for use (%s)\n",  
  124.                  snd_strerror (err));  
  125.             exit (1);  
  126.         }  
  127.       
  128.         while (1) {  
  129.       
  130.             /* wait till the interface is ready for data, or 1 second 
  131.                has elapsed. 
  132.             */  
  133.       
  134.             if ((err = snd_pcm_wait (playback_handle, 1000)) < 0) {  
  135.                     fprintf (stderr, "poll failed (%s)\n", strerror (errno));  
  136.                     break;  
  137.             }                
  138.       
  139.             /* find out how much space is available for playback data */  
  140.       
  141.             if ((frames_to_deliver = snd_pcm_avail_update (playback_handle)) < 0) {  
  142.                 if (frames_to_deliver == -EPIPE) {  
  143.                     fprintf (stderr, "an xrun occured\n");  
  144.                     break;  
  145.                 } else {  
  146.                     fprintf (stderr, "unknown ALSA avail update return value (%d)\n",   
  147.                          frames_to_deliver);  
  148.                     break;  
  149.                 }  
  150.             }  
  151.       
  152.             frames_to_deliver = frames_to_deliver > 4096 ? 4096 : frames_to_deliver;  
  153.       
  154.             /* deliver the data */  
  155.       
  156.             if (playback_callback (frames_to_deliver) != frames_to_deliver) {  
  157.                     fprintf (stderr, "playback callback failed\n");  
  158.                 break;  
  159.             }  
  160.         }  
  161.       
  162.         snd_pcm_close (playback_handle);  
  163.         exit (0);  
  164.     }  
  165. </span>  


 

 最小全双工程序:

全双工可以通过结合上述所展示的回放和采集程序实现。虽然,很多的现有的linux音频程序使用这种设计,但是在作者看来,这个是有很大的缺陷。上诉的中断驱动例子在很多场合下代表一个基本的较好的设计。若是将中断驱动扩展为全双工是相当复杂的。这就是为什么我建议你忘记这个。

术语:

捕捉:从外界采集数据(“记录”和采集不同,记录意味着将存储在某地方,不是alsa API的一部分)。

回放:将数据发送到外界,以致外界可以听到声音。

全双工:捕捉和回放在同一个设备同一时间发生的状态。

xrun:一旦音频接口开始运转,它将一直运转,知道告诉它停止。设备将产生数据为计算机去使用或者计算机发送数据到外界。由于很多原因,你的可能跟不上设备的速度。对于回放来说,可能导致一种结果,音频设备需要的数据从计算机中,但是计算机没有及时发送给设备,而导致设备使用留在硬件缓冲区中的旧数据,这种情况叫做underrun。对于捕捉情况,设备产生了计算机可用的数据,但是没有空间去存储这些数据了,设备不得不重写硬件缓冲区的一部分,而被重写的这部分数据计算机还没有得到,这样数据就丢失了,这个叫做overrun。简单起见,我们使用xrun表示这两种情况。

pcm

Pulse Code Modulation(脉冲编码调制),这个术语描述的是一种方法,用来转换模拟信号为数字信号。这种方法被绝大数的计算机音频设备使用,并且ALSA API使用它对音频作为代替。

声道

帧:在单声道中,一个样本是一个单一的值,用来描述在一个采样点上音频信号的振幅。当我们谈论关于数字音频的时候,我们经常谈论这些数据,这些数据描述了在一个采样点上所有的声道。一集合的样本通常叫做帧,每个声道一个集合。我经常用帧数表示时间的流逝,这个大概和人们用样本测量的精度差不多,但是帧更精确。更重要的是,当我们谈论一个采样点上的描述所有声道的数据集合,他是一个单元才有意义。几乎每个ALSA API函数使用帧作为数据数量的测量单元。

交错模式:一种数据布局(格式安排)。在一个采样点上,每个声道上的采样数据连续的紧挨着另一个声道的采样数据。

非交错模式:一种数据布局。一个信道的所有样本数据存储在另外一个信道的数据之后。
采样时钟:一个时钟源,在 计算机采集或播放时标记时间。一些音频设备可以使你使用外部时钟源,不是字时钟就是自动同步时钟。所有的音频设备本省至少一个采样时钟源,典型的是晶体时钟。一些音频设备不允许时钟的频率被改变。一些时钟不是你期望的那样准确的频率运行。没有两个采样时钟被期望运行时有相同的精确的频率。如果你需要两个样本流去保持同步,他们必须使用同一个采样时钟

posted @ 2016-06-07 13:06  苍月代表我  阅读(1381)  评论(0编辑  收藏  举报