summaryrefslogtreecommitdiffstatshomepage
path: root/src/tools/ldresample.cpp
diff options
context:
space:
mode:
author Miodrag Milanovic <mmicko@gmail.com>2015-11-08 12:56:12 +0100
committer Miodrag Milanovic <mmicko@gmail.com>2015-11-08 12:56:12 +0100
commit7c19aac60e12d6f5ea301bdb34d7826a01e0b06f (patch)
treef310d86aa2c6bfc19d115307dedde4eb0cd52dad /src/tools/ldresample.cpp
parenta57b46ae933badd7441ce1644711dbb851e2b504 (diff)
Rename *.c -> *.cpp in our source (nw)
Diffstat (limited to 'src/tools/ldresample.cpp')
-rw-r--r--src/tools/ldresample.cpp574
1 files changed, 574 insertions, 0 deletions
diff --git a/src/tools/ldresample.cpp b/src/tools/ldresample.cpp
new file mode 100644
index 00000000000..d22fcd7a4a9
--- /dev/null
+++ b/src/tools/ldresample.cpp
@@ -0,0 +1,574 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ ldresample.c
+
+ Laserdisc audio synchronizer and resampler.
+
+****************************************************************************/
+
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <math.h>
+#include <new>
+#include <assert.h>
+#include "bitmap.h"
+#include "chd.h"
+#include "avhuff.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
+const UINT32 MAXIMUM_WINDOW_SIZE = 40;
+
+// number of standard deviations away from silence that we consider a real signal
+const UINT32 SIGNAL_DEVIATIONS = 100;
+
+// number of standard deviations away from silence that we consider the start of a signal
+const UINT32 SIGNAL_START_DEVIATIONS = 5;
+
+// number of consecutive entries of signal before we consider that we found it
+const UINT32 MINIMUM_SIGNAL_COUNT = 20;
+
+
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+struct movie_info
+{
+ double framerate;
+ int iframerate;
+ int numfields;
+ int width;
+ int height;
+ int samplerate;
+ int channels;
+ int interlaced;
+ bitmap_yuy16 bitmap;
+ std::vector<INT16> lsound;
+ std::vector<INT16> rsound;
+ UINT32 samples;
+};
+
+
+// ======================> chd_resample_compressor
+
+class chd_resample_compressor : public chd_file_compressor
+{
+public:
+ // construction/destruction
+ chd_resample_compressor(chd_file &source, movie_info &info, INT64 ioffset, INT64 islope)
+ : m_source(source),
+ m_info(info),
+ m_ioffset(ioffset),
+ m_islope(islope) { }
+
+ // read interface
+ virtual UINT32 read_data(void *_dest, UINT64 offset, UINT32 length)
+ {
+ assert(offset % m_source.hunk_bytes() == 0);
+ assert(length % m_source.hunk_bytes() == 0);
+
+ UINT32 startfield = offset / m_source.hunk_bytes();
+ UINT32 endfield = startfield + length / m_source.hunk_bytes();
+ UINT8 *dest = reinterpret_cast<UINT8 *>(_dest);
+
+ for (UINT32 fieldnum = startfield; fieldnum < endfield; fieldnum++)
+ {
+ generate_one_frame(dest, m_source.hunk_bytes(), fieldnum);
+ dest += m_source.hunk_bytes();
+ }
+ return length;
+ }
+
+private:
+ // internal helpers
+ void generate_one_frame(UINT8 *dest, UINT32 datasize, UINT32 fieldnum);
+
+ // internal state
+ chd_file & m_source;
+ movie_info & m_info;
+ INT64 m_ioffset;
+ INT64 m_islope;
+};
+
+
+
+//**************************************************************************
+// 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
+//**************************************************************************
+
+//-------------------------------------------------
+// open_chd - open a CHD file and return
+// information about it
+//-------------------------------------------------
+
+static chd_error open_chd(chd_file &file, const char *filename, movie_info &info)
+{
+ // open the file
+ chd_error chderr = file.open(filename);
+ if (chderr != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening CHD file: %s\n", chd_file::error_string(chderr));
+ return chderr;
+ }
+
+ // get the metadata
+ std::string metadata;
+ chderr = file.read_metadata(AV_METADATA_TAG, 0, metadata);
+ if (chderr != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error getting A/V metadata: %s\n", chd_file::error_string(chderr));
+ return chderr;
+ }
+
+ // extract the info
+ int fps, fpsfrac, width, height, interlaced, channels, rate;
+ if (sscanf(metadata.c_str(), AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7)
+ {
+ fprintf(stderr, "Improperly formatted metadata\n");
+ return CHDERR_INVALID_DATA;
+ }
+
+ // extract movie info
+ info.iframerate = fps * 1000000 + fpsfrac;
+ info.framerate = info.iframerate / 1000000.0;
+ info.numfields = file.hunk_count();
+ info.width = width;
+ info.height = height;
+ info.interlaced = interlaced;
+ info.samplerate = rate;
+ info.channels = channels;
+
+ // allocate buffers
+ info.bitmap.resize(info.width, info.height);
+ info.lsound.resize(info.samplerate);
+ info.rsound.resize(info.samplerate);
+ return CHDERR_NONE;
+}
+
+
+//-------------------------------------------------
+// create_chd - create a new CHD file
+//-------------------------------------------------
+
+static chd_error create_chd(chd_file_compressor &file, const char *filename, chd_file &source, const movie_info &info)
+{
+ // create the file
+ chd_codec_type compression[4] = { CHD_CODEC_AVHUFF };
+ chd_error chderr = file.create(filename, source.logical_bytes(), source.hunk_bytes(), source.unit_bytes(), compression);
+ if (chderr != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error creating new CHD file: %s\n", chd_file::error_string(chderr));
+ return chderr;
+ }
+
+ // clone the metadata
+ chderr = file.clone_all_metadata(source);
+ if (chderr != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error cloning metadata: %s\n", chd_file::error_string(chderr));
+ return chderr;
+ }
+
+ // begin compressing
+ file.compress_begin();
+ return CHDERR_NONE;
+}
+
+
+//-------------------------------------------------
+// read_chd - read a field from a CHD file
+//-------------------------------------------------
+
+static bool read_chd(chd_file &file, UINT32 field, movie_info &info, UINT32 soundoffs)
+{
+ // configure the codec
+ avhuff_decompress_config avconfig;
+ avconfig.video.wrap(info.bitmap, info.bitmap.cliprect());
+ avconfig.maxsamples = info.lsound.size();
+ avconfig.actsamples = &info.samples;
+ avconfig.audio[0] = &info.lsound[soundoffs];
+ avconfig.audio[1] = &info.rsound[soundoffs];
+
+ // configure the decompressor for this field
+ file.codec_configure(CHD_CODEC_AVHUFF, AVHUFF_CODEC_DECOMPRESS_CONFIG, &avconfig);
+
+ // read the field
+ chd_error chderr = file.read_hunk(field, NULL);
+ return (chderr == CHDERR_NONE);
+}
+
+
+
+//**************************************************************************
+// CORE IMPLEMENTATION
+//**************************************************************************
+
+//-------------------------------------------------
+// find_edge_near_field - given a field number,
+// load +/- 1/2 second on either side and find
+// an audio edge
+//-------------------------------------------------
+
+static bool find_edge_near_field(chd_file &srcfile, UINT32 fieldnum, movie_info &info, bool report_best_field, INT32 &delta)
+{
+ // clear the sound buffers
+ memset(&info.lsound[0], 0, info.lsound.size() * 2);
+ memset(&info.rsound[0], 0, info.rsound.size() * 2);
+
+ // read 1 second around the target area
+ int fields_to_read = info.iframerate / 1000000;
+ INT32 firstfield = fieldnum - (fields_to_read / 2);
+ UINT32 targetsoundstart = 0;
+ UINT32 firstfieldend = 0;
+ UINT32 fieldstart[100];
+ UINT32 soundend = 0;
+ for (INT32 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 (UINT32 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 (UINT32 sampnum = 0; sampnum < soundend - MAXIMUM_WINDOW_SIZE; sampnum++)
+ {
+ // scan forward over the maximum window
+ UINT32 lmax = 0, rmax = 0;
+ for (UINT32 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
+ UINT32 firstlavg = 0;
+ UINT32 firstravg = 0;
+ for (UINT32 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
+ UINT32 firstldev = 0;
+ UINT32 firstrdev = 0;
+ for (UINT32 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
+ UINT32 lcount = 0;
+ UINT32 rcount = 0;
+ UINT32 sampnum = 0;
+ 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)
+ {
+ INT32 curfield;
+ 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;
+}
+
+
+//-------------------------------------------------
+// generate_one_frame - generate a single
+// resampled frame
+//-------------------------------------------------
+
+void chd_resample_compressor::generate_one_frame(UINT8 *dest, UINT32 datasize, UINT32 fieldnum)
+{
+ // determine the first field needed to cover this range of samples
+ UINT32 srcbegin = field_to_sample_number(m_info, fieldnum);
+ INT64 dstbegin = (INT64(srcbegin) << 24) + m_ioffset + m_islope * fieldnum;
+ UINT32 dstbeginoffset;
+ INT32 dstbeginfield;
+ if (dstbegin >= 0)
+ dstbeginfield = sample_number_to_field(m_info, dstbegin >> 24, dstbeginoffset);
+ else
+ {
+ dstbeginfield = -1 - sample_number_to_field(m_info, -dstbegin >> 24, dstbeginoffset);
+ dstbeginoffset = (field_to_sample_number(m_info, -dstbeginfield) - field_to_sample_number(m_info, -dstbeginfield - 1)) - dstbeginoffset;
+ }
+
+ // determine the last field needed to cover this range of samples
+ UINT32 srcend = field_to_sample_number(m_info, fieldnum + 1);
+ INT64 dstend = (INT64(srcend) << 24) + m_ioffset + m_islope * (fieldnum + 1);
+ UINT32 dstendoffset;
+ INT32 dstendfield;
+ if (dstend >= 0)
+ dstendfield = sample_number_to_field(m_info, dstend >> 24, dstendoffset);
+ else
+ {
+ dstendfield = -1 - -sample_number_to_field(m_info, -dstend >> 24, dstendoffset);
+ dstendoffset = (field_to_sample_number(m_info, -dstendfield) - field_to_sample_number(m_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
+ UINT32 dstoffset = srcend - srcbegin;
+ for (INT32 dstfield = dstbeginfield; dstfield <= dstendfield; dstfield++)
+ {
+ if (dstfield >= 0)
+ read_chd(m_source, dstfield, m_info, dstoffset);
+ else
+ {
+ m_info.samples = field_to_sample_number(m_info, -dstfield) - field_to_sample_number(m_info, -dstfield - 1);
+ memset(&m_info.lsound[dstoffset], 0, m_info.samples * sizeof(m_info.lsound[0]));
+ memset(&m_info.rsound[dstoffset], 0, m_info.samples * sizeof(m_info.rsound[0]));
+ }
+ dstoffset += m_info.samples;
+ }
+
+ // resample the destination samples to the source
+ dstoffset = srcend - srcbegin;
+ INT64 dstpos = dstbegin;
+ INT64 dststep = (dstend - dstbegin) / INT64(srcend - srcbegin);
+ for (UINT32 srcoffset = 0; srcoffset < srcend - srcbegin; srcoffset++)
+ {
+ m_info.lsound[srcoffset] = m_info.lsound[(int)(dstoffset + dstbeginoffset + (dstpos >> 24) - (dstbegin >> 24))];
+ m_info.rsound[srcoffset] = m_info.rsound[(int)(dstoffset + dstbeginoffset + (dstpos >> 24) - (dstbegin >> 24))];
+ dstpos += dststep;
+ }
+
+ // read the original frame, pointing the sound buffer past where we've calculated
+ read_chd(m_source, fieldnum, m_info, srcend - srcbegin);
+
+ // assemble the final frame
+ dynamic_buffer buffer;
+ INT16 *sampledata[2] = { &m_info.lsound[0], &m_info.rsound[0] };
+ avhuff_encoder::assemble_data(buffer, m_info.bitmap, m_info.channels, m_info.samples, sampledata);
+ memcpy(dest, &buffer[0], MIN(buffer.size(), datasize));
+ if (buffer.size() < datasize)
+ memset(&dest[buffer.size()], 0, datasize - buffer.size());
+}
+
+
+//-------------------------------------------------
+// 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[])
+{
+ // verify arguments
+ if (argc < 2)
+ return usage();
+ const char *srcfilename = argv[1];
+ const char *dstfilename = (argc < 3) ? NULL : argv[2];
+ double offset = (argc < 4) ? 0.0 : atof(argv[3]);
+ double 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
+ chd_file srcfile;
+ movie_info info;
+ chd_error err = open_chd(srcfile, srcfilename, info);
+ if (err != CHDERR_NONE)
+ {
+ 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)
+ {
+ for (UINT32 fieldnum = 60; fieldnum < info.numfields - 60; fieldnum += 30)
+ {
+ fprintf(stderr, "Field %5d\r", fieldnum);
+ INT32 delta;
+ find_edge_near_field(srcfile, fieldnum, info, true, delta);
+ }
+ }
+
+ // otherwise, resample the source to the destination
+ else
+ {
+ // open the destination file
+ chd_resample_compressor dstfile(srcfile, info, INT64(offset * 65536.0 * 256.0), INT64(slope * 65536.0 * 256.0));
+ err = create_chd(dstfile, 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
+ double progress, ratio;
+ osd_ticks_t last_update = 0;
+ while (dstfile.compress_continue(progress, ratio) == CHDERR_COMPRESSING)
+ if (osd_ticks() - last_update > osd_ticks_per_second() / 4)
+ {
+ last_update = osd_ticks();
+ printf("Processing, %.1f%% complete....\r", progress * 100.0);
+ }
+ }
+ return 0;
+}