- Оригинальный адрес:Video Streaming with Flask
- Оригинальный автор:Miguel Grinberg
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:BriFuture
- Корректор:Mcskiller, Park-ma
Как вы уже знаете, я опубликовал в партнерстве с O'Reilly MediaКниги и несколько видеороликов, объясняющих Flask. Хотя эти книги и видеоролики достаточно подробно рассказывают о Flask, по какой-то причине некоторые функции недостаточно освещены, поэтому я подумал, что было бы неплохо включить их в эту статью.
Эта статья посвященапоток, интересная функция, которая позволяет приложениям Flask обслуживать очень большие ответы небольшими порциями, что может занять много времени. Чтобы проиллюстрировать эту тему, вы увидите, как создать сервер потокового видео в реальном времени.
Уведомление: теперь есть продолжение этой статьи,Flask Video Streaming Revisited, я расскажу о некоторых улучшениях сервера потоковой передачи, представленных в этой статье, в следующем посте.
Что такое поток?
Потоковая передача — это метод, который позволяет серверу разбивать данные ответа на фрагменты по мере того, как он отвечает на запрос. Я могу придумать много причин, которые могут быть полезны:
-
Супер огромные данные ответов. Для очень больших данных ответа очень неэффективно сначала загружать данные ответа в память, а затем возвращать их клиенту. Другой способ - записать данные ответа на диск, а затем использовать
flask.send_file()
Верните файл клиенту, но это увеличит количество операций ввода-вывода. Это гораздо лучший подход, если данные ответа невелики, поскольку данные можно хранить фрагментами. - Данные в реальном времени. Для некоторых приложений может потребоваться возврат данных из источника данных в режиме реального времени в запрос. Хорошим примером является доставка видео или аудио в реальном времени. Многие камеры безопасности используют эту технологию для потоковой передачи видео на сервер.
Потоковая передача с Flask
Колба с помощьюФункции генератораВстроенная поддержка потоковых ответов. Генератор — это специальная функция, которую можно прервать или продолжить работу. Взгляните на функцию ниже:
def gen():
yield 1
yield 2
yield 3
Это функция, которая выполняется в три этапа, каждый из которых возвращает значение. Реализация генераторов выходит за рамки этой статьи, если вам интересно, следующий сеанс оболочки покажет вам, как использовать генераторы:
>>> x = gen()
>>> x
<generator object gen at 0x7f06f3059c30>
>>> x.next()
1
>>> x.next()
2
>>> x.next()
3
>>> x.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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
объект. Flask позаботится о вызове генератора и отправке всех частичных результатов клиенту порциями.
Примечание переводчика: в python3 посетите
/stock-table
При маршрутизации, если в режиме отладки вы видитеAttributeError: 'NoneType' object has no attribute 'app'
, вам нужно использовать входной параметр Responsestream_with_context()
предварительная обработка. Импортируйте функцию:from flask import stream_with_context
, возвращаемое значение маршрута:return Response( stream_with_context( generate_stock_table() ) )
.
Для этого конкретного примера предположимStock.query.all()
Возвращает итерируемый результат запроса к базе данных, затем вы можете создать огромную таблицу со скоростью одной строки за раз, поэтому независимо от того, сколько элементов в результате запроса, использование памяти процессом Python не будет связано с сборкой огромных символы ответа Строка становится все больше и больше.
Дивизиональный ответ
В приведенном выше примере таблицы создается небольшая часть традиционной страницы, а затем все части объединяются в окончательный документ. Это отличный пример того, как генерировать огромные ответы, но более захватывающим является манипулирование данными в реальном времени.
Интересное использование потоков заключается в том, что каждый блок данных заменяет предыдущий блок на странице, чтобы поток мог «воспроизводиться» или анимироваться в окне браузера. Используя эту технику, вы можете использовать изображение для каждой части потока, в результате чего получится классный видеоплеер, работающий в браузере.
Секрет внедрения обновлений на месте заключается в использованиисоставнойотклик. Содержимое частичного ответа представляет собой заголовок, содержащий тип частичного содержимого, за которым следуетграницаОтмечает сегментированные разделы, каждый из которых имеет свой тип контента.
Существует несколько типов частичного содержимого для разных целей. Чтобы каждая часть потока заменяла предыдущую часть, тип контента должен использоватьmultipart/x-mixed-replace
. Чтобы дать вам представление о том, как это выглядит, вот структура сегментированного видеопотока:
HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace; boundary=frame
--frame
Content-Type: image/jpeg
<jpeg data here>
--frame
Content-Type: image/jpeg
<jpeg data here>
...
Как видите, структура проста. главныйContent-Type
голова настроена наmultipart/x-mixed-replace
, который также определяет граничную строку. Затем следуют деления с двумя черточками перед граничной строкой, занимающие одну строку. Эта часть имеет своюContent-Type
заголовок, каждая часть имеет необязательныйContent-Length
Заголовок, указывающий длину в байтах этой части данных, но по крайней мере для картинок браузеры могут обрабатывать потоковые данные без длины.
Создайте сервер потокового видео в реальном времени
В этой статье достаточно теории, теперь пришло время создать полноценное приложение, способное транслировать потоковое видео в браузер.
Существует множество способов потоковой передачи видео в браузер, каждый из которых имеет свои плюсы и минусы. Один из методов, который действительно хорошо работает с потоковой природой Flask, — это потоковая передача серии отдельных изображений JPEG. это называетсяДвижущийся JPEG, этот метод используется некоторыми IP-камерами безопасности. Этот метод имеет низкую задержку, но качество не самое лучшее, потому что сжатие JPEG неэффективно для мобильного видео.
Ниже вы увидите особенно простое, но хорошо зарекомендовавшее себя веб-приложение, которое обслуживает движущийся поток 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
класс, который отвечает за предоставление последовательности кадров. В текущей ситуации хорошей идеей будет поместить часть управления камерой в отдельный модуль, чтобы веб-приложение могло сохранить код чистым, простым и общим.
Приложение имеет два маршрута. маршрутизация/
при условии, что определено вindex.html
Главная страница в шаблоне. Вы можете увидеть содержимое файла шаблона из кода ниже:
<html>
<head>
<title>Video Streaming Demonstration</title>
</head>
<body>
<h1>Video Streaming Demonstration</h1>
<img src="{{ url_for('video_feed') }}">
</body>
</html>
Это простая HTML-страница с заголовком и тегом изображения. Обратите внимание на теги изображенияsrc
Свойство указывает на второй маршрут приложения, и именно здесь происходит волшебство.
маршрутизация/video_feed
То, что возвращается, является потоковым ответом. Поскольку поток возвращает изображение, которое можно отобразить на веб-странице, URL-адрес маршрута помещается в тег изображения.src
в свойствах. Браузер будет автоматически отображать изображение JPEG в потоке, поддерживая обновление элемента изображения, поскольку частичные ответы поддерживаются большинством (или даже всеми) браузерами (если вы найдете браузер, в котором нет этой функции, обязательно дайте мне знать).
существует/video_feed
Генераторная функция, используемая в маршрутизации, называетсяgen()
, он получаетCamera
Экземпляр класса в качестве параметра.mimetype
Настройки параметров такие же, как указано выше, даmultipart/x-mixed-replace
тип, граничная строка устанавливается наframe
.
gen()
Функция входит в цикл, который непрерывно возвращает данные кадра, полученные с камеры, в виде блока ответа. Функция вызываетсяcamera.get_frame()
Метод получает кадр данных с камеры, а затем преобразует кадр в тип содержимогоimage/jpeg
Форма блока ответа дает, как описано выше.
Получить кадры с видеокамеры
Теперь осталось только реализоватьCamera
class, он должен иметь возможность подключаться к оборудованию камеры и загружать видеокадры в реальном времени с оборудования. Преимущество инкапсуляции аппаратно-зависимой части приложения в класс состоит в том, что класс может иметь разные реализации для разных групп людей, но остальная часть приложения остается неизменной. Вы можете думать об этом классе как о драйвере устройства, который обеспечивает единую реализацию независимо от того, какое аппаратное обеспечение фактически используется.
будет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.jpg
,2.jpg
и3.jpg
, а затем вернуться к ним со скоростью один кадр в секунду.get_frame()
Метод использует текущее время в секундах, чтобы решить, какое из трех изображений должно быть возвращено в данный момент. Очень просто, не так ли?
Чтобы запустить эту смоделированную камеру, мне нужно создать три кадра. я используюgimpСделал следующую картинку:
Поскольку камера моделируется, приложение может работать в любой среде, так что вы можете запустить его прямо сейчас! У меня все готово для этого приложения и я его вставилGitHubначальство. если вы знакомы сgit
, вы можете клонировать этот репозиторий с помощью следующей команды:
$ git clone https://github.com/miguelgrinberg/flask-video-streaming.git
Если вы хотите скачать приложение, вы можете скачать его сздесьПолучите zip-архив.
После установки приложения создайте виртуальную среду и установите Flask. Затем вы можете запустить команду:
$ python app.py
Когда вы открываете приложение, введите в браузереhttp://localhost:5000
, вы можете видеть смоделированный видеопоток, непрерывно воспроизводя изображения 1, 2, 3. Разве это не круто?
Когда я сделал это, я загрузил Raspberry Pi с модулем камеры и внедрил новыйCamera
класс, который превращает Raspberry Pi в сервер потокового видео, используяpicamera
пакет для управления оборудованием. Соответствующая реализация камеры здесь не рассматривается, но вы можетеcamera_pi.py
Найдите соответствующий исходный код в .
Если у вас есть Raspberry Pi и модуль камеры, вы можете отредактироватьapp.py
файл, импортированный из этого модуляCamera
class, то вы можете транслировать в прямом эфире с камеры Raspberry Pi, как я сделал на скриншоте ниже:
Если вы хотите это потоковое приложение с разными камерами, то вам придется его переписать.Camera
реализация класса. Я был бы признателен, если бы вы реализовали такой класс камеры и предоставили его для моего проекта GitHub.
ограничения потока
Когда приложение Flask обслуживает регулярные запросы, цикл запроса короткий. Веб-воркер получает запрос, вызывает функцию-обработчик и, наконец, возвращает ответ. Как только ответ возвращается клиенту, рабочий процесс бездействует, ожидая получения следующего запроса.
Когда получен запрос на использование потока, рабочий процесс сохраняется в клиенте на протяжении всего потока. При обработке бесконечного длинного потока, например видеопотока с камеры, рабочий процесс будет держать клиент заблокированным до тех пор, пока клиент не отключится. Это означает, что если не используются специальные методы, приложение должно обслуживать столько веб-воркеров, сколько клиентов. Запуск приложения Flask в режиме отладки означает, что существует только один поток, поэтому вы не можете открыть другое окно браузера и просматривать поток в двух местах одновременно.
Есть много способов обойти это критическое ограничение. Я думаю, что лучшим решением является использование веб-сервера на основе сопрограмм, который очень хорошо поддерживает Flask.gevent. gevent может обрабатывать несколько клиентов в одном рабочем потоке с помощью сопрограмм, потому что gevent модифицирует функции ввода-вывода Python для обработки переключения контекста, когда это необходимо.
в заключении
Если вы пропустили вышеизложенное, вы можете увидеть соответствующий код для этой статьи в репозитории GitHub:GitHub.com/Miguel Next Day NB…. Вы можете найти общую реализацию потокового видео, не требующую камеры, а также реализацию модуля камеры Raspberry Pi. этопоследующие статьиОписывает некоторые улучшения, которые я сделал с момента первоначальной публикации этой статьи.
Я надеюсь, что эта статья прольет свет на тему потоковой передачи. Я сосредоточился на потоковом видео, потому что у меня есть некоторый опыт в этой области, но применение потокового вещания не ограничивается видео. Например, этот метод можно использовать для поддержания связи между сервером и клиентом в течение длительного времени, позволяя серверу отправлять новую информацию по мере ее появления. В последнее время протокол Web Socket может делать это более эффективно, но Web Socket является довольно новым и работает только в современных браузерах, тогда как потоковая передача работает в очень большом количестве браузеров.
Если у вас есть какие-либо вопросы, пожалуйста, напишите их ниже. Я планирую продолжать писать статьи на темы Flask, которые не известны широкой публике, поэтому я надеюсь, что вы каким-то образом сможете связаться со мной, чтобы узнать, когда будут опубликованы новые статьи, увидимся в следующей статье.
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллекти другие поля, если вы хотите видеть больше качественных переводов, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.