mirror of
https://github.com/kevinbentley/Descent3.git
synced 2025-01-22 11:28:56 +00:00
Import source of libacm 1.3, integrate it into CMake build
only the sourcefiles needed here, from https://github.com/markokr/libacm
This commit is contained in:
parent
f61f34971d
commit
f6f3c334de
@ -1,2 +1,5 @@
|
|||||||
# 3rd party libs
|
# 3rd party libs
|
||||||
lib/debugbreak.h
|
lib/debugbreak.h
|
||||||
|
libacm/libacm.h
|
||||||
|
libacm/decode.c
|
||||||
|
libacm/util.c
|
||||||
|
@ -3,4 +3,10 @@ set(CPPS
|
|||||||
aencode.cpp
|
aencode.cpp
|
||||||
libacm.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})
|
||||||
|
912
libacm/decode.c
Normal file
912
libacm/decode.c
Normal file
@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
117
libacm/libacm.h
Normal file
117
libacm/libacm.h
Normal file
@ -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
|
||||||
|
|
278
libacm/util.c
Normal file
278
libacm/util.c
Normal file
@ -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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user