/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2005 Miika Pekkarinen * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include #include #include #include #include "system.h" #include "thread.h" #include "file.h" #include "lcd.h" #include "font.h" #include "backlight.h" #include "button.h" #include "kernel.h" #include "tree.h" #include "debug.h" #include "sprintf.h" #include "settings.h" #include "plugin.h" #include "wps.h" #include "wps-display.h" #include "audio.h" #include "logf.h" #include "mp3_playback.h" #include "mp3data.h" #include "usb.h" #include "status.h" #include "main_menu.h" #include "ata.h" #include "screens.h" #include "playlist.h" #include "playback.h" #include "pcm_playback.h" #include "buffer.h" #ifdef HAVE_LCD_BITMAP #include "icons.h" #include "peakmeter.h" #include "action.h" #endif #include "lang.h" #include "bookmark.h" #include "misc.h" #include "sound.h" static volatile bool playing; static volatile bool paused; #define CODEC_VORBIS "/.rockbox/codecs/codecvorbis.rock"; #define CODEC_MPA_L3 "/.rockbox/codecs/codecmpa.rock"; #define CODEC_FLAC "/.rockbox/codecs/codecflac.rock"; #define CODEC_WAV "/.rockbox/codecs/codecwav.rock"; #define CODEC_A52 "/.rockbox/codecs/codeca52.rock"; #define CODEC_MPC "/.rockbox/codecs/codecmpc.rock"; #define AUDIO_DEFAULT_WATERMARK (1024*256) #define AUDIO_DEFAULT_FILECHUNK (1024*32) #define AUDIO_PLAY 1 #define AUDIO_STOP 2 #define AUDIO_PAUSE 3 #define AUDIO_RESUME 4 #define AUDIO_NEXT 5 #define AUDIO_PREV 6 #define AUDIO_FF_REWIND 7 #define AUDIO_FLUSH_RELOAD 8 #define AUDIO_CODEC_DONE 9 #define CODEC_LOAD 1 #define CODEC_LOAD_DISK 2 /* As defined in plugins/lib/xxx2wav.h */ #define MALLOC_BUFSIZE (512*1024) #define GUARD_BUFSIZE (8*1024) /* TODO: Handle playlist_peek in mpeg.c Track changing */ extern bool audio_is_initialized; /* Buffer control thread. */ static struct event_queue audio_queue; static long audio_stack[(DEFAULT_STACK_SIZE + 0x1000)/sizeof(long)]; static const char audio_thread_name[] = "audio"; /* Codec thread. */ static struct event_queue codec_queue; static long codec_stack[(DEFAULT_STACK_SIZE + 0x2500)/sizeof(long)] __attribute__ ((section(".idata"))); static const char codec_thread_name[] = "codec"; /* Is file buffer currently being refilled? */ static volatile bool filling; /* Ring buffer where tracks and codecs are loaded. */ char *codecbuf; /* Total size of the ring buffer. */ int codecbuflen; /* Bytes available in the buffer. */ static volatile int codecbufused; /* Ring buffer read and write indexes. */ static volatile int buf_ridx; static volatile int buf_widx; #define MAX_TRACK 10 struct track_info { struct mp3entry id3; struct mp3info mp3data; char *codecbuf; size_t codecsize; int codectype; volatile char *filebuf; off_t filerem; off_t filesize; off_t filepos; volatile int available; bool taginfo_ready; int playlist_offset; }; /* Track information (count in file buffer, read/write indexes for track ring structure. */ static int track_count; static volatile int track_ridx; static volatile int track_widx; static bool track_changed; /* Partially loaded song's file handle to continue buffering later. */ static int current_fd; /* Information about how many bytes left on the buffer re-fill run. */ static size_t fill_bytesleft; /* Track info structure about songs in the file buffer. */ static struct track_info tracks[MAX_TRACK]; /* Pointer to track info structure about current song playing. */ static volatile struct track_info *cur_ti; /* Codec API including function callbacks. */ static struct codec_api ci; /* When we change a song and buffer is not in filling state, this variable keeps information about whether to go a next/previous track. */ static int new_track; /* Configuration */ static int conf_bufferlimit; static int conf_watermark; static int conf_filechunk; static bool v1first = false; static void mp3_set_elapsed(struct mp3entry* id3); int mp3_get_file_pos(void); #ifdef SIMULATOR bool audiobuffer_insert_sim(char *buf, size_t length) { (void)buf; (void)length; return true; } #endif void* get_codec_memory_callback(size_t *size) { *size = MALLOC_BUFSIZE; return &audiobuf[0]; } void codec_set_elapsed_callback(unsigned int value) { unsigned int latency; #ifndef SIMULATOR latency = audiobuffer_get_latency(); #else latency = 0; #endif if (value < latency) { cur_ti->id3.elapsed = 0; } else if (value - latency > cur_ti->id3.elapsed || value - latency < cur_ti->id3.elapsed - 2) { cur_ti->id3.elapsed = value - latency; } } size_t codec_filebuf_callback(void *ptr, size_t size) { char *buf = (char *)ptr; int copy_n; int part_n; if (ci.stop_codec || !playing) return 0; copy_n = MIN((off_t)size, (off_t)cur_ti->available + cur_ti->filerem); while (copy_n > cur_ti->available) { yield(); if (ci.stop_codec) return 0; } if (copy_n == 0) return 0; part_n = MIN(copy_n, codecbuflen - buf_ridx); memcpy(buf, &codecbuf[buf_ridx], part_n); if (part_n < copy_n) { memcpy(&buf[part_n], &codecbuf[0], copy_n - part_n); } buf_ridx += copy_n; if (buf_ridx >= codecbuflen) buf_ridx -= codecbuflen; ci.curpos += copy_n; cur_ti->available -= copy_n; codecbufused -= copy_n; return copy_n; } void* codec_request_buffer_callback(size_t *realsize, size_t reqsize) { size_t part_n; if (ci.stop_codec || !playing) { *realsize = 0; return NULL; } *realsize = MIN((off_t)reqsize, (off_t)cur_ti->available + cur_ti->filerem); if (*realsize == 0) { return NULL; } while ((int)*realsize > cur_ti->available) { yield(); if (ci.stop_codec) { *realsize = 0; return NULL; } } part_n = MIN((int)*realsize, codecbuflen - buf_ridx); if (part_n < *realsize) { part_n += GUARD_BUFSIZE; if (part_n < *realsize) *realsize = part_n; memcpy(&codecbuf[codecbuflen], &codecbuf[0], *realsize - (codecbuflen - buf_ridx)); } return (char *)&codecbuf[buf_ridx]; } void codec_advance_buffer_callback(size_t amount) { if ((int)amount > cur_ti->available + cur_ti->filerem) amount = cur_ti->available + cur_ti->filerem; if ((int)amount > cur_ti->available) { codecbufused = 0; buf_ridx = buf_widx; cur_ti->available = 0; while ((int)amount < cur_ti->available && !ci.stop_codec) yield(); } else { cur_ti->available -= amount; codecbufused -= amount; buf_ridx += amount; if (buf_ridx >= codecbuflen) buf_ridx -= codecbuflen; } ci.curpos += amount; cur_ti->id3.offset = ci.curpos; } void codec_advance_buffer_loc_callback(void *ptr) { size_t amount; amount = (int)ptr - (int)&codecbuf[buf_ridx]; codec_advance_buffer_callback(amount); } off_t codec_mp3_get_filepos_callback(int newtime) { off_t newpos; cur_ti->id3.elapsed = newtime; newpos = mp3_get_file_pos(); return newpos; } bool codec_seek_buffer_callback(off_t newpos) { int difference; if (newpos < 0) newpos = 0; if (newpos >= cur_ti->filesize) newpos = cur_ti->filesize - 1; difference = newpos - ci.curpos; if (difference >= 0) { logf("seek: +%d", difference); codec_advance_buffer_callback(difference); #ifndef SIMULATOR pcm_play_stop(); #endif return true; } difference = -difference; if (ci.curpos - difference < 0) difference = ci.curpos; if (codecbufused + difference > codecbuflen) { /* We need to reload the song. FIX THIS! */ return false; } logf("seek: -%d", difference); codecbufused += difference; cur_ti->available += difference; buf_ridx -= difference; if (buf_ridx < 0) buf_ridx = codecbuflen + buf_ridx; ci.curpos -= difference; #ifndef SIMULATOR pcm_play_stop(); #endif return true; } void codec_configure_callback(int setting, void *value) { switch (setting) { case CODEC_SET_FILEBUF_WATERMARK: conf_watermark = (unsigned int)value; break; case CODEC_SET_FILEBUF_CHUNKSIZE: conf_filechunk = (unsigned int)value; break; case CODEC_SET_FILEBUF_LIMIT: conf_bufferlimit = (unsigned int)value; break; default: logf("Illegal key: %d", setting); } } /* Simple file type probing by looking filename extension. */ int probe_file_format(const char *filename) { char *suffix; suffix = strrchr(filename, '.'); if (suffix == NULL) return AFMT_UNKNOWN; suffix += 1; if (!strcasecmp("mp1", suffix)) return AFMT_MPA_L1; else if (!strcasecmp("mp2", suffix)) return AFMT_MPA_L2; else if (!strcasecmp("mpa", suffix)) return AFMT_MPA_L2; else if (!strcasecmp("mp3", suffix)) return AFMT_MPA_L3; else if (!strcasecmp("ogg", suffix)) return AFMT_OGG_VORBIS; else if (!strcasecmp("wav", suffix)) return AFMT_PCM_WAV; else if (!strcasecmp("flac", suffix)) return AFMT_FLAC; else if (!strcasecmp("mpc", suffix)) return AFMT_MPC; else if (!strcasecmp("aac", suffix)) return AFMT_AAC; else if (!strcasecmp("ape", suffix)) return AFMT_APE; else if (!strcasecmp("wma", suffix)) return AFMT_WMA; else if ((!strcasecmp("a52", suffix)) || (!strcasecmp("ac3", suffix))) return AFMT_A52; else if (!strcasecmp("rm", suffix)) return AFMT_REAL; return AFMT_UNKNOWN; } void yield_codecs(void) { yield(); #ifndef SIMULATOR if (!pcm_is_playing()) sleep(5); while (pcm_is_lowdata() && !ci.stop_codec && playing && queue_empty(&audio_queue)) yield(); #endif } void audio_fill_file_buffer(void) { size_t i; int rc; logf("Filling buffer..."); i = 0; while ((off_t)i < tracks[track_widx].filerem) { /* Give codecs some processing time. */ yield_codecs(); if (!queue_empty(&audio_queue)) { logf("Filling interrupted"); close(current_fd); current_fd = -1; return ; } if (fill_bytesleft < MIN((unsigned int)conf_filechunk, tracks[track_widx].filerem - i)) break ; rc = MIN(conf_filechunk, codecbuflen - buf_widx); rc = read(current_fd, &codecbuf[buf_widx], rc); if (rc <= 0) { tracks[track_widx].filerem = 0; break ; } buf_widx += rc; if (buf_widx >= codecbuflen) buf_widx -= codecbuflen; i += rc; tracks[track_widx].available += rc; fill_bytesleft -= rc; } tracks[track_widx].filerem -= i; codecbufused += i; tracks[track_widx].filepos += i; logf("Done:%d", tracks[track_widx].available); if (tracks[track_widx].filerem == 0) { if (++track_widx == MAX_TRACK) track_widx = 0; tracks[track_widx].filerem = 0; close(current_fd); current_fd = -1; } } bool loadcodec(const char *trackname, bool start_play) { char msgbuf[80]; off_t size; int filetype; int fd; int i, rc; const char *codec_path; int copy_n; int prev_track; filetype = probe_file_format(trackname); switch (filetype) { case AFMT_OGG_VORBIS: logf("Codec: Vorbis"); codec_path = CODEC_VORBIS; break; case AFMT_MPA_L2: case AFMT_MPA_L3: logf("Codec: MPA L2/L3"); codec_path = CODEC_MPA_L3; break; case AFMT_PCM_WAV: logf("Codec: PCM WAV"); codec_path = CODEC_WAV; break; case AFMT_FLAC: logf("Codec: FLAC"); codec_path = CODEC_FLAC; break; case AFMT_A52: logf("Codec: A52"); codec_path = CODEC_A52; break; case AFMT_MPC: logf("Codec: Musepack"); codec_path = CODEC_MPC; break; default: logf("Codec: Unsupported"); snprintf(msgbuf, sizeof(msgbuf)-1, "No codec for: %s", trackname); splash(HZ*2, true, msgbuf); codec_path = NULL; } tracks[track_widx].codectype = filetype; tracks[track_widx].codecsize = 0; if (codec_path == NULL) return false; if (!start_play) { prev_track = track_widx - 1; if (prev_track < 0) prev_track = MAX_TRACK-1; if (track_count > 0 && filetype == tracks[prev_track].codectype) { logf("Reusing prev. codec"); return true; } } else { /* Load the codec directly from disk and save some memory. */ cur_ti = &tracks[track_widx]; ci.filesize = cur_ti->filesize; ci.id3 = (struct mp3entry *)&cur_ti->id3; ci.mp3data = (struct mp3info *)&cur_ti->mp3data; ci.taginfo_ready = (bool *)&cur_ti->taginfo_ready; ci.curpos = 0; playing = true; logf("Starting codec"); queue_post(&codec_queue, CODEC_LOAD_DISK, (void *)codec_path); return true; } fd = open(codec_path, O_RDONLY); if (fd < 0) { logf("Codec doesn't exist!"); snprintf(msgbuf, sizeof(msgbuf)-1, "Couldn't load codec: %s", codec_path); splash(HZ*2, true, msgbuf); return false; } size = filesize(fd); if ((off_t)fill_bytesleft < size + conf_watermark) { logf("Not enough space"); close(fd); return false; } i = 0; while (i < size) { yield_codecs(); if (!queue_empty(&audio_queue)) { logf("Buffering interrupted"); close(fd); return false; } copy_n = MIN(conf_filechunk, codecbuflen - buf_widx); rc = read(fd, &codecbuf[buf_widx], copy_n); if (rc < 0) return false; buf_widx += rc; if (buf_widx >= codecbuflen) buf_widx -= codecbuflen; i += rc; } close(fd); logf("Done: %dB", i); codecbufused += size; fill_bytesleft -= size; tracks[track_widx].codecsize = size; return true; } bool audio_load_track(int offset, bool start_play, int peek_offset) { char *trackname; int fd; off_t size; int rc, i; int copy_n; /* Used by the metadata parsers */ unsigned long totalsamples,bytespersample,channels,bitspersample,numbytes; unsigned char* buf; int j,eof; if (track_count >= MAX_TRACK) return false; trackname = playlist_peek(peek_offset); if (!trackname) { return false; } fd = open(trackname, O_RDONLY); if (fd < 0) return false; size = filesize(fd); tracks[track_widx].filerem = size; tracks[track_widx].filesize = size; tracks[track_widx].filepos = 0; tracks[track_widx].available = 0; tracks[track_widx].taginfo_ready = false; tracks[track_widx].playlist_offset = offset; /* Load the codec */ if (buf_widx >= codecbuflen) buf_widx -= codecbuflen; /* Set default values */ if (start_play) { conf_bufferlimit = 0; conf_watermark = AUDIO_DEFAULT_WATERMARK; conf_filechunk = AUDIO_DEFAULT_FILECHUNK; } tracks[track_widx].codecbuf = &codecbuf[buf_widx]; if (!loadcodec(trackname, start_play)) { close(fd); return false; } tracks[track_widx].filebuf = &codecbuf[buf_widx]; //logf("%s", trackname); logf("Buffering track:%d/%d", track_widx, track_ridx); if (!queue_empty(&audio_queue)) { logf("Interrupted!"); ci.stop_codec = true; close(fd); return false; } /* Load codec specific track tag information. */ switch (tracks[track_widx].codectype) { case AFMT_MPA_L2: case AFMT_MPA_L3: /* Should check the return value. */ mp3info(&tracks[track_widx].id3, trackname, v1first); lseek(fd, 0, SEEK_SET); /* This is too slow to execute on some files. */ // get_mp3file_info(fd, &tracks[track_widx].mp3data); if (offset) { lseek(fd, offset, SEEK_SET); tracks[track_widx].id3.offset = offset; mp3_set_elapsed(&tracks[track_widx].id3); tracks[track_widx].filepos = offset; tracks[track_widx].filerem = tracks[track_widx].filesize - offset; ci.curpos = offset; } else { lseek(fd, 0, SEEK_SET); } /* logf("T:%s", tracks[track_widx].id3.title); logf("L:%d", tracks[track_widx].id3.length); logf("O:%d", tracks[track_widx].id3.first_frame_offset); logf("F:%d", tracks[track_widx].id3.frequency); */ tracks[track_widx].taginfo_ready = true; break ; case AFMT_PCM_WAV: /* Use the trackname part of the id3 structure as a temporary buffer */ buf=tracks[track_widx].id3.path; lseek(fd, 0, SEEK_SET); rc = read(fd, buf, 44); if (rc < 44) { close(fd); return false; } if ((memcmp(buf,"RIFF",4)!=0) || (memcmp(&buf[8],"WAVEfmt",7)!=0)) { logf("%s is not a WAV file\n",trackname); close(fd); return(false); } /* FIX: Correctly parse WAV header - we assume canonical 44-byte header */ bitspersample=buf[34]; channels=buf[22]; if ((bitspersample!=16) || (channels != 2)) { logf("Unsupported WAV file - %d bitspersample, %d channels\n", bitspersample,channels); close(fd); return(false); } bytespersample=((bitspersample/8)*channels); numbytes=(buf[40]|(buf[41]<<8)|(buf[42]<<16)|(buf[43]<<24)); totalsamples=numbytes/bytespersample; tracks[track_widx].id3.vbr=false; /* All WAV files are CBR */ tracks[track_widx].id3.filesize=filesize(fd); tracks[track_widx].id3.frequency=buf[24]|(buf[25]<<8)|(buf[26]<<16)|(buf[27]<<24); /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ tracks[track_widx].id3.length=(totalsamples/tracks[track_widx].id3.frequency)*1000; tracks[track_widx].id3.bitrate=(tracks[track_widx].id3.frequency*bytespersample)/(1000/8); lseek(fd, 0, SEEK_SET); strncpy(tracks[track_widx].id3.path,trackname,sizeof(tracks[track_widx].id3.path)); tracks[track_widx].taginfo_ready = true; break; case AFMT_FLAC: /* A simple parser to read vital metadata from a FLAC file - length, frequency, bitrate etc. */ /* This code should either be moved to a seperate file, or discarded in favour of the libFLAC code */ /* The FLAC stream specification can be found at http://flac.sourceforge.net/format.html#stream */ /* Use the trackname part of the id3 structure as a temporary buffer */ buf=tracks[track_widx].id3.path; lseek(fd, 0, SEEK_SET); rc = read(fd, buf, 4); if (rc < 4) { close(fd); return false; } if (memcmp(buf,"fLaC",4)!=0) { logf("%s is not a FLAC file\n",trackname); close(fd); return(false); } while (1) { rc = read(fd, buf, 4); i = (buf[1]<<16)|(buf[2]<<8)|buf[3]; /* The length of the block */ if ((buf[0]&0x7f)==0) { /* 0 is the STREAMINFO block */ rc = read(fd, buf, i); /* FIXME: Don't trust the value of i */ if (rc < 0) { close(fd); return false; } tracks[track_widx].id3.vbr=true; /* All FLAC files are VBR */ tracks[track_widx].id3.filesize=filesize(fd); tracks[track_widx].id3.frequency=(buf[10]<<12)|(buf[11]<<4)|((buf[12]&0xf0)>>4); /* NOT NEEDED: bitspersample=(((buf[12]&0x01)<<4)|((buf[13]&0xf0)>>4))+1; */ /* totalsamples is a 36-bit field, but we assume <= 32 bits are used */ totalsamples=(buf[14]<<24)|(buf[15]<<16)|(buf[16]<<8)|buf[17]; /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ tracks[track_widx].id3.length=(totalsamples/tracks[track_widx].id3.frequency)*1000; tracks[track_widx].id3.bitrate=(filesize(fd)*8)/tracks[track_widx].id3.length; } else if ((buf[0]&0x7f)==4) { /* 4 is the VORBIS_COMMENT block */ /* The next i bytes of the file contain the VORBIS COMMENTS - just skip them for now. */ lseek(fd, i, SEEK_CUR); } else { if (buf[0]&0x80) { /* If we have reached the last metadata block, abort. */ break; } else { lseek(fd, i, SEEK_CUR); /* Skip to next metadata block */ } } } lseek(fd, 0, SEEK_SET); strncpy(tracks[track_widx].id3.path,trackname,sizeof(tracks[track_widx].id3.path)); tracks[track_widx].taginfo_ready = true; break; case AFMT_OGG_VORBIS: /* A simple parser to read vital metadata from an Ogg Vorbis file */ /* An Ogg File is split into pages, each starting with the string "OggS". Each page has a timestamp (in PCM samples) referred to as the "granule position". An Ogg Vorbis has the following structure: 1) Identification header (containing samplerate, numchannels, etc) 2) Comment header - containing the Vorbis Comments 3) Setup header - containing codec setup information 4) Many audio packets... */ /* Use the trackname part of the id3 structure as a temporary buffer */ buf=tracks[track_widx].id3.path; lseek(fd, 0, SEEK_SET); rc = read(fd, buf, 58); if (rc < 4) { close(fd); return false; } if ((memcmp(buf,"OggS",4)!=0) || (memcmp(&buf[29],"vorbis",6)!=0)) { logf("%s is not an Ogg Vorbis file\n",trackname); close(fd); return(false); } /* Ogg stores integers in little-endian format. */ tracks[track_widx].id3.filesize=filesize(fd); tracks[track_widx].id3.frequency=buf[40]|(buf[41]<<8)|(buf[42]<<16)|(buf[43]<<24); channels=buf[39]; /* We now need to search for the last page in the file - identified by by ('O','g','g','S',0) and retrieve totalsamples */ lseek(fd, -32*1024, SEEK_END); eof=0; j=0; /* The number of bytes currently in buffer */ i=0; totalsamples=0; while (!eof) { rc = read(fd, &buf[j], MAX_PATH-j); if (rc <= 0) { eof=1; } else { j+=rc; } /* Inefficient (but simple) search */ i=0; while (i < (j-5)) { if (memcmp(&buf[i],"OggS",5)==0) { if (i < (j-10)) { totalsamples=(buf[i+6])|(buf[i+7]<<8)|(buf[i+8]<<16)|(buf[i+9]<<24); j=0; /* We can discard the rest of the buffer */ } else { break; } } else { i++; } } if (i < (j-5)) { /* Move OggS to start of buffer */ while(i>0) buf[i--]=buf[j--]; } else { j=0; } } tracks[track_widx].id3.length=(totalsamples/tracks[track_widx].id3.frequency)*1000; /* The following calculation should use datasize, not filesize (i.e. exclude comments etc) */ tracks[track_widx].id3.bitrate=(filesize(fd)*8)/tracks[track_widx].id3.length; tracks[track_widx].id3.vbr=true; lseek(fd, 0, SEEK_SET); strncpy(tracks[track_widx].id3.path,trackname,sizeof(tracks[track_widx].id3.path)); tracks[track_widx].taginfo_ready = true; break; /* If we don't know how to read the metadata, just store the filename */ default: strncpy(tracks[track_widx].id3.path,trackname,sizeof(tracks[track_widx].id3.path)); tracks[track_widx].taginfo_ready = true; break; } track_changed = true; track_count++; i = tracks[track_widx].filepos; while (i < size) { /* Give codecs some processing time to prevent glitches. */ yield_codecs(); /* Limit buffering size at first run. */ if (conf_bufferlimit && (int)fill_bytesleft >= conf_bufferlimit) { fill_bytesleft = conf_bufferlimit; conf_bufferlimit = 0; } if (!queue_empty(&audio_queue)) { logf("Buffering interrupted"); close(fd); return false; } if (fill_bytesleft == 0) break ; copy_n = MIN(conf_filechunk, codecbuflen - buf_widx); copy_n = MIN(size - i, copy_n); copy_n = MIN((int)fill_bytesleft, copy_n); rc = read(fd, &codecbuf[buf_widx], copy_n); if (rc < 0) { logf("File error!"); close(fd); return false; } buf_widx += rc; if (buf_widx >= codecbuflen) buf_widx -= codecbuflen; i += rc; tracks[track_widx].available += rc; tracks[track_widx].filerem -= rc; codecbufused += rc; fill_bytesleft -= rc; } tracks[track_widx].filepos = i; /* Leave the file handle open for faster buffer refill. */ if (tracks[track_widx].filerem != 0) { current_fd = fd; logf("Partially buf:%d", tracks[track_widx].available); return false; } else { logf("Completely buf."); close(fd); current_fd = -1; if (++track_widx >= MAX_TRACK) { track_widx = 0; } tracks[track_widx].filerem = 0; } return true; } void audio_insert_tracks(int offset, bool start_playing, int peek_offset) { fill_bytesleft = codecbuflen - codecbufused; filling = true; while (audio_load_track(offset, start_playing, peek_offset)) { start_playing = false; offset = 0; peek_offset++; } filling = false; } void audio_play_start(int offset) { memset(&tracks, 0, sizeof(struct track_info) * MAX_TRACK); sound_set(SOUND_VOLUME, global_settings.volume); track_count = 0; track_widx = 0; track_ridx = 0; buf_ridx = 0; buf_widx = 0; codecbufused = 0; #ifndef SIMULATOR pcm_set_boost_mode(true); #endif audio_insert_tracks(offset, true, 0); #ifndef SIMULATOR pcm_set_boost_mode(false); ata_sleep(); #endif } void audio_check_buffer(void) { int i; int cur_idx; /* Fill buffer as full as possible for cross-fader. */ #ifndef SIMULATOR if (pcm_is_crossfade_enabled() && cur_ti->id3.length > 0 && cur_ti->id3.length - cur_ti->id3.elapsed < 20000 && playing) pcm_set_boost_mode(true); #endif /* Start buffer filling as necessary. */ if (codecbufused > conf_watermark || !queue_empty(&audio_queue) || !playing || ci.stop_codec || ci.reload_codec) return ; filling = true; #ifndef SIMULATOR pcm_set_boost_mode(true); #endif fill_bytesleft = codecbuflen - codecbufused; /* Calculate real track count after throwing away old tracks. */ cur_idx = track_ridx; for (i = 0; i < track_count; i++) { if (cur_idx == track_widx) break ; if (++cur_idx >= MAX_TRACK) cur_idx = 0; } track_count = i; if (tracks[cur_idx].filerem != 0) track_count++; /* Mark all other entries null. */ cur_idx = track_widx; for (i = 0; i < MAX_TRACK - track_count; i++) { if (++cur_idx >= MAX_TRACK) cur_idx = 0; tracks[cur_idx].filesize = 0; tracks[cur_idx].available = 0; } /* Try to load remainings of the file. */ if (tracks[track_widx].filerem > 0) audio_fill_file_buffer(); /* Load new files to fill the entire buffer. */ if (tracks[track_widx].filerem == 0) audio_insert_tracks(0, false, 1); #ifndef SIMULATOR pcm_set_boost_mode(false); if (playing) ata_sleep(); #endif filling = false; } void audio_update_trackinfo(void) { if (new_track >= 0) { buf_ridx += cur_ti->available; codecbufused -= cur_ti->available; cur_ti = &tracks[track_ridx]; buf_ridx += cur_ti->codecsize; codecbufused -= cur_ti->codecsize; if (buf_ridx >= codecbuflen) buf_ridx -= codecbuflen; #ifndef SIMULATOR pcm_crossfade_start(); if (!filling) pcm_set_boost_mode(false); #endif } else { buf_ridx -= ci.curpos + cur_ti->codecsize; codecbufused += ci.curpos + cur_ti->codecsize; cur_ti->available = cur_ti->filesize; cur_ti = &tracks[track_ridx]; buf_ridx -= cur_ti->filesize; codecbufused += cur_ti->filesize; cur_ti->available = cur_ti->filesize; if (buf_ridx < 0) buf_ridx = codecbuflen + buf_ridx; } ci.filesize = cur_ti->filesize; cur_ti->id3.elapsed = 0; cur_ti->id3.offset = 0; ci.id3 = (struct mp3entry *)&cur_ti->id3; ci.mp3data = (struct mp3info *)&cur_ti->mp3data; ci.curpos = 0; ci.taginfo_ready = (bool *)&cur_ti->taginfo_ready; track_changed = true; } void audio_change_track(void) { if (track_ridx == track_widx) { logf("No more tracks"); playing = false; return ; } if (++track_ridx >= MAX_TRACK) track_ridx = 0; audio_update_trackinfo(); queue_post(&codec_queue, CODEC_LOAD, 0); } bool codec_request_next_track_callback(void) { if (ci.stop_codec || !playing || !queue_empty(&audio_queue)) return false; logf("Request new track"); /* Advance to next track. */ if (ci.reload_codec && new_track > 0) { playlist_next(1); if (++track_ridx == MAX_TRACK) track_ridx = 0; if (tracks[track_ridx].filesize == 0) { logf("Loading from disk..."); new_track = 0; queue_post(&audio_queue, AUDIO_PLAY, 0); return false; } } /* Advance to previous track. */ else if (ci.reload_codec && new_track < 0) { playlist_next(-1); if (--track_ridx < 0) track_ridx = MAX_TRACK-1; if (tracks[track_ridx].filesize == 0 || codecbufused+ci.curpos+tracks[track_ridx].filesize /*+ (off_t)tracks[track_ridx].codecsize*/ > codecbuflen) { logf("Loading from disk..."); new_track = 0; queue_post(&audio_queue, AUDIO_PLAY, 0); return false; } } /* Codec requested track change (next track). */ else { playlist_next(1); if (++track_ridx >= MAX_TRACK) track_ridx = 0; if (track_ridx == track_widx && tracks[track_ridx].filerem == 0) { if (ci.reload_codec) { } else { logf("No more tracks"); } new_track = 0; return false; } } ci.reload_codec = false; if (cur_ti->codectype != tracks[track_ridx].codectype) { if (--track_ridx < 0) track_ridx = MAX_TRACK-1; logf("New codec"); new_track = 0; return false; } logf("On-the-fly change"); audio_update_trackinfo(); new_track = 0; return true; } void audio_thread(void) { struct event ev; while (1) { sleep(50); audio_check_buffer(); queue_wait_w_tmo(&audio_queue, &ev, 10); switch (ev.id) { case AUDIO_PLAY: ci.stop_codec = true; ci.reload_codec = false; ci.seek_time = 0; #ifndef SIMULATOR pcm_play_stop(); #endif audio_play_start((int)ev.data); break ; case AUDIO_STOP: paused = false; #ifndef SIMULATOR pcm_play_stop(); pcm_play_pause(true); #endif break ; case AUDIO_PAUSE: break ; case AUDIO_RESUME: break ; case AUDIO_NEXT: break ; case AUDIO_CODEC_DONE: //if (playing) // audio_change_track(); break ; #ifndef SIMULATOR case SYS_USB_CONNECTED: playing = false; ci.stop_codec = true; logf("USB Connection"); pcm_play_stop(); pcm_play_pause(true); usb_acknowledge(SYS_USB_CONNECTED_ACK); usb_wait_for_disconnect(&audio_queue); break ; #endif } } } void codec_thread(void) { struct event ev; size_t codecsize; int status; int wrap; while (1) { status = 0; queue_wait(&codec_queue, &ev); switch (ev.id) { case CODEC_LOAD_DISK: ci.stop_codec = false; status = codec_load_file((char *)ev.data, &ci); break ; case CODEC_LOAD: logf("Codec start"); codecsize = cur_ti->codecsize; if (codecsize == 0) { logf("Codec slot is empty!"); playing = false; break ; } ci.stop_codec = false; wrap = (int)&codecbuf[codecbuflen] - (int)cur_ti->codecbuf; status = codec_load_ram(cur_ti->codecbuf, codecsize, &ci, &codecbuf[0], codecbuflen); break ; #ifndef SIMULATOR case SYS_USB_CONNECTED: usb_acknowledge(SYS_USB_CONNECTED_ACK); usb_wait_for_disconnect(&codec_queue); break ; #endif } switch (ev.id) { case CODEC_LOAD_DISK: case CODEC_LOAD: if (status != PLUGIN_OK) { logf("Codec failure"); playing = false; } else { logf("Codec finished"); } if (playing && !ci.stop_codec && !ci.reload_codec) { audio_change_track(); continue ; } else if (ci.stop_codec) { //playing = false; } //queue_post(&audio_queue, AUDIO_CODEC_DONE, (void *)status); } } } struct mp3entry* audio_current_track(void) { logf("audio_current_track"); if (track_count > 0 && cur_ti->taginfo_ready) return (struct mp3entry *)&cur_ti->id3; else return NULL; } struct mp3entry* audio_next_track(void) { int next_idx = track_ridx + 1; if (track_count == 0) return NULL; if (next_idx >= MAX_TRACK) next_idx = 0; if (!tracks[next_idx].taginfo_ready) return NULL; //logf("audio_next_track"); return &tracks[next_idx].id3; } bool audio_has_changed_track(void) { if (track_changed && track_count > 0) { if (!cur_ti->taginfo_ready) return false; track_changed = false; return true; } return false; } void audio_play(int offset) { logf("audio_play"); ci.stop_codec = true; #ifndef SIMULATOR pcm_play_pause(true); #endif paused = false; playing = true; queue_post(&audio_queue, AUDIO_PLAY, (void *)offset); } void audio_stop(void) { logf("audio_stop"); playing = false; paused = false; ci.stop_codec = true; if (current_fd >= 0) { close(current_fd); current_fd = -1; } queue_post(&audio_queue, AUDIO_STOP, 0); #ifndef SIMULATOR pcm_play_pause(true); #endif } void audio_pause(void) { logf("audio_pause"); #ifndef SIMULATOR pcm_play_pause(false); #endif paused = true; //queue_post(&audio_queue, AUDIO_PAUSE, 0); } void audio_resume(void) { logf("audio_resume"); #ifndef SIMULATOR pcm_play_pause(true); #endif paused = false; //queue_post(&audio_queue, AUDIO_RESUME, 0); } void audio_next(void) { logf("audio_next"); new_track = 1; ci.reload_codec = true; /* Detect if disk is spinning.. */ if (filling) { ci.stop_codec = true; playlist_next(1); queue_post(&audio_queue, AUDIO_PLAY, 0); } else { #ifndef SIMULATOR pcm_play_stop(); #endif } } void audio_prev(void) { logf("audio_prev"); new_track = -1; ci.reload_codec = true; #ifndef SIMULATOR pcm_play_stop(); #endif if (filling) { ci.stop_codec = true; playlist_next(-1); queue_post(&audio_queue, AUDIO_PLAY, 0); } //queue_post(&audio_queue, AUDIO_PREV, 0); } void audio_ff_rewind(int newpos) { logf("rewind: %d", newpos); /* Does not work yet. */ if (playing) ci.seek_time = newpos+1; } void audio_flush_and_reload_tracks(void) { logf("flush & reload"); } void audio_error_clear(void) { } int audio_status(void) { int ret = 0; if (playing) ret |= AUDIO_STATUS_PLAY; if (paused) ret |= AUDIO_STATUS_PAUSE; return ret; } int audio_get_file_pos(void) { return 0; } /* Copied from mpeg.c. Should be moved somewhere else. */ static void mp3_set_elapsed(struct mp3entry* id3) { if ( id3->vbr ) { if ( id3->has_toc ) { /* calculate elapsed time using TOC */ int i; unsigned int remainder, plen, relpos, nextpos; /* find wich percent we're at */ for (i=0; i<100; i++ ) { if ( id3->offset < (int)(id3->toc[i] * (id3->filesize / 256)) ) { break; } } i--; if (i < 0) i = 0; relpos = id3->toc[i]; if (i < 99) { nextpos = id3->toc[i+1]; } else { nextpos = 256; } remainder = id3->offset - (relpos * (id3->filesize / 256)); /* set time for this percent (divide before multiply to prevent overflow on long files. loss of precision is negligible on short files) */ id3->elapsed = i * (id3->length / 100); /* calculate remainder time */ plen = (nextpos - relpos) * (id3->filesize / 256); id3->elapsed += (((remainder * 100) / plen) * (id3->length / 10000)); } else { /* no TOC exists. set a rough estimate using average bitrate */ int tpk = id3->length / (id3->filesize / 1024); id3->elapsed = id3->offset / 1024 * tpk; } } else /* constant bitrate == simple frame calculation */ id3->elapsed = id3->offset / id3->bpf * id3->tpf; } /* Copied from mpeg.c. Should be moved somewhere else. */ int mp3_get_file_pos(void) { int pos = -1; struct mp3entry *id3 = audio_current_track(); if (id3->vbr) { if (id3->has_toc) { /* Use the TOC to find the new position */ unsigned int percent, remainder; int curtoc, nexttoc, plen; percent = (id3->elapsed*100)/id3->length; if (percent > 99) percent = 99; curtoc = id3->toc[percent]; if (percent < 99) nexttoc = id3->toc[percent+1]; else nexttoc = 256; pos = (id3->filesize/256)*curtoc; /* Use the remainder to get a more accurate position */ remainder = (id3->elapsed*100)%id3->length; remainder = (remainder*100)/id3->length; plen = (nexttoc - curtoc)*(id3->filesize/256); pos += (plen/100)*remainder; } else { /* No TOC exists, estimate the new position */ pos = (id3->filesize / (id3->length / 1000)) * (id3->elapsed / 1000); } } else if (id3->bpf && id3->tpf) pos = (id3->elapsed/id3->tpf)*id3->bpf; else { return -1; } if (pos >= (int)(id3->filesize - id3->id3v1len)) { /* Don't seek right to the end of the file so that we can transition properly to the next song */ pos = id3->filesize - id3->id3v1len - 1; } else if (pos < (int)id3->first_frame_offset) { /* skip past id3v2 tag and other leading garbage */ pos = id3->first_frame_offset; } return pos; } #ifndef SIMULATOR void audio_set_buffer_margin(int seconds) { (void)seconds; } #endif void mpeg_id3_options(bool _v1first) { v1first = _v1first; } void audio_init(void) { logf("audio api init"); codecbuflen = audiobufend - audiobuf - PCMBUF_SIZE - MALLOC_BUFSIZE - GUARD_BUFSIZE; //codecbuflen = 2*512*1024; codecbufused = 0; filling = false; codecbuf = &audiobuf[MALLOC_BUFSIZE]; playing = false; paused = false; track_changed = false; current_fd = -1; logf("abuf:%0x", PCMBUF_SIZE); logf("fbuf:%0x", codecbuflen); logf("mbuf:%0x", MALLOC_BUFSIZE); /* Initialize codec api. */ ci.read_filebuf = codec_filebuf_callback; #ifndef SIMULATOR ci.audiobuffer_insert = audiobuffer_insert; #else ci.audiobuffer_insert = audiobuffer_insert_sim; #endif ci.get_codec_memory = get_codec_memory_callback; ci.request_buffer = codec_request_buffer_callback; ci.advance_buffer = codec_advance_buffer_callback; ci.advance_buffer_loc = codec_advance_buffer_loc_callback; ci.request_next_track = codec_request_next_track_callback; ci.mp3_get_filepos = codec_mp3_get_filepos_callback; ci.seek_buffer = codec_seek_buffer_callback; ci.set_elapsed = codec_set_elapsed_callback; ci.configure = codec_configure_callback; queue_init(&audio_queue); queue_init(&codec_queue); create_thread(codec_thread, codec_stack, sizeof(codec_stack), codec_thread_name); create_thread(audio_thread, audio_stack, sizeof(audio_stack), audio_thread_name); #ifndef SIMULATOR audio_is_initialized = true; #endif }