/*************************************************************************** * __________ __ ___. * 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 */ }; /* 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 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(THREAD_ID_CURRENT, 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 = (struct dsp_config *)rb->dsp_configure(NULL, DSP_MYDSP, CODEC_IDX_AUDIO); #ifdef HAVE_PITCHSCREEN rb->sound_set_pitch(PITCH_SPEED_100); #endif rb->dsp_configure(td.dsp, DSP_RESET, 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(THREAD_ID_CURRENT, 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_SWITCH_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.state = TSTATE_RENDER_WAIT; /* Add a frame of audio to the pcm buffer. Maximum is 1152 samples. */ render_wait: if (synth.pcm.length > 0) { const char *src[2] = { (char *)synth.pcm.samples[0], (char *)synth.pcm.samples[1] }; int out_count = (synth.pcm.length * CLOCK_RATE + (td.samplerate - 1)) / td.samplerate; unsigned char *out_buf; ssize_t size = out_count*4; /* Wait for required amount of free buffer space */ while ((out_buf = pcm_output_get_buffer(&size)) == NULL) { /* Wait one frame */ int timeout = out_count*HZ / td.samplerate; str_get_msg_w_tmo(&audio_str, &td.ev, MAX(timeout, 1)); if (td.ev.id != SYS_TIMEOUT) goto message_process; } out_count = rb->dsp_process(td.dsp, out_buf, src, synth.pcm.length); if (out_count <= 0) break; /* Make this data available to DMA */ pcm_output_commit_data(out_count*4, 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 += out_count; } rb->yield(); } /* 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; } }