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.