Search code examples
audio-recordingwavepython-sounddevice24-bitpyaudioanalysis

How to get 24-bit samples from USB audio device using Python?


I am trying to get a stream of 24-bit audio samples from some USB audio device using Python. I already searched for some solutions and found this thread that is using PyAudio stream with a format of pyaudio.paInt24, unfortunately I still got 16-bit samples. I tried to record with PyAudio stream and save with WAVE, sample code:

    def initialize(self):
        self.p = pyaudio.PyAudio()

        self.stream = self.p.open(format=self.format,            # pyaudio.paInt24
                                  channels=self.channels,        # 1
                                  rate=self.fs,                  # 16000
                                  input=True,
                                  input_device_index=self.input_device_index,
                                  frames_per_buffer=self.chunk)  # 1024

    def run(self):

        frames = []

        for _ in range(0, int(self.fs / self.chunk * self.duration)):
            data = self.stream.read(self.chunk)
            frames.append(data)

        self.stream.stop_stream()
        self.stream.close()
        self.p.terminate()

        # write recording to a WAV file
        wf = wave.open(self.recorded_file, 'wb')
        wf.setnchannels(self.channels)
        wf.setsampwidth(self.p.get_sample_size(self.format))
        wf.setframerate(self.fs)
        wf.writeframes(b''.join(frames))
        wf.close()

I also tried the same the SoundDevice, sample code:

    def initialize(self):
        sd.default.samplerate = self.fs         # 16000
        sd.default.channels   = self.channels   # 1
        sd.default.device     = self.input_device_index

    def run(self):

        data = sd.rec(int(self.duration * self.fs), dtype=np.int32)
        sd.wait()

        # Save file
        wf = wave.open(self.recorded_file, 'wb')
        wf.setnchannels(self.channels)
        wf.setsampwidth(3)
        wf.setframerate(self.fs)
        wf.writeframes(b''.join(data))

In order to make sure that the problem occurs when receiving the samples and not while saving them, I made sure that a 24-bit audio file can indeed be saved with WAVE, using this sample code:

import wave
with wave.open(path_to_file_to_read, mode='rb') as rb:
    white_noise = rb.readframes(rb.getframerate())

    with wave.open(path_to_file_to_save+'_save_with_wave.wav', mode='wb') as wb:
        wb.setnchannels(rb.getnchannels())
        wb.setsampwidth(rb.getsampwidth())
        wb.setnframes(rb.getnframes())
        wb.setframerate(rb.getframerate())
        wb.writeframesraw(white_noise)

Also, I put a break point right after the recording was finished to take a look on the recorded samples and I see that it looks like 16-bit: PyAudio samples debuggingI will note that in order to make sure that the USB audio device does send me the 24-bit samples I did a recording using Audacity and indeed I got 24-bit, for some reason I can not do anything similar with Python.

Is there a simple way to record 24-bit samples using Python code?


Solution

  • Both PyAudio and SoundDevice use portaudio bindings between some windows sound architecture and python. PortAudio does not suppport full depth 24 bit, and the result is 16 bit of audio with additional 8 zero bits. SoundCard supports 24 bit - https://soundcard.readthedocs.io/en/latest/

    In order to get real 24 bit samples, you can use the following:

    import soundcard as sc
    import soundfile as sf
    
    FS = 16000
    TIME_IN_SEC = 5
    FRAMES = FS * TIME_IN_SEC
    DEVICE_NAME = 'default'
    FILE_PATH = 'abc.wav'    
    
    # get device
    audio_device = sc.get_microphone(DEVICE_NAME)
    
    # receive samples
    data = audio_device.record(samplerate=FS, numframes=FRAMES)
        
    # write recording to a WAV file
    sf.write(FILE_PATH, data, FS, subtype='PCM_24')