/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2009 Mohamed Tarek * * 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 #include "logf.h" #include "codeclib.h" #include "inttypes.h" #include "libcook/cook.h" CODEC_HEADER static RMContext rmctx IBSS_ATTR_COOK_LARGE_IRAM; static RMPacket pkt IBSS_ATTR_COOK_LARGE_IRAM; static COOKContext q IBSS_ATTR; static int32_t rm_outbuf[2048] IBSS_ATTR_COOK_LARGE_IRAM MEM_ALIGN_ATTR; static void init_rm(RMContext *rmctx) { memcpy(rmctx, (void*)(( (intptr_t)ci->id3->id3v2buf + 3 ) &~ 3), sizeof(RMContext)); } /* this is the codec entry point */ enum codec_status codec_main(enum codec_entry_call_reason reason) { /* Nothing to do */ return CODEC_OK; (void)reason; } /* this is called for each file to process */ enum codec_status codec_run(void) { static size_t buff_size; int datasize, res, consumed, i, time_offset; uint8_t *bit_buffer; uint16_t fs,sps,h; uint32_t packet_count; int scrambling_unit_size, num_units; size_t resume_offset; intptr_t param; enum codec_command_action action; if (codec_init()) { DEBUGF("codec init failed\n"); return CODEC_ERROR; } action = CODEC_ACTION_NULL; param = ci->id3->elapsed; resume_offset = ci->id3->offset; codec_set_replaygain(ci->id3); ci->memset(&rmctx,0,sizeof(RMContext)); ci->memset(&pkt,0,sizeof(RMPacket)); ci->memset(&q,0,sizeof(COOKContext)); ci->seek_buffer(0); init_rm(&rmctx); ci->configure(DSP_SET_FREQUENCY, ci->id3->frequency); /* cook's sample representation is 21.11 * DSP_SET_SAMPLE_DEPTH = 11 (FRACT) + 16 (NATIVE) - 1 (SIGN) = 26 */ ci->configure(DSP_SET_SAMPLE_DEPTH, 26); ci->configure(DSP_SET_STEREO_MODE, rmctx.nb_channels == 1 ? STEREO_MONO : STEREO_NONINTERLEAVED); packet_count = rmctx.nb_packets; rmctx.audio_framesize = rmctx.block_align; rmctx.block_align = rmctx.sub_packet_size; fs = rmctx.audio_framesize; sps= rmctx.block_align; h = rmctx.sub_packet_h; scrambling_unit_size = h * (fs + PACKET_HEADER_SIZE); res =cook_decode_init(&rmctx, &q); if(res < 0) { DEBUGF("failed to initialize cook decoder\n"); return CODEC_ERROR; } /* check for a mid-track resume and force a seek time accordingly */ if(resume_offset) { resume_offset -= MIN(resume_offset, rmctx.data_offset + DATA_HEADER_SIZE); num_units = (int)resume_offset / scrambling_unit_size; /* put number of subpackets to skip in resume_offset */ resume_offset /= (sps + PACKET_HEADER_SIZE); param = (int)resume_offset * ((sps * 8 * 1000)/rmctx.bit_rate); } if (param) { action = CODEC_ACTION_SEEK_TIME; } else { ci->set_elapsed(0); } ci->advance_buffer(rmctx.data_offset + DATA_HEADER_SIZE); /* The main decoder loop */ seek_start : while(packet_count) { bit_buffer = (uint8_t *) ci->request_buffer(&buff_size, scrambling_unit_size); consumed = rm_get_packet(&bit_buffer, &rmctx, &pkt); if(consumed < 0) { DEBUGF("rm_get_packet failed\n"); return CODEC_ERROR; } for(i = 0; i < rmctx.audio_pkt_cnt*(fs/sps) ; i++) { if (action == CODEC_ACTION_NULL) action = ci->get_command(¶m); if (action == CODEC_ACTION_HALT) return CODEC_OK; if (action == CODEC_ACTION_SEEK_TIME) { /* Do not allow seeking beyond the file's length */ if ((unsigned) param > ci->id3->length) { ci->set_elapsed(ci->id3->length); ci->seek_complete(); return CODEC_OK; } ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE); packet_count = rmctx.nb_packets; rmctx.audio_pkt_cnt = 0; rmctx.frame_number = 0; /* Seek to the start of the track */ if (param == 0) { ci->set_elapsed(0); ci->seek_complete(); action = CODEC_ACTION_NULL; goto seek_start; } num_units = (param/(sps*1000*8/rmctx.bit_rate))/(h*(fs/sps)); ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE + consumed * num_units); bit_buffer = (uint8_t *) ci->request_buffer(&buff_size, scrambling_unit_size); consumed = rm_get_packet(&bit_buffer, &rmctx, &pkt); if(consumed < 0) { DEBUGF("rm_get_packet failed\n"); ci->seek_complete(); return CODEC_ERROR; } packet_count = rmctx.nb_packets - rmctx.audio_pkt_cnt * num_units; rmctx.frame_number = (param/(sps*1000*8/rmctx.bit_rate)); while(rmctx.audiotimestamp > (unsigned) param) { rmctx.audio_pkt_cnt = 0; ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE + consumed * (num_units-1)); bit_buffer = (uint8_t *) ci->request_buffer(&buff_size, scrambling_unit_size); consumed = rm_get_packet(&bit_buffer, &rmctx, &pkt); packet_count += rmctx.audio_pkt_cnt; num_units--; } time_offset = param - rmctx.audiotimestamp; i = (time_offset/((sps * 8 * 1000)/rmctx.bit_rate)); ci->set_elapsed(rmctx.audiotimestamp+(1000*8*sps/rmctx.bit_rate)*i); ci->seek_complete(); } action = CODEC_ACTION_NULL; res = cook_decode_frame(&rmctx,&q, rm_outbuf, &datasize, pkt.frames[i], rmctx.block_align); rmctx.frame_number++; /* skip the first two frames; no valid audio */ if(rmctx.frame_number < 3) continue; if(res != rmctx.block_align) { DEBUGF("codec error\n"); return CODEC_ERROR; } ci->pcmbuf_insert(rm_outbuf, rm_outbuf+q.samples_per_channel, q.samples_per_channel); ci->set_elapsed(rmctx.audiotimestamp+(1000*8*sps/rmctx.bit_rate)*i); } packet_count -= rmctx.audio_pkt_cnt; rmctx.audio_pkt_cnt = 0; ci->advance_buffer(consumed); } return CODEC_OK; }