mirror of
https://github.com/holub/mame
synced 2025-05-09 07:41:50 +03:00
407 lines
13 KiB
C
407 lines
13 KiB
C
/***************************************************************************
|
|
|
|
vbiparse.c
|
|
|
|
Parse Philips codes and other data from VBI lines.
|
|
|
|
****************************************************************************
|
|
|
|
Copyright Aaron Giles
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are
|
|
met:
|
|
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in
|
|
the documentation and/or other materials provided with the
|
|
distribution.
|
|
* Neither the name 'MAME' nor the names of its contributors may be
|
|
used to endorse or promote products derived from this software
|
|
without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
|
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
|
|
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
***************************************************************************/
|
|
|
|
#include "osdcore.h"
|
|
#include "vbiparse.h"
|
|
#include <math.h>
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
DEBUGGING
|
|
***************************************************************************/
|
|
|
|
#define PRINTF_WHITE_FLAG 0
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
CONSTANTS
|
|
***************************************************************************/
|
|
|
|
#define MAX_SOURCE_WIDTH 1024
|
|
#define MAX_CLOCK_DIFF 3
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
IMPLEMENTATION
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
vbi_parse_manchester_code - parse a Manchester
|
|
code from a line of video data
|
|
-------------------------------------------------*/
|
|
|
|
int vbi_parse_manchester_code(const UINT16 *source, int sourcewidth, int sourceshift, int expectedbits, UINT32 *result)
|
|
{
|
|
UINT8 srcabs[MAX_SOURCE_WIDTH];
|
|
UINT8 min, max, mid, srcabsval;
|
|
double clock, bestclock;
|
|
int x, firstedge;
|
|
int besterr;
|
|
|
|
/* fail if the width is too large */
|
|
if (sourcewidth > MAX_SOURCE_WIDTH)
|
|
return 0;
|
|
|
|
/* find highs and lows in the line */
|
|
min = 0xff;
|
|
max = 0x00;
|
|
for (x = 0; x < sourcewidth; x++)
|
|
{
|
|
UINT8 rawsrc = source[x] >> sourceshift;
|
|
min = MIN(min, rawsrc);
|
|
max = MAX(max, rawsrc);
|
|
}
|
|
|
|
/* bail if the line is all black or all white */
|
|
if (max < 0x80 || min > 0x80)
|
|
return 0;
|
|
|
|
/* determine the midpoint and then set the thresholds to be halfway */
|
|
mid = (min + max) / 2;
|
|
min = mid - (mid - min) / 2;
|
|
max = mid + (max - mid) / 2;
|
|
|
|
/* convert the source into absolute high/low */
|
|
srcabsval = (source[0] > mid);
|
|
for (x = 0; x < sourcewidth; x++)
|
|
{
|
|
UINT8 rawsrc = source[x] >> sourceshift;
|
|
if (rawsrc >= max)
|
|
srcabsval = 1;
|
|
else if (rawsrc <= min)
|
|
srcabsval = 0;
|
|
srcabs[x] = srcabsval;
|
|
}
|
|
|
|
/* find the first transition; this is assumed to be the middle of the first bit */
|
|
for (x = 0; x < sourcewidth - 1; x++)
|
|
if (srcabs[x] != srcabs[x + 1])
|
|
break;
|
|
if (x == sourcewidth - 1)
|
|
return 0;
|
|
firstedge = x;
|
|
|
|
/* now scan to find a clock that has a nearby transition on each beat */
|
|
bestclock = 0;
|
|
besterr = 1000;
|
|
for (clock = (double)sourcewidth / (double)expectedbits; clock >= 2.0; clock -= 1.0 / (double)expectedbits)
|
|
{
|
|
int error = 0;
|
|
|
|
/* scan for all the expected bits */
|
|
for (x = 1; x < expectedbits; x++)
|
|
{
|
|
int curbit = firstedge + (double)x * clock;
|
|
int offby;
|
|
|
|
/* look for a match that is off by an amount up to the maximum */
|
|
for (offby = 0; offby <= MAX_CLOCK_DIFF; offby++)
|
|
if (srcabs[curbit + offby + 0] != srcabs[curbit + offby + 1] || srcabs[curbit - offby + 0] != srcabs[curbit - offby + 1])
|
|
break;
|
|
|
|
/* if we never found the edge, fail immediately */
|
|
if (offby > MAX_CLOCK_DIFF)
|
|
break;
|
|
|
|
/* only continue if we're still in the running */
|
|
error += offby;
|
|
if (error >= besterr)
|
|
break;
|
|
}
|
|
|
|
/* if we got to the end, this is the best candidate so far */
|
|
if (x == expectedbits)
|
|
{
|
|
besterr = error;
|
|
bestclock = clock;
|
|
}
|
|
}
|
|
|
|
/* if nobody matched, fail */
|
|
if (bestclock == 0)
|
|
return 0;
|
|
|
|
/* now extract the bits */
|
|
for (x = 0; x < expectedbits; x++)
|
|
{
|
|
int leftstart = firstedge + ceil(((double)x - 0.5) * bestclock);
|
|
// int leftmid = firstedge + ((double)x - 0.25) * bestclock;
|
|
int leftend = firstedge + floor(((double)x - 0.0) * bestclock);
|
|
int rightstart = firstedge + ceil(((double)x + 0.0) * bestclock);
|
|
// int rightmid = firstedge + ((double)x + 0.25) * bestclock;
|
|
int rightend = firstedge + floor(((double)x + 0.5) * bestclock);
|
|
int leftavg, rightavg, leftabs, rightabs;
|
|
int confidence = 0;
|
|
int tx;
|
|
|
|
/* compute left and right average values */
|
|
leftavg = 0;
|
|
for (tx = leftstart; tx <= leftend; tx++)
|
|
leftavg += (UINT8)(source[tx] >> sourceshift) - mid;
|
|
leftabs = (leftavg >= 0);
|
|
leftavg = (leftavg < 0) ? -leftavg : leftavg;
|
|
|
|
rightavg = 0;
|
|
for (tx = rightstart; tx <= rightend; tx++)
|
|
rightavg += (UINT8)(source[tx] >> sourceshift) - mid;
|
|
rightabs = (rightavg >= 0);
|
|
rightavg = (rightavg < 0) ? -rightavg : rightavg;
|
|
|
|
/* all bits should be marked by transitions; fail if we don't get one */
|
|
if (leftabs == rightabs)
|
|
return 0;
|
|
|
|
/* store the bit and its confidence level */
|
|
confidence = leftavg + rightavg;
|
|
result[x] = (leftabs < rightabs) | (confidence << 1);
|
|
}
|
|
return expectedbits;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
vbi_parse_white_flag - compute the "white
|
|
flag" from a line of video data
|
|
-------------------------------------------------*/
|
|
|
|
int vbi_parse_white_flag(const UINT16 *source, int sourcewidth, int sourceshift)
|
|
{
|
|
int histo[256] = { 0 };
|
|
int minval = 0xff;
|
|
int maxval = 0x00;
|
|
int subtract;
|
|
int peakval;
|
|
int result;
|
|
int x;
|
|
|
|
/* compute a histogram of values */
|
|
for (x = 0; x < sourcewidth; x++)
|
|
{
|
|
UINT8 yval = source[x] >> sourceshift;
|
|
histo[yval]++;
|
|
}
|
|
|
|
/* remove the lowest 1% of the values to account for noise and determine the minimum */
|
|
subtract = sourcewidth / 100;
|
|
for (minval = 0; minval < 255; minval++)
|
|
if ((subtract -= histo[minval]) < 0)
|
|
break;
|
|
|
|
/* remove the highest 1% of the values to account for noise and determine the maximum */
|
|
subtract = sourcewidth / 100;
|
|
for (maxval = 255; maxval > 0; maxval--)
|
|
if ((subtract -= histo[maxval]) < 0)
|
|
break;
|
|
|
|
/* this is useful for debugging issues with white flag detection */
|
|
if (PRINTF_WHITE_FLAG)
|
|
{
|
|
printf("Histo: min=%02X max=%02X\n", minval, maxval);
|
|
for (x = 0; x < 256; x++)
|
|
if (histo[x] != 0) printf("%dx%02X\n", histo[x], x);
|
|
}
|
|
|
|
/* ignore if we have no dynamic range */
|
|
if (maxval - minval < 10)
|
|
{
|
|
if (PRINTF_WHITE_FLAG)
|
|
printf("White flag NOT detected; threshold too low\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
At this point, there are two approaches that have been tried:
|
|
|
|
1. Find the peak value and call it white if the peak is above
|
|
the 90% line
|
|
|
|
2. Ignore the first and last 20% of the line and count how
|
|
many pixels are above some threshold (75% line was used).
|
|
Call it white if at least 80% of the pixels are above
|
|
the threshold.
|
|
|
|
Both approaches agree 99% of the time, but the first tends to
|
|
be more correct when there is a discrepancy.
|
|
*/
|
|
|
|
/* determine where the peak is */
|
|
peakval = 0;
|
|
for (x = 1; x < 256; x++)
|
|
if (histo[x] > histo[peakval])
|
|
peakval = x;
|
|
|
|
/* return TRUE if it is above the 90% mark */
|
|
result = (peakval > minval + 9 * (maxval - minval) / 10);
|
|
if (PRINTF_WHITE_FLAG)
|
|
printf("White flag %s: peak=%02X thresh=%02X\n", result ? "detected" : "NOT detected", peakval, minval + 9 * (maxval - minval) / 10);
|
|
return result;
|
|
|
|
#ifdef UNUSED_CODE
|
|
{
|
|
int above = 0;
|
|
int thresh;
|
|
|
|
/* alternate approach: */
|
|
|
|
/* ignore the first 1/5 and last 1/5 of the line for the remaining computations */
|
|
source += sourcewidth / 5;
|
|
sourcewidth -= 2 * (sourcewidth / 5);
|
|
|
|
/* count how many values were above the 75% mark of the range */
|
|
thresh = minval + 3 * (maxval - minval) / 4;
|
|
for (x = 0; x < sourcewidth; x++)
|
|
{
|
|
UINT8 yval = source[x] >> sourceshift;
|
|
above += (yval >= thresh);
|
|
}
|
|
/* if at least 80% of the pixels are above the threshold, we'll call it white */
|
|
return ((above * 100) / sourcewidth >= 80);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
vbi_parse_all - parse everything from a video
|
|
frame
|
|
-------------------------------------------------*/
|
|
|
|
void vbi_parse_all(const UINT16 *source, int sourcerowpixels, int sourcewidth, int sourceshift, vbi_metadata *vbi)
|
|
{
|
|
UINT32 bits[2][24];
|
|
UINT8 bitnum;
|
|
|
|
/* first reset it all */
|
|
memset(vbi, 0, sizeof(*vbi));
|
|
|
|
/* get the white flag */
|
|
vbi->white = vbi_parse_white_flag(source + 11 * sourcerowpixels, sourcewidth, sourceshift);
|
|
|
|
/* parse line 16 */
|
|
if (vbi_parse_manchester_code(source + 16 * sourcerowpixels, sourcewidth, sourceshift, 24, bits[0]) == 24)
|
|
for (bitnum = 0; bitnum < 24; bitnum++)
|
|
vbi->line16 = (vbi->line16 << 1) | (bits[0][bitnum] & 1);
|
|
|
|
/* parse line 17 */
|
|
if (vbi_parse_manchester_code(source + 17 * sourcerowpixels, sourcewidth, sourceshift, 24, bits[0]) == 24)
|
|
for (bitnum = 0; bitnum < 24; bitnum++)
|
|
vbi->line17 = (vbi->line17 << 1) | (bits[0][bitnum] & 1);
|
|
|
|
/* parse line 18 */
|
|
if (vbi_parse_manchester_code(source + 18 * sourcerowpixels, sourcewidth, sourceshift, 24, bits[1]) == 24)
|
|
for (bitnum = 0; bitnum < 24; bitnum++)
|
|
vbi->line18 = (vbi->line18 << 1) | (bits[1][bitnum] & 1);
|
|
|
|
/* pick the best out of lines 17/18 */
|
|
/* if we only got one or the other, that's all we have */
|
|
if (vbi->line17 == 0)
|
|
vbi->line1718 = vbi->line18;
|
|
else if (vbi->line18 == 0)
|
|
vbi->line1718 = vbi->line17;
|
|
|
|
/* if they agree, we're golden */
|
|
else if (vbi->line17 == vbi->line18)
|
|
vbi->line1718 = vbi->line17;
|
|
|
|
/* if they don't agree, we have to pick one */
|
|
else
|
|
{
|
|
/* if both are frame numbers, and one is not valid BCD, pick the other */
|
|
if ((vbi->line17 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE && (vbi->line18 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE)
|
|
{
|
|
if ((vbi->line17 & 0xf000) > 0x9000 || (vbi->line17 & 0xf00) > 0x900 || (vbi->line17 & 0xf0) > 0x90 || (vbi->line17 & 0xf) > 0x9)
|
|
vbi->line1718 = vbi->line18;
|
|
else if ((vbi->line18 & 0xf000) > 0x9000 || (vbi->line18 & 0xf00) > 0x900 || (vbi->line18 & 0xf0) > 0x90 || (vbi->line18 & 0xf) > 0x9)
|
|
vbi->line1718 = vbi->line17;
|
|
}
|
|
|
|
/* if still nothing, then scan through the bits and pick the ones with the most confidence */
|
|
if (vbi->line1718 == 0)
|
|
for (bitnum = 0; bitnum < 24; bitnum++)
|
|
vbi->line1718 = (vbi->line1718 << 1) | ((bits[0][bitnum] > bits[1][bitnum]) ? (bits[0][bitnum] & 1) : (bits[1][bitnum] & 1));
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
vbi_metadata_pack - pack the VBI data down
|
|
into a smaller form for storage
|
|
-------------------------------------------------*/
|
|
|
|
void vbi_metadata_pack(UINT8 *dest, UINT32 framenum, const vbi_metadata *vbi)
|
|
{
|
|
dest[0] = framenum >> 16;
|
|
dest[1] = framenum >> 8;
|
|
dest[2] = framenum >> 0;
|
|
dest[3] = vbi->white;
|
|
dest[4] = vbi->line16 >> 16;
|
|
dest[5] = vbi->line16 >> 8;
|
|
dest[6] = vbi->line16 >> 0;
|
|
dest[7] = vbi->line17 >> 16;
|
|
dest[8] = vbi->line17 >> 8;
|
|
dest[9] = vbi->line17 >> 0;
|
|
dest[10] = vbi->line18 >> 16;
|
|
dest[11] = vbi->line18 >> 8;
|
|
dest[12] = vbi->line18 >> 0;
|
|
dest[13] = vbi->line1718 >> 16;
|
|
dest[14] = vbi->line1718 >> 8;
|
|
dest[15] = vbi->line1718 >> 0;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
vbi_metadata_unpack - unpack the VBI data
|
|
from a smaller form into the full structure
|
|
-------------------------------------------------*/
|
|
|
|
void vbi_metadata_unpack(vbi_metadata *vbi, UINT32 *framenum, const UINT8 *source)
|
|
{
|
|
if (framenum != NULL)
|
|
*framenum = (source[0] << 16) | (source[1] << 8) | source[2];
|
|
vbi->white = source[3];
|
|
vbi->line16 = (source[4] << 16) | (source[5] << 8) | source[6];
|
|
vbi->line17 = (source[7] << 16) | (source[8] << 8) | source[9];
|
|
vbi->line18 = (source[10] << 16) | (source[11] << 8) | source[12];
|
|
vbi->line1718 = (source[13] << 16) | (source[14] << 8) | source[15];
|
|
}
|
|
|