From 819c231992043b0201408f2cfba3807bffbdcf5d Mon Sep 17 00:00:00 2001 From: Aaron Giles Date: Thu, 16 Oct 2008 08:47:06 +0000 Subject: 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] --- .gitattributes | 1 + src/tools/ldresample.c | 663 +++++++++++++++++++++++++++++++++++++++++++++++++ src/tools/ldverify.c | 2 +- src/tools/tools.mak | 14 ++ 4 files changed, 679 insertions(+), 1 deletion(-) create mode 100644 src/tools/ldresample.c diff --git a/.gitattributes b/.gitattributes index 770bda4e667..ad1927709dd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/src/tools/ldresample.c b/src/tools/ldresample.c new file mode 100644 index 00000000000..f5ce0d19008 --- /dev/null +++ b/src/tools/ldresample.c @@ -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 +#include +#include +#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; +} diff --git a/src/tools/ldverify.c b/src/tools/ldverify.c index 296a0870b7a..96ce516f855 100644 --- a/src/tools/ldverify.c +++ b/src/tools/ldverify.c @@ -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 \n"); return 1; } diff --git a/src/tools/tools.mak b/src/tools/tools.mak index c9f73eac95a..d9aca2e4673 100644 --- a/src/tools/tools.mak +++ b/src/tools/tools.mak @@ -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 #------------------------------------------------- -- cgit v1.2.3