Bendzko
Bendzko

Reputation: 51

Python: How to decode a mp3 chunk into PCM samples?

I'm trying to catch chunks of an mp3 webstream and decoding them into PCM samples for signal processing. I tried to catch the audio via requests and io.BytesIO to save the data as .wav file.

I have to convert the mp3 data to wav data, but I don't know how. (My goal is not to record a .wav file, i am just doing this to test the algorithm.)

I found the pymedia lib, but it is very old (last commit in 2006), using python 2.7 and for me not installable.

Maybe it is possible with ffmpeg-python, but I have just seen examples using files as input and output.

Here's my code:

import requests
import io
import soundfile as sf
import struct
import wave
import numpy as np


def main():
    stream_url = r'http://dg-wdr-http-dus-dtag-cdn.cast.addradio.de/wdr/1live/diggi/mp3/128/stream.mp3'
    r = requests.get(stream_url, stream=True)
    sample_array = []
    try:
        for block in r.iter_content(1024):
            data, samplerate = sf.read(io.BytesIO(block), format="RAW", channels=2, samplerate=44100, subtype='FLOAT',
                                       dtype='float32')
            sample_array = np.append(sample_array, data)

    except KeyboardInterrupt:
        print("...saving")
        obj = wave.open('sounds/stream1.wav', 'w')
        obj.setnchannels(1)  # mono
        obj.setsampwidth(2)  # bytes
        obj.setframerate(44100)

        data_max = np.nanmax(abs(sample_array))

        # fill WAV with samples from sample_array
        for sample in sample_array:
            if (np.isnan(sample) or np.isnan(32760 * sample / data_max)) is True:
                continue
            try:
                value = int(32760 * sample / data_max)  # normalization INT16
            except ValueError:
                value = 1
            finally:
                data = struct.pack('<h', value)
                obj.writeframesraw(data)

        obj.close()
        print("end")


if __name__ == '__main__':
    main()

Do you have an idea how to handle this problem?

Upvotes: 2

Views: 4929

Answers (3)

Chris P
Chris P

Reputation: 2345

Based on Bendzko answer here is my code:

pip install pyaudio miniaudio

import threading

import urllib.request

import time
try:
    import miniaudio
except ImportError:
    miniaudio = None
    
import pyaudio

import ctypes

import sys 

CHUNK = 4096

p = pyaudio.PyAudio()

stream = p.open(format=pyaudio.paInt16,channels=2,rate=44100,output=True)

    



class RadioThread(threading.Thread):
    def run(self):
        self.url = "https://impradio.bytemasters.gr/8002/stream"
        #run in threading
        client = miniaudio.IceCastClient(self.url)
        pcm_stream = MiniaudioDecoderPcmStream(client.audio_format,client)
        self.audio_playback(pcm_stream)


    def audio_playback(self,pcm_stream):
        global stop_peradio_thread
        while stop_peradio_thread==False:
            try:
                audio = pcm_stream.read(CHUNK)
                stream.write(audio.tobytes())
            except:
                pass
                
                
class MiniaudioDecoderPcmStream(miniaudio.StreamableSource):
    def __init__(self, fmt, stream):
        self.pcm_stream = miniaudio.stream_any(stream, fmt, dither=miniaudio.DitherMode.TRIANGLE)

    def read(self, size):
        try:
            return self.pcm_stream.send(size)
        except StopIteration:
            return b""

def main():
    global stop_peradio_thread
    stop_peradio_thread = False
    t1 = RadioThread()
    t1.start()
    while True:
        try:
            time.sleep(1)
        except KeyboardInterrupt:
            stop_peradio_thread = True
            t1.join()
            sys.exit()
            
main()

Upvotes: 1

Bendzko
Bendzko

Reputation: 51

With the help from Irmen and his "miniaudio" and "synthesizer" library, I could solve the problem.

The problem was, that most radio webstreams uses the ICECAST protocol, which includes interleaved metadata information, so you can't decode it directly.

With the example script https://github.com/irmen/synthesizer/blob/master/examples/internetradio.py as template, I could write a script, which records a webstream until KeyboardInterrupt and saves it as a .wav file.

Here's the main part I edited:

 ...

 def _audio_playback(self, pcm_stream):
    sample_array = None

    with Output(mixing="sequential", frames_per_chunk=44100 // 4) as output:
        print("begin recording")
        while self.decode_flag:
            try:
                audio = pcm_stream.read(44100 * 2 * 2 // 20)
                if not audio:
                    break
            except (IOError, ValueError):
                break
            else:
                sample = Sample.from_raw_frames(audio, 2, 44100, 2)
                if sample_array is None:
                    sample_array = sample.get_frames_numpy_float()
                else:
                    sample_array = np.append(sample_array, sample.get_frames_numpy_float(), axis=0)
        print("...saving")
        wavf.write(self.file_location, 44100, sample_array)
        print("saved")

...

Upvotes: 3

Vikram Dattu
Vikram Dattu

Reputation: 871

You are missing the decoding of mp3 stream. You are just saving mp3 file as wav.

You first need to decode mp3 audio. Which will give you PCM samples + audio info.

Upvotes: 2

Related Questions