summaryrefslogtreecommitdiff
path: root/firmware/target/hosted/android/dx50/pcm-dx50.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/target/hosted/android/dx50/pcm-dx50.c')
-rw-r--r--firmware/target/hosted/android/dx50/pcm-dx50.c364
1 files changed, 364 insertions, 0 deletions
diff --git a/firmware/target/hosted/android/dx50/pcm-dx50.c b/firmware/target/hosted/android/dx50/pcm-dx50.c
new file mode 100644
index 0000000000..e7695873a0
--- /dev/null
+++ b/firmware/target/hosted/android/dx50/pcm-dx50.c
@@ -0,0 +1,364 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2010 Thomas Martitz
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22
23/*
24 * Based, but heavily modified, on the example given at
25 * http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html
26 *
27 * This driver uses the so-called unsafe async callback method and hardcoded device
28 * names. It fails when the audio device is busy by other apps.
29 *
30 * To make the async callback safer, an alternative stack is installed, since
31 * it's run from a signal hanlder (which otherwise uses the user stack). If
32 * tick tasks are run from a signal handler too, please install
33 * an alternative stack for it too.
34 *
35 * TODO: Rewrite this to do it properly with multithreading
36 *
37 * Alternatively, a version using polling in a tick task is provided. While
38 * supposedly safer, it appears to use more CPU (however I didn't measure it
39 * accurately, only looked at htop). At least, in this mode the "default"
40 * device works which doesnt break with other apps running.
41 */
42
43
44#include "autoconf.h"
45
46#include <stdlib.h>
47#include <stdbool.h>
48#include <stdio.h>
49#include <errno.h>
50#include "tinyalsa/asoundlib.h"
51#include "tinyalsa/asound.h"
52#include "system.h"
53#include "debug.h"
54#include "kernel.h"
55
56#include "pcm.h"
57#include "pcm-internal.h"
58#include "pcm_mixer.h"
59#include "pcm_sampr.h"
60#include "audiohw.h"
61
62#include <pthread.h>
63#include <signal.h>
64
65static const snd_pcm_format_t format = PCM_FORMAT_S16_LE; /* sample format */
66static const int channels = 2; /* count of channels */
67static unsigned int rate = 44100; /* stream rate */
68
69typedef struct pcm snd_pcm_t;
70static snd_pcm_t *handle;
71struct pcm_config config;
72
73static snd_pcm_sframes_t period_size = 512; /* if set to >= 1024, all timers become even slower */
74static char *frames;
75
76static const void *pcm_data = 0;
77static size_t pcm_size = 0;
78
79static int recursion;
80
81static int set_hwparams(snd_pcm_t *handle)
82{
83 int err;
84 if (!frames)
85 frames = malloc(pcm_frames_to_bytes(handle, pcm_get_buffer_size(handle)));
86 err = 0; /* success */
87 return err;
88}
89
90
91/* copy pcm samples to a spare buffer, suitable for snd_pcm_writei() */
92static bool fill_frames(void)
93{
94 size_t copy_n, frames_left = period_size;
95 bool new_buffer = false;
96
97 while (frames_left > 0)
98 {
99 if (!pcm_size)
100 {
101 new_buffer = true;
102 if (!pcm_play_dma_complete_callback(PCM_DMAST_OK, &pcm_data,
103 &pcm_size))
104 {
105 return false;
106 }
107 }
108 copy_n = MIN((size_t)pcm_size, pcm_frames_to_bytes(handle, frames_left));
109 memcpy(&frames[pcm_frames_to_bytes(handle, period_size-frames_left)], pcm_data, copy_n);
110
111 pcm_data += copy_n;
112 pcm_size -= copy_n;
113 frames_left -= pcm_bytes_to_frames(handle, copy_n);
114
115 if (new_buffer)
116 {
117 new_buffer = false;
118 pcm_play_dma_status_callback(PCM_DMAST_STARTED);
119 }
120 }
121 return true;
122}
123
124
125static void pcm_tick(void)
126{
127 if (fill_frames())
128 {
129 if (pcm_write(handle, frames, pcm_frames_to_bytes(handle, period_size))) {
130 printf("Error playing sample\n");
131 return;//break;
132 }
133
134 }
135 else
136 {
137 DEBUGF("%s: No Data.\n", __func__);
138 return;//break;
139 }
140}
141
142static int async_rw(snd_pcm_t *handle)
143{
144 int err;
145 snd_pcm_sframes_t sample_size;
146 char *samples;
147
148 /* fill buffer with silence to initiate playback without noisy click */
149 sample_size = pcm_frames_to_bytes(handle, pcm_get_buffer_size(handle));
150 samples = malloc(sample_size);
151
152 memset(samples, 0, sample_size);
153
154 err = pcm_write(handle, samples, sample_size);
155 free(samples);
156
157 if (err != 0)
158 {
159 DEBUGF("Initial write error: %d\n", err);
160 return err;
161 }
162 if (pcm_state(handle) == PCM_STATE_PREPARED)
163 {
164 err = pcm_start(handle);
165 if (err < 0)
166 {
167 DEBUGF("Start error: %d\n", err);
168 return err;
169 }
170 }
171 return 0;
172}
173
174void cleanup(void)
175{
176 free(frames);
177 frames = NULL;
178 pcm_close(handle);
179}
180
181void pcm_play_dma_init(void)
182{
183 config.channels = channels;
184 config.rate = rate;
185 config.period_size = period_size;
186 config.period_count = 4;
187 config.format = format;
188 config.start_threshold = 0;
189 config.stop_threshold = 0;
190 config.silence_threshold = 0;
191
192
193 handle = pcm_open(0, 0, PCM_OUT, &config);
194 if (!handle || !pcm_is_ready(handle)) {
195 printf("Unable to open PCM device: %s\n", pcm_get_error(handle));
196 return;
197 }
198
199 pcm_dma_apply_settings();
200
201 tick_add_task(pcm_tick);
202
203 atexit(cleanup);
204 return;
205}
206
207
208void pcm_play_lock(void)
209{
210 if (recursion++ == 0)
211 tick_remove_task(pcm_tick);
212}
213
214void pcm_play_unlock(void)
215{
216 if (--recursion == 0)
217 tick_add_task(pcm_tick);
218}
219
220static void pcm_dma_apply_settings_nolock(void)
221{
222 set_hwparams(handle);
223}
224
225void pcm_dma_apply_settings(void)
226{
227 pcm_play_lock();
228 pcm_dma_apply_settings_nolock();
229 pcm_play_unlock();
230}
231
232
233void pcm_play_dma_pause(bool pause)
234{
235 (void)pause;
236}
237
238
239void pcm_play_dma_stop(void)
240{
241 pcm_stop(handle);
242}
243
244void pcm_play_dma_start(const void *addr, size_t size)
245{
246#if defined(DX50) || defined(DX90)
247 /* headphone output relay: if this is done at startup already, a loud click is audible on headphones. Here, some time later,
248 the output caps are charged a bit and the click is much softer */
249 system("/system/bin/muteopen");
250#endif
251 pcm_dma_apply_settings_nolock();
252
253 pcm_data = addr;
254 pcm_size = size;
255
256 while (1)
257 {
258 snd_pcm_state_t state = pcm_state(handle);
259 switch (state)
260 {
261 case PCM_STATE_RUNNING:
262 return;
263 case PCM_STATE_XRUN:
264 {
265 printf("No handler for STATE_XRUN!\n");
266 continue;
267 }
268 case PCM_STATE_PREPARED:
269 { /* prepared state, we need to fill the buffer with silence before
270 * starting */
271 int err = async_rw(handle);
272 if (err < 0)
273 printf("Start error: %d\n", err);
274 return;
275 }
276 case PCM_STATE_PAUSED:
277 { /* paused, simply resume */
278 pcm_play_dma_pause(0);
279 return;
280 }
281 case PCM_STATE_DRAINING:
282 /* run until drained */
283 continue;
284 default:
285 DEBUGF("Unhandled state: %d\n", state);
286 return;
287 }
288 }
289}
290
291size_t pcm_get_bytes_waiting(void)
292{
293 return pcm_size;
294}
295
296const void * pcm_play_dma_get_peak_buffer(int *count)
297{
298 uintptr_t addr = (uintptr_t)pcm_data;
299 *count = pcm_size / 4;
300 return (void *)((addr + 3) & ~3);
301}
302
303void pcm_play_dma_postinit(void)
304{
305 return;
306}
307
308void pcm_set_mixer_volume(int volume)
309{
310#if defined(DX50) || defined(DX90)
311 /* -990 to 0 -> 0 to 255 */
312 int val = (volume+990)*255/990;
313#if defined(DX50)
314 FILE *f = fopen("/dev/codec_volume", "w");
315#else /* DX90 */
316 FILE *f = fopen("/sys/class/codec/es9018_volume", "w");
317#endif /* DX50 */
318 fprintf(f, "%d", val);
319 fclose(f);
320#else
321 (void)volume;
322#endif /* DX50 || DX90 */
323}
324
325#ifdef HAVE_RECORDING
326void pcm_rec_lock(void)
327{
328}
329
330void pcm_rec_unlock(void)
331{
332}
333
334void pcm_rec_dma_init(void)
335{
336}
337
338void pcm_rec_dma_close(void)
339{
340}
341
342void pcm_rec_dma_start(void *start, size_t size)
343{
344 (void)start;
345 (void)size;
346}
347
348void pcm_rec_dma_stop(void)
349{
350}
351
352const void * pcm_rec_dma_get_peak_buffer(void)
353{
354 return NULL;
355}
356
357void audiohw_set_recvol(int left, int right, int type)
358{
359 (void)left;
360 (void)right;
361 (void)type;
362}
363
364#endif /* HAVE_RECORDING */