From: bruceperens Date: Wed, 19 Mar 2014 21:32:00 +0000 (+0000) Subject: I seem to have the underrun issue solved. X-Git-Url: http://git.whiteaudio.com/gitweb/?a=commitdiff_plain;h=a65438b9a75618bf795a857a88963c55da8a9d09;p=freetel-svn-tracking.git I seem to have the underrun issue solved. git-svn-id: https://svn.code.sf.net/p/freetel/code@1453 01035d8c-6547-0410-b346-abe4f91aad63 --- diff --git a/freedv-server/source/codec_noop.cpp b/freedv-server/source/codec_noop.cpp index fed732ff..f1edee34 100644 --- a/freedv-server/source/codec_noop.cpp +++ b/freedv-server/source/codec_noop.cpp @@ -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; diff --git a/freedv-server/source/drivers.h b/freedv-server/source/drivers.h index 6047055c..cfcf0e62 100644 --- a/freedv-server/source/drivers.h +++ b/freedv-server/source/drivers.h @@ -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. diff --git a/freedv-server/source/platform/linux/alsa.cpp b/freedv-server/source/platform/linux/alsa.cpp index 8946138d..85184566 100644 --- a/freedv-server/source/platform/linux/alsa.cpp +++ b/freedv-server/source/platform/linux/alsa.cpp @@ -2,6 +2,7 @@ #include #include #include +#include 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); diff --git a/freedv-server/source/platform/linux/audio_in_alsa.cpp b/freedv-server/source/platform/linux/audio_in_alsa.cpp index f128289c..cb8f5c7e 100644 --- a/freedv-server/source/platform/linux/audio_in_alsa.cpp +++ b/freedv-server/source/platform/linux/audio_in_alsa.cpp @@ -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. diff --git a/freedv-server/source/platform/linux/audio_out_alsa.cpp b/freedv-server/source/platform/linux/audio_out_alsa.cpp index ec5f353e..74d3bb73 100644 --- a/freedv-server/source/platform/linux/audio_out_alsa.cpp +++ b/freedv-server/source/platform/linux/audio_out_alsa.cpp @@ -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. diff --git a/freedv-server/source/platform/posix/scheduler.cpp b/freedv-server/source/platform/posix/scheduler.cpp index 2c33ed85..2f79fc4d 100644 --- a/freedv-server/source/platform/posix/scheduler.cpp +++ b/freedv-server/source/platform/posix/scheduler.cpp @@ -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; + } } } diff --git a/freedv-server/source/run.cpp b/freedv-server/source/run.cpp index 897fe61c..c77188e1 100644 --- a/freedv-server/source/run.cpp +++ b/freedv-server/source/run.cpp @@ -23,16 +23,21 @@ 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"); } } diff --git a/freedv-server/source/tone.cpp b/freedv-server/source/tone.cpp index 7598358d..b808254b 100644 --- a/freedv-server/source/tone.cpp +++ b/freedv-server/source/tone.cpp @@ -140,7 +140,7 @@ namespace FreeDV { std::size_t Tone::ready() { - return SIZE_MAX; + return AudioFrameSamples; } float