1 /*
2 * Copyright (C) 2002 - David W. Durham
3 *
4 * This file is part of ReZound, an audio editing application.
5 *
6 * ReZound is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published
8 * by the Free Software Foundation; either version 2 of the License,
9 * or (at your option) any later version.
10 *
11 * ReZound is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19 */
20
21 #include "CPortAudioSoundPlayer.h"
22
23 #ifdef ENABLE_PORTAUDIO
24
25 #include <stdio.h>
26 #include <string.h>
27
28 #include <stdexcept>
29
30 #include "settings.h"
31
32
33
CPortAudioSoundPlayer()34 CPortAudioSoundPlayer::CPortAudioSoundPlayer() :
35 ASoundPlayer(),
36
37 initialized(false),
38 stream(NULL),
39 supportsFullDuplex(true)
40 {
41 PaError err=Pa_Initialize();
42 if(err!=paNoError)
43 throw runtime_error(string(__func__)+" -- error initializing PortAudio -- "+Pa_GetErrorText(err));
44 }
45
~CPortAudioSoundPlayer()46 CPortAudioSoundPlayer::~CPortAudioSoundPlayer()
47 {
48 deinitialize();
49 Pa_Terminate();
50 }
51
isInitialized() const52 bool CPortAudioSoundPlayer::isInitialized() const
53 {
54 return initialized;
55 }
56
initialize()57 void CPortAudioSoundPlayer::initialize()
58 {
59 if(!initialized)
60 {
61 PaError err;
62
63 PaSampleFormat sampleFormat;
64 #if defined(SAMPLE_TYPE_S16)
65 sampleFormat=paInt16;
66 #elif defined(SAMPLE_TYPE_FLOAT)
67 sampleFormat=paFloat32;
68 #else
69 #error unhandled SAMPLE_TYPE_xxx define
70 #endif
71
72
73 // open a PortAudio stream
74 #ifdef ENABLE_PORTAUDIO_V19
75 PaStreamParameters output = { gPortAudioOutputDevice,
76 static_cast<int>(gDesiredOutputChannelCount),
77 sampleFormat,
78 Pa_GetDeviceInfo(gPortAudioOutputDevice)->defaultLowOutputLatency ,
79 NULL};
80
81 err = Pa_OpenStream(
82 &stream,
83 NULL,
84 &output,
85 gDesiredOutputSampleRate,
86 gDesiredOutputBufferSize * gDesiredOutputBufferCount,
87 paClipOff|paDitherOff,
88 CPortAudioSoundPlayer::PortAudioCallback,
89 this);
90
91 #else
92 err = Pa_OpenStream(
93 &stream,
94 paNoDevice, /* recording parameter, we're not recording */
95 0, /* recording parameter, we're not recording */
96 paInt16, /* recording parameter, we're not recording */
97 NULL,
98 gPortAudioOutputDevice,
99 gDesiredOutputChannelCount,
100 sampleFormat,
101 NULL,
102 gDesiredOutputSampleRate,
103 gDesiredOutputBufferSize,
104 gDesiredOutputBufferCount,
105 paClipOff|paDitherOff,
106 CPortAudioSoundPlayer::PortAudioCallback,
107 this);
108 #endif
109
110 if(err!=paNoError)
111 throw runtime_error(string(__func__)+" -- error opening PortAudio stream -- "+Pa_GetErrorText(err));
112 #warning test with some parameters that I know will fail
113
114 // ??? is PortAudio is not going to make the conversion if it cannot get this exact sample reate
115 // and right now, there is no way in libportaudio to get the sample rate that was obtained... So
116 // when they do implement being able to know at what sample rate the device was opened, I will query here
117 devices[0].sampleRate=gDesiredOutputSampleRate; // make note of the sample rate for this device (??? which is only device zero for now)
118 devices[0].channelCount=gDesiredOutputChannelCount; // make note of the number of channels for this device (??? which is only device zero for now)
119
120 // start the PortAudio stream
121 err=Pa_StartStream(stream);
122 if(err!=paNoError)
123 throw runtime_error(string(__func__)+" -- error starting PortAudio stream -- "+Pa_GetErrorText(err));
124
125 supportsFullDuplex=false;
126 /* ??? implement this when/if possible
127 supportsFullDuplex= query libportaudio if the device supports fullduplex mode ... perhaps write a method that runs prior to this point that tried to open the device with input and output parameters to Pa_OpenStream
128 */
129
130 ASoundPlayer::initialize();
131 initialized=true;
132 fprintf(stderr, "PortAudio player initialized\n");
133 }
134 else
135 throw runtime_error(string(__func__)+" -- already initialized");
136 }
137
deinitialize()138 void CPortAudioSoundPlayer::deinitialize()
139 {
140 if(initialized)
141 {
142 PaError err;
143
144 ASoundPlayer::deinitialize();
145
146 // stop the PortAudio stream
147 err=Pa_StopStream(stream);
148 if(err!=paNoError)
149 fprintf(stderr,"%s -- error starting PortAudio stream -- %s\n",__func__,Pa_GetErrorText(err));
150
151 // close PortAudio stream
152 err=Pa_CloseStream(stream);
153 if(err!=paNoError)
154 fprintf(stderr,"%s -- error closing PortAudio stream -- %s\n",__func__,Pa_GetErrorText(err));
155
156 stream=NULL;
157 initialized=false;
158 fprintf(stderr, "PortAudio player deinitialized\n");
159 }
160 }
161
aboutToRecord()162 void CPortAudioSoundPlayer::aboutToRecord()
163 {
164 // ??? Of course it would be nice to know if the record device about to be used is the same as this playback device...
165 // I would just use the port audio device and make it a parameter to this method, except port audio may not been the API being
166 // used... Perhaps what I should do is commit to a rule that if I'm using an API for playback, that I have to use the same API
167 // for recording.. then I could use a void * parameter to this method and I would know it's an int index into PortAudio's known devices
168 // BUT... if I commited to this rule, then perhaps it would make sense to just merge ASoundPlayer and ASoundRecorder together
169 // or maybe just into the same file so then only one file compiles and I could know that the device identification method was consistant
170 if(!supportsFullDuplex)
171 deinitialize();
172 }
173
doneRecording()174 void CPortAudioSoundPlayer::doneRecording()
175 {
176 if(!initialized && !supportsFullDuplex)
177 initialize();
178 }
179
180
181 #ifdef ENABLE_PORTAUDIO_V19
PortAudioCallback(const void * inputBuffer,void * outputBuffer,unsigned long framesPerBuffer,const PaStreamCallbackTimeInfo * outTime,PaStreamCallbackFlags statusFlags,void * userData)182 int CPortAudioSoundPlayer::PortAudioCallback(const void *inputBuffer,void *outputBuffer,unsigned long framesPerBuffer,const PaStreamCallbackTimeInfo* outTime, PaStreamCallbackFlags statusFlags, void *userData)
183 #else
184 int CPortAudioSoundPlayer::PortAudioCallback(void *inputBuffer,void *outputBuffer,unsigned long framesPerBuffer,PaTimestamp outTime,void *userData)
185 #endif
186 {
187 CPortAudioSoundPlayer* that = (CPortAudioSoundPlayer *)userData;
188 if(!that->initialized) {
189 return 0;
190 }
191
192 try
193 {
194 // no conversion necessary because we initialized it with the type of sample_t (if portaudio didn't support our sample_t type natively then we would need to do conversion here)
195 that->mixSoundPlayerChannels(gDesiredOutputChannelCount,(sample_t *)outputBuffer,framesPerBuffer);
196 }
197 catch(exception &e)
198 {
199 fprintf(stderr,"exception caught in play callback: %s\n",e.what());
200 }
201 catch(...)
202 {
203 fprintf(stderr,"unknown exception caught in play callback\n");
204 }
205 return 0;
206 }
207
208 #endif // ENABLE_PORTAUDIO
209