Skip to content

Commit

Permalink
Add support H.264 levels
Browse files Browse the repository at this point in the history
Also implements macroblocks/s limit, and varies num_preview_video_frames
by requested framerate
  • Loading branch information
waveform80 committed Jun 14, 2016
1 parent 1b7697a commit 704a95d
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 8 deletions.
10 changes: 7 additions & 3 deletions picamera/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,9 @@ def start_recording(
'high', but can be one of 'baseline', 'main', 'high', or
'constrained'.
* *level* - The H.264 level to use for encoding. Defaults to '4', but
can be one of '4', '4.1', or '4.2'.
* *intra_period* - The key frame rate (the rate at which I-frames are
inserted in the output). Defaults to ``None``, but can be any 32-bit
integer value representing the number of frames between successive
Expand Down Expand Up @@ -966,8 +969,9 @@ def start_recording(
* *bitrate* - The bitrate at which video will be encoded. Defaults to
17000000 (17Mbps) if not specified. The maximum value is 25000000
(25Mbps). Bitrate 0 indicates the encoder should not use bitrate
control (the encoder is limited by the quality only).
(25Mbps), except for H.264 level 4.2 for which the maximum is
62500000 (62.5Mbps). Bitrate 0 indicates the encoder should not use
bitrate control (the encoder is limited by the quality only).
* *quality* - Specifies the quality that the encoder should attempt
to maintain. For the ``'h264'`` format, use values between 10 and 40
Expand Down Expand Up @@ -1971,7 +1975,7 @@ def _configure_camera(
cc.one_shot_stills = 1
cc.max_preview_video_w = resolution.width
cc.max_preview_video_h = resolution.height
cc.num_preview_video_frames = 3
cc.num_preview_video_frames = max(3, framerate // 10)
cc.stills_capture_circular_buffer_height = 0
cc.fast_preview_resume = 0
cc.use_stc_timestamp = clock_mode
Expand Down
27 changes: 22 additions & 5 deletions picamera/encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ def __init__(
def _create_encoder(
self, format, bitrate=17000000, intra_period=None, profile='high',
quantization=0, quality=0, inline_headers=True, sei=False,
motion_output=None, intra_refresh=None):
motion_output=None, intra_refresh=None, level='4'):
"""
Extends the base :meth:`~PiEncoder._create_encoder` implementation to
configure the video encoder for H.264 or MJPEG output.
Expand All @@ -614,13 +614,23 @@ def _create_encoder(
except KeyError:
raise PiCameraValueError('Unsupported format %s' % format)

if not (0 <= bitrate <= 25000000):
raise PiCameraValueError('bitrate must be between 0 and 25Mbps')
limit = 62500000 if format == 'h264' and level == '4.2' else 25000000
if not (0 <= bitrate <= limit):
raise PiCameraValueError(
'bitrate must be between 0 and %.1fMbps' % (bitrate / 1000000))
self.output_port.bitrate = bitrate
self.output_port.framerate = 0
self.output_port.commit()

if format == 'h264':
limit = 522240 if level == '4.2' else 245760
w, h = self.output_port.framesize
w = mmal.VCOS_ALIGN_UP(w, 16) >> 4
h = mmal.VCOS_ALIGN_UP(h, 16) >> 4
if w * h * (self.parent.framerate + self.parent.framerate_delta) > limit:
raise PiCameraValueError(
'too many macroblocks/s requested; reduce resolution or '
'framerate')
mp = mmal.MMAL_PARAMETER_VIDEO_PROFILE_T(
mmal.MMAL_PARAMETER_HEADER_T(
mmal.MMAL_PARAMETER_PROFILE,
Expand All @@ -633,10 +643,17 @@ def _create_encoder(
'main': mmal.MMAL_VIDEO_PROFILE_H264_MAIN,
'high': mmal.MMAL_VIDEO_PROFILE_H264_HIGH,
'constrained': mmal.MMAL_VIDEO_PROFILE_H264_CONSTRAINED_BASELINE,
}[profile]
}[profile]
except KeyError:
raise PiCameraValueError("Invalid H.264 profile %s" % profile)
mp.profile[0].level = mmal.MMAL_VIDEO_LEVEL_H264_4
try:
mp.profile[0].level = {
'4': mmal.MMAL_VIDEO_LEVEL_H264_4,
'4.1': mmal.MMAL_VIDEO_LEVEL_H264_41,
'4.2': mmal.MMAL_VIDEO_LEVEL_H264_42,
}[level]
except KeyError:
raise PiCameraValueError("Invalid H.264 level %s" % level)
self.output_port.params[mmal.MMAL_PARAMETER_PROFILE] = mp

if inline_headers:
Expand Down
11 changes: 11 additions & 0 deletions tests/test_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,17 @@ def test_multi_res_record(camera, mode):
verify_video(v_stream1, 'h264', resolution)
verify_video(v_stream2, 'h264', new_res)

def test_macroblock_limit(camera):
res, fps = camera.resolution, camera.framerate
try:
camera.resolution = '1080p'
camera.framerate = 31
with pytest.raises(picamera.PiCameraValueError):
camera.start_recording(os.devnull, 'h264')
finally:
camera.resolution = res
camera.framerate = fps

class SizeTest(object):
def __init__(self):
self.size = 0
Expand Down

0 comments on commit 704a95d

Please sign in to comment.