Repository: pytorch/audio

Issue:
Save signal with AMR-NB format
Conversion with `sox input.wav -r 8k ouptut.amr-nb` command works fine, `libsox-fmt-all` is installed.

```python
# saving to GSM works:
torchaudio.save('output.gsm', signal, 8000)

# saving to AMR-NB does not:
torchaudio.save('output.amr-nb', signal, 8000)
#formats: no handler for given file type `amr-nb'
#Traceback (most recent call last):
#  File "<stdin>", line 1, in <module>
#  File "/miniconda/lib/python3.7/site-packages/torchaudio/__init__.py", line 133, in save
#    return save_encinfo(filepath, src, channels_first, si)
#  File "/miniconda/lib/python3.7/site-packages/torchaudio/__init__.py", line 202, in save_encinfo
#    _torch_sox.write_audio_file(filepath, src, signalinfo, encodinginfo, filetype)
#RuntimeError: Error writing audio file: could not open file for writing
```


Discussion:
It seems that somehow AMR-NB was not present at torchaudio compile-time

Resolving patch:
diff --git a/build_tools/setup_helpers/extension.py b/build_tools/setup_helpers/extension.py
--- a/build_tools/setup_helpers/extension.py
+++ b/build_tools/setup_helpers/extension.py
@@ -89,6 +89,8 @@ def _get_extra_objects():
             'libvorbisfile.a',
             'libvorbis.a',
             'libogg.a',
+            'libopencore-amrnb.a',
+            'libopencore-amrwb.a',
         ]
         for lib in libs:
             objs.append(str(_TP_INSTALL_DIR / 'lib' / lib))
diff --git a/torchaudio/backend/sox_io_backend.py b/torchaudio/backend/sox_io_backend.py
--- a/torchaudio/backend/sox_io_backend.py
+++ b/torchaudio/backend/sox_io_backend.py
@@ -40,18 +40,19 @@ def load(
         This function can handle all the codecs that underlying libsox can handle,
         however it is tested on the following formats;
 
-        * WAV
+        * WAV, AMB
 
             * 32-bit floating-point
             * 32-bit signed integer
             * 16-bit signed integer
-            * 8-bit unsigned integer
+            * 8-bit unsigned integer (WAV only)
 
         * MP3
         * FLAC
         * OGG/VORBIS
         * OPUS
         * SPHERE
+        * AMR-NB
 
         To load ``MP3``, ``FLAC``, ``OGG/VORBIS``, ``OPUS`` and other codecs ``libsox`` does not
         handle natively, your installation of ``torchaudio`` has to be linked to ``libsox``
@@ -119,7 +120,7 @@ def save(
     Note:
         Supported formats are;
 
-        * WAV
+        * WAV, AMB
 
             * 32-bit floating-point
             * 32-bit signed integer
@@ -130,6 +131,7 @@ def save(
         * FLAC
         * OGG/VORBIS
         * SPHERE
+        * AMR-NB
 
         To save ``MP3``, ``FLAC``, ``OGG/VORBIS``, and other codecs ``libsox`` does not
         handle natively, your installation of ``torchaudio`` has to be linked to ``libsox``
@@ -160,7 +162,7 @@ def save(
     filepath = str(filepath)
     if compression is None:
         ext = str(filepath).split('.')[-1].lower()
-        if ext in ['wav', 'sph']:
+        if ext in ['wav', 'sph', 'amb', 'amr-nb']:
             compression = 0.
         elif ext == 'mp3':
             compression = -4.5


Test patch:
diff --git a/test/torchaudio_unittest/sox_io_backend/info_test.py b/test/torchaudio_unittest/sox_io_backend/info_test.py
--- a/test/torchaudio_unittest/sox_io_backend/info_test.py
+++ b/test/torchaudio_unittest/sox_io_backend/info_test.py
@@ -122,6 +122,36 @@ def test_sphere(self, sample_rate, num_channels):
         assert info.num_frames == sample_rate * duration
         assert info.num_channels == num_channels
 
+    @parameterized.expand(list(itertools.product(
+        ['float32', 'int32', 'int16', 'uint8'],
+        [8000, 16000],
+        [1, 2],
+    )), name_func=name_func)
+    def test_amb(self, dtype, sample_rate, num_channels):
+        """`sox_io_backend.info` can check amb file correctly"""
+        duration = 1
+        path = self.get_temp_path('data.amb')
+        sox_utils.gen_audio_file(
+            path, sample_rate, num_channels,
+            bit_depth=sox_utils.get_bit_depth(dtype), duration=duration)
+        info = sox_io_backend.info(path)
+        assert info.sample_rate == sample_rate
+        assert info.num_frames == sample_rate * duration
+        assert info.num_channels == num_channels
+
+    def test_amr_nb(self):
+        """`sox_io_backend.info` can check amr-nb file correctly"""
+        duration = 1
+        num_channels = 1
+        sample_rate = 8000
+        path = self.get_temp_path('data.amr-nb')
+        sox_utils.gen_audio_file(
+            path, sample_rate=sample_rate, num_channels=num_channels, bit_depth=16, duration=duration)
+        info = sox_io_backend.info(path)
+        assert info.sample_rate == sample_rate
+        assert info.num_frames == sample_rate * duration
+        assert info.num_channels == num_channels
+
 
 @skipIfNoExtension
 class TestInfoOpus(PytorchTestCase):
diff --git a/test/torchaudio_unittest/sox_io_backend/load_test.py b/test/torchaudio_unittest/sox_io_backend/load_test.py
--- a/test/torchaudio_unittest/sox_io_backend/load_test.py
+++ b/test/torchaudio_unittest/sox_io_backend/load_test.py
@@ -142,6 +142,53 @@ def assert_sphere(self, sample_rate, num_channels, duration):
         assert sr == sample_rate
         self.assertEqual(data, data_ref, atol=4e-05, rtol=1.3e-06)
 
+    def assert_amb(self, dtype, sample_rate, num_channels, normalize, duration):
+        """`sox_io_backend.load` can load amb format.
+
+        This test takes the same strategy as mp3 to compare the result
+        """
+        path = self.get_temp_path('1.original.amb')
+        ref_path = self.get_temp_path('2.reference.wav')
+
+        # 1. Generate amb with sox
+        sox_utils.gen_audio_file(
+            path, sample_rate, num_channels,
+            encoding=sox_utils.get_encoding(dtype),
+            bit_depth=sox_utils.get_bit_depth(dtype), duration=duration)
+        # 2. Convert to wav with sox
+        sox_utils.convert_audio_file(path, ref_path)
+        # 3. Load amb with torchaudio
+        data, sr = sox_io_backend.load(path, normalize=normalize)
+        # 4. Load wav with scipy
+        data_ref = load_wav(ref_path, normalize=normalize)[0]
+        # 5. Compare
+        assert sr == sample_rate
+        self.assertEqual(data, data_ref, atol=4e-05, rtol=1.3e-06)
+
+    def assert_amr_nb(self, duration):
+        """`sox_io_backend.load` can load amr-nb format.
+
+        This test takes the same strategy as mp3 to compare the result
+        """
+        sample_rate = 8000
+        num_channels = 1
+        path = self.get_temp_path('1.original.amr-nb')
+        ref_path = self.get_temp_path('2.reference.wav')
+
+        # 1. Generate amr-nb with sox
+        sox_utils.gen_audio_file(
+            path, sample_rate, num_channels,
+            bit_depth=32, duration=duration)
+        # 2. Convert to wav with sox
+        sox_utils.convert_audio_file(path, ref_path)
+        # 3. Load amr-nb with torchaudio
+        data, sr = sox_io_backend.load(path)
+        # 4. Load wav with scipy
+        data_ref = load_wav(ref_path)[0]
+        # 5. Compare
+        assert sr == sample_rate
+        self.assertEqual(data, data_ref, atol=4e-05, rtol=1.3e-06)
+
 
 @skipIfNoExec('sox')
 @skipIfNoExtension
@@ -260,6 +307,20 @@ def test_sphere(self, sample_rate, num_channels):
         """`sox_io_backend.load` can load sph format correctly."""
         self.assert_sphere(sample_rate, num_channels, duration=1)
 
+    @parameterized.expand(list(itertools.product(
+        ['float32', 'int32', 'int16'],
+        [8000, 16000],
+        [1, 2],
+        [False, True],
+    )), name_func=name_func)
+    def test_amb(self, dtype, sample_rate, num_channels, normalize):
+        """`sox_io_backend.load` can load sph format correctly."""
+        self.assert_amb(dtype, sample_rate, num_channels, normalize, duration=1)
+
+    def test_amr_nb(self):
+        """`sox_io_backend.load` can load amr_nb format correctly."""
+        self.assert_amr_nb(duration=1)
+
 
 @skipIfNoExec('sox')
 @skipIfNoExtension
diff --git a/test/torchaudio_unittest/sox_io_backend/save_test.py b/test/torchaudio_unittest/sox_io_backend/save_test.py
--- a/test/torchaudio_unittest/sox_io_backend/save_test.py
+++ b/test/torchaudio_unittest/sox_io_backend/save_test.py
@@ -200,6 +200,68 @@ def assert_sphere(self, sample_rate, num_channels, duration):
 
         self.assertEqual(found, expected)
 
+    def assert_amb(self, dtype, sample_rate, num_channels, duration):
+        """`sox_io_backend.save` can save amb format.
+
+        This test takes the same strategy as mp3 to compare the result
+        """
+        src_path = self.get_temp_path('1.reference.wav')
+        amb_path = self.get_temp_path('2.1.torchaudio.amb')
+        wav_path = self.get_temp_path('2.2.torchaudio.wav')
+        amb_path_sox = self.get_temp_path('3.1.sox.amb')
+        wav_path_sox = self.get_temp_path('3.2.sox.wav')
+
+        # 1. Generate original wav
+        data = get_wav_data(dtype, num_channels, normalize=False, num_frames=duration * sample_rate)
+        save_wav(src_path, data, sample_rate)
+        # 2.1. Convert the original wav to amb with torchaudio
+        sox_io_backend.save(amb_path, load_wav(src_path, normalize=False)[0], sample_rate)
+        # 2.2. Convert the amb to wav with Sox
+        sox_utils.convert_audio_file(amb_path, wav_path)
+        # 2.3. Load
+        found = load_wav(wav_path)[0]
+
+        # 3.1. Convert the original wav to amb with SoX
+        sox_utils.convert_audio_file(src_path, amb_path_sox)
+        # 3.2. Convert the amb to wav with Sox
+        sox_utils.convert_audio_file(amb_path_sox, wav_path_sox)
+        # 3.3. Load
+        expected = load_wav(wav_path_sox)[0]
+
+        self.assertEqual(found, expected)
+
+    def assert_amr_nb(self, duration):
+        """`sox_io_backend.save` can save amr_nb format.
+
+        This test takes the same strategy as mp3 to compare the result
+        """
+        sample_rate = 8000
+        num_channels = 1
+        src_path = self.get_temp_path('1.reference.wav')
+        amr_path = self.get_temp_path('2.1.torchaudio.amr-nb')
+        wav_path = self.get_temp_path('2.2.torchaudio.wav')
+        amr_path_sox = self.get_temp_path('3.1.sox.amr-nb')
+        wav_path_sox = self.get_temp_path('3.2.sox.wav')
+
+        # 1. Generate original wav
+        data = get_wav_data('int16', num_channels, normalize=False, num_frames=duration * sample_rate)
+        save_wav(src_path, data, sample_rate)
+        # 2.1. Convert the original wav to amr_nb with torchaudio
+        sox_io_backend.save(amr_path, load_wav(src_path, normalize=False)[0], sample_rate)
+        # 2.2. Convert the amr_nb to wav with Sox
+        sox_utils.convert_audio_file(amr_path, wav_path)
+        # 2.3. Load
+        found = load_wav(wav_path)[0]
+
+        # 3.1. Convert the original wav to amr_nb with SoX
+        sox_utils.convert_audio_file(src_path, amr_path_sox)
+        # 3.2. Convert the amr_nb to wav with Sox
+        sox_utils.convert_audio_file(amr_path_sox, wav_path_sox)
+        # 3.3. Load
+        expected = load_wav(wav_path_sox)[0]
+
+        self.assertEqual(found, expected)
+
 
 @skipIfNoExec('sox')
 @skipIfNoExtension
@@ -302,6 +364,19 @@ def test_sphere(self, sample_rate, num_channels):
         """`sox_io_backend.save` can save sph format."""
         self.assert_sphere(sample_rate, num_channels, duration=1)
 
+    @parameterized.expand(list(itertools.product(
+        ['float32', 'int32', 'int16', 'uint8'],
+        [8000, 16000],
+        [1, 2],
+    )), name_func=name_func)
+    def test_amb(self, dtype, sample_rate, num_channels):
+        """`sox_io_backend.save` can save amb format."""
+        self.assert_amb(dtype, sample_rate, num_channels, duration=1)
+
+    def test_amr_nb(self):
+        """`sox_io_backend.save` can save amr-nb format."""
+        self.assert_amr_nb(duration=1)
+
 
 @skipIfNoExec('sox')
 @skipIfNoExtension