2017年10月11日 星期三

[Raspberry PI][Flask] 土炮MJPG Stream

WIKI
Motion JPEGM-JPEGMJPEG,Motion Joint Photographic Experts Group,FourCC:MJPG)是一種影像壓縮格式,其中每一影格圖像都分別使用JPEG編碼。
或許是實作上相對單純好理解,據說MJPEG常用在網路攝影機(IP-Camera)等相關的應用,由於也是相對古老的實作方式,大多的瀏覽器都支援.mjpg格式;在網路上最常被提到的知名套件是MJPG-Streamer ,在輕鬆的設定播放相關參數後可以輕易的將你的攝影機影像投到網頁上.

今天練習用flask實作簡單的mjpg-streamer,算是練練python還有學習一點html與http的概念:



開發環境準備:
  • Python
  • Flask
    • #pip install flask
  • 裝一些好用的VSCode extensions
    • Python Extension Pack :  python 除錯、程式語法格式化、產launch.json之類
    • Happy Flasker:真的超Happy,產flask程式樣板,例如"fapp"產主程式樣板、"froute"產路由樣板
Step 1:建app.py、index.html

在flask上要跑一個超基本的server,顯示hello之類,只需要兩個檔案:
  • app.py
  • templates/index.html
    • 樣板處理(rendering templates),flask預設會去找templates資料夾的內容
    • 見此官網說明
Step 2:(前台)index.html

播放.mjpg檔案不需要任何的javascript,只要指明來源.mjpg網址(url)即可,我們指定顯示來源為source.mjpg,也就是向伺服器索取/source.mjpg:

  %start
<head></head>
<body>
    mjpg demonstration
    <p></p>
    <img src="source.mjpg" alt="failed"/>
</body>
  %end

Step 3:(後台)app.py

(Happy Flasker- "fapp"快速展開基本程式模組)

除了送index.html外,還需要實作/source.mjpg的路由,前台向後台送HTTP-"GET"要求,後台理所當然回應Response,只是此Response從不結束(永遠不送200):

  • 結構包含header與content
    • Header:
      • 整體資料型態為 multipart/x-mixed-replace
        • mixed:後續送出的內容混合了文字與二進位資料
        • replace:後抵達的資料區塊會覆蓋先前的結果,藉此達成動畫效果
        • x-:...不知道什麼意思,但一定要x-mixed-replace才能用
      • boundary=--jpgboundary
        • 指出後續的連續資料塊以"--jpgboundary"作為各單位資料區塊邊界

    • content
      • 每一塊的資料型態為image/jpeg,就是mjpg的由來啦
      • 迷惑:如果jpg的二進位資料最後兩個byte剛好是\r\n的二進位值,瀏覽器如何解析?(疑似Response自己處理掉了計算資料size的部分含在http內送出)
    • 某種大型資料串流(Streaming)的概念,flask的Response物件對於generator會做特別的處置?(一直嘗試呼叫直到迭代器結束?)見Streaming Contents;若不是放generator、又或是非無限generator,Response在迭代完成後會送200作為結束

  %start
from flask import Flask, render_template, Response
import io
import time
app = Flask(__name__)

FRAME_RATE = 60 #60 Frames per second

def gen():
    #returned a generator
    while True:
      time.sleep(1/FRAME_RATE)
      for i in images_bytes:
          yield (b'--jpgboundary'
          b'Content-type: image/jpeg\r\n\r\n' +
          i + b'\r\n')

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

@app.route('/source.mjpg')
def feed_stream():
    #Response is a object
    #Streaming Contents
    #http://flask.pocoo.org/docs/0.12/patterns/streaming/
    return Response(gen(),mimetype='multipart/x-mixed-replace; boundary=--jpgboundary')

if __name__ == '__main__':

  #read out .jpeg files in binary format
  file_list = ['./images/{}.jpeg'.format(x) for x in range(1,10)]
  images_bytes = map(lambda x: open(x,"rb").read(),file_list)

  print "run"
  app.run(host='127.0.0.1', port=8000, debug=True)
  %end

程式碼Repository

沒有留言:

張貼留言