clam
clam

Reputation: 55

Why does writing to a file work, but writing to an HTTP server request handler not work?

I'm using a Raspberry Pi Picamera board to capture data, using the following code:

with picamera.PiCamera(
    sensor_mode=4,
    resolution='1640x1232',
    framerate=30
    ) as camera:

    camera.rotation = 180
    camera.start_recording(StreamingOutput(), format='mjpeg')

try:
    server = StreamingServer(('', 8000), StreamingHandler)
    server.serve_forever()
finally:
    camera.stop_recording()


class StreamingOutput:
    def __init__(self):
        self.frame = None
        self.condition = threading.Condition()
        self._buffer = io.BytesIO()

    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)


class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
    allow_reuse_address = True
    daemon_threads = True


class StreamingHandler(server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/capture.jpg':
            with self._output.condition:
                self._output.condition.wait()
                frame = self._output.frame

            # This works.
            with open("frame.jpg", 'wb') as f:
                f.write(frame)

            # This produces a truncated image.
            self.send_response(200)
            self.send_header('Content-Type', 'image/jpeg')
            self.send_header('Content-Length', len(frame))
            self.end_headers()
            self.wfile.write(frame)

It's the damndest thing: although the image will save to disk just fine (frame.jpg is completely fine), it'll produce a truncated image if going through the HTTP server. Here's a screenshot:

truncated image

I've tried a number of different things, and I'm at a dead end. Any ideas?

Upvotes: 2

Views: 248

Answers (2)

clam
clam

Reputation: 55

Here's the working version:

def do_capture(self):
    with self._output.condition:
        self._output.condition.wait()
        frame = self._output.frame

    self.send_response(200)
    self.send_header('Content-Type', 'image/jpeg')
    self.send_header('Content-Length', len(frame))
    self.end_headers()
    fb = io.BytesIO(frame)
    shutil.copyfileobj(fb, self.wfile)

Wrapping frame in a BytesIO stream works in conjunction with shutil, which expects two file-like objects. Together, it produces a functional stream.

Upvotes: 1

jteezy14
jteezy14

Reputation: 466

My guess is that you are running into a memory issue. When you write to disk the content is streamed, but I expect that when using wfile.write(frame) you are having to nearly double the memory used since you aren't chunking the data (instead of having frame and a chunk of frame in memory at any given time you have two copies. I would try using shutil to see if this corrects the issue by performing shutil.copyfileobj(frame, self.wfile). This is just a guess but hopefully it fixes your problem! shutil docs

Upvotes: 1

Related Questions