Skip to content

Instantly share code, notes, and snippets.

@anthonyeden
Created May 28, 2018 11:46
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save anthonyeden/f3b3bdf6f62badd8f87bb574283f488a to your computer and use it in GitHub Desktop.
Save anthonyeden/f3b3bdf6f62badd8f87bb574283f488a to your computer and use it in GitHub Desktop.
Sending FFmpeg output to HTTP, via Python's Flash
"""
Streaming FFmpeg to HTTP, via Python's Flask.
This is an incredibly simple example, which will yield issues due to inconsistant input and output rates.
If you're going to use this, VLC works okay as a client.
You really need to move FFmpeg into a separate thread, which should help stream audio more consistantly to the HTTP client.
Example by Anthony Eden (https://mediarealm.com.au)
"""
from flask import Flask
from flask import stream_with_context, request, Response
import subprocess
import time
app = Flask(__name__)
@app.route("/")
def hello():
def generate():
startTime = time.time()
buffer = []
sentBurst = False
ffmpeg_command = ["ffmpeg", "-f", "avfoundation", "-i", ":2", "-acodec", "libmp3lame", "-ab", "32k", "-ac", "1", "-f", "mpeg", "pipe:stdout"]
process = subprocess.Popen(ffmpeg_command, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, bufsize = -1)
while True:
# Get some data from ffmpeg
line = process.stdout.read(1024)
# We buffer everything before outputting it
buffer.append(line)
# Minimum buffer time, 3 seconds
if sentBurst is False and time.time() > startTime + 3 and len(buffer) > 0:
sentBurst = True
for i in range(0, len(buffer) - 2):
print "Send initial burst #", i
yield buffer.pop(0)
elif time.time() > startTime + 3 and len(buffer) > 0:
yield buffer.pop(0)
process.poll()
if isinstance(process.returncode, int):
if process.returncode > 0:
print 'FFmpeg Error', p.returncode
break
return Response(stream_with_context(generate()), mimetype = "audio/mpeg")
if __name__ == "__main__":
app.run()
@lxf1992521
Copy link

BUG REPORT:

Each new connection will create a new ffmpeg process, and not exit when connection closed, so there will be lots of ffmpeg process over times

@dsjijo
Copy link

dsjijo commented Apr 28, 2020

any idea how to close the connection and make it work better??

@dsjijo
Copy link

dsjijo commented Apr 28, 2020

I never understood how it worked, but do now from this blog
https://cjwebb.com/posts/python-docker-ffmpeg-h264-mp4/

@brunomsantiago
Copy link

The link above is broken... but there is another one
https://cjwebb.com/python-docker-ffmpeg-h264-mp4/

@RoggerTan
Copy link

any idea how to close the connection and make it work better??

You can catch GeneratorExit exception inside the generate function. Here's the link: pallets/flask#2702 (comment)

I tested and it worked flawlessly.

@askvictor
Copy link

askvictor commented May 5, 2023

Another option to kill the process is to use the call_on_close decorator:

    response = Response(stream_with_context(generate()), mimetype = "audio/mpeg")

    @response.call_on_close
    def on_close():
        process.kill()
        
    return response

But you need to start the ffmpeg process outside of the generate closure e.g.


    ffmpeg_command = ["ffmpeg", "-f", "avfoundation", "-i", ":2", "-acodec", "libmp3lame", "-ab", "32k", "-ac", "1", "-f", "mpeg", "pipe:stdout"]
    process = subprocess.Popen(ffmpeg_command, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, bufsize = -1)
    def generate():
        startTime = time.time()
        buffer = []

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment