如何从HTTP multipart/x-mixed-replace长连接流保存JPEG至Ubuntu服务器
我来帮你搞定这个问题!要把multipart JPEG流里的图片保存下来,不管是直接在Ubuntu服务器本地处理,还是集成到Ruby on Rails应用里,都有可行的方案,咱们一步步来:
在Ubuntu服务器本地解析并保存流中的JPEG文件
方法1:用Python脚本精细解析每个JPEG帧
如果需要单独保存每一张图片,用Python脚本可以精准处理multipart流的结构:
首先安装依赖:
pip install requests
然后创建解析脚本(比如save_camera_stream.py):
import requests import re # 替换成你的相机流地址 STREAM_URL = "http://your-camera-ip/stream" # 发起流式请求,需要认证就加auth参数 response = requests.get(STREAM_URL, stream=True, auth=('username', 'password')) # 从响应头提取multipart分隔符(boundary) content_type = response.headers.get('Content-Type') boundary = re.search(r'boundary=(.*)', content_type).group(1).encode() img_count = 0 current_img_data = b'' # 逐块读取流数据 for chunk in response.iter_content(chunk_size=1024): if not chunk: continue # 检查是否遇到分隔符 if boundary in chunk: # 如果已有积累的图片数据,尝试保存 if current_img_data: # 定位JPEG的起始(FFD8)和结束(FFD9)标记 start_pos = current_img_data.find(b'\xff\xd8') end_pos = current_img_data.find(b'\xff\xd9') if start_pos != -1 and end_pos != -1: jpeg_bytes = current_img_data[start_pos:end_pos+2] # 保存到本地,按序号命名 with open(f'camera_frame_{img_count:04d}.jpg', 'wb') as f: f.write(jpeg_bytes) print(f"已保存第 {img_count+1} 张图片") img_count += 1 # 重置当前数据,从分隔符后开始积累 current_img_data = chunk.split(boundary)[1] else: current_img_data += chunk
运行脚本即可在当前目录生成按顺序命名的JPEG文件。
方法2:用FFmpeg快速处理(适合转视频或定时截图)
如果不需要单独保存每帧,而是想转成视频或者定时截图,FFmpeg更简单高效:
- 把流保存为MP4视频:
ffmpeg -i http://your-camera-ip/stream -c copy camera_stream.mp4
- 每隔1秒保存一张截图:
ffmpeg -i http://your-camera-ip/stream -r 1 -f image2 screenshot_%04d.jpg
在Ruby on Rails应用中解析并保存流
控制器主动拉取并解析流
可以在Rails控制器中编写逻辑,主动请求相机流并保存每个JPEG帧到应用的文件系统:
require 'net/http' require 'uri' class CameraStreamController < ApplicationController skip_before_action :verify_authenticity_token, only: :save_frames def save_frames stream_uri = URI("http://your-camera-ip/stream") http = Net::HTTP.new(stream_uri.host, stream_uri.port) # 如果相机需要认证,添加基础认证 http.basic_auth('username', 'password') if stream_uri.userinfo http.request_get(stream_uri.path) do |response| # 提取multipart分隔符 content_type = response['Content-Type'] boundary = content_type.match(/boundary=(.*)/)[1] boundary_regex = /--#{Regexp.escape(boundary)}/ current_frame = "" frame_count = 0 # 流式读取响应体 response.read_body do |chunk| parts = chunk.split(boundary_regex) parts.each do |part| next if part.strip.empty? if current_frame.empty? current_frame = part else current_frame += part # 检查是否包含完整的JPEG数据 if current_frame.include?("\xff\xd8") && current_frame.include?("\xff\xd9") start_idx = current_frame.index("\xff\xd8") end_idx = current_frame.index("\xff\xd9") + 2 jpeg_data = current_frame[start_idx...end_idx] # 保存到Rails的storage目录(也可以改成public目录) filename = "camera_frame_#{frame_count.to_s.rjust(4, '0')}.jpg" save_path = Rails.root.join('storage', 'camera_frames', filename) FileUtils.mkdir_p(File.dirname(save_path)) # 确保目录存在 File.open(save_path, 'wb') { |f| f.write(jpeg_data) } frame_count += 1 Rails.logger.info("已保存相机帧:#{filename}") current_frame = "" end end end end end render plain: "开始保存相机流帧..." end end
然后在config/routes.rb中添加路由:
get '/camera/save_frames', to: 'camera_stream#save_frames'
关键注意事项
- 异常处理:要添加相机断开、网络异常的重试逻辑,避免脚本或控制器意外退出。
- 权限问题:确保保存文件的目录有写入权限(比如Rails的storage目录要给web服务器用户(如www-data)授权)。
- 性能优化:如果流的帧率很高,要考虑限制保存频率(比如每5帧保存一张),避免磁盘IO过载。
内容的提问来源于stack exchange,提问作者Travis Glover
相关产品推荐
相关产品推荐

