From: bruceperens Date: Thu, 13 Mar 2014 19:46:05 +0000 (+0000) Subject: Use period sizes in dealing with overruns and underruns. X-Git-Url: http://git.whiteaudio.com/gitweb/?a=commitdiff_plain;h=53db607abe49044ebbb88f1e44a776afa233d637;p=freetel-svn-tracking.git Use period sizes in dealing with overruns and underruns. git-svn-id: https://svn.code.sf.net/p/freetel/code@1435 01035d8c-6547-0410-b346-abe4f91aad63 --- diff --git a/freedv-server/source/codec_noop.cpp b/freedv-server/source/codec_noop.cpp index 2a429495..54af458c 100644 --- a/freedv-server/source/codec_noop.cpp +++ b/freedv-server/source/codec_noop.cpp @@ -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 * diff --git a/freedv-server/source/drivers.h b/freedv-server/source/drivers.h index b8925a37..548537c7 100644 --- a/freedv-server/source/drivers.h +++ b/freedv-server/source/drivers.h @@ -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. diff --git a/freedv-server/source/platform/linux/audio_in_alsa.cpp b/freedv-server/source/platform/linux/audio_in_alsa.cpp index d40aad9d..50ba740e 100644 --- a/freedv-server/source/platform/linux/audio_in_alsa.cpp +++ b/freedv-server/source/platform/linux/audio_in_alsa.cpp @@ -2,10 +2,11 @@ #include "drivers.h" #include "alsa.h" -#include -#include #include #include +#include +#include +#include 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 ) { diff --git a/freedv-server/source/platform/linux/audio_out_alsa.cpp b/freedv-server/source/platform/linux/audio_out_alsa.cpp index 85d9d88a..763ee0bd 100644 --- a/freedv-server/source/platform/linux/audio_out_alsa.cpp +++ b/freedv-server/source/platform/linux/audio_out_alsa.cpp @@ -1,12 +1,13 @@ /// The ALSA audio output driver. #include "alsa.h" -#include -#include #include "drivers.h" -#include #include #include +#include +#include +#include +#include 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; } diff --git a/freedv-server/source/run.cpp b/freedv-server/source/run.cpp index c023e34a..dd762f01 100644 --- a/freedv-server/source/run.cpp +++ b/freedv-server/source/run.cpp @@ -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); } }