0

I'm struggling to find a solution for streaming synthesized audio from a Python server. The synthesized audio is incrementally generated and returned as a np.float32 NumPy array. It then needs to be transformed from a NumPy array into an MP3 chunk. Finally, the MP3 chunk is served via flask.

Here is some pseudo-code:

import numpy

from flask import Flask
from flask import Response

app = Flask(__name__)
sample_rate = 24000


def pcm_to_mp3():
    raise NotImplementedError()


def get_synthetic_audio():
    """ Mock function for synthetic audio. """
    while True:
        yield numpy.random.rand(1024) * 2 - 1  # Return: 32-bit Floating Point PCM


@app.route('/stream', methods=['GET'])
def get_stream():
    """ Stream synthetic audio. """

    def response():
        for numpy_array in get_synthetic_audio():
            # NOTE: The raw audio needs additional metadata to be playable like sample rate.
            yield pcm_to_mp3(numpy_array, sample_rate=sample_rate)

    return Response(
        response(),
        headers={
            # NOTE: Ensure stream is not cached.
            'Cache-Control': 'no-cache, no-store, must-revalidate',
            'Pragma': 'no-cache',
            'Expires': '0',
            # NOTE: Enable streaming.
            'Transfer-Encoding': 'chunked'
        },
        mimetype='audio/mpeg')


if __name__ == "__main__":
    app.run()

While a similar setup works for WAV files, I am unable to figure out how to do something similar for MP3 files.

Thank you!


Sources

1 Answer 1

1

I was able to figure out a working approach:

import select
import subprocess

import numpy

from flask import Flask
from flask import Response

app = Flask(__name__)


def get_synthetic_audio(num_samples):
    audio = numpy.random.rand(num_samples).astype(numpy.float32) * 2 - 1
    assert audio.max() <= 1.0
    assert audio.min() >= -1.0
    assert audio.dtype == numpy.float32
    return audio


def response():
    pipe = subprocess.Popen(
        'ffmpeg -f f32le -acodec pcm_f32le -ar 24000 -ac 1 -i pipe: -f mp3 pipe:'
        .split(),
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE)
    poll = select.poll()
    poll.register(pipe.stdout, select.POLLIN)
    while True:
        pipe.stdin.write(get_synthetic_audio(24000).tobytes())
        while poll.poll(0):
            yield pipe.stdout.readline()


@app.route('/stream.mp3', methods=['GET'])
def stream():
    return Response(
        response(),
        headers={
            # NOTE: Ensure stream is not cached.
            'Cache-Control': 'no-cache, no-store, must-revalidate',
            'Pragma': 'no-cache',
            'Expires': '0',
        },
        mimetype='audio/mpeg')


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

During my exploration, I learned that flask does not support chunked transfer encoding. This was surprising because chunked transfer encoding was introduced in 1997 as part of HTTP 1.1.

Regardless, I was surprised to learn that ffmpeg's stream is compatible with flask, and that it was supported on Safari, Firefox, and Chrome.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.