From 9d3d925295112a0080bc1d70fad170db9e1af2a9 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Thu, 13 Oct 2022 11:04:12 -0400 Subject: Revert "RFC: Get rid of mpegplayer plugin" This reverts commit d25d24812e8120c0eb133a412287ac030eb185c9. Change-Id: I1563223e343fb1e2eda72a45823b38350025ff93 --- apps/plugins/mpegplayer/audio_thread.c | 721 +++++++++++++++++++++++++++++++++ 1 file changed, 721 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..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 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; + } +} -- cgit v1.2.3