客户端连接断开后保持PiCamera对象持续活跃的实现方案
问题背景
在RPi4上使用picamera库开发视频流推流服务时,PiCamera官方文档提供的网络流录制基础服务端代码可正常运行,代码如下:
import socket import time import picamera camera = picamera.PiCamera() camera.resolution = (640, 480) camera.framerate = 24 server_socket = socket.socket() server_socket.bind(('0.0.0.0', 8000)) server_socket.listen(0) # 接收单个连接并将其封装为类文件对象 connection = server_socket.accept()[0].makefile('wb') try: camera.start_recording(connection, format='h264') camera.wait_recording(60) camera.stop_recording() finally: connection.close() server_socket.close()
原生代码存在两个局限:仅接受单次客户端连接,固定录制60秒H264流后就会停止录制、关闭连接,无法满足长期运行需求。
针对需求做了两处修改:一是取消60秒录制时长限制,实现永久持续推流;二是调整逻辑让服务端socket永久监听端口,支持客户端反复接入。修改后的服务端代码如下:
import socket import time import picamera camera = picamera.PiCamera() camera.resolution = (640, 480) camera.framerate = 30 server_socket = socket.socket() server_socket.bind(('0.0.0.0', 8000)) server_socket.listen(0) while True: # 永久监听 client_socket, client_address = server_socket.accept() print(f"New connection from {client_address}.") connection = client_socket.makefile('wb') try: camera.start_recording(connection, format='h264') while True: # 持续推流不停止 camera.wait_recording(1) camera.stop_recording() except BrokenPipeError as error: print(f"Error {error} occured.") # 调用camera.stop_recording()会触发ConnectionResetError,不调用则新连接接入时会报PiCameraAlreadyRecording finally: pass # connection.close() # server_socket.close()
故障现象
修改后的代码运行时出现异常:
- 客户端主动断开连接会触发
BrokenPipeError - 若在异常分支调用
camera.stop_recording()方法,会触发ConnectionResetError报错 - 若不调用该方法,后续新客户端连接接入时会触发
PiCameraAlreadyRecording报错,无法完成重连推流
使用的客户端基于OpenCV开发,功能为拉取服务端H264流、垂直翻转画面、实时预览并存储为本地MP4文件,代码如下:
import numpy as np import cv2 import sys import datetime server = 'http://192.168.178.54:8000/' video_capture_0 = cv2.VideoCapture(server) fourcc = cv2.VideoWriter_fourcc(*"mp4v") # Ubuntu22.04下可用的编码:mp4v等 date_time_now = datetime.datetime.now() yyyymmddHHMMSS = date_time_now.strftime("%Y-%m-%d_%H%M%S") video_write_0 = cv2.VideoWriter(f"{yyyymmddHHMMSS}.mp4", fourcc, 30.0, (640, 480)) # 存储流到本地文件 while True: ret0, frame0 = video_capture_0.read() if ret0: frame0 = cv2.flip(frame0, 0) # 垂直翻转画面 cv2.imshow('Test 0', frame0) video_write_0.write(frame0) if cv2.waitKey(1) & 0xFF == ord('q'): break video_capture_0.release() video_write_0.release() cv2.destroyAllWindows()
需要实现的目标:客户端连接断开后PiCamera对象始终保持活跃状态,可正常响应后续新的客户端连接请求。
修复方案
问题根因
异常的核心原因是断连时的资源清理逻辑缺失:picamera启动录制后会持续向绑定的socket文件对象写入流数据,客户端断开后写入操作会触发IO异常,此时既没有安全重置相机的录制状态,也没有关闭失效的连接资源,导致相机一直被占用、新连接无法接入。
修复后服务端代码
import socket import time import picamera # 全局仅初始化一次相机实例,避免反复启停硬件 camera = picamera.PiCamera() camera.resolution = (640, 480) camera.framerate = 30 server_socket = socket.socket() # 开启端口复用,避免服务重启时端口被占用 server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(('0.0.0.0', 8000)) server_socket.listen(0) print("Stream server started on port 8000") while True: client_socket, client_address = server_socket.accept() print(f"New connection from {client_address}.") connection = client_socket.makefile('wb') try: camera.start_recording(connection, format='h264') # 持续推流,wait_recording会在连接异常时抛出错误 while True: camera.wait_recording(1) except Exception as e: # 捕获所有断连相关的IO异常,包括BrokenPipeError、ConnectionResetError print(f"Client {client_address} disconnected: {str(e)}") finally: # 统一做资源清理,保证相机状态被重置 try: if camera.recording: camera.stop_recording() except Exception as e: print(f"Stop recording warning: {str(e)}") # 关闭当前连接资源 try: connection.close() except: pass try: client_socket.close() except: pass # 短暂停顿后回到监听状态,等待下一个客户端连接 time.sleep(0.1)
关键修改点说明
- 为服务端socket增加
SO_REUSEADDR配置,解决服务重启时的端口占用问题 - 扩大异常捕获范围,覆盖客户端断连时可能抛出的所有IO类错误,避免异常直接打断主监听循环
- 资源清理逻辑统一放在
finally块执行:先判断相机录制状态,安全调用stop_recording()并兜底捕获可能的异常,再依次关闭连接文件对象、客户端socket,保证每次连接结束后相机回到空闲状态 - 服务端监听socket全程不关闭,相机实例全局只初始化一次,避免反复启停相机硬件带来的延迟和资源消耗
- 每次断连清理完成后增加短暂时延,避免空转占用CPU资源
补充说明
当前客户端使用http://前缀拉取裸TCP H264流的方式,在网络波动时可能出现花屏、首帧加载慢的问题,如果后续需要更高的流稳定性,可以在推流端封装标准HTTP MJPG流或者部署轻量RTSP服务,当前代码已经可以满足断连后自动恢复、支持客户端反复接入的核心需求。
内容的提问来源于stack exchange,提问作者Adriaan
相关产品推荐
相关产品推荐

