尽管我了解 Python 语法并编写了一些用于数据处理和分析(光谱和图像)的脚本来完成这项工作,但我从未真正使用过网络或流媒体,我想我必须承认我的编程技能相当低。也许,我正在尝试处理超出我当前技能允许的事情,但这可能是开发的常见情况。
无论如何,我正在开发(另一个)GUI 客户端来控制 Raspberry Pi 相机 - 既为了乐趣又为了学习。长话短说,我想从这个 GUI 运行一个流式 http 服务器。我寻求一个现成的解决方案并遵循这个食谱
http://picamera.readthedocs.io/en/latest/recipes2.html#web-streaming
import io
import picamera
import logging
import socketserver
from threading import Condition
from http import server
PAGE="""\
<html>
description of webpage
</html>
"""
class StreamingOutput(object):
def __init__(self):
self.frame = None
self.buffer = io.BytesIO()
self.condition = Condition()
def write(self, buf):
if buf.startswith(b'\xff\xd8'):
# New frame, copy the existing buffer's content and notify all
# clients it's available
self.buffer.truncate()
with self.condition:
self.frame = self.buffer.getvalue()
self.condition.notify_all()
self.buffer.seek(0)
return self.buffer.write(buf)
class StreamingHandler(server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/':
self.send_response(301)
self.send_header('Location', '/index.html')
self.end_headers()
elif self.path == '/index.html':
content = PAGE.encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.send_header('Content-Length', len(content))
self.end_headers()
self.wfile.write(content)
elif self.path == '/stream.mjpg':
self.send_response(200)
self.send_header('Age', 0)
self.send_header('Cache-Control', 'no-cache, private')
self.send_header('Pragma', 'no-cache')
self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME')
self.end_headers()
try:
while True:
with output.condition:
output.condition.wait()
frame = output.frame
self.wfile.write(b'--FRAME\r\n')
self.send_header('Content-Type', 'image/jpeg')
self.send_header('Content-Length', len(frame))
self.end_headers()
self.wfile.write(frame)
self.wfile.write(b'\r\n')
except Exception as e:
logging.warning(
'Removed streaming client %s: %s',
self.client_address, str(e))
else:
self.send_error(404)
self.end_headers()
class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
allow_reuse_address = True
daemon_threads = True
with picamera.PiCamera(resolution='640x480', framerate=24) as camera:
output = StreamingOutput()
camera.start_recording(output, format='mjpeg')
try:
address = ('', 8000)
server = StreamingServer(address, StreamingHandler)
server.serve_forever()
finally:
camera.stop_recording()
好的,如果该代码作为独立应用程序运行,则可以正常工作。但是如果我尝试让它作为一个函数运行,即如果我想在类构造之后这样做
def main():
with picamera.PiCamera(resolution='640x480', framerate=24) as camera:
output = StreamingOutput()
camera.start_recording(output, format='mjpeg')
try:
address = ('', 8000)
server = StreamingServer(address, StreamingHandler)
server.serve_forever()
except(KeyboardInterrupt):
camera.stop_recording()
if __name__ == '__main__':
main()
那么尽管创建了输出和服务器对象,它也不会进行流式传输。我真的很困惑,请问有人可以回答为什么吗? - 如果答案很简单并且问题很愚蠢,我不会感到惊讶,因此,如果有人可以推荐一些教程或简单阅读有关编写用于流式传输/接收数据的服务器/客户端的内容,我将不胜感激。
另一件事是,我希望能够根据请求终止该服务器 - 为此,我想最好的解决方案是使用线程模块并使 gui 和服务器在单独的线程中运行? 非常感谢
N
最佳答案
你是对的,第一个答案非常简单。
问题在于,当定义变量 output
的代码位于 StreamingHandler
中时,该变量不在 scope 内一个名为 main
的函数。
output = 5
def test():
print(output)
# the following statement runs fine, output is in scope because it
# was defined in the top-level scope
test()
def test_2():
print(output_2)
def main():
output_2 = 6
test_2()
# error! test_2 doesn't know the value of output_2 because the
# output_2 variable was declared within main()
main()
因此,您需要找到一种方法将 output
变量传递到服务器。我的方法是在 StreamingHandler 中将输出声明为类变量,并在实例化新的 StreamingServer 时添加输出作为参数,如下所示:
import io
import picamera
import logging
import socketserver
from threading import Condition
from http import server
PAGE="""\
<html>
description of webpage
</html>
"""
class StreamingOutput(object):
def __init__(self):
self.frame = None
self.buffer = io.BytesIO()
self.condition = Condition()
def write(self, buf):
if buf.startswith(b'\xff\xd8'):
# New frame, copy the existing buffer's content and notify all
# clients it's available
self.buffer.truncate()
with self.condition:
self.frame = self.buffer.getvalue()
self.condition.notify_all()
self.buffer.seek(0)
return self.buffer.write(buf)
class StreamingHandler(server.BaseHTTPRequestHandler):
output = None
def do_GET(self):
if self.path == '/':
self.send_response(301)
self.send_header('Location', '/index.html')
self.end_headers()
elif self.path == '/index.html':
content = PAGE.encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.send_header('Content-Length', len(content))
self.end_headers()
self.wfile.write(content)
elif self.path == '/stream.mjpg':
self.send_response(200)
self.send_header('Age', 0)
self.send_header('Cache-Control', 'no-cache, private')
self.send_header('Pragma', 'no-cache')
self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME')
self.end_headers()
try:
while True:
with StreamingHandler.output.condition:
StreamingHandler.output.condition.wait()
frame = StreamingHandler.output.frame
self.wfile.write(b'--FRAME\r\n')
self.send_header('Content-Type', 'image/jpeg')
self.send_header('Content-Length', len(frame))
self.end_headers()
self.wfile.write(frame)
self.wfile.write(b'\r\n')
except Exception as e:
logging.warning(
'Removed streaming client %s: %s',
self.client_address, str(e))
else:
self.send_error(404)
self.end_headers()
class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
allow_reuse_address = True
daemon_threads = True
def __init__(self, address, handler, output):
handler.output = output
super().__init__(address, handler)
def main():
with picamera.PiCamera(resolution='640x480', framerate=24) as camera:
output = StreamingOutput()
camera.start_recording(output, format='mjpeg')
try:
address = ('', 8000)
server = StreamingServer(address, StreamingHandler, output)
server.serve_forever()
except(KeyboardInterrupt):
camera.stop_recording()
if __name__ == '__main__':
main()
我现在把关于杀死服务器的问题留给我博学的同事。
关于Python 模块运行但函数无法运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50354034/