rockbox/lib/rbcodec/codecs/wav_enc.c

245 lines
7.8 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2006 Antonius Hellmann
* Copyright (C) 2006-2013 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 <inttypes.h>
#include "codeclib.h"
CODEC_ENC_HEADER
struct riff_header
{
uint8_t riff_id[4]; /* 00h - "RIFF" */
uint32_t riff_size; /* 04h - sz following headers + data_size */
/* format header */
uint8_t format[4]; /* 08h - "WAVE" */
uint8_t format_id[4]; /* 0Ch - "fmt " */
uint32_t format_size; /* 10h - 16 for PCM (sz format data) */
/* format data */
uint16_t audio_format; /* 14h - 1=PCM */
uint16_t num_channels; /* 16h - 1=M, 2=S, etc. */
uint32_t sample_rate; /* 18h - HZ */
uint32_t byte_rate; /* 1Ch - num_channels*sample_rate*bits_per_sample/8 */
uint16_t block_align; /* 20h - num_channels*bits_per_samples/8 */
uint16_t bits_per_sample; /* 22h - 8=8 bits, 16=16 bits, etc. */
/* Not for audio_format=1 (PCM) */
/* uint16_t extra_param_size; 24h - size of extra data */
/* uint8_t extra_params[extra_param_size]; */
/* data header */
uint8_t data_id[4]; /* 24h - "data" */
uint32_t data_size; /* 28h - num_samples*num_channels*bits_per_sample/8 */
/* uint8_t data[data_size]; 2Ch - actual sound data */
} __attribute__((packed));
#define RIFF_FMT_HEADER_SIZE 12 /* format -> format_size */
#define RIFF_FMT_DATA_SIZE 16 /* audio_format -> bits_per_sample */
#define RIFF_DATA_HEADER_SIZE 8 /* data_id -> data_size */
#define PCM_DEPTH_BYTES 2
#define PCM_DEPTH_BITS 16
#define PCM_SAMP_PER_CHUNK 2048
static int num_channels;
static uint32_t sample_rate;
static size_t frame_size;
static size_t data_size;
static const struct riff_header riff_template_header =
{
/* "RIFF" header */
{ 'R', 'I', 'F', 'F' }, /* riff_id */
0, /* riff_size (*) */
/* format header */
{ 'W', 'A', 'V', 'E' }, /* format */
{ 'f', 'm', 't', ' ' }, /* format_id */
htole32(16), /* format_size */
/* format data */
htole16(1), /* audio_format */
0, /* num_channels (*) */
0, /* sample_rate (*) */
0, /* byte_rate (*) */
0, /* block_align (*) */
htole16(PCM_DEPTH_BITS), /* bits_per_sample */
/* data header */
{ 'd', 'a', 't', 'a' }, /* data_id */
0 /* data_size (*) */
/* (*) updated when finalizing stream */
};
static inline void frame_htole(uint32_t *p, size_t size)
{
#ifdef ROCKBOX_BIG_ENDIAN
/* Byte-swap samples, stereo or mono */
do
{
uint32_t t;
t = swap_odd_even32(*p); *p++ = t;
t = swap_odd_even32(*p); *p++ = t;
t = swap_odd_even32(*p); *p++ = t;
t = swap_odd_even32(*p); *p++ = t;
t = swap_odd_even32(*p); *p++ = t;
t = swap_odd_even32(*p); *p++ = t;
t = swap_odd_even32(*p); *p++ = t;
t = swap_odd_even32(*p); *p++ = t;
}
while (size -= 8 * 2 * PCM_DEPTH_BYTES);
#endif /* ROCKBOX_BIG_ENDIAN */
(void)p; (void)size;
}
static int on_stream_data(struct enc_chunk_data *data)
{
size_t size = data->hdr.size;
if (ci->enc_stream_write(data->data, size) != (ssize_t)size)
return -1;
data_size += size;
return 0;
}
static int on_stream_start(void)
{
/* reset sample count */
data_size = 0;
/* write template header */
if (ci->enc_stream_write(&riff_template_header, sizeof (struct riff_header))
!= sizeof (struct riff_header))
return -1;
return 0;
}
static int on_stream_end(union enc_chunk_hdr *hdr)
{
/* update template header */
struct riff_header riff;
if (hdr->err)
{
/* Called for stream error; get correct data size */
ssize_t size = ci->enc_stream_lseek(0, SEEK_END);
if (size > (ssize_t)sizeof (riff))
data_size = size - sizeof (riff);
}
if (ci->enc_stream_lseek(0, SEEK_SET) ||
ci->enc_stream_read(&riff, sizeof (riff)) != sizeof (riff))
return -1;
/* "RIFF" header */
riff.riff_size = htole32(RIFF_FMT_HEADER_SIZE + RIFF_FMT_DATA_SIZE
+ RIFF_DATA_HEADER_SIZE + data_size);
/* format data */
riff.num_channels = htole16(num_channels);
riff.sample_rate = htole32(sample_rate);
riff.byte_rate = htole32(sample_rate*num_channels*PCM_DEPTH_BYTES);
riff.block_align = htole16(num_channels*PCM_DEPTH_BYTES);
/* data header */
riff.data_size = htole32(data_size);
if (ci->enc_stream_lseek(0, SEEK_SET) != 0)
return -2;
if (ci->enc_stream_write(&riff, sizeof (riff)) != sizeof (riff))
return -3;
return 0;
}
/* this is the codec entry point */
enum codec_status codec_main(enum codec_entry_call_reason reason)
{
return CODEC_OK;
(void)reason;
}
/* this is called for each file to process */
enum codec_status ICODE_ATTR codec_run(void)
{
enum { GETBUF_ENC, GETBUF_PCM } getbuf = GETBUF_ENC;
struct enc_chunk_data *data = NULL;
/* main encoding loop */
while (1)
{
enum codec_command_action action = ci->get_command(NULL);
if (action != CODEC_ACTION_NULL)
break;
/* First obtain output buffer; when available, get PCM data */
switch (getbuf)
{
case GETBUF_ENC:
if (!(data = ci->enc_encbuf_get_buffer(frame_size)))
continue;
getbuf = GETBUF_PCM;
case GETBUF_PCM:
if (!ci->enc_pcmbuf_read(data->data, PCM_SAMP_PER_CHUNK))
continue;
getbuf = GETBUF_ENC;
}
data->hdr.size = frame_size;
data->pcm_count = PCM_SAMP_PER_CHUNK;
frame_htole((uint32_t *)data->data, frame_size);
ci->enc_pcmbuf_advance(PCM_SAMP_PER_CHUNK);
ci->enc_encbuf_finish_buffer();
}
return CODEC_OK;
}
/* this is called by recording system */
int ICODE_ATTR enc_callback(enum enc_callback_reason reason,
void *params)
{
if (LIKELY(reason == ENC_CB_STREAM))
{
switch (((union enc_chunk_hdr *)params)->type)
{
case CHUNK_T_DATA:
return on_stream_data(params);
case CHUNK_T_STREAM_START:
return on_stream_start();
case CHUNK_T_STREAM_END:
return on_stream_end(params);
}
}
else if (reason == ENC_CB_INPUTS)
{
struct enc_inputs *inputs = params;
sample_rate = inputs->sample_rate;
num_channels = inputs->num_channels;
frame_size = PCM_SAMP_PER_CHUNK*PCM_DEPTH_BYTES*num_channels;
}
return 0;
}