Advertisement
JeeDjii

Delay calculation for WinMM

Jul 6th, 2016
63
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 15.32 KB | None | 0 0
  1.         private long GetPublishBufferDelay()
  2.         {
  3.             ulong readSamples = delayMicrophone.ReadSamples;
  4.  
  5.             int nBytesPerSample = (int)Math.Round(((double)waveIn.WaveFormat.BitsPerSample / 8.0));
  6.             long nBytesPerMs = (long)Math.Round(((double)nBytesPerSample * (double)waveIn.WaveFormat.SampleRate) / 1000.0);
  7.             int nSamplePerMs = (int)Math.Round(waveIn.WaveFormat.SampleRate / 1000.0);
  8.  
  9.             ulong recSamples = (ulong)((double)waveIn.GetPosition() / (double)nBytesPerSample);
  10.             int delayNAudio = (int)waveIn.BufferedDuration.TotalMilliseconds;
  11.  
  12.             long recDifference = (long)(delayMicrophone.OldRecSamples - recSamples);
  13.  
  14.             if (recDifference > 64000)
  15.             {
  16.                 logger(null, FLV_LogLevel.DEBUG, String.Format("WRAP 1 (recDifference ={0})", recDifference));
  17.                 // If the sound cards number-of-recorded-samples variable wraps around before
  18.                 // read_sampels wraps around this needs to be adjusted. This can happen on
  19.                 // sound cards that uses less than 32 bits to keep track of number of played out
  20.                 // sampels. To avoid being fooled by sound cards that sometimes produces false
  21.                 // output we compare old value minus the new value with a large value. This is
  22.                 // neccessary because some SC:s produce an output like 153, 198, 175, 230 which
  23.                 // would trigger the wrap-around function if we didn't compare with a large value.
  24.                 // The value 64000 is chosen because 2^16=65536 so we allow wrap around at 16 bits.
  25.                 //
  26.                 int i = 31;
  27.                 while ((delayMicrophone.OldRecSamples <= (ulong)Math.Pow(2, i)) && (i > 14))
  28.                     i--;
  29.  
  30.                 if ((i < 31) && (i > 14))
  31.                 {
  32.                     // Avoid adjusting when there is 32-bit wrap-around since that is
  33.                     // somethying neccessary.
  34.                     //
  35.                     delayMicrophone.ReadSamples = delayMicrophone.ReadSamples - (ulong)Math.Pow(2, i + 1);
  36.                     readSamples = delayMicrophone.ReadSamples;
  37.                     delayMicrophone.WrapCounter++;
  38.                 }
  39.                 else
  40.                 {
  41.                     logger(null, FLV_LogLevel.DEBUG, String.Format("AEC (_rec_samples_old {0} recSamples {1})", delayMicrophone.OldRecSamples, recSamples));
  42.                 }
  43.             }
  44.  
  45.             if ((delayMicrophone.WrapCounter > 200))
  46.             {
  47.                 // Do nothing, handled later
  48.             }
  49.             else if ((delayMicrophone.OldRecSamples > (ulong)Math.Pow(2, 31)) && (recSamples < 96000))
  50.             {
  51.                 logger(null, FLV_LogLevel.DEBUG, String.Format("WRAP 2 (_rec_samples_old {0} recSamples {1})", delayMicrophone.OldRecSamples, recSamples));
  52.                 // Wrap around as expected after having used all 32 bits.
  53.                 delayMicrophone.OldReadSamples = readSamples;
  54.                 delayMicrophone.OldRecSamples = recSamples;
  55.                 delayMicrophone.WrapCounter++;
  56.  
  57.                 int delay = (int) ((double) (recSamples + (ulong) Math.Pow(2, 32) - readSamples) / (double) nSamplePerMs);
  58.                 logger(null, FLV_LogLevel.DEBUG, String.Format("[AEC]GetPublishBufferDelay(0) delayInbuffer({0}) BuiltIn({1}) NAudio({2}) wrapCounter({3})", delay + delayNAudio, delay, delayNAudio, delayMicrophone.WrapCounter));
  59.                 return delay;
  60.  
  61.  
  62.             }
  63.             else if ((recSamples < 96000) && (readSamples > Math.Pow(2, 31)))
  64.             {
  65.                 logger(null, FLV_LogLevel.DEBUG, String.Format("WRAP 3 (readSamples {0} recSamples {1})", readSamples, recSamples));
  66.                 // Wrap around has, as expected, happened for rec_sampels before
  67.                 // readSampels so we have to adjust for this until also readSampels
  68.                 // has had wrap around.
  69.                 delayMicrophone.OldReadSamples = readSamples;
  70.                 delayMicrophone.OldRecSamples = recSamples;
  71.                 delayMicrophone.WrapCounter++;
  72.  
  73.                 int delay = (int)((double)(recSamples + Math.Pow(2, 32) - readSamples) / (double)nSamplePerMs);
  74.                 logger(null, FLV_LogLevel.DEBUG, String.Format("[AEC]GetPublishBufferDelay(1) delayInbuffer({0}) BuiltIn({1}) NAudio({2}) wrapCounter({3})", delay + delayNAudio, delay, delayNAudio, delayMicrophone.WrapCounter));
  75.                 return delay;
  76.             }
  77.  
  78.  
  79.             delayMicrophone.OldReadSamples = delayMicrophone.ReadSamples;
  80.             delayMicrophone.OldRecSamples = recSamples;
  81.             int res = (int)Math.Round((double)(delayMicrophone.OldRecSamples - delayMicrophone.OldReadSamples) / (double)nSamplePerMs);
  82.  
  83.             if ((res > 2000) || (res < 0) || (delayMicrophone.WrapCounter > 200))
  84.             {
  85.                 // Reset everything
  86.                 logger(null, FLV_LogLevel.DEBUG, String.Format("msec_read error (res {0} wrapCounter {1})", res, delayMicrophone.WrapCounter));
  87.                 ulong getSamp = (ulong)((double)waveIn.GetPosition() / (double)nBytesPerSample);
  88.                 //ulong getSamp = (ulong) ((waveIn.BufferMilliseconds - 10) * nSamplePerMs);
  89.                 delayMicrophone.ReadSamples = getSamp;
  90.                 delayMicrophone.OldReadSamples = delayMicrophone.ReadSamples;
  91.                 delayMicrophone.OldRecSamples = getSamp;
  92.  
  93.                 // Guess a decent value
  94.                 res = 20;
  95.             }
  96.  
  97.             delayMicrophone.WrapCounter = 0;
  98.             logger(null, FLV_LogLevel.DEBUG, String.Format("[AEC]GetPublishBufferDelay(2) delayInbuffer({0}) BuiltIn({1}) NAudio({2}) wrapCounter({3})", res + delayNAudio, res, delayNAudio, delayMicrophone.WrapCounter));
  99.             return res;
  100.         }
  101.  
  102.         private long GetPlayBufferDelay(long bufferLength)
  103.         {
  104.             if (waveOut == null || waveStream == null) return 0;
  105.  
  106.             ulong writtenSamples = delaySpeaker.WrittenSamples;
  107.  
  108.             int nBytesPerSample = (int)Math.Round(((double)waveOut.OutputWaveFormat.BitsPerSample / 8.0));
  109.             long nBytesPerMs = (long)Math.Round(((double)nBytesPerSample * (double)waveOut.OutputWaveFormat.SampleRate) / 1000.0);
  110.             int nSamplePerMs = (int)Math.Round(waveOut.OutputWaveFormat.SampleRate / 1000.0);
  111.  
  112.             int addedLatency = waveOut.DesiredLatency;
  113.  
  114.             if (delaySpeaker.IsFirstFrame)
  115.             {
  116.                 delaySpeaker.SampleFirstPos = (ulong)((double)waveOut.GetPosition() / (double)nBytesPerSample);
  117.                 delaySpeaker.IsFirstFrame = false;
  118.             }
  119.  
  120.             ulong playedSamples = (ulong)((double)waveOut.GetPosition() / (double)nBytesPerSample);
  121.             playedSamples -= delaySpeaker.SampleFirstPos; // remove the offset
  122.  
  123.             if (delaySpeaker.WrittenSamples < playedSamples)
  124.             // we got a problem here, we can't have played more sample than we writted.
  125.             {
  126.                 delaySpeaker.WrittenSamples = playedSamples;
  127.                 delaySpeaker.OldWrittenSamples = playedSamples;
  128.                 writtenSamples = playedSamples;
  129.                 logger(null, FLV_LogLevel.DEBUG,
  130.                     String.Format("[AEC]============================================= Resync"));
  131.             }
  132.            
  133.  
  134.             long delayMsInBuffer = (long)(((double)((long)delaySpeaker.WrittenSamples - (long)playedSamples) / (double)nSamplePerMs)); // calc the time hardware buffer
  135.  
  136.             long playedDifference = ((long)delaySpeaker.OldPlayedSamples - (long)playedSamples);
  137.  
  138.  
  139.             int i = 0;
  140.             if (playedDifference > 64000)
  141.             {
  142.                 // If the sound cards number-of-played-out-samples variable wraps around before
  143.                 // written_sampels wraps around this needs to be adjusted. This can happen on
  144.                 // sound cards that uses less than 32 bits to keep track of number of played out
  145.                 // sampels. To avoid being fooled by sound cards that sometimes produces false
  146.                 // output we compare old value minus the new value with a large value. This is
  147.                 // neccessary because some SC:s produce an output like 153, 198, 175, 230 which
  148.                 // would trigger the wrap-around function if we didn't compare with a large value.
  149.                 // The value 64000 is chosen because 2^16=65536 so we allow wrap around at 16 bits.
  150.  
  151.                 i = 31;
  152.                 while (delaySpeaker.OldPlayedSamples <= (ulong)Math.Pow(2, i) && (i > 14))
  153.                 {
  154.                     i--;
  155.                 }
  156.  
  157.                 if ((i < 31) && (i > 14))
  158.                 {
  159.                     // Avoid adjusting when there is 32-bit wrap-around since that is
  160.                     // something neccessary.
  161.                     //
  162.                     logger(null, FLV_LogLevel.DEBUG, String.Format("msecleft() => wrap around occured: {0} bits used by sound card)", (i + 1)));
  163.  
  164.                     delaySpeaker.WrittenSamples = delaySpeaker.WrittenSamples - (ulong)(Math.Pow(2, i + 1));
  165.                     writtenSamples = delaySpeaker.WrittenSamples;
  166.                     delayMsInBuffer = (int)(((double)((long)writtenSamples - (long)playedSamples) / (double)nSamplePerMs));
  167.                 }
  168.             }
  169.             else if ((delaySpeaker.WrittenSamples > Math.Pow(2, 31)) && ((long)writtenSamples < 96000))
  170.             {
  171.                 // Wrap around as expected after having used all 32 bits. (But we still
  172.                 // test if the wrap around happened earlier which it should not)
  173.  
  174.                 i = 31;
  175.                 while (delaySpeaker.OldWrittenSamples <= (ulong)Math.Pow(2, i))
  176.                 {
  177.                     i--;
  178.                 }
  179.  
  180.                 logger(null, FLV_LogLevel.DEBUG, "  msecleft() (wrap around occured after having used all 32 bits)");
  181.  
  182.                 delaySpeaker.OldWrittenSamples = writtenSamples;
  183.                 delaySpeaker.OldPlayedSamples = playedSamples;
  184.                 delayMsInBuffer = (int)((double)(writtenSamples + Math.Pow(2, i + 1) - playedSamples) / (double)nSamplePerMs);
  185.  
  186.             }
  187.             else if ((writtenSamples < (ulong)96000) && (playedSamples > Math.Pow(2, 31)))
  188.             {
  189.                 // Wrap around has, as expected, happened for written_sampels before
  190.                 // playedSampels so we have to adjust for this until also playedSampels
  191.                 // has had wrap around.
  192.  
  193.                 logger(null, FLV_LogLevel.DEBUG, "  msecleft() (wrap around occured: correction of output is done)");
  194.  
  195.                 delaySpeaker.OldWrittenSamples = writtenSamples;
  196.                 delaySpeaker.OldPlayedSamples = playedSamples;
  197.                 delayMsInBuffer = (int)((double)((long)writtenSamples + Math.Pow(2, 32) - (long)playedSamples) / (double)nSamplePerMs);
  198.             }
  199.  
  200.             delaySpeaker.OldWrittenSamples = writtenSamples;
  201.             delaySpeaker.OldPlayedSamples = playedSamples;
  202.  
  203.             //delayMsInBuffer += (long)((double)bufferLength / (double)nBytesPerMs); // add the actual ms
  204.  
  205.  
  206.             long diff, y;
  207.             long time = delaySpeaker.SpeakerStopwatch.ElapsedMilliseconds;
  208.             delaySpeaker.SpeakerStopwatch.Reset();
  209.             delaySpeaker.SpeakerStopwatch.Start();
  210.  
  211.             if ((delayMsInBuffer < 20) || (time - delaySpeaker.DelayControlPrevTime > 40))
  212.             {
  213.                 delaySpeaker.DelayControlPenaltyCounter = 100;
  214.             }
  215.  
  216.             if ((playedSamples != 0))
  217.             {
  218.                 y = (long)playedSamples / 48 - time;
  219.                 if ((delaySpeaker.DelayControlYPrev != 0) && (delaySpeaker.DelayControlPenaltyCounter == 0))
  220.                 {
  221.                     diff = y - delaySpeaker.DelayControlYPrev;
  222.                     delaySpeaker.DelayControlDiffMean = (990 * delaySpeaker.DelayControlDiffMean) / 1000 + 10 * diff;
  223.                 }
  224.                 delaySpeaker.DelayControlYPrev = y;
  225.             }
  226.  
  227.             if (delaySpeaker.DelayControlPenaltyCounter > 0)
  228.             {
  229.                 delaySpeaker.DelayControlPenaltyCounter--;
  230.             }
  231.  
  232.             if (delaySpeaker.DelayControlDiffMean < -200)
  233.             {
  234.                 // Always reset the filter
  235.                 delaySpeaker.DelayControlDiffMean = 0;
  236.  
  237.                 // Problem is detected. Switch delay method and set min buffer to 80.
  238.                 // Reset the filter and keep monitoring the filter output.
  239.                 // If issue is detected a second time, increase min buffer to 100.
  240.                 // If that does not help, we must modify this scheme further.
  241.  
  242.                 UseDelayHeader++;
  243.                 if (UseDelayHeader == 1)
  244.                 {
  245.                     delaySpeaker.MinPlayBufDelay = 80;
  246.                     logger(null, FLV_LogLevel.DEBUG, String.Format("Modification #1: _useHeader = {0}, _minPlayBufDelay = {1}", UseDelayHeader, delaySpeaker.MinPlayBufDelay));
  247.                 }
  248.                 else if (UseDelayHeader == 2)
  249.                 {
  250.                     delaySpeaker.MinPlayBufDelay = 100;   // add some more safety
  251.                     logger(null, FLV_LogLevel.DEBUG, String.Format("Modification #2: _useHeader = {0}, _minPlayBufDelay = {1}", UseDelayHeader, delaySpeaker.MinPlayBufDelay));
  252.                 }
  253.                 else
  254.                 {
  255.                     // This state should not be entered... (HA)
  256.                     logger(null, FLV_LogLevel.DEBUG, "further actions are required!");
  257.                 }
  258.                 logger(null, FLV_LogLevel.DEBUG, "kPlayoutWarning message posted: switching to alternative playout delay method");
  259.             }
  260.             delaySpeaker.DelayControlPrevTime = time;
  261.             delaySpeaker.DelayControlPrevPlay = playedSamples;
  262.  
  263.             // Try a very rough method of looking at how many buffers are still playing
  264.             //int ms_Header = (int)waveOut.HardwareBufferedDuration.TotalMilliseconds;
  265.             int ms_Header = (int)waveStream.BufferedDuration.TotalMilliseconds;
  266.  
  267.             // If this is true we have had a problem with the playout
  268.             if (UseDelayHeader > 0)
  269.             {
  270.                 logger(null, FLV_LogLevel.DEBUG, String.Format("[AEC]GetPlayBufferDelay(0) delayInbuffer({0}) msHeader({1}) useHeader({2})", delayMsInBuffer, ms_Header, UseDelayHeader));
  271.                 return (ms_Header + addedLatency /*+ (int)waveStream.BufferedDuration.TotalMilliseconds*/);
  272.             }
  273.  
  274.             if (ms_Header < delayMsInBuffer)
  275.             {
  276.                 ms_Header -= 6; // Round off as we only have 10ms resolution + Header info is usually slightly delayed compared to GetPosition
  277.  
  278.                 if (ms_Header < 0)
  279.                     ms_Header = 0;
  280.  
  281.                 logger(null, FLV_LogLevel.DEBUG, String.Format("[AEC]GetPlayBufferDelay(1) delayInbuffer({0}) msHeader({1}) useHeader({2})", delayMsInBuffer, ms_Header, UseDelayHeader));
  282.                 return (ms_Header + addedLatency /*+ (int)waveStream.BufferedDuration.TotalMilliseconds*/);
  283.             }
  284.             else
  285.             {
  286.                 logger(null, FLV_LogLevel.DEBUG, String.Format("[AEC]GetPlayBufferDelay(2) delayInbuffer({0}) msHeader({1}) useHeader({2})", delayMsInBuffer, ms_Header, UseDelayHeader));
  287.                 return (delayMsInBuffer + addedLatency /*+ (int)waveStream.BufferedDuration.TotalMilliseconds*/);
  288.             }
  289.  
  290.         }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement