parent
6e08f8a55c
commit
0ec6475902
110
AudioOutput.cc
110
AudioOutput.cc
|
@ -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 */
|
||||||
|
|
|
@ -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
|
||||||
|
|
8
Makefile
8
Makefile
|
@ -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
39
main.cc
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue