Linux 音频ALSA技术说明--part3[转&学习]

转载:http://ivqinwei.blog.163.com/blog/static/110931542011722111421855/


3       ALSA Example

一个典型的声音程序使用PCM0000000000000的程序通常类似下面的伪代码:


打开回放或录音接口

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

while 有数据要被处理:

读PCM数据(录音)

或 写PCM数据(回放)

关闭接口

 



      设置参数,参数设置不当将会导致音频设备无法正常工作。在设置参数前,我们需要了解一下各个参数的含义以及一些基本概念。

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

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

桢(frame):桢记录了一个声音单元,其长度为样本长度与通道数的乘积。8*1                 8*2                16 *1               16*2

采样率(rate):每秒钟采样次数,该次数是针对桢而言。

周期(period):音频设备一次处理所需要的桢数,对于音频设备的数据访问以及音频数据的存储,都是以此为单位。

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





我们将在下文中看到一些可以工作的代码。我建议您在你的Linux系统上测试运行这些代码。查看输出并尝试修改推荐的代码。和本文相关的所有实例清单可以从FTP中获取:ftp.ssc.com/pub/lj/listings/issue126/6735.tgz




3.1   Example1. Display Some PCM Types and Formats.

[QW---例一、显示一些PCM类型和格式]

    #include <alsa/asoundlib.h>  
    int main() {  
        int val;  
            printf("ALSA library version: %s\n", SND_LIB_VERSION_STR);  //ALSA库的版本号
       

          printf("\nPCM stream types:\n");  //PCM流的类型
        for (val = 0; val <= SND_PCM_STREAM_LAST; val++)  
                 printf("  %s\n",  snd_pcm_stream_name((snd_pcm_stream_t)val));  


             printf("\nPCM access types:\n");  //PCM通道类型
        for (val = 0; val <= SND_PCM_ACCESS_LAST; val++)  
                printf("  %s\n",  snd_pcm_access_name((snd_pcm_access_t)val));  
       

         printf("\nPCM formats:\n");  //PCM格式
        for (val = 0; val <= SND_PCM_FORMAT_LAST; val++)  
                if (snd_pcm_format_name((snd_pcm_format_t)val)    != NULL)  
                    printf("  %s (%s)\n",  snd_pcm_format_name((snd_pcm_format_t)val),    
                                                                     snd_pcm_format_description( (snd_pcm_format_t)val));  
        

printf("\nPCM subformats:\n");  //PCM子模式
        for (val = 0; val <= SND_PCM_SUBFORMAT_LAST; val++)  
                printf("  %s (%s)\n",  snd_pcm_subformat_name(( snd_pcm_subformat_t)val),  
                         snd_pcm_subformat_description(( snd_pcm_subformat_t)val));  
       

printf("\nPCM states:\n");  //PCM状态
        for (val = 0; val <= SND_PCM_STATE_LAST; val++)  
            printf("  %s\n",  snd_pcm_state_name((snd_pcm_state_t)val));  
        return 0;  
    }

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

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

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

下面是该程序的Makefile:

    CC=gcc  
    TARGET=test  
    SRC=$(wildcard *.c)  
    OBJECT= ${SRC:.c=.o}  
    INCLUDES=-I/usr/include/alsa  
    LDFLAGS=-lasound  
    all:$(TARGET)  
    $(OBJECT):$(SRC)  
        $(CC) -c $(INCLUDES) $<  
    $(TARGET):$(OBJECT)  
        $(CC) -o $@ $< $(LDFLAGS)  
    .PHONY:clean  
    clean:  
        @rm -rf $(OBJECT) $(TARGET) *~   

[QW改写--乱七八糟:

    CC=gcc  
    TARGET=test  
    SRC=$(wildcard *.c)  
    OBJECT= ${SRC:.c=.o}  
    INCLUDES=-I/usr/include/alsa  
    LDFLAGS=-lasound  
    all:$(TARGET)  


    ${SRC:.c=.o} :$(wildcard *.c)   
        gcc -c 
-I/usr/include/alsa  $<  
    test:
${SRC:.c=.o}    
       gcc -o $@ $< $(LDFLAGS)  
    .PHONY:clean  
    clean:  
        @rm -rf $(OBJECT) $(TARGET) *~ 
  

----QW]
在电脑上运行,结果如下:

    ALSA library version: 1.0.22  
    PCM stream types:  
      PLAYBACK  
      CAPTURE  
    PCM access types:  
      MMAP_INTERLEAVED  
      MMAP_NONINTERLEAVED  
      MMAP_COMPLEX  
      RW_INTERLEAVED  
      RW_NONINTERLEAVED  
    PCM formats:  
      S8 (Signed 8 bit)  
      U8 (Unsigned 8 bit)  
      S16_LE (Signed 16 bit Little Endian)  
      S16_BE (Signed 16 bit Big Endian)  
      U16_LE (Unsigned 16 bit Little Endian)  
      U16_BE (Unsigned 16 bit Big Endian)  
      S24_LE (Signed 24 bit Little Endian)  
      S24_BE (Signed 24 bit Big Endian)  
      U24_LE (Unsigned 24 bit Little Endian)  
      U24_BE (Unsigned 24 bit Big Endian)  
      S32_LE (Signed 32 bit Little Endian)  
      S32_BE (Signed 32 bit Big Endian)  
      U32_LE (Unsigned 32 bit Little Endian)  
      U32_BE (Unsigned 32 bit Big Endian)  
      FLOAT_LE (Float 32 bit Little Endian)  
      FLOAT_BE (Float 32 bit Big Endian)  
      FLOAT64_LE (Float 64 bit Little Endian)  
      FLOAT64_BE (Float 64 bit Big Endian)  
      IEC958_SUBFRAME_LE (IEC-958 Little Endian)  
      IEC958_SUBFRAME_BE (IEC-958 Big Endian)  
      MU_LAW (Mu-Law)  
      A_LAW (A-Law)  
      IMA_ADPCM (Ima-ADPCM)  
      MPEG (MPEG)  
      GSM (GSM)  
      SPECIAL (Special)  
      S24_3LE (Signed 24 bit Little Endian in 3bytes)  
      S24_3BE (Signed 24 bit Big Endian in 3bytes)  
      U24_3LE (Unsigned 24 bit Little Endian in 3bytes)  
      U24_3BE (Unsigned 24 bit Big Endian in 3bytes)  
      S20_3LE (Signed 20 bit Little Endian in 3bytes)  
      S20_3BE (Signed 20 bit Big Endian in 3bytes)  
      U20_3LE (Unsigned 20 bit Little Endian in 3bytes)  
      U20_3BE (Unsigned 20 bit Big Endian in 3bytes)  
      S18_3LE (Signed 18 bit Little Endian in 3bytes)  
      S18_3BE (Signed 18 bit Big Endian in 3bytes)  
      U18_3LE (Unsigned 18 bit Little Endian in 3bytes)  
      U18_3BE (Unsigned 18 bit Big Endian in 3bytes)  
    PCM subformats:  
      STD (Standard)  
    PCM states:  
      OPEN  
      SETUP  
      PREPARED  
      RUNNING  
      XRUN  
      DRAINING  
      PAUSED  
      SUSPENDED  
      DISCONNECTED  







3.2   Example2. Opening PCM Device and Setting Parameters.

    /* 
    This example opens the default PCM device, sets some parameters, and then displays the value 
    of most of the hardware parameters. It does not  perform any sound playback or recording. 

  QW----  这个例子打开一个默认的PCM设备,设置一些参数,然后显示大部分硬件参数的值。它没有实现任何播放声音或录制声音。
    */  
    /* Use the newer ALSA API */  
    #define ALSA_PCM_NEW_HW_PARAMS_API  
    /* All of the ALSA library API is defined in this header   所有的API都定义在头文件里 */  
    

#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;  
        
        rc = snd_pcm_open(&handle, " hw:0,1", SND_PCM_STREAM_PLAYBACK, 0); /* Open PCM device for playback. QW---打开PCM设备以便播放声音*/  
        if (rc < 0) {  
            fprintf(stderr, "unable to open pcm device: %s\n", snd_strerror(rc));  
            exit(1);  
        }  
        
        snd_pcm_hw_params_alloca(&params);    /* Allocate a hardware parameters object.  QW--配一个硬件参数工程*/  
          
    snd_pcm_hw_params_any(handle, params);     /* Fill it in with default values.QW--都设定为默认值 */  
     /* Set the desired hardware parameters.  QW--定预期的硬件参数*/  
        snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); /* Interleaved modeQW--设置交叉模式*/  
          
    snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); /* Signed 16-bit little-endian format QW--有符号低字节16位模式*/  
          
    snd_pcm_hw_params_set_channels(handle, params, 2);  /* Two channels (stereo)QW--双声道,立体声*/  
        val = 44100;  
        snd_pcm_hw_params_set_rate_near(handle,  params, &val, &dir); /* 44100 bits/second sampling rate (CD quality) QW--设置采样率*/  
       

rc = snd_pcm_hw_params(handle, params); /* Write the parameters to the driver QW--写这些参数到驱动里边去*/  
        if (rc < 0) {  
            fprintf(stderr, "unable to set hw parameters: %s\n",  snd_strerror(rc));  
            exit(1);  
        }  
        /* Display information about the PCM interfaceQW---下边就是显示PCM接口的信息了 */  
        printf("PCM handle name = '%s'\n",  snd_pcm_name(handle));  
        printf("PCM state = %s\n",  snd_pcm_state_name(snd_pcm_state(handle)));  
        snd_pcm_hw_params_get_access(params,  (snd_pcm_access_t *) &val);  
        printf("access type = %s\n",  snd_pcm_access_name((snd_pcm_access_t)val));  
        snd_pcm_hw_params_get_format(params, &val);  
        printf("format = '%s' (%s)\n",  snd_pcm_format_name((snd_pcm_format_t)val),  
                                                          snd_pcm_format_description( (snd_pcm_format_t)val));  
        snd_pcm_hw_params_get_subformat(params,  (snd_pcm_subformat_t *)&val);  
        printf("subformat = '%s' (%s)\n",  snd_pcm_subformat_name((snd_pcm_subformat_t)val),  
                                                                snd_pcm_subformat_description( (snd_pcm_subformat_t)val));  
        snd_pcm_hw_params_get_channels(params, &val);  
        printf("channels = %d\n", val);  
        snd_pcm_hw_params_get_rate(params, &val, &dir);  
        printf("rate = %d bps\n", val);  
        snd_pcm_hw_params_get_period_time(params, &val, &dir);  
        printf("period time = %d us\n", val);  
        snd_pcm_hw_params_get_period_size(params, &frames, &dir);  
        printf("period size = %d frames\n", (int)frames);  
        snd_pcm_hw_params_get_buffer_time(params, &val, &dir);  
        printf("buffer time = %d us\n", val);  
        snd_pcm_hw_params_get_buffer_size(params, (snd_pcm_uframes_t *) &val);  
        printf("buffer size = %d frames\n", val);  
        snd_pcm_hw_params_get_periods(params, &val, &dir);  
        printf("periods per buffer = %d frames\n", val);  
        snd_pcm_hw_params_get_rate_numden(params,&val, &val2);  
        printf("exact rate = %d/%d bps\n", val, val2);  
        val = snd_pcm_hw_params_get_sbits(params);  
        printf("significant bits = %d\n", val);  
        snd_pcm_hw_params_get_tick_time(params, &val, &dir);  
        printf("tick time = %d us\n", val);  
        val = snd_pcm_hw_params_is_batch(params);  
        printf("is batch = %d\n", val);  
        val = snd_pcm_hw_params_is_block_transfer(params);  
        printf("is block transfer = %d\n", val);  
        val = snd_pcm_hw_params_is_double(params);  
        printf("is double = %d\n", val);  
        val = snd_pcm_hw_params_is_half_duplex(params);  
        printf("is half duplex = %d\n", val);  
        val = snd_pcm_hw_params_is_joint_duplex(params);  
        printf("is joint duplex = %d\n", val);  
        val = snd_pcm_hw_params_can_overrange(params);  
        printf("can overrange = %d\n", val);  
        val = snd_pcm_hw_params_can_mmap_sample_resolution(params);  
        printf("can mmap = %d\n", val);  
        val = snd_pcm_hw_params_can_pause(params);  
        printf("can pause = %d\n", val);  
        val = snd_pcm_hw_params_can_resume(params);  
        printf("can resume = %d\n", val);  
        val = snd_pcm_hw_params_can_sync_start(params);  
        printf("can sync start = %d\n", val);  
        snd_pcm_close(handle);  
        return 0;  
    }  

 

 example2打开hw:0,1的PCM设备,设置一些硬件参数并且打印出最常用的硬件参数值。它并不做任何回放或录音的操作。snd_pcm_open打开hw:0,1的PCM设备并设置访问模式为PLAYBACK。这个函数返回一个句柄,这个句柄保存在第一个函数参数中。该句柄会在随后的函数中用到。像其它函数一样,这个函数返回一个整数。如果返回值小于0,则代码函数调用出错。如果出错,我们用snd_errstr打开错误信息并退出。

为了设置音频流的硬件参数,我们需要分配一个类型为snd_pcm_hw_param的变量。分配用到函数宏snd_pcm_hw_params_alloca。下一步,我们使用函数snd_pcm_hw_params_any来初始化这个变量,传递先前打开的PCM流句柄。

接下来,我们调用API来设置我们所需的硬件参数。这些函数需要三个参数:PCM流句柄,参数类型,参数值。我们设置流为交错模式,16位的样本大小,2个信道,44100bps的采样率。对于采样率而言,声音硬件并不一定就精确地支持我们所定的采样率,但是我们可以使用函数snd_pcm_hw_params_set_rate_near来设置最接近我们指定的采样率的采样率。

          其实只有当我们调用函数snd_pcm_hw_params后,硬件参数才会起作用。

程序的剩余部分获得并打印一些PCM流参数,包括周期和缓冲区大小。结果可能会因为声音硬件的不同而不同。

运行该程序后,做实验,改动一些代码,设置不同的硬件参数然后观察结果的变化。

在电脑上运行,结果如下:

    PCM handle name = 'hw:0,1'  
    PCM state = PREPARED  
    access type = RW_INTERLEAVED  
    format = 'S16_LE' (Signed 16 bit Little Endian)  
    subformat = 'STD' (Standard)  
    channels = 2  
    rate = 44100 bps  
    period time = 362 us  
    period size = 16 frames  
    buffer time = 362 us  
    buffer size = 16384 frames  
    periods per buffer = 1024 frames  
    exact rate = 1445100000/32768 bps  
    significant bits = 16  
    tick time = 0 us  
    is batch = 0  
    is block transfer = 1  
    is double = 0  
    is half duplex = 0  
    is joint duplex = 0  
    can overrange = 0  
    can mmap = 1  
    can pause = 1  
    can resume = 0  
    can sync start = 1  



[2011年8月22日15时37分25秒看到这个地方]


3.3   Example3.Simple Sound Playback.

    /*Example 3 - Simple sound playback 
    This example reads standard from input and writes to the default PCM device for 5 seconds of data. 
    */  
    /* Use the newer ALSA API */  
    #define ALSA_PCM_NEW_HW_PARAMS_API  
    #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;  
        
        rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);  /* Open PCM device for playback. */  
        if (rc < 0) {  
                fprintf(stderr, "unable to open pcm device: %s\n",  snd_strerror(rc));  
                exit(1);  
        }  
        snd_pcm_hw_params_alloca(&params);      /* Allocate a hardware parameters object. */  
        snd_pcm_hw_params_any(handle, params);       /* Fill it in with default values. */  
        /* Set the desired hardware parameters. */  
        snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);   /* Interleaved mode */  
        snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);  /* Signed 16-bit little-endian format */  
        snd_pcm_hw_params_set_channels(handle, params, 2);  /* Two channels (stereo) */  
        val = 44100;  
        snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);  /* 44100 bits/second sampling rate (CD quality) */  
        frames = 32;  
        snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);  /* Set period size to 32 frames. */  
        rc = snd_pcm_hw_params(handle, params);  /* Write the parameters to the driver */  
        if (rc < 0) {  
                fprintf(stderr,  "unable to set hw parameters: %s\n",  snd_strerror(rc));  
                exit(1);  
        }  
            snd_pcm_hw_params_get_period_size(params, &frames, &dir); /* Use a buffer large enough to hold one period */  
        size = frames * 4; /* 2 bytes/sample, 2 channels */  
        buffer = (char *) malloc(size);  
       
        snd_pcm_hw_params_get_period_time(params,  &val, &dir);  /* We want to loop for 5 seconds */  
        loops = 5000000 / val;                                                                  /* 5 seconds in microseconds divided by period time */  
        while (loops > 0) {  
                loops--;  
                rc = read(0, buffer, size);         //fd为0,表示为标准输入  
                if (rc == 0) {  
                    fprintf(stderr, "end of file on input\n");  
                    break;  
                } else if (rc != size) {  
                    fprintf(stderr, "short read: read %d bytes\n", rc);  
                }  
                rc = snd_pcm_writei(handle, buffer, frames);  
                if (rc == -EPIPE) {  
                        fprintf(stderr, "underrun occurred\n");/* EPIPE means underrun */  
                    snd_pcm_prepare(handle);  
                } else if (rc < 0) {  
                    fprintf(stderr, "error from writei: %s\n", snd_strerror(rc));  
                }  else if (rc != (int)frames) {  
                    fprintf(stderr,  "short write, write %d frames\n", rc);  
                }  
           }  
        snd_pcm_drain(handle);  
        snd_pcm_close(handle);  
        free(buffer);  
            return 0;  
    }  
    example3扩展了之前的示例。向声卡中写入了一些声音样本以实现声音回放。在这个例子中,我们从标准输入中读取数据,每个周期读取足够多的数据,然后将它们写入到声卡中,直到5秒钟的数据全部传输完毕。

     这个程序的开始处和之前的版本一样---打开PCM设备、设置硬件参数。我们使用由ALSA自己选择的周期大小,申请该大小的缓冲区来存储样本。然后我们找出周期时间,这样我们就能计算出本程序为了能够播放5秒钟,需要多少个周期。  

     在处理数据的循环中,我们从标准输入中读入数据,并往缓冲区中填充一个周期的样本。然后检查并处理错误,这些错误可能是由到达文件结尾,或读取的数据长度与我期望的数据长度不一致导致的。

     我们调用snd_pcm_writei来发送数据。它操作起来很像内核的写系统调用,只是这里的大小参数是以帧来计算的。我们检查其返回代码值。返回值为EPIPE表明发生了underrun,使得PCM音频流进入到XRUN状态并停止处理数据。从该状态中恢复过来的标准方法是调用snd_pcm_prepare函数,把PCM流置于PREPARED状态,这样下次我们向该PCM流中数据时,它就能重新开始处理数据。如果我们得到的错误码不是EPIPE,我们把错误码打印出来,然后继续。最后,如果写入的帧数不是我们期望的,则打印出错误消息。

     这个程序一直循环,直到5秒钟的帧全部传输完,或者输入流读到文件结尾。然后我们调用snd_pcm_drain把所有挂起没有传输完的声音样本传输完全,最后关闭该音频流,释放之前动态分配的缓冲区,退出。

     我们可以看到这个程序没有什么用,除非标准输入被重定向到了其它的文件。尝试用设备/dev/urandom来运行这个程序,该设备产生随机数据:

 ./test </dev/urandom

随机数据会产生5秒钟的白色噪声。然后,尝试把标准输入重定向到设备/dev/null和/dev/zero上,并比较结果。改变一些参数,例如采样率和数据格式,然后查看结果的变化。


3.4   Example4. Simple Sound Recording.

    /* 
    Example 4 - Simple sound recording   
    This example reads from the default PCM device 
    and writes to standard output for 5 seconds of data. 
    */  
    /* Use the newer ALSA API */  
    #define ALSA_PCM_NEW_HW_PARAMS_API  
    #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;  
              
        rc = snd_pcm_open(&handle, "default",  SND_PCM_STREAM_CAPTURE, 0); /* Open PCM device for recording (capture). */  
        if (rc < 0) {  
            fprintf(stderr,  "unable to open pcm device: %s\n",  snd_strerror(rc));  
            exit(1);  
        }  
        
        snd_pcm_hw_params_alloca(&params);      /* Allocate a hardware parameters object. */  
          
        snd_pcm_hw_params_any(handle, params);          /* Fill it in with default values. */  
        /* Set the desired hardware parameters. */  
        snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);    /* Interleaved mode */  
       
        snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);    /* Signed 16-bit little-endian format */  
        
        snd_pcm_hw_params_set_channels(handle, params, 2);  /* Two channels (stereo) */  
        
        val = 44100;  
        snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);    /* 44100 bits/second sampling rate (CD quality) */  
          
        frames = 32;  
        snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); /* Set period size to 32 frames. */  
       
        rc = snd_pcm_hw_params(handle, params);     /* Write the parameters to the driver */  
        if (rc < 0) {  
            fprintf(stderr,"unable to set hw parameters: %s\n",  snd_strerror(rc));  
            exit(1);  
        }  
        snd_pcm_hw_params_get_period_size(params,  &frames, &dir);      /* Use a buffer large enough to hold one period */  
        size = frames * 4; /* 2 bytes/sample, 2 channels */  
        buffer = (char *) malloc(size);  
        snd_pcm_hw_params_get_period_time(params,  &val, &dir);     /* We want to loop for 5 seconds */  
        loops = 5000000 / val;  
        while (loops > 0) {  
            loops--;  
            rc = snd_pcm_readi(handle, buffer, frames);  
            if (rc == -EPIPE) {           /* EPIPE means overrun */  
                        fprintf(stderr, "overrun occurred\n");  
                snd_pcm_prepare(handle);  
            } else if (rc < 0) {  
                fprintf(stderr, "error from read: %s\n", snd_strerror(rc));  
            } else if (rc != (int)frames) {  
                fprintf(stderr, "short read, read %d frames\n", rc);  
            }  
        rc = write(1, buffer, size);  
        if (rc != size)  
        fprintf(stderr, "short write: wrote %d bytes\n", rc);  
        }  
      snd_pcm_drain(handle);  
      snd_pcm_close(handle);  
      free(buffer);  
      return 0;  
    } 

     example4类似于example3中的程序,除了这里的程序是做声音的抓取(录音)。当打开PCM设备时我们指定打开模式为SND_PCM_STREAM_CPATURE。在主循环中,我们调用snd_pcm_readi从声卡中读取数据,并把它们写入到标准输出。同样地,我们检查是否有overrun,如果存在,用与前例中相同的方式处理。

     运行example4的程序将录制将近5秒钟的声音数据,并把它们发送到标准输出。你也可以重定向到某个文件。如果你有一个麦克风连接到你的声卡,可以使用某个混音程序(mixer)设置录音源和级别。同样地,你也可以运行一个CD播放器程序并把录音源设成CD。尝试运行程序4并把输出定向到某个文件,然后运行程序3播放该文件里的声音数据:

./listing4>sound.raw

./listing3<sound.raw

如果你的声卡支持全双工,你可以通过管道把两个程序连接起来,这样就可以从声卡中听到录制的声音:

./listing4 | ./listing3

同样地,您可以做实验,看看采样率和样本格式的变化会产生什么影响。

3.5   高级特性

      在前面的例子中,PCM流是以阻塞模式操作的,也就是说,直到数据已经传送完,PCM接口调用才会返回。在事件驱动的交互式程序中,这样会长时间阻塞应用程序,通常是不能接受的。ALSA支持以非阻塞模式打开音频流,这样读写函数调用后立即返回。如果数据传输被挂起,调用不能被处理,ALSA就是返回一个EBUSY的错误码。

许多图形应用程序使用回调来处理事件。ALSA支持以异步的方式打开一个PCM音频流。这使得当某个周期的样本数据被传输完后,某个已注册的回调函数将会调用。

      这里用到的snd_pcm_readi和snd_pcm_writei调用和Linux下的读写系统调用类似。字母i表示处理的帧是交错式(interleaved)的。ALSA中存在非交互模式的对应的函数。Linux下的许多设备也支持mmap系统调用,这个调用将设备内存映射到主内存,这样数据就可以用指针来维护。ALSA也运行以mmap模式打开一个PCM信道,这允许有效的零拷贝(zero copy)方式访问声音数据。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux ALSA-Lib库是用于读取和处理音频的开源库。它提供了一套API,可以让开发者通过 C/C++ 编程语言访问 Linux 系统中的音频设备。 ALSA-Lib 可以实现多种音频设备的读写,包括内置音频硬件,外部 USB 音频设备以及蓝牙音频ALSA-Lib 提供了一个叫做alsa-lib.h的头文件,这个头文件包含了常用的 ALSA-Lib API 函数。开发者可以根据具体需求来选择合适的函数,最常用的是snd_pcm_open()、snd_pcm_hw_params_set_xxx()、snd_pcm_writei()和snd_pcm_close(),这些函数分别用于打开、设置参数、写数据和关闭音频设备。 ALSA-Lib 提供的多种API函数使得开发者可以对音频进行多种高级操作。比如,开发者可以通过snd_pcm_drop()中止当前播放操作,通过snd_pcm_pause()暂停播放,通过snd_pcm_prepare()准备播放,还可以通过调用snd_pcm_avail_update()获取当前音频设备的缓冲区状态。 读取音频数据可以通过snd_pcm_readi()函数实现,这个函数会一次性从设备中读取指定数量的音频采样,并将其存储在一个指定的缓冲区中。开发者还可以选择使用snd_pcm_mmap_readi()和snd_pcm_mmap_begin()来读取音频采样,这两个函数可以实现更高效的读取。 在开发 Linux 音频应用程序时,ALSA-Lib 是非常重要的组件。通过掌握 ALSA-Lib 的 API 函数,开发者可以实现快速、高效地读取和处理音频数据。因此,熟悉 ALSA-Lib 是 Linux 音频开发工程师的必备技能之一。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值