百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Python Flask如何实播放视频流?深入浅出实现方案

zhezhongyun 2025-04-07 21:11 46 浏览

本文致力于流式传输,这是一个有趣的功能,它使Flask应用程序能够长时间有效地将大型响应有效地分成小块。为了说明这一主题,我将向您展示如何构建实时视频流服务器!

注意:本文有后续内容,Flask视频流再回顾,其中我介绍了此处介绍的流服务器的一些改进。

什么是流媒体?

流是一种技术,其中服务器以块的形式提供对请求的响应。我可以想到一些可能有用的原因:

  • 很大的Response。对于非常大的响应,仅在内存中组装响应以将其返回给客户端可能是低效率的。一种替代方法是将响应写入磁盘,然后使用来返回文件flask.send_file(),但这会增加I / O。假设可以分块生成数据,则以小部分提供响应是一种更好的解决方案。
  • 实时数据。对于某些应用程序,请求可能需要返回来自实时源的数据。实时视频或音频提要就是一个很好的例子。许多安全摄像机使用此技术将视频流传输到Web浏览器。

用Flask实现流

Flask通过使用生成器功能为流式响应提供了本机支持。生成器是一种特殊功能,可以中断和恢复。考虑以下功能:

def gen():
    yield 1
    yield 2
    yield 3

此函数分三个步骤运行,每个步骤都返回一个值。描述如何实现生成器功能不在本文的讨论范围之内,但是如果您有点好奇,下面的shell会话将带您了解如何使用生成器:

>>> x = gen()
>>> x

>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
Traceback (most recent call last):
  File "", line 1, in 
StopIteration

您可以在这个简单的示例中看到一个生成器函数可以按顺序返回多个结果。Flask利用生成器功能的这一特性来实现流式传输。

下面的示例显示了如何使用流技术来生成大型数据表,而不必在内存中组装整个表:

from flask import Response, render_template
from app.models import Stock

def generate_stock_table():
    yield render_template('stock_header.html')
    for stock in Stock.query.all():
        yield render_template('stock_row.html', stock=stock)
    yield render_template('stock_footer.html')

@app.route('/stock-table')
def stock_table():
    return Response(generate_stock_table())

在此示例中,您可以看到Flask如何与生成器函数一起工作。返回流式响应的路由需要返回一个Response使用generator函数初始化的对象。然后Flask负责调用生成器,并将所有部分结果作为块发送给客户端。

对于此特定示例,如果假设Stock.query.all()以迭代方式返回数据库查询的结果,则可以一次生成一个潜在的大表,因此无论查询中有多少元素,Python进程中的内存消耗都将由于必须组装一个较大的响应字符串而不会变得越来越大。

Multipart Responses

上面的表格示例会一小部分地生成一个传统页面,所有部分都连接到最终文档中。这是如何产生大响应的一个很好的例子,但是更令人兴奋的是使用实时数据。

流的一种有趣用法是让每个块替换页面中的前一个块,因为这使流能够在浏览器窗口中“播放”或设置动画。使用这种技术,您可以使流中的每个块都成为一个图像,从而为您提供在浏览器中运行的超酷视频供稿!

实现就地更新的秘密是使用Multipart Responses。Multipart Responses包含一个标头,该头包含一个多部分内容类型之一,然后是由边界标记分隔的部分,每个部分都有其自己的特定于部分内容的类型。

有几种多部分内容类型可以满足不同的需求。为了在每个部分都替换前一个部分的流中multipart/x-mixed-replace使用,必须使用内容类型。为了帮助您了解外观,以下是多部分视频流的结构:

HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace; boundary=frame

--frame
Content-Type: image/jpeg


--frame
Content-Type: image/jpeg


...

正如您在上面看到的,结构非常简单。将主Content-Type标头设置为,multipart/x-mixed-replace并定义边界字符串。然后包括每个零件,在它们自己的行中以两个破折号和零件边界字符串为前缀。这些部分具有自己的Content-Type标头,并且每个部分都可以选择包含一个Content-Length标头,该标头的长度为部分有效载荷的字节数,但至少对于图像而言,浏览器能够处理没有该长度的流。

构建实时视频流服务器

本文有足够的理论,现在是时候构建一个完整的应用程序,将实时视频流传输到Web浏览器。

流视频到浏览器的方法有很多,每种方法都有其优点和缺点。与Flask的流传输功能很好配合的方法是流传输一系列独立的JPEG图片。这称为Motion JPEG,并且被许多IP安全摄像机使用。这种方法的延迟时间很短,但是质量并不是最好的,因为JPEG压缩对于运动视频不是很有效。

在下面,您可以看到一个非常简单但完整的Web应用程序,可以为Motion JPEG流提供服务:

#!/usr/bin/env python
from flask import Flask, render_template, Response
from camera import Camera

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

def gen(camera):
    while True:
        frame = camera.get_frame()
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

@app.route('/video_feed')
def video_feed():
    return Response(gen(Camera()),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

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

该应用程序导入一个Camera负责提供帧序列的类。在这种情况下,将摄像机控制部分放在单独的模块中是个好主意,这样,Web应用程序便保持干净,简单和通用。

该应用程序有两条路线。该/路线将服务于在index.html模板中定义的主页。您可以在下面看到此模板文件的内容:


  
    Video Streaming Demonstration
  
  
    

Video Streaming Demonstration

这是一个简单的HTML页面,仅包含标题和图像标签。请注意,图像标签的src属性指向此应用程序的第二条路线,这就是魔术发生的地方。

/video_feed路线返回流响应。因为此流返回要在网页中显示的图像,所以此路由的URLsrc在image标记的属性中。浏览器将通过在其中显示JPEG图像流来自动更新图像元素,因为大多数/所有浏览器都支持多部分响应(如果您发现不喜欢这种浏览器的话,请告诉我)。

/video_feed路由中使用的生成器函数称为gen(),并将Camera类的实例作为参数。在mimetype如上述所示,用参数设定multipart/x-mixed-replace的内容类型和边界设置为字符串"frame"

gen()函数进入一个循环,在该循环中,该函数不断从相机返回帧作为响应块。该函数通过调用camera.get_frame()方法要求相机提供一个帧,然后以该帧的形式格式化为内容类型为的响应块image/jpeg,如上所示。

从摄像机获取帧

现在剩下的就是实现Camera该类了,该类必须连接到摄像机硬件并从中下载实时视频帧。将这个应用程序的硬件相关部分封装在一个类中的好处是,该类可以为不同的人提供不同的实现,但是应用程序的其余部分保持不变。您可以将此类视为设备驱动程序,无论使用什么实际的硬件设备,它都可以提供统一的实现。

Camera类与应用程序的其余部分分开的另一个优点是,很容易使应用程序愚弄应用程序以至于认为实际上有一个摄像头,因为没有摄像头类可以实现为模拟摄像头,而无需真正的硬件。实际上,当我在开发此应用程序时,测试流的最简单方法就是这样做,而不必担心硬件,直到我运行所有其他功能。在下面,您可以看到我使用的简单的模拟摄像机实现:

from time import time

class Camera(object):
    def __init__(self):
        self.frames = [open(f + '.jpg', 'rb').read() for f in ['1', '2', '3']]

    def get_frame(self):
        return self.frames[int(time()) % 3]

此实现从磁盘读取三个图像叫1.jpg2.jpg3.jpg再返回它们彼此反复后,以每秒一帧的速率。该get_frame()方法使用以秒为单位的当前时间来确定在任何给定时刻要返回的三个帧中的哪一个。很简单,对不对?

要运行此仿真相机,我需要创建三个框架。使用gimp我制作了以下图像:

由于摄像机是模拟的,因此该应用程序可以在任何环境下运行,因此您可以立即运行它!我已经准备好将此应用程序放到GitHub上。如果您熟悉,git可以使用以下命令克隆它:

$ git clone https://github.com/miguelgrinberg/flask-video-streaming.git

如果您喜欢下载它,则可以在此处获得一个zip文件。

安装完应用程序后,创建一个虚拟环境并在其中安装Flask。然后,您可以按以下方式运行该应用程序:

$ python app.py

启动应用程序后,进入http://localhost:5000Web浏览器,您将看到模拟的视频流反复播放1、2和3图像。太酷了吧?

一旦一切正常工作,我就用其相机模块启动Raspberry Pi,并实现了一个新Camera类,该类使用该picamera包来控制硬件,从而将Pi转换为视频流服务器。我不会在这里讨论此相机的实现,但是您可以在file的源代码中找到它camera_pi.py

如果您有Raspberry Pi和相机模块,则可以编辑app.pyCamera从该模块导入该类,然后就可以实时播放Pi相机,就像我在以下屏幕截图中所做的那样:

如果要使此流应用程序与其他摄像机一起使用,则您所需要做的就是编写Camera该类的另一个实现。如果您最终写了一篇,那么如果您将其贡献给我的GitHub项目,我将不胜感激。

流媒体的局限性

当Flask应用程序处理常规请求时,请求周期很短。网络工作者接收请求,调用处理程序函数,最后返回响应。一旦响应被发送回客户端,工作人员就可以自由地准备接受另一个请求。

收到使用流式传输的请求时,工作者在流的持续时间内保持与客户端的连接。当使用长而永无休止的流(例如来自摄像机的视频流)时,工作人员将保持锁定状态,直到客户端断开连接。这实际上意味着,除非采取特定措施,否则该应用程序只能为服务与Web工作人员一样多的客户端提供服务。当在调试模式下使用Flask应用程序时,这意味着只有一个,因此您将无法连接第二个浏览器窗口来同时观看来自两个位置的流。

有多种方法可以克服此重要限制。我认为最好的解决方案是使用Flask完全支持的基于协程的Web服务器,例如gevent。通过使用协程,gevent可以在单个工作线程上处理多个客户端,因为gevent修改了Python I / O函数以根据需要发出上下文切换。

结论

如果您在上面错过了它,则支持本文的代码是以下GitHub存储库:https :
//github.com/miguelgrinberg/flask-video-streaming/tree/v1。在这里,您可以找到不需要摄像头的视频流通用实现,以及Raspberry Pi摄像头模块的实现。这篇后续文章描述了本文最初发表后我所进行的一些改进。

我希望本文能对流媒体的话题有所启发。我专注于视频流传输,因为这是我有经验的领域,但是流传输除了视频之外还有更多用途。例如,该技术可用于使客户端和服务器之间的连接保持活跃状态很长时间,从而允许服务器在新信息可用时推送新信息。如今,Web Socket协议是实现此目的的更有效方法,但是Web Socket相当新,并且仅在现代浏览器中有效,而流式传输几乎可以在您能想到的任何浏览器上运行。

Ref:

https://blog.miguelgrinberg.com/post/video-streaming-with-flask

相关推荐

Python入门学习记录之一:变量_python怎么用变量

写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...

python变量命名规则——来自小白的总结

python是一个动态编译类编程语言,所以程序在运行前不需要如C语言的先行编译动作,因此也只有在程序运行过程中才能发现程序的问题。基于此,python的变量就有一定的命名规范。python作为当前热门...

Python入门学习教程:第 2 章 变量与数据类型

2.1什么是变量?在编程中,变量就像一个存放数据的容器,它可以存储各种信息,并且这些信息可以被读取和修改。想象一下,变量就如同我们生活中的盒子,你可以把东西放进去,也可以随时拿出来看看,甚至可以换成...

绘制学术论文中的“三线表”具体指导

在科研过程中,大家用到最多的可能就是“三线表”。“三线表”,一般主要由三条横线构成,当然在变量名栏里也可以拆分单元格,出现更多的线。更重要的是,“三线表”也是一种数据记录规范,以“三线表”形式记录的数...

Python基础语法知识--变量和数据类型

学习Python中的变量和数据类型至关重要,因为它们构成了Python编程的基石。以下是帮助您了解Python中的变量和数据类型的分步指南:1.变量:变量在Python中用于存储数据值。它们充...

一文搞懂 Python 中的所有标点符号

反引号`无任何作用。传说Python3中它被移除是因为和单引号字符'太相似。波浪号~(按位取反符号)~被称为取反或补码运算符。它放在我们想要取反的对象前面。如果放在一个整数n...

Python变量类型和运算符_python中变量的含义

别再被小名词坑哭了:Python新手常犯的那些隐蔽错误,我用同事的真实bug拆给你看我记得有一次和同事张姐一起追查一个看似随机崩溃的脚本,最后发现罪魁祸首竟然是她把变量命名成了list。说实话...

从零开始:深入剖析 Spring Boot3 中配置文件的加载顺序

在当今的互联网软件开发领域,SpringBoot无疑是最为热门和广泛应用的框架之一。它以其强大的功能、便捷的开发体验,极大地提升了开发效率,成为众多开发者构建Web应用程序的首选。而在Spr...

Python中下划线 ‘_’ 的用法,你知道几种

Python中下划线()是一个有特殊含义和用途的符号,它可以用来表示以下几种情况:1在解释器中,下划线(_)表示上一个表达式的值,可以用来进行快速计算或测试。例如:>>>2+...

解锁Shell编程:变量_shell $变量

引言:开启Shell编程大门Shell作为用户与Linux内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是...

一文学会Python的变量命名规则!_python的变量命名有哪些要求

目录1.变量的命名原则3.内置函数尽量不要做变量4.删除变量和垃圾回收机制5.结语1.变量的命名原则①由英文字母、_(下划线)、或中文开头②变量名称只能由英文字母、数字、下画线或中文字所组成。③英文字...

更可靠的Rust-语法篇-区分语句/表达式,略览if/loop/while/for

src/main.rs://函数定义fnadd(a:i32,b:i32)->i32{a+b//末尾表达式}fnmain(){leta:i3...

C++第五课:变量的命名规则_c++中变量的命名规则

变量的命名不是想怎么起就怎么起的,而是有一套固定的规则的。具体规则:1.名字要合法:变量名必须是由字母、数字或下划线组成。例如:a,a1,a_1。2.开头不能是数字。例如:可以a1,但不能起1a。3....

Rust编程-核心篇-不安全编程_rust安全性

Unsafe的必要性Rust的所有权系统和类型系统为我们提供了强大的安全保障,但在某些情况下,我们需要突破这些限制来:与C代码交互实现底层系统编程优化性能关键代码实现某些编译器无法验证的安全操作Rus...

探秘 Python 内存管理:背后的神奇机制

在编程的世界里,内存管理就如同幕后的精密操控者,确保程序的高效运行。Python作为一种广泛使用的编程语言,其内存管理机制既巧妙又复杂,为开发者们提供了便利的同时,也展现了强大的底层控制能力。一、P...