`

Linux音频设备驱动-3

 
阅读更多

Linux音频设备驱动-3

4/27/2010 11:44:06 PM
Linux音频设备驱动-3
2008-10-03 19:07
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 }
分享到:
评论

相关推荐

    华清远见驱动教程

    -第1章、设备驱动概述 ...-第17章、Linux音频设备驱动 -第18章、LCD设备驱动 -第19章、Flash设备驱动 -第20章、USB主机与设备驱动 -第21章、PCI设备驱动 -第22章、Linux设备驱动的调试 -第23章、Linux设备驱动的移植

    Linux音频设备驱动

    对linux的音频驱动讲解的非常到为,写的非常到好,这是大神级别到

    8810音频驱动linux-android。完整资料.zip

    在音频设备驱动中ASoC被分为Machine、Platform和Codec三大部分。 Codec部分:负责音频解码,这部分代码全然无平台无关(设备原厂提供) Platform部分:包括了平台的音频DMA和音频接口的配置和控制(I2S,PCM。AC97...

    Linux设备驱动详解第二版

    第17章 Linux音频设备驱动 388 第18章 LCD设备驱动 440 第19章 Flash设备驱动 479 第20章 USB主机与设备驱动 507 第21章 PCI设备驱动 547 第4篇 Linux设备驱动调试、移植 第22章 Linux设备驱动的...

    arm linux利用alsa驱动并使用usb音频设备

    主要介绍了arm linux利用alsa驱动并使用usb音频设备的相关资料,需要的朋友可以参考下

    Linux设备驱动程序开发详解

    字符设备、块设备、TTY设备、I2C设备、LCD设备、音频设备、USB设备、网络设备、PCI设备等Linux设备驱动的架构和框架中各个复杂数据架构和函数的关系,并讲解了Linux驱动开发的大量实例,使读者能够独立开发各类Linux...

    嵌入式Linux设备驱动开发详解

    本书特色:本书系统地介绍了嵌入式Linux设备驱动开发的相关知识和实例,主要包括: 数码管驱动程序;键盘驱动程序;A/D、D/A驱动程序;LCD驱动程序;触摸屏驱动程序;CAN总线驱动程序;I2C总线驱动程序;音频总线...

    linux设备驱动详解(宋宝华 高清 非影印版)

    字符设备、块设备、TTY设备、IC设备、LCD设备、音频设备、USB设备、网络设备、PCI设备等Linux设备驱动的架构和框架中各个复杂数据架构和函数的关系,并讲解了Linux驱动开发的大量实例,使读者能够独立开发各类Linux...

    精通LINUX设备驱动程序开发

     第16章 Linux无线设备驱动   第17章 存储技术设备   第18章 嵌入式Linux   第19章 用户空间的驱动程序   第20章 其他设备和驱动程序   第21章 调试设备驱动程序   第22章 维护与发布   第23...

    精通Linux设备驱动程序开发

    第16章 Linux无线设备驱动 第17章 存储技术设备 第18章 嵌入式Linux 第19章 用户空间的驱动程序 第20章 其他设备和驱动程序 第21章 调试设备驱动程序 第22章 维护与发布 第23章 结束语 附录A Linux汇编 ...

    Linux 蓝牙协议栈的USB+设备驱动

    3 蓝牙 USB 设备驱动 设备驱动程序在 Linux 内核中起着重要作用,它使某个 硬件能响应一个定义良好的内部编程接口。这些接口隐藏了 设备的工作细节,用户通过一组独立于特定驱动程序的标准 调用来操作设备。而将...

    《Linux 设备驱动开发详解》第一版第一次印刷勘误

    字符设备、块设备、TTY设备、IC设备、LCD设备、音频设备、USB设备、网络设备、PCI设备等Linux设备驱动的架构和框架中各个复杂数据架构和函数的关系,并讲解了Linux驱动开发的大量实例,使读者能够独立开发各类Linux...

    《精通Linux 设备驱动程序开发》.(Sreekrishnan).pdf

     本书是linux设备驱动程序开发领域的权威著作。全书基于2.6内核,不仅透彻讲解了基本概念和技术,更深入探讨了其他书没有涵盖或浅尝辄止的许多重要主题和关键难点,如pcmcia、i2c和usb等外部总线以及视频、音频、...

    精通linux设备驱动程序开发

    《精通Linux设备驱动程序开发》是Linux设备驱动程序开发领域的权威著作。全书基于2.6内核,不仅透彻讲解了基本概念和技术,更深人探讨了其他书没有涵盖或浅尝辄止的许多重要主题和关键难点,如PCMCIA、I2C和USB等...

    Linux设备驱动开发

    字符设备、块设备、TTY设备、I2C设备、LCD设备、音频设备、USB设备、网络设备、PCI设备等Linux设备驱动的架构和框架中各个复杂数据架构和函数的关系,并讲解了Linux驱动开发的大量实例,使读者能够独立开发各类Linux...

    Linux_alsa.zip_alsa_linux 音频驱动

    Linux alsa 音频设备驱动文件

    \精通Linux设备驱动程序开发

    《精通Linux设备驱动程序开发》是Linux设备驱动程序开发领域的权威著作。全书基于2.6内核,不仅透彻讲解了基本概念和技术,更深人探讨了其他书没有涵盖或浅尝辄止的许多重要主题和关键难点,如PCMCIA、I2C和USB等...

    嵌入式Linux设备驱动开发

    字符设备、块设备、TTY设备、I2C设备、LCD设备、音频设备、USB设备、网络设备、PCI设备等Linux设备驱动的架构和框架中各个复杂数据架构和函数的关系,并讲解了大量Linux驱动开发的大量实例,使读者能够独立开发各类...

Global site tag (gtag.js) - Google Analytics