树莓派4摄像头流与以太网通信端口冲突问题解决方案咨询
树莓派摄像头流与指令接收整合问题
我用Raspberry Pi 4实现摄像头视频流功能,通过示例代码搭建了服务器,将树莓派与PC置于同一网络后,可在PC浏览器输入树莓派IP查看实时摄像头流。同时希望PC通过以太网向树莓派发送控制指令(如控制摄像头旋转的舵机指令),两个功能的独立程序均可正常运行,但将二者整合为单程序(分别用线程处理流传输与指令接收)时,始终出现“地址已被占用”错误,求可行的实现方案或替代解决思路。
树莓派端摄像头流代码
import io import picamera import logging import socketserver from threading import Condition from http import server PAGE="""\ <html> <head> <title>Raspberry Pi - Surveillance Camera</title> </head> <body> <center><h1>Raspberry Pi - Surveillance Camera</h1></center> <center><img src="stream.mjpg" width="640" height="480"></center> </body> </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() #Uncomment the next line to change your Pi's Camera rotation (in degrees) #camera.rotation = 90 camera.start_recording(output, format='mjpeg') try: address = ('', 8000) server = StreamingServer(address, StreamingHandler) server.serve_forever() finally: camera.stop_recording()
树莓派端原指令接收代码(独立运行版本)
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = ('192.168.1.145',8000) sock.bind(server_address) sock.listen(1) while 1: connection, client_address = sock.accept() try: message = connection.recv(1024) message = message.decode('utf-8') print(f'Accepted msg from pc {client_address}: {message}') # 这里可以添加舵机控制逻辑 finally: connection.close()
解决方法
方案1:使用不同端口分离服务
最直接的解决方式是给摄像头流服务和指令接收服务分配不同的端口:
- 保持摄像头流服务使用8000端口不变
- 修改指令接收服务的绑定端口为8001(或其他未被占用的端口),PC端发送指令时也对应连接到8001端口
修改后的树莓派整合代码示例:
import io import picamera import logging import socketserver from threading import Condition, Thread from http import server import socket PAGE="""\ <html> <head> <title>Raspberry Pi - Surveillance Camera</title> </head> <body> <center><h1>Raspberry Pi - Surveillance Camera</h1></center> <center><img src="stream.mjpg" width="640" height="480"></center> </body> </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'): 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 def handle_commands(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = ('', 8001) # 使用8001端口 sock.bind(server_address) sock.listen(1) while True: connection, client_address = sock.accept() try: message = connection.recv(1024).decode('utf-8') print(f'Accepted msg from pc {client_address}: {message}') # 执行舵机控制逻辑,比如根据message调用对应的舵机函数 finally: connection.close() with picamera.PiCamera(resolution='640x480', framerate=24) as camera: output = StreamingOutput() camera.start_recording(output, format='mjpeg') try: # 启动指令接收线程 cmd_thread = Thread(target=handle_commands, daemon=True) cmd_thread.start() address = ('', 8000) server = StreamingServer(address, StreamingHandler) server.serve_forever() finally: camera.stop_recording()
方案2:复用HTTP服务器处理指令请求
无需单独开socket服务,直接在现有的HTTP服务器中添加POST请求处理,用来接收PC发送的控制指令。这种方式复用同一个端口,避免端口冲突:
修改StreamingHandler类,新增do_POST方法:
class StreamingHandler(server.BaseHTTPRequestHandler): # 保留原有的do_GET方法不变 def do_GET(self): # ... 原代码 ... def do_POST(self): # 处理PC发送的POST指令 content_length = int(self.headers['Content-Length']) post_data = self.rfile.read(content_length).decode('utf-8') print(f'Received command: {post_data}') # 这里添加舵机控制逻辑,比如解析post_data执行对应动作 # 返回响应 self.send_response(200) self.send_header('Content-Type', 'text/plain') self.end_headers() self.wfile.write(b'Command received')
PC端发送指令的代码示例:
import requests pi_ip = '192.168.1.145' # 发送控制指令 response = requests.post(f'http://{pi_ip}:8000/command', data='rotate_left') print(response.text)
这种方案更简洁,不需要额外维护一个socket服务,统一通过HTTP协议处理视频流和指令请求。
内容的提问来源于stack exchange,提问作者Neo159
相关产品推荐
相关产品推荐

