waveform80 / picamera

A pure Python interface to the Raspberry Pi camera module
https://picamera.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
1.57k stars 357 forks source link

PiCameraCircularIO not truncating #664

Closed leematthewshome closed 3 years ago

leematthewshome commented 3 years ago

The PiCameraCircularIO object does not truncate based on the number of seconds provided as a limit.

I have pretty much taken the exact code from here: https://picamera.readthedocs.io/en/release-1.10/recipes2.html#splitting-to-from-a-circular-stream but am finding that the PiCameraCircularIO does not truncate based on the number of seconds provided as a limit.

My code is below - slightly modified from the example so that every 60 seconds it will write the data to a file. As I understand it the PiCameraCircularIO SHOULD only hold 10 seconds of video, as I have defined it using stream = picamera.PiCameraCircularIO(camera, seconds=10) but whenever the file is created it contains the full 60 seconds of video.

Note however that if I use stream = picamera.PiCameraCircularIO(camera, size=2000000) I do reliably get a file size that is around 1900Kb and about 30 seconds long. So size is working, but seconds is not.

import io
import random
import picamera
from PIL import Image

prior_image = None
counter = 1

def detect_motion(camera):
    global prior_image
    global counter
    stream = io.BytesIO()
    camera.capture(stream, format='jpeg', use_video_port=True)
    stream.seek(0)
    if prior_image is None:
        prior_image = Image.open(stream)
        return False
    else:
        current_image = Image.open(stream)
        prior_image = current_image
        # trigger motion after fixed number of checks
        if counter > 60:
            result = True
            counter = 1
        else:
            print("Noting to see on loop number " + str(counter))
            counter += 1
            result = False
        return result

def write_video(stream):
    with io.open('captured.h264', 'wb') as output:
        for frame in stream.frames:
            if frame.frame_type == picamera.PiVideoFrameType.sps_header:
                stream.seek(frame.position)
                break
        while True:
            buf = stream.read1()
            if not buf:
                break
            output.write(buf)
    stream.seek(0)
    stream.truncate()

print("Lets get started...")
with picamera.PiCamera() as camera:
    camera.resolution = (640, 480)
    stream = picamera.PiCameraCircularIO(camera, seconds=10)
    camera.start_recording(stream, format='h264')
    try:
        while True:
            camera.wait_recording(1)
            if detect_motion(camera):
                print('Motion detected!')
                # Write last 10 seconds to disk 
                write_video(stream)
    finally:
        camera.stop_recording()
6by9 commented 3 years ago

Using seconds is based on the bitrate and duration specified to create a size - https://github.com/waveform80/picamera/blob/master/picamera/streams.py#L696

You've not set the bitrate, so it's at the default of 17Mbit/s. 17Mbit/s at VGA is never going to happen even with the quantisation turned all the way down - it's more sensible for 1080p30. But it means that the computed size is going to cover a longer duration than expected.

Set the bitrate sensibly.

leematthewshome commented 3 years ago

OK. I see from the code it simply does the following:

 if seconds is not None:
            size = bitrate * seconds // 8

I think it would help to illustrate that bitrate needs to be set in the code examples. Otherwise they are quite misleading. Thanks for your help.