Repository: pytorch/audio

Issue:
Return `encoding` attribute from `info` for `sox_io` backend
The legacy sox backend exposed libsox's structures (`sox_signalinfo_t` and `sox_encodinginfo_t`) and that allowed to access most of audio metadata. In the transition to the new backends, these structures were abstracted away and we introduced `AudioMetaData` which only contain very basic information.  Now we would like to add some metadata, in this time, with more consistent behaviors across the backends.

We would like to add a `encoding` (string) attribute, which tells the encoding of audio data being inspected.

(See also #1173, #1094)

## Specifications

We will map the following libsox's [`sox_encoding_t` structure](https://fossies.org/dox/sox-14.4.2/sox_8h.html#ab0a659b0258d1989c751ba5415e6a4cd) to string type. The resulting string expression will be made available to user code via [`AudioMetaData` class](https://github.com/pytorch/audio/blob/99ed7183df3df8278cc46de432c31b254e31573e/torchaudio/backend/common.py#L6-L22)

torchaudio Representation | `sox_encoding_t` | PySoundFile subtype
-- | -- | --
`"PCM_S"` | SOX_ENCODING_SIGN2 | PCM_S8, PCM_16, PCM_24, PCM_32
`"PCM_U"` | SOX_ENCODING_UNSIGNED | PCM_U8
 `"PCM_F"` | SOX_ENCODING_FLOAT | FLOAT, DOUBLE
`"ULAW"` | SOX_ENCODING_ULAW | ULAW
`"ALAW"` | SOX_ENCODING_ALAW | ALAW
`"FLAC"` | SOX_ENCODING_FLAC | FLAC (format)
`"MP3"` | SOX_ENCODING_MP3 | 
`"VORBIS"` | SOX_ENCODING_VORBIS | VORBIS
`"AMR_WB"` | SOX_ENCODING_AMR_WB | 
`"AMR_NB"` | SOX_ENCODING_AMR_NB | 
`"OPUS"` | SOX_ENCODING_OPUS 
`"UNKNOWN"` | SOX_ENCODING_UNKNOWN or Others | Others


## Steps to implement

### sox_io backend
1. Implement the mapping
Add a function to perform the conversion from `sox_encoding_t` to `std::string` in [torchaudio/csrc/sox/io.cpp](https://github.com/pytorch/audio/blob/f1d8d1e0da44f6503c01ddcae2e40772b400ea2d/torchaudio/csrc/sox/io.cpp) before the [`get_info`](https://github.com/pytorch/audio/blob/99ed7183df3df8278cc46de432c31b254e31573e/torchaudio/csrc/sox/io.cpp#L39) function. (Put the implementation in anonymous namespace as it will only be used by `get_info` function).
2. Update `SignalInfo` class
Add `encoding` attribute and getter. [[interface](https://github.com/pytorch/audio/blob/f1d8d1e0da44f6503c01ddcae2e40772b400ea2d/torchaudio/csrc/sox/utils.h#L29-L44), [impl](https://github.com/pytorch/audio/blob/master/torchaudio/csrc/sox/utils.cpp#L64-L80)]
3. Update the [`get_info` function](https://github.com/pytorch/audio/blob/f1d8d1e0da44f6503c01ddcae2e40772b400ea2d/torchaudio/csrc/sox/io.cpp#L33-L50).
First retrieve the `sox_encoding_t` as `sf->encoding->encoding`, then pass it to the function defined above to get the string representation. Then return it via `SignalInfo` class.
4. Update the [`AudioMetaData`](https://github.com/pytorch/audio/blob/f1d8d1e0da44f6503c01ddcae2e40772b400ea2d/torchaudio/backend/common.py#L6-L19) class so that `format` attribute is accessible.
5. Update the Python Front end.
Update the [`info` function](https://github.com/pytorch/audio/blob/f1d8d1e0da44f6503c01ddcae2e40772b400ea2d/torchaudio/backend/sox_io_backend.py#L13-L35) so that the `format` attribute is passed to client code.

### soundfile backend
1. Implement mapping in `_soundfile_backend.py` and update [`info`](https://github.com/pytorch/audio/blob/41c76a17c7a191752eada5346dc88ecfe690dcae/torchaudio/backend/_soundfile_backend.py#L53-L79) function.
2. Update test

## Building and testing locally

The [`info_test.py`](https://github.com/pytorch/audio/blob/f1d8d1e0da44f6503c01ddcae2e40772b400ea2d/test/torchaudio_unittest/sox_io_backend/info_test.py) needs to be updated.

To work on this, `torchaudio` needs to be built from source. Use of `conda` environment (anaconda/miniconda) is highly recommended.

Also, build requires `cmake` and nightly build version of PyTorch. Refer to pytorch.org for the installation.
To install `cmake`, do `pip install cmake`.

Once the environment is setup, the following command will build and run the corresponding tests

```
BUILD_SOX=1 python setup.py develop
(cd test && pytest torchaudio_unittest -v -k "info_test"
```


Discussion:

Resolving patch:
diff --git a/torchaudio/backend/_soundfile_backend.py b/torchaudio/backend/_soundfile_backend.py
--- a/torchaudio/backend/_soundfile_backend.py
+++ b/torchaudio/backend/_soundfile_backend.py
@@ -50,6 +50,37 @@
 }
 
 
+def _get_bit_depth(subtype):
+    if subtype not in _SUBTYPE_TO_BITS_PER_SAMPLE:
+        warnings.warn(
+            f"The {subtype} subtype is unknown to TorchAudio. As a result, the bits_per_sample "
+            "attribute will be set to 0. If you are seeing this warning, please "
+            "report by opening an issue on github (after checking for existing/closed ones). "
+            "You may otherwise ignore this warning."
+        )
+    return _SUBTYPE_TO_BITS_PER_SAMPLE.get(subtype, 0)
+
+
+_SUBTYPE_TO_ENCODING = {
+    'PCM_S8': 'PCM_S',
+    'PCM_16': 'PCM_S',
+    'PCM_24': 'PCM_S',
+    'PCM_32': 'PCM_S',
+    'PCM_U8': 'PCM_U',
+    'FLOAT': 'PCM_F',
+    'DOUBLE': 'PCM_F',
+    'ULAW': 'ULAW',
+    'ALAW': 'ALAW',
+    'VORBIS': 'VORBIS',
+}
+
+
+def _get_encoding(format: str, subtype: str):
+    if format == 'FLAC':
+        return 'FLAC'
+    return _SUBTYPE_TO_ENCODING.get(subtype, 'UNKNOWN')
+
+
 @_mod_utils.requires_module("soundfile")
 def info(filepath: str, format: Optional[str] = None) -> AudioMetaData:
     """Get signal information of an audio file.
@@ -68,15 +99,13 @@ def info(filepath: str, format: Optional[str] = None) -> AudioMetaData:
         AudioMetaData: meta data of the given audio.
     """
     sinfo = soundfile.info(filepath)
-    if sinfo.subtype not in _SUBTYPE_TO_BITS_PER_SAMPLE:
-        warnings.warn(
-            f"The {sinfo.subtype} subtype is unknown to TorchAudio. As a result, the bits_per_sample "
-            "attribute will be set to 0. If you are seeing this warning, please "
-            "report by opening an issue on github (after checking for existing/closed ones). "
-            "You may otherwise ignore this warning."
-        )
-    bits_per_sample = _SUBTYPE_TO_BITS_PER_SAMPLE.get(sinfo.subtype, 0)
-    return AudioMetaData(sinfo.samplerate, sinfo.frames, sinfo.channels, bits_per_sample=bits_per_sample)
+    return AudioMetaData(
+        sinfo.samplerate,
+        sinfo.frames,
+        sinfo.channels,
+        bits_per_sample=_get_bit_depth(sinfo.subtype),
+        encoding=_get_encoding(sinfo.format, sinfo.subtype),
+    )
 
 
 _SUBTYPE2DTYPE = {
diff --git a/torchaudio/backend/common.py b/torchaudio/backend/common.py
--- a/torchaudio/backend/common.py
+++ b/torchaudio/backend/common.py
@@ -14,12 +14,21 @@ class AudioMetaData:
     :ivar int num_channels: The number of channels
     :ivar int bits_per_sample: The number of bits per sample. This is 0 for lossy formats,
         or when it cannot be accurately inferred.
+    :ivar str encoding: Audio encoding.
     """
-    def __init__(self, sample_rate: int, num_frames: int, num_channels: int, bits_per_sample: int):
+    def __init__(
+            self,
+            sample_rate: int,
+            num_frames: int,
+            num_channels: int,
+            bits_per_sample: int,
+            encoding: str,
+    ):
         self.sample_rate = sample_rate
         self.num_frames = num_frames
         self.num_channels = num_channels
         self.bits_per_sample = bits_per_sample
+        self.encoding = encoding
 
 
 @_mod_utils.deprecated('Please migrate to `AudioMetaData`.', '0.9.0')
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
@@ -17,17 +17,15 @@ def _info(
         format: Optional[str] = None,
 ) -> AudioMetaData:
     if hasattr(filepath, 'read'):
-        sinfo = torchaudio._torchaudio.get_info_fileobj(
-            filepath, format)
-        sample_rate, num_channels, num_frames, bits_per_sample = sinfo
-        return AudioMetaData(
-            sample_rate, num_frames, num_channels, bits_per_sample)
+        sinfo = torchaudio._torchaudio.get_info_fileobj(filepath, format)
+        return AudioMetaData(*sinfo)
     sinfo = torch.ops.torchaudio.sox_io_get_info(os.fspath(filepath), format)
     return AudioMetaData(
         sinfo.get_sample_rate(),
         sinfo.get_num_frames(),
         sinfo.get_num_channels(),
         sinfo.get_bits_per_sample(),
+        sinfo.get_encoding(),
     )
 
 
@@ -69,7 +67,8 @@ def info(
         sinfo.get_sample_rate(),
         sinfo.get_num_frames(),
         sinfo.get_num_channels(),
-        sinfo.get_bits_per_sample())
+        sinfo.get_bits_per_sample(),
+        sinfo.get_encoding())
 
 
 @_mod_utils.requires_module('torchaudio._torchaudio')


Test patch:
diff --git a/test/torchaudio_unittest/soundfile_backend/info_test.py b/test/torchaudio_unittest/soundfile_backend/info_test.py
--- a/test/torchaudio_unittest/soundfile_backend/info_test.py
+++ b/test/torchaudio_unittest/soundfile_backend/info_test.py
@@ -13,6 +13,8 @@
     get_wav_data,
     save_wav,
 )
+# TODO refactor and move these to common location
+from torchaudio_unittest.sox_io_backend.info_test import get_encoding, get_bits_per_sample
 from .common import skipIfFormatNotSupported, parameterize
 
 if _mod_utils.is_module_available("soundfile"):
@@ -22,11 +24,10 @@
 @skipIfNoModule("soundfile")
 class TestInfo(TempDirMixin, PytorchTestCase):
     @parameterize(
-        [("float32", 32), ("int32", 32), ("int16", 16), ("uint8", 8)], [8000, 16000], [1, 2],
+        ["float32", "int32", "int16", "uint8"], [8000, 16000], [1, 2],
     )
-    def test_wav(self, dtype_and_bit_depth, sample_rate, num_channels):
+    def test_wav(self, dtype, sample_rate, num_channels):
         """`soundfile_backend.info` can check wav file correctly"""
-        dtype, bits_per_sample = dtype_and_bit_depth
         duration = 1
         path = self.get_temp_path("data.wav")
         data = get_wav_data(
@@ -37,25 +38,8 @@ def test_wav(self, dtype_and_bit_depth, sample_rate, num_channels):
         assert info.sample_rate == sample_rate
         assert info.num_frames == sample_rate * duration
         assert info.num_channels == num_channels
-        assert info.bits_per_sample == bits_per_sample
-
-    @parameterize(
-        [("float32", 32), ("int32", 32), ("int16", 16), ("uint8", 8)], [8000, 16000], [1, 2],
-    )
-    def test_wav_multiple_channels(self, dtype_and_bit_depth, sample_rate, num_channels):
-        """`soundfile_backend.info` can check wav file with channels more than 2 correctly"""
-        dtype, bits_per_sample = dtype_and_bit_depth
-        duration = 1
-        path = self.get_temp_path("data.wav")
-        data = get_wav_data(
-            dtype, num_channels, normalize=False, num_frames=duration * sample_rate
-        )
-        save_wav(path, data, sample_rate)
-        info = soundfile_backend.info(path)
-        assert info.sample_rate == sample_rate
-        assert info.num_frames == sample_rate * duration
-        assert info.num_channels == num_channels
-        assert info.bits_per_sample == bits_per_sample
+        assert info.bits_per_sample == get_bits_per_sample("wav", dtype)
+        assert info.encoding == get_encoding("wav", dtype)
 
     @parameterize([8000, 16000], [1, 2])
     @skipIfFormatNotSupported("FLAC")
@@ -72,6 +56,7 @@ def test_flac(self, sample_rate, num_channels):
         assert info.num_frames == num_frames
         assert info.num_channels == num_channels
         assert info.bits_per_sample == 16
+        assert info.encoding == "FLAC"
 
     @parameterize([8000, 16000], [1, 2])
     @skipIfFormatNotSupported("OGG")
@@ -88,6 +73,7 @@ def test_ogg(self, sample_rate, num_channels):
         assert info.num_frames == sample_rate * duration
         assert info.num_channels == num_channels
         assert info.bits_per_sample == 0
+        assert info.encoding == "VORBIS"
 
     @parameterize([8000, 16000], [1, 2], [('PCM_24', 24), ('PCM_32', 32)])
     @skipIfFormatNotSupported("NIST")
@@ -105,6 +91,7 @@ def test_sphere(self, sample_rate, num_channels, subtype_and_bit_depth):
         assert info.num_frames == sample_rate * duration
         assert info.num_channels == num_channels
         assert info.bits_per_sample == bits_per_sample
+        assert info.encoding == "PCM_S"
 
     def test_unknown_subtype_warning(self):
         """soundfile_backend.info issues a warning when the subtype is unknown
@@ -118,6 +105,7 @@ class MockSoundFileInfo:
                 frames = 356
                 channels = 2
                 subtype = 'UNSEEN_SUBTYPE'
+                format = 'UNKNOWN'
             return MockSoundFileInfo()
 
         with patch("soundfile.info", _mock_info_func):
@@ -147,6 +135,7 @@ def _test_fileobj(self, ext, subtype, bits_per_sample):
         assert info.num_frames == num_frames
         assert info.num_channels == num_channels
         assert info.bits_per_sample == bits_per_sample
+        assert info.encoding == "FLAC" if ext == 'flac' else "PCM_S"
 
     def test_fileobj_wav(self):
         """Loading audio via file-like object works"""
@@ -179,6 +168,7 @@ def _test_tarobj(self, ext, subtype, bits_per_sample):
         assert info.num_frames == num_frames
         assert info.num_channels == num_channels
         assert info.bits_per_sample == bits_per_sample
+        assert info.encoding == "FLAC" if ext == 'flac' else "PCM_S"
 
     def test_tarobj_wav(self):
         """Query compressed audio via file-like object works"""
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
@@ -1,4 +1,5 @@
 import io
+import os
 import itertools
 import tarfile
 
@@ -27,6 +28,30 @@
     import requests
 
 
+def get_encoding(ext, dtype):
+    exts = {
+        'mp3',
+        'flac',
+        'vorbis',
+    }
+    encodings = {
+        'float32': 'PCM_F',
+        'int32': 'PCM_S',
+        'int16': 'PCM_S',
+        'uint8': 'PCM_U',
+    }
+    return ext.upper() if ext in exts else encodings[dtype]
+
+
+def get_bits_per_sample(ext, dtype):
+    bits_per_samples = {
+        'flac': 24,
+        'mp3': 0,
+        'vorbis': 0,
+    }
+    return bits_per_samples.get(ext, sox_utils.get_bit_depth(dtype))
+
+
 @skipIfNoExec('sox')
 @skipIfNoExtension
 class TestInfo(TempDirMixin, PytorchTestCase):
@@ -46,6 +71,7 @@ def test_wav(self, dtype, sample_rate, num_channels):
         assert info.num_frames == sample_rate * duration
         assert info.num_channels == num_channels
         assert info.bits_per_sample == sox_utils.get_bit_depth(dtype)
+        assert info.encoding == get_encoding('wav', dtype)
 
     @parameterized.expand(list(itertools.product(
         ['float32', 'int32', 'int16', 'uint8'],
@@ -63,6 +89,7 @@ def test_wav_multiple_channels(self, dtype, sample_rate, num_channels):
         assert info.num_frames == sample_rate * duration
         assert info.num_channels == num_channels
         assert info.bits_per_sample == sox_utils.get_bit_depth(dtype)
+        assert info.encoding == get_encoding('wav', dtype)
 
     @parameterized.expand(list(itertools.product(
         [8000, 16000],
@@ -83,6 +110,7 @@ def test_mp3(self, sample_rate, num_channels, bit_rate):
         # assert info.num_frames == sample_rate * duration
         assert info.num_channels == num_channels
         assert info.bits_per_sample == 0  # bit_per_sample is irrelevant for compressed formats
+        assert info.encoding == "MP3"
 
     @parameterized.expand(list(itertools.product(
         [8000, 16000],
@@ -102,6 +130,7 @@ def test_flac(self, sample_rate, num_channels, compression_level):
         assert info.num_frames == sample_rate * duration
         assert info.num_channels == num_channels
         assert info.bits_per_sample == 24  # FLAC standard
+        assert info.encoding == "FLAC"
 
     @parameterized.expand(list(itertools.product(
         [8000, 16000],
@@ -121,6 +150,7 @@ def test_vorbis(self, sample_rate, num_channels, quality_level):
         assert info.num_frames == sample_rate * duration
         assert info.num_channels == num_channels
         assert info.bits_per_sample == 0  # bit_per_sample is irrelevant for compressed formats
+        assert info.encoding == "VORBIS"
 
     @parameterized.expand(list(itertools.product(
         [8000, 16000],
@@ -137,9 +167,10 @@ def test_sphere(self, sample_rate, num_channels, bits_per_sample):
         assert info.num_frames == sample_rate * duration
         assert info.num_channels == num_channels
         assert info.bits_per_sample == bits_per_sample
+        assert info.encoding == "PCM_S"
 
     @parameterized.expand(list(itertools.product(
-        ['float32', 'int32', 'int16', 'uint8'],
+        ['int32', 'int16', 'uint8'],
         [8000, 16000],
         [1, 2],
     )), name_func=name_func)
@@ -156,6 +187,7 @@ def test_amb(self, dtype, sample_rate, num_channels):
         assert info.num_frames == sample_rate * duration
         assert info.num_channels == num_channels
         assert info.bits_per_sample == bits_per_sample
+        assert info.encoding == get_encoding("amb", dtype)
 
     def test_amr_nb(self):
         """`sox_io_backend.info` can check amr-nb file correctly"""
@@ -171,6 +203,41 @@ def test_amr_nb(self):
         assert info.num_frames == sample_rate * duration
         assert info.num_channels == num_channels
         assert info.bits_per_sample == 0
+        assert info.encoding == "AMR_NB"
+
+    def test_ulaw(self):
+        """`sox_io_backend.info` can check ulaw file correctly"""
+        duration = 1
+        num_channels = 1
+        sample_rate = 8000
+        path = self.get_temp_path('data.wav')
+        sox_utils.gen_audio_file(
+            path, sample_rate=sample_rate, num_channels=num_channels,
+            bit_depth=8, encoding='u-law',
+            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
+        assert info.bits_per_sample == 8
+        assert info.encoding == "ULAW"
+
+    def test_alaw(self):
+        """`sox_io_backend.info` can check ulaw file correctly"""
+        duration = 1
+        num_channels = 1
+        sample_rate = 8000
+        path = self.get_temp_path('data.wav')
+        sox_utils.gen_audio_file(
+            path, sample_rate=sample_rate, num_channels=num_channels,
+            bit_depth=8, encoding='a-law',
+            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
+        assert info.bits_per_sample == 8
+        assert info.encoding == "ALAW"
 
 
 @skipIfNoExtension
@@ -188,6 +255,7 @@ def test_opus(self, bitrate, num_channels, compression_level):
         assert info.num_frames == 32768
         assert info.num_channels == num_channels
         assert info.bits_per_sample == 0  # bit_per_sample is irrelevant for compressed formats
+        assert info.encoding == "OPUS"
 
 
 @skipIfNoExtension
@@ -205,144 +273,193 @@ def test_mp3(self):
         path = get_asset_path("mp3_without_ext")
         sinfo = sox_io_backend.info(path, format="mp3")
         assert sinfo.sample_rate == 16000
+        assert sinfo.num_frames == 81216
+        assert sinfo.num_channels == 1
         assert sinfo.bits_per_sample == 0  # bit_per_sample is irrelevant for compressed formats
+        assert sinfo.encoding == "MP3"
+
+
+class FileObjTestBase(TempDirMixin):
+    def _gen_file(self, ext, dtype, sample_rate, num_channels, num_frames):
+        path = self.get_temp_path(f'test.{ext}')
+        bit_depth = sox_utils.get_bit_depth(dtype)
+        duration = num_frames / sample_rate
+
+        sox_utils.gen_audio_file(
+            path, sample_rate, num_channels=num_channels,
+            encoding=sox_utils.get_encoding(dtype),
+            bit_depth=bit_depth,
+            duration=duration)
+        return path
 
 
 @skipIfNoExtension
 @skipIfNoExec('sox')
-class TestFileObject(TempDirMixin, PytorchTestCase):
+class TestFileObject(FileObjTestBase, PytorchTestCase):
+    def _query_fileobj(self, ext, dtype, sample_rate, num_channels, num_frames):
+        path = self._gen_file(ext, dtype, sample_rate, num_channels, num_frames)
+        format_ = ext if ext in ['mp3'] else None
+        with open(path, 'rb') as fileobj:
+            return sox_io_backend.info(fileobj, format_)
+
+    def _query_bytesio(self, ext, dtype, sample_rate, num_channels, num_frames):
+        path = self._gen_file(ext, dtype, sample_rate, num_channels, num_frames)
+        format_ = ext if ext in ['mp3'] else None
+        with open(path, 'rb') as file_:
+            fileobj = io.BytesIO(file_.read())
+        return sox_io_backend.info(fileobj, format_)
+
+    def _query_tarfile(self, ext, dtype, sample_rate, num_channels, num_frames):
+        audio_path = self._gen_file(ext, dtype, sample_rate, num_channels, num_frames)
+        audio_file = os.path.basename(audio_path)
+        archive_path = self.get_temp_path('archive.tar.gz')
+        with tarfile.TarFile(archive_path, 'w') as tarobj:
+            tarobj.add(audio_path, arcname=audio_file)
+        format_ = ext if ext in ['mp3'] else None
+        with tarfile.TarFile(archive_path, 'r') as tarobj:
+            fileobj = tarobj.extractfile(audio_file)
+            return sox_io_backend.info(fileobj, format_)
+
     @parameterized.expand([
-        ('wav', 32),
-        ('mp3', 0),
-        ('flac', 24),
-        ('vorbis', 0),
-        ('amb', 32),
+        ('wav', "float32"),
+        ('wav', "int32"),
+        ('wav', "int16"),
+        ('wav', "uint8"),
+        ('mp3', "float32"),
+        ('flac', "float32"),
+        ('vorbis', "float32"),
+        ('amb', "int16"),
     ])
-    def test_fileobj(self, ext, bits_per_sample):
+    def test_fileobj(self, ext, dtype):
         """Querying audio via file object works"""
         sample_rate = 16000
+        num_frames = 3 * sample_rate
         num_channels = 2
-        duration = 3
-        format_ = ext if ext in ['mp3'] else None
-        path = self.get_temp_path(f'test.{ext}')
-
-        sox_utils.gen_audio_file(
-            path, sample_rate, num_channels=2,
-            duration=duration)
+        sinfo = self._query_fileobj(ext, dtype, sample_rate, num_channels, num_frames)
 
-        with open(path, 'rb') as fileobj:
-            sinfo = sox_io_backend.info(fileobj, format_)
+        bits_per_sample = get_bits_per_sample(ext, dtype)
+        num_frames = 0 if ext in ['mp3', 'vorbis'] else num_frames
 
         assert sinfo.sample_rate == sample_rate
         assert sinfo.num_channels == num_channels
-        if ext not in ['mp3', 'vorbis']:  # these container formats do not have length info
-            assert sinfo.num_frames == sample_rate * duration
+        assert sinfo.num_frames == num_frames
         assert sinfo.bits_per_sample == bits_per_sample
+        assert sinfo.encoding == get_encoding(ext, dtype)
 
-    def _test_bytesio(self, ext, bits_per_sample, duration):
+    @parameterized.expand([
+        ('wav', "float32"),
+        ('wav', "int32"),
+        ('wav', "int16"),
+        ('wav', "uint8"),
+        ('mp3', "float32"),
+        ('flac', "float32"),
+        ('vorbis', "float32"),
+        ('amb', "int16"),
+    ])
+    def test_bytesio(self, ext, dtype):
+        """Querying audio via ByteIO object works for small data"""
         sample_rate = 16000
+        num_frames = 3 * sample_rate
         num_channels = 2
-        format_ = ext if ext in ['mp3'] else None
-        path = self.get_temp_path(f'test.{ext}')
+        sinfo = self._query_bytesio(ext, dtype, sample_rate, num_channels, num_frames)
 
-        sox_utils.gen_audio_file(
-            path, sample_rate, num_channels=2,
-            duration=duration)
-
-        with open(path, 'rb') as file_:
-            fileobj = io.BytesIO(file_.read())
-        sinfo = sox_io_backend.info(fileobj, format_)
+        bits_per_sample = get_bits_per_sample(ext, dtype)
+        num_frames = 0 if ext in ['mp3', 'vorbis'] else num_frames
 
         assert sinfo.sample_rate == sample_rate
         assert sinfo.num_channels == num_channels
-        if ext not in ['mp3', 'vorbis']:  # these container formats do not have length info
-            assert sinfo.num_frames == sample_rate * duration
+        assert sinfo.num_frames == num_frames
         assert sinfo.bits_per_sample == bits_per_sample
+        assert sinfo.encoding == get_encoding(ext, dtype)
 
     @parameterized.expand([
-        ('wav', 32),
-        ('mp3', 0),
-        ('flac', 24),
-        ('vorbis', 0),
-        ('amb', 32),
+        ('wav', "float32"),
+        ('wav', "int32"),
+        ('wav', "int16"),
+        ('wav', "uint8"),
+        ('mp3', "float32"),
+        ('flac', "float32"),
+        ('vorbis', "float32"),
+        ('amb', "int16"),
     ])
-    def test_bytesio(self, ext, bits_per_sample):
-        """Querying audio via ByteIO object works"""
-        self._test_bytesio(ext, bits_per_sample, duration=3)
-
-    @parameterized.expand([
-        ('wav', 32),
-        ('mp3', 0),
-        ('flac', 24),
-        ('vorbis', 0),
-        ('amb', 32),
-    ])
-    def test_bytesio_tiny(self, ext, bits_per_sample):
+    def test_bytesio_tiny(self, ext, dtype):
         """Querying audio via ByteIO object works for small data"""
-        self._test_bytesio(ext, bits_per_sample, duration=1 / 1600)
+        sample_rate = 8000
+        num_frames = 4
+        num_channels = 2
+        sinfo = self._query_bytesio(ext, dtype, sample_rate, num_channels, num_frames)
+
+        bits_per_sample = get_bits_per_sample(ext, dtype)
+        num_frames = 0 if ext in ['mp3', 'vorbis'] else num_frames
+
+        assert sinfo.sample_rate == sample_rate
+        assert sinfo.num_channels == num_channels
+        assert sinfo.num_frames == num_frames
+        assert sinfo.bits_per_sample == bits_per_sample
+        assert sinfo.encoding == get_encoding(ext, dtype)
 
     @parameterized.expand([
-        ('wav', 32),
-        ('mp3', 0),
-        ('flac', 24),
-        ('vorbis', 0),
-        ('amb', 32),
+        ('wav', "float32"),
+        ('wav', "int32"),
+        ('wav', "int16"),
+        ('wav', "uint8"),
+        ('mp3', "float32"),
+        ('flac', "float32"),
+        ('vorbis', "float32"),
+        ('amb', "int16"),
     ])
-    def test_tarfile(self, ext, bits_per_sample):
+    def test_tarfile(self, ext, dtype):
         """Querying compressed audio via file-like object works"""
         sample_rate = 16000
+        num_frames = 3.0 * sample_rate
         num_channels = 2
-        duration = 3
-        format_ = ext if ext in ['mp3'] else None
-        audio_file = f'test.{ext}'
-        audio_path = self.get_temp_path(audio_file)
-        archive_path = self.get_temp_path('archive.tar.gz')
+        sinfo = self._query_tarfile(ext, dtype, sample_rate, num_channels, num_frames)
 
-        sox_utils.gen_audio_file(
-            audio_path, sample_rate, num_channels=num_channels, duration=duration)
-
-        with tarfile.TarFile(archive_path, 'w') as tarobj:
-            tarobj.add(audio_path, arcname=audio_file)
-        with tarfile.TarFile(archive_path, 'r') as tarobj:
-            fileobj = tarobj.extractfile(audio_file)
-            sinfo = sox_io_backend.info(fileobj, format=format_)
+        bits_per_sample = get_bits_per_sample(ext, dtype)
+        num_frames = 0 if ext in ['mp3', 'vorbis'] else num_frames
 
         assert sinfo.sample_rate == sample_rate
         assert sinfo.num_channels == num_channels
-        if ext not in ['mp3', 'vorbis']:  # these container formats do not have length info
-            assert sinfo.num_frames == sample_rate * duration
+        assert sinfo.num_frames == num_frames
         assert sinfo.bits_per_sample == bits_per_sample
+        assert sinfo.encoding == get_encoding(ext, dtype)
 
 
 @skipIfNoExtension
 @skipIfNoExec('sox')
 @skipIfNoModule("requests")
-class TestFileObjectHttp(HttpServerMixin, PytorchTestCase):
+class TestFileObjectHttp(HttpServerMixin, FileObjTestBase, PytorchTestCase):
+    def _query_http(self, ext, dtype, sample_rate, num_channels, num_frames):
+        audio_path = self._gen_file(ext, dtype, sample_rate, num_channels, num_frames)
+        audio_file = os.path.basename(audio_path)
+
+        url = self.get_url(audio_file)
+        format_ = ext if ext in ['mp3'] else None
+        with requests.get(url, stream=True) as resp:
+            return sox_io_backend.info(resp.raw, format=format_)
+
     @parameterized.expand([
-        ('wav', 32),
-        ('mp3', 0),
-        ('flac', 24),
-        ('vorbis', 0),
-        ('amb', 32),
+        ('wav', "float32"),
+        ('wav', "int32"),
+        ('wav', "int16"),
+        ('wav', "uint8"),
+        ('mp3', "float32"),
+        ('flac', "float32"),
+        ('vorbis', "float32"),
+        ('amb', "int16"),
     ])
-    def test_requests(self, ext, bits_per_sample):
+    def test_requests(self, ext, dtype):
         """Querying compressed audio via requests works"""
         sample_rate = 16000
+        num_frames = 3.0 * sample_rate
         num_channels = 2
-        duration = 3
-        format_ = ext if ext in ['mp3'] else None
-        audio_file = f'test.{ext}'
-        audio_path = self.get_temp_path(audio_file)
-
-        sox_utils.gen_audio_file(
-            audio_path, sample_rate, num_channels=num_channels, duration=duration)
+        sinfo = self._query_http(ext, dtype, sample_rate, num_channels, num_frames)
 
-        url = self.get_url(audio_file)
-        with requests.get(url, stream=True) as resp:
-            sinfo = sox_io_backend.info(resp.raw, format=format_)
+        bits_per_sample = get_bits_per_sample(ext, dtype)
+        num_frames = 0 if ext in ['mp3', 'vorbis'] else num_frames
 
         assert sinfo.sample_rate == sample_rate
         assert sinfo.num_channels == num_channels
-        if ext not in ['mp3', 'vorbis']:  # these container formats do not have length info
-            assert sinfo.num_frames == sample_rate * duration
+        assert sinfo.num_frames == num_frames
         assert sinfo.bits_per_sample == bits_per_sample
+        assert sinfo.encoding == get_encoding(ext, dtype)