f736159e5c
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@24206 a1c6a512-1295-4272-9138-f99709370657
2900 lines
85 KiB
C
2900 lines
85 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 "buffer.h"
|
|
#include "mp3_playback.h"
|
|
#include "sound.h"
|
|
#include "bitswap.h"
|
|
#include "appevents.h"
|
|
#include "cuesheet.h"
|
|
#include "settings.h"
|
|
#ifndef SIMULATOR
|
|
#include "i2c.h"
|
|
#include "mas.h"
|
|
#include "system.h"
|
|
#include "usb.h"
|
|
#include "file.h"
|
|
#include "hwcompat.h"
|
|
#endif /* !SIMULATOR */
|
|
#ifdef HAVE_LCD_BITMAP
|
|
#include "lcd.h"
|
|
#endif
|
|
|
|
#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
|
|
|
|
#if CONFIG_CODEC == MAS3587F
|
|
extern enum /* from mp3_playback.c */
|
|
{
|
|
MPEG_DECODER,
|
|
MPEG_ENCODER
|
|
} mpeg_mode;
|
|
#endif /* CONFIG_CODEC == MAS3587F */
|
|
|
|
extern char* playlist_peek(int steps);
|
|
extern bool playlist_check(int steps);
|
|
extern int playlist_next(int steps);
|
|
extern int playlist_amount(void);
|
|
extern int playlist_update_resume_info(const struct mp3entry* id3);
|
|
|
|
#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
|
|
/* Play time of the previous track */
|
|
static unsigned long prev_track_elapsed;
|
|
|
|
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 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 */
|
|
|
|
#ifdef SIMULATOR
|
|
static char mpeg_stack[DEFAULT_STACK_SIZE];
|
|
static struct mp3entry taginfo;
|
|
|
|
#else /* !SIMULATOR */
|
|
static struct event_queue mpeg_queue;
|
|
static long mpeg_stack[(DEFAULT_STACK_SIZE + 0x1000)/sizeof(long)];
|
|
|
|
static int audiobuflen;
|
|
static int audiobuf_write;
|
|
static int audiobuf_swapwrite;
|
|
static int 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 */
|
|
#if CONFIG_CODEC == MAS3587F
|
|
static char recording_filename[MAX_PATH]; /* argument to thread */
|
|
static char delayed_filename[MAX_PATH]; /* internal copy of above */
|
|
|
|
static char xing_buffer[MAX_XING_HEADER_SIZE];
|
|
|
|
static bool init_recording_done;
|
|
static bool init_playback_done;
|
|
static bool prerecording; /* True if prerecording is enabled */
|
|
static bool is_prerecording; /* True if we are prerecording */
|
|
static bool is_recording; /* We are recording */
|
|
|
|
static enum {
|
|
NOT_SAVING = 0, /* reasons to save data, sorted by importance */
|
|
BUFFER_FULL,
|
|
NEW_FILE,
|
|
STOP_RECORDING
|
|
} saving_status;
|
|
|
|
static int rec_frequency_index; /* For create_xing_header() calls */
|
|
static int rec_version_index; /* For create_xing_header() calls */
|
|
|
|
struct prerecord_info {
|
|
int mempos;
|
|
unsigned long framecount;
|
|
};
|
|
|
|
static struct prerecord_info prerecord_buffer[MPEG_MAX_PRERECORD_SECONDS];
|
|
static int prerecord_index; /* Current index in the prerecord buffer */
|
|
static int prerecording_max_seconds; /* Max number of seconds to store */
|
|
static int prerecord_count; /* Number of seconds in the prerecord buffer */
|
|
static int prerecord_timeout; /* The tick count of the next prerecord data
|
|
store */
|
|
|
|
static unsigned long record_start_time; /* Value of current_tick when recording
|
|
was started */
|
|
static unsigned long pause_start_time; /* Value of current_tick when pause was
|
|
started */
|
|
static unsigned long last_rec_time;
|
|
static unsigned long num_rec_bytes;
|
|
static unsigned long last_rec_bytes;
|
|
static unsigned long frame_count_start;
|
|
static unsigned long frame_count_end;
|
|
static unsigned long saved_header = 0;
|
|
|
|
/* Shadow MAS registers */
|
|
unsigned long shadow_encoder_control = 0;
|
|
#endif /* CONFIG_CODEC == MAS3587F */
|
|
|
|
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
|
|
unsigned long shadow_io_control_main = 0;
|
|
unsigned long shadow_soft_mute = 0;
|
|
unsigned shadow_codec_reg0;
|
|
#endif /* (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) */
|
|
|
|
#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 */
|
|
|
|
#if (CONFIG_CODEC == MAS3587F) && !defined(SIMULATOR)
|
|
static void init_recording(void);
|
|
static void prepend_header(void);
|
|
static void update_header(void);
|
|
static void start_prerecording(void);
|
|
static void start_recording(void);
|
|
static void stop_recording(void);
|
|
static int get_unsaved_space(void);
|
|
static void pause_recording(void);
|
|
static void resume_recording(void);
|
|
#endif /* (CONFIG_CODEC == MAS3587F) && !defined(SIMULATOR) */
|
|
|
|
|
|
#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;
|
|
}
|
|
#endif
|
|
|
|
/***********************************************************************/
|
|
|
|
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);
|
|
}
|
|
|
|
int audio_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->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;
|
|
}
|
|
|
|
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 */
|
|
}
|
|
|
|
#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_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_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;
|
|
|
|
#if CONFIG_CPU == SH7034
|
|
dbgdata->dma_on = (SCR0 & 0x80) != 0;
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
#if CONFIG_CODEC == MAS3587F
|
|
static int get_unsaved_space(void)
|
|
{
|
|
int space = audiobuf_write - audiobuf_read;
|
|
if (space < 0)
|
|
space += audiobuflen;
|
|
return space;
|
|
}
|
|
|
|
static void drain_dma_buffer(void)
|
|
{
|
|
while (PBDRH & 0x40)
|
|
{
|
|
xor_b(0x08, &PADRH);
|
|
|
|
while (PBDRH & 0x80);
|
|
|
|
xor_b(0x08, &PADRH);
|
|
|
|
while (!(PBDRH & 0x80));
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static long timing_info_index = 0;
|
|
static long timing_info[1024];
|
|
#endif /* DEBUG */
|
|
|
|
void rec_tick (void) __attribute__ ((section (".icode")));
|
|
void rec_tick(void)
|
|
{
|
|
int i;
|
|
int delay;
|
|
char data;
|
|
|
|
if(is_recording && (PBDRH & 0x40))
|
|
{
|
|
#ifdef DEBUG
|
|
timing_info[timing_info_index++] = current_tick;
|
|
TCNT2 = 0;
|
|
#endif /* DEBUG */
|
|
/* Note: Although this loop is run in interrupt context, further
|
|
* optimisation will do no good. The MAS would then deliver bad
|
|
* frames occasionally, as observed in extended experiments. */
|
|
i = 0;
|
|
while (PBDRH & 0x40) /* We try to read as long as EOD is high */
|
|
{
|
|
xor_b(0x08, &PADRH); /* Set PR active, independent of polarity */
|
|
|
|
delay = 100;
|
|
while (PBDRH & 0x80) /* Wait until /RTW becomes active */
|
|
{
|
|
if (--delay <= 0) /* Bail out if we have to wait too long */
|
|
{ /* i.e. the MAS doesn't want to talk to us */
|
|
xor_b(0x08, &PADRH); /* Set PR inactive */
|
|
goto transfer_end; /* and get out of here */
|
|
}
|
|
}
|
|
|
|
data = *(unsigned char *)0x04000000; /* read data byte */
|
|
|
|
xor_b(0x08, &PADRH); /* Set PR inactive */
|
|
|
|
audiobuf[audiobuf_write++] = data;
|
|
|
|
if (audiobuf_write >= audiobuflen)
|
|
audiobuf_write = 0;
|
|
|
|
i++;
|
|
}
|
|
transfer_end:
|
|
|
|
#ifdef DEBUG
|
|
timing_info[timing_info_index++] = TCNT2 + (i << 16);
|
|
timing_info_index &= 0x3ff;
|
|
#endif /* DEBUG */
|
|
|
|
num_rec_bytes += i;
|
|
|
|
if(is_prerecording)
|
|
{
|
|
if(TIME_AFTER(current_tick, prerecord_timeout))
|
|
{
|
|
prerecord_timeout = current_tick + HZ;
|
|
queue_post(&mpeg_queue, MPEG_PRERECORDING_TICK, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Signal to save the data if we are running out of buffer
|
|
space */
|
|
if (audiobuflen - get_unsaved_space() < MPEG_RECORDING_LOW_WATER
|
|
&& saving_status == NOT_SAVING)
|
|
{
|
|
saving_status = BUFFER_FULL;
|
|
queue_post(&mpeg_queue, MPEG_SAVE_DATA, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif /* CONFIG_CODEC == MAS3587F */
|
|
|
|
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(unsigned char** 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 = 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_event(PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE, NULL);
|
|
debug_tags();
|
|
return track;
|
|
}
|
|
|
|
static int new_file(int steps)
|
|
{
|
|
int max_steps = playlist_amount();
|
|
int start = 0;
|
|
int i;
|
|
struct trackdata *track;
|
|
|
|
/* 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 {
|
|
char *trackname;
|
|
|
|
trackname = playlist_peek( start + steps );
|
|
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)
|
|
{
|
|
struct trackdata *track;
|
|
|
|
/* Stop the current stream */
|
|
mp3_play_stop();
|
|
playing = false;
|
|
filling = false;
|
|
|
|
track = get_trackdata(0);
|
|
if (track != NULL)
|
|
prev_track_elapsed = track->id3.elapsed;
|
|
|
|
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) {
|
|
struct trackdata *track;
|
|
|
|
play_pending = false;
|
|
playing = false;
|
|
mp3_play_pause(false);
|
|
|
|
track = get_trackdata(0);
|
|
if (track != NULL)
|
|
prev_track_elapsed = track->id3.elapsed;
|
|
|
|
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");
|
|
|
|
struct trackdata *track = get_trackdata(0);
|
|
prev_track_elapsed = track->id3.elapsed;
|
|
|
|
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
|
|
/* Reset the AVC */
|
|
sound_set_avc(-1);
|
|
#endif /* (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) */
|
|
|
|
if (num_tracks_in_memory() > 0)
|
|
{
|
|
remove_current_tag();
|
|
update_playlist();
|
|
if (is_playing)
|
|
send_event(PLAYBACK_EVENT_TRACK_CHANGE, audio_current_track());
|
|
}
|
|
|
|
current_track_counter++;
|
|
}
|
|
|
|
unsigned long audio_prev_elapsed(void)
|
|
{
|
|
return prev_track_elapsed;
|
|
}
|
|
|
|
#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_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(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(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;
|
|
int start_offset;
|
|
#if CONFIG_CODEC == MAS3587F
|
|
int amount_to_save;
|
|
int save_endpos = 0;
|
|
int rc;
|
|
int level;
|
|
long offset;
|
|
#endif /* CONFIG_CODEC == MAS3587F */
|
|
|
|
is_playing = false;
|
|
play_pending = false;
|
|
playing = false;
|
|
mpeg_file = -1;
|
|
|
|
while(1)
|
|
{
|
|
#if CONFIG_CODEC == MAS3587F
|
|
if(mpeg_mode == MPEG_DECODER)
|
|
{
|
|
#endif /* CONFIG_CODEC == MAS3587F */
|
|
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_offset = (int)ev.data;
|
|
|
|
/* mid-song resume? */
|
|
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:
|
|
DEBUGF("MPEG_STOP\n");
|
|
is_playing = false;
|
|
paused = false;
|
|
|
|
if (playing)
|
|
playlist_update_resume_info(audio_current_track());
|
|
|
|
stop_playing();
|
|
mpeg_stop_done = true;
|
|
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(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();
|
|
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(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 MEM == 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, 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(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 */
|
|
|
|
#if CONFIG_CODEC == MAS3587F
|
|
case MPEG_INIT_RECORDING:
|
|
init_recording();
|
|
init_recording_done = true;
|
|
break;
|
|
#endif /* CONFIG_CODEC == MAS3587F */
|
|
|
|
case SYS_TIMEOUT:
|
|
if (playing)
|
|
playlist_update_resume_info(audio_current_track());
|
|
break;
|
|
}
|
|
#if CONFIG_CODEC == MAS3587F
|
|
}
|
|
else
|
|
{
|
|
queue_wait(&mpeg_queue, &ev);
|
|
switch(ev.id)
|
|
{
|
|
case MPEG_RECORD:
|
|
if (is_prerecording)
|
|
{
|
|
int startpos;
|
|
|
|
/* Go back prerecord_count seconds in the buffer */
|
|
startpos = prerecord_index - prerecord_count;
|
|
if(startpos < 0)
|
|
startpos += prerecording_max_seconds;
|
|
|
|
/* Read the position data from the prerecord buffer */
|
|
frame_count_start = prerecord_buffer[startpos].framecount;
|
|
startpos = prerecord_buffer[startpos].mempos;
|
|
|
|
DEBUGF("Start looking at address %x (%x)\n",
|
|
audiobuf+startpos, startpos);
|
|
|
|
saved_header = mpeg_get_last_header();
|
|
|
|
mem_find_next_frame(startpos, &offset, 1800,
|
|
saved_header);
|
|
|
|
audiobuf_read = startpos + offset;
|
|
if(audiobuf_read >= audiobuflen)
|
|
audiobuf_read -= audiobuflen;
|
|
|
|
DEBUGF("New audiobuf_read address: %x (%x)\n",
|
|
audiobuf+audiobuf_read, audiobuf_read);
|
|
|
|
level = disable_irq_save();
|
|
num_rec_bytes = get_unsaved_space();
|
|
restore_irq(level);
|
|
}
|
|
else
|
|
{
|
|
frame_count_start = 0;
|
|
num_rec_bytes = 0;
|
|
audiobuf_read = MPEG_RESERVED_HEADER_SPACE;
|
|
audiobuf_write = MPEG_RESERVED_HEADER_SPACE;
|
|
}
|
|
|
|
prepend_header();
|
|
DEBUGF("Recording...\n");
|
|
start_recording();
|
|
|
|
/* Wait until at least one frame is encoded and get the
|
|
frame header, for later use by the Xing header
|
|
generation */
|
|
sleep(HZ/5);
|
|
saved_header = mpeg_get_last_header();
|
|
|
|
/* delayed until buffer is saved, don't open yet */
|
|
strcpy(delayed_filename, recording_filename);
|
|
mpeg_file = -1;
|
|
|
|
break;
|
|
|
|
case MPEG_STOP:
|
|
DEBUGF("MPEG_STOP\n");
|
|
|
|
stop_recording();
|
|
|
|
/* Save the remaining data in the buffer */
|
|
save_endpos = audiobuf_write;
|
|
saving_status = STOP_RECORDING;
|
|
queue_post(&mpeg_queue, MPEG_SAVE_DATA, 0);
|
|
break;
|
|
|
|
case MPEG_STOP_DONE:
|
|
DEBUGF("MPEG_STOP_DONE\n");
|
|
|
|
if (mpeg_file >= 0)
|
|
close(mpeg_file);
|
|
mpeg_file = -1;
|
|
|
|
update_header();
|
|
#ifdef DEBUG1
|
|
{
|
|
int i;
|
|
for(i = 0;i < 512;i++)
|
|
{
|
|
DEBUGF("%d - %d us (%d bytes)\n",
|
|
timing_info[i*2],
|
|
(timing_info[i*2+1] & 0xffff) *
|
|
10000 / 13824,
|
|
timing_info[i*2+1] >> 16);
|
|
}
|
|
}
|
|
#endif /* DEBUG1 */
|
|
|
|
if (prerecording)
|
|
{
|
|
start_prerecording();
|
|
}
|
|
mpeg_stop_done = true;
|
|
break;
|
|
|
|
case MPEG_NEW_FILE:
|
|
/* Bail out when a more important save is happening */
|
|
if (saving_status > NEW_FILE)
|
|
break;
|
|
|
|
/* Make sure we have at least one complete frame
|
|
in the buffer. If we haven't recorded a single
|
|
frame within 200ms, the MAS is probably not recording
|
|
anything, and we bail out. */
|
|
amount_to_save = get_unsaved_space();
|
|
if (amount_to_save < 1800)
|
|
{
|
|
sleep(HZ/5);
|
|
amount_to_save = get_unsaved_space();
|
|
}
|
|
|
|
mas_readmem(MAS_BANK_D0, MAS_D0_MPEG_FRAME_COUNT,
|
|
&frame_count_end, 1);
|
|
|
|
last_rec_time = current_tick - record_start_time;
|
|
record_start_time = current_tick;
|
|
if (paused)
|
|
pause_start_time = record_start_time;
|
|
|
|
/* capture all values at one point */
|
|
level = disable_irq_save();
|
|
save_endpos = audiobuf_write;
|
|
last_rec_bytes = num_rec_bytes;
|
|
num_rec_bytes = 0;
|
|
restore_irq(level);
|
|
|
|
if (amount_to_save >= 1800)
|
|
{
|
|
/* Now find a frame boundary to split at */
|
|
save_endpos -= 1800;
|
|
if (save_endpos < 0)
|
|
save_endpos += audiobuflen;
|
|
|
|
rc = mem_find_next_frame(save_endpos, &offset, 1800,
|
|
saved_header);
|
|
if (!rc) /* No header found, save whole buffer */
|
|
offset = 1800;
|
|
|
|
save_endpos += offset;
|
|
if (save_endpos >= audiobuflen)
|
|
save_endpos -= audiobuflen;
|
|
|
|
last_rec_bytes += offset - 1800;
|
|
level = disable_irq_save();
|
|
num_rec_bytes += 1800 - offset;
|
|
restore_irq(level);
|
|
}
|
|
|
|
saving_status = NEW_FILE;
|
|
queue_post(&mpeg_queue, MPEG_SAVE_DATA, 0);
|
|
break;
|
|
|
|
case MPEG_SAVE_DATA:
|
|
if (saving_status == BUFFER_FULL)
|
|
save_endpos = audiobuf_write;
|
|
|
|
if (mpeg_file < 0) /* delayed file open */
|
|
{
|
|
mpeg_file = open(delayed_filename, O_WRONLY|O_CREAT);
|
|
|
|
if (mpeg_file < 0)
|
|
panicf("recfile: %d", mpeg_file);
|
|
}
|
|
|
|
amount_to_save = save_endpos - audiobuf_read;
|
|
if (amount_to_save < 0)
|
|
amount_to_save += audiobuflen;
|
|
|
|
amount_to_save = MIN(amount_to_save,
|
|
audiobuflen - audiobuf_read);
|
|
#if (CONFIG_STORAGE & STORAGE_MMC)
|
|
/* MMC is slow, so don't save too large chunks at once */
|
|
amount_to_save = MIN(0x40000, amount_to_save);
|
|
#elif MEM == 8
|
|
amount_to_save = MIN(0x100000, amount_to_save);
|
|
#endif
|
|
rc = write(mpeg_file, audiobuf + audiobuf_read,
|
|
amount_to_save);
|
|
if (rc < 0)
|
|
{
|
|
if (errno == ENOSPC)
|
|
{
|
|
mpeg_errno = AUDIOERR_DISK_FULL;
|
|
stop_recording();
|
|
queue_post(&mpeg_queue, MPEG_STOP_DONE, 0);
|
|
/* will close the file */
|
|
break;
|
|
}
|
|
else
|
|
panicf("rec wrt: %d", rc);
|
|
}
|
|
|
|
audiobuf_read += amount_to_save;
|
|
if (audiobuf_read >= audiobuflen)
|
|
audiobuf_read = 0;
|
|
|
|
if (audiobuf_read == save_endpos) /* all saved */
|
|
{
|
|
switch (saving_status)
|
|
{
|
|
case BUFFER_FULL:
|
|
rc = fsync(mpeg_file);
|
|
if (rc < 0)
|
|
panicf("rec fls: %d", rc);
|
|
storage_sleep();
|
|
break;
|
|
|
|
case NEW_FILE:
|
|
/* Close the current file */
|
|
rc = close(mpeg_file);
|
|
if (rc < 0)
|
|
panicf("rec cls: %d", rc);
|
|
mpeg_file = -1;
|
|
update_header();
|
|
storage_sleep();
|
|
|
|
/* copy new filename */
|
|
strcpy(delayed_filename, recording_filename);
|
|
prepend_header();
|
|
frame_count_start = frame_count_end;
|
|
break;
|
|
|
|
case STOP_RECORDING:
|
|
queue_post(&mpeg_queue, MPEG_STOP_DONE, 0);
|
|
/* will close the file */
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
saving_status = NOT_SAVING;
|
|
}
|
|
else /* tell ourselves to save the next chunk */
|
|
queue_post(&mpeg_queue, MPEG_SAVE_DATA, 0);
|
|
|
|
break;
|
|
|
|
case MPEG_PRERECORDING_TICK:
|
|
if(!is_prerecording)
|
|
break;
|
|
|
|
/* Store the write pointer every second */
|
|
prerecord_buffer[prerecord_index].mempos = audiobuf_write;
|
|
mas_readmem(MAS_BANK_D0, MAS_D0_MPEG_FRAME_COUNT,
|
|
&prerecord_buffer[prerecord_index].framecount, 1);
|
|
|
|
/* Wrap if necessary */
|
|
if(++prerecord_index == prerecording_max_seconds)
|
|
prerecord_index = 0;
|
|
|
|
/* Update the number of seconds recorded */
|
|
if(prerecord_count < prerecording_max_seconds)
|
|
prerecord_count++;
|
|
break;
|
|
|
|
case MPEG_INIT_PLAYBACK:
|
|
/* Stop the prerecording */
|
|
stop_recording();
|
|
reset_mp3_buffer();
|
|
mp3_play_init();
|
|
init_playback_done = true;
|
|
break;
|
|
|
|
case MPEG_PAUSE_RECORDING:
|
|
pause_recording();
|
|
break;
|
|
|
|
case MPEG_RESUME_RECORDING:
|
|
resume_recording();
|
|
break;
|
|
|
|
case SYS_USB_CONNECTED:
|
|
/* We can safely go to USB mode if no recording
|
|
is taking place */
|
|
if((!is_recording || is_prerecording) && mpeg_stop_done)
|
|
{
|
|
/* Even if we aren't recording, we still call this
|
|
function, to put the MAS in monitoring mode,
|
|
to save power. */
|
|
stop_recording();
|
|
|
|
/* 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 /* CONFIG_CODEC == MAS3587F */
|
|
}
|
|
}
|
|
#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 */
|
|
char cuepath[MAX_PATH];
|
|
|
|
if (look_for_cuesheet_file(id3->path, cuepath) &&
|
|
parse_cuesheet(cuepath, curr_cuesheet))
|
|
{
|
|
id3->cuesheet = curr_cuesheet;
|
|
cue_spoof_id3(curr_cuesheet, id3);
|
|
}
|
|
}
|
|
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 */
|
|
}
|
|
|
|
#if CONFIG_CODEC == MAS3587F
|
|
#ifndef SIMULATOR
|
|
void audio_init_playback(void)
|
|
{
|
|
init_playback_done = false;
|
|
queue_post(&mpeg_queue, MPEG_INIT_PLAYBACK, 0);
|
|
|
|
while(!init_playback_done)
|
|
sleep(1);
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* Recording functions
|
|
***************************************************************************/
|
|
void audio_init_recording(unsigned int buffer_offset)
|
|
{
|
|
buffer_offset = buffer_offset;
|
|
init_recording_done = false;
|
|
queue_post(&mpeg_queue, MPEG_INIT_RECORDING, 0);
|
|
|
|
while(!init_recording_done)
|
|
sleep(1);
|
|
}
|
|
|
|
static void init_recording(void)
|
|
{
|
|
unsigned long val;
|
|
int rc;
|
|
|
|
/* Disable IRQ6 */
|
|
IPRB &= 0xff0f;
|
|
|
|
stop_playing();
|
|
is_playing = false;
|
|
paused = false;
|
|
|
|
/* Init the recording variables */
|
|
is_recording = false;
|
|
is_prerecording = false;
|
|
|
|
mpeg_stop_done = true;
|
|
|
|
mas_reset();
|
|
|
|
/* Enable the audio CODEC and the DSP core, max analog voltage range */
|
|
rc = mas_direct_config_write(MAS_CONTROL, 0x8c00);
|
|
if(rc < 0)
|
|
panicf("mas_ctrl_w: %d", rc);
|
|
|
|
/* Stop the current application */
|
|
val = 0;
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_APP_SELECT, &val, 1);
|
|
do
|
|
{
|
|
mas_readmem(MAS_BANK_D0, MAS_D0_APP_RUNNING, &val, 1);
|
|
} while(val);
|
|
|
|
/* Perform black magic as described by the data sheet */
|
|
if((mas_version_code & 0x0fff) == 0x0102)
|
|
{
|
|
DEBUGF("Performing MAS black magic for B2 version\n");
|
|
mas_writereg(0xa3, 0x98);
|
|
mas_writereg(0x94, 0xfffff);
|
|
val = 0;
|
|
mas_writemem(MAS_BANK_D1, 0, &val, 1);
|
|
mas_writereg(0xa3, 0x90);
|
|
}
|
|
|
|
/* Enable A/D Converters */
|
|
shadow_codec_reg0 = 0xcccd;
|
|
mas_codec_writereg(0x0, shadow_codec_reg0);
|
|
|
|
/* Copy left channel to right (mono mode) */
|
|
mas_codec_writereg(8, 0x8000);
|
|
|
|
/* ADC scale 0%, DSP scale 100%
|
|
We use the DSP output for monitoring, because it works with all
|
|
sources including S/PDIF */
|
|
mas_codec_writereg(6, 0x0000);
|
|
mas_codec_writereg(7, 0x4000);
|
|
|
|
/* No mute */
|
|
shadow_soft_mute = 0;
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_SOFT_MUTE, &shadow_soft_mute, 1);
|
|
|
|
#ifdef HAVE_SPDIF_OUT
|
|
val = 0x09; /* Disable SDO and SDI, low impedance S/PDIF outputs */
|
|
#else
|
|
val = 0x2d; /* Disable SDO and SDI, disable S/PDIF output */
|
|
#endif
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_INTERFACE_CONTROL, &val, 1);
|
|
|
|
/* Set Demand mode, monitoring OFF and validate all settings */
|
|
shadow_io_control_main = 0x125;
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_IO_CONTROL_MAIN, &shadow_io_control_main, 1);
|
|
|
|
/* Start the encoder application */
|
|
val = 0x40;
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_APP_SELECT, &val, 1);
|
|
do
|
|
{
|
|
mas_readmem(MAS_BANK_D0, MAS_D0_APP_RUNNING, &val, 1);
|
|
} while(!(val & 0x40));
|
|
|
|
/* We have started the recording application with monitoring OFF.
|
|
This is because we want to record at least one frame to fill the DMA
|
|
buffer, because the silly MAS will not negate EOD until at least one
|
|
DMA transfer has taken place.
|
|
Now let's wait for some data to be encoded. */
|
|
sleep(HZ/5);
|
|
|
|
/* Now set it to Monitoring mode as default, saves power */
|
|
shadow_io_control_main = 0x525;
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_IO_CONTROL_MAIN, &shadow_io_control_main, 1);
|
|
|
|
/* Wait until the DSP has accepted the settings */
|
|
do
|
|
{
|
|
mas_readmem(MAS_BANK_D0, MAS_D0_IO_CONTROL_MAIN, &val,1);
|
|
} while(val & 1);
|
|
|
|
drain_dma_buffer();
|
|
mpeg_mode = MPEG_ENCODER;
|
|
|
|
DEBUGF("MAS Recording application started\n");
|
|
|
|
/* At this point, all settings are the reset MAS defaults, next thing is to
|
|
call mpeg_set_recording_options(). */
|
|
}
|
|
|
|
void audio_record(const char *filename)
|
|
{
|
|
mpeg_errno = 0;
|
|
|
|
strlcpy(recording_filename, filename, MAX_PATH);
|
|
|
|
queue_post(&mpeg_queue, MPEG_RECORD, 0);
|
|
}
|
|
|
|
void audio_pause_recording(void)
|
|
{
|
|
queue_post(&mpeg_queue, MPEG_PAUSE_RECORDING, 0);
|
|
}
|
|
|
|
void audio_resume_recording(void)
|
|
{
|
|
queue_post(&mpeg_queue, MPEG_RESUME_RECORDING, 0);
|
|
}
|
|
|
|
static void prepend_header(void)
|
|
{
|
|
int startpos;
|
|
unsigned i;
|
|
|
|
/* Make room for header */
|
|
audiobuf_read -= MPEG_RESERVED_HEADER_SPACE;
|
|
if(audiobuf_read < 0)
|
|
{
|
|
/* Clear the bottom half */
|
|
memset(audiobuf, 0, audiobuf_read + MPEG_RESERVED_HEADER_SPACE);
|
|
|
|
/* And the top half */
|
|
audiobuf_read += audiobuflen;
|
|
memset(audiobuf + audiobuf_read, 0, audiobuflen - audiobuf_read);
|
|
}
|
|
else
|
|
{
|
|
memset(audiobuf + audiobuf_read, 0, MPEG_RESERVED_HEADER_SPACE);
|
|
}
|
|
/* Copy the empty ID3 header */
|
|
startpos = audiobuf_read;
|
|
for(i = 0; i < sizeof(empty_id3_header); i++)
|
|
{
|
|
audiobuf[startpos++] = empty_id3_header[i];
|
|
if(startpos == audiobuflen)
|
|
startpos = 0;
|
|
}
|
|
}
|
|
|
|
static void update_header(void)
|
|
{
|
|
int fd, framelen;
|
|
unsigned long frames;
|
|
|
|
if (last_rec_bytes > 0)
|
|
{
|
|
/* Create the Xing header */
|
|
fd = open(delayed_filename, O_RDWR);
|
|
if (fd < 0)
|
|
panicf("rec upd: %d (%s)", fd, recording_filename);
|
|
|
|
frames = frame_count_end - frame_count_start;
|
|
/* If the number of recorded frames has reached 0x7ffff,
|
|
we can no longer trust it */
|
|
if (frame_count_end == 0x7ffff)
|
|
frames = 0;
|
|
|
|
/* saved_header is saved right before stopping the MAS */
|
|
framelen = create_xing_header(fd, 0, last_rec_bytes, xing_buffer,
|
|
frames, last_rec_time * (1000/HZ),
|
|
saved_header, NULL, false);
|
|
|
|
lseek(fd, MPEG_RESERVED_HEADER_SPACE - framelen, SEEK_SET);
|
|
write(fd, xing_buffer, framelen);
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
static void start_prerecording(void)
|
|
{
|
|
unsigned long val;
|
|
|
|
DEBUGF("Starting prerecording\n");
|
|
|
|
prerecord_index = 0;
|
|
prerecord_count = 0;
|
|
prerecord_timeout = current_tick + HZ;
|
|
memset(prerecord_buffer, 0, sizeof(prerecord_buffer));
|
|
reset_mp3_buffer();
|
|
|
|
is_prerecording = true;
|
|
|
|
/* Stop monitoring and start the encoder */
|
|
shadow_io_control_main &= ~(1 << 10);
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_IO_CONTROL_MAIN, &shadow_io_control_main, 1);
|
|
DEBUGF("mas_writemem(MAS_BANK_D0, IO_CONTROL_MAIN, %x)\n", shadow_io_control_main);
|
|
|
|
/* Wait until the DSP has accepted the settings */
|
|
do
|
|
{
|
|
mas_readmem(MAS_BANK_D0, MAS_D0_IO_CONTROL_MAIN, &val,1);
|
|
} while(val & 1);
|
|
|
|
is_recording = true;
|
|
saving_status = NOT_SAVING;
|
|
|
|
demand_irq_enable(true);
|
|
}
|
|
|
|
static void start_recording(void)
|
|
{
|
|
unsigned long val;
|
|
|
|
if(is_prerecording)
|
|
{
|
|
/* This will make the IRQ handler start recording
|
|
for real, i.e send MPEG_SAVE_DATA messages when
|
|
the buffer is full */
|
|
is_prerecording = false;
|
|
}
|
|
else
|
|
{
|
|
/* If prerecording is off, we need to stop the monitoring
|
|
and start the encoder */
|
|
shadow_io_control_main &= ~(1 << 10);
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_IO_CONTROL_MAIN, &shadow_io_control_main, 1);
|
|
DEBUGF("mas_writemem(MAS_BANK_D0, IO_CONTROL_MAIN, %x)\n", shadow_io_control_main);
|
|
|
|
/* Wait until the DSP has accepted the settings */
|
|
do
|
|
{
|
|
mas_readmem(MAS_BANK_D0, MAS_D0_IO_CONTROL_MAIN, &val,1);
|
|
} while(val & 1);
|
|
}
|
|
|
|
is_recording = true;
|
|
saving_status = NOT_SAVING;
|
|
paused = false;
|
|
|
|
/* Store the current time */
|
|
if(prerecording)
|
|
record_start_time = current_tick - prerecord_count * HZ;
|
|
else
|
|
record_start_time = current_tick;
|
|
|
|
pause_start_time = 0;
|
|
|
|
demand_irq_enable(true);
|
|
}
|
|
|
|
static void pause_recording(void)
|
|
{
|
|
pause_start_time = current_tick;
|
|
|
|
/* Set the pause bit */
|
|
shadow_soft_mute |= 2;
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_SOFT_MUTE, &shadow_soft_mute, 1);
|
|
|
|
paused = true;
|
|
}
|
|
|
|
static void resume_recording(void)
|
|
{
|
|
paused = false;
|
|
|
|
/* Clear the pause bit */
|
|
shadow_soft_mute &= ~2;
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_SOFT_MUTE, &shadow_soft_mute, 1);
|
|
|
|
/* Compensate for the time we have been paused */
|
|
if(pause_start_time)
|
|
{
|
|
record_start_time =
|
|
current_tick - (pause_start_time - record_start_time);
|
|
pause_start_time = 0;
|
|
}
|
|
}
|
|
|
|
static void stop_recording(void)
|
|
{
|
|
unsigned long val;
|
|
|
|
/* Let it finish the last frame */
|
|
if(!paused)
|
|
pause_recording();
|
|
sleep(HZ/5);
|
|
|
|
demand_irq_enable(false);
|
|
|
|
is_recording = false;
|
|
is_prerecording = false;
|
|
|
|
last_rec_bytes = num_rec_bytes;
|
|
mas_readmem(MAS_BANK_D0, MAS_D0_MPEG_FRAME_COUNT, &frame_count_end, 1);
|
|
last_rec_time = current_tick - record_start_time;
|
|
|
|
/* Start monitoring */
|
|
shadow_io_control_main |= (1 << 10);
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_IO_CONTROL_MAIN, &shadow_io_control_main, 1);
|
|
DEBUGF("mas_writemem(MAS_BANK_D0, IO_CONTROL_MAIN, %x)\n", shadow_io_control_main);
|
|
|
|
/* Wait until the DSP has accepted the settings */
|
|
do
|
|
{
|
|
mas_readmem(MAS_BANK_D0, MAS_D0_IO_CONTROL_MAIN, &val,1);
|
|
} while(val & 1);
|
|
|
|
resume_recording();
|
|
}
|
|
|
|
void audio_set_recording_options(struct audio_recording_options *options)
|
|
{
|
|
bool is_mpeg1;
|
|
|
|
is_mpeg1 = (options->rec_frequency < 3);
|
|
|
|
rec_version_index = is_mpeg1?3:2;
|
|
rec_frequency_index = options->rec_frequency % 3;
|
|
|
|
shadow_encoder_control = (options->rec_quality << 17) |
|
|
(rec_frequency_index << 10) |
|
|
((is_mpeg1?1:0) << 9) |
|
|
(((options->rec_channels * 2 + 1) & 3) << 6) |
|
|
(1 << 5) /* MS-stereo */ |
|
|
(1 << 2) /* Is an original */;
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_ENCODER_CONTROL, &shadow_encoder_control,1);
|
|
|
|
DEBUGF("mas_writemem(MAS_BANK_D0, ENCODER_CONTROL, %x)\n", shadow_encoder_control);
|
|
|
|
#if CONFIG_TUNER & S1A0903X01
|
|
/* Store the (unpitched) MAS PLL frequency. Used for avoiding FM
|
|
interference with the Samsung tuner. */
|
|
if (rec_frequency_index)
|
|
mas_store_pllfreq(24576000);
|
|
else
|
|
mas_store_pllfreq(22579000);
|
|
#endif
|
|
|
|
shadow_soft_mute = options->rec_editable?4:0;
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_SOFT_MUTE, &shadow_soft_mute,1);
|
|
|
|
DEBUGF("mas_writemem(MAS_BANK_D0, SOFT_MUTE, %x)\n", shadow_soft_mute);
|
|
|
|
shadow_io_control_main = ((1 << 10) | /* Monitoring ON */
|
|
((options->rec_source < 2)?1:2) << 8) | /* Input select */
|
|
(1 << 5) | /* SDO strobe invert */
|
|
((is_mpeg1?0:1) << 3) |
|
|
(1 << 2) | /* Inverted SIBC clock signal */
|
|
1; /* Validate */
|
|
mas_writemem(MAS_BANK_D0, MAS_D0_IO_CONTROL_MAIN, &shadow_io_control_main,1);
|
|
|
|
DEBUGF("mas_writemem(MAS_BANK_D0, IO_CONTROL_MAIN, %x)\n", shadow_io_control_main);
|
|
|
|
if(options->rec_source == AUDIO_SRC_MIC)
|
|
{
|
|
/* Copy left channel to right (mono mode) */
|
|
mas_codec_writereg(8, 0x8000);
|
|
}
|
|
else
|
|
{
|
|
/* Stereo input mode */
|
|
mas_codec_writereg(8, 0);
|
|
}
|
|
|
|
prerecording_max_seconds = options->rec_prerecord_time;
|
|
if(prerecording_max_seconds)
|
|
{
|
|
prerecording = true;
|
|
start_prerecording();
|
|
}
|
|
else
|
|
{
|
|
prerecording = false;
|
|
is_prerecording = false;
|
|
is_recording = false;
|
|
}
|
|
}
|
|
|
|
/* If use_mic is true, the left gain is used */
|
|
void audio_set_recording_gain(int left, int right, int type)
|
|
{
|
|
/* Enable both left and right A/D */
|
|
shadow_codec_reg0 = (left << 12) |
|
|
(right << 8) |
|
|
(left << 4) |
|
|
(type==AUDIO_GAIN_MIC?0x0008:0) | /* Connect left A/D to mic */
|
|
0x0007;
|
|
mas_codec_writereg(0x0, shadow_codec_reg0);
|
|
}
|
|
|
|
/* try to make some kind of beep, also in recording mode */
|
|
void audio_beep(int duration)
|
|
{
|
|
long starttick = current_tick;
|
|
do
|
|
{ /* toggle bit 0 of codec register 0, toggling the DAC off & on.
|
|
* While this is still audible even without an external signal,
|
|
* it doesn't affect the (pre-)recording. */
|
|
mas_codec_writereg(0, shadow_codec_reg0 ^ 1);
|
|
mas_codec_writereg(0, shadow_codec_reg0);
|
|
yield();
|
|
}
|
|
while (current_tick - starttick < duration);
|
|
}
|
|
|
|
void audio_new_file(const char *filename)
|
|
{
|
|
mpeg_errno = 0;
|
|
|
|
strlcpy(recording_filename, filename, MAX_PATH);
|
|
|
|
queue_post(&mpeg_queue, MPEG_NEW_FILE, 0);
|
|
}
|
|
|
|
unsigned long audio_recorded_time(void)
|
|
{
|
|
if(is_prerecording)
|
|
return prerecord_count * HZ;
|
|
|
|
if(is_recording)
|
|
{
|
|
if(paused)
|
|
return pause_start_time - record_start_time;
|
|
else
|
|
return current_tick - record_start_time;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned long audio_num_recorded_bytes(void)
|
|
{
|
|
int num_bytes;
|
|
int index;
|
|
|
|
if(is_recording)
|
|
{
|
|
if(is_prerecording)
|
|
{
|
|
index = prerecord_index - prerecord_count;
|
|
if(index < 0)
|
|
index += prerecording_max_seconds;
|
|
|
|
num_bytes = audiobuf_write - prerecord_buffer[index].mempos;
|
|
if(num_bytes < 0)
|
|
num_bytes += audiobuflen;
|
|
|
|
return num_bytes;
|
|
}
|
|
else
|
|
return num_rec_bytes;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
#else /* SIMULATOR */
|
|
|
|
/* dummies coming up */
|
|
|
|
void audio_init_playback(void)
|
|
{
|
|
/* a dummy */
|
|
}
|
|
unsigned long audio_recorded_time(void)
|
|
{
|
|
/* a dummy */
|
|
return 0;
|
|
}
|
|
void audio_beep(int duration)
|
|
{
|
|
/* a dummy */
|
|
(void)duration;
|
|
}
|
|
void audio_pause_recording(void)
|
|
{
|
|
/* a dummy */
|
|
}
|
|
void audio_resume_recording(void)
|
|
{
|
|
/* a dummy */
|
|
}
|
|
unsigned long audio_num_recorded_bytes(void)
|
|
{
|
|
/* a dummy */
|
|
return 0;
|
|
}
|
|
void audio_record(const char *filename)
|
|
{
|
|
/* a dummy */
|
|
(void)filename;
|
|
}
|
|
void audio_new_file(const char *filename)
|
|
{
|
|
/* a dummy */
|
|
(void)filename;
|
|
}
|
|
|
|
void audio_set_recording_gain(int left, int right, int type)
|
|
{
|
|
/* a dummy */
|
|
(void)left;
|
|
(void)right;
|
|
(void)type;
|
|
}
|
|
void audio_init_recording(unsigned int buffer_offset)
|
|
{
|
|
/* a dummy */
|
|
(void)buffer_offset;
|
|
}
|
|
void audio_set_recording_options(struct audio_recording_options *options)
|
|
{
|
|
/* a dummy */
|
|
(void)options;
|
|
}
|
|
#endif /* SIMULATOR */
|
|
#endif /* CONFIG_CODEC == MAS3587F */
|
|
|
|
void audio_play(long offset)
|
|
{
|
|
#ifdef SIMULATOR
|
|
char* trackname;
|
|
int steps=0;
|
|
|
|
is_playing = true;
|
|
|
|
do {
|
|
trackname = playlist_peek( steps );
|
|
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);
|
|
taginfo.offset = offset;
|
|
set_elapsed(&taginfo);
|
|
is_playing = true;
|
|
playing = true;
|
|
break;
|
|
} while(1);
|
|
#else /* !SIMULATOR */
|
|
is_playing = true;
|
|
|
|
queue_post(&mpeg_queue, MPEG_PLAY, offset);
|
|
#endif /* !SIMULATOR */
|
|
|
|
mpeg_errno = 0;
|
|
}
|
|
|
|
void audio_stop(void)
|
|
{
|
|
#ifndef SIMULATOR
|
|
if (playing)
|
|
{
|
|
struct trackdata *track = get_trackdata(0);
|
|
prev_track_elapsed = track->id3.elapsed;
|
|
}
|
|
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_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* file;
|
|
int steps = 1;
|
|
int index;
|
|
|
|
do {
|
|
file = playlist_peek(steps);
|
|
if(!file)
|
|
break;
|
|
if(mp3info(&taginfo, file)) {
|
|
if(++steps > playlist_amount())
|
|
break;
|
|
continue;
|
|
}
|
|
index = playlist_next(steps);
|
|
taginfo.index = index;
|
|
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* file;
|
|
int steps = -1;
|
|
int index;
|
|
|
|
do {
|
|
file = playlist_peek(steps);
|
|
if(!file)
|
|
break;
|
|
if(mp3info(&taginfo, file)) {
|
|
steps--;
|
|
continue;
|
|
}
|
|
index = playlist_next(steps);
|
|
taginfo.index = index;
|
|
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 (CONFIG_CODEC == MAS3587F) && !defined(SIMULATOR)
|
|
if(is_recording && !is_prerecording)
|
|
ret |= AUDIO_STATUS_RECORD;
|
|
|
|
if(is_prerecording)
|
|
ret |= AUDIO_STATUS_PRERECORD;
|
|
#endif /* CONFIG_CODEC == MAS3587F */
|
|
|
|
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;
|
|
/* cuesheet support */
|
|
if (global_settings.cuesheet)
|
|
curr_cuesheet = (struct cuesheet*)buffer_alloc(sizeof(struct cuesheet));
|
|
|
|
#ifndef SIMULATOR
|
|
audiobuflen = audiobufend - audiobuf;
|
|
queue_init(&mpeg_queue, true);
|
|
#endif /* !SIMULATOR */
|
|
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));
|
|
|
|
#if (CONFIG_CODEC == MAS3587F) && !defined(SIMULATOR)
|
|
if (HW_MASK & PR_ACTIVE_HIGH)
|
|
and_b(~0x08, &PADRH);
|
|
else
|
|
or_b(0x08, &PADRH);
|
|
#endif /* CONFIG_CODEC == MAS3587F */
|
|
|
|
#ifdef DEBUG
|
|
#ifndef SIMULATOR
|
|
dbg_timer_start();
|
|
dbg_cnt2us(0);
|
|
#endif /* !SIMULATOR */
|
|
#endif /* DEBUG */
|
|
}
|
|
|
|
#endif /* CONFIG_CODEC != SWCODEC */
|