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