From cfa6c9f73d719a217938a217817a8bb034fa0048 Mon Sep 17 00:00:00 2001 From: bruceperens Date: Thu, 13 Mar 2014 05:31:47 +0000 Subject: [PATCH] Use the old-style parameter setting functions, so that I can set the period size and buffer size. git-svn-id: https://svn.code.sf.net/p/freetel/code@1434 01035d8c-6547-0410-b346-abe4f91aad63 --- freedv-server/source/drivers.h | 7 ++ freedv-server/source/interfaces.cpp | 4 + freedv-server/source/platform/linux/alsa.cpp | 99 ++++++++++++++++++- .../source/platform/linux/audio_in_alsa.cpp | 38 ++----- .../source/platform/linux/audio_out_alsa.cpp | 62 +++++------- freedv-server/source/run.cpp | 43 +++++++- 6 files changed, 183 insertions(+), 70 deletions(-) diff --git a/freedv-server/source/drivers.h b/freedv-server/source/drivers.h index e6aa2e5d..b8925a37 100644 --- a/freedv-server/source/drivers.h +++ b/freedv-server/source/drivers.h @@ -696,6 +696,13 @@ namespace FreeDV { return a < b ? a : b; } + /// Non-template version of max(). + inline std::size_t + max(std::size_t a, std::size_t b) + { + return a > b ? a : b; + } + struct DriverList { const char * key; union { diff --git a/freedv-server/source/interfaces.cpp b/freedv-server/source/interfaces.cpp index 7f41d818..7ce77ec3 100644 --- a/freedv-server/source/interfaces.cpp +++ b/freedv-server/source/interfaces.cpp @@ -16,6 +16,9 @@ namespace FreeDV { if ( !codec ) codec = Driver::CodecNoOp(empty); + if ( !framer ) + framer = Driver::FramerNoOp(empty); + if ( !keying_output ) keying_output = Driver::KeyingSink(empty); @@ -55,6 +58,7 @@ namespace FreeDV { stream << program_name << " \\" << std::endl; } stream << "--codec=\"" << *codec << "\" \\" << std::endl; + stream << "--framer=\"" << *framer << "\" \\" << std::endl; stream << "--gui=\"" << *user_interface << "\" \\" << std::endl; stream << "--keying=\"" << *keying_output << "\" \\" << std::endl; stream << "--loudspeaker=\"" << *loudspeaker << "\" \\" << std::endl; diff --git a/freedv-server/source/platform/linux/alsa.cpp b/freedv-server/source/platform/linux/alsa.cpp index f2aa5215..8946138d 100644 --- a/freedv-server/source/platform/linux/alsa.cpp +++ b/freedv-server/source/platform/linux/alsa.cpp @@ -1,6 +1,7 @@ +#include "alsa.h" #include -#include #include +#include namespace FreeDV { static std::ostream & @@ -28,7 +29,6 @@ namespace FreeDV { { int card_index = -1; snd_pcm_t * pcm_handle; - snd_ctl_card_info_t * info = 0; const int error = snd_pcm_open( &pcm_handle, @@ -70,4 +70,99 @@ namespace FreeDV { return stream; } + + static void + do_throw( + const int error, + const char * name, + const char * message = 0) + { + std::ostringstream str; + + str << "ALSA device \"" << name << "\" set-up error: "; + if ( message ) + str << message << ": "; + str << snd_strerror(error) << '.'; + throw std::runtime_error(str.str().c_str()); + } + + snd_pcm_t * + ALSASetup( + const char * name, + snd_pcm_stream_t stream, + int mode, + snd_pcm_format_t format, + snd_pcm_access_t access, + unsigned int channels, + unsigned int rate, + snd_pcm_uframes_t period_size, + snd_pcm_uframes_t buffer_size) + { + int error; + snd_pcm_t * handle = 0; + snd_pcm_hw_params_t * hw_params = 0; + + error = snd_pcm_open( + &handle, + name, + stream, + mode); + + if ( error < 0 ) + return 0; + + if ( (error = snd_pcm_hw_params_malloc(&hw_params)) < 0 ) { + snd_pcm_close(handle); + do_throw(error, name, "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"); + } + + if ( (error = snd_pcm_hw_params_set_format(handle, hw_params, format )) < 0 ) { + snd_pcm_close(handle); + do_throw(error, name, "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"); + } + + if ( (error = snd_pcm_hw_params_set_channels(handle, hw_params, channels )) < 0 ) { + snd_pcm_close(handle); + do_throw(error, name, "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"); + } + + if ( (error = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0)) < 0 ) { + snd_pcm_close(handle); + do_throw(error, name, "Disable resampling"); + } + + if ( (error = snd_pcm_hw_params_set_period_size(handle, hw_params, period_size, 0)) < 0 ) { + snd_pcm_close(handle); + do_throw(error, name, "Set I/O period size"); + } + + if ( (error = snd_pcm_hw_params_set_buffer_size(handle, hw_params, buffer_size)) < 0 ) { + snd_pcm_close(handle); + do_throw(error, name, "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"); + } + + snd_pcm_hw_params_free(hw_params); + + return handle; + } } diff --git a/freedv-server/source/platform/linux/audio_in_alsa.cpp b/freedv-server/source/platform/linux/audio_in_alsa.cpp index 0c8b9389..d40aad9d 100644 --- a/freedv-server/source/platform/linux/audio_in_alsa.cpp +++ b/freedv-server/source/platform/linux/audio_in_alsa.cpp @@ -1,10 +1,9 @@ /// The ALSA audio input driver. +#include "drivers.h" +#include "alsa.h" #include #include -#include "drivers.h" -#include -#include #include #include @@ -14,7 +13,7 @@ namespace FreeDV { /// Audio input "ALSA", Uses the Linux ALSA Audio API. class AudioInALSA : public AudioInput { private: - static const int overlong_delay = 300; + static const int overlong_delay = 10000; char * const parameters; snd_pcm_t * handle; @@ -27,7 +26,7 @@ namespace FreeDV { str << "Error on ALSA audio input " << parameters << ": "; if ( message ) str << message << ": "; - str << snd_strerror(error); + str << snd_strerror(error) << '.'; throw std::runtime_error(str.str().c_str()); } public: @@ -52,33 +51,18 @@ namespace FreeDV { 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; - const unsigned int rate = 48000; - const int soft_resample = 0; - const unsigned int latency = 0; int error; - handle = 0; - error = snd_pcm_open( - &handle, + handle = ALSASetup( p, SND_PCM_STREAM_CAPTURE, - 0); - - if ( error != 0 ) - do_throw(error, "Open"); - - if ( (error = snd_pcm_set_params( - handle, + 0, format, access, channels, - rate, - soft_resample, - latency)) < 0 ) - do_throw(error, "Set Parameters"); - - if ( (error = snd_pcm_prepare(handle)) < 0 ) - do_throw(error, "Prepare"); + SampleRate, + SampleRate / 100, + SampleRate / 10); snd_pcm_start(handle); } @@ -136,8 +120,6 @@ namespace FreeDV { // audio samples and a corresponding delay. This can also happen if the // incoming audio interface runs at a faster rate than the outgoing one. if ( delay >= overlong_delay && available > 0 ) { - int dropped = 0; - snd_pcm_drop(handle); snd_pcm_prepare(handle); snd_pcm_start(handle); @@ -153,7 +135,7 @@ namespace FreeDV { if ( error == -EPIPE ) { snd_pcm_recover(handle, error, 1); available = snd_pcm_avail_delay(handle, &available, &delay); - std::cerr << "ALSA input " << parameters << ": read underrun." << std::endl; + std::cerr << "ALSA input " << parameters << ": ready underrun." << std::endl; } if ( error >= 0 ) diff --git a/freedv-server/source/platform/linux/audio_out_alsa.cpp b/freedv-server/source/platform/linux/audio_out_alsa.cpp index 427141fe..85d9d88a 100644 --- a/freedv-server/source/platform/linux/audio_out_alsa.cpp +++ b/freedv-server/source/platform/linux/audio_out_alsa.cpp @@ -1,9 +1,9 @@ /// The ALSA audio output driver. +#include "alsa.h" #include #include #include "drivers.h" -#include #include #include #include @@ -25,7 +25,7 @@ namespace FreeDV { str << "Error on ALSA audio output " << parameters << ": "; if ( message ) str << message << ": "; - str << snd_strerror(error); + str << snd_strerror(error) << '.'; throw std::runtime_error(str.str().c_str()); } public: @@ -50,33 +50,17 @@ namespace FreeDV { 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; - const unsigned int rate = 48000; - const int soft_resample = 0; - const unsigned int latency = 10000; - int error; - - handle = 0; - error = snd_pcm_open( - &handle, + + handle = ALSASetup( p, SND_PCM_STREAM_PLAYBACK, - 0); - - if ( error != 0 ) - do_throw(error, "Open"); - - if ( (error = snd_pcm_set_params( - handle, + 0, format, access, channels, - rate, - soft_resample, - latency)) < 0 ) - do_throw(error, "Set Parameters"); - - if ( (error = snd_pcm_prepare(handle)) < 0 ) - do_throw(error, "Prepare"); + SampleRate, + SampleRate / 100, + SampleRate / 10); } AudioOutALSA::~AudioOutALSA() @@ -90,18 +74,17 @@ namespace FreeDV { std::size_t AudioOutALSA::write16(const std::int16_t * array, std::size_t length) { - int result = snd_pcm_writei(handle, array, length); - if ( result == -EPIPE ) { - snd_pcm_recover(handle, result, 1); - result = snd_pcm_writei(handle, array, length); + int error = snd_pcm_writei(handle, array, length); + if ( error == -EPIPE ) { std::cerr << "ALSA output " << parameters << ": write underrun." << std::endl; - if ( result == -EPIPE ) - return 0; + snd_pcm_recover(handle, error, 1); + error = snd_pcm_writei(handle, array, length); } - if ( result >= 0 ) - return result; + + if ( error >= 0 ) + return error; else { - do_throw(result, "Write"); + do_throw(error, "Write"); return 0; // do_throw doesn't return. } } @@ -126,17 +109,24 @@ namespace FreeDV { int error; 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); - available = snd_pcm_avail_delay(handle, &available, &delay); - std::cerr << "ALSA output " << parameters << ": write underrun." << std::endl; + snd_pcm_pause(handle, 1); + return 10; + if ( error < 0 ) + return 0; } + if ( error == 0 ) return available; else if ( error == -EPIPE ) return 0; else { - do_throw(available, "Get Frames Available for Write"); + do_throw(error, "Get Frames Available for Write"); return 0; // do_throw doesn't return. } } diff --git a/freedv-server/source/run.cpp b/freedv-server/source/run.cpp index 4bb7c46d..c023e34a 100644 --- a/freedv-server/source/run.cpp +++ b/freedv-server/source/run.cpp @@ -5,6 +5,7 @@ #include #include #include +#include /// FIX: /// @@ -33,11 +34,14 @@ namespace FreeDV { FIFO out_fifo; bool ptt_digital; bool ptt_ssb; + std::size_t min_frame_duration; + struct timespec last_frame_time; + struct timespec next_frame_time; void key_down(); void key_up(); void receive(); - void reset_fifos(); + void reset(); void transmit_digital(); void transmit_ssb(); public: @@ -52,6 +56,7 @@ namespace FreeDV { codec_fifo(TempSize * 2), in_fifo(TempSize * 2), out_fifo(TempSize * 2), ptt_digital(false), ptt_ssb(false) { + reset(); } Run::~Run() @@ -59,8 +64,9 @@ namespace FreeDV { } void - Run::reset_fifos() + Run::reset() { + clock_gettime(CLOCK_MONOTONIC, &last_frame_time); in_fifo.reset(); codec_fifo.reset(); out_fifo.reset(); @@ -94,6 +100,9 @@ namespace FreeDV { void Run::receive() { + struct timespec start_time; + clock_gettime(CLOCK_MONOTONIC, &start_time); + // Drain any data that the loudspeaker can take. const std::size_t out_samples = min( i->loudspeaker->ready(), @@ -161,16 +170,42 @@ namespace FreeDV { if ( bytes_to_decode > 0 ) codec_fifo.get_done(bytes_to_decode); - if ( result > 0 ) + // TODO: We can do clock tuning here to maximize power saving, if it + // results in any noticable gain. + if ( result > 0 ) { out_fifo.put_done(result * 2); + + last_frame_time = start_time; + const long duration = ((min_frame_duration - 1) * 1000000); + + next_frame_time.tv_sec = last_frame_time.tv_sec; + next_frame_time.tv_nsec = last_frame_time.tv_nsec + duration; + next_frame_time.tv_sec += next_frame_time.tv_nsec / 1000000000; + next_frame_time.tv_nsec %= 1000000000; + } + else { + // 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; + next_frame_time.tv_nsec %= 1000000000; + } } } void Run::run() { + // 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 = 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()); + + assert(min_frame_duration < 1000000); + while ( true ) { - usleep(1000); + // clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_frame_time, 0); receive(); } } -- 2.25.1