Use period sizes in dealing with overruns and underruns.
authorbruceperens <bruceperens@01035d8c-6547-0410-b346-abe4f91aad63>
Thu, 13 Mar 2014 19:46:05 +0000 (19:46 +0000)
committerbruceperens <bruceperens@01035d8c-6547-0410-b346-abe4f91aad63>
Thu, 13 Mar 2014 19:46:05 +0000 (19:46 +0000)
git-svn-id: https://svn.code.sf.net/p/freetel/code@1435 01035d8c-6547-0410-b346-abe4f91aad63

freedv-server/source/codec_noop.cpp
freedv-server/source/drivers.h
freedv-server/source/platform/linux/audio_in_alsa.cpp
freedv-server/source/platform/linux/audio_out_alsa.cpp
freedv-server/source/run.cpp

index 2a429495841dc5c67c5f94640ba6266a32e9cbbc..54af458ce2439d8e32d631c47ec75a704b6b4e9c 100644 (file)
@@ -56,6 +56,10 @@ namespace FreeDV {
   CodecNoOp::decode16(const std::uint8_t * i, std::int16_t * o, std::size_t * data_length, std::size_t sample_length)
   {
     const std::size_t length = min(*data_length / 2, sample_length);
+    if ( length < (std::size_t)(((double)SampleRate / 1000.0) * 40) ) {
+      *data_length = 0;
+      return 0;
+    }
     memcpy(o, i, length * 2);
     *data_length = length * 2;
     return length;
@@ -71,7 +75,7 @@ namespace FreeDV {
   int
   CodecNoOp::min_frame_duration() const
   {
-    return 1;
+    return 40;
   }
 
   Codec *
index b8925a373488167241e9e611f1a99f5957c62b7e..548537c710733dfc2ffe7040ef02896676c1c1b7 100644 (file)
@@ -13,6 +13,18 @@ namespace FreeDV {
   /// drivers.
   const unsigned int   SampleRate = 48000;
 
+  // The minimum frame duration in milliseconds. The audio interfaces will
+  // use this as a period size. It should be the smallest frame we  expect
+  // a modem/protocol/codec combination to use. If it's too large, latency
+  // will be overlong.
+  const unsigned int   MinimumFrameDuration = 10;
+
+  // The maximum frame duration in milliseconds. The audio interfaces will
+  // use 3 times this as a buffer size. It should be the largest frame we
+  // expect a modem/protocol/codec combination to use. If it's too large,
+  // ALSA bugs surface and cause long delays.
+  const unsigned int   MaximumFrameDuration = 100;
+
   /// Allocate memory and copy a string into it, so that it is permanently
   /// stored.
   /// \param s The string to be copied.
index d40aad9d4d18dcaa5144fa393f99b6b288556464..50ba740ef12a52c6fbff18694d12194d5b188bf2 100644 (file)
@@ -2,10 +2,11 @@
 
 #include "drivers.h"
 #include "alsa.h"
-#include <stdlib.h>
-#include <errno.h>
 #include <sstream>
 #include <stdexcept>
+#include <stdlib.h>
+#include <errno.h>
+#include <math.h>
 
 namespace FreeDV {
   std::ostream & ALSAEnumerate(std::ostream & stream, snd_pcm_stream_t mode);
@@ -13,10 +14,12 @@ namespace FreeDV {
   /// Audio input "ALSA", Uses the Linux ALSA Audio API.
   class AudioInALSA : public AudioInput {
   private:
-    static const int   overlong_delay = 10000;
+    static const int   overlong_delay = ((double)SampleRate / 1000.0)
+                        * MaximumFrameDuration;
 
     char * const       parameters;
     snd_pcm_t *                handle;
+    bool               started;
 
     void
     do_throw(const int error, const char * message = 0)
@@ -46,23 +49,18 @@ namespace FreeDV {
   };
 
   AudioInALSA::AudioInALSA(const char * p)
-  : AudioInput("alsa", p), parameters(strdup(p))
+  : AudioInput("alsa", p), parameters(strdup(p)), started(false)
   {
-    const snd_pcm_format_t     format = SND_PCM_FORMAT_S16;
-    const snd_pcm_access_t     access = SND_PCM_ACCESS_RW_INTERLEAVED;
-    const unsigned int         channels = 1;
-    int                                error;
-
     handle = ALSASetup(
      p,
      SND_PCM_STREAM_CAPTURE,
      0,
-     format,
-     access,
-     channels,
+     SND_PCM_FORMAT_S16,
+     SND_PCM_ACCESS_RW_INTERLEAVED,
+     1,
      SampleRate,
-     SampleRate / 100,
-     SampleRate / 10);
+     (int)ceil(((double)SampleRate / 1000.0) * MinimumFrameDuration),
+     (int)ceil(((double)SampleRate / 1000.0) * MaximumFrameDuration * 2));
 
     snd_pcm_start(handle);
   }
@@ -79,6 +77,7 @@ namespace FreeDV {
   AudioInALSA::read16(std::int16_t * array, std::size_t length)
   {
     int result = snd_pcm_readi(handle, array, length);
+    started = true;
     if ( result == -EPIPE ) {
       snd_pcm_recover(handle, result, 1);
       result = snd_pcm_readi(handle, array, length);
@@ -110,10 +109,16 @@ namespace FreeDV {
   std::size_t
   AudioInALSA::ready()
   {
+    static const int period_size = (int)ceil(
+     ((double)SampleRate * 1000.0) * MinimumFrameDuration);
+
     snd_pcm_sframes_t  available = 0;
     snd_pcm_sframes_t  delay = 0;
     int                        error;
 
+    if ( !started )
+      return period_size;
+
     error = snd_pcm_avail_delay(handle, &available, &delay);
 
     // If the program has been paused, there will be a long queue of incoming
@@ -122,7 +127,7 @@ namespace FreeDV {
     if ( delay >= overlong_delay && available > 0 ) {
       snd_pcm_drop(handle);
       snd_pcm_prepare(handle);
-      snd_pcm_start(handle);
+      started = false;
 
       const double seconds = (double)delay / (double)SampleRate;
 
@@ -130,6 +135,8 @@ namespace FreeDV {
 
       std::cerr << "ALSA input " << parameters << ": overlong delay, dropped "
        << seconds << " seconds of queued audio samples." << std::endl;
+
+      return 0;
     }
 
     if ( error == -EPIPE ) {
index 85d9d88a8b2989f690d56661289c31dc3745d181..763ee0bd8703889d9738fbfe02c077f6c842ed78 100644 (file)
@@ -1,12 +1,13 @@
 /// The ALSA audio output driver. 
 
 #include "alsa.h"
-#include <stdlib.h>
-#include <errno.h>
 #include "drivers.h"
-#include <sys/ioctl.h>
 #include <sstream>
 #include <stdexcept>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <math.h>
 
 namespace FreeDV {
   std::ostream & ALSAEnumerate(std::ostream & stream, snd_pcm_stream_t mode);
@@ -16,6 +17,8 @@ namespace FreeDV {
   private:
     char * const       parameters;
     snd_pcm_t *                handle;
+    std::size_t                period_size;
+    bool               started;
 
     void
     do_throw(const int error, const char * message = 0)
@@ -45,22 +48,21 @@ namespace FreeDV {
   };
 
   AudioOutALSA::AudioOutALSA(const char * p)
-  : AudioOutput("alsa", p), parameters(strdup(p))
+  : AudioOutput("alsa", p), parameters(strdup(p)),
+    period_size(
+     (int)ceil(((double)SampleRate / 1000.0) * MinimumFrameDuration)),
+    started(false)
   {
-    const snd_pcm_format_t     format = SND_PCM_FORMAT_S16;
-    const snd_pcm_access_t     access = SND_PCM_ACCESS_RW_INTERLEAVED;
-    const unsigned int         channels = 1;
-
     handle = ALSASetup(
      p,
      SND_PCM_STREAM_PLAYBACK,
      0,
-     format,
-     access,
-     channels,
+     SND_PCM_FORMAT_S16,
+     SND_PCM_ACCESS_RW_INTERLEAVED,
+     1,
      SampleRate,
-     SampleRate / 100,
-     SampleRate / 10);
+     period_size,
+     (int)ceil(((double)SampleRate / 1000.0) * MaximumFrameDuration));
   }
 
   AudioOutALSA::~AudioOutALSA()
@@ -75,6 +77,7 @@ namespace FreeDV {
   AudioOutALSA::write16(const std::int16_t * array, std::size_t length)
   {
     int error = snd_pcm_writei(handle, array, length);
+    started = true;
     if ( error == -EPIPE ) {
       std::cerr << "ALSA output " << parameters << ": write underrun." << std::endl;
       snd_pcm_recover(handle, error, 1);
@@ -108,15 +111,18 @@ namespace FreeDV {
     snd_pcm_sframes_t  delay = 0;
     int                        error;
 
+    if ( !started )
+      return period_size * 2;
+
     error = snd_pcm_avail_delay(handle, &available, &delay);
-    if ( error == -EIO )
-      return 10;
 
     if ( error == -EPIPE ) {
       std::cerr << "ALSA output " << parameters << ": ready underrun." << std::endl;
       snd_pcm_recover(handle, error, 1);
       snd_pcm_pause(handle, 1);
-      return 10;
+      started = false;
+      return (int)ceil(((double)SampleRate / 1000.0) * MinimumFrameDuration);
+
       if ( error < 0 )
         return 0;
     }
index c023e34a3e8f51c34700559f12d217f380ac5efc..dd762f015401a61a46dea8ebe9bede81bdc22d57 100644 (file)
@@ -25,7 +25,7 @@ namespace FreeDV {
 
   class Run {
   private:
-    const std::size_t  TempSize = 2048;
+    const std::size_t  TempSize = 10240;
     Interfaces * const i;
     bool               begin_receive;
     bool               begin_transmit;
@@ -109,14 +109,14 @@ namespace FreeDV {
                         (out_fifo.get_available() / 2));
 
     if ( out_samples ) {
-      const std::size_t result = i->loudspeaker->write16(
+      const int result = i->loudspeaker->write16(
                                  (std::int16_t *)out_fifo.get(
                                   out_samples * 2),
                                  out_samples);
 
       if ( result > 0 )
         out_fifo.get_done(result * 2);
-      else
+      else if ( result < 0 )
        std::cerr << "Loudspeaker I/O error: " << strerror(errno) << std::endl;
     }
     
@@ -126,13 +126,13 @@ namespace FreeDV {
                         (in_fifo.put_space() / 2));
 
     if ( in_samples ) {
-      const std::size_t result = i->receiver->read16(
+      const int result = i->receiver->read16(
         (std::int16_t *)in_fifo.put(in_samples * 2),
         in_samples);
 
       if ( result > 0 )
         in_fifo.put_done(result * 2);
-      else
+      else if ( result < 0 )
        std::cerr << "Receiver I/O error: " << strerror(errno) << std::endl;
     }
     
@@ -173,6 +173,7 @@ namespace FreeDV {
       // TODO: We can do clock tuning here to maximize power saving, if it
       // results in any noticable gain.
       if ( result > 0 ) {
+       // std::cerr << '.';
         out_fifo.put_done(result * 2);
 
         last_frame_time = start_time;
@@ -184,6 +185,7 @@ namespace FreeDV {
         next_frame_time.tv_nsec %= 1000000000;
       }
       else {
+       // std::cerr << '+';
        // Add a microsecond. We really could poll the I/O interfaces instead.
         next_frame_time.tv_nsec += 1000000;
         next_frame_time.tv_sec += next_frame_time.tv_nsec / 1000000000;
@@ -197,16 +199,17 @@ namespace FreeDV {
   {
     // The no-op codec, modem, and framer may have very small durations.
     // So we set a minimum here.
-    min_frame_duration = 1;
+    min_frame_duration = 10;
     min_frame_duration = max(min_frame_duration, i->modem->min_frame_duration());
     min_frame_duration = max(min_frame_duration, i->codec->min_frame_duration());
     min_frame_duration = max(min_frame_duration, i->framer->min_frame_duration());
 
+    std::cerr << "minimum frame duration is " << min_frame_duration << std::endl;
     assert(min_frame_duration < 1000000);
 
     while ( true ) {
-      // clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_frame_time, 0);
       receive();
+      clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_frame_time, 0);
     }
   }