"vscode:/vscode.git/clone" did not exist on "5e7dd984fe0151198148b9cee6e613805e80998b"
test.py 10.1 KB
Newer Older
David Pollack's avatar
David Pollack committed
1
import unittest
2
import torch
Soumith Chintala's avatar
Soumith Chintala committed
3
import torchaudio
David Pollack's avatar
David Pollack committed
4
import math
David Pollack's avatar
David Pollack committed
5
import os
6
from common_utils import AudioBackendScope, BACKENDS, BACKENDS_MP3, create_temp_assets_dir
Vincent QB's avatar
Vincent QB committed
7
8


David Pollack's avatar
David Pollack committed
9
class Test_LoadSave(unittest.TestCase):
10
    test_dirpath, test_dir = create_temp_assets_dir()
11
12
    test_filepath = os.path.join(test_dirpath, "assets",
                                 "steam-train-whistle-daniel_simon.mp3")
Vincent QB's avatar
Vincent QB committed
13
14
    test_filepath_wav = os.path.join(test_dirpath, "assets",
                                     "steam-train-whistle-daniel_simon.wav")
Soumith Chintala's avatar
Soumith Chintala committed
15

David Pollack's avatar
David Pollack committed
16
    def test_1_save(self):
17
        for backend in BACKENDS_MP3:
Vincent QB's avatar
Vincent QB committed
18
19
20
21
            with self.subTest():
                with AudioBackendScope(backend):
                    self._test_1_save(self.test_filepath, False)

22
        for backend in BACKENDS:
Vincent QB's avatar
Vincent QB committed
23
24
25
26
27
            with self.subTest():
                with AudioBackendScope(backend):
                    self._test_1_save(self.test_filepath_wav, True)

    def _test_1_save(self, test_filepath, normalization):
David Pollack's avatar
David Pollack committed
28
        # load signal
Vincent QB's avatar
Vincent QB committed
29
        x, sr = torchaudio.load(test_filepath, normalization=normalization)
David Pollack's avatar
David Pollack committed
30
31
32
33
34
35
36
37
38
39
40
41
42
43

        # check save
        new_filepath = os.path.join(self.test_dirpath, "test.wav")
        torchaudio.save(new_filepath, x, sr)
        self.assertTrue(os.path.isfile(new_filepath))
        os.unlink(new_filepath)

        # check automatic normalization
        x /= 1 << 31
        torchaudio.save(new_filepath, x, sr)
        self.assertTrue(os.path.isfile(new_filepath))
        os.unlink(new_filepath)

        # test save 1d tensor
David Pollack's avatar
David Pollack committed
44
        x = x[0, :]  # get mono signal
Soumith Chintala's avatar
Soumith Chintala committed
45
        x.squeeze_()  # remove channel dim
David Pollack's avatar
David Pollack committed
46
47
48
49
50
51
        torchaudio.save(new_filepath, x, sr)
        self.assertTrue(os.path.isfile(new_filepath))
        os.unlink(new_filepath)

        # don't allow invalid sizes as inputs
        with self.assertRaises(ValueError):
David Pollack's avatar
David Pollack committed
52
            x.unsqueeze_(1)  # L x C not C x L
David Pollack's avatar
David Pollack committed
53
54
55
56
57
            torchaudio.save(new_filepath, x, sr)

        with self.assertRaises(ValueError):
            x.squeeze_()
            x.unsqueeze_(1)
Soumith Chintala's avatar
Soumith Chintala committed
58
            x.unsqueeze_(0)  # 1 x L x 1
David Pollack's avatar
David Pollack committed
59
60
61
62
            torchaudio.save(new_filepath, x, sr)

        # don't save to folders that don't exist
        with self.assertRaises(OSError):
63
64
            new_filepath = os.path.join(self.test_dirpath, "no-path",
                                        "test.wav")
David Pollack's avatar
David Pollack committed
65
            torchaudio.save(new_filepath, x, sr)
Soumith Chintala's avatar
Soumith Chintala committed
66

Vincent QB's avatar
Vincent QB committed
67
    def test_1_save_sine(self):
68
        for backend in BACKENDS:
Vincent QB's avatar
Vincent QB committed
69
70
71
72
73
74
            with self.subTest():
                with AudioBackendScope(backend):
                    self._test_1_save_sine()

    def _test_1_save_sine(self):

75
        # save created file
76
77
        sinewave_filepath = os.path.join(self.test_dirpath, "assets",
                                         "sinewave.wav")
78
79
80
81
        sr = 16000
        freq = 440
        volume = 0.3

82
        y = (torch.cos(
83
            2 * math.pi * torch.arange(0, 4 * sr).float() * freq / sr))
David Pollack's avatar
David Pollack committed
84
        y.unsqueeze_(0)
85
        # y is between -1 and 1, so must scale
David Pollack's avatar
David Pollack committed
86
        y = (y * volume * (2**31)).long()
87
88
        torchaudio.save(sinewave_filepath, y, sr)
        self.assertTrue(os.path.isfile(sinewave_filepath))
89

90
        # test precision
David Pollack's avatar
David Pollack committed
91
        new_precision = 32
92
        new_filepath = os.path.join(self.test_dirpath, "test.wav")
David Pollack's avatar
David Pollack committed
93
94
95
96
97
        si, ei = torchaudio.info(sinewave_filepath)
        torchaudio.save(new_filepath, y, sr, new_precision)
        si32, ei32 = torchaudio.info(new_filepath)
        self.assertEqual(si.precision, 16)
        self.assertEqual(si32.precision, new_precision)
98
99
        os.unlink(new_filepath)

David Pollack's avatar
David Pollack committed
100
    def test_2_load(self):
101
        for backend in BACKENDS_MP3:
Vincent QB's avatar
Vincent QB committed
102
103
104
105
            with self.subTest():
                with AudioBackendScope(backend):
                    self._test_2_load(self.test_filepath, 278756)

106
        for backend in BACKENDS:
Vincent QB's avatar
Vincent QB committed
107
108
109
110
111
            with self.subTest():
                with AudioBackendScope(backend):
                    self._test_2_load(self.test_filepath_wav, 276858)

    def _test_2_load(self, test_filepath, length):
David Pollack's avatar
David Pollack committed
112
        # check normal loading
Vincent QB's avatar
Vincent QB committed
113
        x, sr = torchaudio.load(test_filepath)
David Pollack's avatar
David Pollack committed
114
        self.assertEqual(sr, 44100)
Vincent QB's avatar
Vincent QB committed
115
        self.assertEqual(x.size(), (2, length))
David Pollack's avatar
David Pollack committed
116
117
118

        # check offset
        offset = 15
Vincent QB's avatar
Vincent QB committed
119
120
        x, _ = torchaudio.load(test_filepath)
        x_offset, _ = torchaudio.load(test_filepath, offset=offset)
121
        self.assertTrue(x[:, offset:].allclose(x_offset))
David Pollack's avatar
David Pollack committed
122
123
124

        # check number of frames
        n = 201
Vincent QB's avatar
Vincent QB committed
125
        x, _ = torchaudio.load(test_filepath, num_frames=n)
David Pollack's avatar
David Pollack committed
126
127
128
        self.assertTrue(x.size(), (2, n))

        # check channels first
Vincent QB's avatar
Vincent QB committed
129
130
        x, _ = torchaudio.load(test_filepath, channels_first=False)
        self.assertEqual(x.size(), (length, 2))
David Pollack's avatar
David Pollack committed
131
132
133
134
135
136
137
138
139
140

        # check raising errors
        with self.assertRaises(OSError):
            torchaudio.load("file-does-not-exist.mp3")

        with self.assertRaises(OSError):
            tdir = os.path.join(
                os.path.dirname(self.test_dirpath), "torchaudio")
            torchaudio.load(tdir)

Vincent QB's avatar
Vincent QB committed
141
    def test_2_load_nonormalization(self):
142
        for backend in BACKENDS_MP3:
Vincent QB's avatar
Vincent QB committed
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
            with self.subTest():
                with AudioBackendScope(backend):
                    self._test_2_load_nonormalization(self.test_filepath, 278756)

    def _test_2_load_nonormalization(self, test_filepath, length):

        # check no normalizing
        x, _ = torchaudio.load(test_filepath, normalization=False)
        self.assertTrue(x.min() <= -1.0)
        self.assertTrue(x.max() >= 1.0)

        # check different input tensor type
        x, _ = torchaudio.load(test_filepath, torch.LongTensor(), normalization=False)
        self.assertTrue(isinstance(x, torch.LongTensor))

David Pollack's avatar
David Pollack committed
158
    def test_3_load_and_save_is_identity(self):
159
        for backend in BACKENDS:
Vincent QB's avatar
Vincent QB committed
160
161
162
163
164
            with self.subTest():
                with AudioBackendScope(backend):
                    self._test_3_load_and_save_is_identity()

    def _test_3_load_and_save_is_identity(self):
165
166
167
168
169
170
171
172
173
        input_path = os.path.join(self.test_dirpath, 'assets', 'sinewave.wav')
        tensor, sample_rate = torchaudio.load(input_path)
        output_path = os.path.join(self.test_dirpath, 'test.wav')
        torchaudio.save(output_path, tensor, sample_rate)
        tensor2, sample_rate2 = torchaudio.load(output_path)
        self.assertTrue(tensor.allclose(tensor2))
        self.assertEqual(sample_rate, sample_rate2)
        os.unlink(output_path)

174
    @unittest.skipIf(set(["sox", "soundfile"]) not in set(BACKENDS), "sox and soundfile are not available")
Vincent QB's avatar
Vincent QB committed
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
    def test_3_load_and_save_is_identity_across_backend(self):
        with self.subTest():
            self._test_3_load_and_save_is_identity_across_backend("sox", "soundfile")
        with self.subTest():
            self._test_3_load_and_save_is_identity_across_backend("soundfile", "sox")

    def _test_3_load_and_save_is_identity_across_backend(self, backend1, backend2):
        with AudioBackendScope(backend1):

            input_path = os.path.join(self.test_dirpath, 'assets', 'sinewave.wav')
            tensor1, sample_rate1 = torchaudio.load(input_path)

            output_path = os.path.join(self.test_dirpath, 'test.wav')
            torchaudio.save(output_path, tensor1, sample_rate1)

        with AudioBackendScope(backend2):
            tensor2, sample_rate2 = torchaudio.load(output_path)

        self.assertTrue(tensor1.allclose(tensor2))
        self.assertEqual(sample_rate1, sample_rate2)
        os.unlink(output_path)

David Pollack's avatar
David Pollack committed
197
    def test_4_load_partial(self):
198
        for backend in BACKENDS_MP3:
Vincent QB's avatar
Vincent QB committed
199
200
201
202
203
            with self.subTest():
                with AudioBackendScope(backend):
                    self._test_4_load_partial()

    def _test_4_load_partial(self):
David Pollack's avatar
David Pollack committed
204
205
        num_frames = 101
        offset = 201
206
207
208
209
        # load entire mono sinewave wav file, load a partial copy and then compare
        input_sine_path = os.path.join(self.test_dirpath, 'assets', 'sinewave.wav')
        x_sine_full, sr_sine = torchaudio.load(input_sine_path)
        x_sine_part, _ = torchaudio.load(input_sine_path, num_frames=num_frames, offset=offset)
210
        l1_error = x_sine_full[:, offset:(num_frames + offset)].sub(x_sine_part).abs().sum().item()
211
        # test for the correct number of samples and that the correct portion was loaded
David Pollack's avatar
David Pollack committed
212
        self.assertEqual(x_sine_part.size(1), num_frames)
213
214
215
216
217
218
219
        self.assertEqual(l1_error, 0.)
        # create a two channel version of this wavefile
        x_2ch_sine = x_sine_full.repeat(1, 2)
        out_2ch_sine_path = os.path.join(self.test_dirpath, 'assets', '2ch_sinewave.wav')
        torchaudio.save(out_2ch_sine_path, x_2ch_sine, sr_sine)
        x_2ch_sine_load, _ = torchaudio.load(out_2ch_sine_path, num_frames=num_frames, offset=offset)
        os.unlink(out_2ch_sine_path)
David Pollack's avatar
David Pollack committed
220
        l1_error = x_2ch_sine_load.sub(x_2ch_sine[:, offset:(offset + num_frames)]).abs().sum().item()
221
222
223
224
225
        self.assertEqual(l1_error, 0.)

        # test with two channel mp3
        x_2ch_full, sr_2ch = torchaudio.load(self.test_filepath, normalization=True)
        x_2ch_part, _ = torchaudio.load(self.test_filepath, normalization=True, num_frames=num_frames, offset=offset)
226
        l1_error = x_2ch_full[:, offset:(offset + num_frames)].sub(x_2ch_part).abs().sum().item()
David Pollack's avatar
David Pollack committed
227
        self.assertEqual(x_2ch_part.size(1), num_frames)
228
229
230
231
232
        self.assertEqual(l1_error, 0.)

        # check behavior if number of samples would exceed file length
        offset_ns = 300
        x_ns, _ = torchaudio.load(input_sine_path, num_frames=100000, offset=offset_ns)
David Pollack's avatar
David Pollack committed
233
        self.assertEqual(x_ns.size(1), x_sine_full.size(1) - offset_ns)
234
235
236
237
238

        # check when offset is beyond the end of the file
        with self.assertRaises(RuntimeError):
            torchaudio.load(input_sine_path, offset=100000)

David Pollack's avatar
David Pollack committed
239
    def test_5_get_info(self):
240
        for backend in BACKENDS:
Vincent QB's avatar
Vincent QB committed
241
242
243
244
245
            with self.subTest():
                with AudioBackendScope(backend):
                    self._test_5_get_info()

    def _test_5_get_info(self):
246
        input_path = os.path.join(self.test_dirpath, 'assets', 'sinewave.wav')
David Pollack's avatar
David Pollack committed
247
248
249
250
251
252
        channels, samples, rate, precision = (1, 64000, 16000, 16)
        si, ei = torchaudio.info(input_path)
        self.assertEqual(si.channels, channels)
        self.assertEqual(si.length, samples)
        self.assertEqual(si.rate, rate)
        self.assertEqual(ei.bits_per_sample, precision)
Soumith Chintala's avatar
Soumith Chintala committed
253

David Pollack's avatar
David Pollack committed
254
255
if __name__ == '__main__':
    unittest.main()