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

什么是流媒体?

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

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

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

    
     
    
    
    

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

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

    
    

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

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

    Multipart Responses

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

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

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

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

    
    

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

    构建实时视频流服务器

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

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

    
    

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

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

    
    

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

    
    

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

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

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

    
    

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

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

    
    

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

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

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

    流媒体的局限性

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

    结论

    Ref:

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

    声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

    上一篇 2021年2月15日
    下一篇 2021年2月15日

    相关推荐