3b7d703329
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@11634 a1c6a512-1295-4272-9138-f99709370657
402 lines
12 KiB
C
402 lines
12 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2006 Antonius Hellmann
|
|
*
|
|
* All files in this archive are subject to the GNU General Public License.
|
|
* See the file COPYING in the source tree root for full license agreement.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifndef SIMULATOR
|
|
|
|
#include <inttypes.h>
|
|
#include "codeclib.h"
|
|
|
|
CODEC_ENC_HEADER
|
|
|
|
struct aiff_header
|
|
{
|
|
uint8_t form_id[4]; /* 00h - 'FORM' */
|
|
uint32_t form_size; /* 04h - size of file - 8 */
|
|
uint8_t aiff_id[4]; /* 08h - 'AIFF' */
|
|
uint8_t comm_id[4]; /* 0Ch - 'COMM' */
|
|
int32_t comm_size; /* 10h - num_channels through sample_rate
|
|
(18) */
|
|
int16_t num_channels; /* 14h - 1=M, 2=S, etc. */
|
|
uint32_t num_sample_frames; /* 16h - num samples for each channel */
|
|
int16_t sample_size; /* 1ah - 1-32 bits per sample */
|
|
uint8_t sample_rate[10]; /* 1ch - IEEE 754 80-bit floating point */
|
|
uint8_t ssnd_id[4]; /* 26h - "SSND" */
|
|
int32_t ssnd_size; /* 2ah - size of chunk from offset to
|
|
end of pcm data */
|
|
uint32_t offset; /* 2eh - data offset from end of header */
|
|
uint32_t block_size; /* 32h - pcm data alignment */
|
|
/* 36h */
|
|
} __attribute__((packed));
|
|
|
|
#define PCM_DEPTH_BYTES 2
|
|
#define PCM_DEPTH_BITS 16
|
|
#define PCM_SAMP_PER_CHUNK 2048
|
|
#define PCM_CHUNK_SIZE (PCM_SAMP_PER_CHUNK*4)
|
|
|
|
/* Template headers */
|
|
struct aiff_header aiff_header =
|
|
{
|
|
{ 'F', 'O', 'R', 'M' }, /* form_id */
|
|
0, /* form_size (*) */
|
|
{ 'A', 'I', 'F', 'F' }, /* aiff_id */
|
|
{ 'C', 'O', 'M', 'M' }, /* comm_id */
|
|
H_TO_BE32(18), /* comm_size */
|
|
0, /* num_channels (*) */
|
|
0, /* num_sample_frames (*) */
|
|
H_TO_BE32(PCM_DEPTH_BITS), /* sample_size */
|
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* sample_rate (*) */
|
|
{ 'S', 'S', 'N', 'D' }, /* ssnd_id */
|
|
0, /* ssnd_size (*) */
|
|
H_TO_BE32(0), /* offset */
|
|
H_TO_BE32(0), /* block_size */
|
|
};
|
|
|
|
/* (*) updated when finalizing file */
|
|
|
|
static int num_channels;
|
|
uint32_t sample_rate;
|
|
uint32_t enc_size;
|
|
|
|
/* convert unsigned 32 bit value to 80-bit floating point number */
|
|
static void uint32_h_to_ieee754_extended_be(uint8_t f[10], uint32_t l) ICODE_ATTR;
|
|
static void uint32_h_to_ieee754_extended_be(uint8_t f[10], uint32_t l)
|
|
{
|
|
int32_t exp;
|
|
|
|
ci->memset(f, 0, 10);
|
|
|
|
if (l == 0)
|
|
return;
|
|
|
|
for (exp = 30; (l & (1ul << 31)) == 0; exp--)
|
|
l <<= 1;
|
|
|
|
/* sign always zero - bit 79 */
|
|
/* exponent is 0-31 (normalized: 31 - shift + 16383) - bits 64-78 */
|
|
f[0] = 0x40;
|
|
f[1] = (uint8_t)exp;
|
|
/* mantissa is value left justified with most significant non-zero
|
|
bit stored in bit 63 - bits 0-63 */
|
|
*(uint32_t *)&f[2] = htobe32(l);
|
|
} /* uint32_h_to_ieee754_extended_be */
|
|
|
|
/* called version often - inline */
|
|
static inline bool is_file_data_ok(struct enc_file_event_data *data) ICODE_ATTR;
|
|
static inline bool is_file_data_ok(struct enc_file_event_data *data)
|
|
{
|
|
return data->rec_file >= 0 && (long)data->chunk->flags >= 0;
|
|
} /* is_file_data_ok */
|
|
|
|
/* called version often - inline */
|
|
static inline bool on_write_chunk(struct enc_file_event_data *data) ICODE_ATTR;
|
|
static inline bool on_write_chunk(struct enc_file_event_data *data)
|
|
{
|
|
if (!is_file_data_ok(data))
|
|
return false;
|
|
|
|
if (data->chunk->enc_data == NULL)
|
|
{
|
|
#ifdef ROCKBOX_HAS_LOGF
|
|
ci->logf("aiff enc: NULL data");
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
if (ci->write(data->rec_file, data->chunk->enc_data,
|
|
data->chunk->enc_size) != (ssize_t)data->chunk->enc_size)
|
|
return false;
|
|
|
|
data->num_pcm_samples += data->chunk->num_pcm;
|
|
return true;
|
|
} /* on_write_chunk */
|
|
|
|
static bool on_start_file(struct enc_file_event_data *data)
|
|
{
|
|
if ((data->chunk->flags & CHUNKF_ERROR) || *data->filename == '\0')
|
|
return false;
|
|
|
|
data->rec_file = ci->open(data->filename, O_RDWR|O_CREAT|O_TRUNC);
|
|
|
|
if (data->rec_file < 0)
|
|
return false;
|
|
|
|
/* reset sample count */
|
|
data->num_pcm_samples = 0;
|
|
|
|
/* write template headers */
|
|
if (ci->write(data->rec_file, &aiff_header, sizeof (aiff_header))
|
|
!= sizeof (aiff_header))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
data->new_enc_size += sizeof(aiff_header);
|
|
return true;
|
|
} /* on_start_file */
|
|
|
|
static bool on_end_file(struct enc_file_event_data *data)
|
|
{
|
|
/* update template headers */
|
|
struct aiff_header hdr;
|
|
uint32_t data_size;
|
|
|
|
if (!is_file_data_ok(data))
|
|
return false;
|
|
|
|
if (ci->lseek(data->rec_file, 0, SEEK_SET) != 0 ||
|
|
ci->read(data->rec_file, &hdr, sizeof (hdr)) != sizeof (hdr))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
data_size = data->num_pcm_samples*num_channels*PCM_DEPTH_BYTES;
|
|
|
|
/* 'FORM' chunk */
|
|
hdr.form_size = htobe32(data_size + sizeof (hdr) - 8);
|
|
|
|
/* 'COMM' chunk */
|
|
hdr.num_channels = htobe16(num_channels);
|
|
hdr.num_sample_frames = htobe32(data->num_pcm_samples);
|
|
uint32_h_to_ieee754_extended_be(hdr.sample_rate, sample_rate);
|
|
|
|
/* 'SSND' chunk */
|
|
hdr.ssnd_size = htobe32(data_size + 8);
|
|
|
|
if (ci->lseek(data->rec_file, 0, SEEK_SET) != 0 ||
|
|
ci->write(data->rec_file, &hdr, sizeof (hdr)) != sizeof (hdr))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ci->fsync(data->rec_file);
|
|
ci->close(data->rec_file);
|
|
data->rec_file = -1;
|
|
|
|
return true;
|
|
} /* on_end_file */
|
|
|
|
static void enc_events_callback(enum enc_events event, void *data) ICODE_ATTR;
|
|
static void enc_events_callback(enum enc_events event, void *data)
|
|
{
|
|
if (event == ENC_WRITE_CHUNK)
|
|
{
|
|
if (on_write_chunk((struct enc_file_event_data *)data))
|
|
return;
|
|
}
|
|
else if (event == ENC_START_FILE)
|
|
{
|
|
if (on_start_file((struct enc_file_event_data *)data))
|
|
return;
|
|
}
|
|
else if (event == ENC_END_FILE)
|
|
{
|
|
if (on_end_file((struct enc_file_event_data *)data))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
((struct enc_file_event_data *)data)->chunk->flags |= CHUNKF_ERROR;
|
|
} /* enc_events_callback */
|
|
|
|
/* convert native pcm samples to aiff format samples */
|
|
static void chunk_to_aiff_format(uint32_t *src, uint32_t *dst) ICODE_ATTR;
|
|
static void chunk_to_aiff_format(uint32_t *src, uint32_t *dst)
|
|
{
|
|
if (num_channels == 1)
|
|
{
|
|
/* On big endian:
|
|
* |LLLLLLLLllllllll|RRRRRRRRrrrrrrrr|
|
|
* |LLLLLLLLllllllll|RRRRRRRRrrrrrrrr| =>
|
|
* |MMMMMMMMmmmmmmmm|MMMMMMMMmmmmmmmm|
|
|
*
|
|
* On little endian:
|
|
* |llllllllLLLLLLLL|rrrrrrrrRRRRRRRR|
|
|
* |llllllllLLLLLLLL|rrrrrrrrRRRRRRRR| =>
|
|
* |MMMMMMMMmmmmmmmm|MMMMMMMMmmmmmmmm|
|
|
*/
|
|
uint32_t *src_end = src + PCM_SAMP_PER_CHUNK;
|
|
|
|
inline void to_mono(uint32_t **src, uint32_t **dst)
|
|
{
|
|
int32_t lr1, lr2;
|
|
|
|
lr1 = *(*src)++;
|
|
lr1 = ((int16_t)lr1 + (lr1 >> 16)) / 2;
|
|
|
|
lr2 = *(*src)++;
|
|
lr2 = ((int16_t)lr2 + (lr2 >> 16)) / 2;
|
|
*(*dst)++ = swap_odd_even_le32((lr1 << 16) | (uint16_t)lr2);
|
|
} /* to_mono */
|
|
|
|
do
|
|
{
|
|
to_mono(&src, &dst);
|
|
to_mono(&src, &dst);
|
|
to_mono(&src, &dst);
|
|
to_mono(&src, &dst);
|
|
to_mono(&src, &dst);
|
|
to_mono(&src, &dst);
|
|
to_mono(&src, &dst);
|
|
to_mono(&src, &dst);
|
|
}
|
|
while (src < src_end);
|
|
}
|
|
else
|
|
{
|
|
#ifdef ROCKBOX_BIG_ENDIAN
|
|
/* |LLLLLLLLllllllll|RRRRRRRRrrrrrrrr| =>
|
|
* |LLLLLLLLllllllll|RRRRRRRRrrrrrrrr|
|
|
*/
|
|
ci->memcpy(dst, src, PCM_CHUNK_SIZE);
|
|
#else
|
|
/* |llllllllLLLLLLLL|rrrrrrrrRRRRRRRR| =>
|
|
* |LLLLLLLLllllllll|RRRRRRRRrrrrrrrr|
|
|
*/
|
|
uint32_t *src_end = src + PCM_SAMP_PER_CHUNK;
|
|
|
|
do
|
|
{
|
|
*dst++ = swap_odd_even32(*src++);
|
|
*dst++ = swap_odd_even32(*src++);
|
|
*dst++ = swap_odd_even32(*src++);
|
|
*dst++ = swap_odd_even32(*src++);
|
|
*dst++ = swap_odd_even32(*src++);
|
|
*dst++ = swap_odd_even32(*src++);
|
|
*dst++ = swap_odd_even32(*src++);
|
|
*dst++ = swap_odd_even32(*src++);
|
|
}
|
|
while (src < src_end);
|
|
#endif
|
|
}
|
|
} /* chunk_to_aiff_format */
|
|
|
|
static bool init_encoder(void)
|
|
{
|
|
struct enc_inputs inputs;
|
|
struct enc_parameters params;
|
|
|
|
if (ci->enc_get_inputs == NULL ||
|
|
ci->enc_set_parameters == NULL ||
|
|
ci->enc_get_chunk == NULL ||
|
|
ci->enc_finish_chunk == NULL ||
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
ci->enc_pcm_buf_near_empty == NULL ||
|
|
#endif
|
|
ci->enc_get_pcm_data == NULL )
|
|
return false;
|
|
|
|
ci->enc_get_inputs(&inputs);
|
|
|
|
if (inputs.config->afmt != AFMT_AIFF)
|
|
return false;
|
|
|
|
sample_rate = inputs.sample_rate;
|
|
num_channels = inputs.num_channels;
|
|
|
|
/* configure the buffer system */
|
|
params.afmt = AFMT_AIFF;
|
|
enc_size = PCM_CHUNK_SIZE*inputs.num_channels / 2;
|
|
params.chunk_size = enc_size;
|
|
params.enc_sample_rate = sample_rate;
|
|
params.reserve_bytes = 0;
|
|
params.events_callback = enc_events_callback;
|
|
ci->enc_set_parameters(¶ms);
|
|
|
|
return true;
|
|
} /* init_encoder */
|
|
|
|
/* main codec entry point */
|
|
enum codec_status codec_main(void)
|
|
{
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
bool cpu_boosted;
|
|
#endif
|
|
|
|
if (!init_encoder())
|
|
{
|
|
ci->enc_codec_loaded = -1;
|
|
return CODEC_ERROR;
|
|
}
|
|
|
|
/* main application waits for this flag during encoder loading */
|
|
ci->enc_codec_loaded = 1;
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
ci->cpu_boost(true);
|
|
cpu_boosted = true;
|
|
#endif
|
|
|
|
/* main encoding loop */
|
|
while(!ci->stop_codec)
|
|
{
|
|
uint32_t *src;
|
|
|
|
while ((src = (uint32_t *)ci->enc_get_pcm_data(PCM_CHUNK_SIZE)) != NULL)
|
|
{
|
|
struct enc_chunk_hdr *chunk;
|
|
|
|
if (ci->stop_codec)
|
|
break;
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
if (!cpu_boosted && ci->enc_pcm_buf_near_empty() == 0)
|
|
{
|
|
ci->cpu_boost(true);
|
|
cpu_boosted = true;
|
|
}
|
|
#endif
|
|
chunk = ci->enc_get_chunk();
|
|
chunk->enc_size = enc_size;
|
|
chunk->num_pcm = PCM_SAMP_PER_CHUNK;
|
|
chunk->enc_data = ENC_CHUNK_SKIP_HDR(chunk->enc_data, chunk);
|
|
|
|
chunk_to_aiff_format(src, (uint32_t *)chunk->enc_data);
|
|
|
|
ci->enc_finish_chunk();
|
|
ci->yield();
|
|
}
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
if (cpu_boosted && ci->enc_pcm_buf_near_empty() != 0)
|
|
{
|
|
ci->cpu_boost(false);
|
|
cpu_boosted = false;
|
|
}
|
|
#endif
|
|
ci->yield();
|
|
}
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
if (cpu_boosted) /* set initial boost state */
|
|
ci->cpu_boost(false);
|
|
#endif
|
|
|
|
/* reset parameters to initial state */
|
|
ci->enc_set_parameters(NULL);
|
|
|
|
/* main application waits for this flag during encoder removing */
|
|
ci->enc_codec_loaded = 0;
|
|
|
|
return CODEC_OK;
|
|
} /* codec_start */
|
|
|
|
#endif /* ndef SIMULATOR */
|