Imported Upstream version 1.7.1
[platform/upstream/edje.git] / src / modules / alsa_snd_player / alsa_snd_player.c
1 /*
2  * Remix ALSA Player: ALSA audio output
3  *
4  * Govindaraju SM <govi.sm@samsung.com>, October 2011
5  * Prince Kumar Dubey <prince.dubey@samsung.com>, October 2011
6  */
7
8 #include "config.h"
9 #include <stdio.h>
10 #include <remix/remix.h>
11 #include <alsa/asoundlib.h>
12 #include <Eina.h>
13 #ifdef HAVE_LIBSNDFILE
14 #include <sndfile.h>
15 #endif
16
17 #define ALSA_PLAYER_BUFFERLEN 2048
18
19 typedef struct _Alsa_Player_Data Alsa_Player_Data;
20 typedef short PLAYER_PCM;
21
22 struct _Alsa_Player_Data
23 {
24    RemixPCM databuffer[ALSA_PLAYER_BUFFERLEN];
25    snd_pcm_t *alsa_dev;
26    unsigned int stereo;
27    unsigned channels;
28    unsigned int frequency;
29 };
30
31 static int _log_dom = -1;
32 static int init_count = 0;
33
34 #ifdef WRN
35 # undef WRN
36 #endif
37 #define WRN(...) EINA_LOG_DOM_WARN(_log_dom, __VA_ARGS__)
38
39 //#define MIXDBG 1
40
41 /* Optimisation dependencies: none */
42 static RemixBase *alsa_player_optimise(RemixEnv *env, RemixBase *base);
43
44 static snd_pcm_t *
45 alsa_open(int channels, unsigned int samplerate, unsigned int *real_samplerate)
46 {
47    const char *device = "default";
48    snd_pcm_t *alsa_dev = NULL;
49    snd_pcm_hw_params_t *hw_params;
50    snd_pcm_uframes_t alsa_buffer_frames;
51    snd_pcm_uframes_t alsa_period_size;
52    unsigned int samplerate_ret = 0;
53    int err;
54    
55    alsa_buffer_frames = ALSA_PLAYER_BUFFERLEN;
56    alsa_period_size = ALSA_PLAYER_BUFFERLEN / 4;
57    
58    if ((err = snd_pcm_open(&alsa_dev, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0)
59      {
60         WRN("cannot open alsa playback stream (%s)", snd_strerror(err));
61         goto catch_error;
62      }
63    snd_pcm_hw_params_alloca(&hw_params);
64    if ((err = snd_pcm_hw_params_any(alsa_dev, hw_params)) < 0)
65      {
66         WRN("cannot initialize snd hw params (%s)", snd_strerror(err));
67         goto catch_error;
68      }
69    if ((err = snd_pcm_hw_params_set_access(alsa_dev, hw_params, 
70                                            SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
71      {
72          WRN("cannot set interleaved access (%s)", snd_strerror(err));
73          goto catch_error;
74      }
75    if ((err = snd_pcm_hw_params_set_format(alsa_dev, hw_params, 
76                                            SND_PCM_FORMAT_FLOAT)) < 0)
77      {
78         WRN("cannot set float sample format (%s)", snd_strerror(err));
79         goto catch_error;
80      }
81 #ifdef MIXDBG // testing/debugging by making output samplerate be 48khz
82    samplerate_ret = 48000;
83    if ((err = snd_pcm_hw_params_set_rate_near(alsa_dev, hw_params, 
84                                               &samplerate_ret, 0)) < 0)
85      {
86         WRN("cannot set sample rate (%s)", snd_strerror(err));
87         goto catch_error;
88      }
89 #else
90    if ((err = snd_pcm_hw_params_set_rate_near(alsa_dev, hw_params, 
91                                               &samplerate, 0)) < 0)
92      {
93         WRN("cannot set sample rate (%s)", snd_strerror(err));
94         goto catch_error;
95      }
96 #endif
97    if ((err = snd_pcm_hw_params_set_channels(alsa_dev, hw_params, channels)) < 0)
98      {
99         WRN("cannot set channel count (%s)", snd_strerror(err));
100         goto catch_error;
101      }
102    if ((err = snd_pcm_hw_params_set_buffer_size_near(alsa_dev, hw_params, 
103                                                      &alsa_buffer_frames)) < 0)
104      {
105         WRN("cannot set buffer size (%s)", snd_strerror(err));
106         goto catch_error;
107      }
108    if ((err = snd_pcm_hw_params_set_period_size_near(alsa_dev, hw_params, 
109                                                      &alsa_period_size, 0)) < 0)
110      {
111         WRN("cannot set period size (%s)", snd_strerror(err));
112         goto catch_error;
113      }
114    if ((err = snd_pcm_hw_params(alsa_dev, hw_params)) < 0)
115      {
116         WRN("cannot set parameters (%s)", snd_strerror(err));
117         goto catch_error;
118      }
119    if ((err = snd_pcm_hw_params_get_rate(hw_params, &samplerate_ret, 0)) < 0)
120      {
121         WRN("cannot get samplerate (%s)", snd_strerror(err));
122         goto catch_error;
123      }
124    if ((err = snd_pcm_prepare(alsa_dev)) < 0)
125      {
126         WRN("cannot prepare audio for use (%s)", snd_strerror(err));
127         goto catch_error;
128      }
129    if (real_samplerate) *real_samplerate = samplerate_ret;
130
131 catch_error:
132    if ((err < 0) && (alsa_dev != NULL))
133      {
134         snd_pcm_close(alsa_dev);
135         return NULL;
136      }
137    return alsa_dev;
138 }
139
140 static RemixBase *
141 alsa_player_reset_device(RemixEnv *env, RemixBase *base)
142 {
143    Alsa_Player_Data *player_data = remix_base_get_instance_data(env, base);
144    unsigned int real_samplerate = 0;
145
146    if (player_data->alsa_dev)
147      {
148         snd_pcm_drain(player_data->alsa_dev);
149         snd_pcm_close(player_data->alsa_dev);
150      }
151    player_data->alsa_dev = alsa_open(player_data->channels,
152                                      player_data->frequency,
153                                      &real_samplerate);
154    if (!player_data->alsa_dev)
155      {
156         remix_set_error(env, REMIX_ERROR_SYSTEM);
157         return RemixNone;
158      }
159 //   printf("%i != %i\n", real_samplerate, player_data->frequency);
160    if (real_samplerate != player_data->frequency)
161      {
162         player_data->frequency = real_samplerate;
163         remix_set_samplerate(env, player_data->frequency);
164      }
165    return base;
166 }
167
168 static RemixBase *
169 alsa_player_init(RemixEnv *env, RemixBase *base, CDSet *parameters __UNUSED__)
170 {
171    CDSet *channels;
172    Alsa_Player_Data *player_data = calloc(1, sizeof(Alsa_Player_Data));
173
174    if (!player_data)
175      {
176         remix_set_error(env, REMIX_ERROR_SYSTEM);
177         return RemixNone;
178      }
179
180    init_count++;
181    if (init_count == 1)
182      {
183         eina_init();
184         _log_dom = eina_log_domain_register("remix-alsa", EINA_COLOR_CYAN);
185      }
186    
187    remix_base_set_instance_data(env, base, player_data);
188    channels = remix_get_channels(env);
189
190    player_data->channels = cd_set_size(env, channels);
191    if (player_data->channels == 1) player_data->stereo = 0;
192    else if (player_data->channels == 2) player_data->stereo = 1;
193    
194    player_data->frequency = remix_get_samplerate(env);
195    alsa_player_reset_device(env, base);
196    base = alsa_player_optimise(env, base);
197    return base;
198 }
199
200 static RemixBase *
201 alsa_player_clone(RemixEnv *env, RemixBase *base __UNUSED__)
202 {
203    RemixBase *new_player = remix_base_new(env);
204    alsa_player_init(env, new_player, NULL);
205    return new_player;
206 }
207
208 static int
209 alsa_player_destroy(RemixEnv *env, RemixBase *base)
210 {
211    Alsa_Player_Data *player_data = remix_base_get_instance_data(env, base);
212    
213    if (player_data->alsa_dev)
214      {
215         snd_pcm_drain(player_data->alsa_dev);
216         snd_pcm_close(player_data->alsa_dev);
217      }
218    free(player_data);
219    init_count--;
220    if (init_count == 0)
221      {
222         eina_log_domain_unregister(_log_dom);
223         _log_dom = -1;
224         eina_shutdown();
225      }
226    return 0;
227 }
228
229 static int
230 alsa_player_ready(RemixEnv *env, RemixBase *base)
231 {
232    Alsa_Player_Data *player_data = remix_base_get_instance_data(env, base);
233    RemixCount nr_channels;
234    CDSet *channels;
235    int samplerate;
236
237    channels = remix_get_channels(env);
238    samplerate = (int)remix_get_samplerate(env);
239    nr_channels = cd_set_size(env, channels);
240    return ((samplerate == (int)player_data->frequency) &&
241            (((nr_channels == 1) && (player_data->stereo == 0)) ||
242                ((nr_channels > 1) && (player_data->stereo == 1))));
243 }
244
245 static RemixBase *
246 alsa_player_prepare(RemixEnv *env, RemixBase *base)
247 {
248    alsa_player_reset_device(env, base);
249    return base;
250 }
251
252 static RemixCount
253 alsa_player_playbuffer(RemixEnv *env __UNUSED__, Alsa_Player_Data *player, RemixPCM *data, RemixCount count)
254 {
255 #ifdef MIXDBG
256      {
257         static int total = 0;
258         static SNDFILE *sfile = NULL;
259         static SF_INFO sfinfo;
260         
261         if (total == 0)
262           {
263              sfinfo.frames = 0;
264              sfinfo.samplerate = player->frequency;
265              sfinfo.channels = 2;
266              sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16 | SF_ENDIAN_LITTLE;
267              sfinfo.sections = 0;
268              sfinfo.seekable = 0;
269              sfile = sf_open("out.wav", SFM_WRITE, &sfinfo);
270           }
271
272         if (sfile)
273           {
274              sf_writef_float(sfile, data, count);
275              total += count;
276           }
277      }
278 #endif
279    return snd_pcm_writei(player->alsa_dev, data, count);
280 }
281
282 static RemixCount
283 alsa_player_chunk(RemixEnv *env, RemixChunk *chunk, RemixCount offset, RemixCount count, int channelname __UNUSED__, void *data)
284 {
285    Alsa_Player_Data *player = data;
286    RemixCount remaining = count, written = 0, n, playcount;
287    RemixPCM *d;
288    
289    while (remaining > 0)
290      {
291         playcount = MIN(remaining, ALSA_PLAYER_BUFFERLEN);
292
293         d = &chunk->data[offset];
294         n = alsa_player_playbuffer(env, player, d, playcount);
295
296         if (n == -1) return -1;
297         else n /= sizeof(PLAYER_PCM);
298         
299         offset += n;
300         written += n;
301         remaining -= n;
302      }
303    return written;
304 }
305
306 static RemixCount
307 alsa_player_process(RemixEnv *env, RemixBase *base, RemixCount count, RemixStream *input, RemixStream *output __UNUSED__)
308 {
309    Alsa_Player_Data *player_data = remix_base_get_instance_data(env, base);
310    RemixCount nr_channels = remix_stream_nr_channels(env, input);
311    RemixCount remaining = count, processed = 0, n, nn;
312    
313    if ((nr_channels == 1) && (player_data->stereo == 0))
314      { /*MONO*/
315         return remix_stream_chunkfuncify(env, input, count,
316                                          alsa_player_chunk, player_data);
317      }
318    else if ((nr_channels == 2) && (player_data->stereo == 1))
319      { /*STEREO*/
320         while (remaining > 0)
321           {
322              n = MIN(remaining, ALSA_PLAYER_BUFFERLEN / 2);
323              n = remix_stream_interleave_2(env, input, 
324                                            REMIX_CHANNEL_LEFT,
325                                            REMIX_CHANNEL_RIGHT,
326                                            player_data->databuffer, n);
327              nn = alsa_player_playbuffer(env, player_data,
328                                          player_data->databuffer, n);
329              processed += n;
330              remaining -= n;
331           }
332         return processed;
333      }
334    WRN("[alsa_player_process] unsupported stream/output channel "
335        "combination %ld / %d", nr_channels, player_data->stereo ? 2 : 1);
336    return -1;
337 }
338
339 static RemixCount
340 alsa_player_length(RemixEnv *env __UNUSED__, RemixBase *base __UNUSED__)
341 {
342    return REMIX_COUNT_INFINITE;
343 }
344
345 static RemixCount
346 alsa_player_seek(RemixEnv *env __UNUSED__, RemixBase *base __UNUSED__, RemixCount count __UNUSED__)
347 {
348    return count;
349 }
350
351 static int
352 alsa_player_flush(RemixEnv *env, RemixBase *base)
353 {
354    alsa_player_reset_device(env, base);
355    return 0;
356 }
357
358 static struct _RemixMethods _alsa_player_methods =
359 {
360    alsa_player_clone,
361    alsa_player_destroy,
362    alsa_player_ready,
363    alsa_player_prepare,
364    alsa_player_process,
365    alsa_player_length,
366    alsa_player_seek,
367    alsa_player_flush,
368 };
369
370 static RemixBase *
371 alsa_player_optimise(RemixEnv *env, RemixBase *base)
372 {
373    remix_base_set_methods(env, base, &_alsa_player_methods);
374    return base;
375 }
376
377 static struct _RemixMetaText alsa_player_metatext =
378 {
379    "alsa_snd_player",
380    "ALSA sound player for Remix",
381    "Output the audio stream into ALSA Driver",
382    "Copyright (C) 2011, Samsung Electronics Co., Ltd.",
383    "http://www.samsung.com",
384    REMIX_ONE_AUTHOR("Govindaraju SM", "prince.dubey@samsung.com"),
385 };
386
387 static struct _RemixPlugin alsa_player_plugin =
388 {
389    &alsa_player_metatext,
390    REMIX_FLAGS_NONE,
391    CD_EMPTY_SET, /* init scheme */
392    alsa_player_init,
393    CD_EMPTY_SET, /* process scheme */
394    NULL, /* suggests */
395    NULL, /* plugin data */
396    NULL  /* destroy */
397 };
398
399 EAPI CDList *
400 remix_load(RemixEnv *env)
401 {
402    CDList *plugins = cd_list_new(env);
403    plugins = cd_list_prepend(env, plugins, CD_POINTER(&alsa_player_plugin));
404    return plugins;
405 }