pythonhttpflaskaudio-streaminghttp-live-streaming

HTTP realtime audio streaming server


As a proof-of-concept I need to create a HTTP server which on GET request should start continuous stream of non-encoded/non-compressed audio data - WAV, PCM16. Let's assume the audio data are chunks of 4096 randomly generated mono audio samples @44.1kHz sampling rate.

What should I put in the HTTP response header in order to browser on the other end start a player in its UI for the user to listen in realtime?

I was reading about "Transfer-Encoding: chunked", "multipart", mimetype="audio/xwav", but still not sute what and how to use...

Great would be if someone can give me an exact example on Python/Flask because I'm not so confident with the web development.

PS1: Replacing HTTP server with an embedded device with limited HW power will be the next stage after the PoC.

PS2: This is the code which actually works and sends an WAV chunk as a single HTTP response:

from flask import Flask, Response,render_template
import pyaudio
import audio_processing as audioRec

app = Flask(__name__)

def genHeader(sampleRate, bitsPerSample, channels, samples):
    datasize = samples * channels * bitsPerSample // 8
    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 += (sampleRate).to_bytes(4,'little')                                  # (4byte)
    o += (sampleRate * channels * bitsPerSample // 8).to_bytes(4,'little')  # (4byte)
    o += (channels * bitsPerSample // 8).to_bytes(2,'little')               # (2byte)
    o += (bitsPerSample).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

FORMAT = pyaudio.paInt16
CHUNK = 102400 #1024
RATE = 44100
bitsPerSample = 16 #16
CHANNELS = 1
wav_header = genHeader(RATE, bitsPerSample, CHANNELS, CHUNK)

audio = pyaudio.PyAudio()

# start Recording
stream = audio.open(format=FORMAT, channels=CHANNELS,
    rate=RATE, input=True, input_device_index=10,
    frames_per_buffer=CHUNK)
# print "recording..."

@app.route('/')
def index():
    """Video streaming home page."""
    return render_template('index2.html')

@app.route('/audio_unlim')
def audio_unlim():
    # start Recording
    def sound():

        #while True:
        #    data = wav_header + stream.read(CHUNK)
        #    yield(data)
        data = wav_header + stream.read(CHUNK)
        yield(data)

    return Response(sound(),
                    mimetype="audio/x-wav")


if __name__ == "__main__":
    app.run(host='0.0.0.0', debug=True, threaded=True,port=5000)

and the index2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <audio controls>
        <source src="{{ url_for('audio_unlim') }}" type="audio/x-wav;codec=pcm">
        Your browser does not support the audio element.
    </audio
</body>
</html>

What to change in order to achieve continuous stream of chunks?


Solution

  • Actually I've made a kind of workaround with the following code (without any index.html) and it works fine without any interruptions:

    from flask import Flask, Response,render_template
    import pyaudio
    import audio_processing as audioRec
    
    app = Flask(__name__)
    
    def genHeader(sampleRate, bitsPerSample, channels, samples):
        datasize = 10240000 # Some veeery big number here instead of: #samples * channels * bitsPerSample // 8
        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 += (sampleRate).to_bytes(4,'little')                                  # (4byte)
        o += (sampleRate * channels * bitsPerSample // 8).to_bytes(4,'little')  # (4byte)
        o += (channels * bitsPerSample // 8).to_bytes(2,'little')               # (2byte)
        o += (bitsPerSample).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
    
    FORMAT = pyaudio.paInt16
    CHUNK = 1024 #1024
    RATE = 44100
    bitsPerSample = 16 #16
    CHANNELS = 1
    wav_header = genHeader(RATE, bitsPerSample, CHANNELS, CHUNK)
    
    audio = pyaudio.PyAudio()
    
    # start Recording
    stream = audio.open(format=FORMAT, channels=CHANNELS,
        rate=RATE, input=True, input_device_index=10,
        frames_per_buffer=CHUNK)
    # print "recording..."
    
    @app.route('/audio_unlim')
    def audio_unlim():
        # start Recording
        def sound():
            data = wav_header
            data += stream.read(CHUNK)
            yield(data)
            while True:
                data = stream.read(CHUNK)
                yield(data)
    
        return Response(sound(), mimetype="audio/x-wav")
    
    
    if __name__ == "__main__":
        app.run(host='0.0.0.0', debug=True, threaded=True,port=5000)
    

    I've just started with sending a WAV header, but the size written there is veeery big number telling the player to wait veeery big buffer of data. Until the "end" player plays the coming chunks of data without any problem (no WAV headers anymore just chunks of audio data!). This is without any "Transfer-encoding: chunked" or anything else! Just set the mimetype to "audio/x-wav". And the HTTP response is extremely simple as follows:

    enter image description here