Skip to content

Commit 0722a87

Browse files
committed
Make NV12 pixel format work
Works for preview (all types), JPEG capture, video recording. Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
1 parent 22fddca commit 0722a87

7 files changed

Lines changed: 83 additions & 6 deletions

File tree

picamera2/encoders/libav_h264_encoder.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,14 @@ def _start(self):
121121
self._stream.codec_context.time_base = Fraction(1, 1000000)
122122
self._stream.codec_context.options["tune"] = "zerolatency"
123123

124-
FORMAT_TABLE = {"YUV420": "yuv420p", "BGR888": "rgb24", "RGB888": "bgr24", "XBGR8888": "rgba", "XRGB8888": "bgra"}
124+
FORMAT_TABLE = {
125+
"YUV420": "yuv420p",
126+
"BGR888": "rgb24",
127+
"RGB888": "bgr24",
128+
"XBGR8888": "rgba",
129+
"XRGB8888": "bgra",
130+
"NV12": "nv12",
131+
}
125132
self._av_input_format = FORMAT_TABLE[self._format]
126133

127134
self._request_release_queue = collections.deque()

picamera2/previews/drm_preview.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class DrmPreview(NullPreview):
5757
"XBGR8888": PixelFormat.XBGR8888,
5858
"YUV420": PixelFormat.YUV420,
5959
"YVU420": PixelFormat.YVU420,
60+
"NV12": PixelFormat.NV12,
6061
"MJPEG": PixelFormat.BGR888,
6162
}
6263

@@ -223,6 +224,9 @@ def render_drm(self, picam2, completed_request):
223224
drmfb = pykms.DmabufFramebuffer(
224225
self.card, width, height, fmt, [fd, fd, fd], [stride, stride2, stride2], [0, size, size + h2 * stride2]
225226
)
227+
elif pixel_format == "NV12":
228+
size = height * stride
229+
drmfb = pykms.DmabufFramebuffer(self.card, width, height, fmt, [fd, fd], [stride, stride], [0, size])
226230
else:
227231
drmfb = pykms.DmabufFramebuffer(self.card, width, height, fmt, [fd], [stride], [0])
228232
self.drmfbs[fb] = drmfb

picamera2/previews/q_gl_picamera2.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ class Buffer:
271271
# doesn't work "VYUY": "VYUY",
272272
"YUV420": "YU12",
273273
"YVU420": "YV12",
274+
"NV12": "NV12",
274275
}
275276

276277
def __init__(self, display, completed_request, max_texture_size):
@@ -306,6 +307,22 @@ def __init__(self, display, completed_request, max_texture_size):
306307
EGL_NONE,
307308
]
308309
# fmt: on
310+
elif pixel_format == "NV12":
311+
h2 = h // 2
312+
# fmt: off
313+
attribs = [
314+
EGL_WIDTH, w,
315+
EGL_HEIGHT, h,
316+
EGL_LINUX_DRM_FOURCC_EXT, fmt,
317+
EGL_DMA_BUF_PLANE0_FD_EXT, fb.planes[0].fd,
318+
EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
319+
EGL_DMA_BUF_PLANE0_PITCH_EXT, cfg.stride,
320+
EGL_DMA_BUF_PLANE1_FD_EXT, fb.planes[0].fd,
321+
EGL_DMA_BUF_PLANE1_OFFSET_EXT, h * cfg.stride,
322+
EGL_DMA_BUF_PLANE1_PITCH_EXT, cfg.stride,
323+
EGL_NONE,
324+
]
325+
# fmt: on
309326
else:
310327
# fmt: off
311328
attribs = [

picamera2/previews/q_picamera2.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,11 @@ def render_request(self, completed_request):
199199
stream_config = camera_config[display_stream_name]
200200

201201
img = completed_request.make_array(display_stream_name)
202-
if stream_config["format"] in ("YUV420", "YUYV"):
202+
fmt = stream_config["format"]
203+
if fmt in ("YUV420", "YUYV", "NV12"):
203204
if cv2_available:
204-
if stream_config["format"] == "YUV420":
205-
img = cv2.cvtColor(img, cv2.COLOR_YUV420p2BGR)
206-
else:
207-
img = cv2.cvtColor(img, cv2.COLOR_YUV2RGB_YUYV)
205+
table = {"YUV420": cv2.COLOR_YUV420p2BGR, "YUYV": cv2.COLOR_YUV2RGB_YUYV, "NV12": cv2.COLOR_YUV2RGB_NV12}
206+
img = cv2.cvtColor(img, table[fmt])
208207
else:
209208
logging.error("Qt preview cannot display YUV420/YUYV without cv2")
210209
return

picamera2/request.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,13 @@ def save(
244244
V = reshaped[2 * height + height // 2 :, : width // 2]
245245
output_bytes = simplejpeg.encode_jpeg_yuv_planes(Y, U, V, quality)
246246
Y = reshaped = U = V = None
247+
elif format == 'NV12':
248+
width, height = self.config[name]['size']
249+
Y = m.array[:height, :width]
250+
U = np.ascontiguousarray(m.array[height : height + height // 2, 0 : 2 * width : 2])
251+
V = np.ascontiguousarray(m.array[height : height + height // 2, 1 : 2 * width : 2])
252+
output_bytes = simplejpeg.encode_jpeg_yuv_planes(Y, U, V, quality)
253+
Y = U = V = None
247254
else:
248255
FORMAT_TABLE = {"XBGR8888": "RGBX", "XRGB8888": "BGRX", "BGR888": "RGB", "RGB888": "BGR"}
249256
output_bytes = simplejpeg.encode_jpeg(m.array, quality, FORMAT_TABLE[format], '420')
@@ -326,6 +333,10 @@ def _make_array_shared(self, buffer: np.ndarray, config: Dict[str, Any]) -> np.n
326333
# These dimensions seem a bit strange, but mean that
327334
# cv2.cvtColor(image, cv2.COLOR_YUV2BGR_YUYV) will convert directly to RGB.
328335
image = array.reshape(h, stride // 2, 2)
336+
elif fmt == "NV12":
337+
# The UV rows should be as long as the Y rows, so we can slice off any
338+
# padding.
339+
image = array.reshape((h * 3 // 2, stride))[:w]
329340
elif fmt == "MJPEG":
330341
image = np.array(Image.open(io.BytesIO(array))) # type: ignore
331342
elif formats.is_raw(fmt):

tests/nv12.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/python3
2+
3+
# Check that the "NV12" pixel format works.
4+
5+
import time
6+
7+
from picamera2 import Picamera2, Preview
8+
from picamera2.encoders import H264Encoder
9+
from picamera2.outputs import PyavOutput
10+
11+
picam2 = Picamera2()
12+
config = picam2.create_video_configuration({'format': 'NV12', 'size': (640, 360)})
13+
picam2.configure(config)
14+
15+
picam2.start_preview(Preview.QTGL)
16+
picam2.start()
17+
18+
time.sleep(1)
19+
20+
picam2.stop_preview()
21+
picam2.start_preview(Preview.QT)
22+
23+
time.sleep(1)
24+
25+
picam2.stop_preview()
26+
picam2.start_preview()
27+
28+
time.sleep(1)
29+
30+
picam2.capture_file("check.jpg")
31+
32+
encoder = H264Encoder(bitrate=5000000)
33+
output = PyavOutput("check.mp4")
34+
picam2.start_recording(encoder, output)
35+
36+
time.sleep(5)
37+
38+
picam2.stop_recording()

tests/test_list.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ tests/null_encoder.py
9595
tests/mode_test.py
9696
tests/multicamera.py
9797
tests/multicamera_2.py
98+
tests/nv12.py
9899
tests/preview_cycle_test.py
99100
tests/preview_location_test.py
100101
tests/preview_start_stop.py

0 commit comments

Comments
 (0)