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;
int
CodecNoOp::min_frame_duration() const
{
- return 1;
+ return 40;
}
Codec *
/// 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.
#include "drivers.h"
#include "alsa.h"
-#include <stdlib.h>
-#include <errno.h>
#include <sstream>
#include <stdexcept>
+#include <stdlib.h>
+#include <errno.h>
+#include <math.h>
namespace FreeDV {
std::ostream & ALSAEnumerate(std::ostream & stream, snd_pcm_stream_t mode);
/// 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)
};
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);
}
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);
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
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;
std::cerr << "ALSA input " << parameters << ": overlong delay, dropped "
<< seconds << " seconds of queued audio samples." << std::endl;
+
+ return 0;
}
if ( error == -EPIPE ) {
/// The ALSA audio output driver.
#include "alsa.h"
-#include <stdlib.h>
-#include <errno.h>
#include "drivers.h"
-#include <sys/ioctl.h>
#include <sstream>
#include <stdexcept>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <math.h>
namespace FreeDV {
std::ostream & ALSAEnumerate(std::ostream & stream, snd_pcm_stream_t mode);
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)
};
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()
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);
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;
}
class Run {
private:
- const std::size_t TempSize = 2048;
+ const std::size_t TempSize = 10240;
Interfaces * const i;
bool begin_receive;
bool begin_transmit;
(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;
}
(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;
}
// 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;
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;
{
// 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);
}
}