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
This commit is contained in:
parent
965e824923
commit
fa5caa0b5b
1 changed files with 75 additions and 88 deletions
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue