|
1 | 1 | #!/usr/bin/env python3 |
2 | 2 | """Play an audio file using a limited amount of memory. |
3 | 3 |
|
4 | | -The soundfile module (http://PySoundFile.rtfd.io/) must be installed for |
5 | | -this to work. NumPy is not needed. |
| 4 | +The soundfile module (https://PySoundFile.readthedocs.io/) must be |
| 5 | +installed for this to work. NumPy is not needed. |
6 | 6 |
|
7 | 7 | In contrast to play_file.py, which loads the whole file into memory |
8 | 8 | before starting playback, this example program only holds a given number |
9 | 9 | of audio blocks in memory and is therefore able to play files that are |
10 | 10 | larger than the available RAM. |
11 | 11 |
|
| 12 | +A similar example could of course be implemented using NumPy, |
| 13 | +but this example shows what can be done when NumPy is not available. |
| 14 | +
|
12 | 15 | """ |
13 | | -from __future__ import division, print_function |
14 | 16 | import argparse |
15 | | -try: |
16 | | - import queue # Python 3.x |
17 | | -except ImportError: |
18 | | - import Queue as queue # Python 2.x |
| 17 | +import queue |
19 | 18 | import sys |
20 | 19 | import threading |
21 | 20 |
|
| 21 | +import sounddevice as sd |
| 22 | +import soundfile as sf |
| 23 | + |
| 24 | + |
22 | 25 | def int_or_str(text): |
23 | 26 | """Helper function for argument parsing.""" |
24 | 27 | try: |
25 | 28 | return int(text) |
26 | 29 | except ValueError: |
27 | 30 | return text |
28 | 31 |
|
29 | | -parser = argparse.ArgumentParser(description=__doc__) |
30 | | -parser.add_argument('filename', help='audio file to be played back') |
31 | | -parser.add_argument('-d', '--device', type=int_or_str, |
32 | | - help='output device (numeric ID or substring)') |
33 | | -parser.add_argument('-b', '--blocksize', type=int, default=2048, |
34 | | - help='block size (default: %(default)s)') |
| 32 | + |
| 33 | +parser = argparse.ArgumentParser(add_help=False) |
| 34 | +parser.add_argument( |
| 35 | + '-l', '--list-devices', action='store_true', |
| 36 | + help='show list of audio devices and exit') |
| 37 | +args, remaining = parser.parse_known_args() |
| 38 | +if args.list_devices: |
| 39 | + print(sd.query_devices()) |
| 40 | + parser.exit(0) |
| 41 | +parser = argparse.ArgumentParser( |
| 42 | + description=__doc__, |
| 43 | + formatter_class=argparse.RawDescriptionHelpFormatter, |
| 44 | + parents=[parser]) |
| 45 | +parser.add_argument( |
| 46 | + 'filename', metavar='FILENAME', |
| 47 | + help='audio file to be played back') |
| 48 | +parser.add_argument( |
| 49 | + '-d', '--device', type=int_or_str, |
| 50 | + help='output device (numeric ID or substring)') |
| 51 | +parser.add_argument( |
| 52 | + '-b', '--blocksize', type=int, default=2048, |
| 53 | + help='block size (default: %(default)s)') |
35 | 54 | parser.add_argument( |
36 | 55 | '-q', '--buffersize', type=int, default=20, |
37 | 56 | help='number of blocks used for buffering (default: %(default)s)') |
38 | | -args = parser.parse_args() |
| 57 | +args = parser.parse_args(remaining) |
39 | 58 | if args.blocksize == 0: |
40 | 59 | parser.error('blocksize must not be zero') |
41 | 60 | if args.buffersize < 1: |
@@ -65,16 +84,12 @@ def callback(outdata, frames, time, status): |
65 | 84 |
|
66 | 85 |
|
67 | 86 | try: |
68 | | - import sounddevice as sd |
69 | | - import soundfile as sf |
70 | | - |
71 | 87 | with sf.SoundFile(args.filename) as f: |
72 | 88 | for _ in range(args.buffersize): |
73 | 89 | data = f.buffer_read(args.blocksize, dtype='float32') |
74 | 90 | if not data: |
75 | 91 | break |
76 | 92 | q.put_nowait(data) # Pre-fill queue |
77 | | - |
78 | 93 | stream = sd.RawOutputStream( |
79 | 94 | samplerate=f.samplerate, blocksize=args.blocksize, |
80 | 95 | device=args.device, channels=f.channels, dtype='float32', |
|
0 commit comments