예제 #1
0
 public void RegisterSuccessfulTimestamp(int frameNo, IotaVtiTimeStamp oddFieldOSD, IotaVtiTimeStamp evenFieldOSD, DateTime oddFieldTimestamp, DateTime evenFieldTimestamp)
 {
     m_PrevFrameNo = frameNo;
     m_PrevOddTicks = oddFieldTimestamp.Ticks;
     m_PrevEvenTicks = evenFieldTimestamp.Ticks;
     m_PrevOddFieldNo = oddFieldOSD.FrameNumber;
     m_PrevEvenFieldNo = evenFieldOSD.FrameNumber;
     m_PrevOddFieldOSD = new IotaVtiTimeStamp(oddFieldOSD);
     m_PrevEvenFieldOSD = new IotaVtiTimeStamp(evenFieldOSD);
 }
예제 #2
0
        public void TestIotaVtiTimeStamp_FrameNo(string frameNo, int expectedFrameNo)
        {
            var str = new IotaVtiTimeStampStrings()
            {
                NumSat  = ' ',
                HH      = "",
                MM      = "",
                SS      = "",
                FFFF1   = "",
                FFFF2   = "",
                FRAMENO = frameNo
            };

            var ts = new IotaVtiTimeStamp(str);

            Assert.AreEqual(expectedFrameNo, ts.FrameNumber);
        }
예제 #3
0
        public void TestIotaVtiTimeStamp_Seconds(string ss, int expectedSeconds)
        {
            var str = new IotaVtiTimeStampStrings()
            {
                NumSat  = ' ',
                HH      = "",
                MM      = "",
                SS      = ss,
                FFFF1   = "",
                FFFF2   = "",
                FRAMENO = ""
            };

            var ts = new IotaVtiTimeStamp(str);

            Assert.AreEqual(expectedSeconds, ts.Seconds);
        }
예제 #4
0
        public void TestIotaVtiTimeStamp_Minutes(string mm, int expectedMinutes)
        {
            var str = new IotaVtiTimeStampStrings()
            {
                NumSat  = ' ',
                HH      = "",
                MM      = mm,
                SS      = "",
                FFFF1   = "",
                FFFF2   = "",
                FRAMENO = ""
            };

            var ts = new IotaVtiTimeStamp(str);

            Assert.AreEqual(expectedMinutes, ts.Minutes);
        }
예제 #5
0
        public void TestIotaVtiTimeStamp_Hours(string hh, int expectedHours)
        {
            var str = new IotaVtiTimeStampStrings()
            {
                NumSat  = ' ',
                HH      = hh,
                MM      = "",
                SS      = "",
                FFFF1   = "",
                FFFF2   = "",
                FRAMENO = ""
            };

            var ts = new IotaVtiTimeStamp(str);

            Assert.AreEqual(expectedHours, ts.Hours);
        }
예제 #6
0
        public void TestIotaVtiTimeStamp_FFF2(string fff2, int expectedFFF)
        {
            var str = new IotaVtiTimeStampStrings()
            {
                NumSat  = ' ',
                HH      = "",
                MM      = "",
                SS      = "",
                FFFF1   = "",
                FFFF2   = fff2,
                FRAMENO = ""
            };

            var ts = new IotaVtiTimeStamp(str);

            Assert.AreEqual(expectedFFF, ts.Milliseconds10);
        }
예제 #7
0
        private bool RecognizedTimestampsConsistent(IotaVtiOcrProcessor stateManager, List<CalibratedBlockPosition> normalizedPositions)
        {
            var allTimeStamps = new List<IotaVtiTimeStamp>();
            int index = 0;
            int totalTimestamps = normalizedPositions.Count;

            for (; ; )
            {
                if (index == totalTimestamps - 1)
                    break;

                IotaVtiTimeStampStrings timeStampStrings = IotaVtiOcrCalibratedState.OcrField(normalizedPositions[index].Image, stateManager, normalizedPositions[index].IsOddField);
                if (!timeStampStrings.AllCharsPresent())
                {
                    return false;
                }

                var timeStamp = new IotaVtiTimeStamp(timeStampStrings);

                if (stateManager.SwapFieldsOrder)
                {
                    if (index + 1 == totalTimestamps - 1)
                        break;

                    IotaVtiTimeStampStrings timeStampStrings2 = IotaVtiOcrCalibratedState.OcrField(normalizedPositions[index + 1].Image, stateManager, normalizedPositions[index + 1].IsOddField);
                    if (!timeStampStrings2.AllCharsPresent())
                    {
                        return false;
                    }
                    var timeStamp2 = new IotaVtiTimeStamp(timeStampStrings2);
                    allTimeStamps.Add(timeStamp2);

                    index++;
                }

                allTimeStamps.Add(timeStamp);

                index++;
            }

            float fieldDurationMS = 0;

            for (int i = 0; i < allTimeStamps.Count - 1; i++)
            {
                if (allTimeStamps[i].FrameNumber != allTimeStamps[i + 1].FrameNumber - 1 &&
                    allTimeStamps[i].FrameNumber != allTimeStamps[i + 1].FrameNumber + 1)
                    return false;

                int totalMillisecondsThis = (allTimeStamps[i].Hours * 3600 + allTimeStamps[i].Minutes * 60 + allTimeStamps[i].Seconds) * 10000 + allTimeStamps[i].Milliseconds10;
                int totalMillisecondsNext = (allTimeStamps[i + 1].Hours * 3600 + allTimeStamps[i + 1].Minutes * 60 + allTimeStamps[i + 1].Seconds) * 10000 + allTimeStamps[i + 1].Milliseconds10;

                fieldDurationMS = Math.Abs((totalMillisecondsNext - totalMillisecondsThis) / 10f);

                if (Math.Abs(fieldDurationMS - IotaVtiOcrProcessor.FIELD_DURATION_PAL) > 0.15 && Math.Abs(fieldDurationMS - IotaVtiOcrProcessor.FIELD_DURATION_NTSC) > 0.15)
                    return false;
            }

            if (Math.Abs(fieldDurationMS - IotaVtiOcrProcessor.FIELD_DURATION_PAL) < 0.15)
                stateManager.VideoFormat = VideoFormat.PAL;
            else if (Math.Abs(fieldDurationMS - IotaVtiOcrProcessor.FIELD_DURATION_NTSC) < 0.15)
                stateManager.VideoFormat = VideoFormat.NTSC;
            else
                stateManager.VideoFormat = null;

            return true;
        }
예제 #8
0
        internal DateTime ExtractDateTime(int frameNo, int frameStep, IotaVtiTimeStamp oddFieldOSD, IotaVtiTimeStamp evenFieldOSD)
        {
            bool failedValidation = false;
            string failedReason = null;

            if (oddFieldOSD == null || evenFieldOSD == null)
                return DateTime.MinValue;

            if (frameStep != m_FrameStep)
            {
                m_FrameStep = frameStep;
                m_Corrector.Reset(m_Processor.VideoFormat);
            }

            if (oddFieldOSD.FrameNumber != evenFieldOSD.FrameNumber - 1 &&
                oddFieldOSD.FrameNumber != evenFieldOSD.FrameNumber + 1)
            {
                // Video fields are not consequtive
                failedValidation = true;
                failedReason = "Video fields are not consequtive";
            }

            try
            {
                DateTime oddFieldTimestamp = new DateTime(1, 1, 1, oddFieldOSD.Hours, oddFieldOSD.Minutes, oddFieldOSD.Seconds).AddMilliseconds(Math.Min(10000, oddFieldOSD.Milliseconds10) / 10.0f);
                DateTime evenFieldTimestamp = new DateTime(1, 1, 1, evenFieldOSD.Hours, evenFieldOSD.Minutes, evenFieldOSD.Seconds).AddMilliseconds(Math.Min(10000, evenFieldOSD.Milliseconds10) / 10.0f);

                double fieldDuration = Math.Abs(new TimeSpan(oddFieldTimestamp.Ticks - evenFieldTimestamp.Ticks).TotalMilliseconds);

                if (m_Processor.VideoFormat.Value == VideoFormat.PAL &&
                    (Math.Abs(fieldDuration - IotaVtiOcrProcessor.FIELD_DURATION_PAL) > 1.0))
                {
                    // PAL field is not 20 ms
                    failedValidation = true;
                    failedReason = string.Format("PAL field is not 20 ms. It is {0} ms", fieldDuration);
                }

                if (m_Processor.VideoFormat.Value == VideoFormat.NTSC &&
                    (Math.Abs(fieldDuration - IotaVtiOcrProcessor.FIELD_DURATION_NTSC) > 1.0))
                {
                    // NTSC field is not 16.68 ms
                    failedValidation = true;
                    failedReason = string.Format("NTSC field is not 16.68 ms. It is {0} ms", fieldDuration);
                }

                int oddEvenFieldDirection = oddFieldTimestamp.Ticks - evenFieldTimestamp.Ticks < 0 ? -1 : 1;
                if (m_Corrector.OddEvenFieldDirectionIsKnown() &&
                    oddEvenFieldDirection != m_Corrector.GetOddEvenFieldDirection())
                {
                    // Field timestamps are wrong have changed order (did they swap)?
                    failedValidation = true;
                    failedReason = "Field timestamps are wrong have changed order (did they swap)?";
                }

                if (failedValidation)
                {
                    string correctionInfo;
                    failedValidation = !m_Corrector.TryToCorrect(frameNo, frameStep, null, oddFieldOSD, evenFieldOSD, m_Processor.EvenBeforeOdd, ref oddFieldTimestamp, ref evenFieldTimestamp, out correctionInfo);
                    failedReason += ". " + correctionInfo;
                }

                if (failedValidation)
                {
                    string errorText = string.Format("OCR ERR: FrameNo: {0}, Odd Timestamp: {1}:{2}:{3}.{4} {5}, Even Timestamp: {6}:{7}:{8}.{9} {10}, {11}",
                        frameNo, oddFieldOSD.Hours, oddFieldOSD.Minutes, oddFieldOSD.Seconds, oddFieldOSD.Milliseconds10, oddFieldOSD.FrameNumber,
                        evenFieldOSD.Hours, evenFieldOSD.Minutes, evenFieldOSD.Seconds, evenFieldOSD.Milliseconds10, evenFieldOSD.FrameNumber, failedReason);

                    Trace.WriteLine(errorText);

                    if (m_CalibrationErrors.Count < 16)
                    {
                        var copy = new List<uint>();
                        copy.AddRange(m_Processor.CurrentImage);
                        m_CalibrationImages.Add(string.Format("ocrerr_{0}.bmp", frameNo), copy.ToArray());
                        m_CalibrationErrors.Add(errorText);
                    }

                    if (m_VideoController != null)
                        m_VideoController.RegisterOcrError();
                }
                else
                    m_Corrector.RegisterSuccessfulTimestamp(frameNo, oddFieldOSD, evenFieldOSD, oddFieldTimestamp, evenFieldTimestamp);

                if (oddFieldOSD.FrameNumber == evenFieldOSD.FrameNumber - 1)
                {
                    return failedValidation ? DateTime.MinValue : oddFieldTimestamp;
                }
                else
                {
                    return failedValidation ? DateTime.MinValue : evenFieldTimestamp;
                }
            }
            catch(Exception ex)
            {
                Trace.WriteLine(ex.GetFullStackTrace());

                return DateTime.MinValue;
            }
        }
예제 #9
0
        internal DateTime ExtractAAVDateTime(int frameNo, int frameStep, IotaVtiTimeStamp oddFieldOSD, IotaVtiTimeStamp evenFieldOSD)
        {
            bool failedValidation = false;
            string failedReason = null;

            if (oddFieldOSD == null || evenFieldOSD == null)
                return DateTime.MinValue;

            int integratedAavFields = 2 * m_InitializationData.IntegratedAAVFrames;

            if (oddFieldOSD.FrameNumber != evenFieldOSD.FrameNumber - integratedAavFields + 1 &&
                oddFieldOSD.FrameNumber != evenFieldOSD.FrameNumber + integratedAavFields - 1)
            {
                // Video fields are not consequtive
                failedValidation = true;
                failedReason = "Integration interval is incomplete";
            }

            try
            {
                //   | e               e |        |   o              o|
                //   |.e.o|.e.o|.e.o|.e.o|        |.e.o|.e.o|.e.o|.e.o|

                DateTime oddFieldTimestamp = new DateTime(1, 1, 1, oddFieldOSD.Hours, oddFieldOSD.Minutes, oddFieldOSD.Seconds).AddMilliseconds(Math.Min(10000, oddFieldOSD.Milliseconds10) / 10.0f);
                DateTime evenFieldTimestamp = new DateTime(1, 1, 1, evenFieldOSD.Hours, evenFieldOSD.Minutes, evenFieldOSD.Seconds).AddMilliseconds(Math.Min(10000, evenFieldOSD.Milliseconds10) / 10.0f);

                double integrationPeriodDuration = Math.Abs(new TimeSpan(oddFieldTimestamp.Ticks - evenFieldTimestamp.Ticks).TotalMilliseconds);

                if (m_Processor.VideoFormat != null)
                {
                    if (m_Processor.VideoFormat.Value == VideoFormat.PAL)
                    {
                        var calcDuration = (integrationPeriodDuration + IotaVtiOcrProcessor.FIELD_DURATION_PAL) / integratedAavFields;
                        if ((Math.Abs(calcDuration - IotaVtiOcrProcessor.FIELD_DURATION_PAL) > 1.0))
                        {
                            // PAL field is not 20 ms
                            failedValidation = true;
                            failedReason = string.Format("PAL field is not 20 ms. It is {0} ms", calcDuration);
                        }
                    }

                    if (m_Processor.VideoFormat.Value == VideoFormat.NTSC)
                    {
                        var calcDuration = (integrationPeriodDuration + IotaVtiOcrProcessor.FIELD_DURATION_NTSC) / integratedAavFields;
                        if (Math.Abs(calcDuration - IotaVtiOcrProcessor.FIELD_DURATION_NTSC) > 1.0)
                        {
                            // NTSC field is not 16.68 ms
                            failedValidation = true;
                            failedReason = string.Format("NTSC field is not 16.68 ms. It is {0} ms", calcDuration);
                        }
                    }
                }

                if (failedValidation)
                {
                    string correctionInfo;
                    failedValidation = !m_Corrector.TryToCorrect(frameNo, frameStep, integratedAavFields, oddFieldOSD, evenFieldOSD, m_Processor.EvenBeforeOdd, ref oddFieldTimestamp, ref evenFieldTimestamp, out correctionInfo);
                    failedReason += ". " + correctionInfo;
                }

                if (failedValidation)
                {
                    string errorText = string.Format("OCR ERR: FrameNo: {0}, Odd Timestamp: {1}:{2}:{3}.{4} {5}, Even Timestamp: {6}:{7}:{8}.{9} {10}, {11}",
                        frameNo, oddFieldOSD.Hours, oddFieldOSD.Minutes, oddFieldOSD.Seconds, oddFieldOSD.Milliseconds10, oddFieldOSD.FrameNumber,
                        evenFieldOSD.Hours, evenFieldOSD.Minutes, evenFieldOSD.Seconds, evenFieldOSD.Milliseconds10, evenFieldOSD.FrameNumber, failedReason);

                    Trace.WriteLine(errorText);

                    if (m_CalibrationErrors.Count < 16)
                    {
                        var copy = new List<uint>();
                        copy.AddRange(m_Processor.CurrentImage);
                        m_CalibrationImages.Add(string.Format("ocrerr_{0}.bmp", frameNo), copy.ToArray());
                        m_CalibrationErrors.Add(errorText);
                    }

                    if (m_VideoController != null)
                        m_VideoController.RegisterOcrError();
                }
                else
                    m_Corrector.RegisterSuccessfulTimestamp(frameNo, oddFieldOSD, evenFieldOSD, oddFieldTimestamp, evenFieldTimestamp);

                if (oddFieldOSD.FrameNumber == evenFieldOSD.FrameNumber - integratedAavFields + 1)
                {
                    return failedValidation ? DateTime.MinValue : oddFieldTimestamp;
                }
                else
                {
                    return failedValidation ? DateTime.MinValue : evenFieldTimestamp;
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.GetFullStackTrace());

                return DateTime.MinValue;
            }
        }
예제 #10
0
        public bool TryToCorrect(int frameNo, int frameStep, int? aavIntegratedFields, IotaVtiTimeStamp oddFieldOSD, IotaVtiTimeStamp evenFieldOSD, bool evenBeforeOdd, ref DateTime oddFieldTimestamp, ref DateTime evenFieldTimestamp, out string correctionDebugInfo)
        {
            if (m_PrevFrameNo == -1 || m_PrevOddTicks == -1 || m_PrevEvenTicks == -1)
            {
                correctionDebugInfo = "Cannot correct. m_PrevFrameNo == -1 || m_PrevOddTicks == -1 || m_PrevEvenTicks == -1";
                return false;
            }

            correctionDebugInfo = string.Format("IOTA-VTI Correction Attempt for Frame {0}. {1:D2}:{2:D2}:{3:D2}.{4:D4} ({5}) - {6:D2}:{7:D2}:{8:D2}.{9:D4} ({10}). FrameStep: {11}",
                    frameNo,
                    oddFieldOSD.Hours, oddFieldOSD.Minutes, oddFieldOSD.Seconds, oddFieldOSD.Milliseconds10, oddFieldOSD.FrameNumber,
                    evenFieldOSD.Hours, evenFieldOSD.Minutes, evenFieldOSD.Seconds, evenFieldOSD.Milliseconds10, evenFieldOSD.FrameNumber,
                    frameStep);

            float knownFrameDuration = m_VideoFormat == VideoFormat.PAL
                        ? 2 * IotaVtiOcrProcessor.FIELD_DURATION_PAL
                        : 2 * IotaVtiOcrProcessor.FIELD_DURATION_NTSC;

            double fieldDuration;

            if (!evenBeforeOdd)
            {
                DateTime oddTimestampToCheck = oddFieldTimestamp;

                fieldDuration = Math.Abs(new TimeSpan(oddFieldTimestamp.Ticks - m_PrevEvenTicks).TotalMilliseconds) - (frameStep - 1) * knownFrameDuration;

                if (!IsFieldDurationOkay(fieldDuration))
                {
                    if (!TryCorrectTimestamp(m_PrevEvenTicks, oddFieldTimestamp, oddFieldOSD, frameStep, null, aavIntegratedFields != null))
                    {
                        Trace.WriteLine(correctionDebugInfo);
                        Trace.WriteLine("IOTA-VTI Correction Failed: Cannot correct field duration PrevEven -> CurrOdd.");
                        return false;
                    }
                    else
                        oddTimestampToCheck = new DateTime(1, 1, 1, oddFieldOSD.Hours, oddFieldOSD.Minutes, oddFieldOSD.Seconds).AddMilliseconds(Math.Min(10000, oddFieldOSD.Milliseconds10) / 10.0f);
                }

                fieldDuration = Math.Abs(new TimeSpan(oddTimestampToCheck.Ticks - evenFieldTimestamp.Ticks).TotalMilliseconds);
                if (aavIntegratedFields != null) fieldDuration = fieldDuration / (aavIntegratedFields.Value - 1);

                if (!IsFieldDurationOkay(fieldDuration))
                {
                    if (!TryCorrectTimestamp(oddFieldTimestamp.Ticks, evenFieldTimestamp, evenFieldOSD, 1, aavIntegratedFields, aavIntegratedFields != null))
                    {
                        Trace.WriteLine(correctionDebugInfo);
                        Trace.WriteLine("IOTA-VTI Correction Failed: Cannot correct field duration CurrOdd -> CurrEven.");
                        return false;
                    }
                }
            }
            else
            {
                DateTime evenTimestampToCheck = evenFieldTimestamp;

                fieldDuration = Math.Abs(new TimeSpan(evenFieldTimestamp.Ticks - m_PrevOddTicks).TotalMilliseconds) - (frameStep - 1) * knownFrameDuration;

                if (!IsFieldDurationOkay(fieldDuration))
                {
                    if (!TryCorrectTimestamp(m_PrevOddTicks, evenFieldTimestamp, evenFieldOSD, frameStep, null, aavIntegratedFields != null))
                    {
                        Trace.WriteLine(correctionDebugInfo);
                        Trace.WriteLine("IOTA-VTI Correction Failed: Cannot correct field duration PrevOdd -> CurrEven.");
                        return false;
                    }
                    else
                        evenTimestampToCheck = new DateTime(1, 1, 1, evenFieldOSD.Hours, evenFieldOSD.Minutes, evenFieldOSD.Seconds).AddMilliseconds(Math.Min(10000, evenFieldOSD.Milliseconds10) / 10.0f);
                }

                fieldDuration = Math.Abs(new TimeSpan(evenTimestampToCheck.Ticks - oddFieldTimestamp.Ticks).TotalMilliseconds);
                if (aavIntegratedFields != null) fieldDuration = fieldDuration / (aavIntegratedFields.Value - 1);

                if (!IsFieldDurationOkay(fieldDuration))
                {
                    if (!TryCorrectTimestamp(evenFieldTimestamp.Ticks, oddFieldTimestamp, oddFieldOSD, 1, aavIntegratedFields, aavIntegratedFields != null))
                    {
                        Trace.WriteLine(correctionDebugInfo);
                        Trace.WriteLine("IOTA-VTI Correction Failed: Cannot correct field duration CurrEven -> CurrOdd.");
                        return false;
                    }
                }
            }

            if (!evenBeforeOdd)
            {
                if (m_PrevEvenFieldNo + 1 != oddFieldOSD.FrameNumber)
                {
                    // We don't correct Field numbers as they are not used anywhere for now, we just ignore them as no errors
                }

                if (oddFieldOSD.FrameNumber + 1 != evenFieldOSD.FrameNumber)
                {
                    // We don't correct Field numbers as they are not used anywhere for now, we just ignore them as no errors
                }
            }
            else
            {
                if (m_PrevEvenFieldNo + 1 != oddFieldOSD.FrameNumber)
                {
                    // We don't correct Field numbers as they are not used anywhere for now, we just ignore them as no errors
                }

                if (oddFieldOSD.FrameNumber + 1 != evenFieldOSD.FrameNumber)
                {
                    // We don't correct Field numbers as they are not used anywhere for now, we just ignore them as no errors
                }
            }

            oddFieldTimestamp = new DateTime(1, 1, 1, oddFieldOSD.Hours, oddFieldOSD.Minutes, oddFieldOSD.Seconds).AddMilliseconds(Math.Min(10000, oddFieldOSD.Milliseconds10) / 10.0f);
            evenFieldTimestamp = new DateTime(1, 1, 1, evenFieldOSD.Hours, evenFieldOSD.Minutes, evenFieldOSD.Seconds).AddMilliseconds(Math.Min(10000, evenFieldOSD.Milliseconds10) / 10.0f);

            return true;
        }
예제 #11
0
        private bool TryCorrectTimestamp(long prevFieldTimestamp, DateTime fieldToCorrectTimestamp, IotaVtiTimeStamp fieldToCorrect, int frameStep, int? aavIntegratedFields, bool isAav)
        {
            double stepCorrection = frameStep > 1 ? ((20000 * (frameStep - 1) * (m_VideoFormat == VideoFormat.PAL ? IotaVtiOcrProcessor.FIELD_DURATION_PAL : IotaVtiOcrProcessor.FIELD_DURATION_NTSC))) : 0;
            double fieldDistance = (10000 * (m_VideoFormat == VideoFormat.PAL ? IotaVtiOcrProcessor.FIELD_DURATION_PAL : IotaVtiOcrProcessor.FIELD_DURATION_NTSC));
            double aavCorrection = aavIntegratedFields.HasValue ? (10000 * (aavIntegratedFields.Value - 2) * (m_VideoFormat == VideoFormat.PAL ? IotaVtiOcrProcessor.FIELD_DURATION_PAL : IotaVtiOcrProcessor.FIELD_DURATION_NTSC)) : 0;
            long expectedTimestamp =
                prevFieldTimestamp + (long)Math.Round(stepCorrection + fieldDistance + aavCorrection);

            // NOTE: We ignore the last digit from the milliseconds when comparing this timestamps. While this allows for incorectly read 10th of milliseconds to be passed
            //       unactioned, it doesn't create any timing or measurement issues
            DateTime expectedDateTime = new DateTime(expectedTimestamp);
            string expectedTimestampString = expectedDateTime.ToString("HH:mm:ss.fff");
            string expectedTimestampStringM1 = expectedDateTime.AddMilliseconds(-1).ToString("HH:mm:ss.fff");
            string expectedTimestampStringP1 = expectedDateTime.AddMilliseconds(1).ToString("HH:mm:ss.fff");
            string actualTimestampString = fieldToCorrectTimestamp.ToString("HH:mm:ss.fff");

            string difference = XorStrings(expectedTimestampString, actualTimestampString);
            long numberDifferences = difference.ToCharArray().Count(c => c != '\0');
            string differenceM1 = XorStrings(expectedTimestampStringM1, actualTimestampString);
            long numberDifferencesM1 = differenceM1.ToCharArray().Count(c => c != '\0');
            string differenceP1 = XorStrings(expectedTimestampStringP1, actualTimestampString);
            long numberDifferencesP1 = differenceP1.ToCharArray().Count(c => c != '\0');

            if (numberDifferences < numberDifferencesM1 && numberDifferences < numberDifferencesP1)
            {
                // Already correct
            }
            else if (numberDifferencesM1 < numberDifferences && numberDifferencesM1 < numberDifferencesP1)
            {
                numberDifferences = numberDifferencesM1;
                expectedDateTime = expectedDateTime.AddMilliseconds(-1);
            }
            else if (numberDifferencesP1 < numberDifferences && numberDifferencesP1 < numberDifferencesM1)
            {
                numberDifferences = numberDifferencesP1;
                expectedDateTime = expectedDateTime.AddMilliseconds(1);
            }

            if (numberDifferences <= TangraConfig.Settings.Generic.OcrMaxNumberErrorsToAutoCorrect ||
                (isAav && fieldToCorrect.Milliseconds10 == 0) /* Duplicated video field in AAV */)
            {
                // We can correct the one or two offending characters

                fieldToCorrect.Hours = expectedDateTime.Hour;
                fieldToCorrect.Minutes = expectedDateTime.Minute;
                fieldToCorrect.Seconds = expectedDateTime.Second;
                fieldToCorrect.Milliseconds10 = (int)Math.Round((expectedDateTime.Ticks % 10000000) / 1000.0);

                return true;
            }
            else
            {
                // Cannot correct more than one differences. Why not??
                return false;
            }
        }