amine@2
|
1 """
|
amine@33
|
2 Module for low-level audio input-output operations.
|
amine@2
|
3
|
amine@32
|
4 Class summary
|
amine@32
|
5 =============
|
amine@32
|
6
|
amine@32
|
7 .. autosummary::
|
amine@32
|
8
|
amine@32
|
9 AudioSource
|
amine@32
|
10 Rewindable
|
amine@32
|
11 BufferAudioSource
|
amine@32
|
12 WaveAudioSource
|
amine@32
|
13 PyAudioSource
|
amine@32
|
14 StdinAudioSource
|
amine@32
|
15 PyAudioPlayer
|
amine@32
|
16
|
amine@32
|
17
|
amine@32
|
18 Function summary
|
amine@32
|
19 ================
|
amine@32
|
20
|
amine@32
|
21 .. autosummary::
|
amine@32
|
22
|
amine@32
|
23 from_file
|
amine@32
|
24 player_for
|
amine@2
|
25 """
|
amine@2
|
26
|
amine@2
|
27 from abc import ABCMeta, abstractmethod
|
amine@2
|
28 import wave
|
amine@10
|
29 import sys
|
amine@2
|
30
|
amine@90
|
31 __all__ = ["AudioIOError", "AudioParameterError", "AudioSource", "Rewindable",
|
amine@90
|
32 "BufferAudioSource", "WaveAudioSource", "PyAudioSource",
|
amine@90
|
33 "StdinAudioSource", "PyAudioPlayer", "from_file", "player_for"]
|
amine@2
|
34
|
amine@2
|
35 DEFAULT_SAMPLE_RATE = 16000
|
amine@2
|
36 DEFAULT_SAMPLE_WIDTH = 2
|
amine@2
|
37 DEFAULT_NB_CHANNELS = 1
|
amine@2
|
38
|
amine@89
|
39 class AudioIOError(Exception):
|
amine@89
|
40 pass
|
amine@89
|
41
|
amine@89
|
42
|
amine@89
|
43 class AudioParameterError(AudioIOError):
|
amine@89
|
44 pass
|
amine@89
|
45
|
amine@2
|
46
|
amine@90
|
47 def check_audio_data(data, sample_width, channels):
|
amine@90
|
48 sample_size_bytes = int(sample_width * channels)
|
amine@90
|
49 nb_samples = len(data) // sample_size_bytes
|
amine@90
|
50 if nb_samples * sample_size_bytes != len(data):
|
amine@90
|
51 raise AudioParameterError("The length of audio data must be an integer "
|
amine@90
|
52 "multiple of `sample_width * channels`")
|
amine@90
|
53
|
amine@90
|
54
|
amine@2
|
55 class AudioSource():
|
amine@2
|
56 """
|
amine@32
|
57 Base class for audio source objects.
|
amine@67
|
58
|
amine@2
|
59 Subclasses should implement methods to open/close and audio stream
|
amine@2
|
60 and read the desired amount of audio samples.
|
amine@67
|
61
|
amine@32
|
62 :Parameters:
|
amine@67
|
63
|
amine@32
|
64 `sampling_rate` : int
|
amine@32
|
65 Number of samples per second of audio stream. Default = 16000.
|
amine@67
|
66
|
amine@32
|
67 `sample_width` : int
|
amine@32
|
68 Size in bytes of one audio sample. Possible values : 1, 2, 4.
|
amine@32
|
69 Default = 2.
|
amine@67
|
70
|
amine@32
|
71 `channels` : int
|
amine@32
|
72 Number of channels of audio stream. The current version supports
|
amine@32
|
73 only mono audio streams (i.e. one channel).
|
amine@2
|
74 """
|
amine@67
|
75
|
amine@32
|
76 __metaclass__ = ABCMeta
|
amine@2
|
77
|
amine@67
|
78 def __init__(self, sampling_rate=DEFAULT_SAMPLE_RATE,
|
amine@67
|
79 sample_width=DEFAULT_SAMPLE_WIDTH,
|
amine@67
|
80 channels=DEFAULT_NB_CHANNELS):
|
amine@67
|
81
|
amine@90
|
82 if sample_width not in (1, 2, 4):
|
amine@90
|
83 raise AudioParameterError("Sample width must be one of: 1, 2 or 4 (bytes)")
|
amine@67
|
84
|
amine@2
|
85 if channels != 1:
|
amine@90
|
86 raise AudioParameterError("Only mono audio is currently supported")
|
amine@67
|
87
|
amine@70
|
88 self._sampling_rate = sampling_rate
|
amine@70
|
89 self._sample_width = sample_width
|
amine@70
|
90 self._channels = channels
|
amine@67
|
91
|
amine@2
|
92 @abstractmethod
|
amine@2
|
93 def is_open(self):
|
amine@2
|
94 """ Return True if audio source is open, False otherwise """
|
amine@67
|
95
|
amine@2
|
96 @abstractmethod
|
amine@2
|
97 def open(self):
|
amine@2
|
98 """ Open audio source """
|
amine@67
|
99
|
amine@2
|
100 @abstractmethod
|
amine@2
|
101 def close(self):
|
amine@2
|
102 """ Close audio source """
|
amine@67
|
103
|
amine@2
|
104 @abstractmethod
|
amine@2
|
105 def read(self, size):
|
amine@2
|
106 """
|
amine@2
|
107 Read and return `size` audio samples at most.
|
amine@67
|
108
|
amine@32
|
109 :Parameters:
|
amine@67
|
110
|
amine@32
|
111 `size` : int
|
amine@32
|
112 the number of samples to read.
|
amine@67
|
113
|
amine@32
|
114 :Returns:
|
amine@67
|
115
|
taf2@55
|
116 Audio data as a string of length 'N' * 'sample_width' * 'channels', where 'N' is:
|
amine@67
|
117
|
amine@32
|
118 - `size` if `size` < 'left_samples'
|
amine@67
|
119
|
amine@32
|
120 - 'left_samples' if `size` > 'left_samples'
|
amine@67
|
121 """
|
amine@67
|
122
|
amine@2
|
123 def get_sampling_rate(self):
|
amine@2
|
124 """ Return the number of samples per second of audio stream """
|
amine@2
|
125 return self.sampling_rate
|
amine@67
|
126
|
amine@70
|
127 @property
|
amine@70
|
128 def sampling_rate(self):
|
amine@70
|
129 """ Number of samples per second of audio stream """
|
amine@70
|
130 return self._sampling_rate
|
amine@70
|
131
|
amine@72
|
132 @property
|
amine@72
|
133 def sr(self):
|
amine@72
|
134 """ Number of samples per second of audio stream """
|
amine@72
|
135 return self._sampling_rate
|
amine@72
|
136
|
amine@2
|
137 def get_sample_width(self):
|
amine@2
|
138 """ Return the number of bytes used to represent one audio sample """
|
amine@2
|
139 return self.sample_width
|
amine@67
|
140
|
amine@70
|
141 @property
|
amine@70
|
142 def sample_width(self):
|
amine@70
|
143 """ Number of bytes used to represent one audio sample """
|
amine@70
|
144 return self._sample_width
|
amine@70
|
145
|
amine@72
|
146 @property
|
amine@72
|
147 def sw(self):
|
amine@72
|
148 """ Number of bytes used to represent one audio sample """
|
amine@72
|
149 return self._sample_width
|
amine@72
|
150
|
amine@2
|
151 def get_channels(self):
|
amine@2
|
152 """ Return the number of channels of this audio source """
|
amine@2
|
153 return self.channels
|
amine@2
|
154
|
amine@70
|
155 @property
|
amine@70
|
156 def channels(self):
|
amine@70
|
157 """ Number of channels of this audio source """
|
amine@70
|
158 return self._channels
|
amine@70
|
159
|
amine@72
|
160 @property
|
amine@72
|
161 def ch(self):
|
amine@72
|
162 """ Return the number of channels of this audio source """
|
amine@72
|
163 return self.channels
|
amine@72
|
164
|
amine@2
|
165
|
amine@2
|
166 class Rewindable():
|
amine@2
|
167 """
|
amine@2
|
168 Base class for rewindable audio streams.
|
amine@2
|
169 Subclasses should implement methods to return to the beginning of an
|
amine@2
|
170 audio stream as well as method to move to an absolute audio position
|
amine@2
|
171 expressed in time or in number of samples.
|
amine@32
|
172 """
|
amine@67
|
173
|
amine@32
|
174 __metaclass__ = ABCMeta
|
amine@67
|
175
|
amine@2
|
176 @abstractmethod
|
amine@2
|
177 def rewind(self):
|
amine@2
|
178 """ Go back to the beginning of audio stream """
|
amine@2
|
179 pass
|
amine@67
|
180
|
amine@2
|
181 @abstractmethod
|
amine@2
|
182 def get_position(self):
|
amine@2
|
183 """ Return the total number of already read samples """
|
amine@67
|
184
|
amine@2
|
185 @abstractmethod
|
amine@2
|
186 def get_time_position(self):
|
amine@2
|
187 """ Return the total duration in seconds of already read data """
|
amine@67
|
188
|
amine@2
|
189 @abstractmethod
|
amine@2
|
190 def set_position(self, position):
|
amine@2
|
191 """ Move to an absolute position
|
amine@67
|
192
|
amine@32
|
193 :Parameters:
|
amine@67
|
194
|
amine@32
|
195 `position` : int
|
amine@32
|
196 number of samples to skip from the start of the stream
|
amine@2
|
197 """
|
amine@67
|
198
|
amine@2
|
199 @abstractmethod
|
amine@2
|
200 def set_time_position(self, time_position):
|
amine@2
|
201 """ Move to an absolute position expressed in seconds
|
amine@67
|
202
|
amine@32
|
203 :Parameters:
|
amine@67
|
204
|
amine@32
|
205 `time_position` : float
|
amine@32
|
206 seconds to skip from the start of the stream
|
amine@2
|
207 """
|
amine@2
|
208 pass
|
amine@48
|
209
|
amine@2
|
210
|
amine@2
|
211 class BufferAudioSource(AudioSource, Rewindable):
|
amine@2
|
212 """
|
amine@32
|
213 An :class:`AudioSource` that encapsulates and reads data from a memory buffer.
|
amine@32
|
214 It implements methods from :class:`Rewindable` and is therefore a navigable :class:`AudioSource`.
|
amine@2
|
215 """
|
amine@67
|
216
|
amine@2
|
217 def __init__(self, data_buffer,
|
amine@67
|
218 sampling_rate=DEFAULT_SAMPLE_RATE,
|
amine@67
|
219 sample_width=DEFAULT_SAMPLE_WIDTH,
|
amine@67
|
220 channels=DEFAULT_NB_CHANNELS):
|
amine@94
|
221 AudioSource.__init__(self, sampling_rate, sample_width, channels)
|
amine@90
|
222 check_audio_data(data_buffer, sample_width, channels)
|
amine@2
|
223 self._buffer = data_buffer
|
amine@94
|
224 self._sample_size_all_channels = sample_width * channels
|
amine@94
|
225 self._current_position_bytes = 0
|
amine@2
|
226 self._is_open = False
|
amine@67
|
227
|
amine@2
|
228 def is_open(self):
|
amine@2
|
229 return self._is_open
|
amine@67
|
230
|
amine@2
|
231 def open(self):
|
amine@2
|
232 self._is_open = True
|
amine@67
|
233
|
amine@2
|
234 def close(self):
|
amine@2
|
235 self._is_open = False
|
amine@2
|
236 self.rewind()
|
amine@67
|
237
|
amine@10
|
238 def read(self, size):
|
amine@2
|
239 if not self._is_open:
|
amine@94
|
240 raise AudioIOError("Stream is not open")
|
amine@94
|
241 bytes_to_read = self._sample_size_all_channels * size
|
amine@94
|
242 data = self._buffer[self._current_position_bytes: self._current_position_bytes + bytes_to_read]
|
amine@94
|
243 if data:
|
amine@94
|
244 self._current_position_bytes += len(data)
|
amine@2
|
245 return data
|
amine@2
|
246 return None
|
amine@67
|
247
|
amine@2
|
248 def get_data_buffer(self):
|
amine@2
|
249 """ Return all audio data as one string buffer. """
|
amine@2
|
250 return self._buffer
|
amine@67
|
251
|
amine@2
|
252 def set_data(self, data_buffer):
|
amine@2
|
253 """ Set new data for this audio stream.
|
amine@67
|
254
|
amine@32
|
255 :Parameters:
|
amine@67
|
256
|
amine@32
|
257 `data_buffer` : str, basestring, Bytes
|
amine@32
|
258 a string buffer with a length multiple of (sample_width * channels)
|
amine@2
|
259 """
|
amine@90
|
260 check_audio_data(data_buffer, self.sample_width, self.channels)
|
amine@2
|
261 self._buffer = data_buffer
|
amine@94
|
262 self._current_position_bytes = 0
|
amine@67
|
263
|
amine@2
|
264 def append_data(self, data_buffer):
|
amine@2
|
265 """ Append data to this audio stream
|
amine@67
|
266
|
amine@32
|
267 :Parameters:
|
amine@67
|
268
|
amine@32
|
269 `data_buffer` : str, basestring, Bytes
|
amine@32
|
270 a buffer with a length multiple of (sample_width * channels)
|
amine@2
|
271 """
|
amine@90
|
272 check_audio_data(data_buffer, self.sample_width, self.channels)
|
amine@2
|
273 self._buffer += data_buffer
|
amine@2
|
274
|
amine@2
|
275 def rewind(self):
|
amine@2
|
276 self.set_position(0)
|
amine@67
|
277
|
amine@2
|
278 def get_position(self):
|
amine@94
|
279 return self._current_position_bytes / self._sample_size_all_channels
|
amine@67
|
280
|
amine@2
|
281 def get_time_position(self):
|
amine@94
|
282 return float(self._current_position_bytes) / (self._sample_size_all_channels * self.sampling_rate)
|
amine@67
|
283
|
amine@2
|
284 def set_position(self, position):
|
amine@2
|
285 if position < 0:
|
amine@2
|
286 raise ValueError("position must be >= 0")
|
amine@94
|
287 position *= self._sample_size_all_channels
|
amine@94
|
288 self._current_position_bytes = position if position < len(self._buffer) else len(self._buffer)
|
amine@2
|
289
|
amine@67
|
290 def set_time_position(self, time_position): # time in seconds
|
amine@2
|
291 position = int(self.sampling_rate * time_position)
|
amine@2
|
292 self.set_position(position)
|
amine@2
|
293
|
amine@48
|
294
|
amine@2
|
295 class WaveAudioSource(AudioSource):
|
amine@32
|
296 """
|
amine@32
|
297 A class for an `AudioSource` that reads data from a wave file.
|
amine@67
|
298
|
amine@32
|
299 :Parameters:
|
amine@67
|
300
|
amine@32
|
301 `filename` :
|
amine@32
|
302 path to a valid wave file
|
amine@32
|
303 """
|
amine@67
|
304
|
amine@2
|
305 def __init__(self, filename):
|
amine@67
|
306
|
amine@2
|
307 self._filename = filename
|
amine@2
|
308 self._audio_stream = None
|
amine@67
|
309
|
amine@2
|
310 stream = wave.open(self._filename)
|
amine@2
|
311 AudioSource.__init__(self, stream.getframerate(),
|
amine@67
|
312 stream.getsampwidth(),
|
amine@67
|
313 stream.getnchannels())
|
amine@2
|
314 stream.close()
|
amine@67
|
315
|
amine@2
|
316 def is_open(self):
|
amine@2
|
317 return self._audio_stream is not None
|
amine@67
|
318
|
amine@2
|
319 def open(self):
|
amine@2
|
320 if(self._audio_stream is None):
|
amine@2
|
321 self._audio_stream = wave.open(self._filename)
|
amine@67
|
322
|
amine@2
|
323 def close(self):
|
amine@2
|
324 if self._audio_stream is not None:
|
amine@2
|
325 self._audio_stream.close()
|
amine@2
|
326 self._audio_stream = None
|
amine@67
|
327
|
amine@2
|
328 def read(self, size):
|
amine@2
|
329 if self._audio_stream is None:
|
amine@2
|
330 raise IOError("Stream is not open")
|
amine@2
|
331 else:
|
amine@2
|
332 data = self._audio_stream.readframes(size)
|
amine@2
|
333 if data is None or len(data) < 1:
|
amine@2
|
334 return None
|
amine@2
|
335 return data
|
amine@2
|
336
|
amine@2
|
337
|
amine@2
|
338 class PyAudioSource(AudioSource):
|
amine@32
|
339 """
|
amine@32
|
340 A class for an `AudioSource` that reads data the built-in microphone using PyAudio.
|
amine@32
|
341 """
|
amine@67
|
342
|
amine@67
|
343 def __init__(self, sampling_rate=DEFAULT_SAMPLE_RATE,
|
amine@67
|
344 sample_width=DEFAULT_SAMPLE_WIDTH,
|
amine@67
|
345 channels=DEFAULT_NB_CHANNELS,
|
mathieu@79
|
346 frames_per_buffer=1024,
|
mathieu@79
|
347 input_device_index=None):
|
amine@67
|
348
|
amine@2
|
349 AudioSource.__init__(self, sampling_rate, sample_width, channels)
|
amine@2
|
350 self._chunk_size = frames_per_buffer
|
mathieu@79
|
351 self.input_device_index = input_device_index
|
amine@67
|
352
|
amine@2
|
353 import pyaudio
|
amine@2
|
354 self._pyaudio_object = pyaudio.PyAudio()
|
amine@67
|
355 self._pyaudio_format = self._pyaudio_object.get_format_from_width(self.sample_width)
|
amine@2
|
356 self._audio_stream = None
|
amine@2
|
357
|
amine@2
|
358 def is_open(self):
|
amine@2
|
359 return self._audio_stream is not None
|
amine@67
|
360
|
amine@2
|
361 def open(self):
|
amine@67
|
362 self._audio_stream = self._pyaudio_object.open(format=self._pyaudio_format,
|
amine@67
|
363 channels=self.channels,
|
amine@67
|
364 rate=self.sampling_rate,
|
amine@67
|
365 input=True,
|
amine@67
|
366 output=False,
|
mathieu@79
|
367 input_device_index=self.input_device_index,
|
amine@67
|
368 frames_per_buffer=self._chunk_size)
|
amine@67
|
369
|
amine@2
|
370 def close(self):
|
amine@2
|
371 if self._audio_stream is not None:
|
amine@2
|
372 self._audio_stream.stop_stream()
|
amine@2
|
373 self._audio_stream.close()
|
amine@2
|
374 self._audio_stream = None
|
amine@67
|
375
|
amine@2
|
376 def read(self, size):
|
amine@2
|
377 if self._audio_stream is None:
|
amine@2
|
378 raise IOError("Stream is not open")
|
amine@67
|
379
|
amine@2
|
380 if self._audio_stream.is_active():
|
amine@2
|
381 data = self._audio_stream.read(size)
|
amine@2
|
382 if data is None or len(data) < 1:
|
amine@2
|
383 return None
|
amine@2
|
384 return data
|
amine@67
|
385
|
amine@2
|
386 return None
|
amine@67
|
387
|
amine@2
|
388
|
amine@10
|
389 class StdinAudioSource(AudioSource):
|
amine@32
|
390 """
|
amine@32
|
391 A class for an :class:`AudioSource` that reads data from standard input.
|
amine@32
|
392 """
|
amine@67
|
393
|
amine@67
|
394 def __init__(self, sampling_rate=DEFAULT_SAMPLE_RATE,
|
amine@67
|
395 sample_width=DEFAULT_SAMPLE_WIDTH,
|
amine@67
|
396 channels=DEFAULT_NB_CHANNELS):
|
amine@67
|
397
|
amine@10
|
398 AudioSource.__init__(self, sampling_rate, sample_width, channels)
|
amine@10
|
399 self._is_open = False
|
amine@67
|
400
|
amine@10
|
401 def is_open(self):
|
amine@10
|
402 return self._is_open
|
amine@67
|
403
|
amine@10
|
404 def open(self):
|
amine@10
|
405 self._is_open = True
|
amine@67
|
406
|
amine@10
|
407 def close(self):
|
amine@10
|
408 self._is_open = False
|
amine@67
|
409
|
amine@10
|
410 def read(self, size):
|
amine@10
|
411 if not self._is_open:
|
amine@10
|
412 raise IOError("Stream is not open")
|
amine@67
|
413
|
amine@10
|
414 to_read = size * self.sample_width * self.channels
|
pete@74
|
415 if sys.version_info >= (3, 0):
|
pete@74
|
416 data = sys.stdin.buffer.read(to_read)
|
pete@74
|
417 else:
|
pete@74
|
418 data = sys.stdin.read(to_read)
|
amine@67
|
419
|
amine@10
|
420 if data is None or len(data) < 1:
|
amine@10
|
421 return None
|
amine@67
|
422
|
amine@10
|
423 return data
|
amine@67
|
424
|
amine@67
|
425
|
amine@2
|
426 class PyAudioPlayer():
|
amine@32
|
427 """
|
amine@32
|
428 A class for audio playback using Pyaudio
|
amine@32
|
429 """
|
amine@67
|
430
|
amine@67
|
431 def __init__(self, sampling_rate=DEFAULT_SAMPLE_RATE,
|
amine@67
|
432 sample_width=DEFAULT_SAMPLE_WIDTH,
|
amine@67
|
433 channels=DEFAULT_NB_CHANNELS):
|
amine@2
|
434 if not sample_width in (1, 2, 4):
|
amine@2
|
435 raise ValueError("Sample width must be one of: 1, 2 or 4 (bytes)")
|
amine@67
|
436
|
amine@2
|
437 self.sampling_rate = sampling_rate
|
amine@2
|
438 self.sample_width = sample_width
|
amine@2
|
439 self.channels = channels
|
amine@67
|
440
|
amine@2
|
441 import pyaudio
|
amine@2
|
442 self._p = pyaudio.PyAudio()
|
amine@67
|
443 self.stream = self._p.open(format=self._p.get_format_from_width(self.sample_width),
|
amine@67
|
444 channels=self.channels, rate=self.sampling_rate,
|
amine@67
|
445 input=False, output=True)
|
amine@67
|
446
|
amine@2
|
447 def play(self, data):
|
amine@2
|
448 if self.stream.is_stopped():
|
amine@2
|
449 self.stream.start_stream()
|
amine@67
|
450
|
amine@10
|
451 for chunk in self._chunk_data(data):
|
amine@10
|
452 self.stream.write(chunk)
|
amine@67
|
453
|
amine@2
|
454 self.stream.stop_stream()
|
amine@67
|
455
|
amine@67
|
456 def stop(self):
|
amine@2
|
457 if not self.stream.is_stopped():
|
amine@2
|
458 self.stream.stop_stream()
|
amine@2
|
459 self.stream.close()
|
amine@2
|
460 self._p.terminate()
|
amine@67
|
461
|
amine@10
|
462 def _chunk_data(self, data):
|
amine@10
|
463 # make audio chunks of 100 ms to allow interruption (like ctrl+c)
|
amine@10
|
464 chunk_size = int((self.sampling_rate * self.sample_width * self.channels) / 10)
|
amine@10
|
465 start = 0
|
amine@10
|
466 while start < len(data):
|
amine@67
|
467 yield data[start: start + chunk_size]
|
amine@10
|
468 start += chunk_size
|
amine@67
|
469
|
amine@2
|
470
|
amine@2
|
471 def from_file(filename):
|
amine@2
|
472 """
|
amine@2
|
473 Create an `AudioSource` object using the audio file specified by `filename`.
|
amine@48
|
474 The appropriate :class:`AudioSource` class is guessed from file's extension.
|
amine@67
|
475
|
amine@32
|
476 :Parameters:
|
amine@67
|
477
|
amine@32
|
478 `filename` :
|
amine@32
|
479 path to an audio file.
|
amine@67
|
480
|
amine@32
|
481 :Returns:
|
amine@67
|
482
|
amine@32
|
483 an `AudioSource` object that reads data from the given file.
|
amine@2
|
484 """
|
amine@67
|
485
|
amine@2
|
486 if filename.lower().endswith(".wav"):
|
amine@2
|
487 return WaveAudioSource(filename)
|
amine@67
|
488
|
amine@67
|
489 raise Exception("Can not create an AudioSource object from '%s'" % (filename))
|
amine@2
|
490
|
amine@2
|
491
|
amine@2
|
492 def player_for(audio_source):
|
amine@2
|
493 """
|
amine@32
|
494 Return a :class:`PyAudioPlayer` that can play data from `audio_source`.
|
amine@67
|
495
|
amine@32
|
496 :Parameters:
|
amine@67
|
497
|
amine@32
|
498 `audio_source` :
|
amine@32
|
499 an `AudioSource` object.
|
amine@67
|
500
|
amine@32
|
501 :Returns:
|
amine@67
|
502
|
amine@32
|
503 `PyAudioPlayer` that has the same sampling rate, sample width and number of channels
|
amine@32
|
504 as `audio_source`.
|
amine@2
|
505 """
|
amine@67
|
506
|
amine@2
|
507 return PyAudioPlayer(audio_source.get_sampling_rate(),
|
amine@67
|
508 audio_source.get_sample_width(),
|
amine@67
|
509 audio_source.get_channels())
|