Python Forum

Full Version: Raspberry PI - PyAudio - Streaming to Web Page
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Hi All, trying to stream from the Microphone on the Raspberry PI. This chunk of code almost works on the PI to a browser window. I just don't hear any audio. If I clap my hands I can see the audio monitor running on the PI with the number feed spike, so I know it's detecting audio. I think there is something fundamentally wrong how I am sending the sample data to the browser so that the audio control doesn't quite work. Anyone have any suggestions or know of the fix?
from flask import Flask, Response
import pyaudio
import wave
import threading
import time
import numpy as np

app = Flask(__name__)
 
CHUNK = 1024  # Samples: 1024,  512, 256, 128 frames per buffer
RATE = 44100  # Equivalent to Human Hearing at 40 kHz
CHANNELS =1
BITS_PER_SAMPLE = 16

mic_data=None
str_data=None


#print(mic_data)
#status = 0 frame_count = 1024 #time_info is like a timestamp json

def callback(in_data, frame_count, time_info, status):
    global mic_data
    global str_data 


    mic_data = np.fromstring(in_data, dtype=np.int16)
    str_data = in_data
 
    print(np.amax(mic_data))
    return (in_data, pyaudio.paContinue)



def mic_stream():

    global CHUNK
    global RATE
    global CHANNELS

    p = pyaudio.PyAudio()

    stream = p.open(format=pyaudio.paInt16,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK,
                stream_callback=callback)

    stream.start_stream()

    while stream.is_active():
        time.sleep(0.1)

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

def generate_wav_header():
    global RATE
    global CHANNELS
    global BITS_PER_SAMPLE

    datasize = 2000*10**6
    o = bytes("RIFF",'ascii')                                               # (4byte) Marks file as RIFF
    o += (datasize + 36).to_bytes(4, 'little')                              # (4byte) File size in bytes excluding this and RIFF marker
    o += bytes("WAVE",'ascii')                                              # (4byte) File type
    o += bytes("fmt ",'ascii')                                              # (4byte) Format Chunk Marker
    o += (16).to_bytes(4, 'little')                                         # (4byte) Length of above format data
    o += (1).to_bytes(2, 'little')                                          # (2byte) Format type (1 - PCM)
    o += (CHANNELS).to_bytes(2, 'little')                                   # (2byte)
    o += (RATE).to_bytes(4, 'little')                                # (4byte)
    o += (RATE * CHANNELS * BITS_PER_SAMPLE // 8).to_bytes(4, 'little') # (4byte)
    o += (CHANNELS * BITS_PER_SAMPLE // 8).to_bytes(2, 'little')            # (2byte)
    o += (BITS_PER_SAMPLE).to_bytes(2, 'little')                            # (2byte)
    o += bytes("data",'ascii')                                              # (4byte) Data Chunk Marker
    o += (datasize).to_bytes(4, 'little')                                   # (4byte) Data size in bytes
    return o

def generate_audio_stream():
    global str_data
    wav_header = generate_wav_header()
    yield wav_header
    while True:
        if str_data:
            yield str_data        

@app.route('/audio_feed')
def audio_feed():
    return Response(generate_audio_stream(),
                    mimetype='audio/wav')

@app.route('/')
def index():
    return '''
    <html>
    <head>
        <title>Audio Streaming</title>
    </head>
    <body>
        <button>Record</button>
        <h1>Audio Streaming</h1>
        <audio controls autoplay>
            <source src="/audio_feed" type="audio/wav">
        </audio>
    </body>
    </html>
    '''

if __name__ == '__main__':
    mic_thread = threading.Thread(target=mic_stream)
    mic_thread.start()
    app.run(host='0.0.0.0', port=9000, debug=True)
from flask import Flask, Response
import pyaudio
import wave
import threading
import time
import numpy as np

app = Flask(__name__)
 
CHUNK = 1024  # Samples: 1024,  512, 256, 128 frames per buffer
RATE = 44100  # Equivalent to Human Hearing at 40 kHz
CHANNELS =1
BITS_PER_SAMPLE = 16

mic_data=None
str_data=None


#print(mic_data)
#status = 0 frame_count = 1024 #time_info is like a timestamp json

def callback(in_data, frame_count, time_info, status):
    global mic_data
    global str_data 


    mic_data = np.fromstring(in_data, dtype=np.int16)
    str_data = in_data
 
    print(np.amax(mic_data))
    return (in_data, pyaudio.paContinue)



def mic_stream():

    global CHUNK
    global RATE
    global CHANNELS

    p = pyaudio.PyAudio()

    stream = p.open(format=pyaudio.paInt16,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK,
                stream_callback=callback)

    stream.start_stream()

    while stream.is_active():
        time.sleep(0.1)

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

def generate_wav_header():
    global RATE
    global CHANNELS
    global BITS_PER_SAMPLE

    datasize = 2000*10**6
    o = bytes("RIFF",'ascii')                                               # (4byte) Marks file as RIFF
    o += (datasize + 36).to_bytes(4, 'little')                              # (4byte) File size in bytes excluding this and RIFF marker
    o += bytes("WAVE",'ascii')                                              # (4byte) File type
    o += bytes("fmt ",'ascii')                                              # (4byte) Format Chunk Marker
    o += (16).to_bytes(4, 'little')                                         # (4byte) Length of above format data
    o += (1).to_bytes(2, 'little')                                          # (2byte) Format type (1 - PCM)
    o += (CHANNELS).to_bytes(2, 'little')                                   # (2byte)
    o += (RATE).to_bytes(4, 'little')                                # (4byte)
    o += (RATE * CHANNELS * BITS_PER_SAMPLE // 8).to_bytes(4, 'little') # (4byte)
    o += (CHANNELS * BITS_PER_SAMPLE // 8).to_bytes(2, 'little')            # (2byte)
    o += (BITS_PER_SAMPLE).to_bytes(2, 'little')                            # (2byte)
    o += bytes("data",'ascii')                                              # (4byte) Data Chunk Marker
    o += (datasize).to_bytes(4, 'little')                                   # (4byte) Data size in bytes
    return o

def generate_audio_stream():
    global str_data
    wav_header = generate_wav_header()
    yield wav_header
    while True:
        if str_data:
            yield str_data        

@app.route('/audio_feed')
def audio_feed():
    return Response(generate_audio_stream(),
                    mimetype='audio/wav')

@app.route('/')
def index():
    return '''
    <html>
    <head>
        <title>Audio Streaming</title>
    </head>
    <body>
        <button>Record</button>
        <h1>Audio Streaming</h1>
        <audio controls autoplay>
            <source src="/audio_feed" type="audio/wav">
        </audio>
    </body>
    </html>
    '''

if __name__ == '__main__':
    mic_thread = threading.Thread(target=mic_stream)
    mic_thread.start()
    app.run(host='0.0.0.0', port=9000, debug=True)
Your code is almost there but has a few issues. The WAV header is not dynamic enough for live streaming, and browsers expect a specific format that WAV might not fulfill effectively in real-time. I've made several improvements:
  1. Dynamic Buffer: The audio_buffer stores chunks of audio, which ensures smoother streaming.
  2. Updated WAV Header: It now has a dynamic size that can handle continuous streaming without freezing.
  3. Thread Safety: I addressed potential concurrency issues by removing direct access to global variables from multiple threads.
Try this updated code, and it should work well for streaming microphone input to your browser.