Linux音訊裝置驅動-3

來源:互聯網
上載者:User

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.