/*************************************************************************** ldverify.c Laserdisc AVI/CHD verifier. Copyright Nicola Salmoria and the MAME Team. Visit http://mamedev.org for licensing and usage restrictions. ***************************************************************************/ #include #include #include "aviio.h" #include "bitmap.h" #include "chd.h" #include "vbiparse.h" /*************************************************************************** CONSTANTS ***************************************************************************/ #define REPORT_BLANKS_THRESHOLD 50 /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ typedef struct _movie_info movie_info; struct _movie_info { double framerate; int numframes; int width; int height; int samplerate; int channels; }; /*************************************************************************** GLOBAL VARIABLES ***************************************************************************/ static UINT8 chdinterlaced; static int video_first_whitefield = -1; static int video_saw_leadin = FALSE; static int video_saw_leadout = FALSE; static int video_last_frame = -1; static int video_last_chapter = -1; static int video_cadence = -1; static UINT32 video_cadence_history = 0; static int video_prev_whitefield = -1; static int video_min_overall = 255; static int video_max_overall = 0; static int video_first_blank_frame = -1; static int video_first_blank_field = -1; static int video_num_blank_fields = -1; static int video_first_low_frame = -1; static int video_first_low_field = -1; static int video_num_low_fields = -1; static int video_first_high_frame = -1; static int video_first_high_field = -1; static int video_num_high_fields = -1; static int audio_min_lsample = 32767; static int audio_min_rsample = 32767; static int audio_max_lsample = -32768; static int audio_max_rsample = -32768; static int audio_min_lsample_count = 0; static int audio_min_rsample_count = 0; static int audio_max_lsample_count = 0; static int audio_max_rsample_count = 0; static int audio_sample_count = 0; /*************************************************************************** AVI HANDLING ***************************************************************************/ /*------------------------------------------------- open_avi - open an AVI file and return information about it -------------------------------------------------*/ static void *open_avi(const char *filename, movie_info *info) { const avi_movie_info *aviinfo; avi_error avierr; avi_file *avi; /* open the file */ avierr = avi_open(filename, &avi); if (avierr != AVIERR_NONE) { fprintf(stderr, "Error opening AVI file: %s\n", avi_error_string(avierr)); return NULL; } /* extract movie info */ aviinfo = avi_get_movie_info(avi); info->framerate = (double)aviinfo->video_timescale / (double)aviinfo->video_sampletime; info->numframes = aviinfo->video_numsamples; info->width = aviinfo->video_width; info->height = aviinfo->video_height; info->samplerate = aviinfo->audio_samplerate; info->channels = aviinfo->audio_channels; return avi; } /*------------------------------------------------- read_avi - read a frame from an AVI file -------------------------------------------------*/ static int read_avi(void *file, int frame, bitmap_t *bitmap, INT16 *lsound, INT16 *rsound, int *samples) { const avi_movie_info *aviinfo = avi_get_movie_info((avi_file *)file); UINT32 firstsample = ((UINT64)aviinfo->audio_samplerate * (UINT64)frame * (UINT64)aviinfo->video_sampletime + aviinfo->video_timescale - 1) / (UINT64)aviinfo->video_timescale; UINT32 lastsample = ((UINT64)aviinfo->audio_samplerate * (UINT64)(frame + 1) * (UINT64)aviinfo->video_sampletime + aviinfo->video_timescale - 1) / (UINT64)aviinfo->video_timescale; avi_error avierr; /* read the frame */ avierr = avi_read_video_frame_yuy16((avi_file *)file, frame, bitmap); if (avierr != AVIERR_NONE) return FALSE; /* read the samples */ avierr = avi_read_sound_samples((avi_file *)file, 0, firstsample, lastsample - firstsample, lsound); avierr = avi_read_sound_samples((avi_file *)file, 1, firstsample, lastsample - firstsample, rsound); if (avierr != AVIERR_NONE) return FALSE; *samples = lastsample - firstsample; return TRUE; } /*------------------------------------------------- close_avi - close an AVI file -------------------------------------------------*/ static void close_avi(void *file) { avi_close((avi_file *)file); } /*************************************************************************** CHD HANDLING ***************************************************************************/ /*------------------------------------------------- open_chd - open a CHD file and return information about it -------------------------------------------------*/ static void *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, 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->framerate = (fps * 1000000 + fpsfrac) / 1000000.0; info->numframes = chd_get_header(chd)->totalhunks; info->width = width; info->height = height; info->samplerate = rate; info->channels = channels; /* convert to an interlaced frame */ chdinterlaced = interlaced; if (interlaced) { info->framerate /= 2; info->numframes = (info->numframes + 1) / 2; info->height *= 2; } return chd; } /*------------------------------------------------- read_chd - read a frame from a CHD file -------------------------------------------------*/ static int read_chd(void *file, int frame, bitmap_t *bitmap, INT16 *lsound, INT16 *rsound, int *samples) { av_codec_decompress_config avconfig = { 0 }; int interlace_factor = chdinterlaced ? 2 : 1; bitmap_t fakebitmap; UINT32 numsamples; chd_error chderr; int fieldnum; /* loop over fields */ *samples = 0; for (fieldnum = 0; fieldnum < interlace_factor; fieldnum++) { /* make a fake bitmap for this field */ fakebitmap = *bitmap; fakebitmap.base = BITMAP_ADDR16(&fakebitmap, fieldnum, 0); fakebitmap.rowpixels *= interlace_factor; fakebitmap.height /= interlace_factor; /* configure the codec */ avconfig.video = &fakebitmap; avconfig.maxsamples = 48000; avconfig.actsamples = &numsamples; avconfig.audio[0] = &lsound[*samples]; avconfig.audio[1] = &rsound[*samples]; /* configure the decompressor for this frame */ chd_codec_config((chd_file *)file, AV_CODEC_DECOMPRESS_CONFIG, &avconfig); /* read the frame */ chderr = chd_read((chd_file *)file, frame * interlace_factor + fieldnum, NULL); if (chderr != CHDERR_NONE) return FALSE; /* account for samples read */ *samples += numsamples; } return TRUE; } /*------------------------------------------------- close_chd - close a CHD file -------------------------------------------------*/ static void close_chd(void *file) { chd_close((chd_file *)file); } /*************************************************************************** CORE IMPLEMENTATION ***************************************************************************/ /*------------------------------------------------- verify_video - verify video frame -------------------------------------------------*/ static void verify_video(int frame, bitmap_t *bitmap) { const int fields_per_frame = 2; int fieldnum; /* loop over fields */ for (fieldnum = 0; fieldnum < fields_per_frame; fieldnum++) { int yminval, ymaxval, cbminval, cbmaxval, crminval, crmaxval; int field = frame * fields_per_frame + fieldnum; int x, y, pixels, remaining; UINT32 yhisto[256] = { 0 }; UINT32 crhisto[256] = { 0 }; UINT32 cbhisto[256] = { 0 }; vbi_metadata metadata; /* output status */ if (frame % 10 == 0 && fieldnum == 0) fprintf(stderr, "%6d.%d...\r", frame, fieldnum); /* parse the VBI data */ vbi_parse_all(BITMAP_ADDR16(bitmap, fieldnum, 0), bitmap->rowpixels * 2, bitmap->width, 8, &metadata); /* if we have data in both 17 and 18, it should match */ if (metadata.line17 != 0 && metadata.line18 != 0 && metadata.line17 != metadata.line18) { printf("%6d.%d: line 17 and 18 data does not match (17=%06X 18=%06X) (WARNING)\n", frame, fieldnum, metadata.line17, metadata.line18); printf("%6d.%d: selected %06X based on bit confidence\n", frame, fieldnum, metadata.line1718); } /* is this a lead-in code? */ if (metadata.line1718 == VBI_CODE_LEADIN) { /* if we haven't seen lead-in yet, detect it */ if (!video_saw_leadin) { video_saw_leadin = TRUE; printf("%6d.%d: lead-in code detected\n", frame, fieldnum); } /* if we've previously seen chapters/frames, that's weird */ if (video_last_frame != -1 || video_last_chapter != -1) printf("%6d.%d: lead-in code detected after frame/chapter data (WARNING)\n", frame, fieldnum); } /* is this a lead-out code? */ if (metadata.line1718 == VBI_CODE_LEADOUT) { /* if we haven't seen lead-in yet, detect it */ if (!video_saw_leadout) { video_saw_leadout = TRUE; printf("%6d.%d: lead-out code detected\n", frame, fieldnum); if (video_last_frame != -1) printf("%6d.%d: final frame number was %d\n", frame, fieldnum, video_last_frame); else printf("%6d.%d: never detected any frame numbers (ERROR)\n", frame, fieldnum); } /* if we've previously seen chapters/frames, that's weird */ if (video_last_frame == -1) printf("%6d.%d: lead-out code detected with no frames detected beforehand (WARNING)\n", frame, fieldnum); } /* is this a frame code? */ if ((metadata.line1718 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE) { int framenum = VBI_CAV_PICTURE(metadata.line1718); /* did we see any leadin? */ if (!video_saw_leadin) { printf("%6d.%d: detected frame number but never saw any lead-in (WARNING)\n", frame, fieldnum); video_saw_leadin = TRUE; } /* if this is the first frame, make sure it's 1 */ if (video_last_frame == -1) { if (framenum == 0) printf("%6d.%d: detected frame 0\n", frame, fieldnum); else if (framenum == 1) printf("%6d.%d: detected frame 1\n", frame, fieldnum); else printf("%6d.%d: first frame number is not 0 or 1 (%d) (ERROR)\n", frame, fieldnum, framenum); } /* print an update every 10000 frames */ if (framenum != 0 && framenum % 10000 == 0) printf("%6d.%d: detected frame %d\n", frame, fieldnum, framenum); /* if this frame is not consecutive, it's an error */ if (video_last_frame != -1 && framenum != video_last_frame + 1) printf("%6d.%d: gap in frame number sequence (%d->%d) (ERROR)\n", frame, fieldnum, video_last_frame, framenum); /* remember the frame number */ video_last_frame = framenum; /* if we've seen a white flag before, but it's not here, warn */ if (video_first_whitefield != -1 && !metadata.white) printf("%6d.%d: detected frame number but no white flag (WARNING)\n", frame, fieldnum); } /* is the whiteflag set? */ if (metadata.white) { /* if this is the first white flag we see, count it */ if (video_first_whitefield == -1) { video_first_whitefield = field; printf("%6d.%d: first white flag seen\n", frame, fieldnum); } /* if we've seen frame numbers before, but not here, warn */ if (video_last_frame != -1 && (metadata.line1718 & VBI_MASK_CAV_PICTURE) != VBI_CODE_CAV_PICTURE) printf("%6d.%d: detected white flag but no frame number (WARNING)\n", frame, fieldnum); } /* if this is the start of a frame, handle cadence */ if (metadata.white || (metadata.line1718 & VBI_MASK_CAV_PICTURE) == VBI_CODE_CAV_PICTURE) { /* if we've seen frames, but we're not yet to the lead-out, check the cadence */ if (video_last_frame != -1 && !video_saw_leadout) { /* make sure we have a proper history */ if (video_prev_whitefield != -1) video_cadence_history = (video_cadence_history << 4) | ((field - video_prev_whitefield) & 0x0f); video_prev_whitefield = field; /* if we don't know our cadence yet, determine it */ if (video_cadence == -1 && (video_cadence_history & 0xf00) != 0) { if ((video_cadence_history & 0xfff) == 0x222) { printf("%6d.%d: detected 2:2 cadence\n", frame, fieldnum); video_cadence = 4; } else if ((video_cadence_history & 0xfff) == 0x323) { printf("%6d.%d: detected 3:2 cadence\n", frame, fieldnum); video_cadence = 5; } else if ((video_cadence_history & 0xfff) == 0x232) { printf("%6d.%d: detected 2:3 cadence\n", frame, fieldnum); video_cadence = 5; } else { printf("%6d.%d: unknown cadence (history %d:%d:%d) (WARNING)\n", frame, fieldnum, (video_cadence_history >> 8) & 15, (video_cadence_history >> 4) & 15, video_cadence_history & 15); } } /* if we know our cadence, make sure we stick to it */ if (video_cadence != -1) { if (video_cadence == 4 && (video_cadence_history & 0xfff) != 0x222) { printf("%6d.%d: missed cadence (history %d:%d:%d) (WARNING)\n", frame, fieldnum, (video_cadence_history >> 8) & 15, (video_cadence_history >> 4) & 15, video_cadence_history & 15); video_cadence = -1; video_cadence_history = 0; } else if (video_cadence == 5 && (video_cadence_history & 0xfff) != 0x323 && (video_cadence_history & 0xfff) != 0x232) { printf("%6d.%d: missed cadence (history %d:%d:%d) (WARNING)\n", frame, fieldnum, (video_cadence_history >> 8) & 15, (video_cadence_history >> 4) & 15, video_cadence_history & 15); video_cadence = -1; video_cadence_history = 0; } } } } /* now examine the active video signal */ pixels = 0; for (y = 22*2 + fieldnum; y < bitmap->height; y += 2) { for (x = 16; x < 720 - 16; x++) { yhisto[*BITMAP_ADDR16(bitmap, y, x) >> 8]++; if (x % 2 == 0) cbhisto[*BITMAP_ADDR16(bitmap, y, x) & 0xff]++; else crhisto[*BITMAP_ADDR16(bitmap, y, x) & 0xff]++; } pixels += 720 - 16 - 16; } /* remove the top/bottom 0.1% of Y */ remaining = pixels / 1000; for (yminval = 0; (remaining -= yhisto[yminval]) >= 0; yminval++) ; remaining = pixels / 1000; for (ymaxval = 255; (remaining -= yhisto[ymaxval]) >= 0; ymaxval--) ; /* remove the top/bottom 0.1% of Cb */ remaining = pixels / 500; for (cbminval = 0; (remaining -= cbhisto[cbminval]) >= 0; cbminval++) ; remaining = pixels / 500; for (cbmaxval = 255; (remaining -= cbhisto[cbmaxval]) >= 0; cbmaxval--) ; /* remove the top/bottom 0.1% of Cr */ remaining = pixels / 500; for (crminval = 0; (remaining -= crhisto[crminval]) >= 0; crminval++) ; remaining = pixels / 500; for (crmaxval = 255; (remaining -= crhisto[crmaxval]) >= 0; crmaxval--) ; /* track blank frames */ if (ymaxval - yminval < 10 && cbmaxval - cbminval < 10 && crmaxval - cbmaxval < 10) { if (video_first_blank_frame == -1) { video_first_blank_frame = frame; video_first_blank_field = fieldnum; video_num_blank_fields = 0; } video_num_blank_fields++; } else if (video_num_blank_fields > 0) { if (video_num_blank_fields >= REPORT_BLANKS_THRESHOLD) printf("%6d.%d-%6d.%d: blank frames for %d fields (INFO)\n", video_first_blank_frame, video_first_blank_field, frame, fieldnum, video_num_blank_fields); video_first_blank_frame = video_first_blank_field = video_num_blank_fields = -1; } /* update the overall min/max */ video_min_overall = MIN(yminval, video_min_overall); video_max_overall = MAX(ymaxval, video_max_overall); /* track low fields */ if (yminval <= 0) { if (video_first_low_frame == -1) { video_first_low_frame = frame; video_first_low_field = fieldnum; video_num_low_fields = 0; } video_num_low_fields++; } else if (video_num_low_fields > 0) { printf("%6d.%d-%6d.%d: active video signal level low for %d fields (WARNING)\n", video_first_low_frame, video_first_low_field, frame, fieldnum, video_num_low_fields); video_first_low_frame = video_first_low_field = video_num_low_fields = -1; } /* track high fields */ if (ymaxval >= 255) { if (video_first_high_frame == -1) { video_first_high_frame = frame; video_first_high_field = fieldnum; video_num_high_fields = 0; } video_num_high_fields++; } else if (video_num_high_fields > 0) { printf("%6d.%d-%6d.%d: active video signal level high for %d fields (WARNING)\n", video_first_high_frame, video_first_high_field, frame, fieldnum, video_num_high_fields); video_first_high_frame = video_first_high_field = video_num_high_fields = -1; } } } /*------------------------------------------------- verify_video_final - final verification -------------------------------------------------*/ static void verify_video_final(int frame, bitmap_t *bitmap) { int fields_per_frame = (bitmap->height >= 288) ? 2 : 1; int field = frame * fields_per_frame; /* did we ever see any white flags? */ if (video_first_whitefield == -1) printf("Track %6d.%d: never saw any white flags (WARNING)\n", field / fields_per_frame, 0); /* did we ever see any lead-out? */ if (!video_saw_leadout) printf("Track %6d.%d: never saw any lead-out (WARNING)\n", field / fields_per_frame, 0); /* any remaining high/low reports? */ if (video_num_blank_fields >= REPORT_BLANKS_THRESHOLD) printf("%6d.%d-%6d.%d: blank frames for %d fields (INFO)\n", video_first_blank_frame, video_first_blank_field, frame, 0, video_num_blank_fields); if (video_num_low_fields > 0) printf("%6d.%d-%6d.%d: active video signal level low for %d fields (WARNING)\n", video_first_low_frame, video_first_low_field, frame, 0, video_num_low_fields); if (video_num_high_fields > 0) printf("%6d.%d-%6d.%d: active video signal level high for %d fields (WARNING)\n", video_first_high_frame, video_first_high_field, frame, 0, video_num_high_fields); /* summary info */ printf("\nVideo summary:\n"); printf(" Overall video range: %d-%d (%02X-%02X)\n", video_min_overall, video_max_overall, video_min_overall, video_max_overall); } /*------------------------------------------------- verify_audio - verify audio data -------------------------------------------------*/ static void verify_audio(const INT16 *lsound, const INT16 *rsound, int samples) { int sampnum; /* count the overall samples */ audio_sample_count += samples; /* iterate over samples, tracking min/max */ for (sampnum = 0; sampnum < samples; sampnum++) { /* did we hit a minimum on the left? */ if (lsound[sampnum] < audio_min_lsample) { audio_min_lsample = lsound[sampnum]; audio_min_lsample_count = 1; } else if (lsound[sampnum] == audio_min_lsample) audio_min_lsample_count++; /* did we hit a maximum on the left? */ if (lsound[sampnum] > audio_max_lsample) { audio_max_lsample = lsound[sampnum]; audio_max_lsample_count = 1; } else if (lsound[sampnum] == audio_max_lsample) audio_max_lsample_count++; /* did we hit a minimum on the right? */ if (rsound[sampnum] < audio_min_rsample) { audio_min_rsample = rsound[sampnum]; audio_min_rsample_count = 1; } else if (rsound[sampnum] == audio_min_rsample) audio_min_rsample_count++; /* did we hit a maximum on the right? */ if (rsound[sampnum] > audio_max_rsample) { audio_max_rsample = rsound[sampnum]; audio_max_rsample_count = 1; } else if (rsound[sampnum] == audio_max_rsample) audio_max_rsample_count++; } } /*------------------------------------------------- verify_audio_final - final verification -------------------------------------------------*/ static void verify_audio_final(void) { printf("\nAudio summary:\n"); printf(" Overall channel 0 range: %d-%d (%04X-%04X)\n", audio_min_lsample, audio_max_lsample, (UINT16)audio_min_lsample, (UINT16)audio_max_lsample); printf(" Overall channel 1 range: %d-%d (%04X-%04X)\n", audio_min_rsample, audio_max_rsample, (UINT16)audio_min_rsample, (UINT16)audio_max_rsample); } /*------------------------------------------------- usage - display program usage -------------------------------------------------*/ static int usage(void) { fprintf(stderr, "Usage: \n"); fprintf(stderr, " ldverify \n"); return 1; } /*------------------------------------------------- main - main entry point -------------------------------------------------*/ int main(int argc, char *argv[]) { movie_info info = { 0 }; INT16 *lsound, *rsound; const char *srcfile; bitmap_t *bitmap; int srcfilelen; int samples = 0; void *file; int isavi; int frame; /* verify arguments */ if (argc < 2) return usage(); srcfile = argv[1]; /* check extension of file */ srcfilelen = strlen(srcfile); if (srcfilelen < 4) return usage(); if (tolower((UINT8)srcfile[srcfilelen-3]) == 'a' && tolower((UINT8)srcfile[srcfilelen-2]) == 'v' && tolower((UINT8)srcfile[srcfilelen-1]) == 'i') isavi = TRUE; else if (tolower((UINT8)srcfile[srcfilelen-3]) == 'c' && tolower((UINT8)srcfile[srcfilelen-2]) == 'h' && tolower((UINT8)srcfile[srcfilelen-1]) == 'd') isavi = FALSE; else return usage(); /* open the file */ printf("Processing file: %s\n", srcfile); file = isavi ? open_avi(srcfile, &info) : open_chd(srcfile, &info); if (file == NULL) { fprintf(stderr, "Unable to open file '%s'\n", srcfile); return 1; } /* comment on the video dimensions */ printf("Video dimensions: %dx%d\n", info.width, info.height); if (info.width != 720) printf("WARNING: Unexpected video width (should be 720)\n"); if (info.height != 524) printf("WARNING: Unexpected video height (should be 262 or 524)\n"); /* comment on the video frame rate */ printf("Video frame rate: %.2fHz\n", info.framerate); if ((int)(info.framerate * 100.0 + 0.5) != 2997) printf("WARNING: Unexpected frame rate (should be 29.97Hz)\n"); /* comment on the sample rate */ printf("Sample rate: %dHz\n", info.samplerate); if (info.samplerate != 48000) printf("WARNING: Unexpected sampele rate (should be 48000Hz)\n"); /* allocate a bitmap */ bitmap = bitmap_alloc(info.width, info.height, BITMAP_FORMAT_YUY16); if (bitmap == NULL) { isavi ? close_avi(file) : close_chd(file); fprintf(stderr, "Out of memory creating %dx%d bitmap\n", info.width, info.height); return 1; } /* allocate sound buffers */ lsound = (INT16 *)malloc(info.samplerate * sizeof(*lsound)); rsound = (INT16 *)malloc(info.samplerate * sizeof(*rsound)); if (lsound == NULL || rsound == NULL) { isavi ? close_avi(file) : close_chd(file); bitmap_free(bitmap); if (rsound != NULL) free(rsound); if (lsound != NULL) free(lsound); fprintf(stderr, "Out of memory allocating sound buffers of %d bytes\n", (INT32)(info.samplerate * sizeof(*rsound))); return 1; } /* loop over frames */ frame = 0; while (isavi ? read_avi(file, frame, bitmap, lsound, rsound, &samples) : read_chd(file, frame, bitmap, lsound, rsound, &samples)) { verify_video(frame, bitmap); verify_audio(lsound, rsound, samples); frame++; } /* close the files */ isavi ? close_avi(file) : close_chd(file); /* final output */ verify_video_final(frame, bitmap); verify_audio_final(); /* free memory */ bitmap_free(bitmap); free(lsound); free(rsound); return 0; }