rockbox/apps/mpeg.c

1980 lines
57 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 by Linus Nielsen Feltzing
*
* 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.
*
****************************************************************************/
#include <stdbool.h>
#include <stdlib.h>
#include "config.h"
#if CONFIG_CODEC != SWCODEC
#include "debug.h"
#include "panic.h"
#include "metadata.h"
#include "mpeg.h"
#include "audio.h"
#include "storage.h"
#include "string.h"
#include <kernel.h>
#include "thread.h"
#include "errno.h"
#include "mp3data.h"
#include "core_alloc.h"
#include "mp3_playback.h"
#include "talk.h"
#include "sound.h"
#include "system.h"
#include "appevents.h"
#include "playlist.h"
#include "cuesheet.h"
#include "settings.h"
#ifndef SIMULATOR
#include "i2c.h"
#include "system.h"
#include "usb.h"
#include "file.h"
#include "hwcompat.h"
#endif /* !SIMULATOR */
#ifdef HAVE_LCD_BITMAP
#include "lcd.h"
#endif /* CONFIG_CODEC != SWCODEC */
#define MPEG_SWAP_CHUNKSIZE 0x2000
#define MPEG_HIGH_WATER 2 /* We leave 2 bytes empty because otherwise we
wouldn't be able to see the difference between
an empty buffer and a full one. */
#define MPEG_LOW_WATER 0x60000
#define MPEG_RECORDING_LOW_WATER 0x80000
#define MPEG_LOW_WATER_CHUNKSIZE 0x40000
#define MPEG_LOW_WATER_SWAP_CHUNKSIZE 0x10000
#if (CONFIG_STORAGE & STORAGE_MMC)
#define MPEG_PLAY_PENDING_THRESHOLD 0x20000
#define MPEG_PLAY_PENDING_SWAPSIZE 0x20000
#else
#define MPEG_PLAY_PENDING_THRESHOLD 0x10000
#define MPEG_PLAY_PENDING_SWAPSIZE 0x10000
#endif
#define MPEG_MAX_PRERECORD_SECONDS 30
/* For ID3 info and VBR header */
#define MPEG_RESERVED_HEADER_SPACE (4096 + 576)
#ifndef SIMULATOR
extern unsigned long mas_version_code;
#endif
#define MPEG_PLAY 1
#define MPEG_STOP 2
#define MPEG_PAUSE 3
#define MPEG_RESUME 4
#define MPEG_NEXT 5
#define MPEG_PREV 6
#define MPEG_FF_REWIND 7
#define MPEG_FLUSH_RELOAD 8
#define MPEG_RECORD 9
#define MPEG_INIT_RECORDING 10
#define MPEG_INIT_PLAYBACK 11
#define MPEG_NEW_FILE 12
#define MPEG_PAUSE_RECORDING 13
#define MPEG_RESUME_RECORDING 14
#define MPEG_NEED_DATA 100
#define MPEG_TRACK_CHANGE 101
#define MPEG_SAVE_DATA 102
#define MPEG_STOP_DONE 103
#define MPEG_PRERECORDING_TICK 104
/* indicator for MPEG_NEED_DATA */
#define GENERATE_UNBUFFER_EVENTS 1
/* list of tracks in memory */
#define MAX_TRACK_ENTRIES (1<<4) /* Must be power of 2 */
#define MAX_TRACK_ENTRIES_MASK (MAX_TRACK_ENTRIES - 1)
struct trackdata
{
struct mp3entry id3;
int mempos;
int load_ahead_index;
};
static struct trackdata trackdata[MAX_TRACK_ENTRIES];
static unsigned int current_track_counter = 0;
#ifndef SIMULATOR
static void stop_playing(void);
static int track_read_idx = 0;
static int track_write_idx = 0;
#endif /* !SIMULATOR */
/* Cuesheet support */
static struct cuesheet *curr_cuesheet = NULL;
static bool checked_for_cuesheet = false;
static const char mpeg_thread_name[] = "mpeg";
static unsigned int audio_thread_id;
static bool audio_is_initialized;
static unsigned int mpeg_errno;
static bool playing = false; /* We are playing an MP3 stream */
static bool is_playing = false; /* We are (attempting to) playing MP3 files */
static bool paused; /* playback is paused */
static int audiobuf_handle; /* handle to the audio buffer */
static char* mpeg_audiobuf; /* poiunter to the audio buffer */
static long audiobuflen; /* length of the audio buffer */
#define AUDIO_BUFFER_RESERVE (256*1024)
#ifdef SIMULATOR
static char mpeg_stack[DEFAULT_STACK_SIZE];
static struct mp3entry taginfo;
#else /* !SIMULATOR */
static struct event_queue mpeg_queue SHAREDBSS_ATTR;
static long mpeg_stack[(DEFAULT_STACK_SIZE + 0x1000)/sizeof(long)];
static int audiobuf_write;
static int audiobuf_swapwrite;
static long audiobuf_read;
static int mpeg_file;
static bool play_pending; /* We are about to start playing */
static bool play_pending_track_change; /* When starting play we're starting a new file */
static bool filling; /* We are filling the buffer with data from disk */
static bool dma_underrun; /* True when the DMA has stopped because of
slow disk reading (read error, shaking) */
static bool mpeg_stop_done;
static int last_dma_tick = 0;
static int last_dma_chunk_size;
static long low_watermark; /* Dynamic low watermark level */
static long low_watermark_margin = 0; /* Extra time in seconds for watermark */
static long lowest_watermark_level; /* Debug value to observe the buffer
usage */
struct audio_resume_info
{
unsigned long elapsed;
unsigned long offset;
};
#ifdef HAVE_RECORDING
static const unsigned char empty_id3_header[] =
{
'I', 'D', '3', 0x03, 0x00, 0x00,
0x00, 0x00, 0x1f, 0x76 /* Size is 4096 minus 10 bytes for the header */
};
#endif /* HAVE_RECORDING */
static int get_unplayed_space(void);
static int get_playable_space(void);
static int get_unswapped_space(void);
#endif /* !SIMULATOR */
static void audio_reset_buffer_noalloc(void* buf, size_t bufsize);
static void audio_reset_buffer(void);
#ifndef SIMULATOR
static int num_tracks_in_memory(void)
{
return (track_write_idx - track_read_idx) & MAX_TRACK_ENTRIES_MASK;
}
#ifdef DEBUG_TAGS
static void debug_tags(void)
{
int i;
for(i = 0;i < MAX_TRACK_ENTRIES;i++)
{
DEBUGF("%d - %s\n", i, trackdata[i].id3.path);
}
DEBUGF("read: %d, write :%d\n", track_read_idx, track_write_idx);
DEBUGF("num_tracks_in_memory: %d\n", num_tracks_in_memory());
}
#else /* !DEBUG_TAGS */
#define debug_tags()
#endif /* !DEBUG_TAGS */
static void remove_current_tag(void)
{
if(num_tracks_in_memory() > 0)
{
/* First move the index, so nobody tries to access the tag */
track_read_idx = (track_read_idx+1) & MAX_TRACK_ENTRIES_MASK;
checked_for_cuesheet = false;
debug_tags();
}
else
{
DEBUGF("remove_current_tag: no tracks to remove\n");
}
}
static void remove_all_non_current_tags(void)
{
track_write_idx = (track_read_idx+1) & MAX_TRACK_ENTRIES_MASK;
debug_tags();
}
static void remove_all_tags(void)
{
track_write_idx = track_read_idx;
debug_tags();
}
static struct trackdata *get_trackdata(int offset)
{
if(offset >= num_tracks_in_memory())
return NULL;
else
return &trackdata[(track_read_idx + offset) & MAX_TRACK_ENTRIES_MASK];
}
#endif /* !SIMULATOR */
/***********************************************************************/
/* audio event handling */
#define MAX_EVENT_HANDLERS 10
struct event_handlers_table
{
AUDIO_EVENT_HANDLER handler;
unsigned short mask;
};
static struct event_handlers_table event_handlers[MAX_EVENT_HANDLERS];
static int event_handlers_count = 0;
void audio_register_event_handler(AUDIO_EVENT_HANDLER handler, unsigned short mask)
{
if (event_handlers_count < MAX_EVENT_HANDLERS)
{
event_handlers[event_handlers_count].handler = handler;
event_handlers[event_handlers_count].mask = mask;
event_handlers_count++;
}
}
/* dispatch calls each handler in the order registered and returns after some
handler actually handles the event (the event is assumed to no longer be valid
after this, due to the handler changing some condition); returns true if someone
handled the event, which is expected to cause the caller to skip its own handling
of the event */
#ifndef SIMULATOR
static bool audio_dispatch_event(unsigned short event, unsigned long data)
{
int i = 0;
for(i=0; i < event_handlers_count; i++)
{
if ( event_handlers[i].mask & event )
{
int rc = event_handlers[i].handler(event, data);
if ( rc == AUDIO_EVENT_RC_HANDLED )
return true;
}
}
return false;
}
static void send_track_event(unsigned int id, struct mp3entry *id3)
{
struct mp3entry *cur_id3 =
&trackdata[track_read_idx & MAX_TRACK_ENTRIES_MASK].id3;
unsigned int flags = id3 == cur_id3 ? TEF_CURRENT : 0;
send_event(id, &(struct track_event){ .flags = flags, .id3 = id3 });
}
#endif /* SIMULATOR */
/***********************************************************************/
static void 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 < 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, use exact calculation */
id3->elapsed = id3->offset / (id3->bitrate / 8);
}
static int audio_get_file_pos_int(struct mp3entry *id3)
{
int pos = -1;
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->bitrate)
pos = id3->elapsed * (id3->bitrate / 8);
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;
}
int audio_get_file_pos(void)
{
struct mp3entry *id3 = audio_current_track();
return id3 ? audio_get_file_pos_int(id3) : 0;
}
unsigned long mpeg_get_last_header(void)
{
#ifdef SIMULATOR
return 0;
#else /* !SIMULATOR */
unsigned long tmp[2];
/* Read the frame data from the MAS and reconstruct it with the
frame sync and all */
mas_readmem(MAS_BANK_D0, MAS_D0_MPEG_STATUS_1, tmp, 2);
return 0xffe00000 | ((tmp[0] & 0x7c00) << 6) | (tmp[1] & 0xffff);
#endif /* !SIMULATOR */
}
static void do_stop(void)
{
is_playing = false;
paused = false;
#ifndef SIMULATOR
if (playing)
playlist_update_resume_info(audio_current_track());
stop_playing();
mpeg_stop_done = true;
#else
playing = false;
#endif
}
/* Buffer must not move. */
static int shrink_callback(int handle, unsigned hints, void* start, size_t old_size)
{
ssize_t extradata_size = old_size - audiobuflen;
/* check what buflib requests */
size_t wanted_size = (hints & BUFLIB_SHRINK_SIZE_MASK);
ssize_t size = (ssize_t)old_size - wanted_size;
/* keep at least 256K for the buffering */
if ((size - extradata_size) < AUDIO_BUFFER_RESERVE)
{
/* check if buflib needs the memory really hard. if yes we give
* up playback for now, otherwise refuse to shrink to keep at least
* 256K for the buffering */
if ((hints & BUFLIB_SHRINK_POS_MASK) != BUFLIB_SHRINK_POS_MASK)
return BUFLIB_CB_CANNOT_SHRINK;
}
/* TODO: Do it without stopping playback, if possible */
bool playing = (audio_status() & AUDIO_STATUS_PLAY) == AUDIO_STATUS_PLAY;
struct mp3entry *id3 = audio_current_track();
unsigned long elapsed = 0, offset = 0;
if (id3)
{
elapsed = id3->elapsed;
offset = id3->offset;
}
/* don't call audio_hard_stop() as it frees this handle */
if (thread_self() == audio_thread_id)
{ /* inline case MPEG_STOP (audio_stop()) response
* if we're in the audio thread since audio_stop() otherwise deadlocks */
do_stop();
}
else
audio_stop();
switch (hints & BUFLIB_SHRINK_POS_MASK)
{
case BUFLIB_SHRINK_POS_BACK:
core_shrink(handle, start, size);
audio_reset_buffer_noalloc(start, size);
break;
case BUFLIB_SHRINK_POS_FRONT:
core_shrink(handle, start + wanted_size, size);
audio_reset_buffer_noalloc(start + wanted_size, size);
break;
case BUFLIB_SHRINK_POS_MASK:
audiobuf_handle = core_free(audiobuf_handle);
mpeg_audiobuf = NULL;
talk_buffer_set_policy(TALK_BUFFER_DEFAULT);
playing = false;
break;
}
if (playing)
{ /* safe to call even from the audio thread (due to queue_post()) */
audio_play(elapsed, offset);
}
return BUFLIB_CB_OK;
}
static struct buflib_callbacks ops = {
.move_callback = NULL,
.shrink_callback = shrink_callback,
};
#ifndef SIMULATOR
/* Send callback events to notify about removing old tracks. */
static void generate_unbuffer_events(void)
{
int i;
int numentries = MAX_TRACK_ENTRIES - num_tracks_in_memory();
int cur_idx = track_write_idx;
for (i = 0; i < numentries; i++)
{
/* Send an event to notify that track has finished. */
send_track_event(PLAYBACK_EVENT_TRACK_FINISH, &trackdata[cur_idx].id3);
cur_idx = (cur_idx + 1) & MAX_TRACK_ENTRIES_MASK;
}
}
/* Send callback events to notify about new tracks. */
static void generate_postbuffer_events(void)
{
int i;
int numentries = num_tracks_in_memory();
int cur_idx = track_read_idx;
for (i = 0; i < numentries; i++)
{
send_track_event(PLAYBACK_EVENT_TRACK_BUFFER, &trackdata[cur_idx].id3);
cur_idx = (cur_idx + 1) & MAX_TRACK_ENTRIES_MASK;
}
}
static void recalculate_watermark(int bitrate)
{
int bytes_per_sec;
int time = storage_spinup_time();
/* A bitrate of 0 probably means empty VBR header. We play safe
and set a high threshold */
if(bitrate == 0)
bitrate = 320;
bytes_per_sec = bitrate * 1000 / 8;
if(time)
{
/* No drive spins up faster than 3.5s */
if(time < 350)
time = 350;
time = time * 3;
low_watermark = ((low_watermark_margin * HZ + time) *
bytes_per_sec) / HZ;
}
else
{
low_watermark = MPEG_LOW_WATER;
}
}
#ifdef HAVE_DISK_STORAGE
void audio_set_buffer_margin(int setting)
{
low_watermark_margin = setting; /* in seconds */
}
#endif
void audio_get_debugdata(struct audio_debug *dbgdata)
{
dbgdata->audiobuflen = audiobuflen;
dbgdata->audiobuf_write = audiobuf_write;
dbgdata->audiobuf_swapwrite = audiobuf_swapwrite;
dbgdata->audiobuf_read = audiobuf_read;
dbgdata->last_dma_chunk_size = last_dma_chunk_size;
dbgdata->playing = playing;
dbgdata->play_pending = play_pending;
dbgdata->is_playing = is_playing;
dbgdata->filling = filling;
dbgdata->dma_underrun = dma_underrun;
dbgdata->unplayed_space = get_unplayed_space();
dbgdata->playable_space = get_playable_space();
dbgdata->unswapped_space = get_unswapped_space();
dbgdata->low_watermark_level = low_watermark;
dbgdata->lowest_watermark_level = lowest_watermark_level;
}
#ifdef DEBUG
static void dbg_timer_start(void)
{
/* We are using timer 2 */
TSTR &= ~0x04; /* Stop the timer */
TSNC &= ~0x04; /* No synchronization */
TMDR &= ~0x44; /* Operate normally */
TCNT2 = 0; /* Start counting at 0 */
TCR2 = 0x03; /* Sysclock/8 */
TSTR |= 0x04; /* Start timer 2 */
}
static int dbg_cnt2us(unsigned int cnt)
{
return (cnt * 10000) / (FREQ/800);
}
#endif /* DEBUG */
static int get_unplayed_space(void)
{
int space = audiobuf_write - audiobuf_read;
if (space < 0)
space += audiobuflen;
return space;
}
static int get_playable_space(void)
{
int space = audiobuf_swapwrite - audiobuf_read;
if (space < 0)
space += audiobuflen;
return space;
}
static int get_unplayed_space_current_song(void)
{
int space;
if (num_tracks_in_memory() > 1)
{
space = get_trackdata(1)->mempos - audiobuf_read;
}
else
{
space = audiobuf_write - audiobuf_read;
}
if (space < 0)
space += audiobuflen;
return space;
}
static int get_unswapped_space(void)
{
int space = audiobuf_write - audiobuf_swapwrite;
if (space < 0)
space += audiobuflen;
return space;
}
void playback_tick(void)
{
struct trackdata *ptd = get_trackdata(0);
if(ptd)
{
ptd->id3.elapsed += (current_tick - last_dma_tick) * 1000 / HZ;
last_dma_tick = current_tick;
audio_dispatch_event(AUDIO_EVENT_POS_REPORT,
(unsigned long)ptd->id3.elapsed);
}
}
static void reset_mp3_buffer(void)
{
audiobuf_read = 0;
audiobuf_write = 0;
audiobuf_swapwrite = 0;
lowest_watermark_level = audiobuflen;
}
/* DMA transfer end interrupt callback */
static void transfer_end(const void** ppbuf, size_t* psize)
{
if(playing && !paused)
{
int unplayed_space_left;
int space_until_end_of_buffer;
int track_offset = 1;
struct trackdata *track;
audiobuf_read += last_dma_chunk_size;
if(audiobuf_read >= audiobuflen)
audiobuf_read = 0;
/* First, check if we are on a track boundary */
if (num_tracks_in_memory() > 1)
{
if (audiobuf_read == get_trackdata(track_offset)->mempos)
{
if ( ! audio_dispatch_event(AUDIO_EVENT_END_OF_TRACK, 0) )
{
queue_post(&mpeg_queue, MPEG_TRACK_CHANGE, 0);
track_offset++;
}
}
}
unplayed_space_left = get_unplayed_space();
space_until_end_of_buffer = audiobuflen - audiobuf_read;
if(!filling && unplayed_space_left < low_watermark)
{
filling = true;
queue_post(&mpeg_queue, MPEG_NEED_DATA, GENERATE_UNBUFFER_EVENTS);
}
if(unplayed_space_left)
{
last_dma_chunk_size = MIN(0x2000, unplayed_space_left);
last_dma_chunk_size = MIN(last_dma_chunk_size,
space_until_end_of_buffer);
/* several tracks loaded? */
track = get_trackdata(track_offset);
if(track)
{
/* will we move across the track boundary? */
if (( audiobuf_read < track->mempos ) &&
((audiobuf_read+last_dma_chunk_size) >
track->mempos ))
{
/* Make sure that we end exactly on the boundary */
last_dma_chunk_size = track->mempos - audiobuf_read;
}
}
*psize = last_dma_chunk_size & 0xffff;
*ppbuf = mpeg_audiobuf + audiobuf_read;
track = get_trackdata(0);
if(track)
track->id3.offset += last_dma_chunk_size;
/* Update the watermark debug level */
if(unplayed_space_left < lowest_watermark_level)
lowest_watermark_level = unplayed_space_left;
}
else
{
/* Check if the end of data is because of a hard disk error.
If there is an open file handle, we are still playing music.
If not, the last file has been loaded, and the file handle is
closed. */
if(mpeg_file >= 0)
{
/* Update the watermark debug level */
if(unplayed_space_left < lowest_watermark_level)
lowest_watermark_level = unplayed_space_left;
DEBUGF("DMA underrun.\n");
dma_underrun = true;
}
else
{
if ( ! audio_dispatch_event(AUDIO_EVENT_END_OF_TRACK, 0) )
{
DEBUGF("No more MP3 data. Stopping.\n");
queue_post(&mpeg_queue, MPEG_TRACK_CHANGE, 0);
playing = false;
}
}
*psize = 0; /* no more transfer */
}
}
}
static struct trackdata *add_track_to_tag_list(const char *filename)
{
struct trackdata *track;
bool send_nid3_event;
if(num_tracks_in_memory() >= MAX_TRACK_ENTRIES)
{
DEBUGF("Tag memory is full\n");
return NULL;
}
track = &trackdata[track_write_idx];
/* grab id3 tag of new file and
remember where in memory it starts */
if(mp3info(&track->id3, filename))
{
DEBUGF("Bad mp3\n");
return NULL;
}
track->mempos = audiobuf_write;
track->id3.elapsed = 0;
#ifdef HAVE_LCD_BITMAP
if (track->id3.title)
lcd_getstringsize(track->id3.title, NULL, NULL);
if (track->id3.artist)
lcd_getstringsize(track->id3.artist, NULL, NULL);
if (track->id3.album)
lcd_getstringsize(track->id3.album, NULL, NULL);
#endif
/* if this track is the next track then let the UI know it can get it */
send_nid3_event = (track_write_idx == track_read_idx + 1);
track_write_idx = (track_write_idx+1) & MAX_TRACK_ENTRIES_MASK;
if (send_nid3_event)
send_track_event(PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE, &track->id3);
debug_tags();
return track;
}
static int new_file(int steps)
{
int max_steps = playlist_amount();
int start = 0;
int i;
struct trackdata *track;
char name_buf[MAX_PATH+1];
const char *trackname;
/* Find out how many steps to advance. The load_ahead_index field tells
us how many playlist entries it had to skip to get to a valid one.
We add those together to find out where to start. */
if(steps > 0 && num_tracks_in_memory() > 1)
{
/* Begin with the song after the currently playing one */
i = 1;
while((track = get_trackdata(i++)))
{
start += track->load_ahead_index;
}
}
do {
trackname = playlist_peek(start + steps, name_buf, sizeof(name_buf));
if ( !trackname )
return -1;
DEBUGF("Loading %s\n", trackname);
mpeg_file = open(trackname, O_RDONLY);
if(mpeg_file < 0) {
DEBUGF("Couldn't open file: %s\n",trackname);
if(steps < 0)
steps--;
else
steps++;
}
else
{
struct trackdata *track = add_track_to_tag_list(trackname);
if(!track)
{
/* Bad mp3 file */
if(steps < 0)
steps--;
else
steps++;
close(mpeg_file);
mpeg_file = -1;
}
else
{
/* skip past id3v2 tag */
lseek(mpeg_file,
track->id3.first_frame_offset,
SEEK_SET);
track->id3.index = steps;
track->load_ahead_index = steps;
track->id3.offset = 0;
if(track->id3.vbr)
/* Average bitrate * 1.5 */
recalculate_watermark(
(track->id3.bitrate * 3) / 2);
else
recalculate_watermark(
track->id3.bitrate);
}
}
/* Bail out if no file could be opened */
if(abs(steps) > max_steps)
return -1;
} while ( mpeg_file < 0 );
return 0;
}
static void stop_playing(void)
{
/* Stop the current stream */
mp3_play_stop();
playing = false;
filling = false;
if(mpeg_file >= 0)
close(mpeg_file);
mpeg_file = -1;
remove_all_tags();
generate_unbuffer_events();
reset_mp3_buffer();
}
static void end_current_track(void)
{
play_pending = false;
playing = false;
mp3_play_pause(false);
reset_mp3_buffer();
remove_all_tags();
generate_unbuffer_events();
if(mpeg_file >= 0)
close(mpeg_file);
}
/* Is this a really the end of playback or is a new playlist starting */
static void check_playlist_end(int direction)
{
/* Use the largest possible step size to account for skipped tracks */
int steps = playlist_amount();
if (direction < 0)
steps = -steps;
if (playlist_next(steps) < 0)
is_playing = false;
}
static void update_playlist(void)
{
if (num_tracks_in_memory() > 0)
{
struct trackdata *track = get_trackdata(0);
track->id3.index = playlist_next(track->id3.index);
}
else
{
/* End of playlist? */
check_playlist_end(1);
}
playlist_update_resume_info(audio_current_track());
}
static void track_change(void)
{
DEBUGF("Track change\n");
if (num_tracks_in_memory() > 0)
{
remove_current_tag();
update_playlist();
if (is_playing)
{
send_track_event(PLAYBACK_EVENT_TRACK_CHANGE,
audio_current_track());
}
}
current_track_counter++;
}
#ifdef DEBUG
void hexdump(const unsigned char *buf, int len)
{
int i;
for(i = 0;i < len;i++)
{
if(i && (i & 15) == 0)
{
DEBUGF("\n");
}
DEBUGF("%02x ", buf[i]);
}
DEBUGF("\n");
}
#endif /* DEBUG */
static void start_playback_if_ready(void)
{
int playable_space;
playable_space = audiobuf_swapwrite - audiobuf_read;
if(playable_space < 0)
playable_space += audiobuflen;
/* See if we have started playing yet. If not, do it. */
if(play_pending || dma_underrun)
{
/* If the filling has stopped, and we still haven't reached
the watermark, the file must be smaller than the
watermark. We must still play it. */
if((playable_space >= MPEG_PLAY_PENDING_THRESHOLD) ||
!filling || dma_underrun)
{
DEBUGF("P\n");
if (play_pending) /* don't do this when recovering from DMA underrun */
{
generate_postbuffer_events(); /* signal first track as buffered */
if (play_pending_track_change)
{
play_pending_track_change = false;
send_track_event(PLAYBACK_EVENT_TRACK_CHANGE,
audio_current_track());
}
play_pending = false;
}
playing = true;
last_dma_chunk_size = MIN(0x2000, get_unplayed_space_current_song());
mp3_play_data(mpeg_audiobuf + audiobuf_read, last_dma_chunk_size, transfer_end);
dma_underrun = false;
if (!paused)
{
last_dma_tick = current_tick;
mp3_play_pause(true);
}
/* Tell ourselves that we need more data */
queue_post(&mpeg_queue, MPEG_NEED_DATA, 0);
}
}
}
static bool swap_one_chunk(void)
{
int free_space_left;
int amount_to_swap;
free_space_left = get_unswapped_space();
if(free_space_left == 0 && !play_pending)
return false;
/* Swap in larger chunks when the user is waiting for the playback
to start, or when there is dangerously little playable data left */
if(play_pending)
amount_to_swap = MIN(MPEG_PLAY_PENDING_SWAPSIZE, free_space_left);
else
{
if(get_playable_space() < low_watermark)
amount_to_swap = MIN(MPEG_LOW_WATER_SWAP_CHUNKSIZE,
free_space_left);
else
amount_to_swap = MIN(MPEG_SWAP_CHUNKSIZE, free_space_left);
}
if(audiobuf_write < audiobuf_swapwrite)
amount_to_swap = MIN(audiobuflen - audiobuf_swapwrite,
amount_to_swap);
else
amount_to_swap = MIN(audiobuf_write - audiobuf_swapwrite,
amount_to_swap);
bitswap(mpeg_audiobuf + audiobuf_swapwrite, amount_to_swap);
audiobuf_swapwrite += amount_to_swap;
if(audiobuf_swapwrite >= audiobuflen)
{
audiobuf_swapwrite = 0;
}
return true;
}
static void mpeg_thread(void)
{
static int pause_tick = 0;
static unsigned int pause_track = 0;
struct queue_event ev;
int len;
int free_space_left;
int unplayed_space_left;
int amount_to_read;
int t1, t2;
unsigned long start_elapsed, start_offset;
is_playing = false;
play_pending = false;
playing = false;
mpeg_file = -1;
while(1)
{
yield();
/* Swap if necessary, and don't block on the queue_wait() */
if(swap_one_chunk())
{
queue_wait_w_tmo(&mpeg_queue, &ev, 0);
}
else if (playing)
{
/* periodically update resume info */
queue_wait_w_tmo(&mpeg_queue, &ev, HZ/2);
}
else
{
DEBUGF("S R:%x W:%x SW:%x\n",
audiobuf_read, audiobuf_write, audiobuf_swapwrite);
queue_wait(&mpeg_queue, &ev);
}
start_playback_if_ready();
switch(ev.id)
{
case MPEG_PLAY:
DEBUGF("MPEG_PLAY\n");
#if CONFIG_TUNER
/* Silence the A/D input, it may be on because the radio
may be playing */
mas_codec_writereg(6, 0x0000);
#endif /* CONFIG_TUNER */
/* Stop the current stream */
paused = false;
end_current_track();
if ( new_file(0) == -1 )
{
is_playing = false;
track_change();
break;
}
start_elapsed = ((struct audio_resume_info *)ev.data)->elapsed;
start_offset = ((struct audio_resume_info *)ev.data)->offset;
/* mid-song resume? */
if (!start_offset && start_elapsed) {
struct mp3entry *id3 = &get_trackdata(0)->id3;
id3->elapsed = start_elapsed;
start_offset = audio_get_file_pos_int(id3);
}
if (start_offset) {
struct mp3entry* id3 = &get_trackdata(0)->id3;
lseek(mpeg_file, start_offset, SEEK_SET);
id3->offset = start_offset;
set_elapsed(id3);
}
else {
/* skip past id3v2 tag */
lseek(mpeg_file,
get_trackdata(0)->id3.first_frame_offset,
SEEK_SET);
}
/* Make it read more data */
filling = true;
queue_post(&mpeg_queue, MPEG_NEED_DATA, 0);
/* Tell the file loading code that we want to start playing
as soon as we have some data */
play_pending = true;
play_pending_track_change = true;
update_playlist();
current_track_counter++;
break;
case MPEG_STOP:
do_stop();
break;
case MPEG_PAUSE:
DEBUGF("MPEG_PAUSE\n");
/* Stop the current stream */
if (playing)
playlist_update_resume_info(audio_current_track());
paused = true;
playing = false;
pause_tick = current_tick;
pause_track = current_track_counter;
mp3_play_pause(false);
break;
case MPEG_RESUME:
DEBUGF("MPEG_RESUME\n");
/* Continue the current stream */
paused = false;
if (!play_pending)
{
playing = true;
if ( current_track_counter == pause_track )
last_dma_tick += current_tick - pause_tick;
else
last_dma_tick = current_tick;
pause_tick = 0;
mp3_play_pause(true);
}
break;
case MPEG_NEXT:
DEBUGF("MPEG_NEXT\n");
/* is next track in ram? */
if ( num_tracks_in_memory() > 1 ) {
int unplayed_space_left, unswapped_space_left;
/* stop the current stream */
play_pending = false;
playing = false;
mp3_play_pause(false);
track_change();
audiobuf_read = get_trackdata(0)->mempos;
last_dma_chunk_size = MIN(0x2000, get_unplayed_space_current_song());
mp3_play_data(mpeg_audiobuf + audiobuf_read, last_dma_chunk_size, transfer_end);
dma_underrun = false;
last_dma_tick = current_tick;
unplayed_space_left = get_unplayed_space();
unswapped_space_left = get_unswapped_space();
/* should we start reading more data? */
if(!filling && (unplayed_space_left < low_watermark)) {
filling = true;
queue_post(&mpeg_queue, MPEG_NEED_DATA, GENERATE_UNBUFFER_EVENTS);
play_pending = true;
} else if(unswapped_space_left &&
unswapped_space_left > unplayed_space_left) {
/* Stop swapping the data from the previous file */
audiobuf_swapwrite = audiobuf_read;
play_pending = true;
} else {
playing = true;
if (!paused)
mp3_play_pause(true);
}
}
else {
if (!playlist_check(1))
break;
/* stop the current stream */
end_current_track();
if (new_file(1) < 0) {
DEBUGF("No more files to play\n");
filling = false;
check_playlist_end(1);
current_track_counter++;
} else {
/* Make it read more data */
filling = true;
queue_post(&mpeg_queue, MPEG_NEED_DATA, 0);
/* Tell the file loading code that we want
to start playing as soon as we have some data */
play_pending = true;
play_pending_track_change = true;
update_playlist();
current_track_counter++;
}
}
break;
case MPEG_PREV: {
DEBUGF("MPEG_PREV\n");
if (!playlist_check(-1))
break;
/* stop the current stream */
end_current_track();
/* Open the next file */
if (new_file(-1) < 0) {
DEBUGF("No more files to play\n");
filling = false;
check_playlist_end(-1);
current_track_counter++;
} else {
/* Make it read more data */
filling = true;
queue_post(&mpeg_queue, MPEG_NEED_DATA, 0);
/* Tell the file loading code that we want to
start playing as soon as we have some data */
play_pending = true;
play_pending_track_change = true;
update_playlist();
current_track_counter++;
}
break;
}
case MPEG_FF_REWIND: {
struct mp3entry *id3 = audio_current_track();
unsigned int oldtime = id3->elapsed;
unsigned int newtime = (unsigned int)ev.data;
int curpos, newpos, diffpos;
DEBUGF("MPEG_FF_REWIND\n");
id3->elapsed = newtime;
newpos = audio_get_file_pos_int(id3);
if(newpos < 0)
{
id3->elapsed = oldtime;
break;
}
if (mpeg_file >= 0)
curpos = lseek(mpeg_file, 0, SEEK_CUR);
else
curpos = id3->filesize;
if (num_tracks_in_memory() > 1)
{
/* We have started loading other tracks that need to be
accounted for */
struct trackdata *track;
int i = 0;
while((track = get_trackdata(i++)))
{
curpos += track->id3.filesize;
}
}
diffpos = curpos - newpos;
if(!filling && diffpos >= 0 && diffpos < audiobuflen)
{
int unplayed_space_left, unswapped_space_left;
/* We are changing to a position that's already in
memory, so we just move the DMA read pointer. */
audiobuf_read = audiobuf_write - diffpos;
if (audiobuf_read < 0)
{
audiobuf_read += audiobuflen;
}
unplayed_space_left = get_unplayed_space();
unswapped_space_left = get_unswapped_space();
/* If unswapped_space_left is larger than
unplayed_space_left, it means that the swapwrite pointer
hasn't yet advanced up to the new location of the read
pointer. We just move it, there is no need to swap
data that won't be played anyway. */
if (unswapped_space_left > unplayed_space_left)
{
DEBUGF("Moved swapwrite\n");
audiobuf_swapwrite = audiobuf_read;
play_pending = true;
}
if (mpeg_file>=0 && unplayed_space_left < low_watermark)
{
/* We need to load more data before starting */
filling = true;
queue_post(&mpeg_queue, MPEG_NEED_DATA, GENERATE_UNBUFFER_EVENTS);
play_pending = true;
}
else
{
/* resume will start at new position */
last_dma_chunk_size =
MIN(0x2000, get_unplayed_space_current_song());
mp3_play_data(mpeg_audiobuf + audiobuf_read,
last_dma_chunk_size, transfer_end);
dma_underrun = false;
}
}
else
{
/* Move to the new position in the file and start
loading data */
reset_mp3_buffer();
if (num_tracks_in_memory() > 1)
{
/* We have to reload the current track */
close(mpeg_file);
remove_all_non_current_tags();
generate_unbuffer_events();
mpeg_file = -1;
}
if (mpeg_file < 0)
{
mpeg_file = open(id3->path, O_RDONLY);
if (mpeg_file < 0)
{
id3->elapsed = oldtime;
break;
}
}
if(-1 == lseek(mpeg_file, newpos, SEEK_SET))
{
id3->elapsed = oldtime;
break;
}
filling = true;
queue_post(&mpeg_queue, MPEG_NEED_DATA, 0);
/* Tell the file loading code that we want to start playing
as soon as we have some data */
play_pending = true;
}
id3->offset = newpos;
break;
}
case MPEG_FLUSH_RELOAD: {
int numtracks = num_tracks_in_memory();
bool reload_track = false;
if (numtracks > 1)
{
/* Reset the buffer */
audiobuf_write = get_trackdata(1)->mempos;
/* Reset swapwrite unless we're still swapping current
track */
if (get_unplayed_space() <= get_playable_space())
audiobuf_swapwrite = audiobuf_write;
close(mpeg_file);
remove_all_non_current_tags();
generate_unbuffer_events();
mpeg_file = -1;
reload_track = true;
}
else if (numtracks == 1 && mpeg_file < 0)
{
reload_track = true;
}
if(reload_track && new_file(1) >= 0)
{
/* Tell ourselves that we want more data */
filling = true;
queue_post(&mpeg_queue, MPEG_NEED_DATA, 0);
}
break;
}
case MPEG_NEED_DATA:
free_space_left = audiobuf_read - audiobuf_write;
/* We interpret 0 as "empty buffer" */
if(free_space_left <= 0)
free_space_left += audiobuflen;
unplayed_space_left = audiobuflen - free_space_left;
/* Make sure that we don't fill the entire buffer */
free_space_left -= MPEG_HIGH_WATER;
if (ev.data == GENERATE_UNBUFFER_EVENTS)
generate_unbuffer_events();
/* do we have any more buffer space to fill? */
if(free_space_left <= 0)
{
DEBUGF("0\n");
filling = false;
generate_postbuffer_events();
storage_sleep();
break;
}
/* Read small chunks while we are below the low water mark */
if(unplayed_space_left < low_watermark)
amount_to_read = MIN(MPEG_LOW_WATER_CHUNKSIZE,
free_space_left);
else
amount_to_read = free_space_left;
/* Don't read more than until the end of the buffer */
amount_to_read = MIN(audiobuflen - audiobuf_write,
amount_to_read);
#if (CONFIG_STORAGE & STORAGE_MMC)
/* MMC is slow, so don't read too large chunks */
amount_to_read = MIN(0x40000, amount_to_read);
#elif MEMORYSIZE == 8
amount_to_read = MIN(0x100000, amount_to_read);
#endif
/* Read as much mpeg data as we can fit in the buffer */
if(mpeg_file >= 0)
{
DEBUGF("R\n");
t1 = current_tick;
len = read(mpeg_file, mpeg_audiobuf + audiobuf_write,
amount_to_read);
if(len > 0)
{
t2 = current_tick;
DEBUGF("time: %d\n", t2 - t1);
DEBUGF("R: %x\n", len);
/* Now make sure that we don't feed the MAS with ID3V1
data */
if (len < amount_to_read)
{
int i;
static const unsigned char tag[] = "TAG";
int taglen = 128;
int tagptr = audiobuf_write + len - 128;
/* Really rare case: entire potential tag wasn't
read in this call AND audiobuf_write < 128 */
if (tagptr < 0)
tagptr += audiobuflen;
for(i = 0;i < 3;i++)
{
if(tagptr >= audiobuflen)
tagptr -= audiobuflen;
if(mpeg_audiobuf[tagptr] != tag[i])
{
taglen = 0;
break;
}
tagptr++;
}
if(taglen)
{
/* Skip id3v1 tag */
DEBUGF("Skipping ID3v1 tag\n");
len -= taglen;
/* In the very rare case when the entire tag
wasn't read in this read() len will be < 0.
Take care of this when changing the write
pointer. */
}
}
audiobuf_write += len;
if (audiobuf_write < 0)
audiobuf_write += audiobuflen;
if(audiobuf_write >= audiobuflen)
{
audiobuf_write = 0;
DEBUGF("W\n");
}
/* Tell ourselves that we want more data */
queue_post(&mpeg_queue, MPEG_NEED_DATA, 0);
}
else
{
if(len < 0)
{
DEBUGF("MPEG read error\n");
}
close(mpeg_file);
mpeg_file = -1;
if(new_file(1) < 0)
{
/* No more data to play */
DEBUGF("No more files to play\n");
filling = false;
}
else
{
/* Tell ourselves that we want more data */
queue_post(&mpeg_queue, MPEG_NEED_DATA, 0);
}
}
}
break;
case MPEG_TRACK_CHANGE:
track_change();
break;
#ifndef USB_NONE
case SYS_USB_CONNECTED:
is_playing = false;
paused = false;
stop_playing();
/* Tell the USB thread that we are safe */
DEBUGF("mpeg_thread got SYS_USB_CONNECTED\n");
usb_acknowledge(SYS_USB_CONNECTED_ACK);
/* Wait until the USB cable is extracted again */
usb_wait_for_disconnect(&mpeg_queue);
break;
#endif /* !USB_NONE */
case SYS_TIMEOUT:
if (playing)
playlist_update_resume_info(audio_current_track());
break;
}
}
}
#endif /* !SIMULATOR */
struct mp3entry* audio_current_track(void)
{
#ifdef SIMULATOR
struct mp3entry *id3 = &taginfo;
#else /* !SIMULATOR */
if(num_tracks_in_memory())
{
struct mp3entry *id3 = &get_trackdata(0)->id3;
#endif
if (!checked_for_cuesheet && curr_cuesheet && id3->cuesheet == NULL)
{
checked_for_cuesheet = true; /* only check once per track */
struct cuesheet_file cue_file;
if (look_for_cuesheet_file(id3, &cue_file) &&
parse_cuesheet(&cue_file, curr_cuesheet))
{
id3->cuesheet = curr_cuesheet;
}
}
return id3;
#ifndef SIMULATOR
}
else
return NULL;
#endif /* !SIMULATOR */
}
struct mp3entry* audio_next_track(void)
{
#ifdef SIMULATOR
return &taginfo;
#else /* !SIMULATOR */
if(num_tracks_in_memory() > 1)
return &get_trackdata(1)->id3;
else
return NULL;
#endif /* !SIMULATOR */
}
size_t audio_buffer_size(void)
{
if (audiobuf_handle > 0)
return audiobuflen;
return 0;
}
size_t audio_buffer_available(void)
{
size_t size = 0;
size_t core_size = core_available();
if (audiobuf_handle > 0)
return audiobuflen - AUDIO_BUFFER_RESERVE - 128;
return MAX(core_size, size);
}
static void audio_reset_buffer_noalloc(void* buf, size_t bufsize)
{
mpeg_audiobuf = buf;
audiobuflen = bufsize;
if (global_settings.cuesheet)
{ /* enable cuesheet support */
curr_cuesheet = (struct cuesheet*)mpeg_audiobuf;
mpeg_audiobuf = SKIPBYTES(mpeg_audiobuf, sizeof(struct cuesheet));
audiobuflen -= sizeof(struct cuesheet);
}
}
static void audio_reset_buffer(void)
{
size_t bufsize = audiobuflen;
/* alloc buffer if it's was never allocated or freed by audio_hard_stop()
* because voice cannot be played during audio playback make
* talk.c give up all buffers and disable itself */
if (!audiobuf_handle)
{
talk_buffer_set_policy(TALK_BUFFER_LOOSE);
audiobuf_handle = core_alloc_maximum("audiobuf", &bufsize, &ops);
}
audio_reset_buffer_noalloc(core_get_data(audiobuf_handle), bufsize);
}
void audio_play(unsigned long elapsed, unsigned long offset)
{
audio_reset_buffer();
#ifdef SIMULATOR
char name_buf[MAX_PATH+1];
const char* trackname;
int steps=0;
is_playing = true;
do {
trackname = playlist_peek(steps, name_buf, sizeof(name_buf));
if (!trackname)
break;
if(mp3info(&taginfo, trackname)) {
/* bad mp3, move on */
if(++steps > playlist_amount())
break;
continue;
}
#ifdef HAVE_MPEG_PLAY
real_mpeg_play(trackname);
#endif
playlist_next(steps);
if (!offset && elapsed)
{
/* has an elapsed time but no offset; elapsed may take
precedence in this case */
taginfo.elapsed = elapsed;
taginfo.offset = audio_get_file_pos_int(&taginfo);
}
else
{
taginfo.offset = offset;
set_elapsed(&taginfo);
}
is_playing = true;
playing = true;
break;
} while(1);
#else /* !SIMULATOR */
static struct audio_resume_info resume;
is_playing = true;
resume.elapsed = elapsed;
resume.offset = offset;
queue_post(&mpeg_queue, MPEG_PLAY, (intptr_t)&resume);
#endif /* !SIMULATOR */
mpeg_errno = 0;
}
void audio_stop(void)
{
if (audiobuf_handle <= 0)
return; /* nothing to do, must be hard-stopped already */
#ifndef SIMULATOR
mpeg_stop_done = false;
queue_post(&mpeg_queue, MPEG_STOP, 0);
while(!mpeg_stop_done)
yield();
#else /* SIMULATOR */
paused = false;
is_playing = false;
playing = false;
#endif /* SIMULATOR */
}
/* dummy */
void audio_stop_recording(void)
{
audio_stop();
}
void audio_hard_stop(void)
{
if (audiobuf_handle > 0)
{
audio_stop();
audiobuf_handle = core_free(audiobuf_handle);
mpeg_audiobuf = NULL;
talk_buffer_set_policy(TALK_BUFFER_DEFAULT);
}
}
void audio_pause(void)
{
#ifndef SIMULATOR
queue_post(&mpeg_queue, MPEG_PAUSE, 0);
#else /* SIMULATOR */
is_playing = true;
playing = false;
paused = true;
#endif /* SIMULATOR */
}
void audio_resume(void)
{
#ifndef SIMULATOR
queue_post(&mpeg_queue, MPEG_RESUME, 0);
#else /* SIMULATOR */
is_playing = true;
playing = true;
paused = false;
#endif /* SIMULATOR */
}
void audio_next(void)
{
#ifndef SIMULATOR
queue_remove_from_head(&mpeg_queue, MPEG_NEED_DATA);
queue_post(&mpeg_queue, MPEG_NEXT, 0);
#else /* SIMULATOR */
char name_buf[MAX_PATH+1];
const char* file;
int steps = 1;
do {
file = playlist_peek(steps, name_buf, sizeof(name_buf));
if(!file)
break;
if(mp3info(&taginfo, file)) {
if(++steps > playlist_amount())
break;
continue;
}
playlist_next(steps);
current_track_counter++;
is_playing = true;
playing = true;
break;
} while(1);
#endif /* SIMULATOR */
}
void audio_prev(void)
{
#ifndef SIMULATOR
queue_remove_from_head(&mpeg_queue, MPEG_NEED_DATA);
queue_post(&mpeg_queue, MPEG_PREV, 0);
#else /* SIMULATOR */
char name_buf[MAX_PATH+1];
const char* file;
int steps = -1;
do {
file = playlist_peek(steps, name_buf, sizeof(name_buf));
if(!file)
break;
if(mp3info(&taginfo, file)) {
steps--;
continue;
}
playlist_next(steps);
current_track_counter++;
is_playing = true;
playing = true;
break;
} while(1);
#endif /* SIMULATOR */
}
void audio_ff_rewind(long newpos)
{
#ifndef SIMULATOR
queue_post(&mpeg_queue, MPEG_FF_REWIND, newpos);
#else /* SIMULATOR */
(void)newpos;
#endif /* SIMULATOR */
}
void audio_flush_and_reload_tracks(void)
{
#ifndef SIMULATOR
queue_post(&mpeg_queue, MPEG_FLUSH_RELOAD, 0);
#endif /* !SIMULATOR*/
}
int audio_status(void)
{
int ret = 0;
if(is_playing)
ret |= AUDIO_STATUS_PLAY;
if(paused)
ret |= AUDIO_STATUS_PAUSE;
if(mpeg_errno)
ret |= AUDIO_STATUS_ERROR;
return ret;
}
/* Unused function
unsigned int audio_error(void)
{
return mpeg_errno;
}
*/
void audio_error_clear(void)
{
mpeg_errno = 0;
}
#ifdef SIMULATOR
static void mpeg_thread(void)
{
struct mp3entry* id3;
while ( 1 ) {
if (is_playing) {
id3 = audio_current_track();
if (!paused)
{
id3->elapsed+=1000;
id3->offset+=1000;
}
if (id3->elapsed>=id3->length)
audio_next();
}
sleep(HZ);
}
}
#endif /* SIMULATOR */
void audio_init(void)
{
mpeg_errno = 0;
audio_reset_buffer();
#ifndef SIMULATOR
queue_init(&mpeg_queue, true);
#endif /* !SIMULATOR */
audio_thread_id = create_thread(mpeg_thread, mpeg_stack,
sizeof(mpeg_stack), 0, mpeg_thread_name
IF_PRIO(, PRIORITY_SYSTEM)
IF_COP(, CPU));
memset(trackdata, 0, sizeof(trackdata));
#ifdef DEBUG
#ifndef SIMULATOR
dbg_timer_start();
dbg_cnt2us(0);
#endif /* !SIMULATOR */
#endif /* DEBUG */
audio_is_initialized = true;
}
#endif /* CONFIG_CODEC != SWCODEC */