rpicamsrc: First attempt at implementing MJPEG and raw video support

This commit is contained in:
Jan Schmidt 2016-10-03 02:34:50 +11:00 committed by Tim-Philipp Müller
parent 0a38642214
commit da86cec40e
3 changed files with 170 additions and 74 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2015 Jan Schmidt <jan@centricular.com>
* Copyright (c) 2013-2016 Jan Schmidt <jan@centricular.com>
Portions:
Copyright (c) 2013, Broadcom Europe Ltd
Copyright (c) 2013, James Hughes
@ -269,6 +269,7 @@ void raspicapture_default_config(RASPIVID_CONFIG *config)
config->demoInterval = 250; // ms
config->immutableInput = 1;
config->profile = MMAL_VIDEO_PROFILE_H264_HIGH;
config->encoding = MMAL_ENCODING_H264;
config->bInlineHeaders = 0;
@ -1119,9 +1120,6 @@ raspi_capture_set_format_and_start(RASPIVID_STATE *state)
format = preview_port->format;
format->encoding = MMAL_ENCODING_OPAQUE;
format->encoding_variant = MMAL_ENCODING_I420;
if(config->camera_parameters.shutter_speed > 6000000)
{
MMAL_PARAMETER_FPS_RANGE_T fps_range = {{MMAL_PARAMETER_FPS_RANGE, sizeof(fps_range)},
@ -1147,6 +1145,8 @@ raspi_capture_set_format_and_start(RASPIVID_STATE *state)
}
format->encoding = MMAL_ENCODING_OPAQUE;
format->encoding_variant = MMAL_ENCODING_I420;
format->es->video.width = VCOS_ALIGN_UP(config->width, 32);
format->es->video.height = VCOS_ALIGN_UP(config->height, 16);
format->es->video.crop.x = 0;
@ -1165,9 +1165,7 @@ raspi_capture_set_format_and_start(RASPIVID_STATE *state)
}
// Set the encode format on the video port
format = video_port->format;
format->encoding_variant = MMAL_ENCODING_I420;
if(config->camera_parameters.shutter_speed > 6000000)
{
@ -1182,7 +1180,16 @@ raspi_capture_set_format_and_start(RASPIVID_STATE *state)
mmal_port_parameter_set(video_port, &fps_range.hdr);
}
format->encoding = MMAL_ENCODING_OPAQUE;
/* If encoding, set opaque tunneling format */
if (state->encoder_component) {
format->encoding = MMAL_ENCODING_OPAQUE;
format->encoding_variant = MMAL_ENCODING_I420;
}
else {
format->encoding = config->encoding;
format->encoding_variant = config->encoding;
}
format->es->video.width = VCOS_ALIGN_UP(config->width, 32);
format->es->video.height = VCOS_ALIGN_UP(config->height, 16);
format->es->video.crop.x = 0;
@ -1279,6 +1286,10 @@ gboolean raspi_capture_request_i_frame(RASPIVID_STATE *state)
MMAL_PORT_T *encoder_output = NULL;
MMAL_STATUS_T status;
MMAL_PARAMETER_BOOLEAN_T param = {{ MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME, sizeof(param)}, 1};
if (state->encoder_component)
return TRUE;
encoder_output = state->encoder_component->output[0];
status = mmal_port_parameter_set(encoder_output, &param.hdr);
if (status != MMAL_SUCCESS)
@ -1302,15 +1313,24 @@ static MMAL_STATUS_T create_encoder_component(RASPIVID_STATE *state)
MMAL_COMPONENT_T *encoder = 0;
MMAL_PORT_T *encoder_input = NULL, *encoder_output = NULL;
MMAL_STATUS_T status;
MMAL_POOL_T *pool;
RASPIVID_CONFIG *config = &state->config;
status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER, &encoder);
gboolean encoded_format =
(config->encoding == MMAL_ENCODING_H264 ||
config->encoding == MMAL_ENCODING_MJPEG ||
config->encoding == MMAL_ENCODING_JPEG);
if (status != MMAL_SUCCESS)
{
vcos_log_error("Unable to create video encoder component");
goto error;
if (!encoded_format)
return MMAL_SUCCESS;
if (config->encoding == MMAL_ENCODING_JPEG)
status = mmal_component_create(MMAL_COMPONENT_DEFAULT_IMAGE_ENCODER, &encoder);
else
status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER, &encoder);
if (status != MMAL_SUCCESS) {
vcos_log_error("Unable to create video encoder component");
goto error;
}
if (!encoder->input_num || !encoder->output_num)
@ -1326,23 +1346,27 @@ static MMAL_STATUS_T create_encoder_component(RASPIVID_STATE *state)
// We want same format on input and output
mmal_format_copy(encoder_output->format, encoder_input->format);
// Only supporting H264 at the moment
encoder_output->format->encoding = MMAL_ENCODING_H264;
// Configure desired encoding
encoder_output->format->encoding = config->encoding;
encoder_output->format->bitrate = config->bitrate;
encoder_output->buffer_size = encoder_output->buffer_size_recommended;
if (config->encoding == MMAL_ENCODING_H264)
encoder_output->buffer_size = encoder_output->buffer_size_recommended;
else
encoder_output->buffer_size = 256<<10;
if (encoder_output->buffer_size < encoder_output->buffer_size_min)
encoder_output->buffer_size = encoder_output->buffer_size_min;
GST_DEBUG ("encoder buffer size is %u", (guint)encoder_output->buffer_size);
encoder_output->buffer_num = encoder_output->buffer_num_recommended;
if (encoder_output->buffer_num < encoder_output->buffer_num_min)
encoder_output->buffer_num = encoder_output->buffer_num_min;
GST_DEBUG ("encoder wants %d buffers of size %u",
(guint)encoder_output->buffer_num, (guint)encoder_output->buffer_size);
// We need to set the frame rate on output to 0, to ensure it gets
// updated correctly from the input framerate when port connected
encoder_output->format->es->video.frame_rate.num = 0;
@ -1350,9 +1374,7 @@ static MMAL_STATUS_T create_encoder_component(RASPIVID_STATE *state)
// Commit the port changes to the output port
status = mmal_port_format_commit(encoder_output);
if (status != MMAL_SUCCESS)
{
if (status != MMAL_SUCCESS) {
vcos_log_error("Unable to set format on video encoder output port");
goto error;
}
@ -1370,7 +1392,7 @@ static MMAL_STATUS_T create_encoder_component(RASPIVID_STATE *state)
}
if (config->intraperiod != -1)
if (config->encoding == MMAL_ENCODING_H264 && config->intraperiod != -1)
{
MMAL_PARAMETER_UINT32_T param = {{ MMAL_PARAMETER_INTRAPERIOD, sizeof(param)}, config->intraperiod};
status = mmal_port_parameter_set(encoder_output, &param.hdr);
@ -1381,7 +1403,7 @@ static MMAL_STATUS_T create_encoder_component(RASPIVID_STATE *state)
}
}
if (config->quantisationParameter)
if (config->encoding == MMAL_ENCODING_H264 && config->quantisationParameter)
{
MMAL_PARAMETER_UINT32_T param = {{ MMAL_PARAMETER_VIDEO_ENCODE_INITIAL_QUANT, sizeof(param)}, config->quantisationParameter};
status = mmal_port_parameter_set(encoder_output, &param.hdr);
@ -1409,6 +1431,7 @@ static MMAL_STATUS_T create_encoder_component(RASPIVID_STATE *state)
}
if (config->encoding == MMAL_ENCODING_H264)
{
MMAL_PARAMETER_VIDEO_PROFILE_T param;
param.hdr.id = MMAL_PARAMETER_PROFILE;
@ -1440,14 +1463,16 @@ static MMAL_STATUS_T create_encoder_component(RASPIVID_STATE *state)
}
//set INLINE VECTORS flag to request motion vector estimates
if (mmal_port_parameter_set_boolean(encoder_output, MMAL_PARAMETER_VIDEO_ENCODE_INLINE_VECTORS, config->inlineMotionVectors) != MMAL_SUCCESS)
if (config->encoding == MMAL_ENCODING_H264 &&
mmal_port_parameter_set_boolean(encoder_output, MMAL_PARAMETER_VIDEO_ENCODE_INLINE_VECTORS, config->inlineMotionVectors) != MMAL_SUCCESS)
{
vcos_log_error("failed to set INLINE VECTORS parameters");
// Continue rather than abort..
}
// Adaptive intra refresh settings
if (config->intra_refresh_type != -1)
if (config->encoding == MMAL_ENCODING_H264 &&
config->intra_refresh_type != -1)
{
MMAL_PARAMETER_VIDEO_INTRA_REFRESH_T param;
@ -1476,6 +1501,23 @@ static MMAL_STATUS_T create_encoder_component(RASPIVID_STATE *state)
}
}
if (config->encoding == MMAL_ENCODING_JPEG)
{
status = mmal_port_parameter_set_uint32(encoder_output, MMAL_PARAMETER_JPEG_Q_FACTOR, config->jpegQuality);
if (status != MMAL_SUCCESS) {
vcos_log_error("Unable to set JPEG quality");
goto error;
}
#ifdef MMAL_PARAMETER_JPEG_RESTART_INTERVAL
status = mmal_port_parameter_set_uint32(encoder_output, MMAL_PARAMETER_JPEG_RESTART_INTERVAL, config->jpegRestartInterval);
if (status != MMAL_SUCCESS) {
vcos_log_error("Unable to set JPEG restart interval");
goto error;
}
#endif
}
// Enable component
status = mmal_component_enable(encoder);
@ -1485,15 +1527,6 @@ static MMAL_STATUS_T create_encoder_component(RASPIVID_STATE *state)
goto error;
}
/* Create pool of buffer headers for the output port to consume */
pool = mmal_port_pool_create(encoder_output, encoder_output->buffer_num, encoder_output->buffer_size);
if (!pool)
{
vcos_log_error("Failed to create buffer header pool for encoder output port %s", encoder_output->name);
}
state->encoder_pool = pool;
state->encoder_component = encoder;
if (config->verbose)
@ -1635,10 +1668,11 @@ raspi_capture_start(RASPIVID_STATE *state)
MMAL_PORT_T *preview_input_port = NULL;
MMAL_PORT_T *encoder_input_port = NULL;
if ((status = create_encoder_component(state)) != MMAL_SUCCESS)
{
vcos_log_error("%s: Failed to create encode component", __func__);
return FALSE;
MMAL_POOL_T *pool;
if ((status = create_encoder_component(state)) != MMAL_SUCCESS) {
vcos_log_error("%s: Failed to create encode component", __func__);
return FALSE;
}
if (config->verbose)
@ -1646,21 +1680,38 @@ raspi_capture_start(RASPIVID_STATE *state)
dump_state(state);
}
state->camera_video_port = state->camera_component->output[MMAL_CAMERA_VIDEO_PORT];
state->camera_still_port = state->camera_component->output[MMAL_CAMERA_CAPTURE_PORT];
camera_preview_port = state->camera_component->output[MMAL_CAMERA_PREVIEW_PORT];
preview_input_port = state->preview_state.preview_component->input[0];
if (state->encoder_component) {
encoder_input_port = state->encoder_component->input[0];
state->encoder_output_port = state->encoder_component->output[0];
} else {
state->encoder_output_port = state->camera_video_port;
}
if ((status = raspi_capture_set_format_and_start(state)) != MMAL_SUCCESS) {
return FALSE;
}
GST_DEBUG ("Creating pool of %d buffers of size %d",
state->encoder_output_port->buffer_num, state->encoder_output_port->buffer_size);
/* Create pool of buffer headers for the output port to consume */
pool = mmal_port_pool_create(state->encoder_output_port,
state->encoder_output_port->buffer_num, state->encoder_output_port->buffer_size);
if (!pool)
{
vcos_log_error("Failed to create buffer header pool for encoder output port %s",
state->encoder_output_port->name);
return FALSE;
}
state->encoder_pool = pool;
if (state->config.verbose)
fprintf(stderr, "Starting component connection stage\n");
camera_preview_port = state->camera_component->output[MMAL_CAMERA_PREVIEW_PORT];
preview_input_port = state->preview_state.preview_component->input[0];
encoder_input_port = state->encoder_component->input[0];
state->camera_video_port = state->camera_component->output[MMAL_CAMERA_VIDEO_PORT];
state->camera_still_port = state->camera_component->output[MMAL_CAMERA_CAPTURE_PORT];
state->encoder_output_port = state->encoder_component->output[0];
if (config->preview_parameters.wantPreview )
{
if (config->verbose)
@ -1678,17 +1729,19 @@ raspi_capture_start(RASPIVID_STATE *state)
}
}
if (config->verbose)
fprintf(stderr, "Connecting camera stills port to encoder input port\n");
if (state->encoder_component) {
if (config->verbose)
fprintf(stderr, "Connecting camera video port to encoder input port\n");
// Now connect the camera to the encoder
status = connect_ports(state->camera_video_port, encoder_input_port, &state->encoder_connection);
if (status != MMAL_SUCCESS)
{
if (config->preview_parameters.wantPreview )
mmal_connection_destroy(state->preview_connection);
vcos_log_error("%s: Failed to connect camera video port to encoder input", __func__);
return FALSE;
// Now connect the camera to the encoder
status = connect_ports(state->camera_video_port, encoder_input_port, &state->encoder_connection);
if (status != MMAL_SUCCESS)
{
if (config->preview_parameters.wantPreview )
mmal_connection_destroy(state->preview_connection);
vcos_log_error("%s: Failed to connect camera video port to encoder input", __func__);
return FALSE;
}
}
// Set up our userdata - this is passed though to the callback where we need the information.
@ -1788,13 +1841,13 @@ raspi_capture_stop(RASPIVID_STATE *state)
if (config->preview_parameters.wantPreview )
mmal_connection_destroy(state->preview_connection);
mmal_connection_destroy(state->encoder_connection);
// Disable all our ports that are not handled by connections
check_disable_port(state->camera_still_port);
check_disable_port(state->encoder_output_port);
if (state->encoder_component) {
mmal_connection_destroy(state->encoder_connection);
mmal_component_disable(state->encoder_component);
destroy_encoder_component(state);
}
@ -1847,7 +1900,7 @@ raspi_capture_update_config (RASPIVID_STATE *state, RASPIVID_CONFIG *config, gbo
if (!dynamic)
return;
if (config->change_flags & PROP_CHANGE_ENCODING) {
if (state->encoder_component && config->change_flags & PROP_CHANGE_ENCODING) {
/* BITRATE or QUANT or KEY Interval, intra refresh */
MMAL_COMPONENT_T *encoder = state->encoder_component;
MMAL_PORT_T *encoder_output = encoder->output[0];

View file

@ -111,6 +111,11 @@ typedef struct
int settings; /// Request settings from the camera
int sensor_mode; /// Sensor mode. 0=auto. Check docs/forum for modes selected by other values.
int intra_refresh_type; /// What intra refresh type to use. -1 to not set.
MMAL_FOURCC_T encoding; // Which encoding to use
int jpegQuality;
int jpegRestartInterval;
} RASPIVID_CONFIG;
typedef struct RASPIVID_STATE_T RASPIVID_STATE;

View file

@ -57,8 +57,6 @@
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <gst/gst.h>
#include <gst/video/video.h>
#include "gstrpicamsrc.h"
@ -135,8 +133,9 @@ enum
PROP_ANNOTATION_TEXT_BG_COLOUR,
PROP_INTRA_REFRESH_TYPE,
#ifdef GST_RPI_CAM_SRC_ENABLE_VIDEO_DIRECTION
PROP_VIDEO_DIRECTION
PROP_VIDEO_DIRECTION,
#endif
PROP_JPEG_QUALITY
};
#define CAMERA_DEFAULT 0
@ -158,6 +157,8 @@ enum
#define EXPOSURE_MODE_DEFAULT GST_RPI_CAM_SRC_EXPOSURE_MODE_AUTO
#define EXPOSURE_METERING_MODE_DEFAULT GST_RPI_CAM_SRC_EXPOSURE_METERING_MODE_AVERAGE
#define DEFAULT_JPEG_QUALITY 50
/*
params->exposureMode = MMAL_PARAM_EXPOSUREMODE_AUTO;
params->exposureMeterMode = MMAL_PARAM_EXPOSUREMETERINGMODE_AVERAGE;
@ -172,8 +173,7 @@ enum
params->roi.w = params->roi.h = 1.0;
*/
#define RAW_AND_JPEG_CAPS \
GST_VIDEO_CAPS_MAKE (GST_VIDEO_FORMATS_ALL) ";" \
#define JPEG_CAPS \
"image/jpeg," \
"width = " GST_VIDEO_SIZE_RANGE "," \
"height = " GST_VIDEO_SIZE_RANGE "," \
@ -186,6 +186,8 @@ enum
"stream-format = (string) byte-stream, " \
"alignment = (string) nal, " \
"profile = (string) { baseline, main, high }"
#define RAW_CAPS \
GST_VIDEO_CAPS_MAKE ("{ I420, RGB, BGR, RGBA }") /* FIXME: Map more raw formats */
#ifdef GST_RPI_CAM_SRC_ENABLE_VIDEO_DIRECTION
#define gst_rpi_cam_src_reset_custom_orientation(src) { src->orientation = GST_VIDEO_ORIENTATION_CUSTOM; }
@ -196,7 +198,7 @@ enum
static GstStaticPadTemplate video_src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ( /*RAW_AND_JPEG_CAPS "; " */ H264_CAPS)
GST_STATIC_CAPS ( H264_CAPS "; " JPEG_CAPS "; " RAW_CAPS)
);
@ -297,6 +299,10 @@ gst_rpi_cam_src_class_init (GstRpiCamSrcClass * klass)
"Bitrate for encoding. 0 for VBR using quantisation-parameter", 0,
BITRATE_HIGHEST, BITRATE_DEFAULT,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_JPEG_QUALITY,
g_param_spec_int ("jpeg-quality", "JPEG Quality",
"Quality setting for JPEG encode", 1, 100, DEFAULT_JPEG_QUALITY,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_KEYFRAME_INTERVAL,
g_param_spec_int ("keyframe-interval", "Keyframe Interface",
"Interval (in frames) between I frames. -1 = automatic, 0 = single-keyframe",
@ -804,6 +810,10 @@ gst_rpi_cam_src_set_property (GObject * object, guint prop_id,
src->capture_config.bitrate = g_value_get_int (value);
src->capture_config.change_flags |= PROP_CHANGE_ENCODING;
break;
case PROP_JPEG_QUALITY:
src->capture_config.jpegQuality = g_value_get_int (value);
src->capture_config.change_flags |= PROP_CHANGE_ENCODING;
break;
case PROP_KEYFRAME_INTERVAL:
src->capture_config.intraperiod = g_value_get_int (value);
src->capture_config.change_flags |= PROP_CHANGE_ENCODING;
@ -1013,6 +1023,9 @@ gst_rpi_cam_src_get_property (GObject * object, guint prop_id,
case PROP_BITRATE:
g_value_set_int (value, src->capture_config.bitrate);
break;
case PROP_JPEG_QUALITY:
g_value_set_int (value, src->capture_config.jpegQuality);
break;
case PROP_KEYFRAME_INTERVAL:
g_value_set_int (value, src->capture_config.intraperiod);
break;
@ -1267,16 +1280,41 @@ gst_rpi_cam_src_set_caps (GstBaseSrc * bsrc, GstCaps * caps)
return FALSE;
structure = gst_caps_get_structure (caps, 0);
profile_str = gst_structure_get_string (structure, "profile");
if (profile_str) {
if (g_str_equal (profile_str, "baseline"))
src->capture_config.profile = MMAL_VIDEO_PROFILE_H264_BASELINE;
else if (g_str_equal (profile_str, "main"))
src->capture_config.profile = MMAL_VIDEO_PROFILE_H264_MAIN;
else if (g_str_equal (profile_str, "high"))
src->capture_config.profile = MMAL_VIDEO_PROFILE_H264_HIGH;
else
g_warning ("Unknown profile string in rpicamsrc caps: %s", profile_str);
if (gst_structure_has_name (structure, "video/x-h264")) {
src->capture_config.encoding = MMAL_ENCODING_H264;
profile_str = gst_structure_get_string (structure, "profile");
if (profile_str) {
if (g_str_equal (profile_str, "baseline"))
src->capture_config.profile = MMAL_VIDEO_PROFILE_H264_BASELINE;
else if (g_str_equal (profile_str, "main"))
src->capture_config.profile = MMAL_VIDEO_PROFILE_H264_MAIN;
else if (g_str_equal (profile_str, "high"))
src->capture_config.profile = MMAL_VIDEO_PROFILE_H264_HIGH;
else
g_warning ("Unknown profile string in rpicamsrc caps: %s", profile_str);
}
}
else if (gst_structure_has_name (structure, "image/jpeg")) {
src->capture_config.encoding = MMAL_ENCODING_MJPEG;
}
else {
/* Raw caps */
switch (GST_VIDEO_INFO_FORMAT(&info)) {
case GST_VIDEO_FORMAT_I420:
src->capture_config.encoding = MMAL_ENCODING_I420;
break;
case GST_VIDEO_FORMAT_RGB:
src->capture_config.encoding = MMAL_ENCODING_RGB24;
break;
case GST_VIDEO_FORMAT_BGR:
src->capture_config.encoding = MMAL_ENCODING_BGR24;
break;
case GST_VIDEO_FORMAT_RGBA:
src->capture_config.encoding = MMAL_ENCODING_RGBA;
break;
default:
return FALSE;
}
}
src->capture_config.width = info.width;