diff --git a/.clang-format-ignore b/.clang-format-ignore index 653f6bde..4cdb8874 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -1,2 +1,5 @@ # 3rd party libs lib/debugbreak.h +libacm/libacm.h +libacm/decode.c +libacm/util.c diff --git a/libacm/CMakeLists.txt b/libacm/CMakeLists.txt index 3508cf3f..01721d22 100644 --- a/libacm/CMakeLists.txt +++ b/libacm/CMakeLists.txt @@ -3,4 +3,10 @@ set(CPPS aencode.cpp libacm.cpp) -add_library(libacm STATIC ${HEADERS} ${CPPS}) +# these are the relevant source files from upstream libacm (https://github.com/markokr/libacm/) +set(LIB_SRC + decode.c + util.c + libacm.h) + +add_library(libacm STATIC ${HEADERS} ${CPPS} ${LIB_SRC}) diff --git a/libacm/decode.c b/libacm/decode.c new file mode 100644 index 00000000..e3421563 --- /dev/null +++ b/libacm/decode.c @@ -0,0 +1,912 @@ +/* + * ACM decoder. + * + * Copyright (c) 2004-2010, Marko Kreen + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "libacm.h" + +#define ACM_BUFLEN (64*1024) + +#define ACM_EXPECTED_EOF -99 + +typedef int (*filler_t)(ACMStream *acm, unsigned ind, unsigned col); + +/************************************** + * Stream processing + **************************************/ + +/* NB: bits <= 31! Thus less checks in code. */ + +static int load_buf(ACMStream *acm) +{ + int res = 0; + + if (acm->file_eof) + return 0; + + acm->buf_start_ofs += acm->buf_size; + + if (acm->io.read_func != NULL) + res = acm->io.read_func(acm->buf, 1, acm->buf_max, + acm->io_arg); + + if (res < 0) + return ACM_ERR_READ_ERR; + + if (res == 0) { + acm->file_eof = 1; + /* add single zero byte */ + acm->buf[0] = 0; + acm->buf_size = 1; + } else { + acm->buf_size = res; + } + acm->buf_pos = 0; + return 0; +} + +static int load_bits(ACMStream *acm) +{ + int err; + unsigned data, got; + unsigned char *p = acm->buf + acm->buf_pos; + switch (acm->buf_size - acm->buf_pos) { + default: + data = 0; + got = 0; + break; + case 1: + data = p[0]; + got = 8; + break; + case 2: + data = p[0] + (p[1] << 8); + got = 16; + break; + case 3: + data = p[0] + (p[1] << 8) + (p[2] << 16); + got = 24; + break; + } + + if ((err = load_buf(acm)) < 0) + return err; + + while (got < 32) { + if (acm->buf_size - acm->buf_pos == 0) + break; + data |= acm->buf[acm->buf_pos] << got; + got += 8; + acm->buf_pos++; + } + acm->bit_data = data; + acm->bit_avail = got; + return 0; +} + +static int get_bits_reload(ACMStream *acm, unsigned bits) +{ + int got, err; + unsigned data, b_data, b_avail; + + data = acm->bit_data; + got = acm->bit_avail; + bits -= got; + + if (acm->buf_size - acm->buf_pos >= 4) { + unsigned char *p = acm->buf + acm->buf_pos; + acm->buf_pos += 4; + b_data = p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24); + b_avail = 32; + } else { + if ((err = load_bits(acm)) < 0) + return err; + if (acm->bit_avail < bits) + return ACM_ERR_UNEXPECTED_EOF; + b_data = acm->bit_data; + b_avail = acm->bit_avail; + } + + data |= (b_data & ((1 << bits) - 1)) << got; + acm->bit_data = b_data >> bits; + acm->bit_avail = b_avail - bits; + return data; +} + +#define GET_BITS_NOERR(tmpval, acm, bits) do { \ + if (acm->bit_avail >= bits) { \ + tmpval = acm->bit_data & ((1 << bits) - 1); \ + acm->bit_data >>= bits; \ + acm->bit_avail -= bits; \ + } else \ + tmpval = get_bits_reload(acm, bits); \ + } while (0) + +#define GET_BITS(res, acm, bits) do { \ + int tmpval; \ + GET_BITS_NOERR(tmpval, acm, bits); \ + if (tmpval < 0) \ + return tmpval; \ + res = tmpval; \ + } while (0) + +#define GET_BITS_EXPECT_EOF(res, acm, bits) do { \ + int tmpval; \ + GET_BITS_NOERR(tmpval, acm, bits); \ + if (tmpval < 0) { \ + if (tmpval == ACM_ERR_UNEXPECTED_EOF) \ + return ACM_EXPECTED_EOF; \ + return tmpval; \ + } \ + res = tmpval; \ + } while (0) + +/************************************************* + * Table filling + *************************************************/ +static const int map_1bit[] = { -1, +1 }; +static const int map_2bit_near[] = { -2, -1, +1, +2 }; +static const int map_2bit_far[] = { -3, -2, +2, +3 }; +static const int map_3bit[] = { -4, -3, -2, -1, +1, +2, +3, +4 }; +static int mul_3x3[3*3*3]; +static int mul_3x5[5*5*5]; +static int mul_2x11[11*11]; +static int tables_generated; + +static void generate_tables(void) +{ + int x1, x2, x3; + if (tables_generated) + return; + for (x3 = 0; x3 < 3; x3++) + for (x2 = 0; x2 < 3; x2++) + for (x1 = 0; x1 < 3; x1++) + mul_3x3[x1 + x2*3 + x3*3*3] = + x1 + (x2 << 4) + (x3 << 8); + for (x3 = 0; x3 < 5; x3++) + for (x2 = 0; x2 < 5; x2++) + for (x1 = 0; x1 < 5; x1++) + mul_3x5[x1 + x2*5 + x3*5*5] = + x1 + (x2 << 4) + (x3 << 8); + for (x2 = 0; x2 < 11; x2++) + for (x1 = 0; x1 < 11; x1++) + mul_2x11[x1 + x2*11] = x1 + (x2 << 4); + + tables_generated = 1; +} + +/* IOW: (r * acm->subblock_len) + c */ +#define set_pos(acm, r, c, idx) do { \ + unsigned _pos = ((r) << acm->info.acm_level) + (c); \ + acm->block[_pos] = acm->midbuf[idx]; \ + } while (0) + +/************ Fillers **********/ + +static int f_zero(ACMStream *acm, unsigned ind, unsigned col) +{ + unsigned i; + for (i = 0; i < acm->info.acm_rows; i++) + set_pos(acm, i, col, 0); + + return 1; +} + +static int f_bad(ACMStream *acm, unsigned ind, unsigned col) +{ + /* corrupt block? */ + return ACM_ERR_CORRUPT; +} + +static int f_linear(ACMStream *acm, unsigned ind, unsigned col) +{ + unsigned int i; + int b, middle = 1 << (ind - 1); + + for (i = 0; i < acm->info.acm_rows; i++) { + GET_BITS(b, acm, ind); + set_pos(acm, i, col, b - middle); + } + return 1; +} + +static int f_k13(ACMStream *acm, unsigned ind, unsigned col) +{ + unsigned i, b; + for (i = 0; i < acm->info.acm_rows; i++) { + GET_BITS(b, acm, 1); + if (b == 0) { + /* 0 */ + set_pos(acm, i++, col, 0); + if (i >= acm->info.acm_rows) + break; + set_pos(acm, i, col, 0); + continue; + } + GET_BITS(b, acm, 1); + if (b == 0) { + /* 1, 0 */ + set_pos(acm, i, col, 0); + continue; + } + /* 1, 1, ? */ + GET_BITS(b, acm, 1); + set_pos(acm, i, col, map_1bit[b]); + } + return 1; +} + +static int f_k12(ACMStream *acm, unsigned ind, unsigned col) +{ + unsigned i, b; + for (i = 0; i < acm->info.acm_rows; i++) { + GET_BITS(b, acm, 1); + if (b == 0) { + /* 0 */ + set_pos(acm, i, col, 0); + continue; + } + + /* 1, ? */ + GET_BITS(b, acm, 1); + set_pos(acm, i, col, map_1bit[b]); + } + return 1; +} + +static int f_k24(ACMStream *acm, unsigned ind, unsigned col) +{ + unsigned i, b; + for (i = 0; i < acm->info.acm_rows; i++) { + GET_BITS(b, acm, 1); + if (b == 0) { + /* 0 */ + set_pos(acm, i++, col, 0); + if (i >= acm->info.acm_rows) break; + set_pos(acm, i, col, 0); + continue; + } + + GET_BITS(b, acm, 1); + if (b == 0) { + /* 1, 0 */ + set_pos(acm, i, col, 0); + continue; + } + + /* 1, 1, ?, ? */ + GET_BITS(b, acm, 2); + set_pos(acm, i, col, map_2bit_near[b]); + } + return 1; +} + +static int f_k23(ACMStream *acm, unsigned ind, unsigned col) +{ + unsigned i, b; + for (i = 0; i < acm->info.acm_rows; i++) { + GET_BITS(b, acm, 1); + if (b == 0) { + /* 0 */ + set_pos(acm, i, col, 0); + continue; + } + + /* 1, ?, ? */ + GET_BITS(b, acm, 2); + set_pos(acm, i, col, map_2bit_near[b]); + } + return 1; +} + +static int f_k35(ACMStream *acm, unsigned ind, unsigned col) +{ + unsigned i, b; + for (i = 0; i < acm->info.acm_rows; i++) { + GET_BITS(b, acm, 1); + if (b == 0) { + /* 0 */ + set_pos(acm, i++, col, 0); + if (i >= acm->info.acm_rows) + break; + set_pos(acm, i, col, 0); + continue; + } + + GET_BITS(b, acm, 1); + if (b == 0) { + /* 1, 0 */ + set_pos(acm, i, col, 0); + continue; + } + + GET_BITS(b, acm, 1); + if (b == 0) { + /* 1, 1, 0, ? */ + GET_BITS(b, acm, 1); + set_pos(acm, i, col, map_1bit[b]); + continue; + } + + /* 1, 1, 1, ?, ? */ + GET_BITS(b, acm, 2); + set_pos(acm, i, col, map_2bit_far[b]); + } + return 1; +} + +static int f_k34(ACMStream *acm, unsigned ind, unsigned col) +{ + unsigned i, b; + for (i = 0; i < acm->info.acm_rows; i++) { + GET_BITS(b, acm, 1); + if (b == 0) { + /* 0 */ + set_pos(acm, i, col, 0); + continue; + } + + GET_BITS(b, acm, 1); + if (b == 0) { + /* 1, 0, ? */ + GET_BITS(b, acm, 1); + set_pos(acm, i, col, map_1bit[b]); + continue; + } + + /* 1, 1, ?, ? */ + GET_BITS(b, acm, 2); + set_pos(acm, i, col, map_2bit_far[b]); + } + return 1; +} + +static int f_k45(ACMStream *acm, unsigned ind, unsigned col) +{ + unsigned i, b; + for (i = 0; i < acm->info.acm_rows; i++) { + GET_BITS(b, acm, 1); + if (b == 0) { + /* 0 */ + set_pos(acm, i, col, 0); i++; + if (i >= acm->info.acm_rows) + break; + set_pos(acm, i, col, 0); + continue; + } + + GET_BITS(b, acm, 1); + if (b == 0) { + /* 1, 0 */ + set_pos(acm, i, col, 0); + continue; + } + + /* 1, 1, ?, ?, ? */ + GET_BITS(b, acm, 3); + set_pos(acm, i, col, map_3bit[b]); + } + return 1; +} + +static int f_k44(ACMStream *acm, unsigned ind, unsigned col) +{ + unsigned i, b; + for (i = 0; i < acm->info.acm_rows; i++) { + GET_BITS(b, acm, 1); + if (b == 0) { + /* 0 */ + set_pos(acm, i, col, 0); + continue; + } + + /* 1, ?, ?, ? */ + GET_BITS(b, acm, 3); + set_pos(acm, i, col, map_3bit[b]); + } + return 1; +} + +static int f_t15(ACMStream *acm, unsigned ind, unsigned col) +{ + unsigned i, b; + int n1, n2, n3; + for (i = 0; i < acm->info.acm_rows; i++) { + /* b = (x1) + (x2 * 3) + (x3 * 9) */ + GET_BITS(b, acm, 5); + + n1 = (mul_3x3[b] & 0x0F) - 1; + n2 = ((mul_3x3[b] >> 4) & 0x0F) - 1; + n3 = ((mul_3x3[b] >> 8) & 0x0F) - 1; + + set_pos(acm, i++, col, n1); + if (i >= acm->info.acm_rows) + break; + set_pos(acm, i++, col, n2); + if (i >= acm->info.acm_rows) + break; + set_pos(acm, i, col, n3); + } + return 1; +} + +static int f_t27(ACMStream *acm, unsigned ind, unsigned col) +{ + unsigned i, b; + int n1, n2, n3; + for (i = 0; i < acm->info.acm_rows; i++) { + /* b = (x1) + (x2 * 5) + (x3 * 25) */ + GET_BITS(b, acm, 7); + + n1 = (mul_3x5[b] & 0x0F) - 2; + n2 = ((mul_3x5[b] >> 4) & 0x0F) - 2; + n3 = ((mul_3x5[b] >> 8) & 0x0F) - 2; + + set_pos(acm, i++, col, n1); + if (i >= acm->info.acm_rows) + break; + set_pos(acm, i++, col, n2); + if (i >= acm->info.acm_rows) + break; + set_pos(acm, i, col, n3); + } + return 1; +} + +static int f_t37(ACMStream *acm, unsigned ind, unsigned col) +{ + unsigned i, b; + int n1, n2; + for (i = 0; i < acm->info.acm_rows; i++) { + /* b = (x1) + (x2 * 11) */ + GET_BITS(b, acm, 7); + + n1 = (mul_2x11[b] & 0x0F) - 5; + n2 = ((mul_2x11[b] >> 4) & 0x0F) - 5; + + set_pos(acm, i++, col, n1); + if (i >= acm->info.acm_rows) + break; + set_pos(acm, i, col, n2); + } + return 1; +} + +/****************/ + +static const filler_t filler_list[] = { + f_zero, f_bad, f_bad, f_linear, /* 0..3 */ + f_linear, f_linear, f_linear, f_linear, /* 4..7 */ + f_linear, f_linear, f_linear, f_linear, /* 8..11 */ + f_linear, f_linear, f_linear, f_linear, /* 12..15 */ + f_linear, f_k13, f_k12, f_t15, /* 16..19 */ + f_k24, f_k23, f_t27, f_k35, /* 20..23 */ + f_k34, f_bad, f_k45, f_k44, /* 24..27 */ + f_bad, f_t37, f_bad, f_bad /* 28..31 */ +}; + +static int fill_block(ACMStream *acm) +{ + unsigned i, ind; + int err; + for (i = 0; i < acm->info.acm_cols; i++) { + GET_BITS_EXPECT_EOF(ind, acm, 5); + err = filler_list[ind](acm, ind, i); + if (err < 0) + return err; + } + return 1; +} + +/********************************************** + * Decompress code + **********************************************/ + +static void juggle(int *wrap_p, int *block_p, unsigned sub_len, unsigned sub_count) +{ + unsigned int i, j; + int *p, r0, r1, r2, r3; + for (i = 0; i < sub_len; i++) { + p = block_p; + r0 = wrap_p[0]; + r1 = wrap_p[1]; + for (j = 0; j < sub_count/2; j++) { + r2 = *p; *p = r1*2 + (r0 + r2); p += sub_len; + r3 = *p; *p = r2*2 - (r1 + r3); p += sub_len; + r0 = r2; r1 = r3; + } + *wrap_p++ = r0; + *wrap_p++ = r1; + block_p++; + } +} + +static void juggle_block(ACMStream *acm) +{ + unsigned sub_count, sub_len, todo_count, step_subcount, i; + int *wrap_p, *block_p, *p; + + /* juggle only if subblock_len > 1 */ + if (acm->info.acm_level == 0) + return; + + /* 2048 / subblock_len */ + if (acm->info.acm_level > 9) + step_subcount = 1; + else + step_subcount = (2048 >> acm->info.acm_level) - 2; + + /* Apply juggle() (rows)x(cols) + * from (step_subcount * 2) x (subblock_len/2) + * to (step_subcount * subblock_len) x (1) + */ + todo_count = acm->info.acm_rows; + block_p = acm->block; + while (1) { + wrap_p = acm->wrapbuf; + sub_count = step_subcount; + if (sub_count > todo_count) + sub_count = todo_count; + + sub_len = acm->info.acm_cols / 2; + sub_count *= 2; + + juggle(wrap_p, block_p, sub_len, sub_count); + wrap_p += sub_len*2; + + for (i = 0, p = block_p; i < sub_count; i++) { + p[0]++; + p += sub_len; + } + + while (sub_len > 1) { + sub_len /= 2; + sub_count *= 2; + juggle(wrap_p, block_p, sub_len, sub_count); + wrap_p += sub_len*2; + } + if (todo_count <= step_subcount) + break; + todo_count -= step_subcount; + block_p += step_subcount << acm->info.acm_level; + } +} + +/***************************************************************/ +static int decode_block(ACMStream *acm) +{ + int pwr, count, val, i, x, err; + + acm->block_ready = 0; + acm->block_pos = 0; + + /* read header */ + GET_BITS_EXPECT_EOF(pwr, acm, 4); + GET_BITS_EXPECT_EOF(val, acm, 16); + + /* generate tables */ + count = 1 << pwr; + for (i = 0, x = 0; i < count; i++) { + acm->midbuf[i] = x; + x += val; + } + for (i = 1, x = -val; i <= count; i++) { + acm->midbuf[-i] = x; + x -= val; + } + + /* to_check? */ + if ((err = fill_block(acm)) <= 0) + return err; + + juggle_block(acm); + + acm->block_ready = 1; + + return 1; +} + +/****************************** + * Output formats + ******************************/ + +static unsigned char *out_s16le(int *src, unsigned char *dst, unsigned n, unsigned shift) +{ + while (n--) { + int val = *src++ >> shift; + *dst++ = val & 0xFF; + *dst++ = (val >> 8) & 0xFF; + } + return dst; +} + +static unsigned char *out_s16be(int *src, unsigned char *dst, unsigned n, unsigned shift) +{ + while (n--) { + int val = *src++ >> shift; + *dst++ = (val >> 8) & 0xFF; + *dst++ = val & 0xFF; + } + return dst; +} + +static unsigned char *out_u16le(int *src, unsigned char *dst, unsigned n, unsigned shift) +{ + while (n--) { + int val = (*src++ >> shift) + 0x8000; + *dst++ = val & 0xFF; + *dst++ = (val >> 8) & 0xFF; + } + return dst; +} + +static unsigned char *out_u16be(int *src, unsigned char *dst, unsigned n, unsigned shift) +{ + while (n--) { + int val = (*src++ >> shift) + 0x8000; + *dst++ = (val >> 8) & 0xFF; + *dst++ = val & 0xFF; + } + return dst; +} + +static int output_values(int *src, unsigned char *dst, int n, + int acm_level, int bigendianp, int wordlen, int sgned) +{ + unsigned char *res = NULL; + if (wordlen == 2) { + if (bigendianp == 0) { + if (sgned) + res = out_s16le(src, dst, n, acm_level); + else + res = out_u16le(src, dst, n, acm_level); + } else { + if (sgned) + res = out_s16be(src, dst, n, acm_level); + else + res = out_u16be(src, dst, n, acm_level); + } + } + if (res != NULL) + return res - dst; + return ACM_ERR_BADFMT; +} + +/* + * WAVC (compressed WAV) files are ACM files with additional header. + * + * 'WAVC' + 'V1.00' + uncompr(4b) + compr(4b) + 12b + */ + +#define WAVC_ID 0x564157 /* 'WAV' */ + +static int read_wavc_header(ACMStream *acm) +{ + static const unsigned short expect[12] = { + /* 'V1.0', raw_size, acm_size */ + 0x3156, 0x302E, 0,0, 0,0, + /* hdrlen?, chans?, bits?, hz */ + 28,0, 1, 16, 22050, 0 + }; + unsigned short i, buf[12]; + + for (i = 0; i < 12; i++) + GET_BITS(buf[i], acm, 16); + if (memcmp(buf, expect, 4) != 0) + return -1; + /* full comparision is too strict */ + if (0 && memcmp(buf + 6, expect + 6, 12) != 0) + return -1; + /* just make sure the magic 28 is there */ + if (expect[6] != buf[6]) + return -1; + + acm->wavc_file = 1; + return 0; +} + +static int read_header(ACMStream *acm) +{ + unsigned int tmp; + + /* read header */ + + GET_BITS(tmp, acm, 24); + if (tmp == WAVC_ID) { + GET_BITS(tmp, acm, 8); + if (tmp != 'C') + return ACM_ERR_NOT_ACM; + if (read_wavc_header(acm) < 0) + return ACM_ERR_NOT_ACM; + GET_BITS(tmp, acm, 24); + } + if (tmp != ACM_ID) + return ACM_ERR_NOT_ACM; + acm->info.acm_id = tmp; + + GET_BITS(acm->info.acm_version, acm, 8); + if (acm->info.acm_version != 1) + return ACM_ERR_NOT_ACM; + GET_BITS(acm->total_values, acm, 16); + GET_BITS(tmp, acm, 16); + acm->total_values += tmp << 16; + if (acm->total_values == 0) + return ACM_ERR_NOT_ACM; + GET_BITS(acm->info.channels, acm, 16); + if (acm->info.channels < 1 || acm->info.channels > 2) + return ACM_ERR_NOT_ACM; + acm->info.acm_channels = acm->info.channels; + GET_BITS(acm->info.rate, acm, 16); + if (acm->info.rate < 4096) + return ACM_ERR_NOT_ACM; + + GET_BITS(acm->info.acm_level, acm, 4); + GET_BITS(acm->info.acm_rows, acm, 12); + if (!acm->info.acm_rows) + return ACM_ERR_NOT_ACM; + return 0; +} + +/*********************************************** + * Public functions + ***********************************************/ + +int acm_open_decoder(ACMStream **res, void *arg, acm_io_callbacks io_cb, int force_chans) +{ + int err = ACM_ERR_OTHER; + ACMStream *acm; + + acm = malloc(sizeof(*acm)); + if (!acm) + return err; + memset(acm, 0, sizeof(*acm)); + + acm->io_arg = arg; + acm->io = io_cb; + + if (acm->io.get_length_func) { + acm->data_len = acm->io.get_length_func(acm->io_arg); + } else { + acm->data_len = 0; + } + + acm->buf_max = ACM_BUFLEN; + acm->buf = malloc(acm->buf_max); + if (!acm->buf) + goto err_out; + + /* read header data */ + err = ACM_ERR_NOT_ACM; + if (read_header(acm) < 0) + goto err_out; + + /* + * Overwrite channel info if requested, otherwise + * ignore the channel count on plain ACM files, + * it is frequently wrong, and actual 1-channel + * files are not interising to listen to anyway (samples). + * + * Trust WAVC files, as they seem to be correct? + */ + if (force_chans > 0) + acm->info.channels = force_chans; + else if (!acm->wavc_file && acm->info.channels < 2) + acm->info.channels = 2; + + /* calculate blocks */ + acm->info.acm_cols = 1 << acm->info.acm_level; + acm->wrapbuf_len = 2 * acm->info.acm_cols - 2; + acm->block_len = acm->info.acm_rows * acm->info.acm_cols; + + /* allocate */ + acm->block = malloc(acm->block_len * sizeof(int)); + acm->wrapbuf = malloc(acm->wrapbuf_len * sizeof(int)); + acm->ampbuf = malloc(0x10000 * sizeof(int)); + acm->midbuf = acm->ampbuf + 0x8000; + + memset(acm->wrapbuf, 0, acm->wrapbuf_len * sizeof(int)); + + generate_tables(); + + *res = acm; + return ACM_OK; + +err_out: + /* disable callbacks */ + memset(&acm->io, 0, sizeof(acm->io)); + acm->io_arg = NULL; + + acm_close(acm); + return err; +} + +int acm_read(ACMStream *acm, void *dst, unsigned numbytes, + int bigendianp, int wordlen, int sgned) +{ + int avail, gotbytes = 0, err; + int *src, numwords; + + if (wordlen == 2) + numwords = numbytes / 2; + else + return ACM_ERR_BADFMT; + + if (acm->stream_pos >= acm->total_values) + return 0; + + if (!acm->block_ready) { + err = decode_block(acm); + if (err == ACM_EXPECTED_EOF) + return 0; + if (err < 0) + return err; + } + + /* check how many words can be read */ + avail = acm->block_len - acm->block_pos; + if (avail < numwords) + numwords = avail; + + if (acm->stream_pos + numwords > acm->total_values) + numwords = acm->total_values - acm->stream_pos; + + if (acm->info.channels > 1) + numwords -= numwords % acm->info.channels; + + /* convert, but if dst == NULL, simulate */ + if (dst != NULL) { + src = acm->block + acm->block_pos; + gotbytes = output_values(src, dst, numwords, + acm->info.acm_level, + bigendianp, wordlen, sgned); + } else + gotbytes = numwords * wordlen; + + if (gotbytes >= 0) { + acm->stream_pos += numwords; + acm->block_pos += numwords; + if (acm->block_pos == acm->block_len) + acm->block_ready = 0; + } + + return gotbytes; +} + +void acm_close(ACMStream *acm) +{ + if (acm == NULL) + return; + if (acm->io.close_func) + acm->io.close_func(acm->io_arg); + if (acm->buf) + free(acm->buf); + if (acm->block) + free(acm->block); + if (acm->wrapbuf) + free(acm->wrapbuf); + if (acm->ampbuf) + free(acm->ampbuf); + free(acm); +} + diff --git a/libacm/libacm.h b/libacm/libacm.h new file mode 100644 index 00000000..96a9d782 --- /dev/null +++ b/libacm/libacm.h @@ -0,0 +1,117 @@ +/* + * libacm - Interplay ACM audio decoder. + * + * Copyright (c) 2004-2010, Marko Kreen + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __LIBACM_H +#define __LIBACM_H + +#define LIBACM_VERSION "1.3" + +#define ACM_ID 0x032897 +#define ACM_WORD 2 + +#define ACM_OK 0 +#define ACM_ERR_OTHER -1 +#define ACM_ERR_OPEN -2 +#define ACM_ERR_NOT_ACM -3 +#define ACM_ERR_READ_ERR -4 +#define ACM_ERR_BADFMT -5 +#define ACM_ERR_CORRUPT -6 +#define ACM_ERR_UNEXPECTED_EOF -7 +#define ACM_ERR_NOT_SEEKABLE -8 + +typedef struct ACMInfo { + unsigned channels; + unsigned rate; + unsigned acm_id; + unsigned acm_version; + unsigned acm_channels; /* channels from header (usually wrong) */ + unsigned acm_level; + unsigned acm_cols; /* 1 << acm_level */ + unsigned acm_rows; +} ACMInfo; + +typedef struct { + /* read bytes */ + int (*read_func)(void *ptr, int size, int n, void *datasrc); + /* optional, must support seeking into start*/ + int (*seek_func)(void *datasrc, int offset, int whence); + /* optional, called on acm_close */ + int (*close_func)(void *datasrc); + /* returns size in bytes*/ + int (*get_length_func)(void *datasrc); +} acm_io_callbacks; + +struct ACMStream { + ACMInfo info; + unsigned total_values; + + /* acm data stream */ + void *io_arg; + acm_io_callbacks io; + unsigned data_len; + + /* acm stream buffer */ + unsigned char *buf; + unsigned buf_max, buf_size, buf_pos, bit_avail; + unsigned bit_data; + unsigned buf_start_ofs; + + /* block lengths (in samples) */ + unsigned block_len; + unsigned wrapbuf_len; + /* buffers */ + int *block; + int *wrapbuf; + int *ampbuf; + int *midbuf; /* pointer into ampbuf */ + /* result */ + unsigned block_ready:1; + unsigned file_eof:1; + unsigned wavc_file:1; + unsigned stream_pos; /* in words. absolute */ + unsigned block_pos; /* in words, relative */ +}; +typedef struct ACMStream ACMStream; + +/* decode.c */ +int acm_open_decoder(ACMStream **res, void *io_arg, acm_io_callbacks io, int force_chans); +int acm_read(ACMStream *acm, void *buf, unsigned nbytes, + int bigendianp, int wordlen, int sgned); +void acm_close(ACMStream *acm); + +/* util.c */ +int acm_open_file(ACMStream **acm, const char *filename, int force_chans); +const ACMInfo *acm_info(ACMStream *acm); +int acm_seekable(ACMStream *acm); +unsigned acm_bitrate(ACMStream *acm); +unsigned acm_rate(ACMStream *acm); +unsigned acm_channels(ACMStream *acm); +unsigned acm_raw_total(ACMStream *acm); +unsigned acm_raw_tell(ACMStream *acm); +unsigned acm_pcm_total(ACMStream *acm); +unsigned acm_pcm_tell(ACMStream *acm); +unsigned acm_time_total(ACMStream *acm); +unsigned acm_time_tell(ACMStream *acm); +int acm_read_loop(ACMStream *acm, void *dst, unsigned len, + int bigendianp, int wordlen, int sgned); +int acm_seek_pcm(ACMStream *acm, unsigned pcm_pos); +int acm_seek_time(ACMStream *acm, unsigned pos_ms); +const char *acm_strerror(int err); + +#endif + diff --git a/libacm/util.c b/libacm/util.c new file mode 100644 index 00000000..57e91743 --- /dev/null +++ b/libacm/util.c @@ -0,0 +1,278 @@ +/* + * Utility functions for libacm. + * + * Copyright (c) 2004-2010, Marko Kreen + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "libacm.h" + +#define WAVC_HEADER_LEN 28 +#define ACM_HEADER_LEN 14 + +/* + * error strings + */ +static const char *_errlist[] = { + "No error", + "ACM error", + "Cannot open file", + "Not an ACM file", + "Read error", + "Bad format", + "Corrupt file", + "Unexcpected EOF", + "Stream not seekable" +}; + +const char *acm_strerror(int err) +{ + int nerr = sizeof(_errlist) / sizeof(char *); + if ((-err) < 0 || (-err) >= nerr) + return "Unknown error"; + return _errlist[-err]; +} + +/* + * File IO using stdio + */ + +static int _read_file(void *ptr, int size, int n, void *arg) +{ + FILE *f = (FILE *)arg; + return fread(ptr, size, n, f); +} + +static int _close_file(void *arg) +{ + FILE *f = (FILE *)arg; + return fclose(f); +} + +static int _seek_file(void *arg, int offset, int whence) +{ + FILE *f = (FILE *)arg; + return fseek(f, offset, whence); +} + +static int _get_length_file(void *arg) +{ + FILE *f = (FILE *)arg; + int res, pos, len = -1; + + pos = ftell(f); + if (pos < 0) + return -1; + + res = fseek(f, 0, SEEK_END); + if (res >= 0) { + len = ftell(f); + fseek(f, pos, SEEK_SET); + } + return len; +} + +int acm_open_file(ACMStream **res, const char *filename, int force_chans) +{ + int err; + FILE *f; + acm_io_callbacks io; + ACMStream *acm; + + if ((f = fopen(filename, "rb")) == NULL) + return ACM_ERR_OPEN; + + memset(&io, 0, sizeof(io)); + io.read_func = _read_file; + io.seek_func = _seek_file; + io.close_func = _close_file; + io.get_length_func = _get_length_file; + + if ((err = acm_open_decoder(&acm, f, io, force_chans)) < 0) { + fclose(f); + return err; + } + *res = acm; + return 0; +} + +/* + * utility functions + */ + +static unsigned pcm2time(ACMStream *acm, unsigned long long pcm) +{ + return pcm * 1000 / acm->info.rate; + /* return ((10 * pcm) / acm->info.rate) * 100; */ +} + +static unsigned time2pcm(ACMStream *acm, unsigned long long time_ms) +{ + return time_ms * acm->info.rate / 1000; + /* return (time_ms / 100) * (acm->info.rate / 10); */ +} + +/* + * info functions + */ + +const ACMInfo *acm_info(ACMStream *acm) +{ + return &acm->info; +} + +unsigned acm_rate(ACMStream *acm) +{ + return acm->info.rate; +} + +unsigned acm_channels(ACMStream *acm) +{ + return acm->info.channels; +} + +int acm_seekable(ACMStream *acm) +{ + return acm->data_len > 0; +} + +unsigned acm_bitrate(ACMStream *acm) +{ + unsigned long long bits, time, bitrate = 0; + + if (acm_raw_total(acm) == 0) + return 13000; + + time = acm_time_total(acm); + if (time > 0) { + bits = 8 * acm_raw_total(acm); + bitrate = 1000 * bits / time; + } + return bitrate; +} + +unsigned acm_pcm_tell(ACMStream *acm) +{ + return acm->stream_pos / acm->info.channels; +} + +unsigned acm_pcm_total(ACMStream *acm) +{ + return acm->total_values / acm->info.channels; +} + +unsigned acm_time_tell(ACMStream *acm) +{ + return pcm2time(acm, acm_pcm_tell(acm)); +} + +unsigned acm_time_total(ACMStream *acm) +{ + return pcm2time(acm, acm_pcm_total(acm)); +} + +unsigned acm_raw_tell(ACMStream *acm) +{ + return acm->buf_start_ofs + acm->buf_pos; +} + +unsigned acm_raw_total(ACMStream *acm) +{ + return acm->data_len; +} + +/* + * seeking + */ + +int acm_seek_time(ACMStream *acm, unsigned time_ms) +{ + int res = acm_seek_pcm(acm, time2pcm(acm, time_ms)); + if (res <= 0) + return res; + return pcm2time(acm, res); +} + +int acm_seek_pcm(ACMStream *acm, unsigned pcm_pos) +{ + unsigned word_pos = pcm_pos * acm->info.channels; + unsigned start_ofs; + + if (word_pos < acm->stream_pos) { + if (acm->io.seek_func == NULL) + return ACM_ERR_NOT_SEEKABLE; + + start_ofs = ACM_HEADER_LEN; + if (acm->wavc_file) + start_ofs += WAVC_HEADER_LEN; + + if (acm->io.seek_func(acm->io_arg, start_ofs, SEEK_SET) < 0) + return ACM_ERR_NOT_SEEKABLE; + + acm->file_eof = 0; + acm->buf_pos = 0; + acm->buf_size = 0; + acm->bit_avail = 0; + acm->bit_data = 0; + + acm->stream_pos = 0; + acm->block_pos = 0; + acm->block_ready = 0; + acm->buf_start_ofs = ACM_HEADER_LEN; + + memset(acm->wrapbuf, 0, acm->wrapbuf_len * sizeof(int)); + } + while (acm->stream_pos < word_pos) { + int step = 2048, res; + if (acm->stream_pos + step > word_pos) + step = word_pos - acm->stream_pos; + + res = acm_read(acm, NULL, step*2, 0,2,1); + if (res < 1) + break; + } + return acm->stream_pos / acm->info.channels; +} + +/* + * read loop - full block reading + */ +int acm_read_loop(ACMStream *acm, void *dst, unsigned bytes, + int bigendianp, int wordlen, int sgned) +{ + unsigned char *dstp = dst; + int res, got = 0; + while (bytes > 0) { + res = acm_read(acm, dstp, bytes, bigendianp, wordlen, sgned); + if (res > 0) { + if (dstp) + dstp += res; + got += res; + bytes -= res; + } else { + if (res < 0 && got == 0) + return res; + break; + } + } + return got; +} +