diff options
Diffstat (limited to 'lib/rbcodec')
-rw-r--r-- | lib/rbcodec/test/Makefile | 74 | ||||
-rw-r--r-- | lib/rbcodec/test/autoconf.h | 17 | ||||
-rw-r--r-- | lib/rbcodec/test/warble.c | 837 |
3 files changed, 928 insertions, 0 deletions
diff --git a/lib/rbcodec/test/Makefile b/lib/rbcodec/test/Makefile new file mode 100644 index 0000000000..cbda9acbdb --- /dev/null +++ b/lib/rbcodec/test/Makefile | |||
@@ -0,0 +1,74 @@ | |||
1 | default: all | ||
2 | |||
3 | .PHONY: default all clean dep | ||
4 | |||
5 | ROOTDIR = $(shell readlink -e ../../..) | ||
6 | BUILDDIR = $(shell pwd)/build | ||
7 | APPSDIR = $(ROOTDIR)/apps | ||
8 | TOOLSDIR = $(ROOTDIR)/tools | ||
9 | DEPFILE = $(BUILDDIR)/make.dep | ||
10 | APP_TYPE = sdl-sim | ||
11 | |||
12 | INCLUDES = -I$(shell pwd) | ||
13 | INCLUDES += -I$(APPSDIR) -I$(APPSDIR)/codecs -I$(APPSDIR)/codecs/lib \ | ||
14 | -I$(APPSDIR)/gui -I$(APPSDIR)/metadata | ||
15 | INCLUDES += -I$(ROOTDIR)/firmware/export -I$(ROOTDIR)/firmware/include \ | ||
16 | -I$(ROOTDIR)/firmware/target/hosted \ | ||
17 | -I$(ROOTDIR)/firmware/target/hosted/sdl | ||
18 | |||
19 | CFLAGS = $(INCLUDES) -DROCKBOX -DSIMULATOR=1 | ||
20 | CFLAGS += -O0 -ggdb -DDEBUG -DLOGF_ENABLE -Wall -Wno-pointer-sign | ||
21 | CFLAGS += -Wstrict-prototypes -pipe -std=gnu99 | ||
22 | PPCFLAGS = $(CFLAGS) | ||
23 | |||
24 | SHARED_CFLAGS = -fPIC -fvisibility=hidden | ||
25 | SHARED_LDFLAG = -shared | ||
26 | |||
27 | WARBLE_OBJS = $(BUILDDIR)/warble.o | ||
28 | WARBLE_CFLAGS = '-DCODECDIR="$(CODECDIR)"' $(shell sdl-config --cflags) | ||
29 | WARBLE_LDFLAGS = -lm -ldl $(shell sdl-config --libs) | ||
30 | |||
31 | include $(ROOTDIR)/tools/functions.make | ||
32 | include $(APPSDIR)/codecs/codecs.make | ||
33 | |||
34 | SRC = $(ROOTDIR)/apps/metadata.c $(ROOTDIR)/apps/replaygain.c \ | ||
35 | $(ROOTDIR)/firmware/buflib.c \ | ||
36 | $(ROOTDIR)/firmware/core_alloc.c \ | ||
37 | $(ROOTDIR)/firmware/common/strlcpy.c \ | ||
38 | $(ROOTDIR)/firmware/common/unicode.c \ | ||
39 | $(ROOTDIR)/firmware/common/structec.c $(ROOTDIR)/apps/mp3data.c \ | ||
40 | $(ROOTDIR)/apps/fixedpoint.c $(ROOTDIR)/uisimulator/common/io.c | ||
41 | SRC += $(APPSDIR)/compressor.c $(APPSDIR)/dsp.c $(APPSDIR)/eq.c $(APPSDIR)/tdspeed.c | ||
42 | SRC += $(wildcard $(ROOTDIR)/apps/metadata/*.c) | ||
43 | |||
44 | OBJ := $(SRC:.c=.o) | ||
45 | OBJ := $(OBJ:.S=.o) | ||
46 | OBJ := $(subst $(ROOTDIR),$(BUILDDIR),$(OBJ)) | ||
47 | |||
48 | all: warble $(CODECS) | ||
49 | |||
50 | dep $(DEPFILE): | ||
51 | $(SILENT)mkdir -p $(dir $(DEPFILE)) | ||
52 | $(call PRINTS,Generating dependencies) | ||
53 | @rm -f $(DEPFILE)_ | ||
54 | $(call mkdepfile,$(DEPFILE)_,$(SRC)) | ||
55 | $(call mkdepfile,$(DEPFILE)_,$(OTHER_SRC)) | ||
56 | $(call mkdepfile,$(DEPFILE)_,$(ASMDEFS_SRC)) | ||
57 | @mv $(DEPFILE)_ $(DEPFILE) | ||
58 | |||
59 | -include $(DEPFILE) | ||
60 | |||
61 | warble: $(WARBLE_OBJS) $(OBJ) | ||
62 | $(call PRINTS,LD $@)$(CC) $(LDFLAGS) $^ -o $@ $(WARBLE_LDFLAGS) | ||
63 | |||
64 | $(BUILDDIR)/%.o: %.c | ||
65 | $(SILENT)mkdir -p $(dir $@) | ||
66 | $(call PRINTS,CC $<)$(CC) $(CFLAGS) -c $< -o $@ $(WARBLE_CFLAGS) | ||
67 | |||
68 | $(BUILDDIR)/%.o: $(ROOTDIR)/%.c | ||
69 | $(SILENT)mkdir -p $(dir $@) | ||
70 | $(call PRINTS,CC $<)$(CC) $(CFLAGS) -c $< -o $@ | ||
71 | |||
72 | clean: | ||
73 | $(SILENT)echo Cleaning build directory | ||
74 | $(SILENT)rm -rf warble $(BUILDDIR)/* | ||
diff --git a/lib/rbcodec/test/autoconf.h b/lib/rbcodec/test/autoconf.h new file mode 100644 index 0000000000..0908ade420 --- /dev/null +++ b/lib/rbcodec/test/autoconf.h | |||
@@ -0,0 +1,17 @@ | |||
1 | #ifndef __BUILD_AUTOCONF_H | ||
2 | #define __BUILD_AUTOCONF_H | ||
3 | |||
4 | #define __PCTOOL__ | ||
5 | #define CONFIG_CODEC SWCODEC | ||
6 | #define TARGET_ID 73 /* sdlapp */ | ||
7 | #define MEMORYSIZE 64 | ||
8 | #define ROCKBOX_LITTLE_ENDIAN 1 | ||
9 | #define HAVE_PITCHSCREEN | ||
10 | #define HAVE_SW_TONE_CONTROLS | ||
11 | #define HAVE_SW_VOLUME_CONTROL | ||
12 | #define VOLUME_MIN -100 | ||
13 | #define VOLUME_MAX 100 | ||
14 | #define SW_VOLUME_MIN -100 | ||
15 | #define SW_VOLUME_MAX 100 | ||
16 | |||
17 | #endif /* __BUILD_AUTOCONF_H */ | ||
diff --git a/lib/rbcodec/test/warble.c b/lib/rbcodec/test/warble.c new file mode 100644 index 0000000000..2cba6c0d59 --- /dev/null +++ b/lib/rbcodec/test/warble.c | |||
@@ -0,0 +1,837 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2011 Sean Bartell | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #define _BSD_SOURCE /* htole64 from endian.h */ | ||
22 | #include <sys/types.h> | ||
23 | #include <SDL.h> | ||
24 | #include <dlfcn.h> | ||
25 | #include <endian.h> | ||
26 | #include <fcntl.h> | ||
27 | #include <math.h> | ||
28 | #include <stdarg.h> | ||
29 | #include <stdio.h> | ||
30 | #include <stdlib.h> | ||
31 | #include <string.h> | ||
32 | #include <sys/stat.h> | ||
33 | #include <unistd.h> | ||
34 | #include "buffering.h" /* TYPE_PACKET_AUDIO */ | ||
35 | #include "codecs.h" | ||
36 | #include "core_alloc.h" /* core_allocator_init */ | ||
37 | #include "debug.h" | ||
38 | #include "dsp.h" | ||
39 | #include "metadata.h" | ||
40 | #include "settings.h" | ||
41 | #include "sound.h" | ||
42 | #include "tdspeed.h" | ||
43 | |||
44 | /***************** EXPORTED *****************/ | ||
45 | |||
46 | struct user_settings global_settings; | ||
47 | volatile long current_tick = 0; | ||
48 | |||
49 | void yield(void) | ||
50 | { | ||
51 | } | ||
52 | |||
53 | int set_irq_level(int level) | ||
54 | { | ||
55 | return 0; | ||
56 | } | ||
57 | |||
58 | void mutex_init(struct mutex *m) | ||
59 | { | ||
60 | } | ||
61 | |||
62 | void mutex_lock(struct mutex *m) | ||
63 | { | ||
64 | } | ||
65 | |||
66 | void mutex_unlock(struct mutex *m) | ||
67 | { | ||
68 | } | ||
69 | |||
70 | void debugf(const char *fmt, ...) | ||
71 | { | ||
72 | va_list ap; | ||
73 | va_start(ap, fmt); | ||
74 | vfprintf(stderr, fmt, ap); | ||
75 | va_end(ap); | ||
76 | } | ||
77 | |||
78 | /***************** INTERNAL *****************/ | ||
79 | |||
80 | static enum { MODE_PLAY, MODE_WRITE } mode; | ||
81 | static bool use_dsp = true; | ||
82 | static bool enable_loop = false; | ||
83 | static const char *config = ""; | ||
84 | |||
85 | static int input_fd; | ||
86 | static enum codec_command_action codec_action; | ||
87 | static intptr_t codec_action_param = 0; | ||
88 | static unsigned long num_output_samples = 0; | ||
89 | static struct codec_api ci; | ||
90 | |||
91 | static struct { | ||
92 | intptr_t freq; | ||
93 | intptr_t stereo_mode; | ||
94 | intptr_t depth; | ||
95 | int channels; | ||
96 | } format; | ||
97 | |||
98 | /***** MODE_WRITE *****/ | ||
99 | |||
100 | #define WAVE_HEADER_SIZE 0x2e | ||
101 | #define WAVE_FORMAT_PCM 1 | ||
102 | #define WAVE_FORMAT_IEEE_FLOAT 3 | ||
103 | static int output_fd; | ||
104 | static bool write_raw = false; | ||
105 | static bool write_header_written = false; | ||
106 | |||
107 | static void write_init(const char *output_fn) | ||
108 | { | ||
109 | mode = MODE_WRITE; | ||
110 | if (!strcmp(output_fn, "-")) { | ||
111 | output_fd = STDOUT_FILENO; | ||
112 | } else { | ||
113 | output_fd = creat(output_fn, 0666); | ||
114 | if (output_fd == -1) { | ||
115 | perror(output_fn); | ||
116 | exit(1); | ||
117 | } | ||
118 | } | ||
119 | } | ||
120 | |||
121 | static void set_le16(char *buf, uint16_t val) | ||
122 | { | ||
123 | buf[0] = val; | ||
124 | buf[1] = val >> 8; | ||
125 | } | ||
126 | |||
127 | static void set_le32(char *buf, uint32_t val) | ||
128 | { | ||
129 | buf[0] = val; | ||
130 | buf[1] = val >> 8; | ||
131 | buf[2] = val >> 16; | ||
132 | buf[3] = val >> 24; | ||
133 | } | ||
134 | |||
135 | static void write_wav_header(void) | ||
136 | { | ||
137 | int channels, sample_size, freq, type; | ||
138 | if (use_dsp) { | ||
139 | channels = 2; | ||
140 | sample_size = 16; | ||
141 | freq = NATIVE_FREQUENCY; | ||
142 | type = WAVE_FORMAT_PCM; | ||
143 | } else { | ||
144 | channels = format.channels; | ||
145 | sample_size = 64; | ||
146 | freq = format.freq; | ||
147 | type = WAVE_FORMAT_IEEE_FLOAT; | ||
148 | } | ||
149 | |||
150 | /* The size fields are normally overwritten by write_quit(). If that fails, | ||
151 | * this fake size ensures the file can still be played. */ | ||
152 | off_t total_size = 0x7fffff00 + WAVE_HEADER_SIZE; | ||
153 | char header[WAVE_HEADER_SIZE] = {"RIFF____WAVEfmt \x12\0\0\0" | ||
154 | "________________\0\0data____"}; | ||
155 | set_le32(header + 0x04, total_size - 8); | ||
156 | set_le16(header + 0x14, type); | ||
157 | set_le16(header + 0x16, channels); | ||
158 | set_le32(header + 0x18, freq); | ||
159 | set_le32(header + 0x1c, freq * channels * sample_size / 8); | ||
160 | set_le16(header + 0x20, channels * sample_size / 8); | ||
161 | set_le16(header + 0x22, sample_size); | ||
162 | set_le32(header + 0x2a, total_size - WAVE_HEADER_SIZE); | ||
163 | write(output_fd, header, sizeof(header)); | ||
164 | write_header_written = true; | ||
165 | } | ||
166 | |||
167 | static void write_quit(void) | ||
168 | { | ||
169 | if (!write_raw) { | ||
170 | /* Write the correct size fields in the header. If lseek fails (e.g. | ||
171 | * for a pipe) nothing is written. */ | ||
172 | off_t total_size = lseek(output_fd, 0, SEEK_CUR); | ||
173 | if (total_size != (off_t)-1) { | ||
174 | char buf[4]; | ||
175 | set_le32(buf, total_size - 8); | ||
176 | lseek(output_fd, 4, SEEK_SET); | ||
177 | write(output_fd, buf, 4); | ||
178 | set_le32(buf, total_size - WAVE_HEADER_SIZE); | ||
179 | lseek(output_fd, 0x2a, SEEK_SET); | ||
180 | write(output_fd, buf, 4); | ||
181 | } | ||
182 | } | ||
183 | if (output_fd != STDOUT_FILENO) | ||
184 | close(output_fd); | ||
185 | } | ||
186 | |||
187 | static uint64_t make_float64(int32_t sample, int shift) | ||
188 | { | ||
189 | /* TODO: be more portable */ | ||
190 | double val = ldexp(sample, -shift); | ||
191 | return *(uint64_t*)&val; | ||
192 | } | ||
193 | |||
194 | static void write_pcm(int16_t *pcm, int count) | ||
195 | { | ||
196 | if (!write_header_written) | ||
197 | write_wav_header(); | ||
198 | int i; | ||
199 | for (i = 0; i < 2 * count; i++) | ||
200 | pcm[i] = htole16(pcm[i]); | ||
201 | write(output_fd, pcm, 4 * count); | ||
202 | } | ||
203 | |||
204 | static void write_pcm_raw(int32_t *pcm, int count) | ||
205 | { | ||
206 | if (write_raw) { | ||
207 | write(output_fd, pcm, count * sizeof(*pcm)); | ||
208 | } else { | ||
209 | if (!write_header_written) | ||
210 | write_wav_header(); | ||
211 | int i; | ||
212 | uint64_t buf[count]; | ||
213 | |||
214 | for (i = 0; i < count; i++) | ||
215 | buf[i] = htole64(make_float64(pcm[i], format.depth)); | ||
216 | write(output_fd, buf, count * sizeof(*buf)); | ||
217 | } | ||
218 | } | ||
219 | |||
220 | /***** MODE_PLAY *****/ | ||
221 | |||
222 | /* MODE_PLAY uses a double buffer: one half is read by the playback thread and | ||
223 | * the other half is written to by the main thread. When a thread is done with | ||
224 | * its current half, it waits for the other thread and then switches. The main | ||
225 | * advantage of this method is its simplicity; the main disadvantage is that it | ||
226 | * has long latency. ALSA buffer underruns still occur sometimes, but this is | ||
227 | * SDL's fault. */ | ||
228 | |||
229 | #define PLAYBACK_BUFFER_SIZE 0x10000 | ||
230 | static bool playback_running = false; | ||
231 | static char playback_buffer[2][PLAYBACK_BUFFER_SIZE]; | ||
232 | static int playback_play_ind, playback_decode_ind; | ||
233 | static int playback_play_pos, playback_decode_pos; | ||
234 | static SDL_sem *playback_play_sema, *playback_decode_sema; | ||
235 | |||
236 | static void playback_init(void) | ||
237 | { | ||
238 | mode = MODE_PLAY; | ||
239 | if (SDL_Init(SDL_INIT_AUDIO)) { | ||
240 | fprintf(stderr, "error: Can't initialize SDL: %s\n", SDL_GetError()); | ||
241 | exit(1); | ||
242 | } | ||
243 | playback_play_ind = 1; | ||
244 | playback_play_pos = PLAYBACK_BUFFER_SIZE; | ||
245 | playback_decode_ind = 0; | ||
246 | playback_decode_pos = 0; | ||
247 | playback_play_sema = SDL_CreateSemaphore(0); | ||
248 | playback_decode_sema = SDL_CreateSemaphore(0); | ||
249 | } | ||
250 | |||
251 | static void playback_callback(void *userdata, Uint8 *stream, int len) | ||
252 | { | ||
253 | while (len > 0) { | ||
254 | if (!playback_running && playback_play_ind == playback_decode_ind | ||
255 | && playback_play_pos >= playback_decode_pos) { | ||
256 | /* end of data */ | ||
257 | memset(stream, 0, len); | ||
258 | SDL_SemPost(playback_play_sema); | ||
259 | return; | ||
260 | } | ||
261 | if (playback_play_pos >= PLAYBACK_BUFFER_SIZE) { | ||
262 | SDL_SemPost(playback_play_sema); | ||
263 | SDL_SemWait(playback_decode_sema); | ||
264 | playback_play_ind = !playback_play_ind; | ||
265 | playback_play_pos = 0; | ||
266 | } | ||
267 | char *play_buffer = playback_buffer[playback_play_ind]; | ||
268 | int copy_len = MIN(len, PLAYBACK_BUFFER_SIZE - playback_play_pos); | ||
269 | memcpy(stream, play_buffer + playback_play_pos, copy_len); | ||
270 | len -= copy_len; | ||
271 | stream += copy_len; | ||
272 | playback_play_pos += copy_len; | ||
273 | } | ||
274 | } | ||
275 | |||
276 | static void playback_start(void) | ||
277 | { | ||
278 | playback_running = true; | ||
279 | SDL_AudioSpec spec = {0}; | ||
280 | spec.freq = NATIVE_FREQUENCY; | ||
281 | spec.format = AUDIO_S16SYS; | ||
282 | spec.channels = 2; | ||
283 | spec.samples = 0x400; | ||
284 | spec.callback = playback_callback; | ||
285 | spec.userdata = NULL; | ||
286 | if (SDL_OpenAudio(&spec, NULL)) { | ||
287 | fprintf(stderr, "error: Can't open SDL audio: %s\n", SDL_GetError()); | ||
288 | exit(1); | ||
289 | } | ||
290 | SDL_PauseAudio(0); | ||
291 | } | ||
292 | |||
293 | static void playback_quit(void) | ||
294 | { | ||
295 | if (!playback_running) | ||
296 | playback_start(); | ||
297 | memset(playback_buffer[playback_decode_ind] + playback_decode_pos, 0, | ||
298 | PLAYBACK_BUFFER_SIZE - playback_decode_pos); | ||
299 | playback_running = false; | ||
300 | SDL_SemPost(playback_decode_sema); | ||
301 | SDL_SemWait(playback_play_sema); | ||
302 | SDL_SemWait(playback_play_sema); | ||
303 | SDL_Quit(); | ||
304 | } | ||
305 | |||
306 | static void playback_pcm(int16_t *pcm, int count) | ||
307 | { | ||
308 | const char *stream = (const char *)pcm; | ||
309 | count *= 4; | ||
310 | |||
311 | while (count > 0) { | ||
312 | if (playback_decode_pos >= PLAYBACK_BUFFER_SIZE) { | ||
313 | if (!playback_running) | ||
314 | playback_start(); | ||
315 | SDL_SemPost(playback_decode_sema); | ||
316 | SDL_SemWait(playback_play_sema); | ||
317 | playback_decode_ind = !playback_decode_ind; | ||
318 | playback_decode_pos = 0; | ||
319 | } | ||
320 | char *decode_buffer = playback_buffer[playback_decode_ind]; | ||
321 | int copy_len = MIN(count, PLAYBACK_BUFFER_SIZE - playback_decode_pos); | ||
322 | memcpy(decode_buffer + playback_decode_pos, stream, copy_len); | ||
323 | stream += copy_len; | ||
324 | count -= copy_len; | ||
325 | playback_decode_pos += copy_len; | ||
326 | } | ||
327 | } | ||
328 | |||
329 | /***** ALL MODES *****/ | ||
330 | |||
331 | static void perform_config(void) | ||
332 | { | ||
333 | /* TODO: equalizer, etc. */ | ||
334 | while (config) { | ||
335 | const char *name = config; | ||
336 | const char *eq = strchr(config, '='); | ||
337 | if (!eq) | ||
338 | break; | ||
339 | const char *val = eq + 1; | ||
340 | const char *end = val + strcspn(val, ": \t\n"); | ||
341 | |||
342 | if (!strncmp(name, "wait=", 5)) { | ||
343 | if (atoi(val) > num_output_samples) | ||
344 | return; | ||
345 | } else if (!strncmp(name, "dither=", 7)) { | ||
346 | dsp_dither_enable(atoi(val) ? true : false); | ||
347 | } else if (!strncmp(name, "halt=", 5)) { | ||
348 | if (atoi(val)) | ||
349 | codec_action = CODEC_ACTION_HALT; | ||
350 | } else if (!strncmp(name, "loop=", 5)) { | ||
351 | enable_loop = atoi(val) != 0; | ||
352 | } else if (!strncmp(name, "offset=", 7)) { | ||
353 | ci.id3->offset = atoi(val); | ||
354 | } else if (!strncmp(name, "rate=", 5)) { | ||
355 | sound_set_pitch(atof(val) * PITCH_SPEED_100); | ||
356 | } else if (!strncmp(name, "seek=", 5)) { | ||
357 | codec_action = CODEC_ACTION_SEEK_TIME; | ||
358 | codec_action_param = atoi(val); | ||
359 | } else if (!strncmp(name, "tempo=", 6)) { | ||
360 | dsp_set_timestretch(atof(val) * PITCH_SPEED_100); | ||
361 | } else if (!strncmp(name, "vol=", 4)) { | ||
362 | global_settings.volume = atoi(val); | ||
363 | dsp_callback(DSP_CALLBACK_SET_SW_VOLUME, 0); | ||
364 | } else { | ||
365 | fprintf(stderr, "error: unrecognized config \"%.*s\"\n", | ||
366 | (int)(eq - name), name); | ||
367 | exit(1); | ||
368 | } | ||
369 | |||
370 | if (*end) | ||
371 | config = end + 1; | ||
372 | else | ||
373 | config = NULL; | ||
374 | } | ||
375 | } | ||
376 | |||
377 | static void *ci_codec_get_buffer(size_t *size) | ||
378 | { | ||
379 | static char buffer[64 * 1024 * 1024]; | ||
380 | char *ptr = buffer; | ||
381 | *size = sizeof(buffer); | ||
382 | if ((intptr_t)ptr & (CACHEALIGN_SIZE - 1)) | ||
383 | ptr += CACHEALIGN_SIZE - ((intptr_t)ptr & (CACHEALIGN_SIZE - 1)); | ||
384 | return ptr; | ||
385 | } | ||
386 | |||
387 | static void ci_pcmbuf_insert(const void *ch1, const void *ch2, int count) | ||
388 | { | ||
389 | num_output_samples += count; | ||
390 | |||
391 | if (use_dsp) { | ||
392 | const char *src[2] = {ch1, ch2}; | ||
393 | while (count > 0) { | ||
394 | int out_count = dsp_output_count(ci.dsp, count); | ||
395 | int in_count = MIN(dsp_input_count(ci.dsp, out_count), count); | ||
396 | int16_t buf[2 * out_count]; | ||
397 | out_count = dsp_process(ci.dsp, (char *)buf, src, in_count); | ||
398 | if (mode == MODE_WRITE) | ||
399 | write_pcm(buf, out_count); | ||
400 | else if (mode == MODE_PLAY) | ||
401 | playback_pcm(buf, out_count); | ||
402 | count -= in_count; | ||
403 | } | ||
404 | } else { | ||
405 | /* Convert to 32-bit interleaved. */ | ||
406 | count *= format.channels; | ||
407 | int i; | ||
408 | int32_t buf[count]; | ||
409 | if (format.depth > 16) { | ||
410 | if (format.stereo_mode == STEREO_NONINTERLEAVED) { | ||
411 | for (i = 0; i < count; i += 2) { | ||
412 | buf[i+0] = ((int32_t*)ch1)[i/2]; | ||
413 | buf[i+1] = ((int32_t*)ch2)[i/2]; | ||
414 | } | ||
415 | } else { | ||
416 | memcpy(buf, ch1, sizeof(buf)); | ||
417 | } | ||
418 | } else { | ||
419 | if (format.stereo_mode == STEREO_NONINTERLEAVED) { | ||
420 | for (i = 0; i < count; i += 2) { | ||
421 | buf[i+0] = ((int16_t*)ch1)[i/2]; | ||
422 | buf[i+1] = ((int16_t*)ch2)[i/2]; | ||
423 | } | ||
424 | } else { | ||
425 | for (i = 0; i < count; i++) { | ||
426 | buf[i] = ((int16_t*)ch1)[i]; | ||
427 | } | ||
428 | } | ||
429 | } | ||
430 | |||
431 | if (mode == MODE_WRITE) | ||
432 | write_pcm_raw(buf, count); | ||
433 | } | ||
434 | |||
435 | perform_config(); | ||
436 | } | ||
437 | |||
438 | static void ci_set_elapsed(unsigned long value) | ||
439 | { | ||
440 | //debugf("Time elapsed: %lu\n", value); | ||
441 | } | ||
442 | |||
443 | static char *input_buffer = 0; | ||
444 | |||
445 | /* | ||
446 | * Read part of the input file into a provided buffer. | ||
447 | * | ||
448 | * The entire size requested will be provided except at the end of the file. | ||
449 | * The current file position will be moved, just like with advance_buffer, but | ||
450 | * the offset is not updated. This invalidates buffers returned by | ||
451 | * request_buffer. | ||
452 | */ | ||
453 | static size_t ci_read_filebuf(void *ptr, size_t size) | ||
454 | { | ||
455 | free(input_buffer); | ||
456 | input_buffer = NULL; | ||
457 | |||
458 | ssize_t actual = read(input_fd, ptr, size); | ||
459 | if (actual < 0) | ||
460 | actual = 0; | ||
461 | ci.curpos += actual; | ||
462 | return actual; | ||
463 | } | ||
464 | |||
465 | /* | ||
466 | * Request a buffer containing part of the input file. | ||
467 | * | ||
468 | * The size provided will be the requested size, or the remaining size of the | ||
469 | * file, whichever is smaller. Packet audio has an additional maximum of 32 | ||
470 | * KiB. The returned buffer remains valid until the next time read_filebuf, | ||
471 | * request_buffer, advance_buffer, or seek_buffer is called. | ||
472 | */ | ||
473 | static void *ci_request_buffer(size_t *realsize, size_t reqsize) | ||
474 | { | ||
475 | free(input_buffer); | ||
476 | if (get_audio_base_data_type(ci.id3->codectype) == TYPE_PACKET_AUDIO) | ||
477 | reqsize = MIN(reqsize, 32 * 1024); | ||
478 | input_buffer = malloc(reqsize); | ||
479 | *realsize = read(input_fd, input_buffer, reqsize); | ||
480 | if (*realsize < 0) | ||
481 | *realsize = 0; | ||
482 | lseek(input_fd, -*realsize, SEEK_CUR); | ||
483 | return input_buffer; | ||
484 | } | ||
485 | |||
486 | /* | ||
487 | * Advance the current position in the input file. | ||
488 | * | ||
489 | * This automatically updates the current offset. This invalidates buffers | ||
490 | * returned by request_buffer. | ||
491 | */ | ||
492 | static void ci_advance_buffer(size_t amount) | ||
493 | { | ||
494 | free(input_buffer); | ||
495 | input_buffer = NULL; | ||
496 | |||
497 | lseek(input_fd, amount, SEEK_CUR); | ||
498 | ci.curpos += amount; | ||
499 | ci.id3->offset = ci.curpos; | ||
500 | } | ||
501 | |||
502 | /* | ||
503 | * Seek to a position in the input file. | ||
504 | * | ||
505 | * This invalidates buffers returned by request_buffer. | ||
506 | */ | ||
507 | static bool ci_seek_buffer(size_t newpos) | ||
508 | { | ||
509 | free(input_buffer); | ||
510 | input_buffer = NULL; | ||
511 | |||
512 | off_t actual = lseek(input_fd, newpos, SEEK_SET); | ||
513 | if (actual >= 0) | ||
514 | ci.curpos = actual; | ||
515 | return actual != -1; | ||
516 | } | ||
517 | |||
518 | static void ci_seek_complete(void) | ||
519 | { | ||
520 | } | ||
521 | |||
522 | static void ci_set_offset(size_t value) | ||
523 | { | ||
524 | ci.id3->offset = value; | ||
525 | } | ||
526 | |||
527 | static void ci_configure(int setting, intptr_t value) | ||
528 | { | ||
529 | if (use_dsp) { | ||
530 | dsp_configure(ci.dsp, setting, value); | ||
531 | } else { | ||
532 | if (setting == DSP_SET_FREQUENCY | ||
533 | || setting == DSP_SWITCH_FREQUENCY) | ||
534 | format.freq = value; | ||
535 | else if (setting == DSP_SET_SAMPLE_DEPTH) | ||
536 | format.depth = value; | ||
537 | else if (setting == DSP_SET_STEREO_MODE) { | ||
538 | format.stereo_mode = value; | ||
539 | format.channels = (value == STEREO_MONO) ? 1 : 2; | ||
540 | } | ||
541 | } | ||
542 | } | ||
543 | |||
544 | static enum codec_command_action ci_get_command(intptr_t *param) | ||
545 | { | ||
546 | enum codec_command_action ret = codec_action; | ||
547 | *param = codec_action_param; | ||
548 | codec_action = CODEC_ACTION_NULL; | ||
549 | return ret; | ||
550 | } | ||
551 | |||
552 | static bool ci_should_loop(void) | ||
553 | { | ||
554 | return enable_loop; | ||
555 | } | ||
556 | |||
557 | static unsigned ci_sleep(unsigned ticks) | ||
558 | { | ||
559 | return 0; | ||
560 | } | ||
561 | |||
562 | static void ci_cpucache_flush(void) | ||
563 | { | ||
564 | } | ||
565 | |||
566 | static void ci_cpucache_invalidate(void) | ||
567 | { | ||
568 | } | ||
569 | |||
570 | static struct codec_api ci = { | ||
571 | |||
572 | 0, /* filesize */ | ||
573 | 0, /* curpos */ | ||
574 | NULL, /* id3 */ | ||
575 | -1, /* audio_hid */ | ||
576 | NULL, /* struct dsp_config *dsp */ | ||
577 | ci_codec_get_buffer, | ||
578 | ci_pcmbuf_insert, | ||
579 | ci_set_elapsed, | ||
580 | ci_read_filebuf, | ||
581 | ci_request_buffer, | ||
582 | ci_advance_buffer, | ||
583 | ci_seek_buffer, | ||
584 | ci_seek_complete, | ||
585 | ci_set_offset, | ||
586 | ci_configure, | ||
587 | ci_get_command, | ||
588 | ci_should_loop, | ||
589 | |||
590 | ci_sleep, | ||
591 | yield, | ||
592 | |||
593 | #if NUM_CORES > 1 | ||
594 | ci_create_thread, | ||
595 | ci_thread_thaw, | ||
596 | ci_thread_wait, | ||
597 | ci_semaphore_init, | ||
598 | ci_semaphore_wait, | ||
599 | ci_semaphore_release, | ||
600 | #endif | ||
601 | |||
602 | ci_cpucache_flush, | ||
603 | ci_cpucache_invalidate, | ||
604 | |||
605 | /* strings and memory */ | ||
606 | strcpy, | ||
607 | strlen, | ||
608 | strcmp, | ||
609 | strcat, | ||
610 | memset, | ||
611 | memcpy, | ||
612 | memmove, | ||
613 | memcmp, | ||
614 | memchr, | ||
615 | #if defined(DEBUG) || defined(SIMULATOR) | ||
616 | debugf, | ||
617 | #endif | ||
618 | #ifdef ROCKBOX_HAS_LOGF | ||
619 | debugf, /* logf */ | ||
620 | #endif | ||
621 | |||
622 | qsort, | ||
623 | |||
624 | #ifdef HAVE_RECORDING | ||
625 | ci_enc_get_inputs, | ||
626 | ci_enc_set_parameters, | ||
627 | ci_enc_get_chunk, | ||
628 | ci_enc_finish_chunk, | ||
629 | ci_enc_get_pcm_data, | ||
630 | ci_enc_unget_pcm_data, | ||
631 | |||
632 | /* file */ | ||
633 | open, | ||
634 | close, | ||
635 | read, | ||
636 | lseek, | ||
637 | write, | ||
638 | ci_round_value_to_list32, | ||
639 | |||
640 | #endif /* HAVE_RECORDING */ | ||
641 | }; | ||
642 | |||
643 | static void print_mp3entry(const struct mp3entry *id3, FILE *f) | ||
644 | { | ||
645 | fprintf(f, "Path: %s\n", id3->path); | ||
646 | if (id3->title) fprintf(f, "Title: %s\n", id3->title); | ||
647 | if (id3->artist) fprintf(f, "Artist: %s\n", id3->artist); | ||
648 | if (id3->album) fprintf(f, "Album: %s\n", id3->album); | ||
649 | if (id3->genre_string) fprintf(f, "Genre: %s\n", id3->genre_string); | ||
650 | if (id3->disc_string || id3->discnum) fprintf(f, "Disc: %s (%d)\n", id3->disc_string, id3->discnum); | ||
651 | if (id3->track_string || id3->tracknum) fprintf(f, "Track: %s (%d)\n", id3->track_string, id3->tracknum); | ||
652 | if (id3->year_string || id3->year) fprintf(f, "Year: %s (%d)\n", id3->year_string, id3->year); | ||
653 | if (id3->composer) fprintf(f, "Composer: %s\n", id3->composer); | ||
654 | if (id3->comment) fprintf(f, "Comment: %s\n", id3->comment); | ||
655 | if (id3->albumartist) fprintf(f, "Album artist: %s\n", id3->albumartist); | ||
656 | if (id3->grouping) fprintf(f, "Grouping: %s\n", id3->grouping); | ||
657 | if (id3->layer) fprintf(f, "Layer: %d\n", id3->layer); | ||
658 | if (id3->id3version) fprintf(f, "ID3 version: %u\n", (int)id3->id3version); | ||
659 | fprintf(f, "Codec: %s\n", audio_formats[id3->codectype].label); | ||
660 | fprintf(f, "Bitrate: %d kb/s\n", id3->bitrate); | ||
661 | fprintf(f, "Frequency: %lu Hz\n", id3->frequency); | ||
662 | if (id3->id3v2len) fprintf(f, "ID3v2 length: %lu\n", id3->id3v2len); | ||
663 | if (id3->id3v1len) fprintf(f, "ID3v1 length: %lu\n", id3->id3v1len); | ||
664 | if (id3->first_frame_offset) fprintf(f, "First frame offset: %lu\n", id3->first_frame_offset); | ||
665 | fprintf(f, "File size without headers: %lu\n", id3->filesize); | ||
666 | fprintf(f, "Song length: %lu ms\n", id3->length); | ||
667 | if (id3->lead_trim > 0 || id3->tail_trim > 0) fprintf(f, "Trim: %d/%d\n", id3->lead_trim, id3->tail_trim); | ||
668 | if (id3->samples) fprintf(f, "Number of samples: %lu\n", id3->samples); | ||
669 | if (id3->frame_count) fprintf(f, "Number of frames: %lu\n", id3->frame_count); | ||
670 | if (id3->bytesperframe) fprintf(f, "Bytes per frame: %lu\n", id3->bytesperframe); | ||
671 | if (id3->vbr) fprintf(f, "VBR: true\n"); | ||
672 | if (id3->has_toc) fprintf(f, "Has TOC: true\n"); | ||
673 | if (id3->channels) fprintf(f, "Number of channels: %u\n", id3->channels); | ||
674 | if (id3->extradata_size) fprintf(f, "Size of extra data: %u\n", id3->extradata_size); | ||
675 | if (id3->needs_upsampling_correction) fprintf(f, "Needs upsampling correction: true\n"); | ||
676 | /* TODO: replaygain; albumart; cuesheet */ | ||
677 | if (id3->mb_track_id) fprintf(f, "Musicbrainz track ID: %s\n", id3->mb_track_id); | ||
678 | } | ||
679 | |||
680 | static void decode_file(const char *input_fn) | ||
681 | { | ||
682 | /* Set up global settings */ | ||
683 | memset(&global_settings, 0, sizeof(global_settings)); | ||
684 | global_settings.timestretch_enabled = true; | ||
685 | dsp_timestretch_enable(true); | ||
686 | tdspeed_init(); | ||
687 | |||
688 | /* Open file */ | ||
689 | if (!strcmp(input_fn, "-")) { | ||
690 | input_fd = STDIN_FILENO; | ||
691 | } else { | ||
692 | input_fd = open(input_fn, O_RDONLY); | ||
693 | if (input_fd == -1) { | ||
694 | perror(input_fn); | ||
695 | exit(1); | ||
696 | } | ||
697 | } | ||
698 | |||
699 | /* Set up ci */ | ||
700 | struct mp3entry id3; | ||
701 | if (!get_metadata(&id3, input_fd, input_fn)) { | ||
702 | fprintf(stderr, "error: metadata parsing failed\n"); | ||
703 | exit(1); | ||
704 | } | ||
705 | print_mp3entry(&id3, stderr); | ||
706 | ci.filesize = filesize(input_fd); | ||
707 | ci.id3 = &id3; | ||
708 | if (use_dsp) { | ||
709 | ci.dsp = (struct dsp_config *)dsp_configure(NULL, DSP_MYDSP, CODEC_IDX_AUDIO); | ||
710 | dsp_configure(ci.dsp, DSP_RESET, 0); | ||
711 | dsp_dither_enable(false); | ||
712 | } | ||
713 | perform_config(); | ||
714 | |||
715 | /* Load codec */ | ||
716 | char str[MAX_PATH]; | ||
717 | snprintf(str, sizeof(str), CODECDIR"/%s.codec", audio_formats[id3.codectype].codec_root_fn); | ||
718 | debugf("Loading %s\n", str); | ||
719 | void *dlcodec = dlopen(str, RTLD_NOW); | ||
720 | if (!dlcodec) { | ||
721 | fprintf(stderr, "error: dlopen failed: %s\n", dlerror()); | ||
722 | exit(1); | ||
723 | } | ||
724 | struct codec_header *c_hdr = NULL; | ||
725 | c_hdr = dlsym(dlcodec, "__header"); | ||
726 | if (c_hdr->lc_hdr.magic != CODEC_MAGIC) { | ||
727 | fprintf(stderr, "error: %s invalid: incorrect magic\n", str); | ||
728 | exit(1); | ||
729 | } | ||
730 | if (c_hdr->lc_hdr.target_id != TARGET_ID) { | ||
731 | fprintf(stderr, "error: %s invalid: incorrect target id\n", str); | ||
732 | exit(1); | ||
733 | } | ||
734 | if (c_hdr->lc_hdr.api_version != CODEC_API_VERSION) { | ||
735 | fprintf(stderr, "error: %s invalid: incorrect API version\n", str); | ||
736 | exit(1); | ||
737 | } | ||
738 | |||
739 | /* Run the codec */ | ||
740 | *c_hdr->api = &ci; | ||
741 | if (c_hdr->entry_point(CODEC_LOAD) != CODEC_OK) { | ||
742 | fprintf(stderr, "error: codec returned error from codec_main\n"); | ||
743 | exit(1); | ||
744 | } | ||
745 | if (c_hdr->run_proc() != CODEC_OK) { | ||
746 | fprintf(stderr, "error: codec error\n"); | ||
747 | } | ||
748 | c_hdr->entry_point(CODEC_UNLOAD); | ||
749 | |||
750 | /* Close */ | ||
751 | dlclose(dlcodec); | ||
752 | if (input_fd != STDIN_FILENO) | ||
753 | close(input_fd); | ||
754 | } | ||
755 | |||
756 | static void print_help(const char *progname) | ||
757 | { | ||
758 | fprintf(stderr, "Usage:\n" | ||
759 | " Play: %s [options] INPUTFILE\n" | ||
760 | "Write to WAV: %s [options] INPUTFILE OUTPUTFILE\n" | ||
761 | "\n" | ||
762 | "general options:\n" | ||
763 | " -c a=1:b=2 Configuration (see below)\n" | ||
764 | " -h Show this help\n" | ||
765 | "\n" | ||
766 | "write to WAV options:\n" | ||
767 | " -f Write raw codec output converted to 64-bit float\n" | ||
768 | " -r Write raw 32-bit codec output without WAV header\n" | ||
769 | "\n" | ||
770 | "configuration:\n" | ||
771 | " dither=<0|1> Enable/disable dithering [0]\n" | ||
772 | " halt=<0|1> Stop decoding if 1 [0]\n" | ||
773 | " loop=<0|1> Enable/disable looping [0]\n" | ||
774 | " offset=<n> Start at byte offset within the file [0]\n" | ||
775 | " rate=<n> Multiply rate by <n> [1.0]\n" | ||
776 | " seek=<n> Seek <n> ms into the file\n" | ||
777 | " tempo=<n> Timestretch by <n> [1.0]\n" | ||
778 | " vol=<n> Set volume to <n> dB [0]\n" | ||
779 | " wait=<n> Don't apply remaining configuration until\n" | ||
780 | " <n> total samples have output\n" | ||
781 | "\n" | ||
782 | "examples:\n" | ||
783 | " # Play while looping; stop after 44100 output samples\n" | ||
784 | " %s in.adx -c loop=1:wait=44100:halt=1\n" | ||
785 | " # Lower pitch 1 octave and write to out.wav\n" | ||
786 | " %s in.ogg -c rate=0.5:tempo=2 out.wav\n" | ||
787 | , progname, progname, progname, progname); | ||
788 | } | ||
789 | |||
790 | int main(int argc, char **argv) | ||
791 | { | ||
792 | int opt; | ||
793 | while ((opt = getopt(argc, argv, "c:fhr")) != -1) { | ||
794 | switch (opt) { | ||
795 | case 'c': | ||
796 | config = optarg; | ||
797 | break; | ||
798 | case 'f': | ||
799 | use_dsp = false; | ||
800 | break; | ||
801 | case 'r': | ||
802 | use_dsp = false; | ||
803 | write_raw = true; | ||
804 | break; | ||
805 | case 'h': /* fallthrough */ | ||
806 | default: | ||
807 | print_help(argv[0]); | ||
808 | exit(1); | ||
809 | } | ||
810 | } | ||
811 | |||
812 | core_allocator_init(); | ||
813 | if (argc == optind + 2) { | ||
814 | write_init(argv[optind + 1]); | ||
815 | } else if (argc == optind + 1) { | ||
816 | if (!use_dsp) { | ||
817 | fprintf(stderr, "error: -r can't be used for playback\n"); | ||
818 | print_help(argv[0]); | ||
819 | exit(1); | ||
820 | } | ||
821 | playback_init(); | ||
822 | } else { | ||
823 | if (argc > 1) | ||
824 | fprintf(stderr, "error: wrong number of arguments\n"); | ||
825 | print_help(argv[0]); | ||
826 | exit(1); | ||
827 | } | ||
828 | |||
829 | decode_file(argv[optind]); | ||
830 | |||
831 | if (mode == MODE_WRITE) | ||
832 | write_quit(); | ||
833 | else if (mode == MODE_PLAY) | ||
834 | playback_quit(); | ||
835 | |||
836 | return 0; | ||
837 | } | ||