From a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Sat, 29 Dec 2007 19:46:35 +0000 Subject: mpegplayer: Make playback engine fully seekable and frame-accurate and split into logical parts. Be sure to have all current features work. Actual UI for seeking will be added soon. Recommended GOP size is about 15-30 frames depending on target or seeking can be slow with really long GOPs (nature of MPEG video). More refined encoding recommendations for a particular player should be posted soon. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15977 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/mpegplayer/audio_thread.c | 743 +++++++++++++++++++++++++++++++++ 1 file changed, 743 insertions(+) create mode 100644 apps/plugins/mpegplayer/audio_thread.c (limited to 'apps/plugins/mpegplayer/audio_thread.c') diff --git a/apps/plugins/mpegplayer/audio_thread.c b/apps/plugins/mpegplayer/audio_thread.c new file mode 100644 index 0000000000..ee6ff05d2d --- /dev/null +++ b/apps/plugins/mpegplayer/audio_thread.c @@ -0,0 +1,743 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * mpegplayer audio thread implementation + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "plugin.h" +#include "mpegplayer.h" +#include "../../codecs/libmad/bit.h" +#include "../../codecs/libmad/mad.h" + +/** Audio stream and thread **/ +struct pts_queue_slot; +struct audio_thread_data +{ + struct queue_event ev; /* Our event queue to receive commands */ + int state; /* Thread state */ + int status; /* Media status (STREAM_PLAYING, etc.) */ + int mad_errors; /* A count of the errors in each frame */ +}; + +/* The audio stack is stolen from the core codec thread (but not in uisim) */ +/* Used for stealing codec thread's stack */ +static uint32_t* audio_stack; +static size_t audio_stack_size; /* Keep gcc happy and init */ +#define AUDIO_STACKSIZE (9*1024) +#ifndef SIMULATOR +static uint32_t codec_stack_copy[AUDIO_STACKSIZE / sizeof(uint32_t)]; +#endif +static struct event_queue audio_str_queue NOCACHEBSS_ATTR; +static struct queue_sender_list audio_str_queue_send NOCACHEBSS_ATTR; +struct stream audio_str IBSS_ATTR; + +/* libmad related definitions */ +static struct mad_stream stream IBSS_ATTR; +static struct mad_frame frame IBSS_ATTR; +static struct mad_synth synth IBSS_ATTR; + +/* 2567 bytes */ +static unsigned char mad_main_data[MAD_BUFFER_MDLEN]; + +/* There isn't enough room for this in IRAM on PortalPlayer, but there + is for Coldfire. */ + +/* 4608 bytes */ +#ifdef CPU_COLDFIRE +static mad_fixed_t mad_frame_overlap[2][32][18] IBSS_ATTR; +#else +static mad_fixed_t mad_frame_overlap[2][32][18]; +#endif + +/** A queue for saving needed information about MPEG audio packets **/ +#define AUDIODESC_QUEUE_LEN (1 << 5) /* 32 should be way more than sufficient - + if not, the case is handled */ +#define AUDIODESC_QUEUE_MASK (AUDIODESC_QUEUE_LEN-1) +struct audio_frame_desc +{ + uint32_t time; /* Time stamp for packet in audio ticks */ + ssize_t size; /* Number of unprocessed bytes left in packet */ +}; + + /* This starts out wr == rd but will never be emptied to zero during + streaming again in order to support initializing the first packet's + timestamp without a special case */ +struct +{ + /* Compressed audio data */ + uint8_t *start; /* Start of encoded audio buffer */ + uint8_t *ptr; /* Pointer to next encoded audio data */ + ssize_t used; /* Number of bytes in MPEG audio buffer */ + /* Compressed audio data descriptors */ + unsigned read, write; + struct audio_frame_desc *curr; /* Current slot */ + struct audio_frame_desc descs[AUDIODESC_QUEUE_LEN]; +} audio_queue; + +static inline int audiodesc_queue_count(void) +{ + return audio_queue.write - audio_queue.read; +} + +static inline bool audiodesc_queue_full(void) +{ + return audio_queue.used >= MPA_MAX_FRAME_SIZE + MAD_BUFFER_GUARD || + audiodesc_queue_count() >= AUDIODESC_QUEUE_LEN; +} + +/* Increments the queue tail postion - should be used to preincrement */ +static inline void audiodesc_queue_add_tail(void) +{ + if (audiodesc_queue_full()) + { + DEBUGF("audiodesc_queue_add_tail: audiodesc queue full!\n"); + return; + } + + audio_queue.write++; +} + +/* Increments the queue tail position - leaves one slot as current */ +static inline bool audiodesc_queue_remove_head(void) +{ + if (audio_queue.write == audio_queue.read) + return false; + + audio_queue.read++; + return true; +} + +/* Returns the "tail" at the index just behind the write index */ +static inline struct audio_frame_desc * audiodesc_queue_tail(void) +{ + return &audio_queue.descs[(audio_queue.write - 1) & AUDIODESC_QUEUE_MASK]; +} + +/* Returns a pointer to the current head */ +static inline struct audio_frame_desc * audiodesc_queue_head(void) +{ + return &audio_queue.descs[audio_queue.read & AUDIODESC_QUEUE_MASK]; +} + +/* Resets the pts queue - call when starting and seeking */ +static void audio_queue_reset(void) +{ + audio_queue.ptr = audio_queue.start; + audio_queue.used = 0; + audio_queue.read = 0; + audio_queue.write = 0; + audio_queue.curr = audiodesc_queue_head(); + audio_queue.curr->time = 0; + audio_queue.curr->size = 0; +} + +static void audio_queue_advance_pos(ssize_t len) +{ + audio_queue.ptr += len; + audio_queue.used -= len; + audio_queue.curr->size -= len; +} + +static int audio_buffer(struct stream *str, enum stream_parse_mode type) +{ + int ret = STREAM_OK; + + /* Carry any overshoot to the next size since we're technically + -size bytes into it already. If size is negative an audio + frame was split across packets. Old has to be saved before + moving the head. */ + if (audio_queue.curr->size <= 0 && audiodesc_queue_remove_head()) + { + struct audio_frame_desc *old = audio_queue.curr; + audio_queue.curr = audiodesc_queue_head(); + audio_queue.curr->size += old->size; + old->size = 0; + } + + /* Add packets to compressed audio buffer until it's full or the + * timestamp queue is full - whichever happens first */ + while (!audiodesc_queue_full()) + { + ret = parser_get_next_data(str, type); + struct audio_frame_desc *curr; + ssize_t len; + + if (ret != STREAM_OK) + break; + + /* Get data from next audio packet */ + len = str->curr_packet_end - str->curr_packet; + + if (str->pkt_flags & PKT_HAS_TS) + { + audiodesc_queue_add_tail(); + curr = audiodesc_queue_tail(); + curr->time = TS_TO_TICKS(str->pts); + /* pts->size should have been zeroed when slot was + freed */ + } + else + { + /* Add to the one just behind the tail - this may be + * the head or the previouly added tail - whether or + * not we'll ever reach this is quite in question + * since audio always seems to have every packet + * timestamped */ + curr = audiodesc_queue_tail(); + } + + curr->size += len; + + /* Slide any remainder over to beginning */ + if (audio_queue.ptr > audio_queue.start && audio_queue.used > 0) + { + rb->memmove(audio_queue.start, audio_queue.ptr, + audio_queue.used); + } + + /* Splice this packet onto any remainder */ + rb->memcpy(audio_queue.start + audio_queue.used, + str->curr_packet, len); + + audio_queue.used += len; + audio_queue.ptr = audio_queue.start; + + rb->yield(); + } + + return ret; +} + +/* Initialise libmad */ +static void init_mad(void) +{ + mad_stream_init(&stream); + mad_frame_init(&frame); + mad_synth_init(&synth); + + /* We do this so libmad doesn't try to call codec_calloc() */ + rb->memset(mad_frame_overlap, 0, sizeof(mad_frame_overlap)); + frame.overlap = (void *)mad_frame_overlap; + + rb->memset(mad_main_data, 0, sizeof(mad_main_data)); + stream.main_data = &mad_main_data; +} + +/* Sync audio stream to a particular frame - see main decoder loop for + * detailed remarks */ +static int audio_sync(struct audio_thread_data *td, + struct str_sync_data *sd) +{ + int retval = STREAM_MATCH; + uint32_t sdtime = TS_TO_TICKS(clip_time(&audio_str, sd->time)); + uint32_t time; + uint32_t duration = 0; + struct stream *str; + struct stream tmp_str; + struct mad_header header; + struct mad_stream stream; + + if (td->ev.id == STREAM_SYNC) + { + /* Actually syncing for playback - use real stream */ + time = 0; + str = &audio_str; + } + else + { + /* Probing - use temp stream */ + time = INVALID_TIMESTAMP; + str = &tmp_str; + str->id = audio_str.id; + } + + str->hdr.pos = sd->sk.pos; + str->hdr.limit = sd->sk.pos + sd->sk.len; + + mad_stream_init(&stream); + mad_header_init(&header); + + while (1) + { + if (audio_buffer(str, STREAM_PM_RANDOM_ACCESS) == STREAM_DATA_END) + { + DEBUGF("audio_sync:STR_DATA_END\n aqu:%ld swl:%ld swr:%ld\n", + audio_queue.used, str->hdr.win_left, str->hdr.win_right); + if (audio_queue.used <= MAD_BUFFER_GUARD) + goto sync_data_end; + } + + stream.error = 0; + mad_stream_buffer(&stream, audio_queue.ptr, audio_queue.used); + + if (stream.sync && mad_stream_sync(&stream) < 0) + { + DEBUGF(" audio: mad_stream_sync failed\n"); + audio_queue_advance_pos(MAX(audio_queue.curr->size - 1, 1)); + continue; + } + + stream.sync = 0; + + if (mad_header_decode(&header, &stream) < 0) + { + DEBUGF(" audio: mad_header_decode failed:%s\n", + mad_stream_errorstr(&stream)); + audio_queue_advance_pos(1); + continue; + } + + duration = 32*MAD_NSBSAMPLES(&header); + time = audio_queue.curr->time; + + DEBUGF(" audio: ft:%u t:%u fe:%u nsamp:%u sampr:%u\n", + (unsigned)TICKS_TO_TS(time), (unsigned)sd->time, + (unsigned)TICKS_TO_TS(time + duration), + (unsigned)duration, header.samplerate); + + audio_queue_advance_pos(stream.this_frame - audio_queue.ptr); + + if (time <= sdtime && sdtime < time + duration) + { + DEBUGF(" audio: ft<=t sdtime) + { + DEBUGF(" audio: ft>t\n"); + break; + } + + audio_queue_advance_pos(stream.next_frame - audio_queue.ptr); + audio_queue.curr->time += duration; + + rb->yield(); + } + +sync_data_end: + if (td->ev.id == STREAM_FIND_END_TIME) + { + if (time != INVALID_TIMESTAMP) + { + time = TICKS_TO_TS(time); + duration = TICKS_TO_TS(duration); + sd->time = time + duration; + retval = STREAM_PERFECT_MATCH; + } + else + { + retval = STREAM_NOT_FOUND; + } + } + + DEBUGF(" audio header: 0x%02X%02X%02X%02X\n", + (unsigned)audio_queue.ptr[0], (unsigned)audio_queue.ptr[1], + (unsigned)audio_queue.ptr[2], (unsigned)audio_queue.ptr[3]); + + return retval; + (void)td; +} + +static void audio_thread_msg(struct audio_thread_data *td) +{ + while (1) + { + intptr_t reply = 0; + + switch (td->ev.id) + { + case STREAM_PLAY: + td->status = STREAM_PLAYING; + + switch (td->state) + { + case TSTATE_INIT: + td->state = TSTATE_DECODE; + case TSTATE_DECODE: + case TSTATE_RENDER_WAIT: + case TSTATE_RENDER_WAIT_END: + break; + + case TSTATE_EOS: + /* At end of stream - no playback possible so fire the + * completion event */ + stream_generate_event(&audio_str, STREAM_EV_COMPLETE, 0); + break; + } + + break; + + case STREAM_PAUSE: + td->status = STREAM_PAUSED; + reply = td->state != TSTATE_EOS; + break; + + case STREAM_STOP: + if (td->state == TSTATE_DATA) + stream_clear_notify(&audio_str, DISK_BUF_DATA_NOTIFY); + + td->status = STREAM_STOPPED; + td->state = TSTATE_EOS; + + reply = true; + break; + + case STREAM_RESET: + if (td->state == TSTATE_DATA) + stream_clear_notify(&audio_str, DISK_BUF_DATA_NOTIFY); + + td->status = STREAM_STOPPED; + td->state = TSTATE_INIT; + + init_mad(); + td->mad_errors = 0; + + audio_queue_reset(); + + reply = true; + break; + + case STREAM_NEEDS_SYNC: + reply = true; /* Audio always needs to */ + break; + + case STREAM_SYNC: + case STREAM_FIND_END_TIME: + if (td->state != TSTATE_INIT) + break; + + reply = audio_sync(td, (struct str_sync_data *)td->ev.data); + break; + + case DISK_BUF_DATA_NOTIFY: + /* Our bun is done */ + if (td->state != TSTATE_DATA) + break; + + td->state = TSTATE_DECODE; + str_data_notify_received(&audio_str); + break; + + case STREAM_QUIT: + /* Time to go - make thread exit */ + td->state = TSTATE_EOS; + return; + } + + str_reply_msg(&audio_str, reply); + + if (td->status == STREAM_PLAYING) + { + switch (td->state) + { + case TSTATE_DECODE: + case TSTATE_RENDER_WAIT: + case TSTATE_RENDER_WAIT_END: + /* These return when in playing state */ + return; + } + } + + str_get_msg(&audio_str, &td->ev); + } +} + +static void audio_thread(void) +{ + struct audio_thread_data td; + + td.status = STREAM_STOPPED; + td.state = TSTATE_EOS; + + /* We need this here to init the EMAC for Coldfire targets */ + init_mad(); + + goto message_wait; + + /* This is the decoding loop. */ + while (1) + { + td.state = TSTATE_DECODE; + + /* Check for any pending messages and process them */ + if (str_have_msg(&audio_str)) + { + message_wait: + /* Wait for a message to be queued */ + str_get_msg(&audio_str, &td.ev); + + message_process: + /* Process a message already dequeued */ + audio_thread_msg(&td); + + switch (td.state) + { + /* These states are the only ones that should return */ + case TSTATE_DECODE: goto audio_decode; + case TSTATE_RENDER_WAIT: goto render_wait; + case TSTATE_RENDER_WAIT_END: goto render_wait_end; + /* Anything else is interpreted as an exit */ + default: return; + } + } + + audio_decode: + + /** Buffering **/ + switch (audio_buffer(&audio_str, STREAM_PM_STREAMING)) + { + case STREAM_DATA_NOT_READY: + { + td.state = TSTATE_DATA; + goto message_wait; + } /* STREAM_DATA_NOT_READY: */ + + case STREAM_DATA_END: + { + if (audio_queue.used > MAD_BUFFER_GUARD) + break; + + /* Used up remainder of compressed audio buffer. + * Force any residue to play if audio ended before + * reaching the threshold */ + td.state = TSTATE_RENDER_WAIT_END; + audio_queue_reset(); + + render_wait_end: + pcm_output_drain(); + + while (pcm_output_used() > (ssize_t)PCMOUT_LOW_WM) + { + str_get_msg_w_tmo(&audio_str, &td.ev, 1); + if (td.ev.id != SYS_TIMEOUT) + goto message_process; + } + + td.state = TSTATE_EOS; + if (td.status == STREAM_PLAYING) + stream_generate_event(&audio_str, STREAM_EV_COMPLETE, 0); + + rb->yield(); + goto message_wait; + } /* STREAM_DATA_END: */ + } + + /** Decoding **/ + mad_stream_buffer(&stream, audio_queue.ptr, audio_queue.used); + + int mad_stat = mad_frame_decode(&frame, &stream); + + ssize_t len = stream.next_frame - audio_queue.ptr; + + if (mad_stat != 0) + { + DEBUGF("audio: Stream error: %s\n", + mad_stream_errorstr(&stream)); + + /* If something's goofed - try to perform resync by moving + * at least one byte at a time */ + audio_queue_advance_pos(MAX(len, 1)); + + if (stream.error == MAD_FLAG_INCOMPLETE + || stream.error == MAD_ERROR_BUFLEN) + { + /* This makes the codec support partially corrupted files */ + if (++td.mad_errors <= MPA_MAX_FRAME_SIZE) + { + stream.error = 0; + rb->yield(); + continue; + } + DEBUGF("audio: Too many errors\n"); + } + else if (MAD_RECOVERABLE(stream.error)) + { + /* libmad says it can recover - just keep on decoding */ + rb->yield(); + continue; + } + else + { + /* Some other unrecoverable error */ + DEBUGF("audio: Unrecoverable error\n"); + } + + /* This is too hard - bail out */ + td.state = TSTATE_EOS; + + if (td.status == STREAM_PLAYING) + stream_generate_event(&audio_str, STREAM_EV_COMPLETE, 0); + + td.status = STREAM_ERROR; + goto message_wait; + } + + /* Adjust sizes by the frame size */ + audio_queue_advance_pos(len); + td.mad_errors = 0; /* Clear errors */ + + /* Generate the pcm samples */ + mad_synth_frame(&synth, &frame); + + /** Output **/ + + /* TODO: Output through core dsp. We'll still use our own PCM buffer + since the core pcm buffer has no timestamping or clock facilities */ + + /* Add a frame of audio to the pcm buffer. Maximum is 1152 samples. */ + render_wait: + if (synth.pcm.length > 0) + { + struct pcm_frame_header *pcm_insert = pcm_output_get_buffer(); + int16_t *audio_data = (int16_t *)pcm_insert->data; + unsigned length = synth.pcm.length; + ssize_t size = sizeof(*pcm_insert) + length*4; + + td.state = TSTATE_RENDER_WAIT; + + /* Wait for required amount of free buffer space */ + while (pcm_output_free() < size) + { + /* Wait one frame */ + int timeout = synth.pcm.length*HZ / synth.pcm.samplerate; + str_get_msg_w_tmo(&audio_str, &td.ev, MAX(timeout, 1)); + if (td.ev.id != SYS_TIMEOUT) + goto message_process; + } + + pcm_insert->time = audio_queue.curr->time; + pcm_insert->size = size; + + /* As long as we're on this timestamp, the time is just incremented + by the number of samples */ + audio_queue.curr->time += length; + + if (MAD_NCHANNELS(&frame.header) == 2) + { + int32_t *left = &synth.pcm.samples[0][0]; + int32_t *right = &synth.pcm.samples[1][0]; + + do + { + /* libmad outputs s3.28 */ + *audio_data++ = clip_sample(*left++ >> 13); + *audio_data++ = clip_sample(*right++ >> 13); + } + while (--length > 0); + } + else /* mono */ + { + int32_t *mono = &synth.pcm.samples[0][0]; + + do + { + int32_t s = clip_sample(*mono++ >> 13); + *audio_data++ = s; + *audio_data++ = s; + } + while (--length > 0); + } + /**/ + + /* Make this data available to DMA */ + pcm_output_add_data(); + } + + rb->yield(); + } /* end decoding loop */ +} + +/* Initializes the audio thread resources and starts the thread */ +bool audio_thread_init(void) +{ + int i; +#ifdef SIMULATOR + /* The simulator thread implementation doesn't have stack buffers, and + these parameters are ignored. */ + (void)i; /* Keep gcc happy */ + audio_stack = NULL; + audio_stack_size = 0; +#else + /* Borrow the codec thread's stack (in IRAM on most targets) */ + audio_stack = NULL; + for (i = 0; i < MAXTHREADS; i++) + { + if (rb->strcmp(rb->threads[i].name, "codec") == 0) + { + /* Wait to ensure the codec thread has blocked */ + while (rb->threads[i].state != STATE_BLOCKED) + rb->yield(); + + /* Now we can steal the stack */ + audio_stack = rb->threads[i].stack; + audio_stack_size = rb->threads[i].stack_size; + + /* Backup the codec thread's stack */ + rb->memcpy(codec_stack_copy, audio_stack, audio_stack_size); + break; + } + } + + if (audio_stack == NULL) + { + /* This shouldn't happen, but deal with it anyway by using + the copy instead */ + audio_stack = codec_stack_copy; + audio_stack_size = AUDIO_STACKSIZE; + } +#endif + + /* Initialise the encoded audio buffer and its descriptors */ + audio_queue.start = mpeg_malloc(AUDIOBUF_ALLOC_SIZE, + MPEG_ALLOC_AUDIOBUF); + if (audio_queue.start == NULL) + return false; + + /* Start the audio thread */ + audio_str.hdr.q = &audio_str_queue; + rb->queue_init(audio_str.hdr.q, false); + rb->queue_enable_queue_send(audio_str.hdr.q, &audio_str_queue_send); + + audio_str.thread = rb->create_thread( + audio_thread, audio_stack, audio_stack_size, 0, + "mpgaudio" IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, CPU)); + + if (audio_str.thread == NULL) + return false; + + /* Wait for thread to initialize */ + str_send_msg(&audio_str, STREAM_NULL, 0); + + return true; +} + +/* Stops the audio thread */ +void audio_thread_exit(void) +{ + if (audio_str.thread != NULL) + { + str_post_msg(&audio_str, STREAM_QUIT, 0); + rb->thread_wait(audio_str.thread); + audio_str.thread = NULL; + } + +#ifndef SIMULATOR + /* Restore the codec thread's stack */ + rb->memcpy(audio_stack, codec_stack_copy, audio_stack_size); +#endif +} -- cgit v1.2.3