3

Is it possible to split a Raspberry Pi display so that it shows up twice on one screen?

I'm building a costume that covers my face. To be able to see, I've got a Raspberry Pi with Pi Camera and a 7" display. The display is attached to a Google Cardboard.

I'd like to set up the Pi to show two images like Google Cardboard apps.

I'm running Jessie on a B+.

  • Would that be like a VR helmet or glasses then? – Bex Jan 9 '17 at 8:20
  • Exactly, but not in 3D. – browntastic Jan 10 '17 at 4:02
5

By far the most efficient way of doing this is using a splitter on the camera's preview port, then connecting that to a couple of renderers. Sadly, the picamera library doesn't support this "out of the box" (it connects a splitter on the video port to support recording at multiple resolutions, or capturing while recording, but it doesn't support multiple previews because that's a fairly niche requirement; see camera hardware for more info on the camera's ports).

However, it's quite easy to arrange by using the mmalobj layer which picamera is built upon (that link goes to the latest version of the docs which includes a long introduction to mmalobj, but also has a few currently unreleased features - luckily we don't need the unreleased bits for this).

Basically we're going to build an MMAL pipeline that looks like this:

                    [0]-->renderer (left eye)
      [0]-->splitter[1]-->renderer (right eye)
camera[1]           [2]
      [2]           [3]

The code doesn't look like "normal" picamera code, but read through the introduction in the link above and you should get a reasonable feel for the mmalobj style of doing things. In the code below I assume you've got a 1080p sized display, and I'm using a 960x720 capture size for a 4:3 ratio display in each eye (centered vertically); you'll need to modify things if your display is a different size:

from picamera import mmalobj as mo, mmal
from signal import pause

camera = mo.MMALCamera()
splitter = mo.MMALSplitter()
render_l = mo.MMALRenderer()
render_r = mo.MMALRenderer()

camera.outputs[0].framesize = (960, 720)
camera.outputs[0].framerate = 30
camera.outputs[0].commit()

p = render_l.inputs[0].params[mmal.MMAL_PARAMETER_DISPLAYREGION]
p.set = mmal.MMAL_DISPLAY_SET_FULLSCREEN | mmal.MMAL_DISPLAY_SET_DEST_RECT
p.fullscreen = False
p.dest_rect = mmal.MMAL_RECT_T(0, 180, 960, 720)
render_l.inputs[0].params[mmal.MMAL_PARAMETER_DISPLAYREGION] = p
p.dest_rect = mmal.MMAL_RECT_T(960, 180, 960, 720)
render_r.inputs[0].params[mmal.MMAL_PARAMETER_DISPLAYREGION] = p

splitter.connect(camera.outputs[0])
render_l.connect(splitter.outputs[0])
render_r.connect(splitter.outputs[1])
pause()

At this point you should have a nice, smooth preview in both eyes (and all the work will be occurring on the GPU, so your CPU is entirely free for other tasks).

Controlling the camera via mmalobj requires a bit more effort than with picamera (unsurprising given that picamera is built upon mmalobj, so picamera is a simpler abstraction on top of the lower level mmalobj). For example, to find out how to change the brightness, take a look at PiCamera._set_brightness:

def _set_brightness(self, value):
    self._check_camera_open()
    if not (0 <= value <= 100):
        raise PiCameraValueError(
            "Invalid brightness value: %d (valid range 0..100)" % value)
    self._camera.control.params[mmal.MMAL_PARAMETER_BRIGHTNESS] = Fraction(value, 100)

We can guess that _check_camera_open is some method which'll raise an exception if the camera's been closed. The next bit just checks the specified value is somewhere in the range 0 to 100, and then there's the useful bit at the end: using mmal.MMAL_PARAMETER_BRIGHTNESS on the camera's control port to set the brightness. Note the value is a Fraction with a denominator of 100, so brightness values are actually between 0 and 1.

How do we use that in the script above? Recall from the mmalobj documentation (somewhere near the end of Threads & Synchronization) that PiCamera._camera (self._camera in the snippet above) is simply an instance of MMALCamera. In the script above we start with camera = mo.MMALCamera() so this simply becomes:

camera.control.params[mmal.MMAL_PARAMETER_BRIGHTNESS] = 0.5

Note: parameters which return fractions also accept any other numeric type and will automatically convert the value to a fraction.

It's even possible to mix picamera and mmalobj in a script (if you're careful). For example, knowing that PiCamera._camera is actually an MMALCamera object I could've constructed the camera object as pc = picamera.PiCamera(); camera = pc._camera in the script above, but this would actually wind up more complicated as we'd then have to deal with the null-sink that PiCamera automatically constructs on the preview port.

  • Thanks for the suggestion. Is this something I can put in a python script? – browntastic Jan 10 '17 at 4:02
  • That is a Python script, so yes absolutely. The pause call at the end just tells the script to do nothing until Ctrl+C is hit (the camera firmware on the GPU handles running the preview). If you want to integrate that into an existing Python script I'd suggest doing all that at the beginning then leave out pause and continue with whatever else you want to do. – Dave Jones Jan 10 '17 at 15:04
  • I got your script work, and I read through mmalobj documentation. Thanks again. Is it possible to set something like the brightness through mmalobj as well? – browntastic Jan 11 '17 at 0:07
  • Yup, certainly is. Basically anything you can do in picamera, you can do in mmalobj with a bit more effort (because picamera is built on top of mmalobj). I'll update the answer with a bit more detail about this. – Dave Jones Jan 11 '17 at 9:36
  • I was so close. Thanks a lot for all your help with this! – browntastic Jan 11 '17 at 23:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.