summaryrefslogtreecommitdiffstats
path: root/apps/plugins/mpegplayer/audio_thread.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/mpegplayer/audio_thread.c')
-rw-r--r--apps/plugins/mpegplayer/audio_thread.c721
1 files changed, 721 insertions, 0 deletions
diff --git a/apps/plugins/mpegplayer/audio_thread.c b/apps/plugins/mpegplayer/audio_thread.c
new file mode 100644
index 0000000000..764ad111f2
--- /dev/null
+++ b/apps/plugins/mpegplayer/audio_thread.c
@@ -0,0 +1,721 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * mpegplayer audio thread implementation
+ *
+ * Copyright (c) 2007 Michael Sevakis
+ *
+ * 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 "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 */
+ unsigned samplerate; /* Current stream sample rate */
+ int nchannels; /* Number of audio channels */
+ struct dsp_config *dsp; /* The DSP we're using */
+ struct dsp_buffer src; /* Current audio data for DSP processing */
+};
+
+/* The audio thread is stolen from the core codec thread */
+static struct event_queue audio_str_queue SHAREDBSS_ATTR;
+static struct queue_sender_list audio_str_queue_send SHAREDBSS_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;
+
+/*sbsample buffer for mad_frame*/
+mad_fixed_t sbsample[2][36][32];
+
+/* 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 */
+#if defined(CPU_COLDFIRE) || defined(CPU_S5L870X)
+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 head 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;
+ rb->memset(audio_queue.descs, 0, sizeof (audio_queue.descs));
+ audio_queue.curr = audiodesc_queue_head();
+}
+
+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)
+{
+ /* init the sbsample buffer */
+ frame.sbsample_prev = &sbsample;
+ frame.sbsample = &sbsample;
+
+ /* We do this so libmad doesn't try to call codec_calloc(). This needs to
+ * be called before mad_stream_init(), mad_frame_inti() and
+ * mad_synth_init(). */
+ frame.overlap = &mad_frame_overlap;
+ stream.main_data = &mad_main_data;
+
+ /* Call mad initialization. Those will zero the arrays frame.overlap,
+ * frame.sbsample and frame.sbsample_prev. Therefore there is no need to
+ * zero them here. */
+ mad_stream_init(&stream);
+ mad_frame_init(&frame);
+ mad_synth_init(&synth);
+}
+
+/* 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",
+ (long)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<fe\n");
+ retval = STREAM_PERFECT_MATCH;
+ break;
+ }
+ else if (time > 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:
+ 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;
+ td->samplerate = 0;
+ td->nchannels = 0;
+
+ 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:
+ /* These return when in playing state */
+ return;
+ }
+ }
+
+ str_get_msg(&audio_str, &td->ev);
+ }
+}
+
+static void audio_thread(void)
+{
+ struct audio_thread_data td;
+#ifdef HAVE_PRIORITY_SCHEDULING
+ /* Up the priority since the core DSP over-yields internally */
+ int old_priority = rb->thread_set_priority(rb->thread_self(),
+ PRIORITY_PLAYBACK-4);
+#endif
+
+ rb->memset(&td, 0, sizeof (td));
+ td.status = STREAM_STOPPED;
+ td.state = TSTATE_EOS;
+
+ /* We need this here to init the EMAC for Coldfire targets */
+ init_mad();
+
+ td.dsp = rb->dsp_get_config(CODEC_IDX_AUDIO);
+ rb->dsp_configure(td.dsp, DSP_SET_OUT_FREQUENCY, CLOCK_RATE);
+#ifdef HAVE_PITCHCONTROL
+ rb->sound_set_pitch(PITCH_SPEED_100);
+ rb->dsp_set_timestretch(PITCH_SPEED_100);
+#endif
+ rb->dsp_configure(td.dsp, DSP_RESET, 0);
+ rb->dsp_configure(td.dsp, DSP_FLUSH, 0);
+ rb->dsp_configure(td.dsp, DSP_SET_SAMPLE_DEPTH, MAD_F_FRACBITS);
+
+ 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;
+ /* Anything else is interpreted as an exit */
+ default:
+ {
+#ifdef HAVE_PRIORITY_SCHEDULING
+ rb->thread_set_priority(rb->thread_self(), old_priority);
+#endif
+ 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; /* Still have frames to decode */
+
+ /* Used up remainder of compressed audio buffer. Wait for
+ * samples on PCM buffer to finish playing. */
+ audio_queue_reset();
+
+ while (1)
+ {
+ if (pcm_output_empty())
+ {
+ td.state = TSTATE_EOS;
+ stream_generate_event(&audio_str, STREAM_EV_COMPLETE, 0);
+ break;
+ }
+
+ pcm_output_drain();
+ str_get_msg_w_tmo(&audio_str, &td.ev, 1);
+
+ if (td.ev.id != SYS_TIMEOUT)
+ break;
+ }
+
+ 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_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;
+ td.status = STREAM_ERROR;
+ stream_generate_event(&audio_str, STREAM_EV_COMPLETE, 0);
+
+ 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 **/
+ if (frame.header.samplerate != td.samplerate)
+ {
+ td.samplerate = frame.header.samplerate;
+ rb->dsp_configure(td.dsp, DSP_SET_FREQUENCY,
+ td.samplerate);
+ }
+
+ if (MAD_NCHANNELS(&frame.header) != td.nchannels)
+ {
+ td.nchannels = MAD_NCHANNELS(&frame.header);
+ rb->dsp_configure(td.dsp, DSP_SET_STEREO_MODE,
+ td.nchannels == 1 ?
+ STEREO_MONO : STEREO_NONINTERLEAVED);
+ }
+
+ td.src.remcount = synth.pcm.length;
+ td.src.pin[0] = synth.pcm.samples[0];
+ td.src.pin[1] = synth.pcm.samples[1];
+ td.src.proc_mask = 0;
+
+ td.state = TSTATE_RENDER_WAIT;
+
+ /* Add a frame of audio to the pcm buffer. Maximum is 1152 samples. */
+ render_wait:
+ rb->yield();
+
+ while (1)
+ {
+ struct dsp_buffer dst;
+ dst.remcount = 0;
+ dst.bufcount = MAX(td.src.remcount, 1024);
+
+ ssize_t size = dst.bufcount * 2 * sizeof(int16_t);
+
+ /* Wait for required amount of free buffer space */
+ while ((dst.p16out = pcm_output_get_buffer(&size)) == NULL)
+ {
+ /* Wait one frame */
+ int timeout = dst.bufcount*HZ / td.samplerate;
+ str_get_msg_w_tmo(&audio_str, &td.ev, MAX(timeout, 1));
+ if (td.ev.id != SYS_TIMEOUT)
+ goto message_process;
+ }
+
+ dst.bufcount = size / (2 * sizeof (int16_t));
+ rb->dsp_process(td.dsp, &td.src, &dst);
+
+ if (dst.remcount > 0)
+ {
+ /* Make this data available to DMA */
+ pcm_output_commit_data(dst.remcount * 2 * sizeof(int16_t),
+ audio_queue.curr->time);
+
+ /* As long as we're on this timestamp, the time is just
+ incremented by the number of samples */
+ audio_queue.curr->time += dst.remcount;
+ }
+ else if (td.src.remcount <= 0)
+ {
+ break;
+ }
+ }
+ } /* end decoding loop */
+}
+
+/* Initializes the audio thread resources and starts the thread */
+bool audio_thread_init(void)
+{
+ /* 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);
+
+ /* We steal the codec thread for audio */
+ rb->codec_thread_do_callback(audio_thread, &audio_str.thread);
+
+ rb->queue_enable_queue_send(audio_str.hdr.q, &audio_str_queue_send,
+ audio_str.thread);
+
+ /* 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 != 0)
+ {
+ str_post_msg(&audio_str, STREAM_QUIT, 0);
+ rb->codec_thread_do_callback(NULL, NULL);
+ audio_str.thread = 0;
+ }
+}