1 #ifndef __SDR_PORTAUDIO_HH__
2 #define __SDR_PORTAUDIO_HH__
3 
4 #include <portaudio.h>
5 
6 #include "buffer.hh"
7 #include "node.hh"
8 #include "config.hh"
9 #include "logger.hh"
10 
11 namespace sdr {
12 
13 /** "Namespace" to collect all static, PortAudio related functions. */
14 class PortAudio {
15 public:
16   /** Initializes the PortAudio system, must be called first. */
17   static void init();
18   /** Shutdown. */
19   static void terminate();
20 
21   /** Returns the number of devices available. */
22   static int numDevices();
23   /** Returns the index of the default input device. */
24   static int defaultInputDevice();
25   /** Returns the index of the default output device. */
26   static int defaultOutputDevice();
27   /** Returns @c true of the given device provides an input chanel. */
28   static bool hasInputStream(int idx);
29   /** Returns @c true of the given device provides an output chanel. */
30   static bool hasOutputStream(int idx);
31   /** Returns the device name. */
32   static std::string deviceName(int idx);
33 };
34 
35 
36 /** PortAudio playback node. */
37 class PortSink: public SinkBase
38 {
39 public:
40   /** Constructor. */
41   PortSink();
42   /** Destructor. */
43   virtual ~PortSink();
44 
45   /** Configures the PortAudio output. */
46   virtual void config(const Config &src_cfg);
47   /** Playback. */
48   virtual void handleBuffer(const RawBuffer &buffer, bool allow_overwrite);
49 
50 protected:
51   /** The PortAudio stream. */
52   PaStream *_stream;
53   /** The frame-size. */
54   size_t _frame_size;
55 };
56 
57 
58 /** PortAudio input stream as a @c Source. */
59 template <class Scalar>
60 class PortSource: public Source
61 {
62 public:
63   /** Constructor. */
PortSource(double sampleRate,size_t bufferSize,int dev=-1)64   PortSource(double sampleRate, size_t bufferSize, int dev=-1):
65     Source(), _streamIsOpen(false), _stream(0), _sampleRate(sampleRate), _is_real(true)
66   {
67     // Allocate buffer
68     _buffer = Buffer<Scalar>(bufferSize);
69     _initializeStream(dev);
70   }
71 
72   /** Destructor. */
~PortSource()73   virtual ~PortSource() { if (0 != _stream) { Pa_CloseStream(_stream); } }
74 
75   /** Reads (blocking) the next buffer from the PortAudio stream. This function can be
76    * connected to the idle event of the @c Queue. */
next()77   void next() {
78     /// @todo Signal loss of samples in debug mode.
79     /// @bug Drop data if output buffer is in use.
80     Pa_ReadStream(_stream, _buffer.ptr(), _buffer.size());
81     this->send(_buffer);
82   }
83 
84   /** Returns the currently selected input device. */
deviceIndex() const85   int deviceIndex() const { return _deviceIndex; }
86 
87   /** Selects the input device, throws a @c ConfigError exception if the device can not
88    * be setup as an input device. Do not call this function, while the @c Queue is running. */
setDeviceIndex(int idx=-1)89   void setDeviceIndex(int idx=-1) {
90     _initializeStream(idx);
91   }
92 
93   /** Checks if the given sample rate is supported by the device. */
hasSampleRate(double sampleRate)94   bool hasSampleRate(double sampleRate) {
95     PaStreamParameters params;
96     params.device = _deviceIndex;
97     params.channelCount = _is_real ? 1 : 2;
98     params.sampleFormat = _fmt;
99     params.hostApiSpecificStreamInfo = 0;
100     return paFormatIsSupported == Pa_IsFormatSupported(&params, 0, sampleRate);
101   }
102 
103   /** Resets the sample rate of the input device. Do not call this function, while the
104    * @c Queue is running. */
setSampleRate(double sampleRate)105   void setSampleRate(double sampleRate) {
106     _sampleRate = sampleRate;
107     _initializeStream(_deviceIndex);
108   }
109 
110 protected:
111   /** Device setup. */
_initializeStream(int idx=-1)112   void _initializeStream(int idx=-1) {
113     // Stop and close stream if running
114     if (_streamIsOpen) {
115       Pa_StopStream(_stream); Pa_CloseStream(_stream); _streamIsOpen = false;
116     }
117 
118     // Determine input stream format by scalar type
119     switch (Config::typeId<Scalar>()) {
120     case Config::Type_f32:
121       _fmt = paFloat32;
122       _is_real = true;
123       break;
124     case Config::Type_u16:
125     case Config::Type_s16:
126       _fmt = paInt16;
127       _is_real = true;
128       break;
129     case Config::Type_cf32:
130       _fmt = paFloat32;
131       _is_real = false;
132       break;
133     case Config::Type_cu16:
134     case Config::Type_cs16:
135       _fmt = paInt16;
136       _is_real = false;
137       break;
138     default:
139       ConfigError err;
140       err << "Can not configure PortAudio sink: Unsupported format " << Config::typeId<Scalar>()
141           << " must be one of " << Config::Type_u16 << ", " << Config::Type_s16
142           << ", " << Config::Type_f32 << ", " << Config::Type_cu16 << ", " << Config::Type_cs16
143           << " or " << Config::Type_cf32 << ".";
144       throw err;
145     }
146 
147     // Assemble config:
148     int numCh = _is_real ? 1 : 2;
149     _deviceIndex = idx < 0 ? Pa_GetDefaultInputDevice() : idx;
150     PaStreamParameters params;
151     params.device = _deviceIndex;
152     params.channelCount = numCh;
153     params.sampleFormat = _fmt;
154     params.suggestedLatency = Pa_GetDeviceInfo(_deviceIndex)->defaultHighInputLatency;
155     params.hostApiSpecificStreamInfo = 0;
156 
157     // open stream
158     PaError err = Pa_OpenStream(&_stream, &params, 0, _sampleRate, _buffer.size(), 0, 0, 0);
159 
160     // Check for errors
161     if (0 > err) {
162       ConfigError err;
163       err << "Can not open PortAudio input stream!"; throw err;
164     }
165     // Mark stream as open
166     _streamIsOpen = true;
167     // Start stream
168     Pa_StartStream(_stream);
169 
170     LogMessage msg(LOG_DEBUG);
171     msg << "Configure PortAudio source: " << std::endl
172         << " sample rate " << _sampleRate  << std::endl
173         << " buffer size " << _buffer.size() << std::endl
174         << " format " << Config::typeId<Scalar>() << std::endl
175         << " num chanels " << numCh;
176     Logger::get().log(msg);
177 
178     // Set config
179     this->setConfig(Config(Config::typeId<Scalar>(), _sampleRate, _buffer.size(), 1));
180   }
181 
182 
183 protected:
184   /** If true, the PortAudio stream, @c _stream is open. */
185   bool _streamIsOpen;
186   /** The PortAudio input stream. */
187   PaStream *_stream;
188   /** The current format of the PortAudio stream. */
189   PaSampleFormat _fmt;
190   /** The current sample rate. */
191   double _sampleRate;
192   /** The current input device index. */
193   int _deviceIndex;
194   /** If @c true, the input stream is real (1 chanel), otherwise complex (2 chanels). */
195   bool _is_real;
196   /** The output buffer. */
197   Buffer<Scalar> _buffer;
198 };
199 
200 }
201 
202 #endif // __SDR_PORTAUDIO_HH__
203