1 | //////////////////////////////////////////////////////////// |
2 | // |
3 | // SFML - Simple and Fast Multimedia Library |
4 | // Copyright (C) 2007-2020 Laurent Gomila (laurent@sfml-dev.org) |
5 | // |
6 | // This software is provided 'as-is', without any express or implied warranty. |
7 | // In no event will the authors be held liable for any damages arising from the use of this software. |
8 | // |
9 | // Permission is granted to anyone to use this software for any purpose, |
10 | // including commercial applications, and to alter it and redistribute it freely, |
11 | // subject to the following restrictions: |
12 | // |
13 | // 1. The origin of this software must not be misrepresented; |
14 | // you must not claim that you wrote the original software. |
15 | // If you use this software in a product, an acknowledgment |
16 | // in the product documentation would be appreciated but is not required. |
17 | // |
18 | // 2. Altered source versions must be plainly marked as such, |
19 | // and must not be misrepresented as being the original software. |
20 | // |
21 | // 3. This notice may not be removed or altered from any source distribution. |
22 | // |
23 | //////////////////////////////////////////////////////////// |
24 | |
25 | //////////////////////////////////////////////////////////// |
26 | // Headers |
27 | //////////////////////////////////////////////////////////// |
28 | #include <SFML/Audio/SoundRecorder.hpp> |
29 | #include <SFML/Audio/AudioDevice.hpp> |
30 | #include <SFML/Audio/ALCheck.hpp> |
31 | #include <SFML/System/Sleep.hpp> |
32 | #include <SFML/System/Err.hpp> |
33 | #include <cstring> |
34 | #include <cassert> |
35 | |
36 | #ifdef _MSC_VER |
37 | #pragma warning(disable: 4355) // 'this' used in base member initializer list |
38 | #endif |
39 | |
40 | |
41 | namespace |
42 | { |
43 | ALCdevice* captureDevice = NULL; |
44 | } |
45 | |
46 | namespace sf |
47 | { |
48 | //////////////////////////////////////////////////////////// |
49 | SoundRecorder::SoundRecorder() : |
50 | m_thread (&SoundRecorder::record, this), |
51 | m_sampleRate (0), |
52 | m_processingInterval(milliseconds(100)), |
53 | m_isCapturing (false), |
54 | m_deviceName (getDefaultDevice()), |
55 | m_channelCount (1) |
56 | { |
57 | |
58 | } |
59 | |
60 | |
61 | //////////////////////////////////////////////////////////// |
62 | SoundRecorder::~SoundRecorder() |
63 | { |
64 | // This assertion is triggered if the recording is still running while |
65 | // the object is destroyed. It ensures that stop() is called in the |
66 | // destructor of the derived class, which makes sure that the recording |
67 | // thread finishes before the derived object is destroyed. Otherwise a |
68 | // "pure virtual method called" exception is triggered. |
69 | assert(!m_isCapturing && "You must call stop() in the destructor of your derived class, so that the recording thread finishes before your object is destroyed." ); |
70 | } |
71 | |
72 | |
73 | //////////////////////////////////////////////////////////// |
74 | bool SoundRecorder::start(unsigned int sampleRate) |
75 | { |
76 | // Check if the device can do audio capture |
77 | if (!isAvailable()) |
78 | { |
79 | err() << "Failed to start capture: your system cannot capture audio data (call SoundRecorder::isAvailable to check it)" << std::endl; |
80 | return false; |
81 | } |
82 | |
83 | // Check that another capture is not already running |
84 | if (captureDevice) |
85 | { |
86 | err() << "Trying to start audio capture, but another capture is already running" << std::endl; |
87 | return false; |
88 | } |
89 | |
90 | // Determine the recording format |
91 | ALCenum format = (m_channelCount == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; |
92 | |
93 | // Open the capture device for capturing 16 bits samples |
94 | captureDevice = alcCaptureOpenDevice(m_deviceName.c_str(), sampleRate, format, sampleRate); |
95 | if (!captureDevice) |
96 | { |
97 | err() << "Failed to open the audio capture device with the name: " << m_deviceName << std::endl; |
98 | return false; |
99 | } |
100 | |
101 | // Clear the array of samples |
102 | m_samples.clear(); |
103 | |
104 | // Store the sample rate |
105 | m_sampleRate = sampleRate; |
106 | |
107 | // Notify derived class |
108 | if (onStart()) |
109 | { |
110 | // Start the capture |
111 | alcCaptureStart(captureDevice); |
112 | |
113 | // Start the capture in a new thread, to avoid blocking the main thread |
114 | m_isCapturing = true; |
115 | m_thread.launch(); |
116 | |
117 | return true; |
118 | } |
119 | |
120 | return false; |
121 | } |
122 | |
123 | |
124 | //////////////////////////////////////////////////////////// |
125 | void SoundRecorder::stop() |
126 | { |
127 | // Stop the capturing thread if there is one |
128 | if (m_isCapturing) |
129 | { |
130 | m_isCapturing = false; |
131 | m_thread.wait(); |
132 | |
133 | // Notify derived class |
134 | onStop(); |
135 | } |
136 | } |
137 | |
138 | |
139 | //////////////////////////////////////////////////////////// |
140 | unsigned int SoundRecorder::getSampleRate() const |
141 | { |
142 | return m_sampleRate; |
143 | } |
144 | |
145 | |
146 | //////////////////////////////////////////////////////////// |
147 | std::vector<std::string> SoundRecorder::getAvailableDevices() |
148 | { |
149 | std::vector<std::string> deviceNameList; |
150 | |
151 | const ALchar* deviceList = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER); |
152 | if (deviceList) |
153 | { |
154 | while (*deviceList) |
155 | { |
156 | deviceNameList.push_back(deviceList); |
157 | deviceList += std::strlen(deviceList) + 1; |
158 | } |
159 | } |
160 | |
161 | return deviceNameList; |
162 | } |
163 | |
164 | |
165 | //////////////////////////////////////////////////////////// |
166 | std::string SoundRecorder::getDefaultDevice() |
167 | { |
168 | return alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); |
169 | } |
170 | |
171 | |
172 | //////////////////////////////////////////////////////////// |
173 | bool SoundRecorder::setDevice(const std::string& name) |
174 | { |
175 | // Store the device name |
176 | if (name.empty()) |
177 | m_deviceName = getDefaultDevice(); |
178 | else |
179 | m_deviceName = name; |
180 | |
181 | if (m_isCapturing) |
182 | { |
183 | // Stop the capturing thread |
184 | m_isCapturing = false; |
185 | m_thread.wait(); |
186 | |
187 | // Determine the recording format |
188 | ALCenum format = (m_channelCount == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; |
189 | |
190 | // Open the requested capture device for capturing 16 bits samples |
191 | captureDevice = alcCaptureOpenDevice(m_deviceName.c_str(), m_sampleRate, format, m_sampleRate); |
192 | if (!captureDevice) |
193 | { |
194 | // Notify derived class |
195 | onStop(); |
196 | |
197 | err() << "Failed to open the audio capture device with the name: " << m_deviceName << std::endl; |
198 | return false; |
199 | } |
200 | |
201 | // Start the capture |
202 | alcCaptureStart(captureDevice); |
203 | |
204 | // Start the capture in a new thread, to avoid blocking the main thread |
205 | m_isCapturing = true; |
206 | m_thread.launch(); |
207 | } |
208 | |
209 | return true; |
210 | } |
211 | |
212 | |
213 | //////////////////////////////////////////////////////////// |
214 | const std::string& SoundRecorder::getDevice() const |
215 | { |
216 | return m_deviceName; |
217 | } |
218 | |
219 | |
220 | //////////////////////////////////////////////////////////// |
221 | void SoundRecorder::setChannelCount(unsigned int channelCount) |
222 | { |
223 | if (m_isCapturing) |
224 | { |
225 | err() << "It's not possible to change the channels while recording." << std::endl; |
226 | return; |
227 | } |
228 | |
229 | if (channelCount < 1 || channelCount > 2) |
230 | { |
231 | err() << "Unsupported channel count: " << channelCount << " Currently only mono (1) and stereo (2) recording is supported." << std::endl; |
232 | return; |
233 | } |
234 | |
235 | m_channelCount = channelCount; |
236 | } |
237 | |
238 | |
239 | //////////////////////////////////////////////////////////// |
240 | unsigned int SoundRecorder::getChannelCount() const |
241 | { |
242 | return m_channelCount; |
243 | } |
244 | |
245 | |
246 | //////////////////////////////////////////////////////////// |
247 | bool SoundRecorder::isAvailable() |
248 | { |
249 | return (priv::AudioDevice::isExtensionSupported("ALC_EXT_CAPTURE" ) != AL_FALSE) || |
250 | (priv::AudioDevice::isExtensionSupported("ALC_EXT_capture" ) != AL_FALSE); // "bug" in Mac OS X 10.5 and 10.6 |
251 | } |
252 | |
253 | |
254 | //////////////////////////////////////////////////////////// |
255 | void SoundRecorder::setProcessingInterval(Time interval) |
256 | { |
257 | m_processingInterval = interval; |
258 | } |
259 | |
260 | |
261 | //////////////////////////////////////////////////////////// |
262 | bool SoundRecorder::onStart() |
263 | { |
264 | // Nothing to do |
265 | return true; |
266 | } |
267 | |
268 | |
269 | //////////////////////////////////////////////////////////// |
270 | void SoundRecorder::onStop() |
271 | { |
272 | // Nothing to do |
273 | } |
274 | |
275 | |
276 | //////////////////////////////////////////////////////////// |
277 | void SoundRecorder::record() |
278 | { |
279 | while (m_isCapturing) |
280 | { |
281 | // Process available samples |
282 | processCapturedSamples(); |
283 | |
284 | // Don't bother the CPU while waiting for more captured data |
285 | sleep(m_processingInterval); |
286 | } |
287 | |
288 | // Capture is finished: clean up everything |
289 | cleanup(); |
290 | } |
291 | |
292 | |
293 | //////////////////////////////////////////////////////////// |
294 | void SoundRecorder::processCapturedSamples() |
295 | { |
296 | // Get the number of samples available |
297 | ALCint samplesAvailable; |
298 | alcGetIntegerv(captureDevice, ALC_CAPTURE_SAMPLES, 1, &samplesAvailable); |
299 | |
300 | if (samplesAvailable > 0) |
301 | { |
302 | // Get the recorded samples |
303 | m_samples.resize(samplesAvailable * getChannelCount()); |
304 | alcCaptureSamples(captureDevice, &m_samples[0], samplesAvailable); |
305 | |
306 | // Forward them to the derived class |
307 | if (!onProcessSamples(&m_samples[0], m_samples.size())) |
308 | { |
309 | // The user wants to stop the capture |
310 | m_isCapturing = false; |
311 | } |
312 | } |
313 | } |
314 | |
315 | |
316 | //////////////////////////////////////////////////////////// |
317 | void SoundRecorder::cleanup() |
318 | { |
319 | // Stop the capture |
320 | alcCaptureStop(captureDevice); |
321 | |
322 | // Get the samples left in the buffer |
323 | processCapturedSamples(); |
324 | |
325 | // Close the device |
326 | alcCaptureCloseDevice(captureDevice); |
327 | captureDevice = NULL; |
328 | } |
329 | |
330 | } // namespace sf |
331 | |