/// 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:
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;
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.
#include <iostream>
#include <sstream>
#include <stdexcept>
+#include <string.h>
namespace FreeDV {
static std::ostream &
const int error = snd_pcm_open(
&pcm_handle,
"default",
- SND_PCM_STREAM_PLAYBACK,
+ mode,
0);
stream << "\"alsa:default\"";
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);
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;
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) << '.';
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);
-/// The ALSA audio input driver.
+// The ALSA audio input driver.
#include "drivers.h"
#include "alsa.h"
/// 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;
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);
}
if ( !started ) {
snd_pcm_start(handle);
- return ((double)SampleRate / 1000.0) * AudioFrameDuration;
+ started = true;
+ return AudioFrameSamples;
}
error = snd_pcm_avail_delay(handle, &available, &delay);
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.
-/// The ALSA audio output driver.
+// The ALSA audio output driver.
#include "alsa.h"
#include "drivers.h"
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()
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 ) {
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
<< 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 ) {
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.
#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;
+ }
}
}
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();
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();
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);
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(
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;
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");
}
}
std::size_t
Tone::ready()
{
- return SIZE_MAX;
+ return AudioFrameSamples;
}
float