1
0
Fork 0

Add ALSA output.

Cleanups.
This commit is contained in:
Joris van Rantwijk 2013-12-29 00:25:58 +01:00
parent 6e08f8a55c
commit 0ec6475902
4 changed files with 125 additions and 36 deletions

View File

@ -5,6 +5,8 @@
#include <fcntl.h> #include <fcntl.h>
#include <algorithm> #include <algorithm>
#include <alsa/asoundlib.h>
#include "SoftFM.h" #include "SoftFM.h"
#include "AudioOutput.h" #include "AudioOutput.h"
@ -124,28 +126,88 @@ private:
// TODO // 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 #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 */

View File

@ -119,7 +119,9 @@ public:
bool write(const SampleVector& samples); bool write(const SampleVector& samples);
private: private:
// TODO unsigned int m_nchannels;
struct _snd_pcm * m_pcm;
std::vector<uint8_t> m_bytebuf;
}; };
#endif #endif

View File

@ -2,8 +2,6 @@
# ----- Tweak these settings to configure for your system # ----- Tweak these settings to configure for your system
# TODO : -D_FILE_OFFSET_BITS=64
CROSS = CROSS =
CFLAGS_OPT = -O2 -ffast-math -ftree-vectorize CFLAGS_OPT = -O2 -ffast-math -ftree-vectorize
CFLAGS_DEBUG = -g CFLAGS_DEBUG = -g
@ -12,6 +10,7 @@ CFLAGS_PATH = -I/home/joris/test/rtl-sdr/inst/include
CFLAGS_EXTRA = CFLAGS_EXTRA =
LDFLAGS_PATH = -L/home/joris/test/rtl-sdr/inst/lib LDFLAGS_PATH = -L/home/joris/test/rtl-sdr/inst/lib
LDFLAGS_EXTRA = LDFLAGS_EXTRA =
LIBS_ALSA = -lasound
LIBS_RTLSDR = /home/joris/test/rtl-sdr/inst/lib/librtlsdr.a -lusb-1.0 LIBS_RTLSDR = /home/joris/test/rtl-sdr/inst/lib/librtlsdr.a -lusb-1.0
LIBS_EXTRA = LIBS_EXTRA =
@ -19,10 +18,11 @@ LIBS_EXTRA =
CXX = $(CROSS)g++ 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) $(CFLAGS_ARCH) $(CFLAGS_PATH) $(CFLAGS_EXTRA)
LDFLAGS = $(LDFLAGS_PATH) $(LDFLAGS_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 OBJS = RtlSdrSource.o Filter.o FmDecode.o AudioOutput.o main.o

39
main.cc
View File

@ -171,6 +171,9 @@ void write_output_data(AudioOutput *output, DataBuffer<Sample> *buf,
// Get samples from buffer and write to output. // Get samples from buffer and write to output.
SampleVector samples = buf->pull(); SampleVector samples = buf->pull();
output->write(samples); 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 main(int argc, char **argv)
{ {
int c;
double freq = -1; double freq = -1;
int devidx = 0; int devidx = 0;
double ifrate = 1.0e6; double ifrate = 1.0e6;
int pcmrate = 48000; int pcmrate = 48000;
int stereo = 1; bool stereo = true;
enum OutputMode { MODE_RAW, MODE_WAV, MODE_ALSA }; enum OutputMode { MODE_RAW, MODE_WAV, MODE_ALSA };
OutputMode outmode = MODE_ALSA; OutputMode outmode = MODE_ALSA;
string filename; string filename;
@ -237,7 +239,22 @@ int main(int argc, char **argv)
fprintf(stderr, fprintf(stderr,
"SoftFM - Software decoder for FM broadcast radio with RTL-SDR\n"); "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) { switch (c) {
case 'f': case 'f':
if (!parse_opt(optarg, freq) || freq <= 0) { if (!parse_opt(optarg, freq) || freq <= 0) {
@ -260,7 +277,7 @@ int main(int argc, char **argv)
} }
break; break;
case 'M': case 'M':
stereo = 0; stereo = false;
break; break;
case 'R': case 'R':
outmode = MODE_RAW; outmode = MODE_RAW;
@ -377,9 +394,16 @@ int main(int argc, char **argv)
audio_output.reset(new RawAudioOutput(filename)); audio_output.reset(new RawAudioOutput(filename));
break; break;
case MODE_WAV: case MODE_WAV:
case MODE_ALSA:
// TODO
abort(); 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. // 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 I/Q imbalance to fix Radio4 noise
// TODO : investigate if PLL receiver is better than phase discriminator at weak reception // TODO : investigate if PLL receiver is better than phase discriminator at weak reception
// TODO : show mono/stereo (switching)
// TODO : show mono/stereo
fprintf(stderr, fprintf(stderr,
"\rblk=%6d freq=%8.4fMHz IF=%+5.1fdB BB=%+5.1fdB audio=%+5.1fdB ", "\rblk=%6d freq=%8.4fMHz IF=%+5.1fdB BB=%+5.1fdB audio=%+5.1fdB ",
block, block,
@ -461,6 +485,7 @@ int main(int argc, char **argv)
// Join background threads. // Join background threads.
source_thread.join(); source_thread.join();
if (outputbuf_samples > 0) { if (outputbuf_samples > 0) {
output_buffer.push_end();
output_thread.join(); output_thread.join();
} }