Release version 0.99.2-1
[fmit.git] / src / CaptureThreadImplPortAudio.cpp
1 // Copyright 2004 "Gilles Degottex"
2
3 // This file is part of "Music"
4
5 // "Music" is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU Lesser General Public License as published by
7 // the Free Software Foundation; either version 2.1 of the License, or
8 // (at your option) any later version.
9 //
10 // "Music" is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
19
20 #include "CaptureThread.h"
21
22 #include <cassert>
23 #include <time.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <iostream>
28 #include <vector>
29 using namespace std;
30
31 // ------------------------------ PortAudio implementation ----------------------------
32 #ifdef CAPTURE_PORTAUDIO
33 CaptureThreadImplPortAudio::CaptureThreadImplPortAudio(CaptureThread* capture_thread)
34         : CaptureThreadImpl(capture_thread, "PortAudio", QString("Portable cross-platform Audio API (lib:")+Pa_GetVersionText()+"["+QString::number(Pa_GetVersion())+"])")
35 {
36         m_stream = NULL;
37
38         m_source = "default";
39 }
40
41 bool CaptureThreadImplPortAudio::is_available()
42 {
43         if(!m_stream)
44         {
45                 try
46                 {
47                         m_err = Pa_Initialize();
48                         if(m_err != paNoError)  throw QString("PortAudio: is_available:Pa_Initialize ")+Pa_GetErrorText(m_err);
49
50                         int numDevices;
51
52                         numDevices = Pa_GetDeviceCount();
53                         if(numDevices < 0)
54                                 throw QString("PortAudio: is_available:Pa_GetDeviceCount ")+Pa_GetErrorText(numDevices);
55                         else if(numDevices == 0)
56                                 throw QString("PortAudio: is_available:Pa_GetDeviceCount no devices available")+Pa_GetErrorText(numDevices);
57
58 /*                      const   PaDeviceInfo *deviceInfo;
59
60                         for(int i=0; i<numDevices; i++ )
61                         {
62                                 deviceInfo = Pa_GetDeviceInfo( i );
63                                 cerr << deviceInfo->name << endl;
64                                 cerr << deviceInfo->defaultSampleRate << endl;
65                         }*/
66                 }
67                 catch(QString error)
68                 {
69                         Pa_Terminate();
70                         m_stream = NULL;
71                         m_status = "N/A";
72                         return false;
73                 }
74                 capture_finished();
75         }
76
77         m_status = "OK";
78
79         return true;
80 }
81
82 void CaptureThreadImplPortAudio::setSamplingRate(int value)
83 {
84 //      cerr << "CaptureThreadImplPortAudio::setSamplingRate " << value << endl;
85
86         assert(value>0 || value==CaptureThread::SAMPLING_RATE_MAX);
87
88         if(m_sampling_rate!=value || value==CaptureThread::SAMPLING_RATE_MAX)
89         {
90                 bool was_running = m_capture_thread->isCapturing();
91                 if(was_running) m_capture_thread->stopCapture();
92
93                 m_sampling_rate = value;
94
95                 if(m_sampling_rate==CaptureThread::SAMPLING_RATE_MAX)
96                 {
97                         try
98                         {
99                                 set_params(true);
100                         }
101                         catch(QString error)
102                         {
103                                 cerr << "CaptureThread: ERROR: " << error.toStdString() << endl;
104                                 m_capture_thread->emitError(error);
105                         }
106
107                         try{
108                                 // it was just for testing
109                                 capture_finished();
110                         }
111                         catch(QString error)
112                         {
113                                 cerr << "CaptureThread: ERROR: " << error.toStdString() << endl;
114                                 m_capture_thread->emitError(error);
115                         }
116                 }
117                 else
118                         m_capture_thread->emitSamplingRateChanged();
119
120                 if(was_running) m_capture_thread->startCapture();
121         }
122
123 //      cerr << "~CaptureThreadImplPortAudio::setSamplingRate" << endl;
124 }
125
126 int CaptureThreadImplPortAudio::PortAudioCallback( const void *inputBuffer, void *outputBuffer,
127                                                                   unsigned long framesPerBuffer,
128                                                                   const PaStreamCallbackTimeInfo* timeInfo,
129                                                                   PaStreamCallbackFlags statusFlags,
130                                                                   void *userData )
131 {return ((CaptureThreadImplPortAudio*)userData)->portAudioCallback(inputBuffer, framesPerBuffer, timeInfo, statusFlags);}
132 int CaptureThreadImplPortAudio::portAudioCallback(const void *inputBuffer,
133                                                 unsigned long framesPerBuffer,
134                                                 const PaStreamCallbackTimeInfo* timeInfo,
135                                                 PaStreamCallbackFlags statusFlags)
136 {
137         if(m_capture_thread->m_pause)
138                 return 0;
139
140         m_capture_thread->m_lock.lock();
141
142         float *in = (float*)inputBuffer;
143
144         for(unsigned long i=0; i<framesPerBuffer; i++)
145                 m_capture_thread->m_values.push_front(*in++);
146 //      addValue(*in++, i, m_channel_count);                            // TODO
147
148         m_capture_thread->m_packet_size = framesPerBuffer;
149         if(m_capture_thread->m_ext_lock)
150         {
151                 m_capture_thread->m_packet_size_sll = 0;
152                 m_capture_thread->m_ext_lock = false;
153         }
154         m_capture_thread->m_packet_size_sll += framesPerBuffer;
155
156         m_capture_thread->m_lock.unlock();
157
158         return 0;
159 }
160
161 void CaptureThreadImplPortAudio::set_params(bool test)
162 {
163         m_err = Pa_Initialize();
164         if(m_err != paNoError)  throw QString("PortAudio: set_params:Pa_Initialize ")+Pa_GetErrorText(m_err);
165
166         PaStreamParameters params;
167         params.device = paNoDevice;
168         params.channelCount = 1;
169         params.sampleFormat = paFloat32;
170         params.suggestedLatency = 0;
171         params.hostApiSpecificStreamInfo = NULL;
172
173         if(m_source!="default") // TODO hum hum
174         {
175                 int     numDevices = Pa_GetDeviceCount();
176                 const PaDeviceInfo* deviceInfo;
177                 for(int i=0; params.device==paNoDevice && i<numDevices; i++)
178                 {
179                         deviceInfo = Pa_GetDeviceInfo(i);
180                         if(QString(deviceInfo->name)==m_source)
181                                 params.device = i;
182                 }
183
184                 if(params.device==paNoDevice)
185                         cerr << "CaptureThread: INFO: PortAudio: cannot determine selected source \"" << m_source.toStdString() << "\"" << endl;
186         }
187
188         if(!test)
189         {
190                 if(params.device==paNoDevice)
191                         cerr << "CaptureThread: INFO: PortAudio: using default device" << endl;
192                 else
193                         cerr << "CaptureThread: INFO: PortAudio: using \"" << m_source.toStdString() << "\"" << endl;
194
195                 setFormatDescrsAndFns(4, true, true, 1); // TODO do something if nbchannel=1 not supported
196         }
197
198         if(m_sampling_rate==CaptureThread::SAMPLING_RATE_MAX || m_sampling_rate==CaptureThread::SAMPLING_RATE_UNKNOWN)
199         {
200                 int old_sampling_rate = m_sampling_rate;
201
202                 cerr << "CaptureThread: INFO: PortAudio: sampling rate set to max or undefined, try to determinate it." << endl;
203
204                 list<int> sampling_rates;
205                 sampling_rates.push_front(8000);        sampling_rates.push_front(11025);       sampling_rates.push_front(16000);
206                 sampling_rates.push_front(22050);       sampling_rates.push_front(24000);       sampling_rates.push_front(32000);
207                 sampling_rates.push_front(44100);
208
209                 m_err = -1;
210                 while(m_err!=paNoError)
211                 {
212                         if(sampling_rates.empty())
213                                 throw QString("PortAudio: cannot set any sample rate (")+Pa_GetErrorText(m_err)+")";
214
215                         m_err = Pa_Initialize();
216                         if(m_err != paNoError)  throw QString("PortAudio: set_params:Pa_Initialize ")+Pa_GetErrorText(m_err);
217
218                         m_sampling_rate = sampling_rates.front();
219                         cerr << "CaptureThread: INFO: PortAudio: try sampling rate " << m_sampling_rate << " ..." << flush;
220
221 //                      cerr << "nbc1 " << params.channelCount << endl;
222
223                         if(params.device==paNoDevice)
224                                 m_err = Pa_OpenDefaultStream(&m_stream, 1, 0, paFloat32, m_sampling_rate, 0, PortAudioCallback, this);
225                         else
226                                 m_err = Pa_OpenStream(&m_stream, &params, NULL, m_sampling_rate, 0, paNoFlag, PortAudioCallback, this);
227
228                         if(m_err != paNoError)  cerr << " failed" << endl;
229                         else                                    cerr << " success" << endl;
230
231                         sampling_rates.pop_front();
232                 }
233
234                 if(old_sampling_rate!=m_sampling_rate)
235                         m_capture_thread->emitSamplingRateChanged();
236         }
237         else
238         {
239 //              cerr << "nbc2 " << params.channelCount << endl;
240 //              cerr << "dev2 " << params.device << "/" << paNoDevice << endl;
241
242                 if(params.device==paNoDevice)
243                 {
244                         m_err = Pa_OpenDefaultStream(&m_stream, 1, 0, paFloat32, m_sampling_rate, 0, PortAudioCallback, this);
245                         if(m_err != paNoError)
246                                 throw QString("PortAudio: set_params:Pa_OpenDefaultStream ")+Pa_GetErrorText(m_err);
247                 }
248                 else
249                 {
250                         m_err = Pa_OpenStream(&m_stream, &params, NULL, m_sampling_rate, 0, paNoFlag, PortAudioCallback, this);
251                         if(m_err != paNoError)
252                                 throw QString("PortAudio: set_params:Pa_OpenStream ")+Pa_GetErrorText(m_err);
253                 }
254         }
255 }
256
257 void CaptureThreadImplPortAudio::capture_init()
258 {
259         set_params(false);
260
261         m_err = Pa_StartStream(m_stream);
262         if(m_err != paNoError)
263                 throw QString("PortAudio: capture_init:Pa_StartStream ")+Pa_GetErrorText(m_err);
264
265         m_capture_thread->m_capturing = true;
266         m_capture_thread->emitCaptureStarted();
267         m_capture_thread->emitCaptureToggled(true);
268 }
269 void CaptureThreadImplPortAudio::capture_finished()
270 {
271         if(m_stream)
272         {
273                 if(!Pa_IsStreamStopped(m_stream))
274                 {
275                         m_err = Pa_StopStream(m_stream);
276                         if(m_err != paNoError)  throw QString("PortAudio: capture_finished: ")+Pa_GetErrorText(m_err);
277                 }
278
279                 if(m_stream)
280                 {
281                         m_err = Pa_CloseStream(m_stream);
282                         if(m_err != paNoError)  throw QString("PortAudio: capture_finished: ")+Pa_GetErrorText(m_err);
283                 }
284
285                 m_stream = NULL;
286
287                 m_capture_thread->m_capturing = false;
288                 m_capture_thread->emitCaptureStoped();
289                 m_capture_thread->emitCaptureToggled(false);
290         }
291
292         m_err = Pa_Terminate();
293 //      if(m_err != paNoError)  throw QString("PortAudio: capture_finished: ")+Pa_GetErrorText(m_err);
294 }
295 void CaptureThreadImplPortAudio::startCapture()
296 {
297         try
298         {
299                 capture_init();
300         }
301         catch(QString error)
302         {
303                 capture_finished();
304                 cerr << "CaptureThread: ERROR: " << error.toStdString() << endl;
305                 m_capture_thread->emitError(error);
306         }
307 }
308 void CaptureThreadImplPortAudio::stopCapture()
309 {
310         try
311         {
312                 capture_finished();
313         }
314         catch(QString error)
315         {
316                 cerr << "CaptureThread: ERROR: " << error.toStdString() << endl;
317                 m_capture_thread->emitError(error);
318         }
319 }
320 CaptureThreadImplPortAudio::~CaptureThreadImplPortAudio()
321 {
322         stopCapture();
323 }
324
325 #endif