Files @ 95b03a865879
Branch filter:

Location: light9/bin/picamserve - annotation

Drew Perttula
picamserve networking config. fix snapshot system
Ignore-this: a886b8f63c40cf47614265c16877ecf2
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
bb92c50438ed
1f877950ad28
bb92c50438ed
bb92c50438ed
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
bb92c50438ed
1f877950ad28
1f877950ad28
bb92c50438ed
bb92c50438ed
bb92c50438ed
2ee97997ee56
1f877950ad28
bb92c50438ed
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
bb92c50438ed
bb92c50438ed
bb92c50438ed
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
1f877950ad28
bb92c50438ed
1f877950ad28
bb92c50438ed
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
1f877950ad28
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
bb92c50438ed
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
bb92c50438ed
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
1f877950ad28
#!env_pi/bin/python
from __future__ import division
from run_local import log
import sys;sys.path.append('/usr/lib/python2.7/dist-packages/')
import io, logging, traceback, time
import cyclone.web
from twisted.internet import reactor, threads
from twisted.internet.defer import inlineCallbacks
from light9 import prof

try:
    import picamera
    cameraCls = picamera.PiCamera
except ImportError:
    class cameraCls(object):
        def __enter__(self): return self
        def __exit__(self, *a): pass
        def capture(self, out, *a, **kw):
            out.write(open('yuv.demo').read())
        def capture_continuous(self, *a, **kw):
            for i in range(1000):
                time.sleep(1)
                yield str(i)

def setCameraParams(c, arg):
    res = int(arg('res', 480))
    c.resolution = {
        480: (640, 480),
        1080: (1920, 1080),
        1944: (2592, 1944),
    }[res]
    c.shutter_speed = int(arg('shutter', 50000))
    c.exposure_mode = arg('exposure_mode', 'fixedfps')
    c.awb_mode = arg('awb_mode', 'off')
    c.brightness = int(arg('brightness', 50))
    
    c.awb_gains = (float(arg('redgain', 1)), float(arg('bluegain', 1)))
    c.ISO = int(arg('iso', 250))

def setupCrop(c, arg):
    c.crop = (float(arg('x', 0)), float(arg('y', 0)),
              float(arg('w', 1)), float(arg('h', 1)))
    rw = rh = int(arg('resize', 100))
    # width 1920, showing w=.3 of image, resize=100 -> scale is 100/.3*1920
    # scl is [ output px / camera px ]
    scl1 = rw / (c.crop[2] * c.resolution[0])
    scl2 = rh / (c.crop[3] * c.resolution[1])
    if scl1 < scl2:
        # width is the constraint; reduce height to the same scale
        rh = int(scl1 * c.crop[3] * c.resolution[1])
    else:
        # height is the constraint
        rw = int(scl2 * c.crop[2] * c.resolution[0])
    return rw, rh
    
@prof.logTime
def getFrame(c, arg):
    setCameraParams(c, arg)
    resize = setupCrop(c, arg)
    out = io.BytesIO('w')
    prof.logTime(c.capture)(out, 'jpeg', use_video_port=True, resize=resize)
    return out.getvalue()
    
class Pic(cyclone.web.RequestHandler):
    def get(self):
        try:
            self.set_header('Content-Type', 'image/jpeg')
            self.write(getFrame(self.settings.camera, self.get_argument))
        except Exception:
            traceback.print_exc()

def captureContinuousAsync(c, resize, onFrame):
    """
    Calls c.capture_continuous is called in another thread. onFrame is
    called in this reactor thread with each (frameTime, frame)
    result. Runs until onFrame raises StopIteration.
    """
    def runner(c, resize):
        stream = io.BytesIO()
        t = time.time()
        for nextFrame in c.capture_continuous(stream, 'jpeg', use_video_port=True,
                                              resize=resize):
            t2 = time.time()
            log.debug(" - framecap got %s bytes in %.1f ms",
                     len(stream.getvalue()), 1000 * (t2 - t))
            try:
                # This is slow, like 13ms. Hopefully
                # capture_continuous is working on gathering the next
                # pic during this time instead of pausing.
                # Instead, we could be stashing frames onto a queue or
                # something that the main thread can pull when
                # possible (and toss if it gets behind).
                threads.blockingCallFromThread(reactor,
                                               onFrame, t, stream.getvalue())
            except StopIteration:
                break
            t3 = time.time()
            log.debug(" - sending to onFrame took %.1fms", 1000 * (t3 - t2))
            stream.truncate()
            stream.seek(0)
            t = time.time()
    return threads.deferToThread(runner, c, resize)

class FpsReport(object):
    def __init__(self):
        self.frameTimes = []
        self.lastFpsLog = 0
        
    def frame(self):
        now = time.time()
        
        self.frameTimes.append(now)
        
        if len(self.frameTimes) > 15:
            del self.frameTimes[:5]
            
        if now > self.lastFpsLog + 2 and len(self.frameTimes) > 5:
            deltas = [(b - a) for a, b in zip(self.frameTimes[:-1],
                                              self.frameTimes[1:])]
            avg = sum(deltas) / len(deltas)
            log.info("fps: %.1f", 1 / avg)
            self.lastFpsLog = now
        
    
class Pics(cyclone.web.RequestHandler):
    @inlineCallbacks
    def get(self):
        try:
            self.set_header('Content-Type', 'x-application/length-time-jpeg')
            c = self.settings.camera
            setCameraParams(c, self.get_argument)
            resize = setupCrop(c, self.get_argument)
                          
            self.running = True
            log.info("connection open from %s", self.request.remote_ip)
            fpsReport = FpsReport()
            
            def onFrame(frameTime, frame):
                if not self.running:
                    raise StopIteration
                    
                now = time.time()
                self.write("%s %s\n" % (len(frame), frameTime))
                self.write(frame)
                self.flush()

                fpsReport.frame()

            yield captureContinuousAsync(c, resize, onFrame)
        except Exception:
            traceback.print_exc()
            
    def on_connection_close(self, *a, **kw):
        log.info("connection closed")
        self.running = False
            
log.setLevel(logging.INFO)

with cameraCls() as camera:
    port = 8001
    reactor.listenTCP(port, cyclone.web.Application(handlers=[
        (r'/pic', Pic),
        (r'/pics', Pics),
        (r'/static/(.*)', cyclone.web.StaticFileHandler, {'path': 'static/'}),
        (r'/(|gui.js)', cyclone.web.StaticFileHandler, {'path': 'light9/vidref/',
                                                 'default_filename': 'index.html'}),
        ], debug=True, camera=camera))
    log.info("serving on %s" % port)
    reactor.run()