Added new tool ldresample to assist in resynchronizing audio tracks in

a CHD with frames. Currently still WIP but useful if you know what 
you're doing. [Aaron Giles]
This commit is contained in:
Aaron Giles 2008-10-16 08:47:06 +00:00
parent a1c2887d78
commit 819c231992
4 changed files with 679 additions and 1 deletions

1
.gitattributes vendored
View File

@ -3473,6 +3473,7 @@ src/tools/chdcd.c svneol=native#text/plain
src/tools/chdcd.h svneol=native#text/plain
src/tools/chdman.c svneol=native#text/plain
src/tools/jedutil.c svneol=native#text/plain
src/tools/ldresample.c svneol=native#text/plain
src/tools/ldverify.c svneol=native#text/plain
src/tools/regrep.c svneol=native#text/plain
src/tools/romcmp.c svneol=native#text/plain

663
src/tools/ldresample.c Normal file
View File

@ -0,0 +1,663 @@
/***************************************************************************
ldresample.c
Laserdisc audio synchronizer and resampler.
Copyright Nicola Salmoria and the MAME Team.
Visit http://mamedev.org for licensing and usage restrictions.
***************************************************************************/
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include "bitmap.h"
#include "chd.h"
#include "vbiparse.h"
/***************************************************************************
CONSTANTS
***************************************************************************/
/* size of window where we scan ahead to find maximum; this should be large enough to
catch peaks of even slow waves */
#define MAXIMUM_WINDOW_SIZE 40
/* number of standard deviations away from silence that we consider a real signal */
#define SIGNAL_DEVIATIONS 100
/* number of standard deviations away from silence that we consider the start of a signal */
#define SIGNAL_START_DEVIATIONS 5
/* number of consecutive entries of signal before we consider that we found it */
#define MINIMUM_SIGNAL_COUNT 20
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
typedef struct _movie_info movie_info;
struct _movie_info
{
double framerate;
int iframerate;
int numfields;
int width;
int height;
int samplerate;
int channels;
int interlaced;
bitmap_t * bitmap;
INT16 * lsound;
INT16 * rsound;
UINT32 samples;
};
/***************************************************************************
GLOBAL VARIABLES
***************************************************************************/
/***************************************************************************
INLINE FUNCTIONS
***************************************************************************/
/*-------------------------------------------------
field_to_sample_number - given a field number
compute the absolute sample number for the
first sample of that field
-------------------------------------------------*/
INLINE UINT32 field_to_sample_number(const movie_info *info, UINT32 field)
{
return ((UINT64)info->samplerate * (UINT64)field * (UINT64)1000000 + info->iframerate - 1) / (UINT64)info->iframerate;
}
/*-------------------------------------------------
sample_number_to_field - given a sample number
compute the field where it is located and
the offset within the field
-------------------------------------------------*/
INLINE UINT32 sample_number_to_field(const movie_info *info, UINT32 samplenum, UINT32 *offset)
{
UINT32 guess = ((UINT64)samplenum * (UINT64)info->iframerate + ((UINT64)info->samplerate * (UINT64)1000000 - 1)) / ((UINT64)info->samplerate * (UINT64)1000000);
while (1)
{
UINT32 fieldstart = field_to_sample_number(info, guess);
UINT32 fieldend = field_to_sample_number(info, guess + 1);
if (samplenum >= fieldstart && samplenum < fieldend)
{
*offset = samplenum - fieldstart;
return guess;
}
else if (samplenum < fieldstart)
guess--;
else
guess++;
}
}
/***************************************************************************
CHD HANDLING
***************************************************************************/
/*-------------------------------------------------
chd_allocate_buffers - allocate buffers for
CHD I/O
-------------------------------------------------*/
static int chd_allocate_buffers(movie_info *info)
{
/* allocate a bitmap */
info->bitmap = bitmap_alloc(info->width, info->height, BITMAP_FORMAT_YUY16);
if (info->bitmap == NULL)
{
fprintf(stderr, "Out of memory creating %dx%d bitmap\n", info->width, info->height);
return FALSE;
}
/* allocate sound buffers */
info->lsound = malloc(info->samplerate * sizeof(*info->lsound));
info->rsound = malloc(info->samplerate * sizeof(*info->rsound));
if (info->lsound == NULL || info->rsound == NULL)
{
fprintf(stderr, "Out of memory allocating sound buffers of %d bytes\n", (INT32)(info->samplerate * sizeof(*info->rsound)));
return FALSE;
}
return TRUE;
}
/*-------------------------------------------------
chd_free_buffers - release buffers for
CHD I/O
-------------------------------------------------*/
static void chd_free_buffers(movie_info *info)
{
if (info->bitmap != NULL)
free(info->bitmap);
if (info->lsound != NULL)
free(info->lsound);
if (info->rsound != NULL)
free(info->rsound);
}
/*-------------------------------------------------
open_chd - open a CHD file and return
information about it
-------------------------------------------------*/
static chd_file *open_chd(const char *filename, movie_info *info)
{
int fps, fpsfrac, width, height, interlaced, channels, rate;
char metadata[256];
chd_error chderr;
chd_file *chd;
/* open the file */
chderr = chd_open(filename, CHD_OPEN_READ, NULL, &chd);
if (chderr != CHDERR_NONE)
{
fprintf(stderr, "Error opening CHD file: %s\n", chd_error_string(chderr));
return NULL;
}
/* get the metadata */
chderr = chd_get_metadata(chd, AV_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL);
if (chderr != CHDERR_NONE)
{
fprintf(stderr, "Error getting A/V metadata: %s\n", chd_error_string(chderr));
chd_close(chd);
return NULL;
}
/* extract the info */
if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7)
{
fprintf(stderr, "Improperly formatted metadata\n");
chd_close(chd);
return NULL;
}
/* extract movie info */
info->iframerate = fps * 1000000 + fpsfrac;
info->framerate = info->iframerate / 1000000.0;
info->numfields = chd_get_header(chd)->totalhunks;
info->width = width;
info->height = height;
info->interlaced = interlaced;
info->samplerate = rate;
info->channels = channels;
/* allocate buffers */
if (!chd_allocate_buffers(info))
return NULL;
return chd;
}
/*-------------------------------------------------
create_chd - create a new CHD file
-------------------------------------------------*/
static chd_file *create_chd(const char *filename, chd_file *source, const movie_info *info)
{
const chd_header *srcheader = chd_get_header(source);
chd_error chderr;
chd_file *chd;
/* create the file */
chderr = chd_create(filename, srcheader->logicalbytes, srcheader->hunkbytes, CHDCOMPRESSION_AV, NULL);
if (chderr != CHDERR_NONE)
{
fprintf(stderr, "Error creating new CHD file: %s\n", chd_error_string(chderr));
return NULL;
}
/* open the file */
chderr = chd_open(filename, CHD_OPEN_READWRITE, NULL, &chd);
if (chderr != CHDERR_NONE)
{
fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(chderr));
return NULL;
}
/* clone the metadata */
chderr = chd_clone_metadata(source, chd);
if (chderr != CHDERR_NONE)
{
fprintf(stderr, "Error cloning metadata: %s\n", chd_error_string(chderr));
chd_close(chd);
return NULL;
}
/* begin compressing */
chderr = chd_compress_begin(chd);
if (chderr != CHDERR_NONE)
{
fprintf(stderr, "Error beginning compression: %s\n", chd_error_string(chderr));
return NULL;
}
return chd;
}
/*-------------------------------------------------
read_chd - read a field from a CHD file
-------------------------------------------------*/
static int read_chd(chd_file *file, UINT32 field, movie_info *info, UINT32 soundoffs)
{
av_codec_decompress_config avconfig = { 0 };
chd_error chderr;
/* configure the codec */
avconfig.video = info->bitmap;
avconfig.maxsamples = 48000;
avconfig.actsamples = &info->samples;
avconfig.audio[0] = info->lsound + soundoffs;
avconfig.audio[1] = info->rsound + soundoffs;
/* configure the decompressor for this field */
chd_codec_config(file, AV_CODEC_DECOMPRESS_CONFIG, &avconfig);
/* read the field */
chderr = chd_read(file, field, NULL);
if (chderr != CHDERR_NONE)
return FALSE;
return TRUE;
}
/*-------------------------------------------------
write_chd - write a field to a CHD file
-------------------------------------------------*/
static int write_chd(chd_file *file, UINT32 field, movie_info *info)
{
av_codec_compress_config avconfig = { 0 };
chd_error chderr;
/* configure the codec */
avconfig.video = info->bitmap;
avconfig.channels = 2;
avconfig.samples = info->samples;
avconfig.audio[0] = info->lsound;
avconfig.audio[1] = info->rsound;
/* configure the decompressor for this field */
chd_codec_config(file, AV_CODEC_COMPRESS_CONFIG, &avconfig);
/* read the field */
chderr = chd_compress_hunk(file, NULL, NULL);
if (chderr != CHDERR_NONE)
return FALSE;
return TRUE;
}
/*-------------------------------------------------
create_close_chd - close a CHD file
-------------------------------------------------*/
static void create_close_chd(chd_file *file)
{
chd_error err;
err = chd_compress_finish(file);
if (err != CHDERR_NONE)
fprintf(stderr, "Error finishing compression: %s\n", chd_error_string(err));
chd_close(file);
}
/*-------------------------------------------------
close_chd - close a CHD file
-------------------------------------------------*/
static void close_chd(chd_file *file, movie_info *info)
{
if (info != NULL)
chd_free_buffers(info);
chd_close(file);
}
/***************************************************************************
CORE IMPLEMENTATION
***************************************************************************/
/*-------------------------------------------------
find_edge_near_field - given a field number,
load +/- 1/2 second on either side and find
an audio edge
-------------------------------------------------*/
static int find_edge_near_field(chd_file *srcfile, UINT32 fieldnum, movie_info *info, int report_best_field, INT32 *delta)
{
int fields_to_read = info->iframerate / 1000000;
UINT32 firstlavg = 0, firstravg = 0;
UINT32 firstldev = 0, firstrdev = 0;
UINT32 lcount = 0, rcount = 0;
UINT32 targetsoundstart = 0;
UINT32 firstfieldend = 0;
INT32 firstfield, curfield;
UINT32 fieldstart[100];
UINT32 soundend = 0;
UINT32 sampnum;
/* clear the sound buffers */
memset(info->lsound, 0, info->samplerate * sizeof(*info->lsound));
memset(info->rsound, 0, info->samplerate * sizeof(*info->rsound));
/* read 1 second around the target area */
firstfield = fieldnum - (fields_to_read / 2);
for (curfield = 0; curfield < fields_to_read; curfield++)
{
/* remember the start of each field */
fieldstart[curfield] = soundend;
/* remember the sound offset where the initial fieldnum is */
if (firstfield + curfield == fieldnum)
targetsoundstart = soundend;
/* read the frame and samples */
if (firstfield + curfield >= 0)
{
read_chd(srcfile, firstfield + curfield, info, soundend);
soundend += info->samples;
/* also remember the offset at the end of the first field */
if (firstfieldend == 0)
firstfieldend = soundend;
}
}
/* compute absolute deltas across the samples */
for (sampnum = 0; sampnum < soundend; sampnum++)
{
info->lsound[sampnum] = labs(info->lsound[sampnum + 1] - info->lsound[sampnum]);
info->rsound[sampnum] = labs(info->rsound[sampnum + 1] - info->rsound[sampnum]);
}
/* for each sample in the collection, find the highest deltas over the
next few samples, and take the nth highest value (to remove outliers) */
for (sampnum = 0; sampnum < soundend - MAXIMUM_WINDOW_SIZE; sampnum++)
{
UINT32 lmax = 0, rmax = 0;
UINT32 scannum;
/* scan forward over the maximum window */
for (scannum = 0; scannum < MAXIMUM_WINDOW_SIZE; scannum++)
{
if (info->lsound[sampnum + scannum] > lmax)
lmax = info->lsound[sampnum + scannum];
if (info->rsound[sampnum + scannum] > rmax)
rmax = info->rsound[sampnum + scannum];
}
/* replace this sample with the maximum value found */
info->lsound[sampnum] = lmax;
info->rsound[sampnum] = rmax;
}
/* now compute the average over the first field, which is assumed to be silence */
for (sampnum = 0; sampnum < firstfieldend; sampnum++)
{
firstlavg += info->lsound[sampnum];
firstravg += info->rsound[sampnum];
}
firstlavg /= firstfieldend;
firstravg /= firstfieldend;
/* then compute the standard deviation over the first field */
for (sampnum = 0; sampnum < firstfieldend; sampnum++)
{
firstldev += (info->lsound[sampnum] - firstlavg) * (info->lsound[sampnum] - firstlavg);
firstrdev += (info->rsound[sampnum] - firstravg) * (info->rsound[sampnum] - firstravg);
}
firstldev = sqrt((double)firstldev / firstfieldend);
firstrdev = sqrt((double)firstrdev / firstfieldend);
/* scan forward through the samples, counting consecutive samples more than
SIGNAL_DEVIATIONS standard deviations away from silence */
for (sampnum = 0; sampnum < soundend; sampnum++)
{
/* left speaker */
if (info->lsound[sampnum] > firstlavg + SIGNAL_DEVIATIONS * firstldev)
lcount++;
else
lcount = 0;
/* right speaker */
if (info->rsound[sampnum] > firstravg + SIGNAL_DEVIATIONS * firstrdev)
rcount++;
else
rcount = 0;
/* stop if we find enough */
if (lcount > MINIMUM_SIGNAL_COUNT || rcount > MINIMUM_SIGNAL_COUNT)
break;
}
/* if we didn't find any, return failure */
if (sampnum >= soundend)
{
if (!report_best_field)
printf("Field %5d: Unable to find edge\n", fieldnum);
return FALSE;
}
/* scan backwards to find the start of the signal */
for ( ; sampnum > 0; sampnum--)
if (info->lsound[sampnum - 1] < firstlavg + SIGNAL_START_DEVIATIONS * firstldev ||
info->rsound[sampnum - 1] < firstravg + SIGNAL_START_DEVIATIONS * firstrdev)
break;
/* if we're to report the best field, figure out which field we are in */
if (report_best_field)
{
for (curfield = 0; curfield < fields_to_read - 1; curfield++)
if (sampnum < fieldstart[curfield + 1])
break;
printf("Field %5d: Edge found at offset %d (frame %.1f)\n", firstfield + curfield, sampnum - fieldstart[curfield], (double)(firstfield + curfield) * 0.5);
}
/* otherwise, compute the delta from the provided field number */
else
{
printf("Field %5d: Edge at offset %d from expected (found at %d, expected %d)\n", fieldnum, sampnum - targetsoundstart, sampnum, targetsoundstart);
*delta = sampnum - targetsoundstart;
}
return TRUE;
}
/*-------------------------------------------------
usage - display program usage
-------------------------------------------------*/
static int usage(void)
{
fprintf(stderr, "Usage: \n");
fprintf(stderr, " ldresample source.chd\n");
fprintf(stderr, " ldresample source.chd output.chd offset [slope]\n");
fprintf(stderr, "\n");
fprintf(stderr, "Where offset and slope make a linear equation f(x) which\n");
fprintf(stderr, "describes the sample offset from the source as a function\n");
fprintf(stderr, "of field number.\n");
return 1;
}
/*-------------------------------------------------
main - main entry point
-------------------------------------------------*/
int main(int argc, char *argv[])
{
movie_info info = { 0 };
const char *srcfilename;
const char *dstfilename;
double offset, slope;
chd_file *srcfile;
chd_file *dstfile;
/* verify arguments */
if (argc < 2)
return usage();
srcfilename = argv[1];
dstfilename = (argc < 3) ? NULL : argv[2];
offset = (argc < 4) ? 0.0 : atof(argv[3]);
slope = (argc < 5) ? 1.0 : atof(argv[4]);
/* print basic information */
printf("Input file: %s\n", srcfilename);
if (dstfilename != NULL)
{
printf("Output file: %s\n", dstfilename);
printf("Offset: %f\n", offset);
printf("Slope: %f\n", slope);
}
/* open the source file */
srcfile = open_chd(srcfilename, &info);
if (srcfile == NULL)
{
fprintf(stderr, "Unable to open file '%s'\n", srcfilename);
return 1;
}
/* output some basics */
printf("Video dimensions: %dx%d\n", info.width, info.height);
printf("Video frame rate: %.2fHz\n", info.framerate);
printf("Sample rate: %dHz\n", info.samplerate);
printf("Total fields: %d\n", info.numfields);
/* if we don't have a destination file, scan for edges */
if (dstfilename == NULL)
{
UINT32 fieldnum;
INT32 delta;
for (fieldnum = 60; fieldnum < info.numfields - 60; fieldnum += 30)
{
fprintf(stderr, "Field %5d\r", fieldnum);
find_edge_near_field(srcfile, fieldnum, &info, TRUE, &delta);
}
}
/* otherwise, resample the source to the destination */
else
{
INT64 ioffset = (INT64)(offset * 65536.0 * 256.0);
INT64 islope = (INT64)(slope * 65536.0 * 256.0);
UINT32 fieldnum;
/* open the destination file */
dstfile = create_chd(dstfilename, srcfile, &info);
if (dstfile == NULL)
{
fprintf(stderr, "Unable to create file '%s'\n", dstfilename);
return 1;
}
/* loop over all the fields in the source file */
for (fieldnum = 0; fieldnum < info.numfields; fieldnum++)
{
UINT32 srcbegin = field_to_sample_number(&info, fieldnum);
UINT32 srcend = field_to_sample_number(&info, fieldnum + 1);
INT64 dstbegin = ((INT64)srcbegin << 24) + ioffset + islope * fieldnum;
INT64 dstend = ((INT64)srcend << 24) + ioffset + islope * (fieldnum + 1);
UINT32 dstbeginoffset, dstendoffset, dstoffset;
INT32 dstbeginfield, dstendfield, dstfield;
INT64 dstpos, dststep;
UINT32 srcoffset;
/* update progress (this ain't fast!) */
if (fieldnum % 10 == 0)
fprintf(stderr, "Field %d\r", fieldnum);
/* determine the first and last fields needed to cover this range of samples */
if (dstbegin >= 0)
dstbeginfield = sample_number_to_field(&info, dstbegin >> 24, &dstbeginoffset);
else
{
dstbeginfield = -1 - sample_number_to_field(&info, -dstbegin >> 24, &dstbeginoffset);
dstbeginoffset = (field_to_sample_number(&info, -dstbeginfield) - field_to_sample_number(&info, -dstbeginfield - 1)) - dstbeginoffset;
}
if (dstend >= 0)
dstendfield = sample_number_to_field(&info, dstend >> 24, &dstendoffset);
else
{
dstendfield = -1 - -sample_number_to_field(&info, -dstend >> 24, &dstendoffset);
dstendoffset = (field_to_sample_number(&info, -dstendfield) - field_to_sample_number(&info, -dstendfield - 1)) - dstendoffset;
}
/*
printf("%5d: start=%10d (%5d.%03d) end=%10d (%5d.%03d)\n",
fieldnum,
(INT32)(dstbegin >> 24), dstbeginfield, dstbeginoffset,
(INT32)(dstend >> 24), dstendfield, dstendoffset);
*/
/* read all samples required into the end of the sound buffers */
dstoffset = srcend - srcbegin;
for (dstfield = dstbeginfield; dstfield <= dstendfield; dstfield++)
{
if (dstfield >= 0)
read_chd(srcfile, dstfield, &info, dstoffset);
else
{
info.samples = field_to_sample_number(&info, -dstfield) - field_to_sample_number(&info, -dstfield - 1);
memset(&info.lsound[dstoffset], 0, info.samples * sizeof(info.lsound[0]));
memset(&info.rsound[dstoffset], 0, info.samples * sizeof(info.rsound[0]));
}
dstoffset += info.samples;
}
/* resample the destination samples to the source */
dstoffset = srcend - srcbegin;
dstpos = dstbegin;
dststep = (dstend - dstbegin) / (INT64)(srcend - srcbegin);
for (srcoffset = 0; srcoffset < srcend - srcbegin; srcoffset++)
{
info.lsound[srcoffset] = info.lsound[dstoffset + dstbeginoffset + (dstpos >> 24) - (dstbegin >> 24)];
info.rsound[srcoffset] = info.rsound[dstoffset + dstbeginoffset + (dstpos >> 24) - (dstbegin >> 24)];
dstpos += dststep;
}
/* read the original frame, pointing the sound buffer past where we've calculated */
read_chd(srcfile, fieldnum, &info, srcend - srcbegin);
/* write it to the destination */
write_chd(dstfile, fieldnum, &info);
}
/* close the destination file */
create_close_chd(dstfile);
}
/* close the source file */
close_chd(srcfile, &info);
return 0;
}

View File

@ -650,7 +650,7 @@ static void verify_audio_final(void)
static int usage(void)
{
fprintf(stderr, "Usage: \n");
fprintf(stderr, " ldverify [avifile.avi|chdfile.chd]\n");
fprintf(stderr, " ldverify <avifile.avi|chdfile.chd>\n");
return 1;
}

View File

@ -26,6 +26,7 @@ TOOLS += \
romcmp$(EXE) \
chdman$(EXE) \
jedutil$(EXE) \
ldresample$(EXE) \
ldverify$(EXE) \
regrep$(EXE) \
srcclean$(EXE) \
@ -73,6 +74,19 @@ jedutil$(EXE): $(JEDUTILOBJS) $(LIBUTIL) $(LIBOCORE) $(ZLIB) $(EXPAT)
#-------------------------------------------------
# ldresample
#-------------------------------------------------
LDRESAMPLEOBJS = \
$(TOOLSOBJ)/ldresample.o \
ldresample$(EXE): $(LDRESAMPLEOBJS) $(LIBUTIL) $(LIBOCORE) $(ZLIB) $(EXPAT)
@echo Linking $@...
$(LD) $(LDFLAGS) $^ $(LIBS) -o $@
#-------------------------------------------------
# ldverify
#-------------------------------------------------