Initial git import
[sxemacs] / src / media / sound-esd.c
1 /* sound-esd.c - play a sound over ESD
2
3    Copyright (C) 2006 Sebastian Freundt
4
5 This file is part of SXEmacs
6
7 SXEmacs is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 SXEmacs is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
19
20
21 /* Synched up with: Not in FSF. */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include "lisp.h"
28
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <stdio.h>
32 #include <errno.h>
33 #include <string.h>
34
35 #include "media.h"
36 #include "sound-esd.h"
37
38 /* the name given to ESD - I think this should identify ourselves */
39 #define ESD_NAME "SXEmacs"
40
41 #define MYSELF ADRIVER_ESD
42
43 Lisp_Object Qesd;
44
45 #define __ESD_DEBUG__(args...)          fprintf(stderr, "ESD " args)
46 #ifndef ESD_DEBUG_FLAG
47 #define ESD_DEBUG(args...)
48 #else
49 #define ESD_DEBUG(args...)              __ESD_DEBUG__(args)
50 #endif
51 #define ESD_DEBUG_HW(args...)           ESD_DEBUG("[hardware]: " args)
52 #define ESD_DEBUG_S(args...)            ESD_DEBUG("[stream]: " args)
53 #define ESD_DEBUG_COE(args...)          ESD_DEBUG("[coerce]: " args)
54 #define ESD_DEBUG_AJ(args...)           ESD_DEBUG("[audio-job]: " args)
55 #define ESD_CRITICAL(args...)           __ESD_DEBUG__("CRITICAL: " args)
56
57 \f
58 DECLARE_AUDIO_DEVICE_SIMPLE_METHS(sound_esd);
59 DEFINE_AUDIO_DEVICE_SIMPLE(sound_esd);
60
61 \f
62 static Lisp_Object
63 sound_esd_mark(ad_device_data *devdata)
64 {
65         sound_esd_data_t *sed = devdata;
66
67         if (sed == NULL)
68                 return Qnil;
69
70         mark_object(sed->server);
71
72         return Qnil;
73 }
74
75 static void
76 sound_esd_print(Lisp_Object device, Lisp_Object pcfun, int ef)
77 {
78         sound_esd_data_t *sed = NULL;
79
80         sed = get_audio_device_data(device);
81         /* cannot use incomplete or corrupt audio devices */
82         if (XAUDIO_DEVICE_DRIVER(device) != MYSELF || sed == NULL) {
83                 write_c_string(" VOID", pcfun);
84                 /* now that we are here, mark AO device as dead */
85                 XAUDIO_DEVICE_STATE(device) = ASTATE_DEAD;
86                 return;
87         }
88
89         /* info about the connected output plugin */
90         write_c_string(" :device ", pcfun);
91         if (NILP(sed->server))
92                 write_c_string("#default", pcfun);
93         else
94                 print_internal(sed->server, pcfun, ef);
95
96         return;
97 }
98
99 \f
100 static int
101 sound_esd_close_device(sound_esd_data_t *sed)
102 {
103         sed->lock = 0;
104
105         ESD_DEBUG_HW("socket closed\n");
106
107         return 0;
108 }
109
110 static int
111 sound_esd_init_socket(sound_esd_data_t *sed, sound_esd_aj_data_t *sesd)
112 {
113         /* convert header information into ESD flags */
114         sesd->flags = 0;
115         sesd->flags = ESD_STREAM | ESD_PLAY;
116         sesd->flags |= ESD_BITS16;
117         sesd->samplewidth = 16;
118         sesd->channels = sesd->mtap->channels;
119         sesd->framesize = sesd->channels * sizeof(int16_t);
120         sesd->samplerate = sesd->mtap->samplerate;
121
122         switch (sesd->mtap->channels) {
123         case 1:
124                 sesd->flags |= ESD_MONO;
125                 break;
126         case 2:
127                 sesd->flags |= ESD_STEREO;
128                 break;
129         case 5:
130                 sesd->flags |= ESD_STEREO;
131                 ADD_MEDIA_SAMPLE_EFFECT(
132                         sesd->coe_chain, sesd->coe_ch_cnt,
133                         MEDIA_SAMPLE_EFFECT(sxe_mse_5ch_to_2ch), NULL);
134                 break;
135         default:
136                 message(GETTEXT("audio-esd: "
137                                 "%d channels - only 1 or 2 supported"),
138                         sesd->mtap->channels);
139                 return -1;
140         }
141
142         return 1;
143 }
144
145 static ad_device_data *
146 sound_esd_create(Lisp_Object esd_options)
147 {
148         /* result */
149         sound_esd_data_t *sed = NULL;
150         /* option keywords */
151         Lisp_Object opt_server;
152         Lisp_Object opt_port;
153
154         /* parse options */
155         opt_server = Fplist_get(esd_options, intern(":server"), Qnil);
156         if (!NILP(opt_server) && !STRINGP(opt_server)) {
157                 wrong_type_argument(Qstringp, opt_server);
158                 return NULL;
159         }
160
161         opt_port = Fplist_get(esd_options, intern(":port"), Qnil);
162         if (!NILP(opt_port) && !NATNUMP(opt_port)) {
163                 wrong_type_argument(Qnatnump, opt_port);
164                 return NULL;
165         }
166
167         /* initialise and fill */
168         sed = xnew_and_zero(sound_esd_data_t);
169         sed->server = opt_server;
170
171         return (ad_device_data*)sed;
172 }
173
174 static void
175 sound_esd_finish(ad_device_data *data)
176 {
177         sound_esd_data_t *sed = data;
178
179         sed->lock = 1;
180         sed->keep_open = 0;
181         sound_esd_close_device(sed);
182
183         ESD_DEBUG("audio-device finished.\n");
184
185         return;
186 }
187 \f
188 #ifdef EF_USE_ASYNEQ
189 static inline void
190 sound_esd_change_volume(audio_job_t aj, audio_job_event_args_t args)
191 {
192         SXE_MUTEX_LOCK(&aj->mtx);
193         aj->volume = args->volume_args;
194         SXE_MUTEX_UNLOCK(&aj->mtx);
195 }
196
197 static inline void
198 sound_esd_change_rate(audio_job_t aj, audio_job_event_args_t args)
199 {
200         SXE_MUTEX_LOCK(&aj->mtx);
201         aj->ratetrafo = args->rate_args;
202         SXE_MUTEX_UNLOCK(&aj->mtx);
203 }
204
205 static inline void
206 sound_esd_change_state(audio_job_t aj, audio_job_event_args_t args)
207 {
208         SXE_MUTEX_LOCK(&aj->mtx);
209         switch (args->state_args) {
210         case aj_pause:
211                 ESD_DEBUG_AJ("->pause state\n");
212                 aj->play_state = MTPSTATE_PAUSE;
213                 break;
214         case aj_resume:
215                 ESD_DEBUG_AJ("->resume state\n");
216                 aj->play_state = MTPSTATE_RUN;
217                 break;
218         case aj_start:
219                 ESD_DEBUG_AJ("->start state\n");
220                 break;
221         case aj_stop:
222                 ESD_DEBUG_AJ("->stop state\n");
223                 aj->play_state = MTPSTATE_STOP;
224                 break;
225         case no_audio_job_change_states:
226         default:
227                 ESD_DEBUG_AJ("->unknown state\n");
228                 break;
229         }
230         SXE_MUTEX_UNLOCK(&aj->mtx);
231 }
232
233 static inline void
234 sound_esd_handle_aj_events(audio_job_t aj)
235         __attribute__((always_inline));
236 static inline void
237 sound_esd_handle_aj_events(audio_job_t aj)
238 {
239         sound_esd_aj_data_t *sasd;
240         audio_job_event_t ev = NULL;
241
242 #if 0
243         assert(audio_job_queue(aj));
244 #endif
245
246         SXE_MUTEX_LOCK(&aj->mtx);
247         sasd = audio_job_device_data(aj);
248         if ((ev = eq_noseeum_dequeue(audio_job_queue(aj))) == NULL) {
249                 SXE_MUTEX_UNLOCK(&aj->mtx);
250                 return;
251         }
252         SXE_MUTEX_UNLOCK(&aj->mtx);
253
254         ESD_DEBUG_AJ("Event 0x%lx\n", (long unsigned int)ev);
255         switch (audio_job_event_kind(ev)) {
256         case aj_change_state:
257                 ESD_DEBUG_AJ("change state event\n");
258                 sound_esd_change_state(aj, &audio_job_event_args(ev));
259                 break;
260         case aj_change_volume:
261                 ESD_DEBUG_AJ("change volume event\n");
262                 sound_esd_change_volume(aj, &audio_job_event_args(ev));
263                 break;
264         case aj_change_rate:
265                 ESD_DEBUG_AJ("change rate event\n");
266                 sound_esd_change_rate(aj, &audio_job_event_args(ev));
267                 break;
268
269         case no_audio_job_event_kinds:
270         default:
271                 ESD_CRITICAL("unknown event\n");
272                 break;
273         }
274         free_audio_job_event(ev);
275 }
276 #endif  /* EF_USE_ASYNEQ */
277 \f
278 static int
279 sound_esd_play(audio_job_t aj)
280 {
281         /* stream stuff */
282         Lisp_Media_Stream *ms;
283         media_substream *mss;
284         /* thread stuff */
285         media_thread_play_state mtp;
286         /* device stuff */
287         Lisp_Object device;
288         Lisp_Audio_Device *lad = NULL;
289         sound_esd_data_t *sed = NULL;
290         /* buffering */
291         int i;
292         size_t len, tmplen;
293         sxe_media_sample_t *tmpbuf;
294         /* esd socket stuff */
295         ssize_t wrtn;
296         int resolution;
297         char *hoststr = NULL;
298         /* subthread stuff */
299         sound_esd_aj_data_t _sesd, *sesd = &_sesd;
300         sxe_mse_volume_args _volargs, *volargs = &_volargs;
301         sxe_mse_rerate_args _rrargs, *rrargs = &_rrargs;
302         /* cache stuff */
303         int alloced_myself = 0;
304
305         /* unpack the media thread */
306         SOUND_UNPACK_MT(aj, device, ms, mss, lad, sed, sesd->mtap);
307
308         /* this lock is totally unnecessary */
309         sed->lock = 1;
310
311         /* init the sesd */
312         sesd->samplerate = sesd->samplewidth = sesd->channels = 0;
313         sesd->framesize = 0;
314         sesd->coe_ch_cnt = 0;
315
316         /* init the socket */
317         sound_esd_init_socket(sed, sesd);
318
319         /* hm, use the given options */
320         hoststr = (NILP(sed->server) ? NULL : (char*)XSTRING_DATA(sed->server));
321         sesd->sock = esd_play_stream(
322                 sesd->flags, sesd->samplerate, hoststr, "SXEmacs");
323         if (sesd->sock < 0)
324                 return 0;
325
326         /* rewind the stream */
327         media_stream_meth(ms, rewind)(mss);
328
329         /* the volume effect */
330         ADD_MEDIA_SAMPLE_EFFECT(
331                 sesd->coe_chain, sesd->coe_ch_cnt,
332                 MEDIA_SAMPLE_EFFECT(sxe_mse_volume), volargs);
333         volargs->num_channels = sesd->channels;
334
335         /* the rerate effect */
336         ADD_MEDIA_SAMPLE_EFFECT(
337                 sesd->coe_chain, sesd->coe_ch_cnt,
338                 MEDIA_SAMPLE_EFFECT(sxe_mse_rerate), rrargs);
339         rrargs->num_channels = sesd->channels;
340         rrargs->srcrate = rrargs->tgtrate = 1;
341
342         ESD_DEBUG_COE("have %d coerce functions in my chain.\n",
343                       sesd->coe_ch_cnt);
344
345         XAUDIO_DEVICE_STATE(device) = ASTATE_ALIVE;
346
347         /* play chunks of the stream */
348         SXE_MUTEX_LOCK(&aj->mtx);
349         if (aj->buffer_alloc_size < SOUND_MAX_AUDIO_FRAME_SIZE) {
350                 alloced_myself = 1;
351                 aj->buffer = xmalloc_atomic(SOUND_MAX_AUDIO_FRAME_SIZE);
352                 aj->buffer_alloc_size = SOUND_MAX_AUDIO_FRAME_SIZE;
353         }
354         SXE_MUTEX_UNLOCK(&aj->mtx);
355         tmpbuf = (sxe_media_sample_t*)aj->buffer;
356         resolution = (sesd->mtap->samplerate * MTPSTATE_REACT_TIME) / 1000000;
357
358         while (aj->play_state != MTPSTATE_STOP) {
359
360 #ifdef EF_USE_ASYNEQ
361                 /* handle job events */
362                 if (audio_job_queue(aj)) {
363                         sound_esd_handle_aj_events(aj);
364                 }
365 #endif
366
367                 SXE_MUTEX_LOCK(&aj->mtx);
368                 mtp = aj->play_state;
369                 SXE_MUTEX_UNLOCK(&aj->mtx);
370                 switch (mtp) {
371                 case MTPSTATE_RUN:
372                         len = media_stream_meth(ms, read)(
373                                 mss, aj->buffer, resolution);
374                         if (!len) {
375                                 ESD_DEBUG_S("finished\n");
376                                 aj->play_state = MTPSTATE_STOP;
377                                 break;
378                         }
379
380                         /* set up the volume args */
381                         volargs->volume[0] = volargs->volume[1] =
382                                 aj->volume;
383                         /* set up the rerate args */
384                         rrargs->tweak = aj->ratetrafo;
385
386                         /* coerce the stuff,
387                            tmplen becomes the number of samples */
388                         tmplen = sesd->channels*len;
389                         for (i = 0; i < sesd->coe_ch_cnt; i++) {
390                                 ESD_DEBUG_COE("calling coerce "
391                                               "%d on b:0x%x l:%d\n",
392                                               i, (unsigned int)tmpbuf, tmplen);
393                                 tmplen = CALL_MEDIA_SAMPLE_EFFECT(
394                                         sesd->coe_chain, i,
395                                         tmpbuf, tmpbuf, tmplen);
396                         }
397
398                         /* bring back to S16 */
399                         MEDIA_SAMPLE_FORMAT_DOWNSAMPLE(sxe_msf_S16)(
400                                 aj->buffer, aj->buffer, tmplen);
401
402                         wrtn = write(
403                                 sesd->sock, aj->buffer,
404                                 tmplen*sizeof(int16_t));
405                         if (wrtn < 0) {
406                                 ESD_DEBUG_S("writing to socket failed: %d.\n",
407                                             wrtn);
408                                 aj->play_state = MTPSTATE_STOP;
409                         }
410                         break;
411                 case MTPSTATE_PAUSE:
412                         ESD_DEBUG("sleeping for %d\n", resolution);
413                         usleep(resolution);
414                         break;
415
416                 case MTPSTATE_UNKNOWN:
417                 case MTPSTATE_STOP:
418                 case NUMBER_OF_MEDIA_THREAD_PLAY_STATES:
419                 default:
420                         ESD_DEBUG("ACK, quit\n");
421                         SXE_MUTEX_LOCK(&aj->mtx);
422                         aj->play_state = MTPSTATE_STOP;
423                         SXE_MUTEX_UNLOCK(&aj->mtx);
424                         break;
425                 }
426         }
427
428         /* close and shutdown */
429         SXE_MUTEX_LOCK(&aj->mtx);
430         if (alloced_myself && aj->buffer) {
431                 xfree(aj->buffer);
432         }
433         aj->buffer = NULL;
434         aj->buffer_alloc_size = 0;
435         SXE_MUTEX_UNLOCK(&aj->mtx);
436
437         sound_esd_close_device(sed);
438         /* we better close the socket now */
439         close(sesd->sock);
440
441         return 1;
442 }
443
444 #undef MYSELF
445
446 /* sound-esd.c ends here */