furnace/extern/mpg123/src/tests/seek_accuracy.c
2025-10-22 23:16:49 -05:00

287 lines
7.3 KiB
C

/*
seek-accuracy: Take some given mpeg file and validate that seeks are indeed accurate.
copyright 2009-2023 by the mpg123 project - free software under the terms of the LGPL 2.1
see COPYING and AUTHORS files in distribution or http://mpg123.org
initially written by Thomas Orgis
arguments: decoder preframes testfile.mpeg
*/
#include "config.h"
#include <mpg123.h>
#include <stdio.h>
#include <fcntl.h>
#include "mpg123.h"
#include "../compat/compat.h"
#define SAMPLES 187
/* Cannot use the const value as fixed array size:-( */
const size_t samples = SAMPLES;
/* Use getlopt.c in future? But heck, don't bloat. */
enum theargs
{
arg_binname = 0
,arg_decoder
,arg_preframes
,arg_file
,arg_total
};
const char* filename;
int channels;
mpg123_handle *m;
size_t first_sample_errs = 0;
int check_seeking(size_t *errs);
int main(int argc, char **argv)
{
int ret = 0;
size_t errs[2] = {0, 0};
size_t errs_ntom[2] = {0, 0};
const char* decoder = "auto";
const char* preframes = "10";
if(argc < arg_total)
{
if(argc < 2)
{
/* Would at least need a file to use ... */
fprintf(stderr, "\nUsage: %s <decoder> <preframes> <mpeg audio file>\n\n", argv[0]);
return -1;
}
/* silently use defaults plus given file */
filename = argv[argc-1];
}
else
{
decoder = argv[arg_decoder];
preframes = argv[arg_preframes];
filename = argv[arg_file];
}
m = mpg123_new(decoder, NULL);
// Try to set decoder to generic to avoid memory sanitizer freaking out about
// asssembly synth routines.
mpg123_decoder(m, "generic");
mpg123_param(m, MPG123_RESYNC_LIMIT, -1, 0);
mpg123_param(m, MPG123_REMOVE_FLAGS, MPG123_GAPLESS, 0);
if(mpg123_param(m, MPG123_PREFRAMES, atol(preframes), 0) == MPG123_OK)
fprintf(stderr,"Testing library with preframes set to %li\n", atol(preframes));
ret = check_seeking(errs);
if(ret == 0)
{
fprintf(stderr,"Now with NtoM resampling to 33333 Hz!");
mpg123_param(m, MPG123_FORCE_RATE, 33333, 0);
ret = check_seeking(errs_ntom);
}
else fprintf(stderr, "Some failure occured! Unable to test properly!");
fprintf(stderr,"\n");
fprintf(stderr,"1to1 indexed seek errors: %zu / %zu\n", errs[0], samples);
fprintf(stderr,"1to1 non-indexed seek errors: %zu / %zu\n", errs[1], samples);
fprintf(stderr,"NtoM indexed seek errors: %zu / %zu\n", errs_ntom[0], samples);
fprintf(stderr,"NtoM non-indexed seek errors: %zu / %zu\n", errs_ntom[1], samples);
fprintf(stderr,"Errors in getting first sample again: %zu\n", first_sample_errs);
fprintf(stderr,"\n");
if(ret == 0)
{
if(first_sample_errs == 0 && errs[0] == 0 && errs[1] == 0 && errs_ntom[0] == 0 && errs_ntom[1] == 0)
{
printf("Congratulations, all seeks were accurate!\n");
printf("But be warned: Not _all_ sample offsets have been tested. This result just means that the basic mechanism of sample-accurate seeking seems to work.\n");
}
else
{
printf("There have been some errors. For layer 3 files, this may be due to too low MPG123_PREFRAMES.\n");
ret = -1;
}
}
else fprintf(stderr, "Some bad failure during checking!\n");
mpg123_delete(m);
mpg123_exit();
printf("%s\n", ret ? "FAIL" : "PASS");
return ret;
}
int open_file()
{
if( mpg123_open(m, filename) == MPG123_OK
&&
mpg123_getformat(m, NULL, &channels, NULL) == MPG123_OK )
{
fprintf(stderr,"Channels: %i\n", channels);
return 0;
}
else
{
fprintf(stderr,"Opening file failed: %s\n", mpg123_strerror(m));
return -1;
}
}
/* Operation:
Read through the whole file and remember selected samples and their position.
Then, seek to the positions selectively and compare.
Second mode: Check if this is identical to fresh decoding and seek (without frame index table)... less important now, focusing on the first one.
*/
struct seeko
{
off_t position[SAMPLES];
short left[SAMPLES];
short right[SAMPLES];
};
/* select some positions to check seek accuracy on */
void fix_positions(struct seeko *so, off_t length)
{
off_t step = (length-1)/(samples-1);
size_t i;
/* simple fixed stepping, in order (one could shuffle this) */
for(i=0; i<samples-1; ++i)
so->position[i] = i*step;
/* plus the last one! */
so->position[samples-1] = length-1;
}
int collect(struct seeko *so)
{
off_t pos_count = 0;
off_t length;
int err = MPG123_OK;
size_t posi = 0;
mpg123_scan(m);
length = mpg123_length(m);
fprintf(stderr,"Estimated length: %" PRIiMAX "\n", (intmax_t)length);
/* Compute the interesting positions */
fix_positions(so, length);
/*
Default format is always 16bit int, rate does not matter.
Let's just get the channel count and not bother.
*/
while(err == MPG123_OK)
{
short buff[1024]; /* choosing a non-divider of mpeg frame size on purpose */
size_t got = 0;
off_t buffsamples;
err = mpg123_read(m, (unsigned char*)buff, 1024*sizeof(short), &got);
buffsamples = got/(channels*sizeof(short));
while(so->position[posi] < pos_count+buffsamples)
{
size_t i = (so->position[posi]-pos_count)*channels;
fprintf(stderr,"got sample %zu (%" PRIiMAX ")\n", posi, (intmax_t)so->position[posi]);
so->left[posi] = buff[i];
if(channels == 2)
so->right[posi] = buff[i+1];
if(++posi >= samples) break;
}
if(posi >= samples) break;
pos_count += buffsamples;
}
if(posi < samples && (err != MPG123_DONE && err != MPG123_OK))
{
fprintf(stderr,"An error occured (not done)?: %s\n", mpg123_strerror(m));
return -1;
}
return 0;
}
/* We allow some minimal difference ... for subtle floating point effects. */
int same(short a, short b)
{
return (abs(a-b) <= 2);
}
int check_sample(struct seeko *so, size_t i)
{
short buf[2]; /* at max one left and right sample */
size_t got = 0;
if(mpg123_seek(m, so->position[i], SEEK_SET) != so->position[i])
{
fprintf(stderr,"Error seeking to %" PRIiMAX ": %s\n", (intmax_t)so->position[i], mpg123_strerror(m));
return -1;
}
if( mpg123_read(m, (unsigned char*)buf, channels*sizeof(short), &got) != MPG123_OK
||
got/sizeof(short) != channels )
{
fprintf(stderr,"Error occured on reading sample %zu! (%s)\n", i, mpg123_strerror(m));
return -1;
}
if(same(buf[0], so->left[i]) && (channels == 1 || same(buf[1], so->right[i])))
{
fprintf(stderr,"sample %zu PASS\n", i);
}
else
{
if(channels == 1) fprintf(stderr,"sample %zu FAIL (%i != %i)\n", i, buf[0], so->left[i]);
else fprintf(stderr,"sample %zu FAIL (%i != %i || %i != %i)\n", i, buf[0], so->left[i], buf[1], so->right[i]);
return -1;
}
return 0;
}
size_t check_positions(struct seeko *so)
{
size_t i;
size_t errs = 0;
fprintf(stderr,"Seeking and comparing...\n");
for(i=0; i<samples; ++i)
{
if(i == samples/2)
{
fprintf(stderr,"a little scan in between\n");
mpg123_scan(m);
}
if(check_sample(so, i) != 0)
++errs;
}
fprintf(stderr,"Check the first one again, seeking back from the end.\n");
if(check_sample(so, 0) != 0)
++first_sample_errs;
return errs;
}
int check_seeking(size_t *errs)
{
struct seeko so;
int ret = 0;
/* First opening. */
if(open_file() != 0) return -1;
fprintf(stderr,"Collecting samples...\n");
ret = collect(&so);
if(ret != 0) return ret;
errs[0] = check_positions(&so);
fprintf(stderr,"With fresh opening (empty seek index):\n");
if(open_file() != 0) return -1;
errs[1] = check_positions(&so);
fprintf(stderr,"Another fresh Opening to see if first sample works:\n");
if(open_file() != 0) return -1;
if(check_sample(&so, 0) != 0)
++first_sample_errs;
return ret;
}