hxtools/smm/pcmdiff.c

215 lines
5.1 KiB
C

// SPDX-License-Identifier: MIT
/*
* Deltify two PCM streams
* used to compare artifacts of audio codecs
* written by Jan Engelhardt, 2008-2010
*/
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libHX/init.h>
#include <libHX/option.h>
struct fdstream {
int fd;
struct stat sb;
void *area;
unsigned long offset;
union {
const void *vptr;
const int16_t *ptr;
};
};
static struct fdstream gfile[3];
static unsigned int invert;
static unsigned long max = 0;
static unsigned int mono_mix;
static double volume = 1.0;
static bool pcmdiff_get_options(int *argc, const char ***argv)
{
struct HXoption options_table[] = {
{.sh = 'a', .type = HXTYPE_ULONG, .ptr = &gfile[0].offset,
.help = "Offset in first file", .htyp = "BYTES"},
{.sh = 'b', .type = HXTYPE_ULONG, .ptr = &gfile[1].offset,
.help = "Offset in second file", .htyp = "BYTES"},
{.sh = 'i', .type = HXTYPE_NONE, .ptr = &invert,
.help = "Invert"},
{.sh = 'm', .type = HXTYPE_ULONG, .ptr = &max,
.help = "maximum seconds"},
{.sh = 'M', .type = HXTYPE_NONE, .ptr = &mono_mix,
.help = "mono mixer"},
{.sh = 'q', .type = HXTYPE_DOUBLE, .ptr = &volume,
.help = "Volume multiplier"},
HXOPT_AUTOHELP,
HXOPT_TABLEEND,
};
if (HX_getopt(options_table, argc, argv, HXOPT_USAGEONERR) !=
HXOPT_ERR_SUCCESS)
return false;
max *= 44100*4;
if (max == 0)
max = -1;
return true;
}
static bool pcmdiff_open_streams(int argc, const char **argv,
struct fdstream *file)
{
if ((file[0].fd = open(argv[1], O_RDONLY)) < 0) {
fprintf(stderr, "open %s failed: %s\n", argv[1], strerror(errno));
return false;
}
if ((file[1].fd = open(argv[2], O_RDONLY)) < 0) {
fprintf(stderr, "open %s failed: %s\n", argv[2], strerror(errno));
return false;
}
if (argc == 3) {
file[2].fd = STDOUT_FILENO;
} else if ((file[2].fd = open(argv[3], O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) {
fprintf(stderr, "open %s failed: %s\n", argv[3], strerror(errno));
return false;
}
if (fstat(file[0].fd, &file[0].sb) < 0 ||
fstat(file[1].fd, &file[1].sb) < 0) {
perror("fstat");
abort();
}
file[0].area = mmap(NULL, file[0].sb.st_size, PROT_READ, MAP_SHARED, file[0].fd, 0);
file[1].area = mmap(NULL, file[1].sb.st_size, PROT_READ, MAP_SHARED, file[1].fd, 0);
if (file[0].area == (void *)-1 || file[1].area == (void *)-1) {
perror("mmap");
abort();
}
file[0].ptr = file[0].area + file[0].offset;
file[1].ptr = file[1].area + file[1].offset;
return true;
}
static inline int16_t clamp16(int32_t x)
{
if (x < -32768)
return -32768;
else if (x > 32767)
return 32767;
return x;
}
static void pcmdiff_matrix_process(struct fdstream *file)
{
unsigned long stream_max = file[0].sb.st_size - file[0].offset;
unsigned long stream_count = 0;
int16_t *stream_buf;
if (file[1].sb.st_size - file[1].offset < stream_max)
stream_max = file[1].sb.st_size - file[1].offset;
if (max < stream_max)
stream_max = max;
stream_max &= ~3;
stream_buf = malloc(stream_max);
if (stream_buf == NULL) {
perror("malloc");
abort();
}
stream_max /= sizeof(int16_t);
if (mono_mix) {
printf("Mono\n");
while (stream_count < stream_max) {
stream_buf[stream_count++] = *file[0].ptr;
stream_buf[stream_count++] = *file[1].ptr;
file[0].ptr += 2;
file[1].ptr += 2;
if (stream_count % 88200 == 0)
fprintf(stderr, "\r\e[2K""%lu seconds", stream_count / 88200);
}
} else {
while (stream_count < stream_max) {
int16_t x = clamp16(*file[0].ptr - *file[1].ptr);
x = (double)x * volume;
stream_buf[stream_count++] = x;
++file[0].ptr;
++file[1].ptr;
if (stream_count % 88200 == 0)
fprintf(stderr, "\r\e[2K""%lu seconds", stream_count / 88200);
}
}
stream_max *= sizeof(int16_t);
write(file[2].fd, stream_buf, stream_max);
free(stream_buf);
}
static void pcmdiff_normal_process(struct fdstream *file)
{
int16_t a_sample, b_sample;
bool a_stop = false, b_stop = false;
do {
if (file[0].vptr < file[0].area + file[0].sb.st_size) {
a_sample = *file[0].ptr++;
} else {
a_sample = 0;
a_stop = true;
}
if (file[1].vptr < file[1].area + file[1].sb.st_size) {
b_sample = *file[1].ptr++;
} else {
b_sample = 0;
b_stop = true;
}
b_sample -= a_sample;
write(file[2].fd, &b_sample, sizeof(b_sample));
} while (!a_stop && !b_stop);
}
static int main2(int argc, const char **argv)
{
if (!pcmdiff_get_options(&argc, &argv))
return EXIT_FAILURE;
if (argc != 3 && argc != 4) {
fprintf(stderr, "Usage: %s FILE1 FILE2 >FILE3\n", *argv);
return EXIT_FAILURE;
}
if (!pcmdiff_open_streams(argc, argv, gfile))
return EXIT_FAILURE;
pcmdiff_matrix_process(gfile);
// pcmdiff_normal_process(gfile);
munmap(gfile[0].area, gfile[0].sb.st_size);
munmap(gfile[1].area, gfile[1].sb.st_size);
close(gfile[0].fd);
close(gfile[1].fd);
close(gfile[2].fd);
return EXIT_SUCCESS;
}
int main(int argc, const char **argv)
{
int ret;
if ((ret = HX_init()) <= 0) {
fprintf(stderr, "HX_init: %s\n", strerror(-ret));
abort();
}
ret = main2(argc, argv);
HX_exit();
return ret;
}