|
|
nono 1.6.0
//
// nono
// Copyright (C) 2025 nono project
// Licensed under nono-license.txt
//
// ADPCM <-> PCM Converter
#include "header.h"
#include <vector>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
//--- ここから adpcm.h 相当 ---
class ADPCMDevice
{
public:
uint32 pcm2adpcm_step(int16 pcm);
int16 adpcm2pcm_step(uint32 data);
private:
inline uint32 adpcm_encode(int32 dn);
inline int32 adpcm_decode(uint32 data, int32 xn);
inline void adpcm_calcstep(uint32 data);
int32 xprev {};
int stepidx {};
static const int adpcm_stepadj[16];
static const int32 adpcm_stepsize[49];
};
//--- ここまで adpcm.h 相当 ---
//--- ここから adpcm.cpp 相当 ---
inline uint32
ADPCMDevice::adpcm_encode(int32 dn)
{
uint32 Ln = 0;
int ss = adpcm_stepsize[stepidx];
if (dn < 0) {
Ln = 0x8;
dn = -dn;
}
if (dn >= ss) {
Ln |= 0x4;
dn -= ss;
}
ss /= 2;
if (dn >= ss) {
Ln |= 0x2;
dn -= ss;
}
ss /= 2;
if (dn >= ss) {
Ln |= 0x1;
}
return Ln;
}
inline int32
ADPCMDevice::adpcm_decode(uint32 data, int32 xn)
{
int b3 = (data & 8) ? 1 : 0;
int b2 = (data & 4) ? 1 : 0;
int b1 = (data & 2) ? 1 : 0;
int b0 = (data & 1);
int32 ss = adpcm_stepsize[stepidx];
int32 dn = (ss * b2) + (ss / 2 * b1) + (ss / 4 * b0) + ss / 8;
if (b3) {
dn = -dn;
}
xn += dn;
// Saturate in 12 bits.
if (__predict_false(xn > 2047)) {
xn = 2047;
} else if (__predict_false(xn < -2047)) {
xn = -2047;
}
return xn;
}
inline void
ADPCMDevice::adpcm_calcstep(uint32 data)
{
stepidx += adpcm_stepadj[data];
if (__predict_false(stepidx < 0)) {
stepidx = 0;
} else if (__predict_false(stepidx > 48)) {
stepidx = 48;
}
}
uint32
ADPCMDevice::pcm2adpcm_step(int16 pcm)
{
// Input of this algorithm is 12 bit.
int32 xn = pcm / 16;
int32 dn = xn - xprev;
uint32 Ln = adpcm_encode(dn);
// next はこの Ln をデコードして PCM にした値
xprev = adpcm_decode(Ln, xprev);
adpcm_calcstep(Ln);
return Ln;
}
int16
ADPCMDevice::adpcm2pcm_step(uint32 data)
{
int32 xn = adpcm_decode(data, xprev);
// next
xprev = xn;
adpcm_calcstep(data);
// このアルゴリズムの出力は(下詰めで) 12 bit。
// ただし MSM6258 の出力 DAC は 10 bit 分しかない。
xn /= 4;
// その 10 bit を 16 bit PCM にする。
xn *= 64;
return xn;
}
/*static*/ const int
ADPCMDevice::adpcm_stepadj[16] = {
-1, -1, -1, -1, 2, 4, 6, 8,
-1, -1, -1, -1, 2, 4, 6, 8,
};
/*static*/ const int32
ADPCMDevice::adpcm_stepsize[49] = {
16, 17, 19, 21, 23, 25, 28, 31, 34, 37,
41, 45, 50, 55, 60, 66, 73, 80, 88, 97,
107, 118, 130, 143, 157, 173, 190, 209, 230, 253,
279, 307, 337, 371, 408, 449, 494, 544, 598, 658,
724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552,
};
//--- ここまで adpcm.cpp 相当 ---
#define RIFF(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
#define VERBOSE(fmt...) do { if (verbose) printf(fmt); } while (0)
struct chunk_header {
uint32 chunktype;
uint32 chunksize;
};
struct fmt_chunk {
uint16 fmtid;
uint16 nchannels;
uint32 frequency;
uint32 byterate;
uint16 framesize;
uint16 bitdepth;
} __packed;
static void usage();
static bool is_wavfile(const char *);
static void encode_file(const char *, const char *);
static void read_fmt_chunk(int ifd, const char *infile);
static void read_data_chunk(int ifd, int ofd, uint32 datasize,
const char *infile, const char *outfile);
static void decode_file(const char *, const char *);
static const char wav_magic[12] = {
'R', 'I', 'F', 'F', 0, 0, 0, 0, 'W', 'A', 'V', 'E'
};
static const int freqtable[] = {
0,
3906,
5208,
7812,
10416,
15625,
};
static ADPCMDevice *dev;
static int freq;
static bool verbose;
int
main(int ac, char *av[])
{
enum {
CMD_AUTO,
CMD_ENCODE,
CMD_DECODE,
} cmd {};
int c;
int f = 0;
while ((c = getopt(ac, av, "def:v")) != -1) {
switch (c) {
case 'd':
cmd = CMD_DECODE;
break;
case 'e':
cmd = CMD_ENCODE;
break;
case 'f':
{
f = atoi(optarg);
if (f < 0 || f > countof(freqtable)) {
usage();
}
break;
}
case 'v':
verbose = true;
break;
default:
usage();
}
}
ac -= optind;
av += optind;
if (ac < 2) {
usage();
}
const char *infile = av[0];
const char *outfile = av[1];
if (cmd == CMD_AUTO) {
if (is_wavfile(infile)) {
cmd = CMD_ENCODE;
VERBOSE("Input file looks WAV\n");
} else {
cmd = CMD_DECODE;
VERBOSE("Input file looks ADPCM\n");
}
}
assert(cmd == CMD_ENCODE || cmd == CMD_DECODE);
dev = new ADPCMDevice();
if (cmd == CMD_DECODE) {
// ADPCM -> PCM
if (f == 0) {
f = 5;
}
freq = freqtable[f];
decode_file(infile, outfile);
} else {
// PCM -> ADPCM
encode_file(infile, outfile);
}
return 0;
}
static void
usage()
{
printf("%s [-e] <PCMfile> <out.adpcm>\n", getprogname());
printf("%s [-d] [-f<n> ] <ADPCMfile> <out.PCM>\n", getprogname());
printf(" -f1: 3.9 kHz, -f2: 5.2 kHz, -f3: 7.8 kHz,");
printf(" -f4: 10.4 kHz, -f5: 15.6 kHz\n");
exit(1);
}
// 入力ファイルが .WAV ファイルっぽければ true を返す。
static bool
is_wavfile(const char *infile)
{
char buf[12];
int fd;
fd = open(infile, O_RDONLY);
if (fd < 0) {
err(1, "%s: open %s", __func__, infile);
}
auto n = read(fd, buf, sizeof(buf));
close(fd);
if (n < 0) {
err(1, "%s: read %s", __func__, infile);
}
if (n < sizeof(buf)) {
return false;
}
memset(buf + 4, 0, 4);
if (memcmp(buf, wav_magic, sizeof(wav_magic)) != 0) {
return false;
}
return true;
}
// PCM -> ADPCM。
// 入力の WAV ファイルは 16bit, 1チャンネル固定。
// サンプリングレートは気にせずデータを列挙するだけ。
static void
encode_file(const char *infile, const char *outfile)
{
char buf[12];
ssize_t n;
int ifd = open(infile, O_RDONLY);
if (ifd < 0) {
err(1, "%s: open", infile);
}
int ofd = creat(outfile, 0644);
if (ofd < 0) {
err(1, "%s: creat", outfile);
}
// RIFF ヘッダ。
n = read(ifd, buf, sizeof(wav_magic));
if (n < 0) {
err(1, "%s: reading header", infile);
}
if (n < sizeof(wav_magic)) {
errx(1, "%s: file too short", infile);
}
if (memcmp(buf + 0, wav_magic + 0, 4) != 0 ||
memcmp(buf + 8, wav_magic + 8, 4) != 0) {
errx(1, "%s: Invalid WAV magic", infile);
}
// チャンクが続く。
for (bool done = false; done == false; ) {
n = read(ifd, buf, sizeof(chunk_header));
if (n < 0) {
err(1, "%s: reading chunk header", infile);
}
if (n == 0) {
break;
}
if (n < sizeof(chunk_header)) {
errx(1, "%s: file too short", infile);
}
const chunk_header *ch = (const chunk_header *)&buf[0];
uint32 chunktype = be32toh(ch->chunktype);
uint32 chunksize = le32toh(ch->chunksize);
uint32 nextpos = lseek(ifd, 0, SEEK_CUR);
nextpos += chunksize;
VERBOSE("Chunk '%c%c%c%c' len=%u\n",
(chunktype >> 24),
(chunktype >> 16) & 0xff,
(chunktype >> 8) & 0xff,
(chunktype & 0xff),
chunksize);
switch (chunktype) {
case RIFF('f', 'm', 't', ' '):
read_fmt_chunk(ifd, infile);
break;
case RIFF('d', 'a', 't', 'a'):
read_data_chunk(ifd, ofd, chunksize, infile, outfile);
// data チャンク処理したら後は無視して終わる?
done = true;
break;
default:
// 知らないチャンクは読み飛ばす。
break;
}
lseek(ifd, nextpos, SEEK_SET);
}
close(ifd);
close(ofd);
}
// ifd から fmt チャンクを読み込んで、パラメータをチェックするだけ。
static void
read_fmt_chunk(int ifd, const char *infile)
{
struct fmt_chunk fmt;
auto n = read(ifd, &fmt, sizeof(fmt));
if (n < 0) {
err(1, "%s: read fmt chunk", infile);
}
if (n < sizeof(fmt)) {
errx(1, "%s: file too short", infile);
}
// 16bit, 1channel, PCM のみサポート。
uint32 format = le16toh(fmt.fmtid);
uint32 nchannels = le16toh(fmt.nchannels);
uint32 bitdepth = le16toh(fmt.bitdepth);
if (format != 1/*PCM*/) {
errx(1, "%s: Unknown WAV format 0x%x", infile, format);
}
if (nchannels != 1) {
errx(1, "%s: Only monaural is supported", infile);
}
if (bitdepth != 16) {
errx(1, "%s: Only 16 bit is supported", infile);
}
}
// ifd から data チャンクを読み込んで ADPCM にして ofd に書き出す。
static void
read_data_chunk(int ifd, int ofd, uint32 datasize,
const char *infile, const char *outfile)
{
uint32 srcsize;
uint32 dstsize;
// 奇数バイトのはずはないけど、一応。
srcsize = datasize;
if ((srcsize & 1U) != 0) {
srcsize += 1;
VERBOSE("srcsize ODD! %u\n", srcsize);
}
VERBOSE("srcsize = %u\n", srcsize);
// 奇数サンプルなら dst は偶数サンプルになるよう切り上げる。
dstsize = srcsize;
if ((dstsize & 2U) != 0) {
dstsize += 2;
VERBOSE("dst sample count ODD! %u\n", dstsize);
}
dstsize /= 4;
VERBOSE("dstsize = %u [bytes]\n", dstsize);
std::vector<uint8> src;
std::vector<uint8> dst;
try {
src.resize(srcsize);
dst.resize(dstsize);
} catch (...) {
errno = ENOMEM;
err(1, "data chunk %u", datasize);
}
auto n = read(ifd, src.data(), datasize);
if (n < 0) {
err(1, "%s: read data", infile);
}
if (n < src.size()) {
errx(1, "%s: file too short", infile);
}
// 2サンプルずつ処理できる。
const int16 *s = (const int16 *)src.data();
uint8 *d = dst.data();
for (const uint8 *end = d + dstsize; d < end; ) {
uint32 v0 = dev->pcm2adpcm_step(*s++);
uint32 v1 = dev->pcm2adpcm_step(*s++);
*d++ = v0 | (v1 << 4);
}
write(ofd, dst.data(), dst.size());
}
// ADPCM -> PCM
static void
decode_file(const char *infile, const char *outfile)
{
struct stat st;
std::vector<uint8> src;
std::vector<int16> dst;
uint8 wavhdr[12];
chunk_header hdr;
fmt_chunk fmt;
ssize_t n;
int ifd = open(infile, O_RDONLY);
if (ifd < 0) {
err(1, "%s: open", infile);
}
if (fstat(ifd, &st) < 0) {
err(1, "%s: fstat", infile);
}
// 一気に確保。
try {
src.resize(st.st_size);
dst.resize(st.st_size * 2);
} catch (...) {
errno = ENOMEM;
err(1, "buf(%zd)", st.st_size);
}
// 出力データ部のバイト数。
size_t databytes = dst.size() * sizeof(int16);
// 全体を読み込む。
n = read(ifd, src.data(), src.size());
if (n < 0) {
err(1, "%s: read data", infile);
}
if (n < src.size()) {
errx(1, "%s: file too short", infile);
}
// 出力ファイル。
int ofd = creat(outfile, 0644);
if (ofd < 0) {
err(1, "%s: creat", outfile);
}
// WAV ヘッダ。
memcpy(wavhdr, wav_magic, sizeof(wav_magic));
*(uint32 *)(wavhdr + 4) = htole32(
sizeof(wavhdr) +
sizeof(hdr) + sizeof(fmt) +
sizeof(hdr) + databytes);
n = write(ofd, wavhdr, sizeof(wavhdr));
if (n < 0) {
err(1, "%s: write", outfile);
}
// fmt チャンク。
hdr.chunktype = htobe32(RIFF('f', 'm', 't', ' '));
hdr.chunksize = htole32(16);
n = write(ofd, &hdr, sizeof(hdr));
if (n < 0) {
err(1, "%s: write", outfile);
}
fmt.fmtid = htole16(1); // PCM
fmt.nchannels = htole16(1);
fmt.frequency = htole32(freq);
fmt.byterate = htole32(freq * 1 * (16 / 8));
fmt.framesize = htole16(1 * (16 / 8));
fmt.bitdepth = htole16(16);
n = write(ofd, &fmt, sizeof(fmt));
if (n < 0) {
err(1, "%s: write", outfile);
}
// data チャンクのヘッダ部。
hdr.chunktype = htobe32(RIFF('d', 'a', 't', 'a'));
hdr.chunksize = databytes;
n = write(ofd, &hdr, sizeof(hdr));
if (n < 0) {
err(1, "%s: write", outfile);
}
const uint8 *s = src.data();
int16 *d = dst.data();
for (const uint8 *end = s + src.size(); s < end; ) {
uint8 data = *s++;
int16 pcm0 = dev->adpcm2pcm_step(data & 0x0f);
int16 pcm1 = dev->adpcm2pcm_step(data >> 4);
*d++ = pcm0;
*d++ = pcm1;
}
write(ofd, dst.data(), dst.size() * sizeof(int16));
close(ifd);
close(ofd);
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.