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 {
if ( !codec )
codec = Driver::CodecNoOp(empty);
+ if ( !framer )
+ framer = Driver::FramerNoOp(empty);
+
if ( !keying_output )
keying_output = Driver::KeyingSink(empty);
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;
+#include "alsa.h"
#include <iostream>
-#include <alsa/asoundlib.h>
#include <sstream>
+#include <stdexcept>
namespace FreeDV {
static std::ostream &
{
int card_index = -1;
snd_pcm_t * pcm_handle;
- snd_ctl_card_info_t * info = 0;
const int error = snd_pcm_open(
&pcm_handle,
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;
+ }
}
/// The ALSA audio input driver.
+#include "drivers.h"
+#include "alsa.h"
#include <stdlib.h>
#include <errno.h>
-#include "drivers.h"
-#include <alsa/asoundlib.h>
-#include <sys/ioctl.h>
#include <sstream>
#include <stdexcept>
/// 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;
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:
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);
}
// 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);
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 )
/// The ALSA audio output driver.
+#include "alsa.h"
#include <stdlib.h>
#include <errno.h>
#include "drivers.h"
-#include <alsa/asoundlib.h>
#include <sys/ioctl.h>
#include <sstream>
#include <stdexcept>
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:
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()
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.
}
}
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.
}
}
#include <iostream>
#include <fstream>
#include <string.h>
+#include <sys/time.h>
/// FIX:
///
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:
codec_fifo(TempSize * 2), in_fifo(TempSize * 2),
out_fifo(TempSize * 2), ptt_digital(false), ptt_ssb(false)
{
+ reset();
}
Run::~Run()
}
void
- Run::reset_fifos()
+ Run::reset()
{
+ clock_gettime(CLOCK_MONOTONIC, &last_frame_time);
in_fifo.reset();
codec_fifo.reset();
out_fifo.reset();
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(),
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();
}
}