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 } |