summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorDave Chapman <dave@dchapman.com>2006-05-20 09:57:55 +0000
committerDave Chapman <dave@dchapman.com>2006-05-20 09:57:55 +0000
commitfa5caa0b5b2bce6ec56a99d716584405854ede76 (patch)
treee4130e1d2092a6d9161afed571f104c88f2ce3b6 /apps
parent965e824923e63b6fd53113ed4c4c2c04692b2fe4 (diff)
downloadrockbox-fa5caa0b5b2bce6ec56a99d716584405854ede76.tar.gz
rockbox-fa5caa0b5b2bce6ec56a99d716584405854ede76.tar.bz2
rockbox-fa5caa0b5b2bce6ec56a99d716584405854ede76.zip
Patch from bug report #5200 by Mark Arigo - attempt to fix gapless playback after seeking in an MP3 file. It works for me, but needs more testing with a wider range of files before we can close the bug report - please post feedback on the tracker.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@9962 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps')
-rw-r--r--apps/codecs/mpa.c163
1 files changed, 75 insertions, 88 deletions
diff --git a/apps/codecs/mpa.c b/apps/codecs/mpa.c
index 4c3784a5f7..81604de08a 100644
--- a/apps/codecs/mpa.c
+++ b/apps/codecs/mpa.c
@@ -40,6 +40,7 @@ mad_fixed_t mad_frame_overlap[2][32][18] IBSS_ATTR;
unsigned char mad_main_data[MAD_BUFFER_MDLEN] IBSS_ATTR;
/* TODO: what latency does layer 1 have? */
int mpeg_latency[3] = { 0, 481, 529 };
+int mpeg_framesize[3] = {384, 1152, 1152};
#ifdef USE_IRAM
extern char iramcopy[];
@@ -50,34 +51,13 @@ extern char iend[];
#endif
struct codec_api *ci;
-int64_t samplecount;
-int64_t samplesdone;
-int stop_skip, start_skip;
-int current_stereo_mode = -1;
-unsigned long current_frequency = 0;
-
-void recalc_samplecount(void)
-{
- /* NOTE: currently this doesn't work, the below calculated samples_count
- seems to be right, but sometimes we just don't have all the data we
- need... */
- if (ci->id3->frame_count) {
- /* TODO: 1152 is the frame size in samples for MPEG1 layer 2 and layer 3,
- it's probably not correct at all for MPEG2 and layer 1 */
- samplecount = ((int64_t)ci->id3->frame_count) * 1152;
- } else {
- samplecount = ((int64_t)ci->id3->length) * current_frequency / 1000;
- }
-
- samplecount -= start_skip + stop_skip;
-}
void init_mad(void)
{
ci->memset(&stream, 0, sizeof(struct mad_stream));
ci->memset(&frame, 0, sizeof(struct mad_frame));
ci->memset(&synth, 0, sizeof(struct mad_synth));
-
+
mad_stream_init(&stream);
mad_frame_init(&frame);
mad_synth_init(&synth);
@@ -94,14 +74,14 @@ enum codec_status codec_start(struct codec_api *api)
int status;
size_t size;
int file_end;
- int frame_skip; /* samples to skip current frame */
int samples_to_skip; /* samples to skip in total for this file (at start) */
char *inputbuffer;
- /* If we know the position isn't exact (i.e., we have seeked to a
- * position that isn't the start of the file), we can't reliably do
- * end-of-file trimming for gapless playback.
- */
- bool exact_position = true;
+ int64_t samplesdone;
+ int stop_skip, start_skip;
+ int current_stereo_mode = -1;
+ unsigned long current_frequency = 0;
+ int framelength;
+ int padding = MAD_BUFFER_GUARD; /* to help mad decode the last frame */
ci = api;
@@ -120,20 +100,17 @@ enum codec_status codec_start(struct codec_api *api)
ci->configure(DSP_SET_CLIP_MIN, (int *)-MAD_F_ONE);
ci->configure(DSP_SET_CLIP_MAX, (int *)(MAD_F_ONE - 1));
ci->configure(CODEC_SET_FILEBUF_CHUNKSIZE, (int *)(1024*16));
-
- /** This label might need to be moved above all the init code, but I don't
- * think reiniting the codec is necessary for MPEG. It might even be unwanted
- * for gapless playback.
- * Reinitializing seems to be necessary to avoid playback quircks when seeking. */
- next_track:
+
+next_track:
status = CODEC_OK;
-
+
+ /* Reinitializing seems to be necessary to avoid playback quircks when seeking. */
init_mad();
file_end = 0;
while (!*ci->taginfo_ready && !ci->stop_codec)
ci->sleep(1);
-
+
ci->configure(DSP_SET_FREQUENCY, (int *)ci->id3->frequency);
current_frequency = ci->id3->frequency;
codec_set_replaygain(ci->id3);
@@ -151,35 +128,52 @@ enum codec_status codec_start(struct codec_api *api)
start_skip = mpeg_latency[ci->id3->layer];
}
+ /* Libmad will not decode the last frame without 8 bytes of extra padding
+ in the buffer. So, we can trick libmad into not decoding the last frame
+ if we are to skip it entirely and then cut the appropriate samples from
+ final frame that we did decode. Note, if all tags (ID3, APE) are not
+ properly stripped from the end of the file, this trick will not work. */
+ if (stop_skip >= mpeg_framesize[ci->id3->layer]) {
+ padding = 0;
+ stop_skip -= mpeg_framesize[ci->id3->layer];
+ } else {
+ padding = MAD_BUFFER_GUARD;
+ }
+
samplesdone = ((int64_t)ci->id3->elapsed) * current_frequency / 1000;
- exact_position = samplesdone == 0;
- samples_to_skip = start_skip;
- recalc_samplecount();
-
+
+ /* Don't skip any samples unless we start at the beginning. */
+ if (samplesdone > 0)
+ samples_to_skip = 0;
+ else
+ samples_to_skip = start_skip;
+
+ framelength = 0;
+
/* This is the decoding loop. */
while (1) {
- int framelength;
-
ci->yield();
if (ci->stop_codec || ci->new_track)
break;
if (ci->seek_time) {
int newpos;
-
- samplesdone = ((int64_t) (ci->seek_time - 1))
- * current_frequency / 1000;
- exact_position = samplesdone == 0;
- if (ci->seek_time-1 == 0)
+ samplesdone = ((int64_t)(ci->seek_time-1))*current_frequency/1000;
+
+ if (ci->seek_time-1 == 0) {
newpos = ci->id3->first_frame_offset;
- else
+ samples_to_skip = start_skip;
+ } else {
newpos = ci->mp3_get_filepos(ci->seek_time-1);
+ samples_to_skip = 0;
+ }
if (!ci->seek_buffer(newpos))
break;
ci->seek_complete();
init_mad();
+ framelength = 0;
}
/* Lock buffers */
@@ -187,18 +181,17 @@ enum codec_status codec_start(struct codec_api *api)
inputbuffer = ci->request_buffer(&size, INPUT_CHUNK_SIZE);
if (size == 0 || inputbuffer == NULL)
break;
- /* size + MAD_BUFFER_GUARD to help mad decode the last frame */
mad_stream_buffer(&stream, (unsigned char *)inputbuffer,
- size + MAD_BUFFER_GUARD);
+ size + padding);
}
-
+
if (mad_frame_decode(&frame, &stream)) {
if (stream.error == MAD_FLAG_INCOMPLETE
|| stream.error == MAD_ERROR_BUFLEN) {
/* This makes the codec support partially corrupted files */
if (file_end == 30)
break;
-
+
/* Fill the buffer */
if (stream.next_frame)
ci->advance_buffer_loc((void *)stream.next_frame);
@@ -216,39 +209,28 @@ enum codec_status codec_start(struct codec_api *api)
}
break;
}
-
+
file_end = 0;
- mad_synth_frame(&synth, &frame);
-
- /* We need to skip samples_to_skip samples from the start of every file
- to properly support LAME style gapless MP3 files. samples_to_skip
- might be larger than one frame. */
- if (samples_to_skip < synth.pcm.length) {
- /* skip just part of the frame */
- frame_skip = samples_to_skip;
+ /* Do the pcmbuf insert here. Note, this is the PREVIOUS frame's pcm
+ data (not the one just decoded above). When we exit the decoding
+ loop we will need to process the final frame that was decoded. */
+ if (framelength > 0) {
+ /* In case of a mono file, the second array will be ignored. */
+ ci->pcmbuf_insert_split(&synth.pcm.samples[0][samples_to_skip],
+ &synth.pcm.samples[1][samples_to_skip],
+ framelength * 4);
+
+ /* Only skip samples for the first frame added. */
samples_to_skip = 0;
- } else {
- /* we need to skip an entire frame */
- frame_skip = synth.pcm.length;
- samples_to_skip -= synth.pcm.length;
- }
-
- framelength = synth.pcm.length - frame_skip;
-
- if (exact_position && (stop_skip > 0)) {
- int64_t max = samplecount - samplesdone;
-
- if (max < 0) max = 0;
- if (max < framelength) framelength = (int)max;
- if (framelength == 0 && frame_skip == 0) break;
}
-
+
+ mad_synth_frame(&synth, &frame);
+
/* Check if sample rate and stereo settings changed in this frame. */
if (frame.header.samplerate != current_frequency) {
current_frequency = frame.header.samplerate;
ci->configure(DSP_SWITCH_FREQUENCY, (int *)current_frequency);
- recalc_samplecount();
}
if (MAD_NCHANNELS(&frame.header) == 2) {
if (current_stereo_mode != STEREO_NONINTERLEAVED) {
@@ -261,27 +243,32 @@ enum codec_status codec_start(struct codec_api *api)
current_stereo_mode = STEREO_MONO;
}
}
-
- /* Check if we can just skip the entire frame. */
- if (frame_skip < synth.pcm.length) {
- /* In case of a mono file, the second array will be ignored. */
- ci->pcmbuf_insert_split(&synth.pcm.samples[0][frame_skip],
- &synth.pcm.samples[1][frame_skip],
- framelength * 4);
- }
-
+
if (stream.next_frame)
ci->advance_buffer_loc((void *)stream.next_frame);
else
ci->advance_buffer(size);
+ framelength = synth.pcm.length - samples_to_skip;
+ if (framelength < 0) {
+ framelength = 0;
+ samples_to_skip -= synth.pcm.length;
+ }
+
samplesdone += framelength;
ci->set_elapsed(samplesdone / (current_frequency / 1000));
}
+
+ /* Finish the remaining decoded frame.
+ Cut the required samples from the end. */
+ if (framelength > stop_skip)
+ ci->pcmbuf_insert_split(synth.pcm.samples[0], synth.pcm.samples[1],
+ (framelength - stop_skip) * 4);
+
stream.error = 0;
-
+
if (ci->request_next_track())
goto next_track;
-
+
return status;
}