From 0ec6475902c1bba64450b0cde043add4c2d68637 Mon Sep 17 00:00:00 2001 From: Joris van Rantwijk Date: Sun, 29 Dec 2013 00:25:58 +0100 Subject: [PATCH] Add ALSA output. Cleanups. --- AudioOutput.cc | 110 ++++++++++++++++++++++++++++++++++++++----------- AudioOutput.h | 4 +- Makefile | 8 ++-- main.cc | 39 ++++++++++++++---- 4 files changed, 125 insertions(+), 36 deletions(-) diff --git a/AudioOutput.cc b/AudioOutput.cc index 28d1cd1..67be008 100644 --- a/AudioOutput.cc +++ b/AudioOutput.cc @@ -5,6 +5,8 @@ #include #include +#include + #include "SoftFM.h" #include "AudioOutput.h" @@ -124,28 +126,88 @@ private: // TODO }; - -/** Write audio data to ALSA device. */ -class AlsaAudioOutput -{ -public: - - /** - * Construct ALSA output stream. - * - * dename :: ALSA PCM device - * samplerate :: audio sample rate in Hz - * stereo :: true if the output stream contains stereo data - */ - AlsaAudioOutput(const std::string& devname, - unsigned int samplerate, - bool stereo); - - ~AlsaAudioOutput(); - bool write(const SampleVector& samples); - std::string error(); - -private: - // TODO -}; #endif + +/* **************** class AlsaAudioOutput **************** */ + +// Construct ALSA output stream. +AlsaAudioOutput::AlsaAudioOutput(const std::string& devname, + unsigned int samplerate, + bool stereo) +{ + m_pcm = NULL; + m_nchannels = stereo ? 2 : 1; + + int r = snd_pcm_open(&m_pcm, devname.c_str(), + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + + if (r < 0) { + m_error = "can not open PCM device '" + devname + "' (" + + strerror(-r) + ")"; + m_zombie = true; + return; + } + + snd_pcm_nonblock(m_pcm, 0); + + r = snd_pcm_set_params(m_pcm, + SND_PCM_FORMAT_S16_LE, + SND_PCM_ACCESS_RW_INTERLEAVED, + m_nchannels, + samplerate, + 1, // allow soft resampling + 500000); // latency in us + + if (r < 0) { + m_error = "can not set PCM parameters ("; + m_error += strerror(-r); + m_error += ")"; + m_zombie = true; + } +} + + +// Destructor. +AlsaAudioOutput::~AlsaAudioOutput() +{ + // Close device. + if (m_pcm != NULL) { + snd_pcm_close(m_pcm); + } +} + + +// Write audio data. +bool AlsaAudioOutput::write(const SampleVector& samples) +{ + if (m_zombie) + return false; + + // Convert samples to bytes. + samplesToInt16(samples, m_bytebuf); + + // Write data. + unsigned int p = 0; + unsigned int n = samples.size() / m_nchannels; + unsigned int framesize = 2 * m_nchannels; + while (p < n) { + + int k = snd_pcm_writei(m_pcm, + m_bytebuf.data() + p * framesize, n - p); + if (k < 0) { + m_error = "write failed ("; + m_error += strerror(errno); + m_error += ")"; + // After an underrun, ALSA keeps returning error codes until we + // explicitly fix the stream. + snd_pcm_recover(m_pcm, k, 0); + return false; + } else { + p += k; + } + } + + return true; +} + +/* end */ diff --git a/AudioOutput.h b/AudioOutput.h index c85dd51..0c71230 100644 --- a/AudioOutput.h +++ b/AudioOutput.h @@ -119,7 +119,9 @@ public: bool write(const SampleVector& samples); private: - // TODO + unsigned int m_nchannels; + struct _snd_pcm * m_pcm; + std::vector m_bytebuf; }; #endif diff --git a/Makefile b/Makefile index 7eafffb..e104c0d 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,6 @@ # ----- Tweak these settings to configure for your system -# TODO : -D_FILE_OFFSET_BITS=64 - CROSS = CFLAGS_OPT = -O2 -ffast-math -ftree-vectorize CFLAGS_DEBUG = -g @@ -12,6 +10,7 @@ CFLAGS_PATH = -I/home/joris/test/rtl-sdr/inst/include CFLAGS_EXTRA = LDFLAGS_PATH = -L/home/joris/test/rtl-sdr/inst/lib LDFLAGS_EXTRA = +LIBS_ALSA = -lasound LIBS_RTLSDR = /home/joris/test/rtl-sdr/inst/lib/librtlsdr.a -lusb-1.0 LIBS_EXTRA = @@ -19,10 +18,11 @@ LIBS_EXTRA = CXX = $(CROSS)g++ -CXXFLAGS = -std=c++11 -Wall $(CFLAGS_OPT) $(CFLAGS_DEBUG) \ +CXXFLAGS = -std=c++11 -Wall -D_FILE_OFFSET_BITS=64 \ + $(CFLAGS_OPT) $(CFLAGS_DEBUG) \ $(CFLAGS_ARCH) $(CFLAGS_PATH) $(CFLAGS_EXTRA) LDFLAGS = $(LDFLAGS_PATH) $(LDFLAGS_EXTRA) -LDLIBS = $(LIBS_RTLSDR) $(LIBS_EXTRA) +LDLIBS = $(LIBS_ALSA) $(LIBS_RTLSDR) $(LIBS_EXTRA) OBJS = RtlSdrSource.o Filter.o FmDecode.o AudioOutput.o main.o diff --git a/main.cc b/main.cc index 666c855..20854fd 100644 --- a/main.cc +++ b/main.cc @@ -171,6 +171,9 @@ void write_output_data(AudioOutput *output, DataBuffer *buf, // Get samples from buffer and write to output. SampleVector samples = buf->pull(); output->write(samples); + if (!(*output)) { + fprintf(stderr, "ERROR: AudioOutput: %s\n", output->error().c_str()); + } } } @@ -222,12 +225,11 @@ bool parse_opt(const char *s, double& v) int main(int argc, char **argv) { - int c; double freq = -1; int devidx = 0; double ifrate = 1.0e6; int pcmrate = 48000; - int stereo = 1; + bool stereo = true; enum OutputMode { MODE_RAW, MODE_WAV, MODE_ALSA }; OutputMode outmode = MODE_ALSA; string filename; @@ -237,7 +239,22 @@ int main(int argc, char **argv) fprintf(stderr, "SoftFM - Software decoder for FM broadcast radio with RTL-SDR\n"); - while ((c = getopt(argc, argv, "f:d:s:r:MR:W:P::b:")) >= 0) { + const struct option longopts[] = { + { "freq", 1, NULL, 'f' }, + { "dev", 1, NULL, 'd' }, + { "ifrate", 1, NULL, 's' }, + { "pcmrate", 1, NULL, 'r' }, + { "mono", 0, NULL, 'M' }, + { "raw", 1, NULL, 'R' }, + { "wav", 1, NULL, 'W' }, + { "play", 2, NULL, 'P' }, + { "buffer", 1, NULL, 'b' }, + { NULL, 0, NULL, 0 } }; + + int c, longindex; + while ((c = getopt_long(argc, argv, + "f:d:s:r:MR:W:P::b:", + longopts, &longindex)) >= 0) { switch (c) { case 'f': if (!parse_opt(optarg, freq) || freq <= 0) { @@ -260,7 +277,7 @@ int main(int argc, char **argv) } break; case 'M': - stereo = 0; + stereo = false; break; case 'R': outmode = MODE_RAW; @@ -377,9 +394,16 @@ int main(int argc, char **argv) audio_output.reset(new RawAudioOutput(filename)); break; case MODE_WAV: - case MODE_ALSA: - // TODO abort(); + case MODE_ALSA: + audio_output.reset(new AlsaAudioOutput(devname, pcmrate, stereo)); + break; + } + + if (!(*audio_output)) { + fprintf(stderr, "ERROR: AudioOutput: %s\n", + audio_output->error().c_str()); + exit(1); } // If buffering enabled, start background output thread. @@ -424,8 +448,8 @@ int main(int argc, char **argv) // TODO : investigate I/Q imbalance to fix Radio4 noise // TODO : investigate if PLL receiver is better than phase discriminator at weak reception +// TODO : show mono/stereo (switching) -// TODO : show mono/stereo fprintf(stderr, "\rblk=%6d freq=%8.4fMHz IF=%+5.1fdB BB=%+5.1fdB audio=%+5.1fdB ", block, @@ -461,6 +485,7 @@ int main(int argc, char **argv) // Join background threads. source_thread.join(); if (outputbuf_samples > 0) { + output_buffer.push_end(); output_thread.join(); }