0
# Utilities and Error Handling
1
2
Utility functions and exception classes for audio processing workflows and error management. These components provide additional functionality and robust error handling for sounddevice applications.
3
4
## Capabilities
5
6
### Utility Functions
7
8
Helper functions for timing operations and library information retrieval.
9
10
```python { .api }
11
def sleep(msec):
12
"""
13
Sleep for specified milliseconds using PortAudio's timing.
14
15
Parameters:
16
- msec (float): Number of milliseconds to sleep
17
18
Returns:
19
None
20
21
Notes:
22
Uses PortAudio's Pa_Sleep() function which may provide more accurate
23
timing than Python's time.sleep() on some platforms.
24
"""
25
26
def get_portaudio_version():
27
"""
28
Get PortAudio library version information.
29
30
Returns:
31
tuple: (version_text, version_number)
32
- version_text (str): Human-readable version string
33
- version_number (int): Numeric version identifier
34
"""
35
```
36
37
### Exception Classes
38
39
Specialized exception classes for handling audio-related errors and controlling stream behavior.
40
41
```python { .api }
42
class PortAudioError(Exception):
43
"""
44
Exception raised for PortAudio-related errors.
45
46
This exception is raised when PortAudio functions return error codes,
47
typically indicating hardware problems, invalid parameters, or
48
system resource issues.
49
50
Attributes:
51
- args[0] (str): Error message describing the specific problem
52
"""
53
54
class CallbackStop(Exception):
55
"""
56
Exception to signal stream callback should stop gracefully.
57
58
Raise this exception from within an audio callback function to
59
stop the stream in a controlled manner. The stream will finish
60
processing the current buffer and then stop.
61
"""
62
63
class CallbackAbort(Exception):
64
"""
65
Exception to signal stream callback should abort immediately.
66
67
Raise this exception from within an audio callback function to
68
abort the stream immediately. This may cause audio dropouts but
69
stops the stream as quickly as possible.
70
"""
71
72
class DeviceList(tuple):
73
"""
74
Special tuple subclass containing device information.
75
76
A list-like object that behaves like a tuple but provides special
77
string representation for interactive display of device information.
78
Each element is a device information dictionary.
79
"""
80
81
class CallbackFlags:
82
"""
83
Status flags for stream callback functions.
84
85
Provides information about buffer over-/underruns and stream conditions
86
that occurred during callback processing.
87
88
Attributes:
89
- input_underflow (bool): Input buffer underflow occurred
90
- input_overflow (bool): Input buffer overflow occurred
91
- output_underflow (bool): Output buffer underflow occurred
92
- output_overflow (bool): Output buffer overflow occurred
93
- priming_output (bool): Stream is priming output buffers
94
"""
95
96
def __init__(self, flags=0x0): ...
97
98
input_underflow: bool
99
input_overflow: bool
100
output_underflow: bool
101
output_overflow: bool
102
priming_output: bool
103
```
104
105
## Usage Examples
106
107
### Precise Timing with sleep()
108
109
```python
110
import sounddevice as sd
111
import time
112
113
def compare_sleep_methods():
114
"""Compare sounddevice.sleep() with time.sleep() precision."""
115
116
# Test sounddevice.sleep()
117
start = time.time()
118
sd.sleep(100) # Sleep for 100 milliseconds
119
sd_duration = time.time() - start
120
121
# Test time.sleep()
122
start = time.time()
123
time.sleep(0.1) # Sleep for 0.1 seconds (100 milliseconds)
124
time_duration = time.time() - start
125
126
print(f"sounddevice.sleep(100): {sd_duration:.6f} seconds")
127
print(f"time.sleep(0.1): {time_duration:.6f} seconds")
128
print(f"Target: 0.100000 seconds")
129
130
compare_sleep_methods()
131
```
132
133
### Using sleep() for Audio Synchronization
134
135
```python
136
import sounddevice as sd
137
import numpy as np
138
139
def play_with_gaps():
140
"""Play audio segments with precise gaps between them."""
141
142
# Generate short audio beeps
143
duration = 0.2 # 200ms beeps
144
samplerate = 44100
145
frequency = 800 # 800 Hz
146
147
t = np.linspace(0, duration, int(samplerate * duration))
148
beep = 0.3 * np.sin(2 * np.pi * frequency * t)
149
150
# Play 5 beeps with 300ms gaps
151
for i in range(5):
152
print(f"Playing beep {i+1}")
153
sd.play(beep, samplerate=samplerate, blocking=True)
154
155
if i < 4: # Don't sleep after the last beep
156
print("Waiting...")
157
sd.sleep(300) # 300ms gap using PortAudio timing
158
159
play_with_gaps()
160
```
161
162
### Getting Library Version Information
163
164
```python
165
import sounddevice as sd
166
167
def show_version_info():
168
"""Display sounddevice and PortAudio version information."""
169
170
# Get sounddevice version
171
print(f"sounddevice version: {sd.__version__}")
172
173
# Get PortAudio version
174
version_text, version_number = sd.get_portaudio_version()
175
print(f"PortAudio version: {version_text}")
176
print(f"PortAudio version number: {version_number}")
177
178
# Decode version number (example for PortAudio v19.7.0)
179
major = (version_number >> 16) & 0xFF
180
minor = (version_number >> 8) & 0xFF
181
patch = version_number & 0xFF
182
print(f"PortAudio version breakdown: {major}.{minor}.{patch}")
183
184
show_version_info()
185
```
186
187
### Exception Handling in Audio Operations
188
189
```python
190
import sounddevice as sd
191
import numpy as np
192
193
def safe_audio_operation():
194
"""Demonstrate proper exception handling for audio operations."""
195
196
try:
197
# Attempt to use a specific device that might not exist
198
devices = sd.query_devices()
199
print("Available devices:")
200
for i, device in enumerate(devices):
201
print(f" {i}: {device['name']}")
202
203
# Try to record from device index 99 (likely doesn't exist)
204
recording = sd.rec(frames=44100, samplerate=44100, device=99)
205
sd.wait()
206
207
except sd.PortAudioError as e:
208
print(f"PortAudio error occurred: {e}")
209
print("Falling back to default device...")
210
211
# Fallback to default device
212
try:
213
recording = sd.rec(frames=44100, samplerate=44100)
214
sd.wait()
215
print("Recording successful with default device")
216
217
except sd.PortAudioError as e2:
218
print(f"Even default device failed: {e2}")
219
return None
220
221
except Exception as e:
222
print(f"Unexpected error: {e}")
223
return None
224
225
return recording
226
227
# Test safe audio operation
228
result = safe_audio_operation()
229
if result is not None:
230
print(f"Recording completed: {len(result)} samples")
231
```
232
233
### Using Callback Exceptions for Stream Control
234
235
```python
236
import sounddevice as sd
237
import numpy as np
238
import time
239
240
def controlled_callback_demo():
241
"""Demonstrate using callback exceptions to control stream behavior."""
242
243
start_time = time.time()
244
max_duration = 5.0 # Maximum recording duration
245
246
recorded_data = []
247
248
def recording_callback(indata, frames, time, status):
249
"""Callback that stops after maximum duration."""
250
if status:
251
print(f"Status: {status}")
252
253
# Store the recorded data
254
recorded_data.append(indata.copy())
255
256
# Check if we should stop
257
elapsed = time.time() - start_time
258
if elapsed > max_duration:
259
print("Maximum duration reached, stopping gracefully...")
260
raise sd.CallbackStop()
261
262
# Check for some error condition (example: too loud input)
263
rms = np.sqrt(np.mean(indata**2))
264
if rms > 0.8: # If input is too loud
265
print("Input too loud, aborting immediately!")
266
raise sd.CallbackAbort()
267
268
try:
269
with sd.InputStream(callback=recording_callback, channels=1):
270
print("Recording started. Speak normally (will stop after 5 seconds)")
271
print("Speak loudly to trigger abort condition")
272
273
# Keep the stream alive
274
while True:
275
sd.sleep(100)
276
277
except sd.CallbackStop:
278
print("Stream stopped gracefully via CallbackStop")
279
except sd.CallbackAbort:
280
print("Stream aborted immediately via CallbackAbort")
281
except KeyboardInterrupt:
282
print("Recording interrupted by user")
283
284
if recorded_data:
285
total_samples = sum(len(block) for block in recorded_data)
286
print(f"Recorded {total_samples} samples in {len(recorded_data)} blocks")
287
288
return recorded_data
289
290
# Run the controlled callback demonstration
291
recorded_blocks = controlled_callback_demo()
292
```
293
294
### Comprehensive Error Handling Strategy
295
296
```python
297
import sounddevice as sd
298
import numpy as np
299
import logging
300
301
# Configure logging for audio errors
302
logging.basicConfig(level=logging.INFO)
303
logger = logging.getLogger(__name__)
304
305
class AudioManager:
306
"""Audio manager with comprehensive error handling."""
307
308
def __init__(self):
309
self.fallback_devices = []
310
self._discover_fallback_devices()
311
312
def _discover_fallback_devices(self):
313
"""Discover available devices for fallback scenarios."""
314
try:
315
devices = sd.query_devices()
316
# Find devices that support both input and output
317
for i, device in enumerate(devices):
318
if (device['max_input_channels'] > 0 and
319
device['max_output_channels'] > 0):
320
self.fallback_devices.append(i)
321
logger.info(f"Found {len(self.fallback_devices)} fallback devices")
322
except sd.PortAudioError as e:
323
logger.error(f"Failed to query devices: {e}")
324
325
def safe_record(self, duration=5.0, samplerate=44100, device=None):
326
"""Record audio with automatic fallback on errors."""
327
328
devices_to_try = [device] if device is not None else [None]
329
devices_to_try.extend(self.fallback_devices)
330
331
for attempt, dev in enumerate(devices_to_try):
332
try:
333
logger.info(f"Attempt {attempt + 1}: Using device {dev}")
334
335
recording = sd.rec(
336
frames=int(duration * samplerate),
337
samplerate=samplerate,
338
channels=1,
339
device=dev
340
)
341
sd.wait()
342
343
logger.info("Recording successful")
344
return recording
345
346
except sd.PortAudioError as e:
347
logger.warning(f"Device {dev} failed: {e}")
348
continue
349
except Exception as e:
350
logger.error(f"Unexpected error with device {dev}: {e}")
351
continue
352
353
logger.error("All devices failed")
354
return None
355
356
def get_system_info(self):
357
"""Get comprehensive system audio information."""
358
info = {}
359
360
try:
361
# PortAudio version
362
version_text, version_number = sd.get_portaudio_version()
363
info['portaudio_version'] = version_text
364
info['portaudio_version_number'] = version_number
365
366
# sounddevice version
367
info['sounddevice_version'] = sd.__version__
368
369
# Device count
370
devices = sd.query_devices()
371
info['device_count'] = len(devices)
372
info['input_devices'] = sum(1 for d in devices if d['max_input_channels'] > 0)
373
info['output_devices'] = sum(1 for d in devices if d['max_output_channels'] > 0)
374
375
# Host APIs
376
hostapis = sd.query_hostapis()
377
info['hostapi_count'] = len(hostapis)
378
info['hostapi_names'] = [api['name'] for api in hostapis]
379
380
except Exception as e:
381
logger.error(f"Failed to get system info: {e}")
382
info['error'] = str(e)
383
384
return info
385
386
# Usage example
387
audio_manager = AudioManager()
388
389
# Get system information
390
system_info = audio_manager.get_system_info()
391
print("System Audio Information:")
392
for key, value in system_info.items():
393
print(f" {key}: {value}")
394
395
# Safe recording with fallback
396
recording = audio_manager.safe_record(duration=3.0)
397
if recording is not None:
398
print(f"Successfully recorded {len(recording)} samples")
399
else:
400
print("Recording failed on all available devices")
401
```
402
403
## Error Code Reference
404
405
Common PortAudioError conditions and their meanings:
406
407
- **"Invalid device"**: Specified device index or name not found
408
- **"Invalid sample rate"**: Sample rate not supported by device
409
- **"Invalid number of channels"**: Channel count not supported
410
- **"Device unavailable"**: Device is busy or not accessible
411
- **"Insufficient memory"**: Not enough memory for audio buffers
412
- **"Host API not found"**: Requested host API not available
413
- **"Stream is not active"**: Attempted operation on inactive stream
414
- **"Invalid latency"**: Specified latency not achievable
415
416
## Callback Exception Guidelines
417
418
When using callback exceptions:
419
420
- **CallbackStop**: Use for graceful shutdown (normal completion, user request)
421
- **CallbackAbort**: Use for emergency shutdown (hardware failure, critical error)
422
- Handle exceptions outside the callback to avoid audio dropouts
423
- Log callback exceptions for debugging purposes
424
- Implement fallback strategies when callbacks fail