I seem to have the underrun issue solved.
authorbruceperens <bruceperens@01035d8c-6547-0410-b346-abe4f91aad63>
Wed, 19 Mar 2014 21:32:00 +0000 (21:32 +0000)
committerbruceperens <bruceperens@01035d8c-6547-0410-b346-abe4f91aad63>
Wed, 19 Mar 2014 21:32:00 +0000 (21:32 +0000)
git-svn-id: https://svn.code.sf.net/p/freetel/code@1453 01035d8c-6547-0410-b346-abe4f91aad63

freedv-server/source/codec_noop.cpp
freedv-server/source/drivers.h
freedv-server/source/platform/linux/alsa.cpp
freedv-server/source/platform/linux/audio_in_alsa.cpp
freedv-server/source/platform/linux/audio_out_alsa.cpp
freedv-server/source/platform/posix/scheduler.cpp
freedv-server/source/run.cpp
freedv-server/source/tone.cpp

index fed732ff45fa457b0c788f5e22f1ee345e355757..f1edee34d4290ffa3133ee9d5f5c47b1cd0e2af1 100644 (file)
@@ -7,6 +7,8 @@ namespace FreeDV {
   /// Codec "no-op", just copies its input to its output. For plain SSB voice, and for testing.
   class CodecNoOp : public Codec {
     static const std::size_t   FrameDuration = 40;
+    static const std::size_t   FrameSamples = SamplesPerMillisecond
+                                * FrameDuration;
 
   public:
 
@@ -57,8 +59,10 @@ namespace FreeDV {
   std::size_t
   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) * FrameDuration) )
+    std::size_t length = min(*data_length / 2, sample_length);
+    length -= length % FrameSamples;
+
+    if ( length < FrameSamples )
     {
       *data_length = 0;
       return 0;
index 6047055cada0ebe2512a3ea3d42a8edda207c3d1..cfcf0e629a9098893263c17b4c74a5835e2a26b5 100644 (file)
@@ -28,6 +28,15 @@ const unsigned int   AudioFrameDuration = 10;
 const unsigned int     AudioFrameSamples = SamplesPerMillisecond
                         * AudioFrameDuration;
 
+/// The maximum frame duration in milliseconds. This will be used to set
+/// buffer sizes. It must be larger than the frame duration used by your
+/// modem/framer/codec combination, or they will stall.
+const unsigned int     MaximumFrameDuration = 100;
+
+/// The number of audio samples in the maximum-duration frame.
+const unsigned int     MaximumFrameSamples = SamplesPerMillisecond
+                        * MaximumFrameDuration;
+
 /// Allocate memory and copy a string into it, so that it is permanently
 /// stored.
 /// \param s The string to be copied.
index 8946138d202fb6cd90a1dbbdae4ffa876022d51a..851845669265fb13ac222c9e07a751c98c6879cb 100644 (file)
@@ -2,6 +2,7 @@
 #include <iostream>
 #include <sstream>
 #include <stdexcept>
+#include <string.h>
 
 namespace FreeDV {
   static std::ostream &
@@ -33,7 +34,7 @@ namespace FreeDV {
     const int error = snd_pcm_open(
      &pcm_handle,
      "default",
-     SND_PCM_STREAM_PLAYBACK,
+     mode,
      0);
   
     stream << "\"alsa:default\"";
@@ -42,9 +43,10 @@ namespace FreeDV {
   
     while ( snd_card_next(&card_index) == 0 && card_index >= 0 ) {
       char             device_name[20];
-      snd_ctl_t *              ctl_handle = 0;
-      int                      pcm_error = 0;
+      snd_ctl_t *      ctl_handle = 0;
+      int              pcm_error = 0;
       char *           longname = 0;
+      char *           i;
   
       if ( snd_card_get_longname(card_index, &longname) == 0 ) {
         sprintf(device_name, "hw:%d", card_index);
@@ -56,6 +58,14 @@ namespace FreeDV {
           
         const int error = ctl_error ? ctl_error : pcm_error;
     
+        i = strstr(longname, ", full speed");
+        if ( i )
+          *i = '\0';
+       
+        i = strstr(longname, " irq ");
+        if ( i )
+          *i = '\0';
+
         stream << "\"alsa:" << longname << '"';
         error_message(stream, error);
         stream << std::endl;
@@ -75,11 +85,19 @@ namespace FreeDV {
   do_throw(
    const int error,
    const char * name,
+   snd_pcm_stream_t access,
    const char * message = 0)
   {
     std::ostringstream str;
 
-    str << "ALSA device \"" << name << "\" set-up error: ";
+    str << "ALSA ";
+
+    if ( access == SND_PCM_STREAM_CAPTURE )
+      str << "input";
+    else
+      str << "output";
+
+    str << " device \"" << name << "\" set-up error: ";
      if ( message )
        str << message << ": ";
      str << snd_strerror(error) << '.';
@@ -113,52 +131,52 @@ namespace FreeDV {
  
     if ( (error = snd_pcm_hw_params_malloc(&hw_params)) < 0 ) {
       snd_pcm_close(handle);
-      do_throw(error, name, "ALSA hardare parameter allocation");
+      do_throw(error, name, stream, "ALSA hardare parameter allocation");
     }
  
     if ( (error = snd_pcm_hw_params_any(handle, hw_params )) < 0 ) {
       snd_pcm_close(handle);
-      do_throw(error, name, "Get configuration space for device");
+      do_throw(error, name, stream, "Get configuration space for device");
     }
 
     if ( (error = snd_pcm_hw_params_set_format(handle, hw_params, format )) < 0 ) {
       snd_pcm_close(handle);
-      do_throw(error, name, "Set format");
+      do_throw(error, name, stream, "Set format");
     }
 
     if ( (error = snd_pcm_hw_params_set_access(handle, hw_params, access )) < 0 ) {
       snd_pcm_close(handle);
-      do_throw(error, name, "Set access");
+      do_throw(error, name, stream, "Set access");
     }
 
     if ( (error = snd_pcm_hw_params_set_channels(handle, hw_params, channels )) < 0 ) {
       snd_pcm_close(handle);
-      do_throw(error, name, "Set channels");
+      do_throw(error, name, stream, "Set channels");
     }
 
     if ( (error = snd_pcm_hw_params_set_rate(handle, hw_params, rate, 0 )) < 0 ) {
       snd_pcm_close(handle);
-      do_throw(error, name, "Set rate");
+      do_throw(error, name, stream, "Set rate");
     }
 
     if ( (error = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0)) < 0 ) {
       snd_pcm_close(handle);
-      do_throw(error, name, "Disable resampling");
+      do_throw(error, name, stream, "Disable resampling");
     }
 
-    if ( (error = snd_pcm_hw_params_set_period_size(handle, hw_params, period_size, 0)) < 0 ) {
+    if ( (error = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &period_size, 0)) < 0 ) {
       snd_pcm_close(handle);
-      do_throw(error, name, "Set I/O period size");
+      do_throw(error, name, stream, "Set I/O period size");
     }
 
-    if ( (error = snd_pcm_hw_params_set_buffer_size(handle, hw_params, buffer_size)) < 0 ) {
+    if ( (error = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &buffer_size)) < 0 ) {
       snd_pcm_close(handle);
-      do_throw(error, name, "Set I/O buffer size");
+      do_throw(error, name, stream, "Set I/O buffer size");
     }
  
     if ( (error = snd_pcm_hw_params(handle, hw_params)) < 0 ) {
       snd_pcm_close(handle);
-      do_throw(error, name, "ALSA hardware parameter select");
+      do_throw(error, name, stream, "ALSA hardware parameter select");
     }
  
     snd_pcm_hw_params_free(hw_params);
index f128289c1bb379a4f3461b8874f7a7963d617a37..cb8f5c7ed8c7d8232abce4b14fae47f4b057e9fe 100644 (file)
@@ -1,4 +1,4 @@
-/// The ALSA audio input driver. 
+// The ALSA audio input driver. 
 
 #include "drivers.h"
 #include "alsa.h"
@@ -14,8 +14,7 @@ namespace FreeDV {
   /// Audio input "ALSA", Uses the Linux ALSA Audio API.
   class AudioInALSA : public AudioInput {
   private:
-    static const int   overlong_delay = ((double)SampleRate / 1000.0)
-                        * AudioFrameDuration * 2;
+    static const int   overlong_delay = AudioFrameSamples * 2;
 
     char * const       parameters;
     snd_pcm_t *                handle;
@@ -67,8 +66,8 @@ namespace FreeDV {
      SND_PCM_ACCESS_RW_INTERLEAVED,
      1,
      SampleRate,
-     (int)ceil((double)SampleRate / 1000.0) * AudioFrameDuration,
-     (int)ceil((double)SampleRate / 1000.0) * AudioFrameDuration);
+     AudioFrameSamples / 2,
+     AudioFrameSamples);
 
     snd_pcm_start(handle);
   }
@@ -135,7 +134,8 @@ namespace FreeDV {
 
     if ( !started ) {
       snd_pcm_start(handle);
-      return ((double)SampleRate / 1000.0) * AudioFrameDuration;
+      started = true;
+      return AudioFrameSamples;
     }
 
     error = snd_pcm_avail_delay(handle, &available, &delay);
@@ -155,20 +155,17 @@ namespace FreeDV {
       std::cerr << "ALSA input \"" << parameters << "\": overlong delay, dropped "
        << seconds << " seconds of input." << std::endl;
 
-      return 0;
+      return 1;
     }
 
     if ( error == -EPIPE ) {
       snd_pcm_recover(handle, error, 1);
-      available = snd_pcm_avail_delay(handle, &available, &delay);
       std::cerr << "ALSA input \"" << parameters << "\": ready underrun." << std::endl;
+      return 0;
     }
 
     if ( error >= 0 )
       return available;
-
-    else if ( error == -EPIPE )
-      return 0;
     else {
       do_throw(error, "Get Frames Available for Read");
       return 0; // do_throw doesn't return.
index ec5f353e611bc452b4d0835b84551d208088d61b..74d3bb732d2dcc9012214bdb685e9883a944e061 100644 (file)
@@ -1,4 +1,4 @@
-/// The ALSA audio output driver. 
+// The ALSA audio output driver. 
 
 #include "alsa.h"
 #include "drivers.h"
@@ -66,9 +66,8 @@ namespace FreeDV {
      SND_PCM_ACCESS_RW_INTERLEAVED,
      1,
      SampleRate,
-     (int)ceil((double)SampleRate / 1000.0) * AudioFrameDuration,
-     (int)ceil((double)SampleRate / 1000.0) * AudioFrameDuration);
-    snd_pcm_pause(handle, 1);
+     AudioFrameSamples / 2,
+     AudioFrameSamples);
   }
 
   AudioOutALSA::~AudioOutALSA()
@@ -82,6 +81,23 @@ namespace FreeDV {
   std::size_t
   AudioOutALSA::write16(const std::int16_t * array, std::size_t length)
   {
+    if ( !started ) {
+      // Preload the audio output queue with one frame of silence. This makes
+      // underruns less likely. This delays the output by one frame from where
+      // we would otherwise have started it. Otherwise we tend to underrun
+      // repeatedly at startup time.
+      //
+      // Note that all inputs and outputs of the typical user do not run on
+      // a shared clock. There is the potential for overruns or underruns
+      // during long operation because of this. Professional studios use
+      // a shared clock, and the more expensive equipment that supports it,
+      // to avoid this problem.
+      //
+      int16_t  buf[AudioFrameSamples];
+      memset(buf, 0, sizeof(buf));
+      snd_pcm_writei(handle, buf, sizeof(buf) / sizeof(*buf));
+    }
+
     int error = snd_pcm_writei(handle, array, length);
     started = true;
     if ( error == -EPIPE ) {
@@ -131,10 +147,10 @@ namespace FreeDV {
     int                        error;
 
     if ( !started )
-      return ((double)SampleRate / 1000.0) * AudioFrameDuration;
+      return AudioFrameSamples;
 
     error = snd_pcm_avail_delay(handle, &available, &delay);
-    if ( delay > (((double)SampleRate / 1000.0) * AudioFrameDuration * 2) ) {
+    if ( delay > (AudioFrameSamples * 2) ) {
       const double seconds = (double)delay / (double)SampleRate;
 
       std::cerr << "ALSA output \"" << parameters
@@ -142,8 +158,9 @@ namespace FreeDV {
        << seconds << " seconds of output." << std::endl;
       snd_pcm_drop(handle);
       snd_pcm_prepare(handle);
+      snd_pcm_pause(handle, 1);
       started = false;
-      return 0;
+      return AudioFrameSamples;
     }
 
     if ( error == -EPIPE ) {
@@ -151,16 +168,12 @@ namespace FreeDV {
       snd_pcm_recover(handle, error, 1);
       snd_pcm_pause(handle, 1);
       started = false;
-      return 0;
 
-      if ( error < 0 )
-        return 0;
+      return AudioFrameSamples;
     }
 
     if ( error == 0 )
       return available;
-    else if ( error == -EPIPE )
-      return 0;
     else {
       do_throw(error, "Get Frames Available for Write");
       return 0; // do_throw doesn't return.
index 2c33ed8542d49284fb24740fcff048abdde0617a..2f79fc4d848a3b36349417ef220b898a8e03e604 100644 (file)
@@ -70,11 +70,12 @@ namespace FreeDV {
 #endif
     if ( old_kernel )
       std::cerr << old_kernel_message;
-    else if ( insufficient_privilege )
+    else if ( insufficient_privilege ) {
       std::cerr << privilege_message_a;
       std::cerr << program_name;
       std::cerr << privilege_message_b;
       std::cerr << program_name;
       std::cerr << privilege_message_c;
+    }
   }
 }
index 897fe61c56dc579cf74892ab108b870e38abfa5b..c77188e119db00a3ff8f4f35584d7688273c0528 100644 (file)
 namespace FreeDV {
   class Run {
   private:
-    const std::size_t  TempSize = 10240;
+    const std::size_t  FIFOSize = MaximumFrameSamples * sizeof(int16_t) * 2;
     Interfaces * const i;
     bool               begin_receive;
     bool               begin_transmit;
     FIFO               codec_fifo;
     FIFO               in_fifo;
     FIFO               out_fifo;
+    int                        poll_fd_count;
     bool               ptt_digital;
     bool               ptt_ssb;
+    bool               started;
+    PollType           poll_fds[100];
  
+    void               add_poll_device(IODevice * device);
+    void               do_throw(int error, const char * message);
     void               key_down();
     void               key_up();
     void               receive();
@@ -48,19 +53,69 @@ namespace FreeDV {
   
   Run::Run(Interfaces * interfaces)
   : i(interfaces), begin_receive(true), begin_transmit(false),
-    codec_fifo(TempSize * 2), in_fifo(TempSize * 2),
-    out_fifo(TempSize * 2), ptt_digital(false), ptt_ssb(false)
+    codec_fifo(FIFOSize), in_fifo(FIFOSize),
+    out_fifo(FIFOSize), poll_fd_count(0), ptt_digital(false), ptt_ssb(false),
+    started(false)
   {
     reset();
+    // add_poll_device(i->loudspeaker);
+    add_poll_device(i->receiver);
+    add_poll_device(i->microphone);
+    add_poll_device(i->ptt_input_digital);
+    add_poll_device(i->ptt_input_ssb);
+    add_poll_device(i->text_input);
+    add_poll_device(i->user_interface);
   }
 
   Run::~Run()
   {
   }
 
+  void
+  Run::add_poll_device(IODevice * device)
+  {
+    static const int   space = sizeof(poll_fds) / sizeof(*poll_fds);
+
+    const int result = device->poll_fds(
+     &poll_fds[poll_fd_count],
+     space - poll_fd_count);
+
+    if ( result >= 0 ) {
+      const int new_size = poll_fd_count + result;
+
+      if ( new_size < space )
+        poll_fd_count = new_size;
+      else
+        do_throw(0, "Too many file descriptors for poll");
+    }
+    else {
+      std::ostringstream       str;
+
+      device->print(str);
+      do_throw(result, str.str().c_str());
+    }
+  }
+
+  void
+  Run::do_throw(const int error, const char * message = 0)
+  {
+    std::ostringstream str;
+
+    str << "Main loop error: ";
+    if ( message )
+      str << message << ": ";
+    if ( error ) {
+      str << ": ";
+      str << strerror(error);
+    }
+    str << '.';
+    throw std::runtime_error(str.str().c_str());
+  }
+
   void
   Run::reset()
   {
+    started = false;
     in_fifo.reset();
     codec_fifo.reset();
     out_fifo.reset();
@@ -94,29 +149,12 @@ namespace FreeDV {
   void
   Run::receive()
   {
-    // Drain any data that the loudspeaker can take.
-    const std::size_t  out_samples = min(
-                        i->loudspeaker->ready(),
-                        (out_fifo.get_available() / 2));
-
-    if ( out_samples ) {
-      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 if ( result < 0 )
-       std::cerr << "Loudspeaker I/O error: " << strerror(errno) << std::endl;
-    }
-    
     // Fill any data that the receiver can provide.
     const std::size_t  in_samples = min(
                         i->receiver->ready(),
                         (in_fifo.put_space() / 2));
 
-    if ( in_samples ) {
+    if ( in_samples > 0 ) {
       const int result = i->receiver->read16(
         (std::int16_t *)in_fifo.put(in_samples * 2),
         in_samples);
@@ -127,8 +165,8 @@ namespace FreeDV {
        std::cerr << "Receiver I/O error: " << strerror(errno) << std::endl;
     }
     
-    if ( in_fifo.get_available() > 0 ) {
-      std::size_t      samples_to_demodulate = in_fifo.get_available() / 2;
+    std::size_t        samples_to_demodulate = in_fifo.get_available() / 2;
+    if ( samples_to_demodulate > 0 ) {
       const std::size_t        bytes_to_demodulate = codec_fifo.put_space();
 
       std::size_t result = i->modem->demodulate16(
@@ -146,8 +184,8 @@ namespace FreeDV {
         codec_fifo.put_done(result);
     }
 
-    if ( codec_fifo.get_available() > 0 ) {
-      std::size_t bytes_to_decode = codec_fifo.get_available();
+    std::size_t bytes_to_decode = codec_fifo.get_available();
+    if ( bytes_to_decode > 0 ) {
 
       const std::size_t samples_to_decode = out_fifo.put_space() / 2;
 
@@ -164,13 +202,43 @@ namespace FreeDV {
       if ( result > 0 )
         out_fifo.put_done(result * 2);
     }
+
+    // Drain any data that the loudspeaker can take.
+    const std::size_t  out_samples = min(
+                        i->loudspeaker->ready(),
+                        (out_fifo.get_available() / 2));
+
+    if ( out_samples > 0 ) {
+      const int result = i->loudspeaker->write16(
+                                 (std::int16_t *)out_fifo.get(
+                                  out_samples * 2),
+                                 out_samples);
+
+      if ( result > 0 ) {
+        started = true;
+        out_fifo.get_done(result * 2);
+      }
+      else if ( result < 0 )
+       std::cerr << "Loudspeaker I/O error: " << strerror(errno) << std::endl;
+    }
   }
   
   void
   Run::run()
   {
-    while ( true ) {
+    for ( ; ; ) {
       receive();
+
+      for ( int i = 0; i < poll_fd_count; i++ )
+        poll_fds[i].revents = 0;
+
+      const int result = IODevice::poll(
+       poll_fds,
+       poll_fd_count,
+       AudioFrameDuration);
+
+      if ( result < 0 )
+        do_throw(result, "Poll");
     }
   }
 
index 7598358db7051c76a3ee64488bf3b3f7933d1a1a..b808254b205d6d6f1b577dab2309df531d24bcd1 100644 (file)
@@ -140,7 +140,7 @@ namespace FreeDV {
   std::size_t
   Tone::ready()
   {
-    return SIZE_MAX;
+    return AudioFrameSamples;
   }
 
   float