alsa用户空间编程

来源:互联网 发布:中国企业数据库 编辑:程序博客网 时间:2024/06/06 01:27

ALSA简介

alsa由以下几个部分组成:
1) driver内核驱动程序,包括硬件相关的和一些公共代码,了解可参考《writing an ALSA Driver》
2) library用户空间的函数库,需要包含头文件asoundlib.h,链接共享库libasound.so
3) lib-plugins提供了两个插件,一个用jack模拟alsa接口,一个用oss来模拟alsa接口。alsa可以作为jack的后端,jack也可以作为alsa的后端。alsa可以模拟oss,oss也可以模拟alsa。
4) utilities一些基于alsa的命令行小程序,可以作为示例代码参考
5) tools一些小工具,比如vxloader可以用来加载firmware
6) OSS compat与oss兼容的代码

目前alsa内核提供给用户空间的接口有:
1) information interface(/proc/asound)
2) control interface(/dev/snd/controlCx)
3) Mixer interfacce(/dev/snd/mixerCxDx)
4) PCM interface(/dev/sndpcmCxDx)
5) Raw MIDI interface(/dev/snd/midiCxDx)
6) Sequencer interface(/dev/snd/seq)
7) Timer interface(/dev/snd/timer)
和OSS类似,也是以文件的方式提供的,但这些接口是给alsalib使用的,而不是给应用程序使用的。应用程序应该使用alsalib,或者更高级的接口,比如jack提供的接口。

开发基于ALSA的应用程序时,不要直接使用alsa-driver提供的接口,而应该使用alsalib的函数。alsalib提供了丰富的功能,估计有好几百个函数,幸好常用的并不多。ALSA的howto提供一个简单的播放和录音的示例,值得参考。


Device naming

The library API works with logical device names rather than device files. The device names can be real hardware devices or plugins.
Hardware devices use the format hw:i,j, where i is the card number and j is the device on that card. The first sound device is hw:0,0. The alias default refers to the first sound device and is used in all of the examples in this article.
Plugins use other unique names; plughw:, for example, is a plugin that provides access to the hardware device but provides features, such as sampling rate conversion, in software for hardware that does not directly support it. The dmix and dshare plugins allow you to downmix several streams and split a single stream dynamically among different applications.
If you use the “plughw” interface, you need not care much about the sound hardware. If your soundcard does not support the sample rate or sample format you specify, your data will be automatically converted. This also applies to the access type and the number of channels. With the “hw” interface, you have to check whether your hardware supports the configuration you would like to use.


声音缓存和数据传输

每个声卡都有一个硬件缓存区来保存记录下来的样本。当缓存区足够满时,声卡将产生一个中断,内核声卡驱动使用DMA将样本传送到应用程序缓存区,类似的,对于播放,任何应用程序使用DMA将自己的缓存区数据传送到声卡的硬件缓存区中。
这个硬件缓存区是ring buffer,意味着当数据写到buffer的end时又会从buffer的start处开始写。在hardware buffer以及application buffer都有指针指向当前位置。
buffer的大小可以通过ALSA库函数调用来控制,缓存区很大,一次传输操作可能会导致不可接受的延迟,为了解决这个问题,ALSA将buffer拆分成一系列periods;ALSA以period为单元来传送数据,每个period存储一些frames;每个frame是一次采样的数据。对于立体声设备,一个frame会包含左右声道的采样。如下图:
这里写图片描述
这里左右声道的数据保存在一个frame中,叫做interleaved mode。而non-interleaved mode则是一个channel的数据保存在另一个channel数据的后面。


Overrun and underrun

在录音时,如果应用程序没有及时的从ring buffer中读取数据,ring buffer将被新的数据覆盖,这种数据的丢失叫overrun。
在播放时,如果应用程序没有及时的将数据写入到ring buffer,ring buffer将会饿死,称为underrun。
ALSA documentation称这两种情况为XRUN。


Display Some PCM Types and Formats

/* list1.c * http://www.linuxjournal.com/article/6735?page=0,1 * 20150911 * display some PCM types and formats */#include "alsa/asoundlib.h"int main(){        int val;        snd_pcm_t *handle;        printf("ALSA library version: %s\n", SND_LIB_VERSION_STR);        printf("\n***PCM stream types***:\n");        for (val = 0;val <= SND_PCM_STREAM_LAST;val++)                printf("%s\n", snd_pcm_stream_name((snd_pcm_stream_t)val));        printf("\n***PCM access types***:\n");        for (val = 0; val <= SND_PCM_ACCESS_LAST; val++)                printf("%s\n", snd_pcm_access_name((snd_pcm_access_t)val));        printf("\n***PCM format***:\n");        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("\n***PCM subformat***:\n");        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("\n***PCM states***:\n");        for (val = 0;val < SND_PCM_STATE_LAST;val++)                printf("%s\n", snd_pcm_state_name((snd_pcm_state_t)val));        return 0;}

Makefile内容:

INCLUDE\_DIR = /home/xxx/usr_lib/alsa-lib/includeLIB\_DIR = /home/xxx/usr_lib/alsa-lib/liblist1:list1.c        gcc -o $@ $^ -I$(INCLUDE_DIR) -L$(LIB_DIR) -lasound 

Opening PCM Device and Setting Parameters

/* * list2.c * http://www.linuxjournal.com/article/6735?page=0,1 * 20150911 * opening PCM device and setting parameters *//* use the newer ALSA API */#define ALSA_PCM_NEW_HW_PARAMS_API/* All of the ALSA library API is defined in this header */#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, "default", SND_PCM_STREAM_PLAYBACK, 0);    if (rc < 0) {        fprintf(stderr, "unable to open pcm device: %s\n", snd_strerror(rc));        exit(1);    }    /* Alloc a hardware parameters object */    snd_pcm_hw_params_alloca(&params);    /* Fill it with default values */    snd_pcm_hw_params_any(handle, params);    /* set the disired hardware parameters */    snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);    /* signed 16 bit little-endian format */    snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);    /* two channels (stereo) */    snd_pcm_hw_params_set_channels(handle, params, 2);    val = 44100;    /* 44100 bits/second sampling rate (CD quality) */    snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);    /* write params to the driver */    rc = snd_pcm_hw_params(handle, params);    if (rc < 0) {        fprintf(stderr, "unable to set hw params: %s\n", snd_strerror(rc));        exit(1);    }    /* display information about PCM interface */    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, (snd_pcm_format_t *)&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, (snd_pcm_uframes_t *)&frames, &dir);    printf("period size=%d frames\n", (int)frames);//add (int) to avoid compile warning    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 *)&frames);    printf("buffer size=%d frames\n", (int)frames);//add (int) to avoid compile warning    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 jonit 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;}

change the device name from “default” to “hw:0,0” or “plughw:0,0” and see whether the results change.


Simple Sound Playback

/* * list3.c * read from input(STDIN) and writes to the default PCM device * for 5 seconds of data ./list3 < /dev/urandom *//* use the newer ALSA_API */#define ALSA_PCM_NEW_HW_PARAMS_API#include <alsa/asoundlib.h>#include <alsa/pcm.h>int main(){    int rc;    snd_pcm_t *handle;    snd_pcm_hw_params_t *params;    unsigned int val;    int dir;    snd_pcm_uframes_t frames;    int size;    long loops;    char *buffer;    int fd;    rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);    if (rc < 0) {        fprintf(stderr, "unable to open PCM device: %s\n",                snd_strerror(rc));        exit(1);    }    /* alloc hardware params object */    snd_pcm_hw_params_alloca(&params);    /* fill it with default values */    snd_pcm_hw_params_any(handle, params);    /* interleaved mode */    snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);    /* signed 16 bit little ending format */    snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);    /* two channels (stereo) */    snd_pcm_hw_params_set_channels(handle, params, 2);    /* 44100 bits/second sampling rate (CD quality) */    val = 44100;    snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);    /* set period size t 32 frames */    frames = 32;        snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);    /* write params to the driver */    rc = snd_pcm_hw_params(handle, params);    if (rc < 0) {        fprintf(stderr, "unable to set hw params: %s\n",                snd_strerror(rc));        exit(1);    }    /* use buffer large enough to hold one period */    snd_pcm_hw_params_get_period_size(params, &frames, &dir);    size = frames * 4; //2 bytes/sample, 2 channels    buffer = (char *)malloc(size);    /* we want to loop for 5 seconds */    snd_pcm_hw_params_get_period_time(params, &val, &dir);    /* 5 second in micro seconds divided by period time */    loops = 5000000 / val;    while (loops > 0) {        loops--;        rc = read(0, buffer, size);        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) {            /* -EPIPE means underrun */            fprintf(stderr, "underrun occured\n");            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);        }    }    /* allow any pending sound samples to be transferred */    snd_pcm_drain(handle);    snd_pcm_close(handle);    free(buffer);    return 0;   }

这个例子中从标准输入读取数据,然后将数据写到sound card中
./list3 < /dev/urandom 可以听到耳机口输出white noise


Simple Sound Recording

/* * list4.c * this example reads from the default PCM device and * writes to standard output(STDOUT) for 5 seconds of data mobile play music to PC record input, then do ./list4 > sound.raw PC playback output, do ./list3 < sound.raw *//* use the newer ALSA API */#define ALSA_PCM_NEW_HW_PARAMS_API#include <alsa/asoundlib.h>int main(){    int rc;    snd_pcm_t *handle;    snd_pcm_hw_params_t *params;    unsigned int val;    int dir;    snd_pcm_uframes_t frames;    char *buffer;    long loops;    int size;    /* open PCM device for recording (capture) */    rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);    if (rc < 0) {        fprintf(stderr, "unable to open pcm device: %s\n", snd_strerror(rc));        exit(1);    }    /* alloc a hardware params object */    snd_pcm_hw_params_alloca(&params);    /* fill it with default values */    snd_pcm_hw_params_any(handle, params);    /* interleaved mode */    snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);    /* signed 16 bit little ending format */    snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);    /* two channels */    snd_pcm_hw_params_set_channels(handle, params, 2);    /* 44100 bits/second sampling rate (CD quality) */    val = 44100;    snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);    /* set period size to 32 frames */    frames = 32;    snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);    rc = snd_pcm_hw_params(handle, params);    if (rc < 0) {        fprintf(stderr, "unable to set hw params: %s\n", snd_strerror(rc));        exit(1);    }    /* use a buffer large enough to hold one period */    snd_pcm_hw_params_get_period_size(params, &frames, &dir);    size = frames * 4;    buffer = (char *)malloc(size);    /* we want to loop for 5 seconds */    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) {            /* EPIPE means overrun */            fprintf(stderr, "overrun occured\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;}

Advanced Features

前面几个例子中,PCM stream工作在blocking mode,也就是说,直到数据传输完了调用才返回,这种情况可能会让应用程序等待很长一段时间。ALSA允许以nonblocking mode打开stream,这样,read和write调用会立刻返回,如果数据传输被挂起而且调用得不到处理,那么ALSA会返回EBUSY。

许多图形界面应用程序使用callback来处理events,ALSA支持以asynchronous mode打开PCM stream,通过注册callback函数,当一个period的数据传输完成,callback函数会被调用。

snd_pcm_readi和snd_pcm_writei与系统调用read和write类似,”i”代表frames是interleaved,”n”代表frames是non-interleaved。
for non-interleaved access, each period contains first all sample data for the first channel followed by sample data for the second channel and so on.

ALSA也支持以mmap mode打开PCM channel。

最后,感谢这篇文章,让我们开始进入alsa用户空间编程的世界。
《Introduction to Sound Programming with ALSA》
原文:http://www.linuxjournal.com/article/6735?page=0,1

1 0
原创粉丝点击