Linux音频设备驱动-3【转】

转:http://hi.baidu.com/geyangshun/blog/item/8e397f1e3840c9f21ad57639.html

4、put()函数
put()用于从用户空间写入值,如果值被改变,该函数返回1,否则返回0;如果发生错误,该函数返回1个错误码。代码清单17.22给出了1个put()函数的范例。
代码清单17.22 snd_ctl_elem_info结构体中put()函数范例
1 static int snd_xxxctl_put(struct snd_kcontrol *kcontrol, struct
2    snd_ctl_elem_value *ucontrol)
3 {
4    //从snd_kcontrol获得xxxchip指针
5    struct xxxchip *chip = snd_kcontrol_chip(kcontrol);
6    int changed = 0;//缺省返回值为0
7    //值被改变
8    if (chip->current_value != ucontrol->value.integer.value[0])
9    {
10     change_current_value(chip, ucontrol->value.integer.value[0]);
11     changed = 1;//返回值为1
12   }
13   return changed;
14 }
对于get()和put()函数而言,如果control有多于1个元素,即count>1,则每个元素都需要被返回或写入。
5、构造control
当所有事情准备好后,我们需要创建1个control,调用snd_ctl_add()和snd_ctl_new1()这2个函数来完成,这2个函数的原型为:
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol);

struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
      void *private_data);
snd_ctl_new1()函数用于创建1个snd_kcontrol并返回其指针,snd_ctl_add()函数用于将创建的snd_kcontrol添加到对应的card中。
6、变更通知
如果驱动中需要在中断服务程序中改变或更新1个control,可以调用snd_ctl_notify()函数,此函数原型为:
void snd_ctl_notify(struct snd_card *card, unsigned int mask, struct snd_ctl_elem_id *id);
该函数的第2个参数为事件掩码(event-mask),第3个参数为该通知的control元素id指针。
例如,如下语句定义的事件掩码SNDRV_CTL_EVENT_MASK_VALUE意味着control值的改变被通知:
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, id_pointer);
17.4.4 AC97 API接口
ALSA AC97编解码层被很好地定义,利用它,驱动工程师只需编写少量底层的控制函数。
1、AC97实例构造
为了创建1个AC97实例,首先需要调用snd_ac97_bus()函数构建AC97总线及其操作,这个函数的原型为:
int snd_ac97_bus(struct snd_card *card, int num, struct snd_ac97_bus_ops *ops,
   void *private_data, struct snd_ac97_bus **rbus);
该函数的第3个参数ops是1个snd_ac97_bus_ops结构体,其定义如代码清单17.23。
代码清单17.23 snd_ac97_bus_ops结构体
1 struct snd_ac97_bus_ops
2 {
3    void(*reset)(struct snd_ac97 *ac97); //复位函数
4    //写入函数
5    void(*write)(struct snd_ac97 *ac97, unsigned short reg, unsigned short val);
6    //读取函数
7    unsigned short(*read)(struct snd_ac97 *ac97, unsigned short reg);
8    void(*wait)(struct snd_ac97 *ac97);
9    void(*init)(struct snd_ac97 *ac97);
10 };
接下来,调用snd_ac97_mixer()函数注册混音器,这个函数的原型为:
int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, struct snd_ac97 **rac97);
代码清单17.24演示了AC97实例的创建过程。
代码清单17.24 AC97实例的创建过程范例
1 struct snd_ac97_bus *bus;
2 //AC97总线操作
3 static struct snd_ac97_bus_ops ops =
4 {
5    .write = snd_mychip_ac97_write,
6    .read = snd_mychip_ac97_read,
7 };
8 //AC97总线与操作创建
9 snd_ac97_bus(card, 0, &ops, NULL, &bus);
10 //AC97模板
11 struct snd_ac97_template ac97;
12 int err;
13 memset(&ac97, 0, sizeof(ac97));
14 ac97.private_data = chip;//私有数据
15 //注册混音器
16 snd_ac97_mixer(bus, &ac97, &chip->ac97);
上述代码第1行的snd_ac97_bus结构体指针bus的指针被传入第9行的snd_ac97_bus()函数并被赋值,chip->ac97的指针被传入第16行的snd_ac97_mixer()并被赋值,chip->ac97将成员新创建AC97实例的指针。
如果1个声卡上包含多个编解码器,这种情况下,需要多次调用snd_ac97_mixer()并对snd_ac97的num成员(编解码器序号)赋予相应的序号。驱动中可以为不同的编解码器编写不同的snd_ac97_bus_ops成员函数中,或者只是在相同的一套成员函数中通过ac97.num获得序号后再区分进行具体的操作。
2、snd_ac97_bus_ops成员函数
snd_ac97_bus_ops结构体中的read()和write()成员函数完成底层的硬件访问,reset()函数用于复位编解码器,wait()函数用于编解码器标准初始化过程中的特定等待,如果芯片要求额外的等待时间,应实现这个函数,init()用于完成编解码器附加的初始化。代码清单17.25给出了read()和write()函数的范例。
代码清单17.25 snd_ac97_bus_ops结构体中read()和write()函数范例
1 static unsigned short snd_xxxchip_ac97_read(struct snd_ac97 *ac97, unsigned
2    short reg)
3 {
4    struct xxxchip *chip = ac97->private_data;
5    ...
6    return the_register_value; //返回寄存器值
7 }
8
9 static void snd_xxxchip_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
10   unsigned short val)
11 {
12   struct xxxchip *chip = ac97->private_data;
13   ...
14   // 将被给的寄存器值写入codec
15 }
3、修改寄存器
如果需要在驱动中访问编解码器,可使用如下函数:
void snd_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);

int snd_ac97_update(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);

int snd_ac97_update_bits(struct snd_ac97 *ac97, unsigned short reg, unsigned short mask, unsigned short value);

unsigned short snd_ac97_read(struct snd_ac97 *ac97, unsigned short reg);
snd_ac97_update()与void snd_ac97_write()的区别在于前者在值已经设置的情况下不会再设置,而后者则会再写一次。snd_ac97_update_bits()用于更新寄存器的某些位,由mask决定。
除此之外,还有1个函数可用于设置采样率:
int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate);
这个函数的第2个参数reg可以是AC97_PCM_MIC_ADC_RATE、AC97_PCM_FRONT_DAC_RATE、 AC97_PCM_LR_ADC_RATE和AC97_SPDIF,对于AC97_SPDIF而言,寄存器并非真地被改变了,只是相应的IEC958状态位将被更新。
4、时钟调整
在一些芯片上,编解码器的时钟不是48000而是使用PCI时钟以节省1个晶体,在这种情况下,我们应该改变bus->clock为相应的值,例如intel8x0和es1968包含时钟的自动测量函数。
5、proc文件
ALSA AC97接口会创建如/proc/asound/card0/codec97#0/ac97#0-0和ac97#0-0+regs这样的proc文件,通过这些文件可以察看编解码器目前的状态和寄存器。
如果1个chip上有多个codecs,可多次调用snd_ac97_mixer()。
17.4.5 ALSA用户空间编程
ALSA驱动的声卡在用户空间不宜直接使用文件接口,而应使用alsa-lib,代码清单17.26给出了基于ALSA音频驱动的最简单的放音应用程序。
代码清单17.26 ALSA用户空间放音程序
1 #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;   //PCM设备句柄
11   snd_pcm_hw_params_t *hw_params; //硬件信息和PCM流配置
12   //打开PCM,最后1个参数为0意味着标准配置
13   if ((err = snd_pcm_open(&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)
14     ) < 0)
15   {
16     fprintf(stderr, "cannot open audio device %s (%s)/n", argv[1], snd_strerror
17       (err));
18     exit(1);
19   }
20   //分配snd_pcm_hw_params_t结构体
21   if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
22   {
23     fprintf(stderr, "cannot allocate hardware parameter structure (%s)/n",
24       snd_strerror(err));
25     exit(1);
26   }
27   //初始化hw_params
28   if ((err = snd_pcm_hw_params_any(playback_handle, hw_params)) < 0)
29   {
30     fprintf(stderr, "cannot initialize hardware parameter structure (%s)/n",
31       snd_strerror(err));
32     exit(1);
33   }
34   //初始化访问权限
35   if ((err = snd_pcm_hw_params_set_access(playback_handle, hw_params,
36     SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
37   {
38     fprintf(stderr, "cannot set access type (%s)/n", snd_strerror(err));
39     exit(1);
40   }
41   //初始化采样格式
42   if ((err = snd_pcm_hw_params_set_format(playback_handle, hw_params,
43     SND_PCM_FORMAT_S16_LE)) < 0)
44   {
45     fprintf(stderr, "cannot set sample format (%s)/n", snd_strerror(err));
46     exit(1);
47   }
48   //设置采样率,如果硬件不支持我们设置的采样率,将使用最接近的
49   if ((err = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, 44100,
50     0)) < 0)
51   {
52     fprintf(stderr, "cannot set sample rate (%s)/n", snd_strerror(err));
53     exit(1);
54   }
55   //设置通道数量
56   if ((err = snd_pcm_hw_params_set_channels(playback_handle, hw_params, 2)) < 0)
57   {
58     fprintf(stderr, "cannot set channel count (%s)/n", snd_strerror(err));
59     exit(1);
60   }
61   //设置hw_params
62   if ((err = snd_pcm_hw_params(playback_handle, hw_params)) < 0)
63   {
64     fprintf(stderr, "cannot set parameters (%s)/n", snd_strerror(err));
65     exit(1);
66   }
67   //释放分配的snd_pcm_hw_params_t结构体
68   snd_pcm_hw_params_free(hw_params);
69   //完成硬件参数设置,使设备准备好
70   if ((err = snd_pcm_prepare(playback_handle)) < 0)
71   {
72     fprintf(stderr, "cannot prepare audio interface for use (%s)/n",
73       snd_strerror(err));
74     exit(1);
75   }
76
77   for (i = 0; i < 10; ++i)
78   {
79     //写音频数据到PCM设备
80     if ((err = snd_pcm_writei(playback_handle, buf, 128)) != 128)
81     {
82       fprintf(stderr, "write to audio interface failed (%s)/n", snd_strerror
83         (err));
84       exit(1);
85     }
86   }
87   //关闭PCM设备句柄
88   snd_pcm_close(playback_handle);
89   exit(0);
90 }
由上述代码可以看出,ALSA用户空间编程的流程与17.3.4节给出的OSS驱动用户空间编程的流程基本是一致的,都经过了“打开――设置参数――读写音频数据”的过程,不同在于OSS打开的是设备文件,设置参数使用的是Linux ioctl()系统调用,读写音频数据使用的是Linux read()、write()文件API,而ALSA则全部使用alsa-lib中的API。
把上述代码第80行的snd_pcm_writei()函数替换为snd_pcm_readi()就编程了1个最简单的录音程序。
代码清单17.27的程序打开1个音频接口,配置它为立体声、16位、44.1khz采样和基于interleave的读写。它阻塞等待直接接口准备好接收放音数据,这时候将数据拷贝到缓冲区。这种设计方法使得程序很容易移植到类似JACK、LADSPA、Coreaudio、VST等callback机制驱动的系统。
代码清单17.27 ALSA用户空间放音程序(基于“中断”)
1   #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 playback_callback(snd_pcm_sframes_t nframes)
11 {
12    int err;
13    printf("playback callback called with %u frames/n", nframes);
14    /* 填充缓冲区 */
15    if ((err = snd_pcm_writei(playback_handle, buf, nframes)) < 0)
16    {
17      fprintf(stderr, "write failed (%s)/n", snd_strerror(err));
18    }
19
20    return err;
21 }
22
23 main(int argc, char *argv[])
24 {
25
26    snd_pcm_hw_params_t *hw_params;
27    snd_pcm_sw_params_t *sw_params;
28    snd_pcm_sframes_t frames_to_deliver;
29    int nfds;
30    int err;
31    struct pollfd *pfds;
32
33    if ((err = snd_pcm_open(&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)
34      ) < 0)
35    {
36      fprintf(stderr, "cannot open audio device %s (%s)/n", argv[1], snd_strerror
37        (err));
38      exit(1);
39    }
40
41    if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
42    {
43      fprintf(stderr, "cannot allocate hardware parameter structure (%s)/n",
44        snd_strerror(err));
45      exit(1);
46    }
47
48    if ((err = snd_pcm_hw_params_any(playback_handle, hw_params)) < 0)
49    {
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,
56      SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
57    {
58      fprintf(stderr, "cannot set access type (%s)/n", snd_strerror(err));
59      exit(1);
60    }
61
62    if ((err = snd_pcm_hw_params_set_format(playback_handle, hw_params,
63      SND_PCM_FORMAT_S16_LE)) < 0)
64    {
65      fprintf(stderr, "cannot set sample format (%s)/n", snd_strerror(err));
66      exit(1);
67    }
68
69    if ((err = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, 44100,
70      0)) < 0)
71    {
72      fprintf(stderr, "cannot set sample rate (%s)/n", snd_strerror(err));
73      exit(1);
74    }
75
76    if ((err = snd_pcm_hw_params_set_channels(playback_handle, hw_params, 2)) < 0)
77    {
78      fprintf(stderr, "cannot set channel count (%s)/n", snd_strerror(err));
79      exit(1);
80    }
81
82    if ((err = snd_pcm_hw_params(playback_handle, hw_params)) < 0)
83    {
84      fprintf(stderr, "cannot set parameters (%s)/n", snd_strerror(err));
85      exit(1);
86    }
87
88    snd_pcm_hw_params_free(hw_params);
89
90    /* 告诉ALSA当4096个以上帧可以传递时唤醒我们 */
91    if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0)
92    {
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    {
99      fprintf(stderr, "cannot initialize software parameters structure (%s)/n",
100       snd_strerror(err));
101     exit(1);
102   }
103   /* 设置4096帧传递一次数据 */
104   if ((err = snd_pcm_sw_params_set_avail_min(playback_handle, sw_params, 4096))
105     < 0)
106   {
107     fprintf(stderr, "cannot set minimum available count (%s)/n", snd_strerror
108       (err));
109     exit(1);
110   }
111   /* 一旦有数据就开始播放 */
112   if ((err = snd_pcm_sw_params_set_start_threshold(playback_handle, sw_params,
113     0U)) < 0)
114   {
115     fprintf(stderr, "cannot set start mode (%s)/n", snd_strerror(err));
116     exit(1);
117   }
118   if ((err = snd_pcm_sw_params(playback_handle, sw_params)) < 0)
119   {
120     fprintf(stderr, "cannot set software parameters (%s)/n", snd_strerror(err));
121     exit(1);
122   }
123
124   /* 每4096帧接口将中断内核,ALSA将很快唤醒本程序 */
125
126   if ((err = snd_pcm_prepare(playback_handle)) < 0)
127   {
128     fprintf(stderr, "cannot prepare audio interface for use (%s)/n",
129       snd_strerror(err));
130     exit(1);
131   }
132
133   while (1)
134   {
135
136     /* 等待,直到接口准备好传递数据,或者1秒超时发生 */
137     if ((err = snd_pcm_wait(playback_handle, 1000)) < 0)
138     {
139       fprintf(stderr, "poll failed (%s)/n", strerror(errno));
140       break;
141     }
142
143     /* 查出有多少空间可放置playback数据 */
144     if ((frames_to_deliver = snd_pcm_avail_update(playback_handle)) < 0)
145     {
146       if (frames_to_deliver == - EPIPE)
147       {
148         fprintf(stderr, "an xrun occured/n");
149         break;
150       }
151       else
152       {
153         fprintf(stderr, "unknown ALSA avail update return value (%d)/n",
154           frames_to_deliver);
155         break;
156       }
157     }
158
159     frames_to_deliver = frames_to_deliver > 4096 ? 4096 : frames_to_deliver;
160
161     /* 传递数据 */
162     if (playback_callback(frames_to_deliver) != frames_to_deliver)
163     {
164       fprintf(stderr, "playback callback failed/n");
165       break;
166     }
167   }
168
169   snd_pcm_close(playback_handle);
170   exit(0);
171 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值