| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * |
| * Copyright (C) 2011 Sean Bartell |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| ****************************************************************************/ |
| |
| #define _BSD_SOURCE /* htole64 from endian.h */ |
| #include <sys/types.h> |
| #include <SDL.h> |
| #include <dlfcn.h> |
| #include <endian.h> |
| #include <fcntl.h> |
| #include <math.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include "buffering.h" /* TYPE_PACKET_AUDIO */ |
| #include "codecs.h" |
| #include "dsp_core.h" |
| #include "metadata.h" |
| #include "settings.h" |
| #include "sound.h" |
| #include "tdspeed.h" |
| #include "kernel.h" |
| #include "platform.h" |
| |
| /***************** EXPORTED *****************/ |
| |
| struct user_settings global_settings; |
| |
| int set_irq_level(int level) |
| { |
| return 0; |
| } |
| |
| void mutex_init(struct mutex *m) |
| { |
| } |
| |
| void mutex_lock(struct mutex *m) |
| { |
| } |
| |
| void mutex_unlock(struct mutex *m) |
| { |
| } |
| |
| void debugf(const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| vfprintf(stderr, fmt, ap); |
| va_end(ap); |
| } |
| |
| int find_first_set_bit(uint32_t value) |
| { |
| if (value == 0) |
| return 32; |
| return __builtin_ctz(value); |
| } |
| |
| /***************** INTERNAL *****************/ |
| |
| static enum { MODE_PLAY, MODE_WRITE } mode; |
| static bool use_dsp = true; |
| static bool enable_loop = false; |
| static const char *config = ""; |
| |
| /* Volume control */ |
| #define VOL_FRACBITS 31 |
| #define VOL_FACTOR_UNITY (1u << VOL_FRACBITS) |
| static uint32_t playback_vol_factor = VOL_FACTOR_UNITY; |
| |
| static int input_fd; |
| static enum codec_command_action codec_action; |
| static intptr_t codec_action_param = 0; |
| static unsigned long num_output_samples = 0; |
| static struct codec_api ci; |
| |
| static struct { |
| intptr_t freq; |
| intptr_t stereo_mode; |
| intptr_t depth; |
| int channels; |
| } format; |
| |
| /***** MODE_WRITE *****/ |
| |
| #define WAVE_HEADER_SIZE 0x2e |
| #define WAVE_FORMAT_PCM 1 |
| #define WAVE_FORMAT_IEEE_FLOAT 3 |
| static int output_fd; |
| static bool write_raw = false; |
| static bool write_header_written = false; |
| |
| static void write_init(const char *output_fn) |
| { |
| mode = MODE_WRITE; |
| if (!strcmp(output_fn, "-")) { |
| output_fd = STDOUT_FILENO; |
| } else { |
| output_fd = creat(output_fn, 0666); |
| if (output_fd == -1) { |
| perror(output_fn); |
| exit(1); |
| } |
| } |
| } |
| |
| static void set_le16(char *buf, uint16_t val) |
| { |
| buf[0] = val; |
| buf[1] = val >> 8; |
| } |
| |
| static void set_le32(char *buf, uint32_t val) |
| { |
| buf[0] = val; |
| buf[1] = val >> 8; |
| buf[2] = val >> 16; |
| buf[3] = val >> 24; |
| } |
| |
| static void write_wav_header(void) |
| { |
| int channels, sample_size, freq, type; |
| if (use_dsp) { |
| channels = 2; |
| sample_size = 16; |
| freq = dsp_get_output_frequency(ci.dsp); |
| type = WAVE_FORMAT_PCM; |
| } else { |
| channels = format.channels; |
| sample_size = 64; |
| freq = format.freq; |
| type = WAVE_FORMAT_IEEE_FLOAT; |
| } |
| |
| /* The size fields are normally overwritten by write_quit(). If that fails, |
| * this fake size ensures the file can still be played. */ |
| off_t total_size = 0x7fffff00 + WAVE_HEADER_SIZE; |
| char header[WAVE_HEADER_SIZE] = {"RIFF____WAVEfmt \x12\0\0\0" |
| "________________\0\0data____"}; |
| set_le32(header + 0x04, total_size - 8); |
| set_le16(header + 0x14, type); |
| set_le16(header + 0x16, channels); |
| set_le32(header + 0x18, freq); |
| set_le32(header + 0x1c, freq * channels * sample_size / 8); |
| set_le16(header + 0x20, channels * sample_size / 8); |
| set_le16(header + 0x22, sample_size); |
| set_le32(header + 0x2a, total_size - WAVE_HEADER_SIZE); |
| write(output_fd, header, sizeof(header)); |
| write_header_written = true; |
| } |
| |
| static void write_quit(void) |
| { |
| if (!write_raw) { |
| /* Write the correct size fields in the header. If lseek fails (e.g. |
| * for a pipe) nothing is written. */ |
| off_t total_size = lseek(output_fd, 0, SEEK_CUR); |
| if (total_size != (off_t)-1) { |
| char buf[4]; |
| set_le32(buf, total_size - 8); |
| lseek(output_fd, 4, SEEK_SET); |
| write(output_fd, buf, 4); |
| set_le32(buf, total_size - WAVE_HEADER_SIZE); |
| lseek(output_fd, 0x2a, SEEK_SET); |
| write(output_fd, buf, 4); |
| } |
| } |
| if (output_fd != STDOUT_FILENO) |
| close(output_fd); |
| } |
| |
| static uint64_t make_float64(int32_t sample, int shift) |
| { |
| /* TODO: be more portable */ |
| double val = ldexp(sample, -shift); |
| return *(uint64_t*)&val; |
| } |
| |
| static void write_pcm(int16_t *pcm, int count) |
| { |
| if (!write_header_written) |
| write_wav_header(); |
| int i; |
| for (i = 0; i < 2 * count; i++) |
| pcm[i] = htole16(pcm[i]); |
| write(output_fd, pcm, 4 * count); |
| } |
| |
| static void write_pcm_raw(int32_t *pcm, int count) |
| { |
| if (write_raw) { |
| write(output_fd, pcm, count * sizeof(*pcm)); |
| } else { |
| if (!write_header_written) |
| write_wav_header(); |
| int i; |
| uint64_t buf[count]; |
| |
| for (i = 0; i < count; i++) |
| buf[i] = htole64(make_float64(pcm[i], format.depth)); |
| write(output_fd, buf, count * sizeof(*buf)); |
| } |
| } |
| |
| /***** MODE_PLAY *****/ |
| |
| /* MODE_PLAY uses a double buffer: one half is read by the playback thread and |
| * the other half is written to by the main thread. When a thread is done with |
| * its current half, it waits for the other thread and then switches. The main |
| * advantage of this method is its simplicity; the main disadvantage is that it |
| * has long latency. ALSA buffer underruns still occur sometimes, but this is |
| * SDL's fault. */ |
| |
| #define PLAYBACK_BUFFER_SIZE 0x10000 |
| static bool playback_running = false; |
| static char playback_buffer[2][PLAYBACK_BUFFER_SIZE]; |
| static int playback_play_ind, playback_decode_ind; |
| static int playback_play_pos, playback_decode_pos; |
| static SDL_sem *playback_play_sema, *playback_decode_sema; |
| |
| static void playback_init(void) |
| { |
| mode = MODE_PLAY; |
| if (SDL_Init(SDL_INIT_AUDIO)) { |
| fprintf(stderr, "error: Can't initialize SDL: %s\n", SDL_GetError()); |
| exit(1); |
| } |
| playback_play_ind = 1; |
| playback_play_pos = PLAYBACK_BUFFER_SIZE; |
| playback_decode_ind = 0; |
| playback_decode_pos = 0; |
| playback_play_sema = SDL_CreateSemaphore(0); |
| playback_decode_sema = SDL_CreateSemaphore(0); |
| } |
| |
| static void playback_copy_audio_buffer_S16SYS( |
| void *dst, const void *src, int len) |
| { |
| int64_t factor = playback_vol_factor; |
| |
| if (factor == VOL_FACTOR_UNITY) { |
| memcpy(dst, src, len); |
| } else { |
| const int16_t *s = src; |
| int16_t *d = dst; |
| |
| while (len) { |
| *d++ = factor * *s++ >> VOL_FRACBITS; |
| *d++ = factor * *s++ >> VOL_FRACBITS; |
| len -= sizeof (int16_t) * 2; |
| } |
| } |
| } |
| |
| static void playback_set_volume(int volume) |
| { |
| if (volume > 0) |
| volume = 0; |
| |
| playback_vol_factor = pow(10, (double)volume / 20.0) * VOL_FACTOR_UNITY; |
| } |
| |
| static void playback_callback(void *userdata, Uint8 *stream, int len) |
| { |
| while (len > 0) { |
| if (!playback_running && playback_play_ind == playback_decode_ind |
| && playback_play_pos >= playback_decode_pos) { |
| /* end of data */ |
| memset(stream, 0, len); |
| SDL_SemPost(playback_play_sema); |
| return; |
| } |
| if (playback_play_pos >= PLAYBACK_BUFFER_SIZE) { |
| SDL_SemPost(playback_play_sema); |
| SDL_SemWait(playback_decode_sema); |
| playback_play_ind = !playback_play_ind; |
| playback_play_pos = 0; |
| } |
| char *play_buffer = playback_buffer[playback_play_ind]; |
| int copy_len = MIN(len, PLAYBACK_BUFFER_SIZE - playback_play_pos); |
| playback_copy_audio_buffer_S16SYS(stream, |
| play_buffer + playback_play_pos, copy_len); |
| len -= copy_len; |
| stream += copy_len; |
| playback_play_pos += copy_len; |
| } |
| } |
| |
| static void playback_start(void) |
| { |
| playback_running = true; |
| SDL_AudioSpec spec = {0}; |
| spec.freq = dsp_get_output_frequency(ci.dsp); |
| spec.format = AUDIO_S16SYS; |
| spec.channels = 2; |
| spec.samples = 0x400; |
| spec.callback = playback_callback; |
| spec.userdata = NULL; |
| if (SDL_OpenAudio(&spec, NULL)) { |
| fprintf(stderr, "error: Can't open SDL audio: %s\n", SDL_GetError()); |
| exit(1); |
| } |
| SDL_PauseAudio(0); |
| } |
| |
| static void playback_quit(void) |
| { |
| if (!playback_running) |
| playback_start(); |
| memset(playback_buffer[playback_decode_ind] + playback_decode_pos, 0, |
| PLAYBACK_BUFFER_SIZE - playback_decode_pos); |
| playback_running = false; |
| SDL_SemPost(playback_decode_sema); |
| SDL_SemWait(playback_play_sema); |
| SDL_SemWait(playback_play_sema); |
| SDL_Quit(); |
| } |
| |
| static void playback_pcm(int16_t *pcm, int count) |
| { |
| const char *stream = (const char *)pcm; |
| count *= 4; |
| |
| while (count > 0) { |
| if (playback_decode_pos >= PLAYBACK_BUFFER_SIZE) { |
| if (!playback_running) |
| playback_start(); |
| SDL_SemPost(playback_decode_sema); |
| SDL_SemWait(playback_play_sema); |
| playback_decode_ind = !playback_decode_ind; |
| playback_decode_pos = 0; |
| } |
| char *decode_buffer = playback_buffer[playback_decode_ind]; |
| int copy_len = MIN(count, PLAYBACK_BUFFER_SIZE - playback_decode_pos); |
| memcpy(decode_buffer + playback_decode_pos, stream, copy_len); |
| stream += copy_len; |
| count -= copy_len; |
| playback_decode_pos += copy_len; |
| } |
| } |
| |
| /***** ALL MODES *****/ |
| |
| static void perform_config(void) |
| { |
| /* TODO: equalizer, etc. */ |
| while (config) { |
| const char *name = config; |
| const char *eq = strchr(config, '='); |
| if (!eq) |
| break; |
| const char *val = eq + 1; |
| const char *end = val + strcspn(val, ": \t\n"); |
| |
| if (!strncmp(name, "wait=", 5)) { |
| if (atoi(val) > num_output_samples) |
| return; |
| } else if (!strncmp(name, "dither=", 7)) { |
| dsp_dither_enable(atoi(val) ? true : false); |
| } else if (!strncmp(name, "halt=", 5)) { |
| if (atoi(val)) |
| codec_action = CODEC_ACTION_HALT; |
| } else if (!strncmp(name, "loop=", 5)) { |
| enable_loop = atoi(val) != 0; |
| } else if (!strncmp(name, "offset=", 7)) { |
| ci.id3->offset = atoi(val); |
| } else if (!strncmp(name, "rate=", 5)) { |
| dsp_set_pitch(atof(val) * PITCH_SPEED_100); |
| } else if (!strncmp(name, "seek=", 5)) { |
| codec_action = CODEC_ACTION_SEEK_TIME; |
| codec_action_param = atoi(val); |
| } else if (!strncmp(name, "tempo=", 6)) { |
| dsp_set_timestretch(atof(val) * PITCH_SPEED_100); |
| } else if (!strncmp(name, "vol=", 4)) { |
| playback_set_volume(atoi(val)); |
| } else { |
| fprintf(stderr, "error: unrecognized config \"%.*s\"\n", |
| (int)(eq - name), name); |
| exit(1); |
| } |
| |
| if (*end) |
| config = end + 1; |
| else |
| config = NULL; |
| } |
| } |
| |
| static void *ci_codec_get_buffer(size_t *size) |
| { |
| static char buffer[64 * 1024 * 1024]; |
| char *ptr = buffer; |
| *size = sizeof(buffer); |
| if ((intptr_t)ptr & (CACHEALIGN_SIZE - 1)) |
| ptr += CACHEALIGN_SIZE - ((intptr_t)ptr & (CACHEALIGN_SIZE - 1)); |
| return ptr; |
| } |
| |
| static void ci_pcmbuf_insert(const void *ch1, const void *ch2, int count) |
| { |
| num_output_samples += count; |
| |
| if (use_dsp) { |
| struct dsp_buffer src; |
| src.remcount = count; |
| src.pin[0] = ch1; |
| src.pin[1] = ch2; |
| src.proc_mask = 0; |
| while (1) { |
| int out_count = MAX(count, 512); |
| int16_t buf[2 * out_count]; |
| struct dsp_buffer dst; |
| |
| dst.remcount = 0; |
| dst.p16out = buf; |
| dst.bufcount = out_count; |
| |
| dsp_process(ci.dsp, &src, &dst); |
| |
| if (dst.remcount > 0) { |
| if (mode == MODE_WRITE) |
| write_pcm(buf, dst.remcount); |
| else if (mode == MODE_PLAY) |
| playback_pcm(buf, dst.remcount); |
| } else if (src.remcount <= 0) { |
| break; |
| } |
| } |
| } else { |
| /* Convert to 32-bit interleaved. */ |
| count *= format.channels; |
| int i; |
| int32_t buf[count]; |
| if (format.depth > 16) { |
| if (format.stereo_mode == STEREO_NONINTERLEAVED) { |
| for (i = 0; i < count; i += 2) { |
| buf[i+0] = ((int32_t*)ch1)[i/2]; |
| buf[i+1] = ((int32_t*)ch2)[i/2]; |
| } |
| } else { |
| memcpy(buf, ch1, sizeof(buf)); |
| } |
| } else { |
| if (format.stereo_mode == STEREO_NONINTERLEAVED) { |
| for (i = 0; i < count; i += 2) { |
| buf[i+0] = ((int16_t*)ch1)[i/2]; |
| buf[i+1] = ((int16_t*)ch2)[i/2]; |
| } |
| } else { |
| for (i = 0; i < count; i++) { |
| buf[i] = ((int16_t*)ch1)[i]; |
| } |
| } |
| } |
| |
| if (mode == MODE_WRITE) |
| write_pcm_raw(buf, count); |
| } |
| |
| perform_config(); |
| } |
| |
| static void ci_set_elapsed(unsigned long value) |
| { |
| //debugf("Time elapsed: %lu\n", value); |
| } |
| |
| static char *input_buffer = 0; |
| |
| /* |
| * Read part of the input file into a provided buffer. |
| * |
| * The entire size requested will be provided except at the end of the file. |
| * The current file position will be moved, just like with advance_buffer, but |
| * the offset is not updated. This invalidates buffers returned by |
| * request_buffer. |
| */ |
| static size_t ci_read_filebuf(void *ptr, size_t size) |
| { |
| free(input_buffer); |
| input_buffer = NULL; |
| |
| ssize_t actual = read(input_fd, ptr, size); |
| if (actual < 0) |
| actual = 0; |
| ci.curpos += actual; |
| return actual; |
| } |
| |
| /* |
| * Request a buffer containing part of the input file. |
| * |
| * The size provided will be the requested size, or the remaining size of the |
| * file, whichever is smaller. Packet audio has an additional maximum of 32 |
| * KiB. The returned buffer remains valid until the next time read_filebuf, |
| * request_buffer, advance_buffer, or seek_buffer is called. |
| */ |
| static void *ci_request_buffer(size_t *realsize, size_t reqsize) |
| { |
| free(input_buffer); |
| if (!rbcodec_format_is_atomic(ci.id3->codectype)) |
| reqsize = MIN(reqsize, 32 * 1024); |
| input_buffer = malloc(reqsize); |
| *realsize = read(input_fd, input_buffer, reqsize); |
| if (*realsize < 0) |
| *realsize = 0; |
| lseek(input_fd, -*realsize, SEEK_CUR); |
| return input_buffer; |
| } |
| |
| /* |
| * Advance the current position in the input file. |
| * |
| * This automatically updates the current offset. This invalidates buffers |
| * returned by request_buffer. |
| */ |
| static void ci_advance_buffer(size_t amount) |
| { |
| free(input_buffer); |
| input_buffer = NULL; |
| |
| lseek(input_fd, amount, SEEK_CUR); |
| ci.curpos += amount; |
| ci.id3->offset = ci.curpos; |
| } |
| |
| /* |
| * Seek to a position in the input file. |
| * |
| * This invalidates buffers returned by request_buffer. |
| */ |
| static bool ci_seek_buffer(size_t newpos) |
| { |
| free(input_buffer); |
| input_buffer = NULL; |
| |
| off_t actual = lseek(input_fd, newpos, SEEK_SET); |
| if (actual >= 0) |
| ci.curpos = actual; |
| return actual != -1; |
| } |
| |
| static void ci_seek_complete(void) |
| { |
| } |
| |
| static void ci_set_offset(size_t value) |
| { |
| ci.id3->offset = value; |
| } |
| |
| static void ci_configure(int setting, intptr_t value) |
| { |
| if (use_dsp) { |
| dsp_configure(ci.dsp, setting, value); |
| } else { |
| if (setting == DSP_SET_FREQUENCY |
| || setting == DSP_SET_FREQUENCY) |
| format.freq = value; |
| else if (setting == DSP_SET_SAMPLE_DEPTH) |
| format.depth = value; |
| else if (setting == DSP_SET_STEREO_MODE) { |
| format.stereo_mode = value; |
| format.channels = (value == STEREO_MONO) ? 1 : 2; |
| } |
| } |
| } |
| |
| static enum codec_command_action ci_get_command(intptr_t *param) |
| { |
| enum codec_command_action ret = codec_action; |
| *param = codec_action_param; |
| codec_action = CODEC_ACTION_NULL; |
| return ret; |
| } |
| |
| static bool ci_should_loop(void) |
| { |
| return enable_loop; |
| } |
| |
| static unsigned ci_sleep(unsigned ticks) |
| { |
| return 0; |
| } |
| |
| static void ci_debugf(const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| vfprintf(stderr, fmt, ap); |
| va_end(ap); |
| } |
| |
| #ifdef ROCKBOX_HAS_LOGF |
| static void ci_logf(const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| vfprintf(stderr, fmt, ap); |
| putc('\n', stderr); |
| va_end(ap); |
| } |
| #endif |
| |
| static void ci_yield(void) |
| { |
| } |
| |
| static void commit_dcache(void) {} |
| static void commit_discard_dcache(void) {} |
| static void commit_discard_idcache(void) {} |
| |
| static struct codec_api ci = { |
| |
| 0, /* filesize */ |
| 0, /* curpos */ |
| NULL, /* id3 */ |
| -1, /* audio_hid */ |
| NULL, /* struct dsp_config *dsp */ |
| ci_codec_get_buffer, |
| ci_pcmbuf_insert, |
| ci_set_elapsed, |
| ci_read_filebuf, |
| ci_request_buffer, |
| ci_advance_buffer, |
| ci_seek_buffer, |
| ci_seek_complete, |
| ci_set_offset, |
| ci_configure, |
| ci_get_command, |
| ci_should_loop, |
| |
| ci_sleep, |
| ci_yield, |
| |
| #if NUM_CORES > 1 |
| ci_create_thread, |
| ci_thread_thaw, |
| ci_thread_wait, |
| ci_semaphore_init, |
| ci_semaphore_wait, |
| ci_semaphore_release, |
| #endif |
| |
| commit_dcache, |
| commit_discard_dcache, |
| commit_discard_idcache, |
| |
| /* strings and memory */ |
| strcpy, |
| strlen, |
| strcmp, |
| strcat, |
| memset, |
| memcpy, |
| memmove, |
| memcmp, |
| memchr, |
| #if defined(DEBUG) || defined(SIMULATOR) |
| ci_debugf, |
| #endif |
| #ifdef ROCKBOX_HAS_LOGF |
| ci_logf, |
| #endif |
| |
| qsort, |
| |
| #ifdef HAVE_RECORDING |
| ci_enc_get_inputs, |
| ci_enc_set_parameters, |
| ci_enc_get_chunk, |
| ci_enc_finish_chunk, |
| ci_enc_get_pcm_data, |
| ci_enc_unget_pcm_data, |
| |
| /* file */ |
| open, |
| close, |
| read, |
| lseek, |
| write, |
| ci_round_value_to_list32, |
| |
| #endif /* HAVE_RECORDING */ |
| }; |
| |
| static void print_mp3entry(const struct mp3entry *id3, FILE *f) |
| { |
| fprintf(f, "Path: %s\n", id3->path); |
| if (id3->title) fprintf(f, "Title: %s\n", id3->title); |
| if (id3->artist) fprintf(f, "Artist: %s\n", id3->artist); |
| if (id3->album) fprintf(f, "Album: %s\n", id3->album); |
| if (id3->genre_string) fprintf(f, "Genre: %s\n", id3->genre_string); |
| if (id3->disc_string || id3->discnum) fprintf(f, "Disc: %s (%d)\n", id3->disc_string, id3->discnum); |
| if (id3->track_string || id3->tracknum) fprintf(f, "Track: %s (%d)\n", id3->track_string, id3->tracknum); |
| if (id3->year_string || id3->year) fprintf(f, "Year: %s (%d)\n", id3->year_string, id3->year); |
| if (id3->composer) fprintf(f, "Composer: %s\n", id3->composer); |
| if (id3->comment) fprintf(f, "Comment: %s\n", id3->comment); |
| if (id3->albumartist) fprintf(f, "Album artist: %s\n", id3->albumartist); |
| if (id3->grouping) fprintf(f, "Grouping: %s\n", id3->grouping); |
| if (id3->layer) fprintf(f, "Layer: %d\n", id3->layer); |
| if (id3->id3version) fprintf(f, "ID3 version: %u\n", (int)id3->id3version); |
| fprintf(f, "Codec: %s\n", audio_formats[id3->codectype].label); |
| fprintf(f, "Bitrate: %d kb/s\n", id3->bitrate); |
| fprintf(f, "Frequency: %lu Hz\n", id3->frequency); |
| if (id3->id3v2len) fprintf(f, "ID3v2 length: %lu\n", id3->id3v2len); |
| if (id3->id3v1len) fprintf(f, "ID3v1 length: %lu\n", id3->id3v1len); |
| if (id3->first_frame_offset) fprintf(f, "First frame offset: %lu\n", id3->first_frame_offset); |
| fprintf(f, "File size without headers: %lu\n", id3->filesize); |
| fprintf(f, "Song length: %lu ms\n", id3->length); |
| if (id3->lead_trim > 0 || id3->tail_trim > 0) fprintf(f, "Trim: %d/%d\n", id3->lead_trim, id3->tail_trim); |
| if (id3->samples) fprintf(f, "Number of samples: %lu\n", id3->samples); |
| if (id3->frame_count) fprintf(f, "Number of frames: %lu\n", id3->frame_count); |
| if (id3->bytesperframe) fprintf(f, "Bytes per frame: %lu\n", id3->bytesperframe); |
| if (id3->vbr) fprintf(f, "VBR: true\n"); |
| if (id3->has_toc) fprintf(f, "Has TOC: true\n"); |
| if (id3->channels) fprintf(f, "Number of channels: %u\n", id3->channels); |
| if (id3->extradata_size) fprintf(f, "Size of extra data: %u\n", id3->extradata_size); |
| if (id3->needs_upsampling_correction) fprintf(f, "Needs upsampling correction: true\n"); |
| /* TODO: replaygain; albumart; cuesheet */ |
| if (id3->mb_track_id) fprintf(f, "Musicbrainz track ID: %s\n", id3->mb_track_id); |
| } |
| |
| static void decode_file(const char *input_fn) |
| { |
| /* Initialize DSP before any sort of interaction */ |
| dsp_init(); |
| |
| /* Set up global settings */ |
| memset(&global_settings, 0, sizeof(global_settings)); |
| global_settings.timestretch_enabled = true; |
| dsp_timestretch_enable(true); |
| |
| /* Open file */ |
| if (!strcmp(input_fn, "-")) { |
| input_fd = STDIN_FILENO; |
| } else { |
| input_fd = open(input_fn, O_RDONLY); |
| if (input_fd == -1) { |
| perror(input_fn); |
| exit(1); |
| } |
| } |
| |
| /* Set up ci */ |
| struct mp3entry id3; |
| if (!get_metadata(&id3, input_fd, input_fn)) { |
| fprintf(stderr, "error: metadata parsing failed\n"); |
| exit(1); |
| } |
| print_mp3entry(&id3, stderr); |
| ci.filesize = filesize(input_fd); |
| ci.id3 = &id3; |
| if (use_dsp) { |
| ci.dsp = dsp_get_config(CODEC_IDX_AUDIO); |
| dsp_configure(ci.dsp, DSP_SET_OUT_FREQUENCY, DSP_OUT_DEFAULT_HZ); |
| dsp_configure(ci.dsp, DSP_RESET, 0); |
| dsp_dither_enable(false); |
| } |
| perform_config(); |
| |
| /* Load codec */ |
| char str[MAX_PATH]; |
| snprintf(str, sizeof(str), CODECDIR"/%s.codec", audio_formats[id3.codectype].codec_root_fn); |
| debugf("Loading %s\n", str); |
| void *dlcodec = dlopen(str, RTLD_NOW); |
| if (!dlcodec) { |
| fprintf(stderr, "error: dlopen failed: %s\n", dlerror()); |
| exit(1); |
| } |
| struct codec_header *c_hdr = NULL; |
| c_hdr = dlsym(dlcodec, "__header"); |
| if (c_hdr->lc_hdr.magic != CODEC_MAGIC) { |
| fprintf(stderr, "error: %s invalid: incorrect magic\n", str); |
| exit(1); |
| } |
| if (c_hdr->lc_hdr.target_id != TARGET_ID) { |
| fprintf(stderr, "error: %s invalid: incorrect target id\n", str); |
| exit(1); |
| } |
| if (c_hdr->lc_hdr.api_version != CODEC_API_VERSION) { |
| fprintf(stderr, "error: %s invalid: incorrect API version\n", str); |
| exit(1); |
| } |
| |
| /* Run the codec */ |
| *c_hdr->api = &ci; |
| if (c_hdr->entry_point(CODEC_LOAD) != CODEC_OK) { |
| fprintf(stderr, "error: codec returned error from codec_main\n"); |
| exit(1); |
| } |
| if (c_hdr->run_proc() != CODEC_OK) { |
| fprintf(stderr, "error: codec error\n"); |
| } |
| c_hdr->entry_point(CODEC_UNLOAD); |
| |
| /* Close */ |
| dlclose(dlcodec); |
| if (input_fd != STDIN_FILENO) |
| close(input_fd); |
| } |
| |
| static void print_help(const char *progname) |
| { |
| fprintf(stderr, "Usage:\n" |
| " Play: %s [options] INPUTFILE\n" |
| "Write to WAV: %s [options] INPUTFILE OUTPUTFILE\n" |
| "\n" |
| "general options:\n" |
| " -c a=1:b=2 Configuration (see below)\n" |
| " -h Show this help\n" |
| "\n" |
| "write to WAV options:\n" |
| " -f Write raw codec output converted to 64-bit float\n" |
| " -r Write raw 32-bit codec output without WAV header\n" |
| "\n" |
| "configuration:\n" |
| " dither=<0|1> Enable/disable dithering [0]\n" |
| " halt=<0|1> Stop decoding if 1 [0]\n" |
| " loop=<0|1> Enable/disable looping [0]\n" |
| " offset=<n> Start at byte offset within the file [0]\n" |
| " rate=<n> Multiply rate by <n> [1.0]\n" |
| " seek=<n> Seek <n> ms into the file\n" |
| " tempo=<n> Timestretch by <n> [1.0]\n" |
| " vol=<n> Set volume attenuation to <n> dB [-0]\n" |
| " wait=<n> Don't apply remaining configuration until\n" |
| " <n> total samples have output\n" |
| "\n" |
| "examples:\n" |
| " # Play while looping; stop after 44100 output samples\n" |
| " %s in.adx -c loop=1:wait=44100:halt=1\n" |
| " # Lower pitch 1 octave and write to out.wav\n" |
| " %s in.ogg -c rate=0.5:tempo=2 out.wav\n" |
| , progname, progname, progname, progname); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int opt; |
| while ((opt = getopt(argc, argv, "c:fhr")) != -1) { |
| switch (opt) { |
| case 'c': |
| config = optarg; |
| break; |
| case 'f': |
| use_dsp = false; |
| break; |
| case 'r': |
| use_dsp = false; |
| write_raw = true; |
| break; |
| case 'h': /* fallthrough */ |
| default: |
| print_help(argv[0]); |
| exit(1); |
| } |
| } |
| |
| if (argc == optind + 2) { |
| write_init(argv[optind + 1]); |
| } else if (argc == optind + 1) { |
| if (!use_dsp) { |
| fprintf(stderr, "error: -r can't be used for playback\n"); |
| print_help(argv[0]); |
| exit(1); |
| } |
| playback_init(); |
| } else { |
| if (argc > 1) |
| fprintf(stderr, "error: wrong number of arguments\n"); |
| print_help(argv[0]); |
| exit(1); |
| } |
| |
| decode_file(argv[optind]); |
| |
| if (mode == MODE_WRITE) |
| write_quit(); |
| else if (mode == MODE_PLAY) |
| playback_quit(); |
| |
| return 0; |
| } |