要素のイベント
音量調節アプリなどの場合、他のアプリで音量が変更された時、その通知を受けて、自身のアプリで音量値を変更したいという場合があるでしょう。
ALSA では、カードの要素が変更された時、通知を受けることができます。
ALSA では、カードの要素が変更された時、通知を受けることができます。
イベントを待つ
イベントを待つ方法としては、以下の3通りがあります。
他の処理と並行して行うなら、非同期ハンドラを使うのが簡単です。
ただ、非同期ハンドラの場合は、どのタイミングでハンドラ関数が来るかわからないので、プログラム内で特定の処理を行っている時は、ALSA のイベント処理を行わないなどの、同期処理が必要になる場合があります。
GUI のメインループなどと一緒に使うなら、poll() を使うことで、GUI・ALSA・その他のイベントなどを、まとめて待つことができます。
- snd_ctl_wait() で、イベントが来るまで待つ。
- poll() で、イベントが来るまで待つ。
- 非同期ハンドラをセットする。イベントが来たら関数が実行される。
他の処理と並行して行うなら、非同期ハンドラを使うのが簡単です。
ただ、非同期ハンドラの場合は、どのタイミングでハンドラ関数が来るかわからないので、プログラム内で特定の処理を行っている時は、ALSA のイベント処理を行わないなどの、同期処理が必要になる場合があります。
GUI のメインループなどと一緒に使うなら、poll() を使うことで、GUI・ALSA・その他のイベントなどを、まとめて待つことができます。
開く時のモード
snd_ctl_open() でサウンドカードを開く時、mode 引数で SND_CTL_ASYNC (非同期通知) を指定できますが、非同期ハンドラを使う際に、SND_CTL_ASYNC が必要になるわけではありません。
むしろ、SND_CTL_ASYNC をセットすると、ハンドラが実行されません。
非同期ハンドラをセットすると、ALSA lib 内部で、自動的に非同期モードが設定されるので、snd_ctl_open() 時の mode で、SND_CTL_ASYNC を指定する必要はありません。
むしろ、SND_CTL_ASYNC をセットすると、ハンドラが実行されません。
非同期ハンドラをセットすると、ALSA lib 内部で、自動的に非同期モードが設定されるので、snd_ctl_open() 時の mode で、SND_CTL_ASYNC を指定する必要はありません。
イベント
イベントの通知を登録
まずは、イベントが受け取れるように、登録を行う必要があります。
subscribe は、0 で登録解除、1 で登録、-1 で登録されているか確認。
登録が解除されていると、イベントは来ません。
イベントを受け取りたい場合は、poll() や非同期ハンドラなどの方法に関わらず、共通で実行する必要があります。
int snd_ctl_subscribe_events(snd_ctl_t *ctl, int subscribe);
subscribe は、0 で登録解除、1 で登録、-1 で登録されているか確認。
登録が解除されていると、イベントは来ません。
イベントを受け取りたい場合は、poll() や非同期ハンドラなどの方法に関わらず、共通で実行する必要があります。
イベントの読み込み
int snd_ctl_read(snd_ctl_t *ctl, snd_ctl_event_t *event);
イベントが来た場合に、起こったイベントを snd_ctl_event_t に読み込みます。
戻り値は、読み込まれたイベントの数 (0 か 1)。負の値でエラーコード。
snd_ctl_event_t
snd_ctl_event_t は、あらかじめ確保しておく必要があります。
//サイズ取得 size_t snd_ctl_event_sizeof(void); //確保 int snd_ctl_event_malloc(snd_ctl_event_t **ptr); //解放 void snd_ctl_event_free(snd_ctl_event_t *obj);
イベントタイプの取得
snd_ctl_event_type_t snd_ctl_event_get_type(const snd_ctl_event_t *obj);
読み込まれたイベントの、イベントタイプを返します。
現在は、SND_CTL_EVENT_ELEM (要素関連イベント) しかありません。
イベントマスクの取得
unsigned int snd_ctl_event_elem_get_mask(const snd_ctl_event_t *obj);
要素に対して、何が行われたかを示すイベントマスクを取得します。
SND_CTL_EVENT_MASK_REMOVE | 要素が削除された |
---|---|
SND_CTL_EVENT_MASK_VALUE | 値の変更 |
SND_CTL_EVENT_MASK_INFO | 情報の変更 |
SND_CTL_EVENT_MASK_ADD | 要素の追加 |
SND_CTL_EVENT_MASK_TLV | TLVの値が変更 |
SND_CTL_EVENT_MASK_REMOVE の扱いに注意してください。
この値は、(~0U) になっており、0 のビット反転なので、すべてのビットが ON の状態です。
最初にこの値を判定し、値が異なれば、他のフラグをチェックします。
イベントの要素の識別子を取得
void snd_ctl_event_elem_get_id(const snd_ctl_event_t *obj, snd_ctl_elem_id_t *ptr); unsigned int snd_ctl_event_elem_get_numid(const snd_ctl_event_t *obj); snd_ctl_elem_iface_t snd_ctl_event_elem_get_interface(const snd_ctl_event_t *obj); unsigned int snd_ctl_event_elem_get_device(const snd_ctl_event_t *obj); unsigned int snd_ctl_event_elem_get_subdevice(const snd_ctl_event_t *obj); const char *snd_ctl_event_elem_get_name(const snd_ctl_event_t *obj); unsigned int snd_ctl_event_elem_get_index(const snd_ctl_event_t *obj);
snd_ctl_event_elem_get_* で、イベントが起きた要素の、識別子の情報を取得します。
非同期ハンドラ
イベントが来たという通知を受けるために、まずは、非同期ハンドラを使う方法を紹介します。
イベントが来た時に実行される非同期ハンドラを追加し、コールバック関数をセットします。
不要になった非同期ハンドラを削除します。
非同期ハンドラに関連付けられている情報を取得します。
非同期ハンドラの追加
int snd_async_add_ctl_handler(snd_async_handler_t **handler, snd_ctl_t *ctl, snd_async_callback_t callback, void *private_data); //コールバック関数 typedef void (*snd_async_callback_t)(snd_async_handler_t *handler);
イベントが来た時に実行される非同期ハンドラを追加し、コールバック関数をセットします。
handler | 非同期ハンドラの、ハンドラが返る |
---|---|
callback | コールバック関数。 snd_async_callback_t は、他の非同期ハンドラでも使われる、共通の関数型です。 |
private_data | 非同期ハンドラに関連付ける、プライベートデータ |
非同期ハンドラの削除
int snd_async_del_handler(snd_async_handler_t *handler);
不要になった非同期ハンドラを削除します。
非同期ハンドラの情報取得
//snd_ctl_t を取得 snd_ctl_t *snd_async_handler_get_ctl(snd_async_handler_t *handler); //プライベートデータを取得 void *snd_async_handler_get_callback_private(snd_async_handler_t *handler);
非同期ハンドラに関連付けられている情報を取得します。
プログラム (1)
非同期ハンドラでイベントを待ち、イベントの要素の名前と、イベントマスクを出力します。
Enter キーを押すと、終了します。
pavucontrol で出力の音量を変更すると、このように、音量の要素の値が変更された、という通知が来ます。
変更された値は、snd_ctl_elem_value_t に読み込む必要があります。
Enter キーを押すと、終了します。
$ cc -o 07a-async 07a-async -lasound
#include <stdio.h> #include <alsa/asoundlib.h> snd_ctl_event_t *event; unsigned int count = 0; void _callback(snd_async_handler_t *handler) { snd_ctl_t *ctl; unsigned int mask; ctl = snd_async_handler_get_ctl(handler); if(snd_ctl_read(ctl, event) <= 0) return; mask = snd_ctl_event_elem_get_mask(event); printf("-- %u --\n", count++); printf("name: '%s'\n", snd_ctl_event_elem_get_name(event)); printf("mask: 0x%X ", mask); if(mask == SND_CTL_EVENT_MASK_REMOVE) printf("REMOVE"); else { if(mask & SND_CTL_EVENT_MASK_VALUE) printf("VALUE "); if(mask & SND_CTL_EVENT_MASK_INFO) printf("INFO "); if(mask & SND_CTL_EVENT_MASK_ADD) printf("ADD "); if(mask & SND_CTL_EVENT_MASK_TLV) printf("TLV "); } printf("\n"); } int main(int argc,char **argv) { snd_ctl_t *ctl; snd_async_handler_t *async; if(snd_ctl_open(&ctl, (argc >= 2)? argv[1]: "default", 0)) return 1; //非同期ハンドラ snd_async_add_ctl_handler(&async, ctl, _callback, NULL); snd_ctl_subscribe_events(ctl, 1); snd_ctl_event_malloc(&event); //Enter が押されるまで待つ getchar(); // snd_ctl_subscribe_events(ctl, 0); snd_async_del_handler(async); snd_ctl_event_free(event); snd_ctl_close(ctl); return 0; }
実行結果
-- 0 -- name: 'Master Playback Volume' mask: 0x1 VALUE -- 1 -- name: 'PCM Playback Volume' mask: 0x1 VALUE -- 2 -- name: 'Master Playback Volume' mask: 0x1 VALUE -- 3 -- name: 'Master Playback Volume' mask: 0x1 VALUE -- 4 -- name: 'Master Playback Volume' mask: 0x1 VALUE
pavucontrol で出力の音量を変更すると、このように、音量の要素の値が変更された、という通知が来ます。
変更された値は、snd_ctl_elem_value_t に読み込む必要があります。
poll
次に、poll() を使って、イベントを待つ方法を紹介します。
poll() を行うために、struct pollfd の情報が必要なので、ALSA イベント用の pollfd の情報を取得した後、poll() を行い、何かが起こるまで待ちます。
ALSA 用の struct pollfd の数を取得します。
基本的に1が返ります。
space 個の pfds の配列に、ALSA 用の情報をセットします。
戻り値で、実際にセットされた数が返ります。
poll() の実行後、戻り値で1以上が返った時(pollfd 内の1つ以上のファイルディスクリプタで、何らかのイベントが起こった時)、nfds 個の pollfd の配列内から、ALSA 用の情報を参照し、pollfd の revents の値を返します。
pollfd の revents は、その fd で実際に起こったイベントのフラグです。
POLLIN フラグが ON の場合、読み込みが可能になった、つまり、イベントが読み込みできるということなので、その後、snd_ctl_read() を実行して、実際にイベントを読み込みます。
poll() を行うために、struct pollfd の情報が必要なので、ALSA イベント用の pollfd の情報を取得した後、poll() を行い、何かが起こるまで待ちます。
//<poll.h> struct pollfd { int fd; short int events; short int revents; };
必要な pollfd の数を取得
int snd_ctl_poll_descriptors_count(snd_ctl_t *ctl);
ALSA 用の struct pollfd の数を取得します。
基本的に1が返ります。
pollfd の情報をセット
int snd_ctl_poll_descriptors(snd_ctl_t *ctl, struct pollfd *pfds, unsigned int space);
space 個の pfds の配列に、ALSA 用の情報をセットします。
戻り値で、実際にセットされた数が返ります。
pollfd から、起こったイベントを取得
int snd_ctl_poll_descriptors_revents(snd_ctl_t *ctl, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
poll() の実行後、戻り値で1以上が返った時(pollfd 内の1つ以上のファイルディスクリプタで、何らかのイベントが起こった時)、nfds 個の pollfd の配列内から、ALSA 用の情報を参照し、pollfd の revents の値を返します。
pollfd の revents は、その fd で実際に起こったイベントのフラグです。
POLLIN フラグが ON の場合、読み込みが可能になった、つまり、イベントが読み込みできるということなので、その後、snd_ctl_read() を実行して、実際にイベントを読み込みます。
プログラム (2)
poll() でイベントを待ち、イベントの内容を出力します。
Ctrl+C で終了します。
Ctrl+C で終了します。
$ cc -o 07b-poll 07b-poll -lasound
#include <stdlib.h> #include <stdio.h> #include <signal.h> #include <poll.h> #include <errno.h> #include <alsa/asoundlib.h> unsigned int count = 0; static void _put_event(snd_ctl_t *ctl,snd_ctl_event_t *event) { unsigned int mask; if(snd_ctl_read(ctl, event) <= 0) return; mask = snd_ctl_event_elem_get_mask(event); printf("-- %u --\n", count++); printf("name: '%s'\n", snd_ctl_event_elem_get_name(event)); printf("mask: 0x%X ", mask); if(mask == SND_CTL_EVENT_MASK_REMOVE) printf("REMOVE"); else { if(mask & SND_CTL_EVENT_MASK_VALUE) printf("VALUE "); if(mask & SND_CTL_EVENT_MASK_INFO) printf("INFO "); if(mask & SND_CTL_EVENT_MASK_ADD) printf("ADD "); if(mask & SND_CTL_EVENT_MASK_TLV) printf("TLV "); } printf("\n"); } static void _sig_handler(int sig) { } /* シグナルのセット */ static void _set_signal(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = _sig_handler; sigaction(SIGINT, &sa, NULL); } int main(int argc,char **argv) { int pollcnt,ret,i; snd_ctl_t *ctl; snd_ctl_event_t *event; struct pollfd *pfd; unsigned short rev; if(snd_ctl_open(&ctl, (argc >= 2)? argv[1]: "default", 0)) return 1; //シグナル _set_signal(); //イベント snd_ctl_subscribe_events(ctl, 1); snd_ctl_event_malloc(&event); //pollfd pollcnt = snd_ctl_poll_descriptors_count(ctl); pfd = (struct pollfd *)malloc(sizeof(struct pollfd) * pollcnt); snd_ctl_poll_descriptors(ctl, pfd, pollcnt); for(i = 0; i < pollcnt; i++) printf("fd:%d, events:0x%X\n", pfd[i].fd, pfd[i].events); //poll while(1) { ret = poll(pfd, pollcnt, -1); if(ret < 0) { //シグナルによる割り込み if(errno == EINTR) break; } else if(ret > 0) { ret = snd_ctl_poll_descriptors_revents(ctl, pfd, pollcnt, &rev); if(ret == 0 && (rev & POLLIN)) _put_event(ctl, event); } } free(pfd); printf("finish!\n"); // snd_ctl_subscribe_events(ctl, 0); snd_ctl_event_free(event); snd_ctl_close(ctl); return 0; }
解説
Ctrl+C が押された時に、プログラムが強制終了されないようにするため、SIGINT のシグナルハンドラを、何もしない関数に変更しています。
poll() 中にシグナルが発生した場合、戻り値で負の値が返り、errno に EINTR がセットされているので、それをループ終了の条件とします。
poll() 中にシグナルが発生した場合、戻り値で負の値が返り、errno に EINTR がセットされているので、それをループ終了の条件とします。