mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-10-09 23:53:28 -04:00
The decoder gave the DSP samples with two fractional bits (sample depth 17), far fewer than any other codec. The DSP's filters are only as precise as the samples they are given: with an equalizer band at a low frequency the output was noise, or held at full scale. Scale the last stage of the synthesis filter up by 11 bits and set the sample depth to 28. On ARMv4 the bits are taken from the 64-bit sums of the dewindowing, which had them. Elsewhere the input of that stage is scaled, in its matrixing step; that is not done on ARMv4 because its multiplier takes longer for the larger operands (51.97 against 48.85 MHz below). The Coldfire path uses the C matrixing and has not been run. Seven files decoded under perfsim for the Sansa e200v1 and Clip+: shifted down again, the e200v1 output is within one step of the old output, and the Clip+ output no further from the e200v1's than before. Through a ten band equalizer with bands at 32 and 64 Hz (the filter fix of the DSP included), the noise of atrac3_lp2_132.oma falls from -64 dB to -101 dB relative to the signal. Estimated with perfsim (not measured on a device), atrac3_lp2_132.oma: e200v1 48.81 -> 48.85 MHz, Clip+ 26.91 -> 26.96 MHz. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
256 lines
8.7 KiB
C
256 lines
8.7 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
*
|
|
* 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 <string.h>
|
|
|
|
#include "codeclib.h"
|
|
#include "libatrac/atrac3.h"
|
|
|
|
CODEC_HEADER
|
|
|
|
static RMContext rmctx IBSS_ATTR_LARGE_IRAM;
|
|
static RMPacket pkt IBSS_ATTR_LARGE_IRAM;
|
|
static ATRAC3Context q IBSS_ATTR;
|
|
|
|
static void init_rm(RMContext *rmctx)
|
|
{
|
|
/* initialize the RMContext */
|
|
memcpy(rmctx, (void*)(( (intptr_t)ci->id3->id3v2buf + 3 ) &~ 3), sizeof(RMContext));
|
|
|
|
/* and atrac3 expects extradata in id3v2buf, so we shall give it that */
|
|
memcpy(ci->id3->id3v2buf, (char*)rmctx->codec_extradata, rmctx->extradata_size*sizeof(char));
|
|
}
|
|
|
|
static int request_packet(int size)
|
|
{
|
|
int consumed = 0;
|
|
while (1)
|
|
{
|
|
uint8_t *buffer = ci->request_buffer((size_t *)(&consumed), size);
|
|
if (!consumed)
|
|
break;
|
|
consumed = rm_get_packet(&buffer, &rmctx, &pkt);
|
|
if (consumed < 0 || consumed == size)
|
|
break;
|
|
ci->advance_buffer(size);
|
|
}
|
|
return consumed;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
int datasize, res, consumed, i, time_offset;
|
|
uint16_t fs,sps,h;
|
|
// uint32_t packet_count;
|
|
int spn, packet_header_size, scrambling_unit_size, num_units, elapsed;
|
|
int playback_on = -1;
|
|
size_t resume_offset;
|
|
intptr_t param;
|
|
long action;
|
|
|
|
if (codec_init()) {
|
|
DEBUGF("codec init failed\n");
|
|
return CODEC_ERROR;
|
|
}
|
|
|
|
action = CODEC_ACTION_NULL;
|
|
elapsed = 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(ATRAC3Context));
|
|
|
|
ci->seek_buffer(0);
|
|
init_rm(&rmctx);
|
|
|
|
ci->configure(DSP_SET_FREQUENCY, ci->id3->frequency);
|
|
ci->configure(DSP_SET_SAMPLE_DEPTH, ATRAC3_OUTPUT_DEPTH);
|
|
ci->configure(DSP_SET_STEREO_MODE, rmctx.nb_channels == 1 ?
|
|
STEREO_MONO : STEREO_NONINTERLEAVED);
|
|
|
|
packet_header_size = PACKET_HEADER_SIZE +
|
|
((rmctx.flags & RM_PKT_V1) ? 1 : 0);
|
|
// 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);
|
|
spn = h * fs / sps;
|
|
|
|
/* The decoder takes its frame size from the id3, which the RM
|
|
* metadata parser does not fill in. */
|
|
ci->id3->bytesperframe = sps;
|
|
|
|
res = atrac3_decode_init(&q, ci->id3, rmctx.nb_channels, rmctx.extradata_size);
|
|
if(res < 0) {
|
|
DEBUGF("failed to initialize RM atrac 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 packets to skip in resume_offset */
|
|
resume_offset = num_units * h;
|
|
elapsed = (int)resume_offset * ((8000LL * fs)/rmctx.bit_rate);
|
|
}
|
|
|
|
if (elapsed > 0) {
|
|
param = elapsed;
|
|
action = CODEC_ACTION_SEEK_TIME;
|
|
}
|
|
else {
|
|
elapsed = 0;
|
|
ci->set_elapsed(0);
|
|
}
|
|
|
|
ci->advance_buffer(rmctx.data_offset + DATA_HEADER_SIZE);
|
|
|
|
/* The main decoder loop */
|
|
seek_start :
|
|
while((unsigned)elapsed < rmctx.duration)
|
|
{
|
|
consumed = request_packet(scrambling_unit_size);
|
|
if (!consumed)
|
|
break;
|
|
if(consumed < 0 && playback_on != 0) {
|
|
if(playback_on == -1) {
|
|
/* Error only if packet-parsing failed and playback hadn't started */
|
|
DEBUGF("rm_get_packet failed\n");
|
|
return CODEC_ERROR;
|
|
}
|
|
else
|
|
return CODEC_OK;
|
|
}
|
|
|
|
for (i = 0; i < spn; 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) {
|
|
elapsed = 0;
|
|
ci->set_elapsed(0);
|
|
ci->seek_complete();
|
|
action = CODEC_ACTION_NULL;
|
|
goto seek_start;
|
|
}
|
|
num_units = (param/(sps*1000*8/rmctx.bit_rate))/spn;
|
|
ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE + scrambling_unit_size * num_units);
|
|
consumed = request_packet(scrambling_unit_size);
|
|
if (!consumed)
|
|
return CODEC_OK;
|
|
if(consumed < 0 && playback_on != 0) {
|
|
if(playback_on == -1) {
|
|
/* Error only if packet-parsing failed and playback hadn't started */
|
|
DEBUGF("rm_get_packet failed\n");
|
|
return CODEC_ERROR;
|
|
}
|
|
else
|
|
return CODEC_OK;
|
|
}
|
|
|
|
// packet_count = rmctx.nb_packets - h * num_units;
|
|
rmctx.frame_number = (param/(sps*1000*8/rmctx.bit_rate));
|
|
while (rmctx.audiotimestamp > (unsigned)param && num_units-- > 0) {
|
|
rmctx.audio_pkt_cnt = 0;
|
|
ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE + scrambling_unit_size * num_units);
|
|
consumed = request_packet(scrambling_unit_size);
|
|
if (!consumed)
|
|
return CODEC_OK;
|
|
if(consumed < 0 && playback_on != 0) {
|
|
if(playback_on == -1) {
|
|
/* Error only if packet-parsing failed and playback hadn't started */
|
|
DEBUGF("rm_get_packet failed\n");
|
|
return CODEC_ERROR;
|
|
}
|
|
else
|
|
return CODEC_OK;
|
|
}
|
|
|
|
// packet_count += h;
|
|
}
|
|
|
|
if (num_units < 0)
|
|
rmctx.audiotimestamp = 0;
|
|
time_offset = param - rmctx.audiotimestamp;
|
|
i = (time_offset/((sps * 8 * 1000)/rmctx.bit_rate));
|
|
elapsed = param;
|
|
ci->set_elapsed(elapsed);
|
|
ci->seek_complete();
|
|
}
|
|
|
|
action = CODEC_ACTION_NULL;
|
|
|
|
if(pkt.length)
|
|
res = atrac3_decode_frame(sps, &q, &datasize, pkt.frames[i], sps);
|
|
else /* indicates that there are no remaining frames */
|
|
return CODEC_OK;
|
|
|
|
if (res != sps) {
|
|
DEBUGF("codec error\n");
|
|
return CODEC_ERROR;
|
|
}
|
|
|
|
if(datasize)
|
|
ci->pcmbuf_insert(q.outSamples, q.outSamples + 1024, q.samples_per_frame / rmctx.nb_channels);
|
|
playback_on = 1;
|
|
elapsed = rmctx.audiotimestamp+(1000*8*sps/rmctx.bit_rate)*i;
|
|
ci->set_elapsed(elapsed);
|
|
rmctx.frame_number++;
|
|
}
|
|
// packet_count -= h;
|
|
rmctx.audio_pkt_cnt = 0;
|
|
ci->advance_buffer(scrambling_unit_size);
|
|
}
|
|
|
|
return CODEC_OK;
|
|
}
|