From 21650c2660077a7bc85b90d2d44637ce1a5d0980 Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Wed, 29 Jan 2020 06:58:57 +0100 Subject: [PATCH 01/14] Changed method of handling available sources in ld-diffdod (to fix some bugs) --- tools/ld-diffdod/tbcsources.cpp | 128 +++++++++++++++++--------------- tools/ld-diffdod/tbcsources.h | 12 +-- 2 files changed, 75 insertions(+), 65 deletions(-) diff --git a/tools/ld-diffdod/tbcsources.cpp b/tools/ld-diffdod/tbcsources.cpp index 4e4e00f35..016689b91 100644 --- a/tools/ld-diffdod/tbcsources.cpp +++ b/tools/ld-diffdod/tbcsources.cpp @@ -240,26 +240,26 @@ void TbcSources::performFrameDiffDod(qint32 targetVbiFrame, qint32 dodThreshold, QVector secondFields = getFieldData(targetVbiFrame, false); // Create a differential map of the fields for the avaialble frames (based on the DOD threshold) - QVector> firstFieldsDiff = getFieldDiff(firstFields, dodThreshold); - QVector> secondFieldsDiff = getFieldDiff(secondFields, dodThreshold); + QVector> firstFieldsDiff = getFieldDiff(targetVbiFrame, firstFields, dodThreshold); + QVector> secondFieldsDiff = getFieldDiff(targetVbiFrame, secondFields, dodThreshold); // Perform luma clip check? if (lumaClip) { - performLumaClip(firstFields, firstFieldsDiff); - performLumaClip(secondFields, secondFieldsDiff); + performLumaClip(targetVbiFrame, firstFields, firstFieldsDiff); + performLumaClip(targetVbiFrame, secondFields, secondFieldsDiff); } // Create the drop-out metadata based on the differential map of the fields - QVector firstFieldDropouts = getFieldDropouts(firstFieldsDiff); - QVector secondFieldDropouts = getFieldDropouts(secondFieldsDiff); + QVector firstFieldDropouts = getFieldDropouts(targetVbiFrame, firstFieldsDiff); + QVector secondFieldDropouts = getFieldDropouts(targetVbiFrame, secondFieldsDiff); // Concatenate dropouts on the same line that are close together (to cut down on the // amount of generated metadata with noisy/bad sources) - concatenateFieldDropouts(firstFieldDropouts); - concatenateFieldDropouts(secondFieldDropouts); + concatenateFieldDropouts(targetVbiFrame, firstFieldDropouts); + concatenateFieldDropouts(targetVbiFrame, secondFieldDropouts); // Write the dropout metadata back to the sources - writeDropoutMetadata(firstFieldDropouts, secondFieldDropouts, targetVbiFrame); + writeDropoutMetadata(targetVbiFrame, firstFieldDropouts, secondFieldDropouts); } // Get the field data for the specified frame @@ -278,14 +278,16 @@ QVector TbcSources::getFieldData(qint32 targetVbiFrame, bool qInfo() << "Only" << availableSourcesForFrame.size() << "source frames are available - can not perform diffDOD for VBI frame" << targetVbiFrame; return QVector(); } - qDebug() << "TbcSources::performFrameDiffDod(): Processing VBI Frame" << targetVbiFrame << "-" << availableSourcesForFrame.size() << "sources available"; + + // Only display on first field (otherwise we will get 2 of the same debug) + if (isFirstField) qDebug() << "TbcSources::performFrameDiffDod(): Processing VBI Frame" << targetVbiFrame << "-" << availableSourcesForFrame.size() << "sources available"; // Get the field data for the frame from all of the available sources and copy locally QVector fields; - fields.resize(availableSourcesForFrame.size()); + fields.resize(getNumberOfAvailableSources()); QVector sourceFirstFieldPointer; - sourceFirstFieldPointer.resize(availableSourcesForFrame.size()); + sourceFirstFieldPointer.resize(getNumberOfAvailableSources()); for (qint32 sourceNo = 0; sourceNo < availableSourcesForFrame.size(); sourceNo++) { qint32 fieldNumber = -1; @@ -295,14 +297,14 @@ QVector TbcSources::getFieldData(qint32 targetVbiFrame, bool ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, availableSourcesForFrame[sourceNo])); // Copy the data locally - fields[sourceNo] = (sourceVideos[availableSourcesForFrame[sourceNo]]->sourceVideo.getVideoField(fieldNumber)); + fields[availableSourcesForFrame[sourceNo]] = (sourceVideos[availableSourcesForFrame[sourceNo]]->sourceVideo.getVideoField(fieldNumber)); // Filter out the chroma information from the fields leaving just luma Filters filters; if (videoParameters.isSourcePal) { - filters.palLumaFirFilter(fields[sourceNo].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); + filters.palLumaFirFilter(fields[availableSourcesForFrame[sourceNo]].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); } else { - filters.ntscLumaFirFilter(fields[sourceNo].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); + filters.ntscLumaFirFilter(fields[availableSourcesForFrame[sourceNo]].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); } // Remove the existing field dropout metadata for the field @@ -314,18 +316,21 @@ QVector TbcSources::getFieldData(qint32 targetVbiFrame, bool // Create a differential map of the fields (this is a map of each dot in the field and how many // other sources it differs from) -QVector> TbcSources::getFieldDiff(QVector &fields, qint32 dodThreshold) +QVector> TbcSources::getFieldDiff(qint32 targetVbiFrame, QVector &fields, qint32 dodThreshold) { // Get the metadata for the video parameters (all sources are the same, so just grab from the first) LdDecodeMetaData::VideoParameters videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); + // Check how many source frames are available for the current frame + QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); + // Make a vector to store the result of the diff QVector> fieldDiff; - fieldDiff.resize(fields.size()); + fieldDiff.resize(getNumberOfAvailableSources()); // Set the diff vector elements to zero (and resize the sub-vectors) - for (qint32 sourceCounter = 0; sourceCounter < fields.size(); sourceCounter++) { - fieldDiff[sourceCounter].fill(0, videoParameters.fieldHeight * videoParameters.fieldWidth); + for (qint32 sourceNo = 0; sourceNo < availableSourcesForFrame.size(); sourceNo++) { + fieldDiff[availableSourcesForFrame[sourceNo]].fill(0, videoParameters.fieldHeight * videoParameters.fieldWidth); } // Process the fields one line at a time @@ -335,19 +340,19 @@ QVector> TbcSources::getFieldDiff(QVector &fi // Compare all combinations of source and target field lines // Note: the source is the field line we are building a DO map for, target is the field line we are // comparing the source to. - for (qint32 sourceCounter = 0; sourceCounter < fields.size(); sourceCounter++) { - for (qint32 targetCounter = 0; targetCounter < fields.size(); targetCounter++) { - if (sourceCounter != targetCounter) { + for (qint32 sourceNo = 0; sourceNo < availableSourcesForFrame.size(); sourceNo++) { + for (qint32 targetCounter = 0; targetCounter < availableSourcesForFrame.size(); targetCounter++) { + if (availableSourcesForFrame[sourceNo] != availableSourcesForFrame[targetCounter]) { for (qint32 x = 0; x < videoParameters.fieldWidth; x++) { // Get the IRE values for target and source fields, cast to 32 bit signed - qint32 targetIre = static_cast(fields[targetCounter][x + startOfLinePointer]); - qint32 sourceIre = static_cast(fields[sourceCounter][x + startOfLinePointer]); + qint32 targetIre = static_cast(fields[availableSourcesForFrame[targetCounter]][x + startOfLinePointer]); + qint32 sourceIre = static_cast(fields[availableSourcesForFrame[sourceNo]][x + startOfLinePointer]); // Diff the 16-bit pixel values of the first fields qint32 difference = abs(targetIre - sourceIre); // If the source and target differ, increment the fieldDiff - if (difference > dodThreshold) fieldDiff[sourceCounter][x + startOfLinePointer] += 1; + if (difference > dodThreshold) fieldDiff[availableSourcesForFrame[sourceNo]][x + startOfLinePointer] += 1; } } } @@ -358,23 +363,26 @@ QVector> TbcSources::getFieldDiff(QVector &fi } // Perform a luma clip check on the field -void TbcSources::performLumaClip(QVector &fields, QVector> &fieldsDiff) +void TbcSources::performLumaClip(qint32 targetVbiFrame, QVector &fields, QVector> &fieldsDiff) { // Get the metadata for the video parameters (all sources are the same, so just grab from the first) LdDecodeMetaData::VideoParameters videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); + // Check how many source frames are available for the current frame + QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); + // Process the fields one line at a time for (qint32 y = videoParameters.firstActiveFieldLine; y < videoParameters.lastActiveFieldLine; y++) { qint32 startOfLinePointer = y * videoParameters.fieldWidth; - for (qint32 sourceCounter = 0; sourceCounter < fields.size(); sourceCounter++) { + for (qint32 sourceNo = 0; sourceNo < availableSourcesForFrame.size(); sourceNo++) { // Set the clipping levels qint32 blackClipLevel = videoParameters.black16bIre - 4000; qint32 whiteClipLevel = videoParameters.white16bIre + 4000; for (qint32 x = videoParameters.activeVideoStart; x < videoParameters.activeVideoEnd; x++) { // Get the IRE value for the source field, cast to 32 bit signed - qint32 sourceIre = static_cast(fields[sourceCounter][x + startOfLinePointer]); + qint32 sourceIre = static_cast(fields[availableSourcesForFrame[sourceNo]][x + startOfLinePointer]); // Check for a luma clip event if ((sourceIre < blackClipLevel) || (sourceIre > whiteClipLevel)) { @@ -391,14 +399,14 @@ void TbcSources::performLumaClip(QVector &fields, QVector minX; i--) { - qint32 ire = static_cast(fields[sourceCounter][x + startOfLinePointer]); + qint32 ire = static_cast(fields[availableSourcesForFrame[sourceNo]][x + startOfLinePointer]); if (ire < videoParameters.black16bIre || ire > videoParameters.white16bIre) { startX = i; } } for (qint32 i = x+1; i < maxX; i++) { - qint32 ire = static_cast(fields[sourceCounter][x + startOfLinePointer]); + qint32 ire = static_cast(fields[availableSourcesForFrame[sourceNo]][x + startOfLinePointer]); if (ire < videoParameters.black16bIre || ire > videoParameters.white16bIre) { endX = i; } @@ -406,7 +414,7 @@ void TbcSources::performLumaClip(QVector &fields, QVector &fields, QVector TbcSources::getFieldDropouts(QVector> &fieldsDiff) +QVector TbcSources::getFieldDropouts(qint32 targetVbiFrame, QVector> &fieldsDiff) { // Get the metadata for the video parameters (all sources are the same, so just grab from the first) LdDecodeMetaData::VideoParameters videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); + // Check how many source frames are available for the current frame + QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); + // This compares each available source against all other available sources to determine where the source differs. // If any of the frame's contents do not match that of the other sources, the frame's pixels are marked as dropouts. QVector frameDropouts; - frameDropouts.resize(fieldsDiff.size()); + frameDropouts.resize(getNumberOfAvailableSources()); // Define the area in which DOD should be performed qint32 areaStart = videoParameters.colourBurstStart; @@ -442,10 +453,10 @@ QVector TbcSources::getFieldDropouts(QVector TbcSources::getFieldDropouts(QVector(fieldsDiff[sourceNo][x + startOfLinePointer]) <= diffCompareThreshold) { + if (static_cast(fieldsDiff[availableSourcesForFrame[sourceNo]][x + startOfLinePointer]) <= diffCompareThreshold) { // Current X is not a dropout if (doCounter > 0) { doCounter--; if (doCounter == 0) { // Mark the previous x as the end of the dropout - frameDropouts[sourceNo].startx.append(doStart); - frameDropouts[sourceNo].endx.append(x - 1); - frameDropouts[sourceNo].fieldLine.append(doFieldLine); + frameDropouts[availableSourcesForFrame[sourceNo]].startx.append(doStart); + frameDropouts[availableSourcesForFrame[sourceNo]].endx.append(x - 1); + frameDropouts[availableSourcesForFrame[sourceNo]].fieldLine.append(doFieldLine); } } } else { @@ -482,9 +493,9 @@ QVector TbcSources::getFieldDropouts(QVector 0) { doCounter = 0; - frameDropouts[sourceNo].startx.append(doStart); - frameDropouts[sourceNo].endx.append(areaEnd); - frameDropouts[sourceNo].fieldLine.append(doFieldLine); + frameDropouts[availableSourcesForFrame[sourceNo]].startx.append(doStart); + frameDropouts[availableSourcesForFrame[sourceNo]].endx.append(areaEnd); + frameDropouts[availableSourcesForFrame[sourceNo]].fieldLine.append(doFieldLine); } } // Next source @@ -493,14 +504,13 @@ QVector TbcSources::getFieldDropouts(QVector &firstFieldDropouts, - QVector &secondFieldDropouts, qint32 targetVbiFrame) +void TbcSources::writeDropoutMetadata(qint32 targetVbiFrame, QVector &firstFieldDropouts, + QVector &secondFieldDropouts) { // Check how many source frames are available for the current frame QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); // Write the first and second field line metadata back to the source - QVector totalForSource(availableSourcesForFrame.size()); for (qint32 sourceNo = 0; sourceNo < availableSourcesForFrame.size(); sourceNo++) { // Get the required field numbers qint32 firstFieldNumber = sourceVideos[availableSourcesForFrame[sourceNo]]-> @@ -516,39 +526,39 @@ void TbcSources::writeDropoutMetadata(QVector &first "frame" << targetVbiFrame << "fields" << firstFieldNumber << "/" << secondFieldNumber << "- Dropout records" << totalFirstDropouts << "/" << totalSecondDropouts; - // Record the total number of DOs for this source - totalForSource[sourceNo] = totalFirstDropouts + totalSecondDropouts; - // Write the metadata - sourceVideos[availableSourcesForFrame[sourceNo]]->ldDecodeMetaData.updateFieldDropOuts(firstFieldDropouts[sourceNo], firstFieldNumber); - sourceVideos[availableSourcesForFrame[sourceNo]]->ldDecodeMetaData.updateFieldDropOuts(secondFieldDropouts[sourceNo], secondFieldNumber); + sourceVideos[availableSourcesForFrame[sourceNo]]->ldDecodeMetaData.updateFieldDropOuts(firstFieldDropouts[availableSourcesForFrame[sourceNo]], firstFieldNumber); + sourceVideos[availableSourcesForFrame[sourceNo]]->ldDecodeMetaData.updateFieldDropOuts(secondFieldDropouts[availableSourcesForFrame[sourceNo]], secondFieldNumber); } } // Method to concatenate dropouts on the same line that are close together // (to cut down on the amount of generated metadata with noisy/bad sources) -void TbcSources::concatenateFieldDropouts(QVector &dropouts) +void TbcSources::concatenateFieldDropouts(qint32 targetVbiFrame, QVector &dropouts) { + // Check how many source frames are available for the current frame + QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); + // This variable controls the minimum allowed gap between dropouts // if the gap between the end of the last dropout and the start of // the next is less than minimumGap, the two dropouts will be // concatenated together qint32 minimumGap = 50; - for (qint32 sourceNo = 0; sourceNo < dropouts.size(); sourceNo++) { + for (qint32 sourceNo = 0; sourceNo < availableSourcesForFrame.size(); sourceNo++) { // Start from 1 as 0 has no previous dropout qint32 i = 1; - while (i < dropouts[sourceNo].startx.size()) { + while (i < dropouts[availableSourcesForFrame[sourceNo]].startx.size()) { // Is the current dropout on the same field line as the last? - if (dropouts[sourceNo].fieldLine[i - 1] == dropouts[sourceNo].fieldLine[i]) { - if ((dropouts[sourceNo].endx[i - 1] + minimumGap) > (dropouts[sourceNo].startx[i])) { + if (dropouts[availableSourcesForFrame[sourceNo]].fieldLine[i - 1] == dropouts[availableSourcesForFrame[sourceNo]].fieldLine[i]) { + if ((dropouts[availableSourcesForFrame[sourceNo]].endx[i - 1] + minimumGap) > (dropouts[availableSourcesForFrame[sourceNo]].startx[i])) { // Concatenate - dropouts[sourceNo].endx[i - 1] = dropouts[sourceNo].endx[i]; + dropouts[availableSourcesForFrame[sourceNo]].endx[i - 1] = dropouts[availableSourcesForFrame[sourceNo]].endx[i]; // Remove the current dropout - dropouts[sourceNo].startx.removeAt(i); - dropouts[sourceNo].endx.removeAt(i); - dropouts[sourceNo].fieldLine.removeAt(i); + dropouts[availableSourcesForFrame[sourceNo]].startx.removeAt(i); + dropouts[availableSourcesForFrame[sourceNo]].endx.removeAt(i); + dropouts[availableSourcesForFrame[sourceNo]].fieldLine.removeAt(i); } } diff --git a/tools/ld-diffdod/tbcsources.h b/tools/ld-diffdod/tbcsources.h index 1cda62765..efd4f7390 100644 --- a/tools/ld-diffdod/tbcsources.h +++ b/tools/ld-diffdod/tbcsources.h @@ -70,12 +70,12 @@ class TbcSources : public QObject void performFrameDiffDod(qint32 targetVbiFrame, qint32 dodOnThreshold, bool lumaClip); QVector getFieldData(qint32 targetVbiFrame, bool isFirstField); - QVector > getFieldDiff(QVector &fields, qint32 dodThreshold); - void performLumaClip(QVector &fields, QVector> &fieldsDiff); - QVector getFieldDropouts(QVector > &fieldsDiff); - void writeDropoutMetadata(QVector &firstFieldDropouts, - QVector &secondFieldDropouts, qint32 targetVbiFrame); - void concatenateFieldDropouts(QVector &dropouts); + QVector > getFieldDiff(qint32 targetVbiFrame, QVector &fields, qint32 dodThreshold); + void performLumaClip(qint32 targetVbiFrame, QVector &fields, QVector> &fieldsDiff); + QVector getFieldDropouts(qint32 targetVbiFrame, QVector > &fieldsDiff); + void writeDropoutMetadata(qint32 targetVbiFrame, QVector &firstFieldDropouts, + QVector &secondFieldDropouts); + void concatenateFieldDropouts(qint32 targetVbiFrame, QVector &dropouts); QVector getAvailableSourcesForFrame(qint32 vbiFrameNumber); bool setDiscTypeAndMaxMinFrameVbi(qint32 sourceNumber); From 79a759cb578c983aa30e3d88b71134c354acf669 Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Wed, 29 Jan 2020 07:32:43 +0100 Subject: [PATCH 02/14] Improvements to code readability --- tools/ld-diffdod/tbcsources.cpp | 107 ++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 48 deletions(-) diff --git a/tools/ld-diffdod/tbcsources.cpp b/tools/ld-diffdod/tbcsources.cpp index 016689b91..dc3fd9c03 100644 --- a/tools/ld-diffdod/tbcsources.cpp +++ b/tools/ld-diffdod/tbcsources.cpp @@ -289,26 +289,27 @@ QVector TbcSources::getFieldData(qint32 targetVbiFrame, bool QVector sourceFirstFieldPointer; sourceFirstFieldPointer.resize(getNumberOfAvailableSources()); - for (qint32 sourceNo = 0; sourceNo < availableSourcesForFrame.size(); sourceNo++) { + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source qint32 fieldNumber = -1; - if (isFirstField) fieldNumber = sourceVideos[availableSourcesForFrame[sourceNo]]-> - ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, availableSourcesForFrame[sourceNo])); - else fieldNumber = sourceVideos[availableSourcesForFrame[sourceNo]]-> - ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, availableSourcesForFrame[sourceNo])); + if (isFirstField) fieldNumber = sourceVideos[sourceNo]-> + ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); + else fieldNumber = sourceVideos[sourceNo]-> + ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); // Copy the data locally - fields[availableSourcesForFrame[sourceNo]] = (sourceVideos[availableSourcesForFrame[sourceNo]]->sourceVideo.getVideoField(fieldNumber)); + fields[sourceNo] = (sourceVideos[sourceNo]->sourceVideo.getVideoField(fieldNumber)); // Filter out the chroma information from the fields leaving just luma Filters filters; if (videoParameters.isSourcePal) { - filters.palLumaFirFilter(fields[availableSourcesForFrame[sourceNo]].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); + filters.palLumaFirFilter(fields[sourceNo].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); } else { - filters.ntscLumaFirFilter(fields[availableSourcesForFrame[sourceNo]].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); + filters.ntscLumaFirFilter(fields[sourceNo].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); } // Remove the existing field dropout metadata for the field - sourceVideos[availableSourcesForFrame[sourceNo]]->ldDecodeMetaData.clearFieldDropOuts(fieldNumber); + sourceVideos[sourceNo]->ldDecodeMetaData.clearFieldDropOuts(fieldNumber); } return fields; @@ -329,8 +330,8 @@ QVector> TbcSources::getFieldDiff(qint32 targetVbiFrame, QVector fieldDiff.resize(getNumberOfAvailableSources()); // Set the diff vector elements to zero (and resize the sub-vectors) - for (qint32 sourceNo = 0; sourceNo < availableSourcesForFrame.size(); sourceNo++) { - fieldDiff[availableSourcesForFrame[sourceNo]].fill(0, videoParameters.fieldHeight * videoParameters.fieldWidth); + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + fieldDiff[availableSourcesForFrame[sourcePointer]].fill(0, videoParameters.fieldHeight * videoParameters.fieldWidth); } // Process the fields one line at a time @@ -340,19 +341,21 @@ QVector> TbcSources::getFieldDiff(qint32 targetVbiFrame, QVector // Compare all combinations of source and target field lines // Note: the source is the field line we are building a DO map for, target is the field line we are // comparing the source to. - for (qint32 sourceNo = 0; sourceNo < availableSourcesForFrame.size(); sourceNo++) { - for (qint32 targetCounter = 0; targetCounter < availableSourcesForFrame.size(); targetCounter++) { - if (availableSourcesForFrame[sourceNo] != availableSourcesForFrame[targetCounter]) { + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + for (qint32 targetPointer = 0; targetPointer < availableSourcesForFrame.size(); targetPointer++) { + qint32 targetNo = availableSourcesForFrame[targetPointer]; // Get the actual target + if (sourceNo != targetNo) { for (qint32 x = 0; x < videoParameters.fieldWidth; x++) { // Get the IRE values for target and source fields, cast to 32 bit signed - qint32 targetIre = static_cast(fields[availableSourcesForFrame[targetCounter]][x + startOfLinePointer]); - qint32 sourceIre = static_cast(fields[availableSourcesForFrame[sourceNo]][x + startOfLinePointer]); + qint32 targetIre = static_cast(fields[targetNo][x + startOfLinePointer]); + qint32 sourceIre = static_cast(fields[sourceNo][x + startOfLinePointer]); // Diff the 16-bit pixel values of the first fields qint32 difference = abs(targetIre - sourceIre); // If the source and target differ, increment the fieldDiff - if (difference > dodThreshold) fieldDiff[availableSourcesForFrame[sourceNo]][x + startOfLinePointer] += 1; + if (difference > dodThreshold) fieldDiff[sourceNo][x + startOfLinePointer] += 1; } } } @@ -375,14 +378,16 @@ void TbcSources::performLumaClip(qint32 targetVbiFrame, QVector(fields[availableSourcesForFrame[sourceNo]][x + startOfLinePointer]); + qint32 sourceIre = static_cast(fields[sourceNo][x + startOfLinePointer]); // Check for a luma clip event if ((sourceIre < blackClipLevel) || (sourceIre > whiteClipLevel)) { @@ -399,14 +404,14 @@ void TbcSources::performLumaClip(qint32 targetVbiFrame, QVector minX; i--) { - qint32 ire = static_cast(fields[availableSourcesForFrame[sourceNo]][x + startOfLinePointer]); + qint32 ire = static_cast(fields[sourceNo][x + startOfLinePointer]); if (ire < videoParameters.black16bIre || ire > videoParameters.white16bIre) { startX = i; } } for (qint32 i = x+1; i < maxX; i++) { - qint32 ire = static_cast(fields[availableSourcesForFrame[sourceNo]][x + startOfLinePointer]); + qint32 ire = static_cast(fields[sourceNo][x + startOfLinePointer]); if (ire < videoParameters.black16bIre || ire > videoParameters.white16bIre) { endX = i; } @@ -414,7 +419,7 @@ void TbcSources::performLumaClip(qint32 targetVbiFrame, QVector TbcSources::getFieldDropouts(qint32 targetVb qint32 diffCompareThreshold = availableSourcesForFrame.size() - 2; // Process each source line in turn - for (qint32 sourceNo = 0; sourceNo < availableSourcesForFrame.size(); sourceNo++) { + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + // Mark the individual dropouts qint32 doCounter = 0; qint32 minimumDetectLength = 5; @@ -468,15 +475,15 @@ QVector TbcSources::getFieldDropouts(qint32 targetVb // active video area for (qint32 x = areaStart; x < areaEnd; x++) { // Compare field dot to threshold - if (static_cast(fieldsDiff[availableSourcesForFrame[sourceNo]][x + startOfLinePointer]) <= diffCompareThreshold) { + if (static_cast(fieldsDiff[sourceNo][x + startOfLinePointer]) <= diffCompareThreshold) { // Current X is not a dropout if (doCounter > 0) { doCounter--; if (doCounter == 0) { // Mark the previous x as the end of the dropout - frameDropouts[availableSourcesForFrame[sourceNo]].startx.append(doStart); - frameDropouts[availableSourcesForFrame[sourceNo]].endx.append(x - 1); - frameDropouts[availableSourcesForFrame[sourceNo]].fieldLine.append(doFieldLine); + frameDropouts[sourceNo].startx.append(doStart); + frameDropouts[sourceNo].endx.append(x - 1); + frameDropouts[sourceNo].fieldLine.append(doFieldLine); } } } else { @@ -493,9 +500,9 @@ QVector TbcSources::getFieldDropouts(qint32 targetVb if (doCounter > 0) { doCounter = 0; - frameDropouts[availableSourcesForFrame[sourceNo]].startx.append(doStart); - frameDropouts[availableSourcesForFrame[sourceNo]].endx.append(areaEnd); - frameDropouts[availableSourcesForFrame[sourceNo]].fieldLine.append(doFieldLine); + frameDropouts[sourceNo].startx.append(doStart); + frameDropouts[sourceNo].endx.append(areaEnd); + frameDropouts[sourceNo].fieldLine.append(doFieldLine); } } // Next source @@ -511,24 +518,26 @@ void TbcSources::writeDropoutMetadata(qint32 targetVbiFrame, QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); // Write the first and second field line metadata back to the source - for (qint32 sourceNo = 0; sourceNo < availableSourcesForFrame.size(); sourceNo++) { + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + // Get the required field numbers - qint32 firstFieldNumber = sourceVideos[availableSourcesForFrame[sourceNo]]-> - ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, availableSourcesForFrame[sourceNo])); - qint32 secondFieldNumber = sourceVideos[availableSourcesForFrame[sourceNo]]-> - ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, availableSourcesForFrame[sourceNo])); + qint32 firstFieldNumber = sourceVideos[sourceNo]-> + ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); + qint32 secondFieldNumber = sourceVideos[sourceNo]-> + ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); // Calculate the total number of dropouts detected for the frame - qint32 totalFirstDropouts = firstFieldDropouts[availableSourcesForFrame[sourceNo]].startx.size(); - qint32 totalSecondDropouts = secondFieldDropouts[availableSourcesForFrame[sourceNo]].startx.size(); + qint32 totalFirstDropouts = firstFieldDropouts[sourceNo].startx.size(); + qint32 totalSecondDropouts = secondFieldDropouts[sourceNo].startx.size(); - qDebug() << "TbcSources::performFrameDiffDod(): Writing source" << availableSourcesForFrame[sourceNo] << + qDebug() << "TbcSources::performFrameDiffDod(): Writing source" << sourceNo << "frame" << targetVbiFrame << "fields" << firstFieldNumber << "/" << secondFieldNumber << "- Dropout records" << totalFirstDropouts << "/" << totalSecondDropouts; // Write the metadata - sourceVideos[availableSourcesForFrame[sourceNo]]->ldDecodeMetaData.updateFieldDropOuts(firstFieldDropouts[availableSourcesForFrame[sourceNo]], firstFieldNumber); - sourceVideos[availableSourcesForFrame[sourceNo]]->ldDecodeMetaData.updateFieldDropOuts(secondFieldDropouts[availableSourcesForFrame[sourceNo]], secondFieldNumber); + sourceVideos[sourceNo]->ldDecodeMetaData.updateFieldDropOuts(firstFieldDropouts[sourceNo], firstFieldNumber); + sourceVideos[sourceNo]->ldDecodeMetaData.updateFieldDropOuts(secondFieldDropouts[sourceNo], secondFieldNumber); } } @@ -545,20 +554,22 @@ void TbcSources::concatenateFieldDropouts(qint32 targetVbiFrame, QVector (dropouts[availableSourcesForFrame[sourceNo]].startx[i])) { + if (dropouts[sourceNo].fieldLine[i - 1] == dropouts[sourceNo].fieldLine[i]) { + if ((dropouts[sourceNo].endx[i - 1] + minimumGap) > (dropouts[sourceNo].startx[i])) { // Concatenate - dropouts[availableSourcesForFrame[sourceNo]].endx[i - 1] = dropouts[availableSourcesForFrame[sourceNo]].endx[i]; + dropouts[sourceNo].endx[i - 1] = dropouts[sourceNo].endx[i]; // Remove the current dropout - dropouts[availableSourcesForFrame[sourceNo]].startx.removeAt(i); - dropouts[availableSourcesForFrame[sourceNo]].endx.removeAt(i); - dropouts[availableSourcesForFrame[sourceNo]].fieldLine.removeAt(i); + dropouts[sourceNo].startx.removeAt(i); + dropouts[sourceNo].endx.removeAt(i); + dropouts[sourceNo].fieldLine.removeAt(i); } } From 5e72f76962f7dfcf23bb210dae3306f1272b3dbe Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Sat, 1 Feb 2020 14:46:30 +0100 Subject: [PATCH 03/14] Prevent ld-dropout-correct overwriting the output file name --- tools/ld-dropout-correct/main.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/ld-dropout-correct/main.cpp b/tools/ld-dropout-correct/main.cpp index 31463c88d..8a13821f6 100644 --- a/tools/ld-dropout-correct/main.cpp +++ b/tools/ld-dropout-correct/main.cpp @@ -188,6 +188,16 @@ int main(int argc, char *argv[]) } } + // Check that the output file does not already exist + if (outputFilename != "-") { + QFileInfo outputFileInfo(outputFilename); + if (outputFileInfo.exists()) { + // Quit with error + qCritical("Specified output file already exists - will not overwrite"); + return -1; + } + } + // Metadata filename for output TBC QString outputJsonFilename = outputFilename + ".json"; if (parser.isSet(outputJsonOption)) { From 357fc74ad5519056d5be684c84b02dc42d6d3d64 Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Sat, 1 Feb 2020 15:31:23 +0100 Subject: [PATCH 04/14] New median based differential error detection --- tools/ld-diffdod/main.cpp | 4 +- tools/ld-diffdod/tbcsources.cpp | 152 +++++++++++++++++--------------- tools/ld-diffdod/tbcsources.h | 7 +- 3 files changed, 87 insertions(+), 76 deletions(-) diff --git a/tools/ld-diffdod/main.cpp b/tools/ld-diffdod/main.cpp index dd95bd1b1..d3e1417f8 100644 --- a/tools/ld-diffdod/main.cpp +++ b/tools/ld-diffdod/main.cpp @@ -68,7 +68,7 @@ int main(int argc, char *argv[]) // Option to select DOD threshold (-x / --dod-threshold) QCommandLineOption dodThresholdOption(QStringList() << "x" << "dod-threshold", - QCoreApplication::translate("main", "Specify the DOD threshold (100-65435 default: 400"), + QCoreApplication::translate("main", "Specify the DOD threshold (100-65435 default: 600"), QCoreApplication::translate("main", "number")); parser.addOption(dodThresholdOption); @@ -116,7 +116,7 @@ int main(int argc, char *argv[]) return -1; } - qint32 dodThreshold = 400; + qint32 dodThreshold = 600; if (parser.isSet(dodThresholdOption)) { dodThreshold = parser.value(dodThresholdOption).toInt(); diff --git a/tools/ld-diffdod/tbcsources.cpp b/tools/ld-diffdod/tbcsources.cpp index dc3fd9c03..38c9c22b5 100644 --- a/tools/ld-diffdod/tbcsources.cpp +++ b/tools/ld-diffdod/tbcsources.cpp @@ -240,8 +240,8 @@ void TbcSources::performFrameDiffDod(qint32 targetVbiFrame, qint32 dodThreshold, QVector secondFields = getFieldData(targetVbiFrame, false); // Create a differential map of the fields for the avaialble frames (based on the DOD threshold) - QVector> firstFieldsDiff = getFieldDiff(targetVbiFrame, firstFields, dodThreshold); - QVector> secondFieldsDiff = getFieldDiff(targetVbiFrame, secondFields, dodThreshold); + QVector firstFieldsDiff = getFieldErrorByMedian(targetVbiFrame, firstFields, dodThreshold); + QVector secondFieldsDiff = getFieldErrorByMedian(targetVbiFrame, secondFields, dodThreshold); // Perform luma clip check? if (lumaClip) { @@ -250,8 +250,8 @@ void TbcSources::performFrameDiffDod(qint32 targetVbiFrame, qint32 dodThreshold, } // Create the drop-out metadata based on the differential map of the fields - QVector firstFieldDropouts = getFieldDropouts(targetVbiFrame, firstFieldsDiff); - QVector secondFieldDropouts = getFieldDropouts(targetVbiFrame, secondFieldsDiff); + QVector firstFieldDropouts = getFieldDropouts(targetVbiFrame, firstFieldsDiff, true); + QVector secondFieldDropouts = getFieldDropouts(targetVbiFrame, secondFieldsDiff, false); // Concatenate dropouts on the same line that are close together (to cut down on the // amount of generated metadata with noisy/bad sources) @@ -271,22 +271,14 @@ QVector TbcSources::getFieldData(qint32 targetVbiFrame, bool // Check how many source frames are available for the current frame QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); - // DiffDOD requires at least three source frames. If 3 sources are not available leave any existing DO records in place - // and output a warning to the user - if (availableSourcesForFrame.size() < 3) { - // Differential DOD requires at least 3 valid source frames - qInfo() << "Only" << availableSourcesForFrame.size() << "source frames are available - can not perform diffDOD for VBI frame" << targetVbiFrame; - return QVector(); - } - // Only display on first field (otherwise we will get 2 of the same debug) - if (isFirstField) qDebug() << "TbcSources::performFrameDiffDod(): Processing VBI Frame" << targetVbiFrame << "-" << availableSourcesForFrame.size() << "sources available"; + if (isFirstField) qDebug() << "Processing VBI Frame" << targetVbiFrame << "-" << availableSourcesForFrame.size() << "sources available"; // Get the field data for the frame from all of the available sources and copy locally QVector fields; fields.resize(getNumberOfAvailableSources()); - QVector sourceFirstFieldPointer; + QVector sourceFirstFieldPointer; sourceFirstFieldPointer.resize(getNumberOfAvailableSources()); for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { @@ -315,9 +307,8 @@ QVector TbcSources::getFieldData(qint32 targetVbiFrame, bool return fields; } -// Create a differential map of the fields (this is a map of each dot in the field and how many -// other sources it differs from) -QVector> TbcSources::getFieldDiff(qint32 targetVbiFrame, QVector &fields, qint32 dodThreshold) +// Create an error map of the fields based on median value differential analysis +QVector TbcSources::getFieldErrorByMedian(qint32 targetVbiFrame, QVector &fields, qint32 dodThreshold) { // Get the metadata for the video parameters (all sources are the same, so just grab from the first) LdDecodeMetaData::VideoParameters videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); @@ -325,39 +316,37 @@ QVector> TbcSources::getFieldDiff(qint32 targetVbiFrame, QVector // Check how many source frames are available for the current frame QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); + // This method requires at least three source frames + if (availableSourcesForFrame.size() < 3) { + return QVector(); + } + // Make a vector to store the result of the diff - QVector> fieldDiff; + QVector fieldDiff; fieldDiff.resize(getNumberOfAvailableSources()); - // Set the diff vector elements to zero (and resize the sub-vectors) - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - fieldDiff[availableSourcesForFrame[sourcePointer]].fill(0, videoParameters.fieldHeight * videoParameters.fieldWidth); + // Resize the fieldDiff sub-vectors + for (qint32 sourcePointer = 0; sourcePointer < getNumberOfAvailableSources(); sourcePointer++) { + fieldDiff[sourcePointer].resize(videoParameters.fieldHeight * videoParameters.fieldWidth); } - // Process the fields one line at a time for (qint32 y = 0; y < videoParameters.fieldHeight; y++) { qint32 startOfLinePointer = y * videoParameters.fieldWidth; + for (qint32 x = 0; x < videoParameters.fieldWidth; x++) { + // Get the dot value from all of the sources + QVector dotValues(getNumberOfAvailableSources()); + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + dotValues[sourceNo] = static_cast(fields[sourceNo][x + startOfLinePointer]); + } - // Compare all combinations of source and target field lines - // Note: the source is the field line we are building a DO map for, target is the field line we are - // comparing the source to. - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - for (qint32 targetPointer = 0; targetPointer < availableSourcesForFrame.size(); targetPointer++) { - qint32 targetNo = availableSourcesForFrame[targetPointer]; // Get the actual target - if (sourceNo != targetNo) { - for (qint32 x = 0; x < videoParameters.fieldWidth; x++) { - // Get the IRE values for target and source fields, cast to 32 bit signed - qint32 targetIre = static_cast(fields[targetNo][x + startOfLinePointer]); - qint32 sourceIre = static_cast(fields[sourceNo][x + startOfLinePointer]); - - // Diff the 16-bit pixel values of the first fields - qint32 difference = abs(targetIre - sourceIre); - - // If the source and target differ, increment the fieldDiff - if (difference > dodThreshold) fieldDiff[sourceNo][x + startOfLinePointer] += 1; - } - } + // Compute the median of the dot values + qint32 dotMedian = median(dotValues); + + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + if (abs(dotValues[sourceNo] - dotMedian) > dodThreshold) fieldDiff[sourceNo][x + startOfLinePointer] = 1; + else fieldDiff[sourceNo][x + startOfLinePointer] = 0; } } } @@ -365,8 +354,16 @@ QVector> TbcSources::getFieldDiff(qint32 targetVbiFrame, QVector return fieldDiff; } +// Method to find the median of a vector of qint32s +qint32 TbcSources::median(QVector v) +{ + size_t n = v.size() / 2; + std::nth_element(v.begin(), v.begin()+n, v.end()); + return v[n]; +} + // Perform a luma clip check on the field -void TbcSources::performLumaClip(qint32 targetVbiFrame, QVector &fields, QVector> &fieldsDiff) +void TbcSources::performLumaClip(qint32 targetVbiFrame, QVector &fields, QVector &fieldsDiff) { // Get the metadata for the video parameters (all sources are the same, so just grab from the first) LdDecodeMetaData::VideoParameters videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); @@ -419,7 +416,7 @@ void TbcSources::performLumaClip(qint32 targetVbiFrame, QVector TbcSources::getFieldDropouts(qint32 targetVbiFrame, QVector> &fieldsDiff) +// This method compares each available source against all other available sources to determine where the source differs. +// If any of the frame's contents do not match that of the other sources, the frame's pixels are marked as dropouts. +QVector TbcSources::getFieldDropouts(qint32 targetVbiFrame, QVector &fieldsDiff, bool isFirstField) { // Get the metadata for the video parameters (all sources are the same, so just grab from the first) LdDecodeMetaData::VideoParameters videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); @@ -438,10 +437,30 @@ QVector TbcSources::getFieldDropouts(qint32 targetVb // Check how many source frames are available for the current frame QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); - // This compares each available source against all other available sources to determine where the source differs. - // If any of the frame's contents do not match that of the other sources, the frame's pixels are marked as dropouts. - QVector frameDropouts; - frameDropouts.resize(getNumberOfAvailableSources()); + // Create and resize the return data vector + QVector fieldDropouts; + fieldDropouts.resize(getNumberOfAvailableSources()); + + // This method requires at least three source frames + if (availableSourcesForFrame.size() < 3) { + // Not enough source frames, preserve the current dropout metadata + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + + // Get the required field number + qint32 fieldNumber; + if (isFirstField) fieldNumber = sourceVideos[sourceNo]-> + ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); + else fieldNumber = sourceVideos[sourceNo]-> + ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); + + fieldDropouts[sourceNo] = sourceVideos[sourceNo]->ldDecodeMetaData.getFieldDropOuts(fieldNumber); + } + + if (isFirstField) qInfo() << "Only" << availableSourcesForFrame.size() << "available sources for VBI frame" << + targetVbiFrame << "- preserving original dropout data"; + return fieldDropouts; + } // Define the area in which DOD should be performed qint32 areaStart = videoParameters.colourBurstStart; @@ -451,15 +470,6 @@ QVector TbcSources::getFieldDropouts(qint32 targetVb for (qint32 y = 0; y < videoParameters.fieldHeight; y++) { qint32 startOfLinePointer = y * videoParameters.fieldWidth; - // Now the value of diffs[source].firstDiff[x]/diffs[source].secondDiff[x] contains the number of other target fields - // that differ from the source field line. - - // The minimum number of sources for diffDOD is 3, and when comparing 3 sources, each source has to - // match at least 2 other sources. As the sources increase, so does the required number of matches - // (i.e. for 4 sources, 3 should match and so on). This makes the diffDOD better and better as the - // number of available sources increase. - qint32 diffCompareThreshold = availableSourcesForFrame.size() - 2; - // Process each source line in turn for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source @@ -475,15 +485,15 @@ QVector TbcSources::getFieldDropouts(qint32 targetVb // active video area for (qint32 x = areaStart; x < areaEnd; x++) { // Compare field dot to threshold - if (static_cast(fieldsDiff[sourceNo][x + startOfLinePointer]) <= diffCompareThreshold) { + if (static_cast(fieldsDiff[sourceNo][x + startOfLinePointer]) == 0) { // Current X is not a dropout if (doCounter > 0) { doCounter--; if (doCounter == 0) { // Mark the previous x as the end of the dropout - frameDropouts[sourceNo].startx.append(doStart); - frameDropouts[sourceNo].endx.append(x - 1); - frameDropouts[sourceNo].fieldLine.append(doFieldLine); + fieldDropouts[sourceNo].startx.append(doStart); + fieldDropouts[sourceNo].endx.append(x - 1); + fieldDropouts[sourceNo].fieldLine.append(doFieldLine); } } } else { @@ -500,15 +510,15 @@ QVector TbcSources::getFieldDropouts(qint32 targetVb if (doCounter > 0) { doCounter = 0; - frameDropouts[sourceNo].startx.append(doStart); - frameDropouts[sourceNo].endx.append(areaEnd); - frameDropouts[sourceNo].fieldLine.append(doFieldLine); + fieldDropouts[sourceNo].startx.append(doStart); + fieldDropouts[sourceNo].endx.append(areaEnd); + fieldDropouts[sourceNo].fieldLine.append(doFieldLine); } } // Next source } // Next line - return frameDropouts; + return fieldDropouts; } void TbcSources::writeDropoutMetadata(qint32 targetVbiFrame, QVector &firstFieldDropouts, @@ -531,7 +541,7 @@ void TbcSources::writeDropoutMetadata(qint32 targetVbiFrame, QVector clvCount) { sourceVideos[sourceNumber]->isSourceCav = true; - qDebug() << "TbcSources::getIsSourceCav(): Got" << cavCount << "valid CAV picture numbers - source disc type is CAV"; + qDebug() << "Got" << cavCount << "valid CAV picture numbers - source disc type is CAV"; qInfo() << "Disc type is CAV"; } else { sourceVideos[sourceNumber]->isSourceCav = false; - qDebug() << "TbcSources::getIsSourceCav(): Got" << clvCount << "valid CLV picture numbers - source disc type is CLV"; + qDebug() << "Got" << clvCount << "valid CLV picture numbers - source disc type is CLV"; qInfo() << "Disc type is CLV"; } diff --git a/tools/ld-diffdod/tbcsources.h b/tools/ld-diffdod/tbcsources.h index efd4f7390..c1466273c 100644 --- a/tools/ld-diffdod/tbcsources.h +++ b/tools/ld-diffdod/tbcsources.h @@ -70,9 +70,10 @@ class TbcSources : public QObject void performFrameDiffDod(qint32 targetVbiFrame, qint32 dodOnThreshold, bool lumaClip); QVector getFieldData(qint32 targetVbiFrame, bool isFirstField); - QVector > getFieldDiff(qint32 targetVbiFrame, QVector &fields, qint32 dodThreshold); - void performLumaClip(qint32 targetVbiFrame, QVector &fields, QVector> &fieldsDiff); - QVector getFieldDropouts(qint32 targetVbiFrame, QVector > &fieldsDiff); + QVector getFieldErrorByMedian(qint32 targetVbiFrame, QVector &fields, qint32 dodThreshold); + qint32 median(QVector v); + void performLumaClip(qint32 targetVbiFrame, QVector &fields, QVector &fieldsDiff); + QVector getFieldDropouts(qint32 targetVbiFrame, QVector &fieldsDiff, bool isFirstField); void writeDropoutMetadata(qint32 targetVbiFrame, QVector &firstFieldDropouts, QVector &secondFieldDropouts); void concatenateFieldDropouts(qint32 targetVbiFrame, QVector &dropouts); From 6c38cbeb334a375f1ee15984a47e739404530ac9 Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Sat, 1 Feb 2020 17:26:28 +0100 Subject: [PATCH 05/14] First pass at Rec.709 logarithmic brightness comparison --- tools/ld-diffdod/main.cpp | 8 ++--- tools/ld-diffdod/tbcsources.cpp | 64 ++++++++++++++++++++++++++------- tools/ld-diffdod/tbcsources.h | 3 +- 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/tools/ld-diffdod/main.cpp b/tools/ld-diffdod/main.cpp index d3e1417f8..5a515b79c 100644 --- a/tools/ld-diffdod/main.cpp +++ b/tools/ld-diffdod/main.cpp @@ -68,7 +68,7 @@ int main(int argc, char *argv[]) // Option to select DOD threshold (-x / --dod-threshold) QCommandLineOption dodThresholdOption(QStringList() << "x" << "dod-threshold", - QCoreApplication::translate("main", "Specify the DOD threshold (100-65435 default: 600"), + QCoreApplication::translate("main", "Specify the DOD threshold percent (1 to 100% default: 5"), QCoreApplication::translate("main", "number")); parser.addOption(dodThresholdOption); @@ -116,13 +116,13 @@ int main(int argc, char *argv[]) return -1; } - qint32 dodThreshold = 600; + qint32 dodThreshold = 5; if (parser.isSet(dodThresholdOption)) { dodThreshold = parser.value(dodThresholdOption).toInt(); - if (dodThreshold < 100 || dodThreshold > 65435) { + if (dodThreshold < 1 || dodThreshold > 100) { // Quit with error - qCritical("DOD threshold must be between 100 and 65435"); + qCritical("DOD threshold must be between 1 and 100 percent"); return -1; } } diff --git a/tools/ld-diffdod/tbcsources.cpp b/tools/ld-diffdod/tbcsources.cpp index 38c9c22b5..dea7cb704 100644 --- a/tools/ld-diffdod/tbcsources.cpp +++ b/tools/ld-diffdod/tbcsources.cpp @@ -231,9 +231,9 @@ void TbcSources::verifySources(qint32 vbiStartFrame, qint32 length) // Note: This method processes a single frame void TbcSources::performFrameDiffDod(qint32 targetVbiFrame, qint32 dodThreshold, bool lumaClip) { - // Range check the diffDOD threshold - if (dodThreshold < 100) dodThreshold = 100; - if (dodThreshold > 65435) dodThreshold = 65435; + // Range check the diffDOD threshold percent + if (dodThreshold < 1) dodThreshold = 1; + if (dodThreshold > 100) dodThreshold = 100; // Get the field data for the current frame (from all available sources) QVector firstFields = getFieldData(targetVbiFrame, true); @@ -321,6 +321,9 @@ QVector TbcSources::getFieldErrorByMedian(qint32 targetVbiFrame, QVe return QVector(); } + // Normalize the % dodThreshold to 0.00-1.00 + double threshold = static_cast(dodThreshold) / 100.0; + // Make a vector to store the result of the diff QVector fieldDiff; fieldDiff.resize(getNumberOfAvailableSources()); @@ -341,11 +344,12 @@ QVector TbcSources::getFieldErrorByMedian(qint32 targetVbiFrame, QVe } // Compute the median of the dot values - qint32 dotMedian = median(dotValues); + double vMedian = static_cast(convertLinearToBrightness(median(dotValues), videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal)); for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - if (abs(dotValues[sourceNo] - dotMedian) > dodThreshold) fieldDiff[sourceNo][x + startOfLinePointer] = 1; + double v = convertLinearToBrightness(dotValues[sourceNo], videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal); + if ((v - vMedian) > threshold) fieldDiff[sourceNo][x + startOfLinePointer] = 1; else fieldDiff[sourceNo][x + startOfLinePointer] = 0; } } @@ -354,14 +358,6 @@ QVector TbcSources::getFieldErrorByMedian(qint32 targetVbiFrame, QVe return fieldDiff; } -// Method to find the median of a vector of qint32s -qint32 TbcSources::median(QVector v) -{ - size_t n = v.size() / 2; - std::nth_element(v.begin(), v.begin()+n, v.end()); - return v[n]; -} - // Perform a luma clip check on the field void TbcSources::performLumaClip(qint32 targetVbiFrame, QVector &fields, QVector &fieldsDiff) { @@ -718,6 +714,48 @@ qint32 TbcSources::convertVbiFrameNumberToSequential(qint32 vbiFrameNumber, qint return vbiFrameNumber - sourceVideos[sourceNumber]->minimumVbiFrameNumber + 1; } +// Method to find the median of a vector of qint32s +qint32 TbcSources::median(QVector v) +{ + size_t n = v.size() / 2; + std::nth_element(v.begin(), v.begin()+n, v.end()); + return v[n]; +} + +// Method to convert a linear IRE to a logarithmic reflective brightness % +// Note: Follows the Rec. 709 OETF transfer function +double TbcSources::convertLinearToBrightness(quint16 value, quint16 black16bIre, quint16 white16bIre, bool isSourcePal) +{ + double v = 0; + double l = static_cast(value); + + // Factors to scale Y according to the black to white interval + // (i.e. make the black level 0 and the white level 65535) + qreal yScale = (1.0 / (black16bIre - white16bIre)) * -65535; + + if (!isSourcePal) { + // NTSC uses a 75% white point; so here we scale the result by + // 25% (making 100 IRE 25% over the maximum allowed white point) + yScale *= 125.0 / 100.0; + } + + // Scale the L to 0-65535 where 0 = blackIreLevel and 65535 = whiteIreLevel + l = (l - black16bIre) * yScale; + l = qBound(0.0, l, 65535.0); + + // Scale L to 0.00-1.00 + l = (1.0 / 65535.0) * l; + + // Rec. 709 - https://en.wikipedia.org/wiki/Rec._709#Transfer_characteristics + if (l < 0.018) { + v = 4.500 * l; + } else { + v = pow(1.099 * l, 0.45) - 0.099; + } + + return v; +} + diff --git a/tools/ld-diffdod/tbcsources.h b/tools/ld-diffdod/tbcsources.h index c1466273c..9fbffe3b5 100644 --- a/tools/ld-diffdod/tbcsources.h +++ b/tools/ld-diffdod/tbcsources.h @@ -71,7 +71,6 @@ class TbcSources : public QObject void performFrameDiffDod(qint32 targetVbiFrame, qint32 dodOnThreshold, bool lumaClip); QVector getFieldData(qint32 targetVbiFrame, bool isFirstField); QVector getFieldErrorByMedian(qint32 targetVbiFrame, QVector &fields, qint32 dodThreshold); - qint32 median(QVector v); void performLumaClip(qint32 targetVbiFrame, QVector &fields, QVector &fieldsDiff); QVector getFieldDropouts(qint32 targetVbiFrame, QVector &fieldsDiff, bool isFirstField); void writeDropoutMetadata(qint32 targetVbiFrame, QVector &firstFieldDropouts, @@ -81,6 +80,8 @@ class TbcSources : public QObject QVector getAvailableSourcesForFrame(qint32 vbiFrameNumber); bool setDiscTypeAndMaxMinFrameVbi(qint32 sourceNumber); qint32 convertVbiFrameNumberToSequential(qint32 vbiFrameNumber, qint32 sourceNumber); + qint32 median(QVector v); + double convertLinearToBrightness(quint16 value, quint16 black16bIre, quint16 white16bIre, bool isSourcePal); }; #endif // TBCSOURCES_H From fc5a2a7b3837787d49fc8e71020fa3c46bad3937 Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Sat, 1 Feb 2020 18:55:37 +0100 Subject: [PATCH 06/14] Added in linear colour burst checking --- tools/ld-diffdod/tbcsources.cpp | 39 +++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/tools/ld-diffdod/tbcsources.cpp b/tools/ld-diffdod/tbcsources.cpp index dea7cb704..6ce0e4127 100644 --- a/tools/ld-diffdod/tbcsources.cpp +++ b/tools/ld-diffdod/tbcsources.cpp @@ -308,6 +308,7 @@ QVector TbcSources::getFieldData(qint32 targetVbiFrame, bool } // Create an error map of the fields based on median value differential analysis +// Note: This only functions within the colour burst and visible areas of the frame QVector TbcSources::getFieldErrorByMedian(qint32 targetVbiFrame, QVector &fields, qint32 dodThreshold) { // Get the metadata for the video parameters (all sources are the same, so just grab from the first) @@ -324,18 +325,21 @@ QVector TbcSources::getFieldErrorByMedian(qint32 targetVbiFrame, QVe // Normalize the % dodThreshold to 0.00-1.00 double threshold = static_cast(dodThreshold) / 100.0; + // Calculate the linear threshold for the colourburst region + qint32 cbThreshold = ((65535 / 100) * dodThreshold) / 8; // Note: This is just a guess + // Make a vector to store the result of the diff QVector fieldDiff; fieldDiff.resize(getNumberOfAvailableSources()); - // Resize the fieldDiff sub-vectors + // Resize the fieldDiff sub-vectors and default the elements to zero for (qint32 sourcePointer = 0; sourcePointer < getNumberOfAvailableSources(); sourcePointer++) { - fieldDiff[sourcePointer].resize(videoParameters.fieldHeight * videoParameters.fieldWidth); + fieldDiff[sourcePointer].fill(0, videoParameters.fieldHeight * videoParameters.fieldWidth); } for (qint32 y = 0; y < videoParameters.fieldHeight; y++) { qint32 startOfLinePointer = y * videoParameters.fieldWidth; - for (qint32 x = 0; x < videoParameters.fieldWidth; x++) { + for (qint32 x = videoParameters.colourBurstStart; x < videoParameters.activeVideoEnd; x++) { // Get the dot value from all of the sources QVector dotValues(getNumberOfAvailableSources()); for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { @@ -343,14 +347,26 @@ QVector TbcSources::getFieldErrorByMedian(qint32 targetVbiFrame, QVe dotValues[sourceNo] = static_cast(fields[sourceNo][x + startOfLinePointer]); } - // Compute the median of the dot values - double vMedian = static_cast(convertLinearToBrightness(median(dotValues), videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal)); + // If we are in the visible area use Rec.709 logarithmic comparison + if (x >= videoParameters.activeVideoStart && x < videoParameters.activeVideoEnd) { + // Compute the median of the dot values + double vMedian = static_cast(convertLinearToBrightness(median(dotValues), videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal)); - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - double v = convertLinearToBrightness(dotValues[sourceNo], videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal); - if ((v - vMedian) > threshold) fieldDiff[sourceNo][x + startOfLinePointer] = 1; - else fieldDiff[sourceNo][x + startOfLinePointer] = 0; + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + double v = convertLinearToBrightness(dotValues[sourceNo], videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal); + if ((v - vMedian) > threshold) fieldDiff[sourceNo][x + startOfLinePointer] = 1; + } + } + + // If we are in the colourburst use linear comparison + if (x >= videoParameters.colourBurstStart && x < videoParameters.colourBurstEnd) { + // We are in the colour burst, use linear comparison + qint32 dotMedian = median(dotValues); + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + if ((dotValues[sourceNo] - dotMedian) > cbThreshold) fieldDiff[sourceNo][x + startOfLinePointer] = 1; + } } } } @@ -740,8 +756,7 @@ double TbcSources::convertLinearToBrightness(quint16 value, quint16 black16bIre, } // Scale the L to 0-65535 where 0 = blackIreLevel and 65535 = whiteIreLevel - l = (l - black16bIre) * yScale; - l = qBound(0.0, l, 65535.0); + l = qBound(0.0, (l - black16bIre) * yScale, 65535.0); // Scale L to 0.00-1.00 l = (1.0 / 65535.0) * l; From 97dee51ba041433abc71310a0f81f868f61a9d50 Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Sat, 1 Feb 2020 22:54:18 +0100 Subject: [PATCH 07/14] Small speed increase --- tools/ld-diffdod/tbcsources.cpp | 53 ++++++++++++++------------------- tools/ld-diffdod/tbcsources.h | 2 +- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/tools/ld-diffdod/tbcsources.cpp b/tools/ld-diffdod/tbcsources.cpp index 6ce0e4127..ece9ba0cf 100644 --- a/tools/ld-diffdod/tbcsources.cpp +++ b/tools/ld-diffdod/tbcsources.cpp @@ -299,9 +299,6 @@ QVector TbcSources::getFieldData(qint32 targetVbiFrame, bool } else { filters.ntscLumaFirFilter(fields[sourceNo].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); } - - // Remove the existing field dropout metadata for the field - sourceVideos[sourceNo]->ldDecodeMetaData.clearFieldDropOuts(fieldNumber); } return fields; @@ -323,10 +320,10 @@ QVector TbcSources::getFieldErrorByMedian(qint32 targetVbiFrame, QVe } // Normalize the % dodThreshold to 0.00-1.00 - double threshold = static_cast(dodThreshold) / 100.0; + float threshold = static_cast(dodThreshold) / 100.0; // Calculate the linear threshold for the colourburst region - qint32 cbThreshold = ((65535 / 100) * dodThreshold) / 8; // Note: This is just a guess + qint32 cbThreshold = ((65535 / 100) * dodThreshold) / 8; // Note: The /8 is just a guess // Make a vector to store the result of the diff QVector fieldDiff; @@ -350,11 +347,11 @@ QVector TbcSources::getFieldErrorByMedian(qint32 targetVbiFrame, QVe // If we are in the visible area use Rec.709 logarithmic comparison if (x >= videoParameters.activeVideoStart && x < videoParameters.activeVideoEnd) { // Compute the median of the dot values - double vMedian = static_cast(convertLinearToBrightness(median(dotValues), videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal)); + float vMedian = static_cast(convertLinearToBrightness(median(dotValues), videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal)); for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - double v = convertLinearToBrightness(dotValues[sourceNo], videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal); + float v = convertLinearToBrightness(dotValues[sourceNo], videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal); if ((v - vMedian) > threshold) fieldDiff[sourceNo][x + startOfLinePointer] = 1; } } @@ -455,23 +452,10 @@ QVector TbcSources::getFieldDropouts(qint32 targetVb // This method requires at least three source frames if (availableSourcesForFrame.size() < 3) { - // Not enough source frames, preserve the current dropout metadata - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - - // Get the required field number - qint32 fieldNumber; - if (isFirstField) fieldNumber = sourceVideos[sourceNo]-> - ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); - else fieldNumber = sourceVideos[sourceNo]-> - ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); - - fieldDropouts[sourceNo] = sourceVideos[sourceNo]->ldDecodeMetaData.getFieldDropOuts(fieldNumber); - } - + // Not enough source frames if (isFirstField) qInfo() << "Only" << availableSourcesForFrame.size() << "available sources for VBI frame" << targetVbiFrame << "- preserving original dropout data"; - return fieldDropouts; + return QVector(); } // Define the area in which DOD should be performed @@ -557,9 +541,16 @@ void TbcSources::writeDropoutMetadata(qint32 targetVbiFrame, QVectorldDecodeMetaData.updateFieldDropOuts(firstFieldDropouts[sourceNo], firstFieldNumber); - sourceVideos[sourceNo]->ldDecodeMetaData.updateFieldDropOuts(secondFieldDropouts[sourceNo], secondFieldNumber); + // Only replace the existing metadata if it was possible to create new metadata + if (availableSourcesForFrame.size() >= 3) { + // Remove the existing field dropout metadata for the field + sourceVideos[sourceNo]->ldDecodeMetaData.clearFieldDropOuts(firstFieldNumber); + sourceVideos[sourceNo]->ldDecodeMetaData.clearFieldDropOuts(secondFieldNumber); + + // Write the new field dropout metadata + sourceVideos[sourceNo]->ldDecodeMetaData.updateFieldDropOuts(firstFieldDropouts[sourceNo], firstFieldNumber); + sourceVideos[sourceNo]->ldDecodeMetaData.updateFieldDropOuts(secondFieldDropouts[sourceNo], secondFieldNumber); + } } } @@ -740,14 +731,14 @@ qint32 TbcSources::median(QVector v) // Method to convert a linear IRE to a logarithmic reflective brightness % // Note: Follows the Rec. 709 OETF transfer function -double TbcSources::convertLinearToBrightness(quint16 value, quint16 black16bIre, quint16 white16bIre, bool isSourcePal) +float TbcSources::convertLinearToBrightness(quint16 value, quint16 black16bIre, quint16 white16bIre, bool isSourcePal) { - double v = 0; - double l = static_cast(value); + float v = 0; + float l = static_cast(value); // Factors to scale Y according to the black to white interval // (i.e. make the black level 0 and the white level 65535) - qreal yScale = (1.0 / (black16bIre - white16bIre)) * -65535; + float yScale = (1.0 / (black16bIre - white16bIre)) * -65535; if (!isSourcePal) { // NTSC uses a 75% white point; so here we scale the result by @@ -756,7 +747,9 @@ double TbcSources::convertLinearToBrightness(quint16 value, quint16 black16bIre, } // Scale the L to 0-65535 where 0 = blackIreLevel and 65535 = whiteIreLevel - l = qBound(0.0, (l - black16bIre) * yScale, 65535.0); + l = (l - black16bIre) * yScale; + if (l > 65535) l = 65535; + if (l < 0) l = 0; // Scale L to 0.00-1.00 l = (1.0 / 65535.0) * l; diff --git a/tools/ld-diffdod/tbcsources.h b/tools/ld-diffdod/tbcsources.h index 9fbffe3b5..e9c90fe39 100644 --- a/tools/ld-diffdod/tbcsources.h +++ b/tools/ld-diffdod/tbcsources.h @@ -81,7 +81,7 @@ class TbcSources : public QObject bool setDiscTypeAndMaxMinFrameVbi(qint32 sourceNumber); qint32 convertVbiFrameNumberToSequential(qint32 vbiFrameNumber, qint32 sourceNumber); qint32 median(QVector v); - double convertLinearToBrightness(quint16 value, quint16 black16bIre, quint16 white16bIre, bool isSourcePal); + float convertLinearToBrightness(quint16 value, quint16 black16bIre, quint16 white16bIre, bool isSourcePal); }; #endif // TBCSOURCES_H From cde9a2ee93cd185355f3347ebaf9dc56124e02f6 Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Sun, 2 Feb 2020 18:01:00 +0100 Subject: [PATCH 08/14] Refactor of code structure --- tools/ld-diffdod/diffdod.cpp | 372 +++++++-- tools/ld-diffdod/diffdod.h | 36 +- tools/ld-diffdod/ld-diffdod.pro | 4 +- tools/ld-diffdod/main.cpp | 6 +- tools/ld-diffdod/sources.cpp | 499 ++++++++++++ tools/ld-diffdod/{tbcsources.h => sources.h} | 54 +- tools/ld-diffdod/tbcsources.cpp | 782 ------------------- 7 files changed, 876 insertions(+), 877 deletions(-) create mode 100644 tools/ld-diffdod/sources.cpp rename tools/ld-diffdod/{tbcsources.h => sources.h} (57%) delete mode 100644 tools/ld-diffdod/tbcsources.cpp diff --git a/tools/ld-diffdod/diffdod.cpp b/tools/ld-diffdod/diffdod.cpp index 7688f0622..75d4c8d7e 100644 --- a/tools/ld-diffdod/diffdod.cpp +++ b/tools/ld-diffdod/diffdod.cpp @@ -24,67 +24,343 @@ #include "diffdod.h" -Diffdod::Diffdod(QObject *parent) : QObject(parent) +DiffDod::DiffDod(QObject *parent) : QObject(parent) { } -bool Diffdod::process(QVector inputFilenames, bool reverse, - qint32 dodThreshold, bool lumaClip, - qint32 startVbi, qint32 lengthVbi) +// Perform differential dropout detection to determine (for each source) which frame pixels are valid +// Note: This method processes a single frame +void DiffDod::performFrameDiffDod(QVector& firstFields, QVector& secondFields, + qint32 dodThreshold, bool lumaClip, LdDecodeMetaData::VideoParameters videoParameters, + QVector availableSourcesForFrame) { - // Show input filenames - qInfo() << "Processing" << inputFilenames.size() << "input TBC files:"; - for (qint32 i = 0; i < inputFilenames.size(); i++) qInfo().nospace() << " Source #" << i << ": " << inputFilenames[i]; - - // And then show the rest... - if (reverse) qInfo() << "Using reverse field order"; else qInfo() << "Using normal field order"; - qInfo() << "Dropout detection threshold is" << dodThreshold; - if (lumaClip) qInfo() << "Performing luma clip detection"; else qInfo() << "Not performing luma clip detection"; - qInfo() << ""; - - // Load the input TBC files - if (!loadInputTbcFiles(inputFilenames, reverse)) { - qCritical() << "Error: Unable to load input TBC files - cannot continue!"; - return false; + // Range check the diffDOD threshold percent + if (dodThreshold < 1) dodThreshold = 1; + if (dodThreshold > 100) dodThreshold = 100; + + // Create a differential map of the fields for the avaialble frames (based on the DOD threshold) + QVector firstFieldsDiff = getFieldErrorByMedian(firstFields, dodThreshold, + videoParameters, availableSourcesForFrame); + QVector secondFieldsDiff = getFieldErrorByMedian(secondFields, dodThreshold, + videoParameters, availableSourcesForFrame); + + // Perform luma clip check? + if (lumaClip) { + performLumaClip(firstFields, firstFieldsDiff, videoParameters, availableSourcesForFrame); + performLumaClip(secondFields, secondFieldsDiff, videoParameters, availableSourcesForFrame); + } + + // Create the drop-out metadata based on the differential map of the fields + m_firstFieldDropouts = getFieldDropouts(firstFieldsDiff, videoParameters, availableSourcesForFrame); + m_secondFieldDropouts = getFieldDropouts(secondFieldsDiff, videoParameters, availableSourcesForFrame); + + // Concatenate dropouts on the same line that are close together (to cut down on the + // amount of generated metadata with noisy/bad sources) + concatenateFieldDropouts(m_firstFieldDropouts, availableSourcesForFrame); + concatenateFieldDropouts(m_secondFieldDropouts, availableSourcesForFrame); +} + +// Method to get the result of the diffDOD process +void DiffDod::getResults(QVector& firstFieldDropouts, + QVector& secondFieldDropouts) +{ + firstFieldDropouts = m_firstFieldDropouts; + secondFieldDropouts = m_secondFieldDropouts; +} + +// Private methods ---------------------------------------------------------------------------------------------------- + +// Create an error map of the fields based on median value differential analysis +// Note: This only functions within the colour burst and visible areas of the frame +QVector DiffDod::getFieldErrorByMedian(QVector &fields, qint32 dodThreshold, + LdDecodeMetaData::VideoParameters videoParameters, + QVector availableSourcesForFrame) +{ + // This method requires at least three source frames + if (availableSourcesForFrame.size() < 3) { + return QVector(); + } + + // Normalize the % dodThreshold to 0.00-1.00 + float threshold = static_cast(dodThreshold) / 100.0; + + // Calculate the linear threshold for the colourburst region + qint32 cbThreshold = ((65535 / 100) * dodThreshold) / 8; // Note: The /8 is just a guess + + // Make a vector to store the result of the diff + QVector fieldDiff; + fieldDiff.resize(fields.size()); + + // Resize the fieldDiff sub-vectors and default the elements to zero + for (qint32 sourcePointer = 0; sourcePointer < fields.size(); sourcePointer++) { + fieldDiff[sourcePointer].fill(0, videoParameters.fieldHeight * videoParameters.fieldWidth); + } + + for (qint32 y = 0; y < videoParameters.fieldHeight; y++) { + qint32 startOfLinePointer = y * videoParameters.fieldWidth; + for (qint32 x = videoParameters.colourBurstStart; x < videoParameters.activeVideoEnd; x++) { + // Get the dot value from all of the sources + QVector dotValues(fields.size()); + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + dotValues[sourceNo] = static_cast(fields[sourceNo][x + startOfLinePointer]); + } + + // If we are in the visible area use Rec.709 logarithmic comparison + if (x >= videoParameters.activeVideoStart && x < videoParameters.activeVideoEnd) { + // Compute the median of the dot values + float vMedian = static_cast(convertLinearToBrightness(median(dotValues), videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal)); + + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + float v = convertLinearToBrightness(dotValues[sourceNo], videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal); + if ((v - vMedian) > threshold) fieldDiff[sourceNo][x + startOfLinePointer] = 1; + } + } + + // If we are in the colourburst use linear comparison + if (x >= videoParameters.colourBurstStart && x < videoParameters.colourBurstEnd) { + // We are in the colour burst, use linear comparison + qint32 dotMedian = median(dotValues); + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + if ((dotValues[sourceNo] - dotMedian) > cbThreshold) fieldDiff[sourceNo][x + startOfLinePointer] = 1; + } + } + } } - // Show disc and video information - qInfo() << ""; - qInfo() << "Sources have VBI frame number range of" << tbcSources.getMinimumVbiFrameNumber() << "to" << tbcSources.getMaximumVbiFrameNumber(); - - // Check start and length - qint32 vbiStartFrame = startVbi; - if (vbiStartFrame < tbcSources.getMinimumVbiFrameNumber()) - vbiStartFrame = tbcSources.getMinimumVbiFrameNumber(); - - qint32 length = lengthVbi; - if (length > (tbcSources.getMaximumVbiFrameNumber() - vbiStartFrame + 1)) - length = tbcSources.getMaximumVbiFrameNumber() - vbiStartFrame + 1; - if (length == -1) length = tbcSources.getMaximumVbiFrameNumber() - tbcSources.getMinimumVbiFrameNumber() + 1; - - // Verify frame source availablity - qInfo() << ""; - qInfo() << "Verifying VBI frame multi-source availablity:"; - tbcSources.verifySources(vbiStartFrame, length); - - qInfo() << "Processing" << length << "frames starting from VBI frame" << vbiStartFrame; - if (!tbcSources.saveSources(vbiStartFrame, length, dodThreshold, lumaClip)) { - qCritical() << "Saving source failed!"; - return false; + return fieldDiff; +} + +// Perform a luma clip check on the field +void DiffDod::performLumaClip(QVector &fields, QVector &fieldsDiff, + LdDecodeMetaData::VideoParameters videoParameters, + QVector availableSourcesForFrame) +{ + // Process the fields one line at a time + for (qint32 y = videoParameters.firstActiveFieldLine; y < videoParameters.lastActiveFieldLine; y++) { + qint32 startOfLinePointer = y * videoParameters.fieldWidth; + + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + + // Set the clipping levels + qint32 blackClipLevel = videoParameters.black16bIre - 4000; + qint32 whiteClipLevel = videoParameters.white16bIre + 4000; + + for (qint32 x = videoParameters.activeVideoStart; x < videoParameters.activeVideoEnd; x++) { + // Get the IRE value for the source field, cast to 32 bit signed + qint32 sourceIre = static_cast(fields[sourceNo][x + startOfLinePointer]); + + // Check for a luma clip event + if ((sourceIre < blackClipLevel) || (sourceIre > whiteClipLevel)) { + // Luma has clipped, scan back and forth looking for the start + // and end points of the event (i.e. the point where the event + // goes back into the expected IRE range) + qint32 range = 10; // maximum + and - scan range + qint32 minX = x - range; + if (minX < videoParameters.activeVideoStart) minX = videoParameters.activeVideoStart; + qint32 maxX = x + range; + if (maxX > videoParameters.activeVideoEnd) maxX = videoParameters.activeVideoEnd; + + qint32 startX = x; + qint32 endX = x; + + for (qint32 i = x; i > minX; i--) { + qint32 ire = static_cast(fields[sourceNo][x + startOfLinePointer]); + if (ire < videoParameters.black16bIre || ire > videoParameters.white16bIre) { + startX = i; + } + } + + for (qint32 i = x+1; i < maxX; i++) { + qint32 ire = static_cast(fields[sourceNo][x + startOfLinePointer]); + if (ire < videoParameters.black16bIre || ire > videoParameters.white16bIre) { + endX = i; + } + } + + // Mark the dropout + for (qint32 i = startX; i < endX; i++) { + fieldsDiff[sourceNo][i + startOfLinePointer] = 1; + } + + x = x + range; + } + } + } + } +} + +// Method to create the field drop-out metadata based on the differential map of the fields +// This method compares each available source against all other available sources to determine where the source differs. +// If any of the frame's contents do not match that of the other sources, the frame's pixels are marked as dropouts. +QVector DiffDod::getFieldDropouts(QVector &fieldsDiff, + LdDecodeMetaData::VideoParameters videoParameters, + QVector availableSourcesForFrame) +{ + // Create and resize the return data vector + QVector fieldDropouts; + fieldDropouts.resize(fieldsDiff.size()); + + // This method requires at least three source frames + if (availableSourcesForFrame.size() < 3) { + return QVector(); } - return true; + // Define the area in which DOD should be performed + qint32 areaStart = videoParameters.colourBurstStart; + qint32 areaEnd = videoParameters.activeVideoEnd; + + // Process the frame one line at a time (both fields) + for (qint32 y = 0; y < videoParameters.fieldHeight; y++) { + qint32 startOfLinePointer = y * videoParameters.fieldWidth; + + // Process each source line in turn + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + + // Mark the individual dropouts + qint32 doCounter = 0; + qint32 minimumDetectLength = 5; + + qint32 doStart = 0; + qint32 doFieldLine = 0; + + // Only create dropouts between the start of the colour burst and the end of the + // active video area + for (qint32 x = areaStart; x < areaEnd; x++) { + // Compare field dot to threshold + if (static_cast(fieldsDiff[sourceNo][x + startOfLinePointer]) == 0) { + // Current X is not a dropout + if (doCounter > 0) { + doCounter--; + if (doCounter == 0) { + // Mark the previous x as the end of the dropout + fieldDropouts[sourceNo].startx.append(doStart); + fieldDropouts[sourceNo].endx.append(x - 1); + fieldDropouts[sourceNo].fieldLine.append(doFieldLine); + } + } + } else { + // Current X is a dropout + if (doCounter == 0) { + doCounter = minimumDetectLength; + doStart = x; + doFieldLine = y + 1; + } + } + } + + // Ensure metadata dropouts end at the end of the active video area + if (doCounter > 0) { + doCounter = 0; + + fieldDropouts[sourceNo].startx.append(doStart); + fieldDropouts[sourceNo].endx.append(areaEnd); + fieldDropouts[sourceNo].fieldLine.append(doFieldLine); + } + + } // Next source + } // Next line + + return fieldDropouts; } -bool Diffdod::loadInputTbcFiles(QVector inputFilenames, bool reverse) +// Method to concatenate dropouts on the same line that are close together +// (to cut down on the amount of generated metadata with noisy/bad sources) +void DiffDod::concatenateFieldDropouts(QVector &dropouts, + QVector availableSourcesForFrame) { - for (qint32 i = 0; i < inputFilenames.size(); i++) { - qInfo().nospace() << "Loading TBC input source #" << i << " - Filename: " << inputFilenames[i]; - if (!tbcSources.loadSource(inputFilenames[i], reverse)) { - return false; + // This variable controls the minimum allowed gap between dropouts + // if the gap between the end of the last dropout and the start of + // the next is less than minimumGap, the two dropouts will be + // concatenated together + qint32 minimumGap = 50; + + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + + // Start from 1 as 0 has no previous dropout + qint32 i = 1; + while (i < dropouts[sourceNo].startx.size()) { + // Is the current dropout on the same field line as the last? + if (dropouts[sourceNo].fieldLine[i - 1] == dropouts[sourceNo].fieldLine[i]) { + if ((dropouts[sourceNo].endx[i - 1] + minimumGap) > (dropouts[sourceNo].startx[i])) { + // Concatenate + dropouts[sourceNo].endx[i - 1] = dropouts[sourceNo].endx[i]; + + // Remove the current dropout + dropouts[sourceNo].startx.removeAt(i); + dropouts[sourceNo].endx.removeAt(i); + dropouts[sourceNo].fieldLine.removeAt(i); + } + } + + // Next dropout + i++; } } +} + +// Method to find the median of a vector of qint32s +qint32 DiffDod::median(QVector v) +{ + size_t n = v.size() / 2; + std::nth_element(v.begin(), v.begin()+n, v.end()); + return v[n]; +} + +// Method to convert a linear IRE to a logarithmic reflective brightness % +// Note: Follows the Rec. 709 OETF transfer function +float DiffDod::convertLinearToBrightness(quint16 value, quint16 black16bIre, quint16 white16bIre, bool isSourcePal) +{ + float v = 0; + float l = static_cast(value); + + // Factors to scale Y according to the black to white interval + // (i.e. make the black level 0 and the white level 65535) + float yScale = (1.0 / (black16bIre - white16bIre)) * -65535; + + if (!isSourcePal) { + // NTSC uses a 75% white point; so here we scale the result by + // 25% (making 100 IRE 25% over the maximum allowed white point) + yScale *= 125.0 / 100.0; + } - return true; + // Scale the L to 0-65535 where 0 = blackIreLevel and 65535 = whiteIreLevel + l = (l - black16bIre) * yScale; + if (l > 65535) l = 65535; + if (l < 0) l = 0; + + // Scale L to 0.00-1.00 + l = (1.0 / 65535.0) * l; + + // Rec. 709 - https://en.wikipedia.org/wiki/Rec._709#Transfer_characteristics + if (l < 0.018) { + v = 4.500 * l; + } else { + v = pow(1.099 * l, 0.45) - 0.099; + } + + return v; } + + + + + + + + + + + + + + + + + diff --git a/tools/ld-diffdod/diffdod.h b/tools/ld-diffdod/diffdod.h index 5ccf88511..c54b08c09 100644 --- a/tools/ld-diffdod/diffdod.h +++ b/tools/ld-diffdod/diffdod.h @@ -26,24 +26,42 @@ #define DIFFDOD_H #include +#include +#include #include -#include -#include "tbcsources.h" +// TBC library includes +#include "sourcevideo.h" +#include "lddecodemetadata.h" +#include "vbidecoder.h" +#include "filters.h" -class Diffdod : public QObject +class DiffDod : public QObject { Q_OBJECT public: - explicit Diffdod(QObject *parent = nullptr); - - bool process(QVector inputFilenames, bool reverse, - qint32 dodThreshold, bool lumaClip, qint32 startVbi, qint32 lengthVbi); + explicit DiffDod(QObject *parent = nullptr); + void performFrameDiffDod(QVector &firstFields, QVector &secondFields, qint32 dodOnThreshold, bool lumaClip, + LdDecodeMetaData::VideoParameters videoParameters, QVector availableSourcesForFrame); + void getResults(QVector& firstFieldDropouts, + QVector& secondFieldDropouts); private: - TbcSources tbcSources; + QVector m_firstFieldDropouts; + QVector m_secondFieldDropouts; + + QVector getFieldErrorByMedian(QVector &fields, qint32 dodThreshold, LdDecodeMetaData::VideoParameters videoParameters, QVector availableSourcesForFrame); + void performLumaClip(QVector &fields, QVector &fieldsDiff, + LdDecodeMetaData::VideoParameters videoParameters, + QVector availableSourcesForFrame); + QVector getFieldDropouts(QVector &fieldsDiff, + LdDecodeMetaData::VideoParameters videoParameters, + QVector availableSourcesForFrame); + + void concatenateFieldDropouts(QVector &dropouts, QVector availableSourcesForFrame); - bool loadInputTbcFiles(QVector inputFilenames, bool reverse); + qint32 median(QVector v); + float convertLinearToBrightness(quint16 value, quint16 black16bIre, quint16 white16bIre, bool isSourcePal); }; #endif // DIFFDOD_H diff --git a/tools/ld-diffdod/ld-diffdod.pro b/tools/ld-diffdod/ld-diffdod.pro index 851519e85..6a618aa65 100644 --- a/tools/ld-diffdod/ld-diffdod.pro +++ b/tools/ld-diffdod/ld-diffdod.pro @@ -22,7 +22,7 @@ SOURCES += \ ../library/tbc/logging.cpp \ diffdod.cpp \ main.cpp \ - tbcsources.cpp + sources.cpp HEADERS += \ ../library/filter/firfilter.h \ @@ -32,7 +32,7 @@ HEADERS += \ ../library/tbc/filters.h \ ../library/tbc/logging.h \ diffdod.h \ - tbcsources.h + sources.h # Add external includes to the include path INCLUDEPATH += ../library/filter diff --git a/tools/ld-diffdod/main.cpp b/tools/ld-diffdod/main.cpp index 5a515b79c..dcf3301ed 100644 --- a/tools/ld-diffdod/main.cpp +++ b/tools/ld-diffdod/main.cpp @@ -28,7 +28,7 @@ #include #include "logging.h" -#include "diffdod.h" +#include "sources.h" int main(int argc, char *argv[]) { @@ -150,8 +150,8 @@ int main(int argc, char *argv[]) } // Process the TBC file - Diffdod diffdod; - if (!diffdod.process(inputFilenames, reverse, dodThreshold, lumaClip, vbiFrameStart, vbiFrameLength)) { + Sources sources; + if (!sources.process(inputFilenames, reverse, dodThreshold, lumaClip, vbiFrameStart, vbiFrameLength)) { return 1; } diff --git a/tools/ld-diffdod/sources.cpp b/tools/ld-diffdod/sources.cpp new file mode 100644 index 000000000..c436f82a5 --- /dev/null +++ b/tools/ld-diffdod/sources.cpp @@ -0,0 +1,499 @@ +/************************************************************************ + + sources.cpp + + ld-diffdod - TBC Differential Drop-Out Detection tool + Copyright (C) 2019 Simon Inns + + This file is part of ld-decode-tools. + + ld-diffdod is free software: you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +************************************************************************/ + +#include "sources.h" + +Sources::Sources(QObject *parent) : QObject(parent) +{ + currentSource = 0; + currentVbiFrameNumber = 1; +} + +bool Sources::process(QVector inputFilenames, bool reverse, + qint32 dodThreshold, bool lumaClip, + qint32 startVbi, qint32 lengthVbi) +{ + // Show input filenames + qInfo() << "Processing" << inputFilenames.size() << "input TBC files:"; + for (qint32 i = 0; i < inputFilenames.size(); i++) qInfo().nospace() << " Source #" << i << ": " << inputFilenames[i]; + + // And then show the rest... + if (reverse) qInfo() << "Using reverse field order"; else qInfo() << "Using normal field order"; + qInfo().nospace() << "Dropout detection threshold is " << dodThreshold << "% difference"; + if (lumaClip) qInfo() << "Performing luma clip detection"; else qInfo() << "Not performing luma clip detection"; + qInfo() << ""; + + // Load the input TBC files --------------------------------------------------------------------------------------- + if (!loadInputTbcFiles(inputFilenames, reverse)) { + qCritical() << "Error: Unable to load input TBC files - cannot continue!"; + return false; + } + + // Show disc and video information + qInfo() << ""; + qInfo() << "Sources have VBI frame number range of" << getMinimumVbiFrameNumber() << + "to" << getMaximumVbiFrameNumber(); + + // Check start and length + qint32 vbiStartFrame = startVbi; + if (vbiStartFrame < getMinimumVbiFrameNumber()) + vbiStartFrame = getMinimumVbiFrameNumber(); + + qint32 length = lengthVbi; + if (length > (getMaximumVbiFrameNumber() - vbiStartFrame + 1)) + length = getMaximumVbiFrameNumber() - vbiStartFrame + 1; + if (length == -1) length = getMaximumVbiFrameNumber() - getMinimumVbiFrameNumber() + 1; + + // Verify frame source availablity + qInfo() << ""; + qInfo() << "Verifying VBI frame multi-source availablity:"; + verifySources(vbiStartFrame, length); + + // Process the sources -------------------------------------------------------------------------------------------- + qInfo() << ""; + qInfo() << "Processing" << length << "frames starting from VBI frame" << vbiStartFrame; + processSources(vbiStartFrame, length, dodThreshold, lumaClip); + + // Save the sources ----------------------------------------------------------------------------------------------- + qInfo() << ""; + qInfo() << "Saving sources"; + saveSources(); + + return true; +} + +// Load all available input sources +bool Sources::loadInputTbcFiles(QVector inputFilenames, bool reverse) +{ + for (qint32 i = 0; i < inputFilenames.size(); i++) { + qInfo().nospace() << "Loading TBC input source #" << i << " - Filename: " << inputFilenames[i]; + if (!loadSource(inputFilenames[i], reverse)) { + return false; + } + } + + return true; +} + +// Load a TBC source video; returns false on failure +bool Sources::loadSource(QString filename, bool reverse) +{ + // Check that source file isn't already loaded + for (qint32 i = 0; i < sourceVideos.size(); i++) { + if (filename == sourceVideos[i]->filename) { + qCritical() << "Cannot load source - source is already loaded!"; + return false; + } + } + + bool loadSuccessful = true; + sourceVideos.resize(sourceVideos.size() + 1); + qint32 newSourceNumber = sourceVideos.size() - 1; + sourceVideos[newSourceNumber] = new Source; + LdDecodeMetaData::VideoParameters videoParameters; + + // Open the TBC metadata file + qInfo() << "Processing input TBC JSON metadata..."; + if (!sourceVideos[newSourceNumber]->ldDecodeMetaData.read(filename + ".json")) { + // Open failed + qWarning() << "Open TBC JSON metadata failed for filename" << filename; + qCritical() << "Cannot load source - JSON metadata could not be read!"; + + delete sourceVideos[newSourceNumber]; + sourceVideos.remove(newSourceNumber); + currentSource = 0; + return false; + } + + // Set the source as reverse field order if required + if (reverse) sourceVideos[newSourceNumber]->ldDecodeMetaData.setIsFirstFieldFirst(false); + + // Get the video parameters from the metadata + videoParameters = sourceVideos[newSourceNumber]->ldDecodeMetaData.getVideoParameters(); + + // Ensure that the TBC file has been mapped + if (!videoParameters.isMapped) { + qWarning() << "New source video has not been mapped!"; + qCritical() << "Cannot load source - The TBC has not been mapped (please run ld-discmap on the source)!"; + loadSuccessful = false; + } + + // Ensure that the video standard matches any existing sources + if (loadSuccessful) { + if ((sourceVideos.size() - 1 > 0) && (sourceVideos[0]->ldDecodeMetaData.getVideoParameters().isSourcePal + != videoParameters.isSourcePal)) { + qWarning() << "New source video standard does not match existing source(s)!"; + qCritical() << "Cannot load source - Mixing PAL and NTSC sources is not supported!"; + loadSuccessful = false; + } + } + + if (videoParameters.isSourcePal) qInfo() << "Video format is PAL"; else qInfo() << "Video format is NTSC"; + + // Ensure that the video has VBI data + if (loadSuccessful) { + if (!sourceVideos[newSourceNumber]->ldDecodeMetaData.getFieldVbi(1).inUse) { + qWarning() << "New source video does not contain VBI data!"; + qCritical() << "Cannot load source - No VBI data available. Please run ld-process-vbi before loading source!"; + loadSuccessful = false; + } + } + + // Determine the minimum and maximum VBI frame number and the disc type + if (loadSuccessful) { + qInfo() << "Determining input TBC disc type and VBI frame range..."; + if (!setDiscTypeAndMaxMinFrameVbi(newSourceNumber)) { + // Failed + qCritical() << "Cannot load source - Could not determine disc type and/or VBI frame range!"; + loadSuccessful = false; + } + } + + // Show the 0 and 100IRE points for the source + qInfo() << "Source has 0IRE at" << videoParameters.black16bIre << "and 100IRE at" << videoParameters.white16bIre; + + // Open the new source TBC video + if (loadSuccessful) { + qInfo() << "Loading input TBC video data..."; + if (!sourceVideos[newSourceNumber]->sourceVideo.open(filename, videoParameters.fieldWidth * videoParameters.fieldHeight)) { + // Open failed + qWarning() << "Open TBC file failed for filename" << filename; + qCritical() << "Cannot load source - Error reading source TBC data file!"; + loadSuccessful = false; + } + } + + // Finish up + if (loadSuccessful) { + // Loading successful + sourceVideos[newSourceNumber]->filename = filename; + loadSuccessful = true; + } else { + // Loading unsuccessful - Remove the new source entry and default the current source + sourceVideos[newSourceNumber]->sourceVideo.close(); + delete sourceVideos[newSourceNumber]; + sourceVideos.remove(newSourceNumber); + currentSource = 0; + return false; + } + + // Select the new source + currentSource = newSourceNumber; + + return true; +} + +// Method to work out the disc type (CAV or CLV) and the maximum and minimum +// VBI frame numbers for the source +bool Sources::setDiscTypeAndMaxMinFrameVbi(qint32 sourceNumber) +{ + sourceVideos[sourceNumber]->isSourceCav = false; + + // Determine the disc type + VbiDecoder vbiDecoder; + qint32 cavCount = 0; + qint32 clvCount = 0; + + qint32 typeCountMax = 100; + if (sourceVideos[sourceNumber]->ldDecodeMetaData.getNumberOfFrames() < typeCountMax) + typeCountMax = sourceVideos[sourceNumber]->ldDecodeMetaData.getNumberOfFrames(); + + // Using sequential frame numbering starting from 1 + for (qint32 seqFrame = 1; seqFrame <= typeCountMax; seqFrame++) { + // Get the VBI data and then decode + QVector vbi1 = sourceVideos[sourceNumber]->ldDecodeMetaData.getFieldVbi(sourceVideos[sourceNumber]-> + ldDecodeMetaData.getFirstFieldNumber(seqFrame)).vbiData; + QVector vbi2 = sourceVideos[sourceNumber]->ldDecodeMetaData.getFieldVbi(sourceVideos[sourceNumber]-> + ldDecodeMetaData.getSecondFieldNumber(seqFrame)).vbiData; + VbiDecoder::Vbi vbi = vbiDecoder.decodeFrame(vbi1[0], vbi1[1], vbi1[2], vbi2[0], vbi2[1], vbi2[2]); + + // Look for a complete, valid CAV picture number or CLV time-code + if (vbi.picNo > 0) cavCount++; + + if (vbi.clvHr != -1 && vbi.clvMin != -1 && + vbi.clvSec != -1 && vbi.clvPicNo != -1) clvCount++; + } + qDebug() << "Got" << cavCount << "CAV picture codes and" << clvCount << "CLV timecodes"; + + // If the metadata has no picture numbers or time-codes, we cannot use the source + if (cavCount == 0 && clvCount == 0) { + qDebug() << "Source does not seem to contain valid CAV picture numbers or CLV time-codes - cannot process"; + return false; + } + + // Determine disc type + if (cavCount > clvCount) { + sourceVideos[sourceNumber]->isSourceCav = true; + qDebug() << "Got" << cavCount << "valid CAV picture numbers - source disc type is CAV"; + qInfo() << "Disc type is CAV"; + } else { + sourceVideos[sourceNumber]->isSourceCav = false; + qDebug() << "Got" << clvCount << "valid CLV picture numbers - source disc type is CLV"; + qInfo() << "Disc type is CLV"; + + } + + // Disc has been mapped, so we can use the first and last frame numbers as the + // min and max range of VBI frame numbers in the input source + QVector vbi1 = sourceVideos[sourceNumber]->ldDecodeMetaData.getFieldVbi(sourceVideos[sourceNumber]-> + ldDecodeMetaData.getFirstFieldNumber(1)).vbiData; + QVector vbi2 = sourceVideos[sourceNumber]->ldDecodeMetaData.getFieldVbi(sourceVideos[sourceNumber]-> + ldDecodeMetaData.getSecondFieldNumber(1)).vbiData; + VbiDecoder::Vbi vbi = vbiDecoder.decodeFrame(vbi1[0], vbi1[1], vbi1[2], vbi2[0], vbi2[1], vbi2[2]); + + if (sourceVideos[sourceNumber]->isSourceCav) { + sourceVideos[sourceNumber]->minimumVbiFrameNumber = vbi.picNo; + } else { + LdDecodeMetaData::ClvTimecode timecode; + timecode.hours = vbi.clvHr; + timecode.minutes = vbi.clvMin; + timecode.seconds = vbi.clvSec; + timecode.pictureNumber = vbi.clvPicNo; + sourceVideos[sourceNumber]->minimumVbiFrameNumber = sourceVideos[sourceNumber]->ldDecodeMetaData.convertClvTimecodeToFrameNumber(timecode); + } + + vbi1 = sourceVideos[sourceNumber]->ldDecodeMetaData.getFieldVbi(sourceVideos[sourceNumber]-> + ldDecodeMetaData.getFirstFieldNumber(sourceVideos[sourceNumber]->ldDecodeMetaData.getNumberOfFrames())).vbiData; + vbi2 = sourceVideos[sourceNumber]->ldDecodeMetaData.getFieldVbi(sourceVideos[sourceNumber]-> + ldDecodeMetaData.getSecondFieldNumber(sourceVideos[sourceNumber]->ldDecodeMetaData.getNumberOfFrames())).vbiData; + vbi = vbiDecoder.decodeFrame(vbi1[0], vbi1[1], vbi1[2], vbi2[0], vbi2[1], vbi2[2]); + + if (sourceVideos[sourceNumber]->isSourceCav) { + sourceVideos[sourceNumber]->maximumVbiFrameNumber = vbi.picNo; + } else { + LdDecodeMetaData::ClvTimecode timecode; + timecode.hours = vbi.clvHr; + timecode.minutes = vbi.clvMin; + timecode.seconds = vbi.clvSec; + timecode.pictureNumber = vbi.clvPicNo; + sourceVideos[sourceNumber]->maximumVbiFrameNumber = sourceVideos[sourceNumber]->ldDecodeMetaData.convertClvTimecodeToFrameNumber(timecode); + } + + if (sourceVideos[sourceNumber]->isSourceCav) { + // If the source is CAV frame numbering should be a minimum of 1 (it + // can be 0 for CLV sources) + if (sourceVideos[sourceNumber]->minimumVbiFrameNumber < 1) { + qCritical() << "CAV start frame of" << sourceVideos[sourceNumber]->minimumVbiFrameNumber << "is out of bounds (should be 1 or above)"; + return false; + } + } + + qInfo() << "VBI frame number range is" << sourceVideos[sourceNumber]->minimumVbiFrameNumber << "to" << + sourceVideos[sourceNumber]->maximumVbiFrameNumber; + + return true; +} + +// Get the minimum VBI frame number for all sources +qint32 Sources::getMinimumVbiFrameNumber() +{ + qint32 minimumFrameNumber = 1000000; + for (qint32 i = 0; i < sourceVideos.size(); i++) { + if (sourceVideos[i]->minimumVbiFrameNumber < minimumFrameNumber) + minimumFrameNumber = sourceVideos[i]->minimumVbiFrameNumber; + } + + return minimumFrameNumber; +} + +// Get the maximum VBI frame number for all sources +qint32 Sources::getMaximumVbiFrameNumber() +{ + qint32 maximumFrameNumber = 0; + for (qint32 i = 0; i < sourceVideos.size(); i++) { + if (sourceVideos[i]->maximumVbiFrameNumber > maximumFrameNumber) + maximumFrameNumber = sourceVideos[i]->maximumVbiFrameNumber; + } + + return maximumFrameNumber; +} + +// Verify that at least 3 sources are available for every VBI frame +void Sources::verifySources(qint32 vbiStartFrame, qint32 length) +{ + qint32 uncorrectableFrameCount = 0; + + // Process the sources frame by frame + for (qint32 vbiFrame = vbiStartFrame; vbiFrame < vbiStartFrame + length; vbiFrame++) { + // Check how many source frames are available for the current frame + QVector availableSourcesForFrame = getAvailableSourcesForFrame(vbiFrame); + + // DiffDOD requires at least three source frames. If 3 sources are not available leave any existing DO records in place + // and output a warning to the user + if (availableSourcesForFrame.size() < 3) { + // Differential DOD requires at least 3 valid source frames + qInfo().nospace() << "Frame #" << vbiFrame << " has only " << availableSourcesForFrame.size() << " source frames available - cannot correct"; + uncorrectableFrameCount++; + } + } + + if (uncorrectableFrameCount != 0) { + qInfo() << "Warning:" << uncorrectableFrameCount << "frame(s) cannot be corrected!"; + } else { + qInfo() << "All frames have at least 3 sources available"; + } +} + +// Method that returns a vector of the sources that contain data for the required VBI frame number +QVector Sources::getAvailableSourcesForFrame(qint32 vbiFrameNumber) +{ + QVector availableSourcesForFrame; + for (qint32 sourceNo = 0; sourceNo < sourceVideos.size(); sourceNo++) { + if (vbiFrameNumber >= sourceVideos[sourceNo]->minimumVbiFrameNumber && vbiFrameNumber <= sourceVideos[sourceNo]->maximumVbiFrameNumber) { + // Get the field numbers for the frame + qint32 firstFieldNumber = sourceVideos[sourceNo]->ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(vbiFrameNumber, sourceNo)); + qint32 secondFieldNumber = sourceVideos[sourceNo]->ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(vbiFrameNumber, sourceNo)); + + // Ensure the frame is not a padded field (i.e. missing) + if (!(sourceVideos[sourceNo]->ldDecodeMetaData.getField(firstFieldNumber).pad && + sourceVideos[sourceNo]->ldDecodeMetaData.getField(secondFieldNumber).pad)) { + availableSourcesForFrame.append(sourceNo); + } + } + } + + return availableSourcesForFrame; +} + +// Method to convert a VBI frame number to a sequential frame number +qint32 Sources::convertVbiFrameNumberToSequential(qint32 vbiFrameNumber, qint32 sourceNumber) +{ + // Offset the VBI frame number to get the sequential source frame number + return vbiFrameNumber - sourceVideos[sourceNumber]->minimumVbiFrameNumber + 1; +} + +// Get the number of available sources +qint32 Sources::getNumberOfAvailableSources() +{ + return sourceVideos.size(); +} + +// Method to process a source frame +void Sources::processSources(qint32 vbiStartFrame, qint32 length, qint32 dodThreshold, bool lumaClip) +{ + // Get the metadata for the video parameters (all sources are the same, so just grab from the first) + LdDecodeMetaData::VideoParameters videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); + + // Define a processing object + DiffDod diffDod; + + // Process the sources frame by frame + for (qint32 targetVbiFrame = vbiStartFrame; targetVbiFrame < vbiStartFrame + length; targetVbiFrame++) { + if ((targetVbiFrame % 100 == 0) || (targetVbiFrame == vbiStartFrame)) qInfo() << "Processing VBI frame" << targetVbiFrame; + + // Get the number of available sources for the current frame + QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); + + // Get the field data for the current frame (from all available sources) + QVector firstFields = getFieldData(targetVbiFrame, true, videoParameters, availableSourcesForFrame); + QVector secondFields = getFieldData(targetVbiFrame, false, videoParameters, availableSourcesForFrame); + + // Process the field + diffDod.performFrameDiffDod(firstFields, secondFields, dodThreshold, lumaClip, videoParameters, availableSourcesForFrame); + + // Get the result + QVector firstFieldDropouts; + QVector secondFieldDropouts; + diffDod.getResults(firstFieldDropouts, secondFieldDropouts); + + // Write the first and second field line metadata back to the source + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + + // Get the required field numbers + qint32 firstFieldNumber = sourceVideos[sourceNo]-> + ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); + qint32 secondFieldNumber = sourceVideos[sourceNo]-> + ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); + + // Calculate the total number of dropouts detected for the frame + qint32 totalFirstDropouts = firstFieldDropouts[sourceNo].startx.size(); + qint32 totalSecondDropouts = secondFieldDropouts[sourceNo].startx.size(); + + qDebug() << "Writing source" << sourceNo << + "frame" << targetVbiFrame << "fields" << firstFieldNumber << "/" << secondFieldNumber << + "- Dropout records" << totalFirstDropouts << "/" << totalSecondDropouts; + + // Only replace the existing metadata if it was possible to create new metadata + if (availableSourcesForFrame.size() >= 3) { + // Remove the existing field dropout metadata for the field + sourceVideos[sourceNo]->ldDecodeMetaData.clearFieldDropOuts(firstFieldNumber); + sourceVideos[sourceNo]->ldDecodeMetaData.clearFieldDropOuts(secondFieldNumber); + + // Write the new field dropout metadata + sourceVideos[sourceNo]->ldDecodeMetaData.updateFieldDropOuts(firstFieldDropouts[sourceNo], firstFieldNumber); + sourceVideos[sourceNo]->ldDecodeMetaData.updateFieldDropOuts(secondFieldDropouts[sourceNo], secondFieldNumber); + } + } + + } +} + +// Method to write the source metadata to disc +void Sources::saveSources() +{ + // Save the sources' metadata + for (qint32 sourceNo = 0; sourceNo < sourceVideos.size(); sourceNo++) { + // Write the JSON metadata + qInfo() << "Writing JSON metadata file for TBC file" << sourceNo; + sourceVideos[sourceNo]->ldDecodeMetaData.write(sourceVideos[sourceNo]->filename + ".json"); + } +} + +// Get the field data for the specified frame +QVector Sources::getFieldData(qint32 targetVbiFrame, bool isFirstField, LdDecodeMetaData::VideoParameters videoParameters, + QVector &availableSourcesForFrame) +{ + // Only display on first field (otherwise we will get 2 of the same debug) + if (isFirstField) qDebug() << "Processing VBI Frame" << targetVbiFrame << "-" << availableSourcesForFrame.size() << "sources available"; + + // Get the field data for the frame from all of the available sources and copy locally + QVector fields; + fields.resize(getNumberOfAvailableSources()); + + QVector sourceFirstFieldPointer; + sourceFirstFieldPointer.resize(getNumberOfAvailableSources()); + + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + qint32 fieldNumber = -1; + if (isFirstField) fieldNumber = sourceVideos[sourceNo]-> + ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); + else fieldNumber = sourceVideos[sourceNo]-> + ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); + + // Copy the data locally + fields[sourceNo] = (sourceVideos[sourceNo]->sourceVideo.getVideoField(fieldNumber)); + + // Filter out the chroma information from the fields leaving just luma + Filters filters; + if (videoParameters.isSourcePal) { + filters.palLumaFirFilter(fields[sourceNo].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); + } else { + filters.ntscLumaFirFilter(fields[sourceNo].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); + } + } + + return fields; +} diff --git a/tools/ld-diffdod/tbcsources.h b/tools/ld-diffdod/sources.h similarity index 57% rename from tools/ld-diffdod/tbcsources.h rename to tools/ld-diffdod/sources.h index e9c90fe39..95706d3e8 100644 --- a/tools/ld-diffdod/tbcsources.h +++ b/tools/ld-diffdod/sources.h @@ -1,6 +1,6 @@ /************************************************************************ - tbcsources.h + sources.h ld-diffdod - TBC Differential Drop-Out Detection tool Copyright (C) 2019 Simon Inns @@ -22,34 +22,23 @@ ************************************************************************/ -#ifndef TBCSOURCES_H -#define TBCSOURCES_H +#ifndef SOURCES_H +#define SOURCES_H #include -#include -#include #include +#include -// TBC library includes -#include "sourcevideo.h" -#include "lddecodemetadata.h" -#include "vbidecoder.h" -#include "filters.h" +#include "diffdod.h" -class TbcSources : public QObject +class Sources : public QObject { Q_OBJECT public: - explicit TbcSources(QObject *parent = nullptr); + explicit Sources(QObject *parent = nullptr); - bool loadSource(QString filename, bool reverse); - bool unloadSource(); - bool saveSources(qint32 vbiStartFrame, qint32 length, qint32 dodThreshold, bool lumaClip); - - qint32 getNumberOfAvailableSources(); - qint32 getMinimumVbiFrameNumber(); - qint32 getMaximumVbiFrameNumber(); - void verifySources(qint32 vbiStartFrame, qint32 length); + bool process(QVector inputFilenames, bool reverse, + qint32 dodThreshold, bool lumaClip, qint32 startVbi, qint32 lengthVbi); private: // Source definition @@ -68,20 +57,19 @@ class TbcSources : public QObject QVector sourceVideos; qint32 currentSource; - void performFrameDiffDod(qint32 targetVbiFrame, qint32 dodOnThreshold, bool lumaClip); - QVector getFieldData(qint32 targetVbiFrame, bool isFirstField); - QVector getFieldErrorByMedian(qint32 targetVbiFrame, QVector &fields, qint32 dodThreshold); - void performLumaClip(qint32 targetVbiFrame, QVector &fields, QVector &fieldsDiff); - QVector getFieldDropouts(qint32 targetVbiFrame, QVector &fieldsDiff, bool isFirstField); - void writeDropoutMetadata(qint32 targetVbiFrame, QVector &firstFieldDropouts, - QVector &secondFieldDropouts); - void concatenateFieldDropouts(qint32 targetVbiFrame, QVector &dropouts); - - QVector getAvailableSourcesForFrame(qint32 vbiFrameNumber); + bool loadInputTbcFiles(QVector inputFilenames, bool reverse); + bool loadSource(QString filename, bool reverse); bool setDiscTypeAndMaxMinFrameVbi(qint32 sourceNumber); + qint32 getMinimumVbiFrameNumber(); + qint32 getMaximumVbiFrameNumber(); + void verifySources(qint32 vbiStartFrame, qint32 length); + QVector getAvailableSourcesForFrame(qint32 vbiFrameNumber); qint32 convertVbiFrameNumberToSequential(qint32 vbiFrameNumber, qint32 sourceNumber); - qint32 median(QVector v); - float convertLinearToBrightness(quint16 value, quint16 black16bIre, quint16 white16bIre, bool isSourcePal); + qint32 getNumberOfAvailableSources(); + void processSources(qint32 vbiStartFrame, qint32 length, qint32 dodThreshold, bool lumaClip); + void saveSources(); + QVector getFieldData(qint32 targetVbiFrame, bool isFirstField, LdDecodeMetaData::VideoParameters videoParameters, + QVector &availableSourcesForFrame); }; -#endif // TBCSOURCES_H +#endif // SOURCES_H diff --git a/tools/ld-diffdod/tbcsources.cpp b/tools/ld-diffdod/tbcsources.cpp deleted file mode 100644 index ece9ba0cf..000000000 --- a/tools/ld-diffdod/tbcsources.cpp +++ /dev/null @@ -1,782 +0,0 @@ -/************************************************************************ - - tbcsources.cpp - - ld-diffdod - TBC Differential Drop-Out Detection tool - Copyright (C) 2019 Simon Inns - - This file is part of ld-decode-tools. - - ld-diffdod is free software: you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -************************************************************************/ - -#include "tbcsources.h" - -TbcSources::TbcSources(QObject *parent) : QObject(parent) -{ - currentSource = 0; - currentVbiFrameNumber = 1; -} - -// Load a TBC source video; returns false on failure -bool TbcSources::loadSource(QString filename, bool reverse) -{ - // Check that source file isn't already loaded - for (qint32 i = 0; i < sourceVideos.size(); i++) { - if (filename == sourceVideos[i]->filename) { - qCritical() << "Cannot load source - source is already loaded!"; - return false; - } - } - - bool loadSuccessful = true; - sourceVideos.resize(sourceVideos.size() + 1); - qint32 newSourceNumber = sourceVideos.size() - 1; - sourceVideos[newSourceNumber] = new Source; - LdDecodeMetaData::VideoParameters videoParameters; - - // Open the TBC metadata file - qInfo() << "Processing input TBC JSON metadata..."; - if (!sourceVideos[newSourceNumber]->ldDecodeMetaData.read(filename + ".json")) { - // Open failed - qWarning() << "Open TBC JSON metadata failed for filename" << filename; - qCritical() << "Cannot load source - JSON metadata could not be read!"; - - delete sourceVideos[newSourceNumber]; - sourceVideos.remove(newSourceNumber); - currentSource = 0; - return false; - } - - // Set the source as reverse field order if required - if (reverse) sourceVideos[newSourceNumber]->ldDecodeMetaData.setIsFirstFieldFirst(false); - - // Get the video parameters from the metadata - videoParameters = sourceVideos[newSourceNumber]->ldDecodeMetaData.getVideoParameters(); - - // Ensure that the TBC file has been mapped - if (!videoParameters.isMapped) { - qWarning() << "New source video has not been mapped!"; - qCritical() << "Cannot load source - The TBC has not been mapped (please run ld-discmap on the source)!"; - loadSuccessful = false; - } - - // Ensure that the video standard matches any existing sources - if (loadSuccessful) { - if ((sourceVideos.size() - 1 > 0) && (sourceVideos[0]->ldDecodeMetaData.getVideoParameters().isSourcePal != videoParameters.isSourcePal)) { - qWarning() << "New source video standard does not match existing source(s)!"; - qCritical() << "Cannot load source - Mixing PAL and NTSC sources is not supported!"; - loadSuccessful = false; - } - } - - if (videoParameters.isSourcePal) qInfo() << "Video format is PAL"; else qInfo() << "Video format is NTSC"; - - // Ensure that the video has VBI data - if (loadSuccessful) { - if (!sourceVideos[newSourceNumber]->ldDecodeMetaData.getFieldVbi(1).inUse) { - qWarning() << "New source video does not contain VBI data!"; - qCritical() << "Cannot load source - No VBI data available. Please run ld-process-vbi before loading source!"; - loadSuccessful = false; - } - } - - // Determine the minimum and maximum VBI frame number and the disc type - if (loadSuccessful) { - qInfo() << "Determining input TBC disc type and VBI frame range..."; - if (!setDiscTypeAndMaxMinFrameVbi(newSourceNumber)) { - // Failed - qCritical() << "Cannot load source - Could not determine disc type and/or VBI frame range!"; - loadSuccessful = false; - } - } - - // Show the 0 and 100IRE points for the source - qInfo() << "Source has 0IRE at" << videoParameters.black16bIre << "and 100IRE at" << videoParameters.white16bIre; - - // Open the new source TBC video - if (loadSuccessful) { - qInfo() << "Loading input TBC video data..."; - if (!sourceVideos[newSourceNumber]->sourceVideo.open(filename, videoParameters.fieldWidth * videoParameters.fieldHeight)) { - // Open failed - qWarning() << "Open TBC file failed for filename" << filename; - qCritical() << "Cannot load source - Error reading source TBC data file!"; - loadSuccessful = false; - } - } - - // Finish up - if (loadSuccessful) { - // Loading successful - sourceVideos[newSourceNumber]->filename = filename; - loadSuccessful = true; - } else { - // Loading unsuccessful - Remove the new source entry and default the current source - sourceVideos[newSourceNumber]->sourceVideo.close(); - delete sourceVideos[newSourceNumber]; - sourceVideos.remove(newSourceNumber); - currentSource = 0; - return false; - } - - // Select the new source - currentSource = newSourceNumber; - - return true; -} - -// Unload a source video and remove it's data -bool TbcSources::unloadSource() -{ - sourceVideos[currentSource]->sourceVideo.close(); - delete sourceVideos[currentSource]; - sourceVideos.remove(currentSource); - currentSource = 0; - - return false; -} - -// Save TBC source video metadata for all sources; returns false on failure -bool TbcSources::saveSources(qint32 vbiStartFrame, qint32 length, qint32 dodThreshold, bool lumaClip) -{ - // Process the sources frame by frame - for (qint32 vbiFrame = vbiStartFrame; vbiFrame < vbiStartFrame + length; vbiFrame++) { - if ((vbiFrame % 100 == 0) || (vbiFrame == vbiStartFrame)) qInfo() << "Processing VBI frame" << vbiFrame; - - // Process - performFrameDiffDod(vbiFrame, dodThreshold, lumaClip); - } - - // Save the sources' metadata - for (qint32 sourceNo = 0; sourceNo < sourceVideos.size(); sourceNo++) { - // Write the JSON metadata - qInfo() << "Writing JSON metadata file for TBC file" << sourceNo; - sourceVideos[sourceNo]->ldDecodeMetaData.write(sourceVideos[sourceNo]->filename + ".json"); - } - - return true; -} - -// Get the number of available sources -qint32 TbcSources::getNumberOfAvailableSources() -{ - return sourceVideos.size(); -} - -// Get the minimum VBI frame number for all sources -qint32 TbcSources::getMinimumVbiFrameNumber() -{ - qint32 minimumFrameNumber = 1000000; - for (qint32 i = 0; i < sourceVideos.size(); i++) { - if (sourceVideos[i]->minimumVbiFrameNumber < minimumFrameNumber) - minimumFrameNumber = sourceVideos[i]->minimumVbiFrameNumber; - } - - return minimumFrameNumber; -} - -// Get the maximum VBI frame number for all sources -qint32 TbcSources::getMaximumVbiFrameNumber() -{ - qint32 maximumFrameNumber = 0; - for (qint32 i = 0; i < sourceVideos.size(); i++) { - if (sourceVideos[i]->maximumVbiFrameNumber > maximumFrameNumber) - maximumFrameNumber = sourceVideos[i]->maximumVbiFrameNumber; - } - - return maximumFrameNumber; -} - -// Verify that at least 3 sources are available for every VBI frame -void TbcSources::verifySources(qint32 vbiStartFrame, qint32 length) -{ - qint32 uncorrectableFrameCount = 0; - - // Process the sources frame by frame - for (qint32 vbiFrame = vbiStartFrame; vbiFrame < vbiStartFrame + length; vbiFrame++) { - // Check how many source frames are available for the current frame - QVector availableSourcesForFrame = getAvailableSourcesForFrame(vbiFrame); - - // DiffDOD requires at least three source frames. If 3 sources are not available leave any existing DO records in place - // and output a warning to the user - if (availableSourcesForFrame.size() < 3) { - // Differential DOD requires at least 3 valid source frames - qInfo().nospace() << "Frame #" << vbiFrame << " has only " << availableSourcesForFrame.size() << " source frames available - cannot correct"; - uncorrectableFrameCount++; - } - } - - if (uncorrectableFrameCount != 0) { - qInfo() << "Warning:" << uncorrectableFrameCount << "frame(s) cannot be corrected!"; - } else { - qInfo() << "All frames have at least 3 sources available"; - } -} - -// Private methods ---------------------------------------------------------------------------------------------------- - -// Perform differential dropout detection to determine (for each source) which frame pixels are valid -// Note: This method processes a single frame -void TbcSources::performFrameDiffDod(qint32 targetVbiFrame, qint32 dodThreshold, bool lumaClip) -{ - // Range check the diffDOD threshold percent - if (dodThreshold < 1) dodThreshold = 1; - if (dodThreshold > 100) dodThreshold = 100; - - // Get the field data for the current frame (from all available sources) - QVector firstFields = getFieldData(targetVbiFrame, true); - QVector secondFields = getFieldData(targetVbiFrame, false); - - // Create a differential map of the fields for the avaialble frames (based on the DOD threshold) - QVector firstFieldsDiff = getFieldErrorByMedian(targetVbiFrame, firstFields, dodThreshold); - QVector secondFieldsDiff = getFieldErrorByMedian(targetVbiFrame, secondFields, dodThreshold); - - // Perform luma clip check? - if (lumaClip) { - performLumaClip(targetVbiFrame, firstFields, firstFieldsDiff); - performLumaClip(targetVbiFrame, secondFields, secondFieldsDiff); - } - - // Create the drop-out metadata based on the differential map of the fields - QVector firstFieldDropouts = getFieldDropouts(targetVbiFrame, firstFieldsDiff, true); - QVector secondFieldDropouts = getFieldDropouts(targetVbiFrame, secondFieldsDiff, false); - - // Concatenate dropouts on the same line that are close together (to cut down on the - // amount of generated metadata with noisy/bad sources) - concatenateFieldDropouts(targetVbiFrame, firstFieldDropouts); - concatenateFieldDropouts(targetVbiFrame, secondFieldDropouts); - - // Write the dropout metadata back to the sources - writeDropoutMetadata(targetVbiFrame, firstFieldDropouts, secondFieldDropouts); -} - -// Get the field data for the specified frame -QVector TbcSources::getFieldData(qint32 targetVbiFrame, bool isFirstField) -{ - // Get the metadata for the video parameters (all sources are the same, so just grab from the first) - LdDecodeMetaData::VideoParameters videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); - - // Check how many source frames are available for the current frame - QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); - - // Only display on first field (otherwise we will get 2 of the same debug) - if (isFirstField) qDebug() << "Processing VBI Frame" << targetVbiFrame << "-" << availableSourcesForFrame.size() << "sources available"; - - // Get the field data for the frame from all of the available sources and copy locally - QVector fields; - fields.resize(getNumberOfAvailableSources()); - - QVector sourceFirstFieldPointer; - sourceFirstFieldPointer.resize(getNumberOfAvailableSources()); - - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - qint32 fieldNumber = -1; - if (isFirstField) fieldNumber = sourceVideos[sourceNo]-> - ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); - else fieldNumber = sourceVideos[sourceNo]-> - ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); - - // Copy the data locally - fields[sourceNo] = (sourceVideos[sourceNo]->sourceVideo.getVideoField(fieldNumber)); - - // Filter out the chroma information from the fields leaving just luma - Filters filters; - if (videoParameters.isSourcePal) { - filters.palLumaFirFilter(fields[sourceNo].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); - } else { - filters.ntscLumaFirFilter(fields[sourceNo].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); - } - } - - return fields; -} - -// Create an error map of the fields based on median value differential analysis -// Note: This only functions within the colour burst and visible areas of the frame -QVector TbcSources::getFieldErrorByMedian(qint32 targetVbiFrame, QVector &fields, qint32 dodThreshold) -{ - // Get the metadata for the video parameters (all sources are the same, so just grab from the first) - LdDecodeMetaData::VideoParameters videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); - - // Check how many source frames are available for the current frame - QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); - - // This method requires at least three source frames - if (availableSourcesForFrame.size() < 3) { - return QVector(); - } - - // Normalize the % dodThreshold to 0.00-1.00 - float threshold = static_cast(dodThreshold) / 100.0; - - // Calculate the linear threshold for the colourburst region - qint32 cbThreshold = ((65535 / 100) * dodThreshold) / 8; // Note: The /8 is just a guess - - // Make a vector to store the result of the diff - QVector fieldDiff; - fieldDiff.resize(getNumberOfAvailableSources()); - - // Resize the fieldDiff sub-vectors and default the elements to zero - for (qint32 sourcePointer = 0; sourcePointer < getNumberOfAvailableSources(); sourcePointer++) { - fieldDiff[sourcePointer].fill(0, videoParameters.fieldHeight * videoParameters.fieldWidth); - } - - for (qint32 y = 0; y < videoParameters.fieldHeight; y++) { - qint32 startOfLinePointer = y * videoParameters.fieldWidth; - for (qint32 x = videoParameters.colourBurstStart; x < videoParameters.activeVideoEnd; x++) { - // Get the dot value from all of the sources - QVector dotValues(getNumberOfAvailableSources()); - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - dotValues[sourceNo] = static_cast(fields[sourceNo][x + startOfLinePointer]); - } - - // If we are in the visible area use Rec.709 logarithmic comparison - if (x >= videoParameters.activeVideoStart && x < videoParameters.activeVideoEnd) { - // Compute the median of the dot values - float vMedian = static_cast(convertLinearToBrightness(median(dotValues), videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal)); - - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - float v = convertLinearToBrightness(dotValues[sourceNo], videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal); - if ((v - vMedian) > threshold) fieldDiff[sourceNo][x + startOfLinePointer] = 1; - } - } - - // If we are in the colourburst use linear comparison - if (x >= videoParameters.colourBurstStart && x < videoParameters.colourBurstEnd) { - // We are in the colour burst, use linear comparison - qint32 dotMedian = median(dotValues); - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - if ((dotValues[sourceNo] - dotMedian) > cbThreshold) fieldDiff[sourceNo][x + startOfLinePointer] = 1; - } - } - } - } - - return fieldDiff; -} - -// Perform a luma clip check on the field -void TbcSources::performLumaClip(qint32 targetVbiFrame, QVector &fields, QVector &fieldsDiff) -{ - // Get the metadata for the video parameters (all sources are the same, so just grab from the first) - LdDecodeMetaData::VideoParameters videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); - - // Check how many source frames are available for the current frame - QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); - - // Process the fields one line at a time - for (qint32 y = videoParameters.firstActiveFieldLine; y < videoParameters.lastActiveFieldLine; y++) { - qint32 startOfLinePointer = y * videoParameters.fieldWidth; - - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - - // Set the clipping levels - qint32 blackClipLevel = videoParameters.black16bIre - 4000; - qint32 whiteClipLevel = videoParameters.white16bIre + 4000; - - for (qint32 x = videoParameters.activeVideoStart; x < videoParameters.activeVideoEnd; x++) { - // Get the IRE value for the source field, cast to 32 bit signed - qint32 sourceIre = static_cast(fields[sourceNo][x + startOfLinePointer]); - - // Check for a luma clip event - if ((sourceIre < blackClipLevel) || (sourceIre > whiteClipLevel)) { - // Luma has clipped, scan back and forth looking for the start - // and end points of the event (i.e. the point where the event - // goes back into the expected IRE range) - qint32 range = 10; // maximum + and - scan range - qint32 minX = x - range; - if (minX < videoParameters.activeVideoStart) minX = videoParameters.activeVideoStart; - qint32 maxX = x + range; - if (maxX > videoParameters.activeVideoEnd) maxX = videoParameters.activeVideoEnd; - - qint32 startX = x; - qint32 endX = x; - - for (qint32 i = x; i > minX; i--) { - qint32 ire = static_cast(fields[sourceNo][x + startOfLinePointer]); - if (ire < videoParameters.black16bIre || ire > videoParameters.white16bIre) { - startX = i; - } - } - - for (qint32 i = x+1; i < maxX; i++) { - qint32 ire = static_cast(fields[sourceNo][x + startOfLinePointer]); - if (ire < videoParameters.black16bIre || ire > videoParameters.white16bIre) { - endX = i; - } - } - - // Mark the dropout - for (qint32 i = startX; i < endX; i++) { - fieldsDiff[sourceNo][i + startOfLinePointer] = 1; - } - - x = x + range; - } - } - } - } -} - -// Method to create the field drop-out metadata based on the differential map of the fields -// This method compares each available source against all other available sources to determine where the source differs. -// If any of the frame's contents do not match that of the other sources, the frame's pixels are marked as dropouts. -QVector TbcSources::getFieldDropouts(qint32 targetVbiFrame, QVector &fieldsDiff, bool isFirstField) -{ - // Get the metadata for the video parameters (all sources are the same, so just grab from the first) - LdDecodeMetaData::VideoParameters videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); - - // Check how many source frames are available for the current frame - QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); - - // Create and resize the return data vector - QVector fieldDropouts; - fieldDropouts.resize(getNumberOfAvailableSources()); - - // This method requires at least three source frames - if (availableSourcesForFrame.size() < 3) { - // Not enough source frames - if (isFirstField) qInfo() << "Only" << availableSourcesForFrame.size() << "available sources for VBI frame" << - targetVbiFrame << "- preserving original dropout data"; - return QVector(); - } - - // Define the area in which DOD should be performed - qint32 areaStart = videoParameters.colourBurstStart; - qint32 areaEnd = videoParameters.activeVideoEnd; - - // Process the frame one line at a time (both fields) - for (qint32 y = 0; y < videoParameters.fieldHeight; y++) { - qint32 startOfLinePointer = y * videoParameters.fieldWidth; - - // Process each source line in turn - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - - // Mark the individual dropouts - qint32 doCounter = 0; - qint32 minimumDetectLength = 5; - - qint32 doStart = 0; - qint32 doFieldLine = 0; - - // Only create dropouts between the start of the colour burst and the end of the - // active video area - for (qint32 x = areaStart; x < areaEnd; x++) { - // Compare field dot to threshold - if (static_cast(fieldsDiff[sourceNo][x + startOfLinePointer]) == 0) { - // Current X is not a dropout - if (doCounter > 0) { - doCounter--; - if (doCounter == 0) { - // Mark the previous x as the end of the dropout - fieldDropouts[sourceNo].startx.append(doStart); - fieldDropouts[sourceNo].endx.append(x - 1); - fieldDropouts[sourceNo].fieldLine.append(doFieldLine); - } - } - } else { - // Current X is a dropout - if (doCounter == 0) { - doCounter = minimumDetectLength; - doStart = x; - doFieldLine = y + 1; - } - } - } - - // Ensure metadata dropouts end at the end of the active video area - if (doCounter > 0) { - doCounter = 0; - - fieldDropouts[sourceNo].startx.append(doStart); - fieldDropouts[sourceNo].endx.append(areaEnd); - fieldDropouts[sourceNo].fieldLine.append(doFieldLine); - } - - } // Next source - } // Next line - - return fieldDropouts; -} - -void TbcSources::writeDropoutMetadata(qint32 targetVbiFrame, QVector &firstFieldDropouts, - QVector &secondFieldDropouts) -{ - // Check how many source frames are available for the current frame - QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); - - // Write the first and second field line metadata back to the source - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - - // Get the required field numbers - qint32 firstFieldNumber = sourceVideos[sourceNo]-> - ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); - qint32 secondFieldNumber = sourceVideos[sourceNo]-> - ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); - - // Calculate the total number of dropouts detected for the frame - qint32 totalFirstDropouts = firstFieldDropouts[sourceNo].startx.size(); - qint32 totalSecondDropouts = secondFieldDropouts[sourceNo].startx.size(); - - qDebug() << "Writing source" << sourceNo << - "frame" << targetVbiFrame << "fields" << firstFieldNumber << "/" << secondFieldNumber << - "- Dropout records" << totalFirstDropouts << "/" << totalSecondDropouts; - - // Only replace the existing metadata if it was possible to create new metadata - if (availableSourcesForFrame.size() >= 3) { - // Remove the existing field dropout metadata for the field - sourceVideos[sourceNo]->ldDecodeMetaData.clearFieldDropOuts(firstFieldNumber); - sourceVideos[sourceNo]->ldDecodeMetaData.clearFieldDropOuts(secondFieldNumber); - - // Write the new field dropout metadata - sourceVideos[sourceNo]->ldDecodeMetaData.updateFieldDropOuts(firstFieldDropouts[sourceNo], firstFieldNumber); - sourceVideos[sourceNo]->ldDecodeMetaData.updateFieldDropOuts(secondFieldDropouts[sourceNo], secondFieldNumber); - } - } -} - -// Method to concatenate dropouts on the same line that are close together -// (to cut down on the amount of generated metadata with noisy/bad sources) -void TbcSources::concatenateFieldDropouts(qint32 targetVbiFrame, QVector &dropouts) -{ - // Check how many source frames are available for the current frame - QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); - - // This variable controls the minimum allowed gap between dropouts - // if the gap between the end of the last dropout and the start of - // the next is less than minimumGap, the two dropouts will be - // concatenated together - qint32 minimumGap = 50; - - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - - // Start from 1 as 0 has no previous dropout - qint32 i = 1; - while (i < dropouts[sourceNo].startx.size()) { - // Is the current dropout on the same field line as the last? - if (dropouts[sourceNo].fieldLine[i - 1] == dropouts[sourceNo].fieldLine[i]) { - if ((dropouts[sourceNo].endx[i - 1] + minimumGap) > (dropouts[sourceNo].startx[i])) { - // Concatenate - dropouts[sourceNo].endx[i - 1] = dropouts[sourceNo].endx[i]; - - // Remove the current dropout - dropouts[sourceNo].startx.removeAt(i); - dropouts[sourceNo].endx.removeAt(i); - dropouts[sourceNo].fieldLine.removeAt(i); - } - } - - // Next dropout - i++; - } - } -} - -// Method that returns a vector of the sources that contain data for the required VBI frame number -QVector TbcSources::getAvailableSourcesForFrame(qint32 vbiFrameNumber) -{ - QVector availableSourcesForFrame; - for (qint32 sourceNo = 0; sourceNo < sourceVideos.size(); sourceNo++) { - if (vbiFrameNumber >= sourceVideos[sourceNo]->minimumVbiFrameNumber && vbiFrameNumber <= sourceVideos[sourceNo]->maximumVbiFrameNumber) { - // Get the field numbers for the frame - qint32 firstFieldNumber = sourceVideos[sourceNo]->ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(vbiFrameNumber, sourceNo)); - qint32 secondFieldNumber = sourceVideos[sourceNo]->ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(vbiFrameNumber, sourceNo)); - - // Ensure the frame is not a padded field (i.e. missing) - if (!(sourceVideos[sourceNo]->ldDecodeMetaData.getField(firstFieldNumber).pad && - sourceVideos[sourceNo]->ldDecodeMetaData.getField(secondFieldNumber).pad)) { - availableSourcesForFrame.append(sourceNo); - } - } - } - - return availableSourcesForFrame; -} - -// Method to work out the disc type (CAV or CLV) and the maximum and minimum -// VBI frame numbers for the source -bool TbcSources::setDiscTypeAndMaxMinFrameVbi(qint32 sourceNumber) -{ - sourceVideos[sourceNumber]->isSourceCav = false; - - // Determine the disc type - VbiDecoder vbiDecoder; - qint32 cavCount = 0; - qint32 clvCount = 0; - - qint32 typeCountMax = 100; - if (sourceVideos[sourceNumber]->ldDecodeMetaData.getNumberOfFrames() < typeCountMax) - typeCountMax = sourceVideos[sourceNumber]->ldDecodeMetaData.getNumberOfFrames(); - - // Using sequential frame numbering starting from 1 - for (qint32 seqFrame = 1; seqFrame <= typeCountMax; seqFrame++) { - // Get the VBI data and then decode - QVector vbi1 = sourceVideos[sourceNumber]->ldDecodeMetaData.getFieldVbi(sourceVideos[sourceNumber]-> - ldDecodeMetaData.getFirstFieldNumber(seqFrame)).vbiData; - QVector vbi2 = sourceVideos[sourceNumber]->ldDecodeMetaData.getFieldVbi(sourceVideos[sourceNumber]-> - ldDecodeMetaData.getSecondFieldNumber(seqFrame)).vbiData; - VbiDecoder::Vbi vbi = vbiDecoder.decodeFrame(vbi1[0], vbi1[1], vbi1[2], vbi2[0], vbi2[1], vbi2[2]); - - // Look for a complete, valid CAV picture number or CLV time-code - if (vbi.picNo > 0) cavCount++; - - if (vbi.clvHr != -1 && vbi.clvMin != -1 && - vbi.clvSec != -1 && vbi.clvPicNo != -1) clvCount++; - } - qDebug() << "Got" << cavCount << "CAV picture codes and" << clvCount << "CLV timecodes"; - - // If the metadata has no picture numbers or time-codes, we cannot use the source - if (cavCount == 0 && clvCount == 0) { - qDebug() << "Source does not seem to contain valid CAV picture numbers or CLV time-codes - cannot process"; - return false; - } - - // Determine disc type - if (cavCount > clvCount) { - sourceVideos[sourceNumber]->isSourceCav = true; - qDebug() << "Got" << cavCount << "valid CAV picture numbers - source disc type is CAV"; - qInfo() << "Disc type is CAV"; - } else { - sourceVideos[sourceNumber]->isSourceCav = false; - qDebug() << "Got" << clvCount << "valid CLV picture numbers - source disc type is CLV"; - qInfo() << "Disc type is CLV"; - - } - - // Disc has been mapped, so we can use the first and last frame numbers as the - // min and max range of VBI frame numbers in the input source - QVector vbi1 = sourceVideos[sourceNumber]->ldDecodeMetaData.getFieldVbi(sourceVideos[sourceNumber]-> - ldDecodeMetaData.getFirstFieldNumber(1)).vbiData; - QVector vbi2 = sourceVideos[sourceNumber]->ldDecodeMetaData.getFieldVbi(sourceVideos[sourceNumber]-> - ldDecodeMetaData.getSecondFieldNumber(1)).vbiData; - VbiDecoder::Vbi vbi = vbiDecoder.decodeFrame(vbi1[0], vbi1[1], vbi1[2], vbi2[0], vbi2[1], vbi2[2]); - - if (sourceVideos[sourceNumber]->isSourceCav) { - sourceVideos[sourceNumber]->minimumVbiFrameNumber = vbi.picNo; - } else { - LdDecodeMetaData::ClvTimecode timecode; - timecode.hours = vbi.clvHr; - timecode.minutes = vbi.clvMin; - timecode.seconds = vbi.clvSec; - timecode.pictureNumber = vbi.clvPicNo; - sourceVideos[sourceNumber]->minimumVbiFrameNumber = sourceVideos[sourceNumber]->ldDecodeMetaData.convertClvTimecodeToFrameNumber(timecode); - } - - vbi1 = sourceVideos[sourceNumber]->ldDecodeMetaData.getFieldVbi(sourceVideos[sourceNumber]-> - ldDecodeMetaData.getFirstFieldNumber(sourceVideos[sourceNumber]->ldDecodeMetaData.getNumberOfFrames())).vbiData; - vbi2 = sourceVideos[sourceNumber]->ldDecodeMetaData.getFieldVbi(sourceVideos[sourceNumber]-> - ldDecodeMetaData.getSecondFieldNumber(sourceVideos[sourceNumber]->ldDecodeMetaData.getNumberOfFrames())).vbiData; - vbi = vbiDecoder.decodeFrame(vbi1[0], vbi1[1], vbi1[2], vbi2[0], vbi2[1], vbi2[2]); - - if (sourceVideos[sourceNumber]->isSourceCav) { - sourceVideos[sourceNumber]->maximumVbiFrameNumber = vbi.picNo; - } else { - LdDecodeMetaData::ClvTimecode timecode; - timecode.hours = vbi.clvHr; - timecode.minutes = vbi.clvMin; - timecode.seconds = vbi.clvSec; - timecode.pictureNumber = vbi.clvPicNo; - sourceVideos[sourceNumber]->maximumVbiFrameNumber = sourceVideos[sourceNumber]->ldDecodeMetaData.convertClvTimecodeToFrameNumber(timecode); - } - - if (sourceVideos[sourceNumber]->isSourceCav) { - // If the source is CAV frame numbering should be a minimum of 1 (it - // can be 0 for CLV sources) - if (sourceVideos[sourceNumber]->minimumVbiFrameNumber < 1) { - qCritical() << "CAV start frame of" << sourceVideos[sourceNumber]->minimumVbiFrameNumber << "is out of bounds (should be 1 or above)"; - return false; - } - } - - qInfo() << "VBI frame number range is" << sourceVideos[sourceNumber]->minimumVbiFrameNumber << "to" << - sourceVideos[sourceNumber]->maximumVbiFrameNumber; - - return true; -} - -// Method to convert a VBI frame number to a sequential frame number -qint32 TbcSources::convertVbiFrameNumberToSequential(qint32 vbiFrameNumber, qint32 sourceNumber) -{ - // Offset the VBI frame number to get the sequential source frame number - return vbiFrameNumber - sourceVideos[sourceNumber]->minimumVbiFrameNumber + 1; -} - -// Method to find the median of a vector of qint32s -qint32 TbcSources::median(QVector v) -{ - size_t n = v.size() / 2; - std::nth_element(v.begin(), v.begin()+n, v.end()); - return v[n]; -} - -// Method to convert a linear IRE to a logarithmic reflective brightness % -// Note: Follows the Rec. 709 OETF transfer function -float TbcSources::convertLinearToBrightness(quint16 value, quint16 black16bIre, quint16 white16bIre, bool isSourcePal) -{ - float v = 0; - float l = static_cast(value); - - // Factors to scale Y according to the black to white interval - // (i.e. make the black level 0 and the white level 65535) - float yScale = (1.0 / (black16bIre - white16bIre)) * -65535; - - if (!isSourcePal) { - // NTSC uses a 75% white point; so here we scale the result by - // 25% (making 100 IRE 25% over the maximum allowed white point) - yScale *= 125.0 / 100.0; - } - - // Scale the L to 0-65535 where 0 = blackIreLevel and 65535 = whiteIreLevel - l = (l - black16bIre) * yScale; - if (l > 65535) l = 65535; - if (l < 0) l = 0; - - // Scale L to 0.00-1.00 - l = (1.0 / 65535.0) * l; - - // Rec. 709 - https://en.wikipedia.org/wiki/Rec._709#Transfer_characteristics - if (l < 0.018) { - v = 4.500 * l; - } else { - v = pow(1.099 * l, 0.45) - 0.099; - } - - return v; -} - - - - - - - - - - - - - - - - - From 847aceb1274fd1de6ef1b5b15af2ce774eeb71e9 Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Mon, 3 Feb 2020 07:15:25 +0100 Subject: [PATCH 09/14] Restructure ready for multi-threading implementation --- tools/ld-diffdod/main.cpp | 23 ++++++++++++++-- tools/ld-diffdod/sources.cpp | 52 +++++++++++++++++++++++++----------- tools/ld-diffdod/sources.h | 29 +++++++++++++++----- 3 files changed, 79 insertions(+), 25 deletions(-) diff --git a/tools/ld-diffdod/main.cpp b/tools/ld-diffdod/main.cpp index dcf3301ed..b56d7bda6 100644 --- a/tools/ld-diffdod/main.cpp +++ b/tools/ld-diffdod/main.cpp @@ -84,6 +84,13 @@ int main(int argc, char *argv[]) QCoreApplication::translate("main", "number")); parser.addOption(lengthVbiOption); + // Option to select the number of threads (-t) + QCommandLineOption threadsOption(QStringList() << "t" << "threads", + QCoreApplication::translate( + "main", "Specify the number of concurrent threads (default is the number of logical CPUs)"), + QCoreApplication::translate("main", "number")); + parser.addOption(threadsOption); + // Positional argument to specify input TBC files parser.addPositionalArgument("input", QCoreApplication::translate("main", "Specify input TBC files (minimum of 3)")); @@ -149,9 +156,21 @@ int main(int argc, char *argv[]) } } + qint32 maxThreads = QThread::idealThreadCount(); + if (parser.isSet(threadsOption)) { + maxThreads = parser.value(threadsOption).toInt(); + + if (maxThreads < 1) { + // Quit with error + qCritical("Specified number of threads must be greater than zero"); + return -1; + } + } + // Process the TBC file - Sources sources; - if (!sources.process(inputFilenames, reverse, dodThreshold, lumaClip, vbiFrameStart, vbiFrameLength)) { + Sources sources(inputFilenames, reverse, dodThreshold, lumaClip, + vbiFrameStart, vbiFrameLength, maxThreads); + if (!sources.process()) { return 1; } diff --git a/tools/ld-diffdod/sources.cpp b/tools/ld-diffdod/sources.cpp index c436f82a5..09034534d 100644 --- a/tools/ld-diffdod/sources.cpp +++ b/tools/ld-diffdod/sources.cpp @@ -24,28 +24,33 @@ #include "sources.h" -Sources::Sources(QObject *parent) : QObject(parent) +Sources::Sources(QVector inputFilenames, bool reverse, + qint32 dodThreshold, bool lumaClip, + qint32 startVbi, qint32 lengthVbi, + qint32 maxThreads, QObject *parent) + : QObject(parent), m_inputFilenames(inputFilenames), m_reverse(reverse), + m_dodThreshold(dodThreshold), m_lumaClip(lumaClip), m_startVbi(startVbi), + m_lengthVbi(lengthVbi), m_maxThreads(maxThreads) { + // Used to track the sources as they are loaded currentSource = 0; - currentVbiFrameNumber = 1; } -bool Sources::process(QVector inputFilenames, bool reverse, - qint32 dodThreshold, bool lumaClip, - qint32 startVbi, qint32 lengthVbi) +bool Sources::process() { // Show input filenames - qInfo() << "Processing" << inputFilenames.size() << "input TBC files:"; - for (qint32 i = 0; i < inputFilenames.size(); i++) qInfo().nospace() << " Source #" << i << ": " << inputFilenames[i]; + qInfo() << "Processing" << m_inputFilenames.size() << "input TBC files:"; + for (qint32 i = 0; i < m_inputFilenames.size(); i++) qInfo().nospace() << " Source #" << i << ": " << m_inputFilenames[i]; // And then show the rest... - if (reverse) qInfo() << "Using reverse field order"; else qInfo() << "Using normal field order"; - qInfo().nospace() << "Dropout detection threshold is " << dodThreshold << "% difference"; - if (lumaClip) qInfo() << "Performing luma clip detection"; else qInfo() << "Not performing luma clip detection"; + qInfo() << "Using" << m_maxThreads << "threads to process sources"; + if (m_reverse) qInfo() << "Using reverse field order"; else qInfo() << "Using normal field order"; + qInfo().nospace() << "Dropout detection threshold is " << m_dodThreshold << "% difference"; + if (m_lumaClip) qInfo() << "Performing luma clip detection"; else qInfo() << "Not performing luma clip detection"; qInfo() << ""; // Load the input TBC files --------------------------------------------------------------------------------------- - if (!loadInputTbcFiles(inputFilenames, reverse)) { + if (!loadInputTbcFiles(m_inputFilenames, m_reverse)) { qCritical() << "Error: Unable to load input TBC files - cannot continue!"; return false; } @@ -56,30 +61,36 @@ bool Sources::process(QVector inputFilenames, bool reverse, "to" << getMaximumVbiFrameNumber(); // Check start and length - qint32 vbiStartFrame = startVbi; + qint32 vbiStartFrame = m_startVbi; if (vbiStartFrame < getMinimumVbiFrameNumber()) vbiStartFrame = getMinimumVbiFrameNumber(); - qint32 length = lengthVbi; + qint32 length = m_lengthVbi; if (length > (getMaximumVbiFrameNumber() - vbiStartFrame + 1)) length = getMaximumVbiFrameNumber() - vbiStartFrame + 1; if (length == -1) length = getMaximumVbiFrameNumber() - getMinimumVbiFrameNumber() + 1; // Verify frame source availablity qInfo() << ""; - qInfo() << "Verifying VBI frame multi-source availablity:"; + qInfo() << "Verifying VBI frame multi-source availablity..."; verifySources(vbiStartFrame, length); // Process the sources -------------------------------------------------------------------------------------------- qInfo() << ""; + qInfo() << "Beginning multi-threaded diffDOD processing..."; qInfo() << "Processing" << length << "frames starting from VBI frame" << vbiStartFrame; - processSources(vbiStartFrame, length, dodThreshold, lumaClip); + processSources(vbiStartFrame, length, m_dodThreshold, m_lumaClip); // Save the sources ----------------------------------------------------------------------------------------------- qInfo() << ""; - qInfo() << "Saving sources"; + qInfo() << "Saving sources..."; saveSources(); + // Unload the input sources + qInfo() << ""; + qInfo() << "Cleaning up..."; + unloadInputTbcFiles(); + return true; } @@ -96,6 +107,15 @@ bool Sources::loadInputTbcFiles(QVector inputFilenames, bool reverse) return true; } +// Unload the input sources +void Sources::unloadInputTbcFiles() +{ + for (qint32 sourceNo = 0; sourceNo < getNumberOfAvailableSources(); sourceNo++) { + delete sourceVideos[sourceNo]; + sourceVideos.removeFirst(); + } +} + // Load a TBC source video; returns false on failure bool Sources::loadSource(QString filename, bool reverse) { diff --git a/tools/ld-diffdod/sources.h b/tools/ld-diffdod/sources.h index 95706d3e8..ba6c8196e 100644 --- a/tools/ld-diffdod/sources.h +++ b/tools/ld-diffdod/sources.h @@ -25,20 +25,28 @@ #ifndef SOURCES_H #define SOURCES_H -#include #include #include +#include +#include +#include +#include +#include + #include "diffdod.h" class Sources : public QObject { Q_OBJECT + public: - explicit Sources(QObject *parent = nullptr); + explicit Sources(QVector inputFilenames, bool reverse, + qint32 dodThreshold, bool lumaClip, + qint32 startVbi, qint32 lengthVbi, + qint32 maxThreads, QObject *parent = nullptr); - bool process(QVector inputFilenames, bool reverse, - qint32 dodThreshold, bool lumaClip, qint32 startVbi, qint32 lengthVbi); + bool process(); private: // Source definition @@ -51,13 +59,20 @@ class Sources : public QObject bool isSourceCav; }; - // The frame number is common between sources - qint32 currentVbiFrameNumber; - QVector sourceVideos; qint32 currentSource; + // Setup variables + QVector m_inputFilenames; + bool m_reverse; + qint32 m_dodThreshold; + bool m_lumaClip; + qint32 m_startVbi; + qint32 m_lengthVbi; + qint32 m_maxThreads; + bool loadInputTbcFiles(QVector inputFilenames, bool reverse); + void unloadInputTbcFiles(); bool loadSource(QString filename, bool reverse); bool setDiscTypeAndMaxMinFrameVbi(qint32 sourceNumber); qint32 getMinimumVbiFrameNumber(); From af7daed52e9dd8afcc77ebf224c5055d7ce09f86 Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Tue, 4 Feb 2020 19:52:33 +0100 Subject: [PATCH 10/14] Multithreaded version of ld-diffdod --- tools/ld-diffdod/diffdod.cpp | 108 ++++++++++++---------- tools/ld-diffdod/diffdod.h | 25 +++-- tools/ld-diffdod/sources.cpp | 172 ++++++++++++++++++++++------------- tools/ld-diffdod/sources.h | 27 +++++- 4 files changed, 212 insertions(+), 120 deletions(-) diff --git a/tools/ld-diffdod/diffdod.cpp b/tools/ld-diffdod/diffdod.cpp index 75d4c8d7e..d25cc35b5 100644 --- a/tools/ld-diffdod/diffdod.cpp +++ b/tools/ld-diffdod/diffdod.cpp @@ -23,50 +23,65 @@ ************************************************************************/ #include "diffdod.h" +#include "sources.h" -DiffDod::DiffDod(QObject *parent) : QObject(parent) +DiffDod::DiffDod(QAtomicInt& abort, Sources& sources, QObject *parent) + : QThread(parent), m_abort(abort), m_sources(sources) { } -// Perform differential dropout detection to determine (for each source) which frame pixels are valid -// Note: This method processes a single frame -void DiffDod::performFrameDiffDod(QVector& firstFields, QVector& secondFields, - qint32 dodThreshold, bool lumaClip, LdDecodeMetaData::VideoParameters videoParameters, - QVector availableSourcesForFrame) +// Run method for thread +void DiffDod::run() { - // Range check the diffDOD threshold percent - if (dodThreshold < 1) dodThreshold = 1; - if (dodThreshold > 100) dodThreshold = 100; - - // Create a differential map of the fields for the avaialble frames (based on the DOD threshold) - QVector firstFieldsDiff = getFieldErrorByMedian(firstFields, dodThreshold, - videoParameters, availableSourcesForFrame); - QVector secondFieldsDiff = getFieldErrorByMedian(secondFields, dodThreshold, - videoParameters, availableSourcesForFrame); - - // Perform luma clip check? - if (lumaClip) { - performLumaClip(firstFields, firstFieldsDiff, videoParameters, availableSourcesForFrame); - performLumaClip(secondFields, secondFieldsDiff, videoParameters, availableSourcesForFrame); - } + // Set up the input variables + qint32 targetVbiFrame; + QVector firstFields; + QVector secondFields; + LdDecodeMetaData::VideoParameters videoParameters; + QVector availableSourcesForFrame; + qint32 dodThreshold; + bool lumaClip; + + // Set up the output variables + QVector firstFieldDropouts; + QVector secondFieldDropouts; + + // Process frames until there's nothing left to process + while(!m_abort) { + // Get the next frame to process ------------------------------------------------------------------------------ + if (!m_sources.getInputFrame(targetVbiFrame, firstFields, secondFields, videoParameters, + availableSourcesForFrame, dodThreshold, lumaClip)) { + // No more input fields --> exit + break; + } - // Create the drop-out metadata based on the differential map of the fields - m_firstFieldDropouts = getFieldDropouts(firstFieldsDiff, videoParameters, availableSourcesForFrame); - m_secondFieldDropouts = getFieldDropouts(secondFieldsDiff, videoParameters, availableSourcesForFrame); + // Process the frame ------------------------------------------------------------------------------------------ - // Concatenate dropouts on the same line that are close together (to cut down on the - // amount of generated metadata with noisy/bad sources) - concatenateFieldDropouts(m_firstFieldDropouts, availableSourcesForFrame); - concatenateFieldDropouts(m_secondFieldDropouts, availableSourcesForFrame); -} + // Create a differential map of the fields for the avaialble frames (based on the DOD threshold) + QVector firstFieldsDiff = getFieldErrorByMedian(firstFields, dodThreshold, + videoParameters, availableSourcesForFrame); + QVector secondFieldsDiff = getFieldErrorByMedian(secondFields, dodThreshold, + videoParameters, availableSourcesForFrame); -// Method to get the result of the diffDOD process -void DiffDod::getResults(QVector& firstFieldDropouts, - QVector& secondFieldDropouts) -{ - firstFieldDropouts = m_firstFieldDropouts; - secondFieldDropouts = m_secondFieldDropouts; + // Perform luma clip check? + if (lumaClip) { + performLumaClip(firstFields, firstFieldsDiff, videoParameters, availableSourcesForFrame); + performLumaClip(secondFields, secondFieldsDiff, videoParameters, availableSourcesForFrame); + } + + // Create the drop-out metadata based on the differential map of the fields + firstFieldDropouts = getFieldDropouts(firstFieldsDiff, videoParameters, availableSourcesForFrame); + secondFieldDropouts = getFieldDropouts(secondFieldsDiff, videoParameters, availableSourcesForFrame); + + // Concatenate dropouts on the same line that are close together (to cut down on the + // amount of generated metadata with noisy/bad sources) + concatenateFieldDropouts(firstFieldDropouts, availableSourcesForFrame); + concatenateFieldDropouts(secondFieldDropouts, availableSourcesForFrame); + + // Return the processed frame --------------------------------------------------------------------------------- + m_sources.setOutputFrame(targetVbiFrame, firstFieldDropouts, secondFieldDropouts, availableSourcesForFrame); + } } // Private methods ---------------------------------------------------------------------------------------------------- @@ -77,9 +92,18 @@ QVector DiffDod::getFieldErrorByMedian(QVector &f LdDecodeMetaData::VideoParameters videoParameters, QVector availableSourcesForFrame) { + // Make a vector to store the result of the diff + QVector fieldDiff; + fieldDiff.resize(fields.size()); + + // Resize the fieldDiff sub-vectors and default the elements to zero + for (qint32 sourcePointer = 0; sourcePointer < fields.size(); sourcePointer++) { + fieldDiff[sourcePointer].fill(0, videoParameters.fieldHeight * videoParameters.fieldWidth); + } + // This method requires at least three source frames if (availableSourcesForFrame.size() < 3) { - return QVector(); + return fieldDiff; } // Normalize the % dodThreshold to 0.00-1.00 @@ -88,15 +112,6 @@ QVector DiffDod::getFieldErrorByMedian(QVector &f // Calculate the linear threshold for the colourburst region qint32 cbThreshold = ((65535 / 100) * dodThreshold) / 8; // Note: The /8 is just a guess - // Make a vector to store the result of the diff - QVector fieldDiff; - fieldDiff.resize(fields.size()); - - // Resize the fieldDiff sub-vectors and default the elements to zero - for (qint32 sourcePointer = 0; sourcePointer < fields.size(); sourcePointer++) { - fieldDiff[sourcePointer].fill(0, videoParameters.fieldHeight * videoParameters.fieldWidth); - } - for (qint32 y = 0; y < videoParameters.fieldHeight; y++) { qint32 startOfLinePointer = y * videoParameters.fieldWidth; for (qint32 x = videoParameters.colourBurstStart; x < videoParameters.activeVideoEnd; x++) { @@ -207,7 +222,7 @@ QVector DiffDod::getFieldDropouts(QVector(); + return fieldDropouts; } // Define the area in which DOD should be performed @@ -285,6 +300,7 @@ void DiffDod::concatenateFieldDropouts(QVector &drop // Start from 1 as 0 has no previous dropout qint32 i = 1; + while (i < dropouts[sourceNo].startx.size()) { // Is the current dropout on the same field line as the last? if (dropouts[sourceNo].fieldLine[i - 1] == dropouts[sourceNo].fieldLine[i]) { diff --git a/tools/ld-diffdod/diffdod.h b/tools/ld-diffdod/diffdod.h index c54b08c09..b2a5b695a 100644 --- a/tools/ld-diffdod/diffdod.h +++ b/tools/ld-diffdod/diffdod.h @@ -26,9 +26,11 @@ #define DIFFDOD_H #include -#include -#include +#include +#include +#include #include +#include // TBC library includes #include "sourcevideo.h" @@ -36,20 +38,23 @@ #include "vbidecoder.h" #include "filters.h" -class DiffDod : public QObject +class Sources; + +class DiffDod : public QThread { Q_OBJECT public: - explicit DiffDod(QObject *parent = nullptr); + explicit DiffDod(QAtomicInt& abort, Sources& sources, QObject *parent = nullptr); + +protected: + void run() override; - void performFrameDiffDod(QVector &firstFields, QVector &secondFields, qint32 dodOnThreshold, bool lumaClip, - LdDecodeMetaData::VideoParameters videoParameters, QVector availableSourcesForFrame); - void getResults(QVector& firstFieldDropouts, - QVector& secondFieldDropouts); private: - QVector m_firstFieldDropouts; - QVector m_secondFieldDropouts; + // Decoder pool + QAtomicInt& m_abort; + Sources& m_sources; + // Processing methods QVector getFieldErrorByMedian(QVector &fields, qint32 dodThreshold, LdDecodeMetaData::VideoParameters videoParameters, QVector availableSourcesForFrame); void performLumaClip(QVector &fields, QVector &fieldsDiff, LdDecodeMetaData::VideoParameters videoParameters, diff --git a/tools/ld-diffdod/sources.cpp b/tools/ld-diffdod/sources.cpp index 09034534d..777d3ab94 100644 --- a/tools/ld-diffdod/sources.cpp +++ b/tools/ld-diffdod/sources.cpp @@ -76,10 +76,40 @@ bool Sources::process() verifySources(vbiStartFrame, length); // Process the sources -------------------------------------------------------------------------------------------- + inputFrameNumber = vbiStartFrame; + lastFrameNumber = vbiStartFrame + length; + qInfo() << ""; qInfo() << "Beginning multi-threaded diffDOD processing..."; - qInfo() << "Processing" << length << "frames starting from VBI frame" << vbiStartFrame; - processSources(vbiStartFrame, length, m_dodThreshold, m_lumaClip); + qInfo() << "Processing" << length << "frames - from VBI frame" << inputFrameNumber << "to" << lastFrameNumber; + totalTimer.start(); + + // Start a vector of decoding threads to process the video + qInfo() << "Beginning multi-threaded dropout correction process..."; + QVector threads; + threads.resize(m_maxThreads); + for (qint32 i = 0; i < m_maxThreads; i++) { + threads[i] = new DiffDod(abort, *this); + threads[i]->start(QThread::LowPriority); + } + + // Wait for the workers to finish + for (qint32 i = 0; i < m_maxThreads; i++) { + threads[i]->wait(); + delete threads[i]; + } + + // Did any of the threads abort? + if (abort) { + qCritical() << "Threads aborted! Cleaning up..."; + unloadInputTbcFiles(); + return false; + } + + // Show the processing speed to the user + float totalSecs = (static_cast(totalTimer.elapsed()) / 1000.0); + qInfo().nospace() << "DiffDOD complete - " << length << " frames in " << totalSecs << " seconds (" << + length / totalSecs << " FPS)"; // Save the sources ----------------------------------------------------------------------------------------------- qInfo() << ""; @@ -94,6 +124,83 @@ bool Sources::process() return true; } +// Provide a frame to the threaded processing +bool Sources::getInputFrame(qint32& targetVbiFrame, + QVector& firstFields, QVector& secondFields, + LdDecodeMetaData::VideoParameters& videoParameters, + QVector& availableSourcesForFrame, + qint32& dodThreshold, bool& lumaClip) +{ + QMutexLocker locker(&inputMutex); + + if (inputFrameNumber > lastFrameNumber) { + // No more input frames + return false; + } + + targetVbiFrame = inputFrameNumber; + inputFrameNumber++; + + // Get the metadata for the video parameters (all sources are the same, so just grab from the first) + videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); + + // Get the number of available sources for the current frame + availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); + + // Get the field data for the current frame (from all available sources) + firstFields = getFieldData(targetVbiFrame, true, videoParameters, availableSourcesForFrame); + secondFields = getFieldData(targetVbiFrame, false, videoParameters, availableSourcesForFrame); + + // Set the other miscellaneous parameters + dodThreshold = m_dodThreshold; + lumaClip = m_lumaClip; + + return true; +} + +// Receive a frame from the threaded processing +bool Sources::setOutputFrame(qint32 targetVbiFrame, + QVector firstFieldDropouts, + QVector secondFieldDropouts, + QVector availableSourcesForFrame) +{ + QMutexLocker locker(&outputMutex); + + // Write the first and second field line metadata back to the source + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + + // Get the required field numbers + qint32 firstFieldNumber = sourceVideos[sourceNo]-> + ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); + qint32 secondFieldNumber = sourceVideos[sourceNo]-> + ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); + + // Calculate the total number of dropouts detected for the frame + qint32 totalFirstDropouts = 0; + qint32 totalSecondDropouts = 0; + if (firstFieldDropouts.size() > 0) totalFirstDropouts = firstFieldDropouts[sourceNo].startx.size(); + if (secondFieldDropouts.size() > 0) totalSecondDropouts = secondFieldDropouts[sourceNo].startx.size(); + + qDebug() << "Writing source" << sourceNo << + "frame" << targetVbiFrame << "fields" << firstFieldNumber << "/" << secondFieldNumber << + "- Dropout records" << totalFirstDropouts << "/" << totalSecondDropouts; + + // Only replace the existing metadata if it was possible to create new metadata + if (availableSourcesForFrame.size() >= 3) { + // Remove the existing field dropout metadata for the field + sourceVideos[sourceNo]->ldDecodeMetaData.clearFieldDropOuts(firstFieldNumber); + sourceVideos[sourceNo]->ldDecodeMetaData.clearFieldDropOuts(secondFieldNumber); + + // Write the new field dropout metadata + sourceVideos[sourceNo]->ldDecodeMetaData.updateFieldDropOuts(firstFieldDropouts[sourceNo], firstFieldNumber); + sourceVideos[sourceNo]->ldDecodeMetaData.updateFieldDropOuts(secondFieldDropouts[sourceNo], secondFieldNumber); + } + } + + return true; +} + // Load all available input sources bool Sources::loadInputTbcFiles(QVector inputFilenames, bool reverse) { @@ -409,67 +516,6 @@ qint32 Sources::getNumberOfAvailableSources() return sourceVideos.size(); } -// Method to process a source frame -void Sources::processSources(qint32 vbiStartFrame, qint32 length, qint32 dodThreshold, bool lumaClip) -{ - // Get the metadata for the video parameters (all sources are the same, so just grab from the first) - LdDecodeMetaData::VideoParameters videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); - - // Define a processing object - DiffDod diffDod; - - // Process the sources frame by frame - for (qint32 targetVbiFrame = vbiStartFrame; targetVbiFrame < vbiStartFrame + length; targetVbiFrame++) { - if ((targetVbiFrame % 100 == 0) || (targetVbiFrame == vbiStartFrame)) qInfo() << "Processing VBI frame" << targetVbiFrame; - - // Get the number of available sources for the current frame - QVector availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); - - // Get the field data for the current frame (from all available sources) - QVector firstFields = getFieldData(targetVbiFrame, true, videoParameters, availableSourcesForFrame); - QVector secondFields = getFieldData(targetVbiFrame, false, videoParameters, availableSourcesForFrame); - - // Process the field - diffDod.performFrameDiffDod(firstFields, secondFields, dodThreshold, lumaClip, videoParameters, availableSourcesForFrame); - - // Get the result - QVector firstFieldDropouts; - QVector secondFieldDropouts; - diffDod.getResults(firstFieldDropouts, secondFieldDropouts); - - // Write the first and second field line metadata back to the source - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - - // Get the required field numbers - qint32 firstFieldNumber = sourceVideos[sourceNo]-> - ldDecodeMetaData.getFirstFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); - qint32 secondFieldNumber = sourceVideos[sourceNo]-> - ldDecodeMetaData.getSecondFieldNumber(convertVbiFrameNumberToSequential(targetVbiFrame, sourceNo)); - - // Calculate the total number of dropouts detected for the frame - qint32 totalFirstDropouts = firstFieldDropouts[sourceNo].startx.size(); - qint32 totalSecondDropouts = secondFieldDropouts[sourceNo].startx.size(); - - qDebug() << "Writing source" << sourceNo << - "frame" << targetVbiFrame << "fields" << firstFieldNumber << "/" << secondFieldNumber << - "- Dropout records" << totalFirstDropouts << "/" << totalSecondDropouts; - - // Only replace the existing metadata if it was possible to create new metadata - if (availableSourcesForFrame.size() >= 3) { - // Remove the existing field dropout metadata for the field - sourceVideos[sourceNo]->ldDecodeMetaData.clearFieldDropOuts(firstFieldNumber); - sourceVideos[sourceNo]->ldDecodeMetaData.clearFieldDropOuts(secondFieldNumber); - - // Write the new field dropout metadata - sourceVideos[sourceNo]->ldDecodeMetaData.updateFieldDropOuts(firstFieldDropouts[sourceNo], firstFieldNumber); - sourceVideos[sourceNo]->ldDecodeMetaData.updateFieldDropOuts(secondFieldDropouts[sourceNo], secondFieldNumber); - } - } - - } -} - // Method to write the source metadata to disc void Sources::saveSources() { diff --git a/tools/ld-diffdod/sources.h b/tools/ld-diffdod/sources.h index ba6c8196e..3dd83310b 100644 --- a/tools/ld-diffdod/sources.h +++ b/tools/ld-diffdod/sources.h @@ -48,6 +48,18 @@ class Sources : public QObject bool process(); + // Member functions used by worker threads + bool getInputFrame(qint32& targetVbiFrame, + QVector& firstFields, QVector& secondFields, + LdDecodeMetaData::VideoParameters& videoParameters, + QVector& availableSourcesForFrame, + qint32& dodThreshold, bool& lumaClip); + + bool setOutputFrame(qint32 targetVbiFrame, + QVector firstFieldDropouts, + QVector secondFieldDropouts, + QVector availableSourcesForFrame); + private: // Source definition struct Source { @@ -61,6 +73,7 @@ class Sources : public QObject QVector sourceVideos; qint32 currentSource; + QElapsedTimer totalTimer; // Setup variables QVector m_inputFilenames; @@ -71,6 +84,18 @@ class Sources : public QObject qint32 m_lengthVbi; qint32 m_maxThreads; + // Input stream variables (all guarded by inputMutex while threads are running) + QMutex inputMutex; + qint32 inputFrameNumber; + qint32 lastFrameNumber; + + // Output stream variables (all guarded by outputMutex while threads are running) + QMutex outputMutex; + + // Atomic abort flag shared by worker threads; workers watch this, and shut + // down as soon as possible if it becomes true + QAtomicInt abort; + bool loadInputTbcFiles(QVector inputFilenames, bool reverse); void unloadInputTbcFiles(); bool loadSource(QString filename, bool reverse); @@ -81,7 +106,7 @@ class Sources : public QObject QVector getAvailableSourcesForFrame(qint32 vbiFrameNumber); qint32 convertVbiFrameNumberToSequential(qint32 vbiFrameNumber, qint32 sourceNumber); qint32 getNumberOfAvailableSources(); - void processSources(qint32 vbiStartFrame, qint32 length, qint32 dodThreshold, bool lumaClip); + //void processSources(qint32 vbiStartFrame, qint32 length, qint32 dodThreshold, bool lumaClip); void saveSources(); QVector getFieldData(qint32 targetVbiFrame, bool isFirstField, LdDecodeMetaData::VideoParameters videoParameters, QVector &availableSourcesForFrame); From 173340a60ab6369c3fd2ad56cd21d0e6a2308d54 Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Wed, 5 Feb 2020 20:23:28 +0100 Subject: [PATCH 11/14] Moved luma filter to threaded process for speed --- tools/ld-diffdod/diffdod.cpp | 33 ++++++++++++++++++++++++++++----- tools/ld-diffdod/diffdod.h | 9 +++++++-- tools/ld-diffdod/sources.cpp | 14 +++----------- tools/ld-diffdod/sources.h | 2 +- 4 files changed, 39 insertions(+), 19 deletions(-) diff --git a/tools/ld-diffdod/diffdod.cpp b/tools/ld-diffdod/diffdod.cpp index d25cc35b5..20729545f 100644 --- a/tools/ld-diffdod/diffdod.cpp +++ b/tools/ld-diffdod/diffdod.cpp @@ -58,6 +58,10 @@ void DiffDod::run() // Process the frame ------------------------------------------------------------------------------------------ + // Filter the frame to leave just the luma information + performLumaFilter(firstFields, videoParameters, availableSourcesForFrame); + performLumaFilter(secondFields, videoParameters, availableSourcesForFrame); + // Create a differential map of the fields for the avaialble frames (based on the DOD threshold) QVector firstFieldsDiff = getFieldErrorByMedian(firstFields, dodThreshold, videoParameters, availableSourcesForFrame); @@ -66,8 +70,8 @@ void DiffDod::run() // Perform luma clip check? if (lumaClip) { - performLumaClip(firstFields, firstFieldsDiff, videoParameters, availableSourcesForFrame); - performLumaClip(secondFields, secondFieldsDiff, videoParameters, availableSourcesForFrame); + performLumaClip(firstFields, firstFieldsDiff, videoParameters, availableSourcesForFrame, 5); + performLumaClip(secondFields, secondFieldsDiff, videoParameters, availableSourcesForFrame, 5); } // Create the drop-out metadata based on the differential map of the fields @@ -86,6 +90,23 @@ void DiffDod::run() // Private methods ---------------------------------------------------------------------------------------------------- +void DiffDod::performLumaFilter(QVector &fields, + LdDecodeMetaData::VideoParameters videoParameters, + QVector availableSourcesForFrame) +{ + // Filter out the chroma information from the fields leaving just luma + Filters filters; + + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + if (videoParameters.isSourcePal) { + filters.palLumaFirFilter(fields[sourceNo].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); + } else { + filters.ntscLumaFirFilter(fields[sourceNo].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); + } + } +} + // Create an error map of the fields based on median value differential analysis // Note: This only functions within the colour burst and visible areas of the frame QVector DiffDod::getFieldErrorByMedian(QVector &fields, qint32 dodThreshold, @@ -152,7 +173,8 @@ QVector DiffDod::getFieldErrorByMedian(QVector &f // Perform a luma clip check on the field void DiffDod::performLumaClip(QVector &fields, QVector &fieldsDiff, LdDecodeMetaData::VideoParameters videoParameters, - QVector availableSourcesForFrame) + QVector availableSourcesForFrame, + qint32 lumaClipThreshold) { // Process the fields one line at a time for (qint32 y = videoParameters.firstActiveFieldLine; y < videoParameters.lastActiveFieldLine; y++) { @@ -162,8 +184,9 @@ void DiffDod::performLumaClip(QVector &fields, QVector getFieldErrorByMedian(QVector &fields, qint32 dodThreshold, LdDecodeMetaData::VideoParameters videoParameters, QVector availableSourcesForFrame); + void performLumaFilter(QVector &fields, + LdDecodeMetaData::VideoParameters videoParameters, + QVector availableSourcesForFrame); + QVector getFieldErrorByMedian(QVector &fields, qint32 dodThreshold, + LdDecodeMetaData::VideoParameters videoParameters, + QVector availableSourcesForFrame); void performLumaClip(QVector &fields, QVector &fieldsDiff, LdDecodeMetaData::VideoParameters videoParameters, - QVector availableSourcesForFrame); + QVector availableSourcesForFrame, qint32 lumaClipThreshold); QVector getFieldDropouts(QVector &fieldsDiff, LdDecodeMetaData::VideoParameters videoParameters, QVector availableSourcesForFrame); diff --git a/tools/ld-diffdod/sources.cpp b/tools/ld-diffdod/sources.cpp index 777d3ab94..e3c686c0e 100644 --- a/tools/ld-diffdod/sources.cpp +++ b/tools/ld-diffdod/sources.cpp @@ -148,8 +148,8 @@ bool Sources::getInputFrame(qint32& targetVbiFrame, availableSourcesForFrame = getAvailableSourcesForFrame(targetVbiFrame); // Get the field data for the current frame (from all available sources) - firstFields = getFieldData(targetVbiFrame, true, videoParameters, availableSourcesForFrame); - secondFields = getFieldData(targetVbiFrame, false, videoParameters, availableSourcesForFrame); + firstFields = getFieldData(targetVbiFrame, true, availableSourcesForFrame); + secondFields = getFieldData(targetVbiFrame, false, availableSourcesForFrame); // Set the other miscellaneous parameters dodThreshold = m_dodThreshold; @@ -528,7 +528,7 @@ void Sources::saveSources() } // Get the field data for the specified frame -QVector Sources::getFieldData(qint32 targetVbiFrame, bool isFirstField, LdDecodeMetaData::VideoParameters videoParameters, +QVector Sources::getFieldData(qint32 targetVbiFrame, bool isFirstField, QVector &availableSourcesForFrame) { // Only display on first field (otherwise we will get 2 of the same debug) @@ -551,14 +551,6 @@ QVector Sources::getFieldData(qint32 targetVbiFrame, bool isF // Copy the data locally fields[sourceNo] = (sourceVideos[sourceNo]->sourceVideo.getVideoField(fieldNumber)); - - // Filter out the chroma information from the fields leaving just luma - Filters filters; - if (videoParameters.isSourcePal) { - filters.palLumaFirFilter(fields[sourceNo].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); - } else { - filters.ntscLumaFirFilter(fields[sourceNo].data(), videoParameters.fieldWidth * videoParameters.fieldHeight); - } } return fields; diff --git a/tools/ld-diffdod/sources.h b/tools/ld-diffdod/sources.h index 3dd83310b..279459ae2 100644 --- a/tools/ld-diffdod/sources.h +++ b/tools/ld-diffdod/sources.h @@ -108,7 +108,7 @@ class Sources : public QObject qint32 getNumberOfAvailableSources(); //void processSources(qint32 vbiStartFrame, qint32 length, qint32 dodThreshold, bool lumaClip); void saveSources(); - QVector getFieldData(qint32 targetVbiFrame, bool isFirstField, LdDecodeMetaData::VideoParameters videoParameters, + QVector getFieldData(qint32 targetVbiFrame, bool isFirstField, QVector &availableSourcesForFrame); }; From 4f28e72a59e90ed2fb8873c4bf6ff0fb3309e080 Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Wed, 5 Feb 2020 21:36:52 +0100 Subject: [PATCH 12/14] Small bug fixes --- tools/ld-diffdod/diffdod.cpp | 2 +- tools/ld-diffdod/sources.cpp | 7 ++++++- tools/ld-diffdod/sources.h | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/ld-diffdod/diffdod.cpp b/tools/ld-diffdod/diffdod.cpp index 20729545f..1f693eb57 100644 --- a/tools/ld-diffdod/diffdod.cpp +++ b/tools/ld-diffdod/diffdod.cpp @@ -361,7 +361,7 @@ float DiffDod::convertLinearToBrightness(quint16 value, quint16 black16bIre, qui // Factors to scale Y according to the black to white interval // (i.e. make the black level 0 and the white level 65535) - float yScale = (1.0 / (black16bIre - white16bIre)) * -65535; + float yScale = (1.0 / (white16bIre - black16bIre)) * 65535; if (!isSourcePal) { // NTSC uses a 75% white point; so here we scale the result by diff --git a/tools/ld-diffdod/sources.cpp b/tools/ld-diffdod/sources.cpp index e3c686c0e..e14c6a10c 100644 --- a/tools/ld-diffdod/sources.cpp +++ b/tools/ld-diffdod/sources.cpp @@ -78,6 +78,7 @@ bool Sources::process() // Process the sources -------------------------------------------------------------------------------------------- inputFrameNumber = vbiStartFrame; lastFrameNumber = vbiStartFrame + length; + processedFrames = 0; qInfo() << ""; qInfo() << "Beginning multi-threaded diffDOD processing..."; @@ -140,6 +141,7 @@ bool Sources::getInputFrame(qint32& targetVbiFrame, targetVbiFrame = inputFrameNumber; inputFrameNumber++; + processedFrames++; // Get the metadata for the video parameters (all sources are the same, so just grab from the first) videoParameters = sourceVideos[0]->ldDecodeMetaData.getVideoParameters(); @@ -155,6 +157,9 @@ bool Sources::getInputFrame(qint32& targetVbiFrame, dodThreshold = m_dodThreshold; lumaClip = m_lumaClip; + // User feedback + if (processedFrames % 100 == 0) qInfo() << "Processing frame" << targetVbiFrame; + return true; } @@ -470,7 +475,7 @@ void Sources::verifySources(qint32 vbiStartFrame, qint32 length) // and output a warning to the user if (availableSourcesForFrame.size() < 3) { // Differential DOD requires at least 3 valid source frames - qInfo().nospace() << "Frame #" << vbiFrame << " has only " << availableSourcesForFrame.size() << " source frames available - cannot correct"; + qDebug().nospace() << "Frame #" << vbiFrame << " has only " << availableSourcesForFrame.size() << " source frames available - cannot correct"; uncorrectableFrameCount++; } } diff --git a/tools/ld-diffdod/sources.h b/tools/ld-diffdod/sources.h index 279459ae2..dff0247b9 100644 --- a/tools/ld-diffdod/sources.h +++ b/tools/ld-diffdod/sources.h @@ -74,6 +74,7 @@ class Sources : public QObject QVector sourceVideos; qint32 currentSource; QElapsedTimer totalTimer; + qint32 processedFrames; // Setup variables QVector m_inputFilenames; From 41f99ba85e9c2a3860d2e8c3630813e9e3f58411 Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Thu, 6 Feb 2020 08:31:59 +0100 Subject: [PATCH 13/14] Added signal clip detection (as replacement for luma clip detection) --- tools/ld-diffdod/diffdod.cpp | 190 +++++++++++++++++------------------ tools/ld-diffdod/diffdod.h | 10 +- tools/ld-diffdod/main.cpp | 13 +-- tools/ld-diffdod/sources.cpp | 10 +- tools/ld-diffdod/sources.h | 4 +- 5 files changed, 113 insertions(+), 114 deletions(-) diff --git a/tools/ld-diffdod/diffdod.cpp b/tools/ld-diffdod/diffdod.cpp index 1f693eb57..e895143d9 100644 --- a/tools/ld-diffdod/diffdod.cpp +++ b/tools/ld-diffdod/diffdod.cpp @@ -41,7 +41,7 @@ void DiffDod::run() LdDecodeMetaData::VideoParameters videoParameters; QVector availableSourcesForFrame; qint32 dodThreshold; - bool lumaClip; + bool signalClip; // Set up the output variables QVector firstFieldDropouts; @@ -51,32 +51,45 @@ void DiffDod::run() while(!m_abort) { // Get the next frame to process ------------------------------------------------------------------------------ if (!m_sources.getInputFrame(targetVbiFrame, firstFields, secondFields, videoParameters, - availableSourcesForFrame, dodThreshold, lumaClip)) { + availableSourcesForFrame, dodThreshold, signalClip)) { // No more input fields --> exit break; } // Process the frame ------------------------------------------------------------------------------------------ + // Create the field difference maps + // Make a vector to store the result of the diff + QVector firstFieldDiff; + QVector secondFieldDiff; + firstFieldDiff.resize(firstFields.size()); + secondFieldDiff.resize(secondFields.size()); + + // Resize the fieldDiff sub-vectors and default the elements to zero + for (qint32 sourcePointer = 0; sourcePointer < firstFieldDiff.size(); sourcePointer++) { + firstFieldDiff[sourcePointer].fill(0, videoParameters.fieldHeight * videoParameters.fieldWidth); + } + for (qint32 sourcePointer = 0; sourcePointer < secondFieldDiff.size(); sourcePointer++) { + secondFieldDiff[sourcePointer].fill(0, videoParameters.fieldHeight * videoParameters.fieldWidth); + } + + // Perform the clip check + if (signalClip) { + performClipCheck(firstFields, firstFieldDiff, videoParameters, availableSourcesForFrame); + performClipCheck(secondFields, secondFieldDiff, videoParameters, availableSourcesForFrame); + } + // Filter the frame to leave just the luma information performLumaFilter(firstFields, videoParameters, availableSourcesForFrame); performLumaFilter(secondFields, videoParameters, availableSourcesForFrame); // Create a differential map of the fields for the avaialble frames (based on the DOD threshold) - QVector firstFieldsDiff = getFieldErrorByMedian(firstFields, dodThreshold, - videoParameters, availableSourcesForFrame); - QVector secondFieldsDiff = getFieldErrorByMedian(secondFields, dodThreshold, - videoParameters, availableSourcesForFrame); - - // Perform luma clip check? - if (lumaClip) { - performLumaClip(firstFields, firstFieldsDiff, videoParameters, availableSourcesForFrame, 5); - performLumaClip(secondFields, secondFieldsDiff, videoParameters, availableSourcesForFrame, 5); - } + getFieldErrorByMedian(firstFields, firstFieldDiff, dodThreshold, videoParameters, availableSourcesForFrame); + getFieldErrorByMedian(secondFields, secondFieldDiff, dodThreshold, videoParameters, availableSourcesForFrame); // Create the drop-out metadata based on the differential map of the fields - firstFieldDropouts = getFieldDropouts(firstFieldsDiff, videoParameters, availableSourcesForFrame); - secondFieldDropouts = getFieldDropouts(secondFieldsDiff, videoParameters, availableSourcesForFrame); + firstFieldDropouts = getFieldDropouts(firstFieldDiff, videoParameters, availableSourcesForFrame); + secondFieldDropouts = getFieldDropouts(secondFieldDiff, videoParameters, availableSourcesForFrame); // Concatenate dropouts on the same line that are close together (to cut down on the // amount of generated metadata with noisy/bad sources) @@ -90,6 +103,63 @@ void DiffDod::run() // Private methods ---------------------------------------------------------------------------------------------------- +// Create an error maps of the field based on absolute clipping of the input field values (i.e. +// where the signal clips on 0 or 65535 before any filtering) +void DiffDod::performClipCheck(QVector &fields, QVector &fieldDiff, + LdDecodeMetaData::VideoParameters videoParameters, + QVector availableSourcesForFrame) +{ + // Process the fields one line at a time + for (qint32 y = videoParameters.firstActiveFieldLine; y < videoParameters.lastActiveFieldLine; y++) { + qint32 startOfLinePointer = y * videoParameters.fieldWidth; + + for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { + qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source + + for (qint32 x = videoParameters.colourBurstStart; x < videoParameters.activeVideoEnd; x++) { + // Get the IRE value for the source field, cast to 32 bit signed + qint32 sourceIre = static_cast(fields[sourceNo][x + startOfLinePointer]); + + // Check for a luma clip event + if ((sourceIre == 0) || (sourceIre == 65535)) { + // Signal has clipped, scan back and forth looking for the start + // and end points of the event (i.e. the point where the event + // goes back into the expected range) + qint32 range = 10; // maximum + and - scan range + qint32 minX = x - range; + if (minX < videoParameters.activeVideoStart) minX = videoParameters.activeVideoStart; + qint32 maxX = x + range; + if (maxX > videoParameters.activeVideoEnd) maxX = videoParameters.activeVideoEnd; + + qint32 startX = x; + qint32 endX = x; + + for (qint32 i = x; i > minX; i--) { + qint32 ire = static_cast(fields[sourceNo][x + startOfLinePointer]); + if (ire > 200 || ire < 65335) { + startX = i; + } + } + + for (qint32 i = x + 1; i < maxX; i++) { + qint32 ire = static_cast(fields[sourceNo][x + startOfLinePointer]); + if (ire > 200 || ire < 65335) { + endX = i; + } + } + + // Mark the dropout + for (qint32 i = startX; i < endX; i++) { + fieldDiff[sourceNo][i + startOfLinePointer] = 1; + } + + x = x + range; + } + } + } + } +} + void DiffDod::performLumaFilter(QVector &fields, LdDecodeMetaData::VideoParameters videoParameters, QVector availableSourcesForFrame) @@ -109,22 +179,14 @@ void DiffDod::performLumaFilter(QVector &fields, // Create an error map of the fields based on median value differential analysis // Note: This only functions within the colour burst and visible areas of the frame -QVector DiffDod::getFieldErrorByMedian(QVector &fields, qint32 dodThreshold, - LdDecodeMetaData::VideoParameters videoParameters, - QVector availableSourcesForFrame) +void DiffDod::getFieldErrorByMedian(QVector &fields, QVector &fieldDiff, + qint32 dodThreshold, + LdDecodeMetaData::VideoParameters videoParameters, + QVector availableSourcesForFrame) { - // Make a vector to store the result of the diff - QVector fieldDiff; - fieldDiff.resize(fields.size()); - - // Resize the fieldDiff sub-vectors and default the elements to zero - for (qint32 sourcePointer = 0; sourcePointer < fields.size(); sourcePointer++) { - fieldDiff[sourcePointer].fill(0, videoParameters.fieldHeight * videoParameters.fieldWidth); - } - // This method requires at least three source frames if (availableSourcesForFrame.size() < 3) { - return fieldDiff; + return; } // Normalize the % dodThreshold to 0.00-1.00 @@ -151,7 +213,7 @@ QVector DiffDod::getFieldErrorByMedian(QVector &f for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source float v = convertLinearToBrightness(dotValues[sourceNo], videoParameters.black16bIre, videoParameters.white16bIre, videoParameters.isSourcePal); - if ((v - vMedian) > threshold) fieldDiff[sourceNo][x + startOfLinePointer] = 1; + if ((v - vMedian) > threshold) fieldDiff[sourceNo][x + startOfLinePointer] = 2; } } @@ -161,71 +223,7 @@ QVector DiffDod::getFieldErrorByMedian(QVector &f qint32 dotMedian = median(dotValues); for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - if ((dotValues[sourceNo] - dotMedian) > cbThreshold) fieldDiff[sourceNo][x + startOfLinePointer] = 1; - } - } - } - } - - return fieldDiff; -} - -// Perform a luma clip check on the field -void DiffDod::performLumaClip(QVector &fields, QVector &fieldsDiff, - LdDecodeMetaData::VideoParameters videoParameters, - QVector availableSourcesForFrame, - qint32 lumaClipThreshold) -{ - // Process the fields one line at a time - for (qint32 y = videoParameters.firstActiveFieldLine; y < videoParameters.lastActiveFieldLine; y++) { - qint32 startOfLinePointer = y * videoParameters.fieldWidth; - - for (qint32 sourcePointer = 0; sourcePointer < availableSourcesForFrame.size(); sourcePointer++) { - qint32 sourceNo = availableSourcesForFrame[sourcePointer]; // Get the actual source - - // Set the clipping levels - qint32 threshold = (65535 / 100) * lumaClipThreshold; - qint32 blackClipLevel = videoParameters.black16bIre - threshold; - qint32 whiteClipLevel = videoParameters.white16bIre + threshold; - - for (qint32 x = videoParameters.activeVideoStart; x < videoParameters.activeVideoEnd; x++) { - // Get the IRE value for the source field, cast to 32 bit signed - qint32 sourceIre = static_cast(fields[sourceNo][x + startOfLinePointer]); - - // Check for a luma clip event - if ((sourceIre < blackClipLevel) || (sourceIre > whiteClipLevel)) { - // Luma has clipped, scan back and forth looking for the start - // and end points of the event (i.e. the point where the event - // goes back into the expected IRE range) - qint32 range = 10; // maximum + and - scan range - qint32 minX = x - range; - if (minX < videoParameters.activeVideoStart) minX = videoParameters.activeVideoStart; - qint32 maxX = x + range; - if (maxX > videoParameters.activeVideoEnd) maxX = videoParameters.activeVideoEnd; - - qint32 startX = x; - qint32 endX = x; - - for (qint32 i = x; i > minX; i--) { - qint32 ire = static_cast(fields[sourceNo][x + startOfLinePointer]); - if (ire < videoParameters.black16bIre || ire > videoParameters.white16bIre) { - startX = i; - } - } - - for (qint32 i = x+1; i < maxX; i++) { - qint32 ire = static_cast(fields[sourceNo][x + startOfLinePointer]); - if (ire < videoParameters.black16bIre || ire > videoParameters.white16bIre) { - endX = i; - } - } - - // Mark the dropout - for (qint32 i = startX; i < endX; i++) { - fieldsDiff[sourceNo][i + startOfLinePointer] = 1; - } - - x = x + range; + if ((dotValues[sourceNo] - dotMedian) > cbThreshold) fieldDiff[sourceNo][x + startOfLinePointer] = 2; } } } @@ -235,13 +233,13 @@ void DiffDod::performLumaClip(QVector &fields, QVector DiffDod::getFieldDropouts(QVector &fieldsDiff, +QVector DiffDod::getFieldDropouts(QVector &fieldDiff, LdDecodeMetaData::VideoParameters videoParameters, QVector availableSourcesForFrame) { // Create and resize the return data vector QVector fieldDropouts; - fieldDropouts.resize(fieldsDiff.size()); + fieldDropouts.resize(fieldDiff.size()); // This method requires at least three source frames if (availableSourcesForFrame.size() < 3) { @@ -271,7 +269,7 @@ QVector DiffDod::getFieldDropouts(QVector(fieldsDiff[sourceNo][x + startOfLinePointer]) == 0) { + if (static_cast(fieldDiff[sourceNo][x + startOfLinePointer]) == 0) { // Current X is not a dropout if (doCounter > 0) { doCounter--; diff --git a/tools/ld-diffdod/diffdod.h b/tools/ld-diffdod/diffdod.h index 74eb975ee..9985acfbf 100644 --- a/tools/ld-diffdod/diffdod.h +++ b/tools/ld-diffdod/diffdod.h @@ -55,16 +55,16 @@ class DiffDod : public QThread Sources& m_sources; // Processing methods + void performClipCheck(QVector &fields, QVector &fieldDiff, + LdDecodeMetaData::VideoParameters videoParameters, + QVector availableSourcesForFrame); void performLumaFilter(QVector &fields, LdDecodeMetaData::VideoParameters videoParameters, QVector availableSourcesForFrame); - QVector getFieldErrorByMedian(QVector &fields, qint32 dodThreshold, + void getFieldErrorByMedian(QVector &fields, QVector &fieldDiff, qint32 dodThreshold, LdDecodeMetaData::VideoParameters videoParameters, QVector availableSourcesForFrame); - void performLumaClip(QVector &fields, QVector &fieldsDiff, - LdDecodeMetaData::VideoParameters videoParameters, - QVector availableSourcesForFrame, qint32 lumaClipThreshold); - QVector getFieldDropouts(QVector &fieldsDiff, + QVector getFieldDropouts(QVector &fieldDiff, LdDecodeMetaData::VideoParameters videoParameters, QVector availableSourcesForFrame); diff --git a/tools/ld-diffdod/main.cpp b/tools/ld-diffdod/main.cpp index b56d7bda6..536d4586f 100644 --- a/tools/ld-diffdod/main.cpp +++ b/tools/ld-diffdod/main.cpp @@ -61,10 +61,10 @@ int main(int argc, char *argv[]) QCoreApplication::translate("main", "Reverse the field order to second/first (default first/second)")); parser.addOption(setReverseOption); - // Option to turn off luma clip detection (-n / --lumaclip) - QCommandLineOption setLumaOption(QStringList() << "n" << "lumaclip", - QCoreApplication::translate("main", "Perform luma clip signal dropout detection")); - parser.addOption(setLumaOption); + // Option to turn off signal clip detection (-n / --noclip) + QCommandLineOption setNoClipOption(QStringList() << "n" << "noclip", + QCoreApplication::translate("main", "Do not perform signal clip dropout detection")); + parser.addOption(setNoClipOption); // Option to select DOD threshold (-x / --dod-threshold) QCommandLineOption dodThresholdOption(QStringList() << "x" << "dod-threshold", @@ -102,7 +102,8 @@ int main(int argc, char *argv[]) // Get the options from the parser bool reverse = parser.isSet(setReverseOption); - bool lumaClip = parser.isSet(setLumaOption); + bool signalClip = true; + if (parser.isSet(setNoClipOption)) signalClip = false; QVector inputFilenames; QStringList positionalArguments = parser.positionalArguments(); @@ -168,7 +169,7 @@ int main(int argc, char *argv[]) } // Process the TBC file - Sources sources(inputFilenames, reverse, dodThreshold, lumaClip, + Sources sources(inputFilenames, reverse, dodThreshold, signalClip, vbiFrameStart, vbiFrameLength, maxThreads); if (!sources.process()) { return 1; diff --git a/tools/ld-diffdod/sources.cpp b/tools/ld-diffdod/sources.cpp index e14c6a10c..e27cfa929 100644 --- a/tools/ld-diffdod/sources.cpp +++ b/tools/ld-diffdod/sources.cpp @@ -25,11 +25,11 @@ #include "sources.h" Sources::Sources(QVector inputFilenames, bool reverse, - qint32 dodThreshold, bool lumaClip, + qint32 dodThreshold, bool signalClip, qint32 startVbi, qint32 lengthVbi, qint32 maxThreads, QObject *parent) : QObject(parent), m_inputFilenames(inputFilenames), m_reverse(reverse), - m_dodThreshold(dodThreshold), m_lumaClip(lumaClip), m_startVbi(startVbi), + m_dodThreshold(dodThreshold), m_signalClip(signalClip), m_startVbi(startVbi), m_lengthVbi(lengthVbi), m_maxThreads(maxThreads) { // Used to track the sources as they are loaded @@ -46,7 +46,7 @@ bool Sources::process() qInfo() << "Using" << m_maxThreads << "threads to process sources"; if (m_reverse) qInfo() << "Using reverse field order"; else qInfo() << "Using normal field order"; qInfo().nospace() << "Dropout detection threshold is " << m_dodThreshold << "% difference"; - if (m_lumaClip) qInfo() << "Performing luma clip detection"; else qInfo() << "Not performing luma clip detection"; + if (m_signalClip) qInfo() << "Performing signal clip detection"; else qInfo() << "Not performing signal clip detection"; qInfo() << ""; // Load the input TBC files --------------------------------------------------------------------------------------- @@ -130,7 +130,7 @@ bool Sources::getInputFrame(qint32& targetVbiFrame, QVector& firstFields, QVector& secondFields, LdDecodeMetaData::VideoParameters& videoParameters, QVector& availableSourcesForFrame, - qint32& dodThreshold, bool& lumaClip) + qint32& dodThreshold, bool& signalClip) { QMutexLocker locker(&inputMutex); @@ -155,7 +155,7 @@ bool Sources::getInputFrame(qint32& targetVbiFrame, // Set the other miscellaneous parameters dodThreshold = m_dodThreshold; - lumaClip = m_lumaClip; + signalClip = m_signalClip; // User feedback if (processedFrames % 100 == 0) qInfo() << "Processing frame" << targetVbiFrame; diff --git a/tools/ld-diffdod/sources.h b/tools/ld-diffdod/sources.h index dff0247b9..f576fa96a 100644 --- a/tools/ld-diffdod/sources.h +++ b/tools/ld-diffdod/sources.h @@ -53,7 +53,7 @@ class Sources : public QObject QVector& firstFields, QVector& secondFields, LdDecodeMetaData::VideoParameters& videoParameters, QVector& availableSourcesForFrame, - qint32& dodThreshold, bool& lumaClip); + qint32& dodThreshold, bool& signalClip); bool setOutputFrame(qint32 targetVbiFrame, QVector firstFieldDropouts, @@ -80,7 +80,7 @@ class Sources : public QObject QVector m_inputFilenames; bool m_reverse; qint32 m_dodThreshold; - bool m_lumaClip; + bool m_signalClip; qint32 m_startVbi; qint32 m_lengthVbi; qint32 m_maxThreads; From d762223b509adcd5dc225b84b6dbeeee6dcebb9e Mon Sep 17 00:00:00 2001 From: Simon Inns Date: Thu, 6 Feb 2020 14:34:11 +0100 Subject: [PATCH 14/14] Changed luma filter settings --- tools/ld-diffdod/diffdod.cpp | 2 +- tools/ld-diffdod/main.cpp | 4 ++-- tools/library/tbc/filters.cpp | 20 ++++++++------------ 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/tools/ld-diffdod/diffdod.cpp b/tools/ld-diffdod/diffdod.cpp index e895143d9..09dcbf78c 100644 --- a/tools/ld-diffdod/diffdod.cpp +++ b/tools/ld-diffdod/diffdod.cpp @@ -193,7 +193,7 @@ void DiffDod::getFieldErrorByMedian(QVector &fields, QVector< float threshold = static_cast(dodThreshold) / 100.0; // Calculate the linear threshold for the colourburst region - qint32 cbThreshold = ((65535 / 100) * dodThreshold) / 8; // Note: The /8 is just a guess + qint32 cbThreshold = ((65535 / 100) * dodThreshold) / 4; // Note: The /4 is just a guess for (qint32 y = 0; y < videoParameters.fieldHeight; y++) { qint32 startOfLinePointer = y * videoParameters.fieldWidth; diff --git a/tools/ld-diffdod/main.cpp b/tools/ld-diffdod/main.cpp index 536d4586f..737ee4156 100644 --- a/tools/ld-diffdod/main.cpp +++ b/tools/ld-diffdod/main.cpp @@ -68,7 +68,7 @@ int main(int argc, char *argv[]) // Option to select DOD threshold (-x / --dod-threshold) QCommandLineOption dodThresholdOption(QStringList() << "x" << "dod-threshold", - QCoreApplication::translate("main", "Specify the DOD threshold percent (1 to 100% default: 5"), + QCoreApplication::translate("main", "Specify the DOD threshold percent (1 to 100% default: 7%"), QCoreApplication::translate("main", "number")); parser.addOption(dodThresholdOption); @@ -124,7 +124,7 @@ int main(int argc, char *argv[]) return -1; } - qint32 dodThreshold = 5; + qint32 dodThreshold = 7; if (parser.isSet(dodThresholdOption)) { dodThreshold = parser.value(dodThresholdOption).toInt(); diff --git a/tools/library/tbc/filters.cpp b/tools/library/tbc/filters.cpp index 741bfb847..9f6dad312 100644 --- a/tools/library/tbc/filters.cpp +++ b/tools/library/tbc/filters.cpp @@ -29,25 +29,21 @@ #include // PAL - Filter at Fsc/2 (Fsc = 4433618 (/2 = 2,216,809), sample rate = 17,734,472) -// 2.2 MHz LPF - 15 Taps +// 2.2 MHz LPF - 5 Taps // import scipy.signal -// scipy.signal.firwin(15, [2.2e6/17734472], window='hamming') -static constexpr std::array palLumaFilterCoeffs { - 0.00188029, 0.00616523, 0.01927009, 0.04479076, 0.08068761, - 0.11896474, 0.14846274, 0.15955709, 0.14846274, 0.11896474, - 0.08068761, 0.04479076, 0.01927009, 0.00616523, 0.00188029 +// scipy.signal.firwin(5, [2.2e6/17734472], window='hamming') +static constexpr std::array palLumaFilterCoeffs { + 0.03283437, 0.23959832, 0.45513461, 0.23959832, 0.03283437 }; static constexpr auto palLumaFilter = makeFIRFilter(palLumaFilterCoeffs); // NTSC - Filter at Fsc/2 (Fsc = 3579545 (/2 = 1,789,772.5), sample rate = 14,318,180) -// 1.8 MHz LPF - 15 Taps +// 1.8 MHz LPF - 5 Taps // import scipy.signal -// scipy.signal.firwin(15, [1.8e6/14318180], window='hamming') -static constexpr std::array ntscLumaFilterCoeffs { - 0.00170818, 0.00592632, 0.01890583, 0.04442077, 0.0805412 , - 0.11921885, 0.14910167, 0.16035437, 0.14910167, 0.11921885, - 0.0805412 , 0.04442077, 0.01890583, 0.00592632, 0.00170818 +// scipy.signal.firwin(5, [1.8e6/14318180], window='hamming') +static constexpr std::array ntscLumaFilterCoeffs { + 0.03275786, 0.23955702, 0.45537024, 0.23955702, 0.03275786 }; static constexpr auto ntscLumaFilter = makeFIRFilter(ntscLumaFilterCoeffs);