diff options
author | Tim-Philipp Müller <tim@centricular.com> | 2020-07-09 14:46:30 +0300 |
---|---|---|
committer | Tim-Philipp Müller <tim@centricular.com> | 2020-07-10 19:36:14 +0300 |
commit | 84dbf94313936b3c7a9bceddd956517e7f147f6c (patch) | |
tree | 3f7fffe52c8ec48b6729bffe75b1351cff611206 /sys | |
parent | 83beb9211bb9c475998d6e2a6ce8fb339d63f109 (diff) | |
parent | e5593a4c9c35a56749d68d5974b868968ac29e98 (diff) |
Merge branch 'plugin-move-rpicamsrc'
Move rpicamsrc from https://github.com/thaytan/gst-rpicamsrc/
It's a useful little element and works well, so might as well
make sure it's widely available so people can stop piping
raspivid output into fdsrc.
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/667>
Diffstat (limited to 'sys')
-rw-r--r-- | sys/rpicamsrc/RaspiCLI.c | 155 | ||||
-rw-r--r-- | sys/rpicamsrc/RaspiCLI.h | 56 | ||||
-rw-r--r-- | sys/rpicamsrc/RaspiCamControl.c | 1467 | ||||
-rw-r--r-- | sys/rpicamsrc/RaspiCamControl.h | 220 | ||||
-rw-r--r-- | sys/rpicamsrc/RaspiCapture.c | 2026 | ||||
-rw-r--r-- | sys/rpicamsrc/RaspiCapture.h | 143 | ||||
-rw-r--r-- | sys/rpicamsrc/RaspiPreview.c | 307 | ||||
-rw-r--r-- | sys/rpicamsrc/RaspiPreview.h | 75 | ||||
-rw-r--r-- | sys/rpicamsrc/RaspiStill.c | 1516 | ||||
-rw-r--r-- | sys/rpicamsrc/RaspiStillYUV.c | 957 | ||||
-rw-r--r-- | sys/rpicamsrc/gstrpicam-enums-template.c | 40 | ||||
-rw-r--r-- | sys/rpicamsrc/gstrpicam-enums-template.h | 24 | ||||
-rw-r--r-- | sys/rpicamsrc/gstrpicam_types.h | 101 | ||||
-rw-r--r-- | sys/rpicamsrc/gstrpicamsrc.c | 1477 | ||||
-rw-r--r-- | sys/rpicamsrc/gstrpicamsrc.h | 113 | ||||
-rw-r--r-- | sys/rpicamsrc/gstrpicamsrcdeviceprovider.c | 149 | ||||
-rw-r--r-- | sys/rpicamsrc/gstrpicamsrcdeviceprovider.h | 79 | ||||
-rw-r--r-- | sys/rpicamsrc/meson.build | 24 |
18 files changed, 8929 insertions, 0 deletions
diff --git a/sys/rpicamsrc/RaspiCLI.c b/sys/rpicamsrc/RaspiCLI.c new file mode 100644 index 000000000..2bbb3ce09 --- /dev/null +++ b/sys/rpicamsrc/RaspiCLI.c @@ -0,0 +1,155 @@ +/* +Copyright (c) 2013, Broadcom Europe Ltd +Copyright (c) 2013, James Hughes +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * \file RaspiCLI.c + * Code for handling command line parameters + * + * \date 4th March 2013 + * \Author: James Hughes + * + * Description + * + * Some functions/structures for command line parameter parsing + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <memory.h> + +#include "interface/vcos/vcos.h" + +#include "RaspiCLI.h" + + +/** + * Convert a string from command line to a comand_id from the list + * + * @param commands Array of command to check + * @param num_command Number of commands in the array + * @param arg String to search for in the list + * @param num_parameters Returns the number of parameters used by the command + * + * @return command ID if found, -1 if not found + * + */ +int raspicli_get_command_id(const COMMAND_LIST *commands, const int num_commands, const char *arg, int *num_parameters) +{ + int command_id = -1; + int j; + + vcos_assert(commands); + vcos_assert(num_parameters); + vcos_assert(arg); + + if (!commands || !num_parameters || !arg) + return -1; + + for (j = 0; j < num_commands; j++) + { + if (!strcmp(arg, commands[j].command) || + !strcmp(arg, commands[j].abbrev)) + { + // match + command_id = commands[j].id; + *num_parameters = commands[j].num_parameters; + break; + } + } + + return command_id; +} + + +/** + * Display the list of commands in help format + * + * @param commands Array of command to check + * @param num_command Number of commands in the arry + * + * + */ +void raspicli_display_help(const COMMAND_LIST *commands, const int num_commands) +{ + int i; + + vcos_assert(commands); + + if (!commands) + return; + + for (i = 0; i < num_commands; i++) + { + fprintf(stderr, "-%s, -%s\t: %s\n", commands[i].abbrev, + commands[i].command, commands[i].help); + } +} + + +/** + * Function to take a string, a mapping, and return the int equivalent + * @param str Incoming string to match + * @param map Mapping data + * @param num_refs The number of items in the mapping data + * @return The integer match for the string, or -1 if no match + */ +int raspicli_map_xref(const char *str, const XREF_T *map, int num_refs) +{ + int i; + + for (i=0;i<num_refs;i++) + { + if (!strcasecmp(str, map[i].mode)) + { + return map[i].mmal_mode; + } + } + return -1; +} + +/** + * Function to take a mmal enum (as int) and return the string equivalent + * @param en Incoming int to match + * @param map Mapping data + * @param num_refs The number of items in the mapping data + * @return const pointer to string, or NULL if no match + */ +const char *raspicli_unmap_xref(const int en, const XREF_T *map, int num_refs) +{ + int i; + + for (i=0;i<num_refs;i++) + { + if (en == map[i].mmal_mode) + { + return map[i].mode; + } + } + return NULL; +} diff --git a/sys/rpicamsrc/RaspiCLI.h b/sys/rpicamsrc/RaspiCLI.h new file mode 100644 index 000000000..38f252498 --- /dev/null +++ b/sys/rpicamsrc/RaspiCLI.h @@ -0,0 +1,56 @@ +/* +Copyright (c) 2013, Broadcom Europe Ltd +Copyright (c) 2013, James Hughes +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef RASPICLI_H_ +#define RASPICLI_H_ + +typedef struct +{ + int id; + char *command; + char *abbrev; + char *help; + int num_parameters; +} COMMAND_LIST; + +/// Cross reference structure, mode string against mode id +typedef struct xref_t +{ + char *mode; + int mmal_mode; +} XREF_T; + + +void raspicli_display_help(const COMMAND_LIST *commands, const int num_commands); +int raspicli_get_command_id(const COMMAND_LIST *commands, const int num_commands, const char *arg, int *num_parameters); + +int raspicli_map_xref(const char *str, const XREF_T *map, int num_refs); +const char *raspicli_unmap_xref(const int en, const XREF_T *map, int num_refs); + + +#endif diff --git a/sys/rpicamsrc/RaspiCamControl.c b/sys/rpicamsrc/RaspiCamControl.c new file mode 100644 index 000000000..1b3d05964 --- /dev/null +++ b/sys/rpicamsrc/RaspiCamControl.c @@ -0,0 +1,1467 @@ +/* + * Copyright (c) 2013 Jan Schmidt <jan@centricular.com> +Portions: +Copyright (c) 2013, Broadcom Europe Ltd +Copyright (c) 2013, James Hughes +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <stdio.h> +#include <memory.h> + +#include <gst/gst.h> + +#include "interface/vcos/vcos.h" + +#include "interface/vmcs_host/vc_vchi_gencmd.h" +#include "interface/mmal/mmal.h" +#include "interface/mmal/mmal_logging.h" +#include "interface/mmal/util/mmal_util.h" +#include "interface/mmal/util/mmal_util_params.h" +#include "interface/mmal/util/mmal_default_components.h" +#include "RaspiCamControl.h" +#include "RaspiCapture.h" + +#if 0 +/// Structure to cross reference exposure strings against the MMAL parameter equivalent +static XREF_T exposure_map[] = +{ + {"auto", MMAL_PARAM_EXPOSUREMODE_AUTO}, + {"night", MMAL_PARAM_EXPOSUREMODE_NIGHT}, + {"nightpreview", MMAL_PARAM_EXPOSUREMODE_NIGHTPREVIEW}, + {"backlight", MMAL_PARAM_EXPOSUREMODE_BACKLIGHT}, + {"spotlight", MMAL_PARAM_EXPOSUREMODE_SPOTLIGHT}, + {"sports", MMAL_PARAM_EXPOSUREMODE_SPORTS}, + {"snow", MMAL_PARAM_EXPOSUREMODE_SNOW}, + {"beach", MMAL_PARAM_EXPOSUREMODE_BEACH}, + {"verylong", MMAL_PARAM_EXPOSUREMODE_VERYLONG}, + {"fixedfps", MMAL_PARAM_EXPOSUREMODE_FIXEDFPS}, + {"antishake", MMAL_PARAM_EXPOSUREMODE_ANTISHAKE}, + {"fireworks", MMAL_PARAM_EXPOSUREMODE_FIREWORKS} +}; + +static const int exposure_map_size = sizeof(exposure_map) / sizeof(exposure_map[0]); + +/// Structure to cross reference awb strings against the MMAL parameter equivalent +static XREF_T awb_map[] = +{ + {"off", MMAL_PARAM_AWBMODE_OFF}, + {"auto", MMAL_PARAM_AWBMODE_AUTO}, + {"sun", MMAL_PARAM_AWBMODE_SUNLIGHT}, + {"cloud", MMAL_PARAM_AWBMODE_CLOUDY}, + {"shade", MMAL_PARAM_AWBMODE_SHADE}, + {"tungsten", MMAL_PARAM_AWBMODE_TUNGSTEN}, + {"fluorescent", MMAL_PARAM_AWBMODE_FLUORESCENT}, + {"incandescent", MMAL_PARAM_AWBMODE_INCANDESCENT}, + {"flash", MMAL_PARAM_AWBMODE_FLASH}, + {"horizon", MMAL_PARAM_AWBMODE_HORIZON} +}; + +static const int awb_map_size = sizeof(awb_map) / sizeof(awb_map[0]); + +/// Structure to cross reference image effect against the MMAL parameter equivalent +static XREF_T imagefx_map[] = +{ + {"none", MMAL_PARAM_IMAGEFX_NONE}, + {"negative", MMAL_PARAM_IMAGEFX_NEGATIVE}, + {"solarise", MMAL_PARAM_IMAGEFX_SOLARIZE}, + {"sketch", MMAL_PARAM_IMAGEFX_SKETCH}, + {"denoise", MMAL_PARAM_IMAGEFX_DENOISE}, + {"emboss", MMAL_PARAM_IMAGEFX_EMBOSS}, + {"oilpaint", MMAL_PARAM_IMAGEFX_OILPAINT}, + {"hatch", MMAL_PARAM_IMAGEFX_HATCH}, + {"gpen", MMAL_PARAM_IMAGEFX_GPEN}, + {"pastel", MMAL_PARAM_IMAGEFX_PASTEL}, + {"watercolour", MMAL_PARAM_IMAGEFX_WATERCOLOUR}, + {"film", MMAL_PARAM_IMAGEFX_FILM}, + {"blur", MMAL_PARAM_IMAGEFX_BLUR}, + {"saturation", MMAL_PARAM_IMAGEFX_SATURATION}, + {"colourswap", MMAL_PARAM_IMAGEFX_COLOURSWAP}, + {"washedout", MMAL_PARAM_IMAGEFX_WASHEDOUT}, + {"posterise", MMAL_PARAM_IMAGEFX_POSTERISE}, + {"colourpoint", MMAL_PARAM_IMAGEFX_COLOURPOINT}, + {"colourbalance", MMAL_PARAM_IMAGEFX_COLOURBALANCE}, + {"cartoon", MMAL_PARAM_IMAGEFX_CARTOON} + }; + +static const int imagefx_map_size = sizeof(imagefx_map) / sizeof(imagefx_map[0]); + +static XREF_T metering_mode_map[] = +{ + {"average", MMAL_PARAM_EXPOSUREMETERINGMODE_AVERAGE}, + {"spot", MMAL_PARAM_EXPOSUREMETERINGMODE_SPOT}, + {"backlit", MMAL_PARAM_EXPOSUREMETERINGMODE_BACKLIT}, + {"matrix", MMAL_PARAM_EXPOSUREMETERINGMODE_MATRIX} +}; + +static const int metering_mode_map_size = sizeof(metering_mode_map)/sizeof(metering_mode_map[0]); + +static XREF_T drc_mode_map[] = +{ + {"off", MMAL_PARAMETER_DRC_STRENGTH_OFF}, + {"low", MMAL_PARAMETER_DRC_STRENGTH_LOW}, + {"med", MMAL_PARAMETER_DRC_STRENGTH_MEDIUM}, + {"high", MMAL_PARAMETER_DRC_STRENGTH_HIGH} +}; + +static const int drc_mode_map_size = sizeof(drc_mode_map)/sizeof(drc_mode_map[0]); + +static XREF_T stereo_mode_map[] = +{ + {"off", MMAL_STEREOSCOPIC_MODE_NONE}, + {"sbs", MMAL_STEREOSCOPIC_MODE_SIDE_BY_SIDE}, + {"tb", MMAL_STEREOSCOPIC_MODE_TOP_BOTTOM}, +}; + +static const int stereo_mode_map_size = sizeof(stereo_mode_map)/sizeof(stereo_mode_map[0]); + + +#define CommandSharpness 0 +#define CommandContrast 1 +#define CommandBrightness 2 +#define CommandSaturation 3 +#define CommandISO 4 +#define CommandVideoStab 5 +#define CommandEVComp 6 +#define CommandExposure 7 +#define CommandAWB 8 +#define CommandImageFX 9 +#define CommandColourFX 10 +#define CommandMeterMode 11 +#define CommandRotation 12 +#define CommandHFlip 13 +#define CommandVFlip 14 +#define CommandROI 15 +#define CommandShutterSpeed 16 +#define CommandAwbGains 17 +#define CommandDRCLevel 18 +#define CommandStatsPass 19 +#define CommandAnnotate 20 +#define CommandStereoMode 21 +#define CommandStereoDecimate 22 +#define CommandStereoSwap 23 +#define CommandAnnotateExtras 24 + +static COMMAND_LIST cmdline_commands[] = +{ + {CommandSharpness, "-sharpness", "sh", "Set image sharpness (-100 to 100)", 1}, + {CommandContrast, "-contrast", "co", "Set image contrast (-100 to 100)", 1}, + {CommandBrightness, "-brightness","br", "Set image brightness (0 to 100)", 1}, + {CommandSaturation, "-saturation","sa", "Set image saturation (-100 to 100)", 1}, + {CommandISO, "-ISO", "ISO","Set capture ISO", 1}, + {CommandVideoStab, "-vstab", "vs", "Turn on video stabilisation", 0}, + {CommandEVComp, "-ev", "ev", "Set EV compensation", 1}, + {CommandExposure, "-exposure", "ex", "Set exposure mode (see Notes)", 1}, + {CommandAWB, "-awb", "awb","Set AWB mode (see Notes)", 1}, + {CommandImageFX, "-imxfx", "ifx","Set image effect (see Notes)", 1}, + {CommandColourFX, "-colfx", "cfx","Set colour effect (U:V)", 1}, + {CommandMeterMode, "-metering", "mm", "Set metering mode (see Notes)", 1}, + {CommandRotation, "-rotation", "rot","Set image rotation (0-359)", 1}, + {CommandHFlip, "-hflip", "hf", "Set horizontal flip", 0}, + {CommandVFlip, "-vflip", "vf", "Set vertical flip", 0}, + {CommandROI, "-roi", "roi","Set region of interest (x,y,w,d as normalised coordinates [0.0-1.0])", 1}, + {CommandShutterSpeed,"-shutter", "ss", "Set shutter speed in microseconds", 1}, + {CommandAwbGains, "-awbgains", "awbg", "Set AWB gains - AWB mode must be off", 1}, + {CommandDRCLevel, "-drc", "drc", "Set DRC Level", 1}, + {CommandStatsPass, "-stats", "st", "Force recomputation of statistics on stills capture pass"}, + {CommandAnnotate, "-annotate", "a", "Enable/Set annotate flags or text", 1}, + {CommandStereoMode, "-stereo", "3d", "Select stereoscopic mode", 1}, + {CommandStereoDecimate,"-decimate","dec", "Half width/height of stereo image"}, + {CommandStereoSwap, "-3dswap", "3dswap", "Swap camera order for stereoscopic"}, + {CommandAnnotateExtras,"-annotateex","ae", "Set extra annotation parameters (text size, text colour(hex YUV), bg colour(hex YUV))", 2}, +}; + +static int cmdline_commands_size = sizeof(cmdline_commands) / sizeof(cmdline_commands[0]); + +static const int exposure_map_size = 1; +static const int awb_map_size = 1; +static const int metering_mode_map_size = 1; + +#define parameter_reset -99999 + +/** + * Update the passed in parameter according to the rest of the parameters + * passed in. + * + * + * @return 0 if reached end of cycle for this parameter, !0 otherwise + */ +static int update_cycle_parameter(int *option, int min, int max, int increment) +{ + vcos_assert(option); + if (!option) + return 0; + + if (*option == parameter_reset) + *option = min - increment; + + *option += increment; + + if (*option > max) + { + *option = parameter_reset; + return 0; + } + else + return 1; +} +#endif + + +/** + * Test/Demo code to cycle through a bunch of camera settings + * This code is pretty hacky so please don't complain!! + * It only does stuff that should have a visual impact (hence demo!) + * This will override any user supplied parameters + * + * Each call of this function will move on to the next setting + * + * @param camera Pointer to the camera to change settings on. + * @return 0 if reached end of complete sequence, !0 otherwise + */ + +int raspicamcontrol_cycle_test(MMAL_COMPONENT_T *camera) +{ + return 1; +} +#if 0 + static int parameter = 0; + static int parameter_option = parameter_reset; // which value the parameter currently has + + vcos_assert(camera); + + // We are going to cycle through all the relevant entries in the parameter block + // and send options to the camera. + if (parameter == 0) + { + // sharpness + if (update_cycle_parameter(¶meter_option, -100, 100, 10)) + raspicamcontrol_set_sharpness(camera, parameter_option); + else + { + raspicamcontrol_set_sharpness(camera, 0); + parameter++; + } + } + else + if (parameter == 1) + { + // contrast + if (update_cycle_parameter(¶meter_option, -100, 100, 10)) + raspicamcontrol_set_contrast(camera, parameter_option); + else + { + raspicamcontrol_set_contrast(camera, 0); + parameter++; + } + } + else + if (parameter == 2) + { + // brightness + if (update_cycle_parameter(¶meter_option, 0, 100, 10)) + raspicamcontrol_set_brightness(camera, parameter_option); + else + { + raspicamcontrol_set_brightness(camera, 50); + parameter++; + } + } + else + if (parameter == 3) + { + // contrast + if (update_cycle_parameter(¶meter_option, -100, 100, 10)) + raspicamcontrol_set_saturation(camera, parameter_option); + else + { + parameter++; + raspicamcontrol_set_saturation(camera, 0); + } + } + else + if (parameter == 4) + { + // EV + if (update_cycle_parameter(¶meter_option, -10, 10, 4)) + raspicamcontrol_set_exposure_compensation(camera, parameter_option); + else + { + raspicamcontrol_set_exposure_compensation(camera, 0); + parameter++; + } + } + else + if (parameter == 5) + { + // MMAL_PARAM_EXPOSUREMODE_T + if (update_cycle_parameter(¶meter_option, 0, exposure_map_size, 1)) + raspicamcontrol_set_exposure_mode(camera, exposure_map[parameter_option].mmal_mode); + else + { + raspicamcontrol_set_exposure_mode(camera, MMAL_PARAM_EXPOSUREMODE_AUTO); + parameter++; + } + } + else + if (parameter == 6) + { + // MMAL_PARAM_AWB_T + if (update_cycle_parameter(¶meter_option, 0, awb_map_size, 1)) + raspicamcontrol_set_awb_mode(camera, awb_map[parameter_option].mmal_mode); + else + { + raspicamcontrol_set_awb_mode(camera, MMAL_PARAM_AWBMODE_AUTO); + parameter++; + } + } + if (parameter == 7) + { + // MMAL_PARAM_IMAGEFX_T + if (update_cycle_parameter(¶meter_option, 0, imagefx_map_size, 1)) + raspicamcontrol_set_imageFX(camera, imagefx_map[parameter_option].mmal_mode); + else + { + raspicamcontrol_set_imageFX(camera, MMAL_PARAM_IMAGEFX_NONE); + parameter++; + } + } + if (parameter == 8) + { + MMAL_PARAM_COLOURFX_T colfx = {0,0,0}; + switch (parameter_option) + { + case parameter_reset : + parameter_option = 1; + colfx.u = 128; + colfx.v = 128; + break; + case 1 : + parameter_option = 2; + colfx.u = 100; + colfx.v = 200; + break; + case 2 : + parameter_option = parameter_reset; + colfx.enable = 0; + parameter++; + break; + } + raspicamcontrol_set_colourFX(camera, &colfx); + } + + // Orientation + if (parameter == 9) + { + switch (parameter_option) + { + case parameter_reset: + raspicamcontrol_set_rotation(camera, 90); + parameter_option = 1; + break; + + case 1 : + raspicamcontrol_set_rotation(camera, 180); + parameter_option = 2; + break; + + case 2 : + raspicamcontrol_set_rotation(camera, 270); + parameter_option = 3; + break; + + case 3 : + { + raspicamcontrol_set_rotation(camera, 0); + raspicamcontrol_set_flips(camera, 1,0); + parameter_option = 4; + break; + } + case 4 : + { + raspicamcontrol_set_flips(camera, 0,1); + parameter_option = 5; + break; + } + case 5 : + { + raspicamcontrol_set_flips(camera, 1, 1); + parameter_option = 6; + break; + } + case 6 : + { + raspicamcontrol_set_flips(camera, 0, 0); + parameter_option = parameter_reset; + parameter++; + break; + } + } + } + + if (parameter == 10) + { + parameter = 1; + return 0; + } + + return 1; +} +#endif + + +#if 0 +/** + * Parse a possible command pair - command and parameter + * @param arg1 Command + * @param arg2 Parameter (could be NULL) + * @return How many parameters were used, 0,1,2 + */ +int raspicamcontrol_parse_cmdline(RASPICAM_CAMERA_PARAMETERS *params, const char *arg1, const char *arg2) +{ + int command_id, used = 0, num_parameters; + + if (!arg1) + return 0; + + command_id = raspicli_get_command_id(cmdline_commands, cmdline_commands_size, arg1, &num_parameters); + + // If invalid command, or we are missing a parameter, drop out + if (command_id==-1 || (command_id != -1 && num_parameters > 0 && arg2 == NULL)) + return 0; + + switch (command_id) + { + case CommandSharpness : // sharpness - needs single number parameter + sscanf(arg2, "%d", ¶ms->sharpness); + used = 2; + break; + + case CommandContrast : // contrast - needs single number parameter + sscanf(arg2, "%d", ¶ms->contrast); + used = 2; + break; + + case CommandBrightness : // brightness - needs single number parameter + sscanf(arg2, "%d", ¶ms->brightness); + used = 2; + break; + + case CommandSaturation : // saturation - needs single number parameter + sscanf(arg2, "%d", ¶ms->saturation); + used = 2; + break; + + case CommandISO : // ISO - needs single number parameter + sscanf(arg2, "%d", ¶ms->ISO); + used = 2; + break; + + case CommandVideoStab : // video stabilisation - if here, its on + params->videoStabilisation = 1; + used = 1; + break; + + case CommandEVComp : // EV - needs single number parameter + sscanf(arg2, "%d", ¶ms->exposureCompensation); + used = 2; + break; + + case CommandExposure : // exposure mode - needs string + params->exposureMode = exposure_mode_from_string(arg2); + used = 2; + break; + + case CommandAWB : // AWB mode - needs single number parameter + params->awbMode = awb_mode_from_string(arg2); + used = 2; + break; + + case CommandImageFX : // Image FX - needs string + params->imageEffect = imagefx_mode_from_string(arg2); + used = 2; + break; + + case CommandColourFX : // Colour FX - needs string "u:v" + sscanf(arg2, "%d:%d", ¶ms->colourEffects.u, ¶ms->colourEffects.u); + params->colourEffects.enable = 1; + used = 2; + break; + + case CommandMeterMode: + params->exposureMeterMode = metering_mode_from_string(arg2); + used = 2; + break; + + case CommandRotation : // Rotation - degree + sscanf(arg2, "%d", ¶ms->rotation); + used = 2; + break; + + case CommandHFlip : + params->hflip = 1; + used = 1; + break; + + case CommandVFlip : + params->vflip = 1; + used = 1; + break; + + case CommandROI : + { + double x,y,w,h; + int args; + + args = sscanf(arg2, "%lf,%lf,%lf,%lf", &x,&y,&w,&h); + + if (args != 4 || x > 1.0 || y > 1.0 || w > 1.0 || h > 1.0) + { + return 0; + } + + // Make sure we stay within bounds + if (x + w > 1.0) + w = 1 - x; + + if (y + h > 1.0) + h = 1 - y; + + params->roi.x = x; + params->roi.y = y; + params->roi.w = w; + params->roi.h = h; + + used = 2; + break; + } + + case CommandShutterSpeed : // Shutter speed needs single number parameter + { + sscanf(arg2, "%d", ¶ms->shutter_speed); + used = 2; + break; + } + + case CommandAwbGains : + { + double r,b; + int args; + + args = sscanf(arg2, "%lf,%lf", &r,&b); + + if (args != 2 || r > 8.0 || b > 8.0) + { + return 0; + } + + params->awb_gains_r = r; + params->awb_gains_b = b; + + used = 2; + break; + } + + case CommandDRCLevel: + { + params->drc_level = drc_mode_from_string(arg2); + used = 2; + break; + } + + case CommandStatsPass: + { + params->stats_pass = MMAL_TRUE; + used = 1; + break; + } + + case CommandAnnotate: + { + // If parameter is a number, assume its a bitmask, otherwise a string + if (isdigit(*arg2)) + { + sscanf(arg2, "%u", ¶ms->enable_annotate); + } + else + { + params->enable_annotate = ANNOTATE_USER_TEXT; + //copy string char by char and replace "\n" with newline character + unsigned char c; + char const *s = arg2; + char *t = ¶ms->annotate_string[0]; + int n=0; + while ((c = *s++) && n < MMAL_CAMERA_ANNOTATE_MAX_TEXT_LEN_V3-1) + { + if (c == '\\' && *s) + { + switch (c = *s++) + { + case 'n': + c = '\n'; + break; + + default: + c = '\\'; + s--; + break; + } + } + *(t++) = c; + n++; + } + *t='\0'; + + //params->annotate_string[MMAL_CAMERA_ANNOTATE_MAX_TEXT_LEN_V3-1] = '\0'; + } + used=2; + break; + } + + case CommandAnnotateExtras: + { + // 3 parameters - text size (6-80), text colour (Hex VVUUYY) and background colour (Hex VVUUYY) + sscanf(arg2, "%u,%X,%X", ¶ms->annotate_text_size, + ¶ms->annotate_text_colour, + ¶ms->annotate_bg_colour); + used=2; + break; + } + + case CommandStereoMode: + { + params->stereo_mode.mode = stereo_mode_from_string(arg2); + used = 2; + break; + } + + case CommandStereoDecimate: + { + params->stereo_mode.decimate = MMAL_TRUE; + used = 1; + break; + } + + case CommandStereoSwap: + { + params->stereo_mode.swap_eyes = MMAL_TRUE; + used = 1; + break; + } + + } + + return used; +} + +/** + * Display help for command line options + */ +void raspicamcontrol_display_help() +{ + int i; + + fprintf(stderr, "\nImage parameter commands\n\n"); + + raspicli_display_help(cmdline_commands, cmdline_commands_size); + + fprintf(stderr, "\n\nNotes\n\nExposure mode options :\n%s", exposure_map[0].mode ); + + for (i=1;i<exposure_map_size;i++) + { + fprintf(stderr, ",%s", exposure_map[i].mode); + } + + fprintf(stderr, "\n\nAWB mode options :\n%s", awb_map[0].mode ); + + for (i=1;i<awb_map_size;i++) + { + fprintf(stderr, ",%s", awb_map[i].mode); + } + + fprintf(stderr, "\n\nImage Effect mode options :\n%s", imagefx_map[0].mode ); + + for (i=1;i<imagefx_map_size;i++) + { + fprintf(stderr, ",%s", imagefx_map[i].mode); + } + + fprintf(stderr, "\n\nMetering Mode options :\n%s", metering_mode_map[0].mode ); + + for (i=1;i<metering_mode_map_size;i++) + { + fprintf(stderr, ",%s", metering_mode_map[i].mode); + } + + fprintf(stderr, "\n\nDynamic Range Compression (DRC) options :\n%s", drc_mode_map[0].mode ); + + for (i=1;i<drc_mode_map_size;i++) + { + fprintf(stderr, ",%s", drc_mode_map[i].mode); + } + + fprintf(stderr, "\n"); +} + +#endif +/** + * Dump contents of camera parameter structure to stdout for debugging/verbose logging + * + * @param params Const pointer to parameters structure to dump + */ +void raspicamcontrol_dump_parameters(const RASPICAM_CAMERA_PARAMETERS *params) +{ + //const char *exp_mode = raspicli_unmap_xref(params->exposureMode, exposure_map, exposure_map_size); + //const char *awb_mode = raspicli_unmap_xref(params->awbMode, awb_map, awb_map_size); + //const char *image_effect = raspicli_unmap_xref(params->imageEffect, imagefx_map, imagefx_map_size); + //const char *metering_mode = raspicli_unmap_xref(params->exposureMeterMode, metering_mode_map, metering_mode_map_size); + + fprintf(stderr, "Sharpness %d, Contrast %d, Brightness %d\n", params->sharpness, params->contrast, params->brightness); + fprintf(stderr, "Saturation %d, ISO %d, Video Stabilisation %s, Exposure compensation %d\n", params->saturation, params->ISO, params->videoStabilisation ? "Yes": "No", params->exposureCompensation); + //fprintf(stderr, "Exposure Mode '%s', AWB Mode '%s', Image Effect '%s'\n", exp_mode, awb_mode, image_effect); + fprintf(stderr, "Exposure Mode '%d', AWB Mode '%d', Image Effect '%d'\n", params->exposureMode, params->awbMode, params->imageEffect); + //fprintf(stderr, "Metering Mode '%s', Colour Effect Enabled %s with U = %d, V = %d\n", metering_mode, params->colourEffects.enable ? "Yes":"No", params->colourEffects.u, params->colourEffects.v); + fprintf(stderr, "Rotation %d, hflip %s, vflip %s\n", params->rotation, params->hflip ? "Yes":"No",params->vflip ? "Yes":"No"); + fprintf(stderr, "ROI x %lf, y %f, w %f h %f\n", params->roi.x, params->roi.y, params->roi.w, params->roi.h); +} + +/** + * Convert a MMAL status return value to a simple boolean of success + * ALso displays a fault if code is not success + * + * @param status The error code to convert + * @return 0 if status is sucess, 1 otherwise + */ +int mmal_status_to_int(MMAL_STATUS_T status) +{ + if (status == MMAL_SUCCESS) + return 0; + else + { + switch (status) + { + case MMAL_ENOMEM : vcos_log_error("Out of memory"); break; + case MMAL_ENOSPC : vcos_log_error("Out of resources (other than memory)"); break; + case MMAL_EINVAL: vcos_log_error("Argument is invalid"); break; + case MMAL_ENOSYS : vcos_log_error("Function not implemented"); break; + case MMAL_ENOENT : vcos_log_error("No such file or directory"); break; + case MMAL_ENXIO : vcos_log_error("No such device or address"); break; + case MMAL_EIO : vcos_log_error("I/O error"); break; + case MMAL_ESPIPE : vcos_log_error("Illegal seek"); break; + case MMAL_ECORRUPT : vcos_log_error("Data is corrupt \attention FIXME: not POSIX"); break; + case MMAL_ENOTREADY :vcos_log_error("Component is not ready \attention FIXME: not POSIX"); break; + case MMAL_ECONFIG : vcos_log_error("Component is not configured \attention FIXME: not POSIX"); break; + case MMAL_EISCONN : vcos_log_error("Port is already connected "); break; + case MMAL_ENOTCONN : vcos_log_error("Port is disconnected"); break; + case MMAL_EAGAIN : vcos_log_error("Resource temporarily unavailable. Try again later"); break; + case MMAL_EFAULT : vcos_log_error("Bad address"); break; + default : vcos_log_error("Unknown status error"); break; + } + + return 1; + } +} + +/** + * Give the supplied parameter block a set of default values + * @params Pointer to parameter block + */ +void raspicamcontrol_set_defaults(RASPICAM_CAMERA_PARAMETERS *params) +{ + vcos_assert(params); + + params->sharpness = 0; + params->contrast = 0; + params->brightness = 50; + params->saturation = 0; + params->ISO = 0; // 0 = auto + params->videoStabilisation = 0; + params->exposureCompensation = 0; + params->exposureMode = MMAL_PARAM_EXPOSUREMODE_AUTO; + params->exposureMeterMode = MMAL_PARAM_EXPOSUREMETERINGMODE_AVERAGE; + params->awbMode = MMAL_PARAM_AWBMODE_AUTO; + params->imageEffect = MMAL_PARAM_IMAGEFX_NONE; + params->colourEffects.enable = 0; + params->colourEffects.u = 128; + params->colourEffects.v = 128; + params->rotation = 0; + params->hflip = params->vflip = 0; + params->roi.x = params->roi.y = 0.0; + params->roi.w = params->roi.h = 1.0; + params->shutter_speed = 0; // 0 = auto + params->awb_gains_r = 0; // Only have any function if AWB OFF is used. + params->awb_gains_b = 0; + params->drc_level = MMAL_PARAMETER_DRC_STRENGTH_OFF; + params->stats_pass = MMAL_FALSE; + params->enable_annotate = 0; + params->annotate_string[0] = '\0'; + params->annotate_text_size = 0; //Use firmware default + params->annotate_text_colour = -1; //Use firmware default + params->annotate_bg_colour = -1; //Use firmware default + params->stereo_mode.mode = MMAL_STEREOSCOPIC_MODE_NONE; + params->stereo_mode.decimate = MMAL_FALSE; + params->stereo_mode.swap_eyes = MMAL_FALSE; +} + +/** + * Get all the current camera parameters from specified camera component + * @param camera Pointer to camera component + * @param params Pointer to parameter block to accept settings + * @return 0 if successful, non-zero if unsuccessful + */ +int raspicamcontrol_get_all_parameters(MMAL_COMPONENT_T *camera, RASPICAM_CAMERA_PARAMETERS *params) +{ + vcos_assert(camera); + vcos_assert(params); + + if (!camera || !params) + return 1; + +/* TODO : Write these get functions + params->sharpness = raspicamcontrol_get_sharpness(camera); + params->contrast = raspicamcontrol_get_contrast(camera); + params->brightness = raspicamcontrol_get_brightness(camera); + params->saturation = raspicamcontrol_get_saturation(camera); + params->ISO = raspicamcontrol_get_ISO(camera); + params->videoStabilisation = raspicamcontrol_get_video_stabilisation(camera); + params->exposureCompensation = raspicamcontrol_get_exposure_compensation(camera); + params->exposureMode = raspicamcontrol_get_exposure_mode(camera); + params->awbMode = raspicamcontrol_get_awb_mode(camera); + params->imageEffect = raspicamcontrol_get_image_effect(camera); + params->colourEffects = raspicamcontrol_get_colour_effect(camera); + params->thumbnailConfig = raspicamcontrol_get_thumbnail_config(camera); +*/ + return 0; +} + +/** + * Set the specified camera to all the specified settings + * @param camera Pointer to camera component + * @param params Pointer to parameter block containing parameters + * @return 0 if successful, none-zero if unsuccessful. + */ +int raspicamcontrol_set_all_parameters(MMAL_COMPONENT_T *camera, const RASPICAM_CAMERA_PARAMETERS *params) +{ + int result; + + result = raspicamcontrol_set_saturation(camera, params->saturation); + result += raspicamcontrol_set_sharpness(camera, params->sharpness); + result += raspicamcontrol_set_contrast(camera, params->contrast); + result += raspicamcontrol_set_brightness(camera, params->brightness); + result += raspicamcontrol_set_ISO(camera, params->ISO); + result += raspicamcontrol_set_video_stabilisation(camera, params->videoStabilisation); + result += raspicamcontrol_set_exposure_compensation(camera, params->exposureCompensation); + result += raspicamcontrol_set_exposure_mode(camera, params->exposureMode); + result += raspicamcontrol_set_metering_mode(camera, params->exposureMeterMode); + result += raspicamcontrol_set_awb_mode(camera, params->awbMode); + result += raspicamcontrol_set_awb_gains(camera, params->awb_gains_r, params->awb_gains_b); + result += raspicamcontrol_set_imageFX(camera, params->imageEffect); + result += raspicamcontrol_set_colourFX(camera, ¶ms->colourEffects); + //result += raspicamcontrol_set_thumbnail_parameters(camera, ¶ms->thumbnailConfig); TODO Not working for some reason + result += raspicamcontrol_set_rotation(camera, params->rotation); + result += raspicamcontrol_set_flips(camera, params->hflip, params->vflip); + result += raspicamcontrol_set_ROI(camera, params->roi); + result += raspicamcontrol_set_shutter_speed(camera, params->shutter_speed); + result += raspicamcontrol_set_DRC(camera, params->drc_level); + result += raspicamcontrol_set_stats_pass(camera, params->stats_pass); + result += raspicamcontrol_set_annotate(camera, params->enable_annotate, params->annotate_string, + params->annotate_text_size, + params->annotate_text_colour, + params->annotate_bg_colour); + + return result; +} + +/** + * Adjust the saturation level for images + * @param camera Pointer to camera component + * @param saturation Value to adjust, -100 to 100 + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_saturation(MMAL_COMPONENT_T *camera, int saturation) +{ + int ret = 0; + + if (!camera) + return 1; + + if (saturation >= -100 && saturation <= 100) + { + MMAL_RATIONAL_T value = {saturation, 100}; + ret = mmal_status_to_int(mmal_port_parameter_set_rational(camera->control, MMAL_PARAMETER_SATURATION, value)); + } + else + { + vcos_log_error("Invalid saturation value"); + ret = 1; + } + + return ret; +} + +/** + * Set the sharpness of the image + * @param camera Pointer to camera component + * @param sharpness Sharpness adjustment -100 to 100 + */ +int raspicamcontrol_set_sharpness(MMAL_COMPONENT_T *camera, int sharpness) +{ + int ret = 0; + + if (!camera) + return 1; + + if (sharpness >= -100 && sharpness <= 100) + { + MMAL_RATIONAL_T value = {sharpness, 100}; + ret = mmal_status_to_int(mmal_port_parameter_set_rational(camera->control, MMAL_PARAMETER_SHARPNESS, value)); + } + else + { + vcos_log_error("Invalid sharpness value"); + ret = 1; + } + + return ret; +} + +/** + * Set the contrast adjustment for the image + * @param camera Pointer to camera component + * @param contrast Contrast adjustment -100 to 100 + * @return + */ +int raspicamcontrol_set_contrast(MMAL_COMPONENT_T *camera, int contrast) +{ + int ret = 0; + + if (!camera) + return 1; + + if (contrast >= -100 && contrast <= 100) + { + MMAL_RATIONAL_T value = {contrast, 100}; + ret = mmal_status_to_int(mmal_port_parameter_set_rational(camera->control, MMAL_PARAMETER_CONTRAST, value)); + } + else + { + vcos_log_error("Invalid contrast value"); + ret = 1; + } + + return ret; +} + +/** + * Adjust the brightness level for images + * @param camera Pointer to camera component + * @param brightness Value to adjust, 0 to 100 + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_brightness(MMAL_COMPONENT_T *camera, int brightness) +{ + int ret = 0; + + if (!camera) + return 1; + + if (brightness >= 0 && brightness <= 100) + { + MMAL_RATIONAL_T value = {brightness, 100}; + ret = mmal_status_to_int(mmal_port_parameter_set_rational(camera->control, MMAL_PARAMETER_BRIGHTNESS, value)); + } + else + { + vcos_log_error("Invalid brightness value"); + ret = 1; + } + + return ret; +} + +/** + * Adjust the ISO used for images + * @param camera Pointer to camera component + * @param ISO Value to set TODO : + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_ISO(MMAL_COMPONENT_T *camera, int ISO) +{ + if (!camera) + return 1; + + return mmal_status_to_int(mmal_port_parameter_set_uint32(camera->control, MMAL_PARAMETER_ISO, ISO)); +} + +/** + * Adjust the metering mode for images + * @param camera Pointer to camera component + * @param saturation Value from following + * - MMAL_PARAM_EXPOSUREMETERINGMODE_AVERAGE, + * - MMAL_PARAM_EXPOSUREMETERINGMODE_SPOT, + * - MMAL_PARAM_EXPOSUREMETERINGMODE_BACKLIT, + * - MMAL_PARAM_EXPOSUREMETERINGMODE_MATRIX + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_metering_mode(MMAL_COMPONENT_T *camera, MMAL_PARAM_EXPOSUREMETERINGMODE_T m_mode ) +{ + MMAL_PARAMETER_EXPOSUREMETERINGMODE_T meter_mode = {{MMAL_PARAMETER_EXP_METERING_MODE,sizeof(meter_mode)}, + m_mode}; + if (!camera) + return 1; + + return mmal_status_to_int(mmal_port_parameter_set(camera->control, &meter_mode.hdr)); +} + + +/** + * Set the video stabilisation flag. Only used in video mode + * @param camera Pointer to camera component + * @param saturation Flag 0 off 1 on + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_video_stabilisation(MMAL_COMPONENT_T *camera, int vstabilisation) +{ + if (!camera) + return 1; + + return mmal_status_to_int(mmal_port_parameter_set_boolean(camera->control, MMAL_PARAMETER_VIDEO_STABILISATION, vstabilisation)); +} + +/** + * Adjust the exposure compensation for images (EV) + * @param camera Pointer to camera component + * @param exp_comp Value to adjust, -10 to +10 + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_exposure_compensation(MMAL_COMPONENT_T *camera, int exp_comp) +{ + if (!camera) + return 1; + + return mmal_status_to_int(mmal_port_parameter_set_int32(camera->control, MMAL_PARAMETER_EXPOSURE_COMP , exp_comp)); +} + + +/** + * Set exposure mode for images + * @param camera Pointer to camera component + * @param mode Exposure mode to set from + * - MMAL_PARAM_EXPOSUREMODE_OFF, + * - MMAL_PARAM_EXPOSUREMODE_AUTO, + * - MMAL_PARAM_EXPOSUREMODE_NIGHT, + * - MMAL_PARAM_EXPOSUREMODE_NIGHTPREVIEW, + * - MMAL_PARAM_EXPOSUREMODE_BACKLIGHT, + * - MMAL_PARAM_EXPOSUREMODE_SPOTLIGHT, + * - MMAL_PARAM_EXPOSUREMODE_SPORTS, + * - MMAL_PARAM_EXPOSUREMODE_SNOW, + * - MMAL_PARAM_EXPOSUREMODE_BEACH, + * - MMAL_PARAM_EXPOSUREMODE_VERYLONG, + * - MMAL_PARAM_EXPOSUREMODE_FIXEDFPS, + * - MMAL_PARAM_EXPOSUREMODE_ANTISHAKE, + * - MMAL_PARAM_EXPOSUREMODE_FIREWORKS, + * + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_exposure_mode(MMAL_COMPONENT_T *camera, MMAL_PARAM_EXPOSUREMODE_T mode) +{ + MMAL_PARAMETER_EXPOSUREMODE_T exp_mode = {{MMAL_PARAMETER_EXPOSURE_MODE,sizeof(exp_mode)}, mode}; + + if (!camera) + return 1; + + return mmal_status_to_int(mmal_port_parameter_set(camera->control, &exp_mode.hdr)); +} + + +/** + * Set the aWB (auto white balance) mode for images + * @param camera Pointer to camera component + * @param awb_mode Value to set from + * - MMAL_PARAM_AWBMODE_OFF, + * - MMAL_PARAM_AWBMODE_AUTO, + * - MMAL_PARAM_AWBMODE_SUNLIGHT, + * - MMAL_PARAM_AWBMODE_CLOUDY, + * - MMAL_PARAM_AWBMODE_SHADE, + * - MMAL_PARAM_AWBMODE_TUNGSTEN, + * - MMAL_PARAM_AWBMODE_FLUORESCENT, + * - MMAL_PARAM_AWBMODE_INCANDESCENT, + * - MMAL_PARAM_AWBMODE_FLASH, + * - MMAL_PARAM_AWBMODE_HORIZON, + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_awb_mode(MMAL_COMPONENT_T *camera, MMAL_PARAM_AWBMODE_T awb_mode) +{ + MMAL_PARAMETER_AWBMODE_T param = {{MMAL_PARAMETER_AWB_MODE,sizeof(param)}, awb_mode}; + + if (!camera) + return 1; + + return mmal_status_to_int(mmal_port_parameter_set(camera->control, ¶m.hdr)); +} + +int raspicamcontrol_set_awb_gains(MMAL_COMPONENT_T *camera, float r_gain, float b_gain) +{ + MMAL_PARAMETER_AWB_GAINS_T param = {{MMAL_PARAMETER_CUSTOM_AWB_GAINS,sizeof(param)}, {0,0}, {0,0}}; + + if (!camera) + return 1; + + if (!r_gain || !b_gain) + return 0; + + param.r_gain.num = (unsigned int)(r_gain * 65536); + param.b_gain.num = (unsigned int)(b_gain * 65536); + param.r_gain.den = param.b_gain.den = 65536; + return mmal_status_to_int(mmal_port_parameter_set(camera->control, ¶m.hdr)); +} + +/** + * Set the image effect for the images + * @param camera Pointer to camera component + * @param imageFX Value from + * - MMAL_PARAM_IMAGEFX_NONE, + * - MMAL_PARAM_IMAGEFX_NEGATIVE, + * - MMAL_PARAM_IMAGEFX_SOLARIZE, + * - MMAL_PARAM_IMAGEFX_POSTERIZE, + * - MMAL_PARAM_IMAGEFX_WHITEBOARD, + * - MMAL_PARAM_IMAGEFX_BLACKBOARD, + * - MMAL_PARAM_IMAGEFX_SKETCH, + * - MMAL_PARAM_IMAGEFX_DENOISE, + * - MMAL_PARAM_IMAGEFX_EMBOSS, + * - MMAL_PARAM_IMAGEFX_OILPAINT, + * - MMAL_PARAM_IMAGEFX_HATCH, + * - MMAL_PARAM_IMAGEFX_GPEN, + * - MMAL_PARAM_IMAGEFX_PASTEL, + * - MMAL_PARAM_IMAGEFX_WATERCOLOUR, + * - MMAL_PARAM_IMAGEFX_FILM, + * - MMAL_PARAM_IMAGEFX_BLUR, + * - MMAL_PARAM_IMAGEFX_SATURATION, + * - MMAL_PARAM_IMAGEFX_COLOURSWAP, + * - MMAL_PARAM_IMAGEFX_WASHEDOUT, + * - MMAL_PARAM_IMAGEFX_POSTERISE, + * - MMAL_PARAM_IMAGEFX_COLOURPOINT, + * - MMAL_PARAM_IMAGEFX_COLOURBALANCE, + * - MMAL_PARAM_IMAGEFX_CARTOON, + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_imageFX(MMAL_COMPONENT_T *camera, MMAL_PARAM_IMAGEFX_T imageFX) +{ + MMAL_PARAMETER_IMAGEFX_T imgFX = {{MMAL_PARAMETER_IMAGE_EFFECT,sizeof(imgFX)}, imageFX}; + + if (!camera) + return 1; + + return mmal_status_to_int(mmal_port_parameter_set(camera->control, &imgFX.hdr)); +} + +/* TODO :what to do with the image effects parameters? + MMAL_PARAMETER_IMAGEFX_PARAMETERS_T imfx_param = {{MMAL_PARAMETER_IMAGE_EFFECT_PARAMETERS,sizeof(imfx_param)}, + imageFX, 0, {0}}; +mmal_port_parameter_set(camera->control, &imfx_param.hdr); + */ + +/** + * Set the colour effect for images (Set UV component) + * @param camera Pointer to camera component + * @param colourFX Contains enable state and U and V numbers to set (e.g. 128,128 = Black and white) + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_colourFX(MMAL_COMPONENT_T *camera, const MMAL_PARAM_COLOURFX_T *colourFX) +{ + MMAL_PARAMETER_COLOURFX_T colfx = {{MMAL_PARAMETER_COLOUR_EFFECT,sizeof(colfx)}, 0, 0, 0}; + + if (!camera) + return 1; + + colfx.enable = colourFX->enable; + colfx.u = colourFX->u; + colfx.v = colourFX->v; + + return mmal_status_to_int(mmal_port_parameter_set(camera->control, &colfx.hdr)); + +} + + +/** + * Set the rotation of the image + * @param camera Pointer to camera component + * @param rotation Degree of rotation (any number, but will be converted to 0,90,180 or 270 only) + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_rotation(MMAL_COMPONENT_T *camera, int rotation) +{ + int ret; + int my_rotation = ((rotation % 360 ) / 90) * 90; + + ret = mmal_port_parameter_set_int32(camera->output[0], MMAL_PARAMETER_ROTATION, my_rotation); + mmal_port_parameter_set_int32(camera->output[1], MMAL_PARAMETER_ROTATION, my_rotation); + mmal_port_parameter_set_int32(camera->output[2], MMAL_PARAMETER_ROTATION, my_rotation); + + return ret; +} + +/** + * Set the flips state of the image + * @param camera Pointer to camera component + * @param hflip If true, horizontally flip the image + * @param vflip If true, vertically flip the image + * + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_flips(MMAL_COMPONENT_T *camera, int hflip, int vflip) +{ + MMAL_PARAMETER_MIRROR_T mirror = {{MMAL_PARAMETER_MIRROR, sizeof(MMAL_PARAMETER_MIRROR_T)}, MMAL_PARAM_MIRROR_NONE}; + + if (hflip && vflip) + mirror.value = MMAL_PARAM_MIRROR_BOTH; + else + if (hflip) + mirror.value = MMAL_PARAM_MIRROR_HORIZONTAL; + else + if (vflip) + mirror.value = MMAL_PARAM_MIRROR_VERTICAL; + + mmal_port_parameter_set(camera->output[0], &mirror.hdr); + mmal_port_parameter_set(camera->output[1], &mirror.hdr); + return mmal_port_parameter_set(camera->output[2], &mirror.hdr); +} + +/** + * Set the ROI of the sensor to use for captures/preview + * @param camera Pointer to camera component + * @param rect Normalised coordinates of ROI rectangle + * + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_ROI(MMAL_COMPONENT_T *camera, PARAM_FLOAT_RECT_T rect) +{ + MMAL_PARAMETER_INPUT_CROP_T crop = {{MMAL_PARAMETER_INPUT_CROP, sizeof(MMAL_PARAMETER_INPUT_CROP_T)}}; + + crop.rect.x = (65536 * rect.x); + crop.rect.y = (65536 * rect.y); + crop.rect.width = (65536 * rect.w); + crop.rect.height = (65536 * rect.h); + + return mmal_port_parameter_set(camera->control, &crop.hdr); +} + +/** + * Adjust the exposure time used for images + * @param camera Pointer to camera component + * @param shutter speed in microseconds + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_shutter_speed(MMAL_COMPONENT_T *camera, int speed) +{ + if (!camera) + return 1; + + return mmal_status_to_int(mmal_port_parameter_set_uint32(camera->control, MMAL_PARAMETER_SHUTTER_SPEED, speed)); +} + +/** + * Adjust the Dynamic range compression level + * @param camera Pointer to camera component + * @param strength Strength of DRC to apply + * MMAL_PARAMETER_DRC_STRENGTH_OFF + * MMAL_PARAMETER_DRC_STRENGTH_LOW + * MMAL_PARAMETER_DRC_STRENGTH_MEDIUM + * MMAL_PARAMETER_DRC_STRENGTH_HIGH + * + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_DRC(MMAL_COMPONENT_T *camera, MMAL_PARAMETER_DRC_STRENGTH_T strength) +{ + MMAL_PARAMETER_DRC_T drc = {{MMAL_PARAMETER_DYNAMIC_RANGE_COMPRESSION, sizeof(MMAL_PARAMETER_DRC_T)}, strength}; + + if (!camera) + return 1; + + return mmal_status_to_int(mmal_port_parameter_set(camera->control, &drc.hdr)); +} + +int raspicamcontrol_set_stats_pass(MMAL_COMPONENT_T *camera, int stats_pass) +{ + if (!camera) + return 1; + + return mmal_status_to_int(mmal_port_parameter_set_boolean(camera->control, MMAL_PARAMETER_CAPTURE_STATS_PASS, stats_pass)); +} + + +/** + * Set the annotate data + * @param camera Pointer to camera component + * @param Bitmask of required annotation data. 0 for off. + * @param If set, a pointer to text string to use instead of bitmask, max length 32 characters + * + * @return 0 if successful, non-zero if any parameters out of range + */ +int raspicamcontrol_set_annotate(MMAL_COMPONENT_T *camera, const int settings, const char *string, + const int text_size, const int text_colour, const int bg_colour) +{ + MMAL_PARAMETER_CAMERA_ANNOTATE_V3_T annotate = + {{MMAL_PARAMETER_ANNOTATE, sizeof(MMAL_PARAMETER_CAMERA_ANNOTATE_V3_T)}}; + + if (settings) + { + time_t t = time(NULL); + struct tm tm = *localtime(&t); + char tmp[MMAL_CAMERA_ANNOTATE_MAX_TEXT_LEN_V3]; + + annotate.enable = 1; + + if (settings & (ANNOTATE_APP_TEXT | ANNOTATE_USER_TEXT)) + { + strncpy(annotate.text, string, MMAL_CAMERA_ANNOTATE_MAX_TEXT_LEN_V3); + annotate.text[MMAL_CAMERA_ANNOTATE_MAX_TEXT_LEN_V3-1] = '\0'; + } + + if (settings & ANNOTATE_TIME_TEXT) + { + strftime(tmp, 32, "%X ", &tm ); + strncat(annotate.text, tmp, MMAL_CAMERA_ANNOTATE_MAX_TEXT_LEN_V3 - strlen(annotate.text) - 1); + } + + if (settings & ANNOTATE_DATE_TEXT) + { + strftime(tmp, 32, "%x", &tm ); + strncat(annotate.text, tmp, MMAL_CAMERA_ANNOTATE_MAX_TEXT_LEN_V3 - strlen(annotate.text) - 1); + } + + if (settings & ANNOTATE_SHUTTER_SETTINGS) + annotate.show_shutter = MMAL_TRUE; + + if (settings & ANNOTATE_GAIN_SETTINGS) + annotate.show_analog_gain = MMAL_TRUE; + + if (settings & ANNOTATE_LENS_SETTINGS) + annotate.show_lens = MMAL_TRUE; + + if (settings & ANNOTATE_CAF_SETTINGS) + annotate.show_caf = MMAL_TRUE; + + if (settings & ANNOTATE_MOTION_SETTINGS) + annotate.show_motion = MMAL_TRUE; + + if (settings & ANNOTATE_FRAME_NUMBER) + annotate.show_frame_num = MMAL_TRUE; + + if (settings & ANNOTATE_BLACK_BACKGROUND) + annotate.enable_text_background = MMAL_TRUE; + + annotate.text_size = text_size; + + if (text_colour != -1) + { + annotate.custom_text_colour = MMAL_TRUE; + annotate.custom_text_Y = text_colour&0xff; + annotate.custom_text_U = (text_colour>>8)&0xff; + annotate.custom_text_V = (text_colour>>16)&0xff; + } + else + annotate.custom_text_colour = MMAL_FALSE; + + if (bg_colour != -1) + { + annotate.custom_background_colour = MMAL_TRUE; + annotate.custom_background_Y = bg_colour&0xff; + annotate.custom_background_U = (bg_colour>>8)&0xff; + annotate.custom_background_V = (bg_colour>>16)&0xff; + } + else + annotate.custom_background_colour = MMAL_FALSE; + } + else + annotate.enable = 0; + + return mmal_status_to_int(mmal_port_parameter_set(camera->control, &annotate.hdr)); +} + +int raspicamcontrol_set_stereo_mode(MMAL_PORT_T *port, MMAL_PARAMETER_STEREOSCOPIC_MODE_T *stereo_mode) +{ + MMAL_PARAMETER_STEREOSCOPIC_MODE_T stereo = { {MMAL_PARAMETER_STEREOSCOPIC_MODE, sizeof(stereo)}, + MMAL_STEREOSCOPIC_MODE_NONE, MMAL_FALSE, MMAL_FALSE }; + if (stereo_mode->mode != MMAL_STEREOSCOPIC_MODE_NONE) + { + stereo.mode = stereo_mode->mode; + stereo.decimate = stereo_mode->decimate; + stereo.swap_eyes = stereo_mode->swap_eyes; + } + return mmal_status_to_int(mmal_port_parameter_set(port, &stereo.hdr)); +} + +/** + * Asked GPU how much memory it has allocated + * + * @return amount of memory in MB + */ +static int raspicamcontrol_get_mem_gpu(void) +{ + char response[80] = ""; + int gpu_mem = 0; + if (vc_gencmd(response, sizeof response, "get_mem gpu") == 0) + vc_gencmd_number_property(response, "gpu", &gpu_mem); + return gpu_mem; +} + +/** + * Ask GPU about its camera abilities + * @param supported None-zero if software supports the camera + * @param detected None-zero if a camera has been detected + */ +void raspicamcontrol_get_camera(int *supported, int *detected) +{ + char response[80] = ""; + if (vc_gencmd(response, sizeof response, "get_camera") == 0) + { + if (supported) + vc_gencmd_number_property(response, "supported", supported); + if (detected) + vc_gencmd_number_property(response, "detected", detected); + } +} + +/** + * Check to see if camera is supported, and we have allocated enough meooryAsk GPU about its camera abilities + * @param supported None-zero if software supports the camera + * @param detected None-zero if a camera has been detected + */ +void raspicamcontrol_check_configuration(int min_gpu_mem) +{ + int gpu_mem = raspicamcontrol_get_mem_gpu(); + int supported = 0, detected = 0; + raspicamcontrol_get_camera(&supported, &detected); + if (!supported) + vcos_log_error("Camera is not enabled in this build. Try running \"sudo raspi-config\" and ensure that \"camera\" has been enabled\n"); + else if (gpu_mem < min_gpu_mem) + vcos_log_error("Only %dM of gpu_mem is configured. Try running \"sudo raspi-config\" and ensure that \"memory_split\" has a value of %d or greater\n", gpu_mem, min_gpu_mem); + else if (!detected) + vcos_log_error("Camera is not detected. Please check carefully the camera module is installed correctly\n"); + else + vcos_log_error("Failed to run camera app. Please check for firmware updates\n"); +} + diff --git a/sys/rpicamsrc/RaspiCamControl.h b/sys/rpicamsrc/RaspiCamControl.h new file mode 100644 index 000000000..83e9eddf1 --- /dev/null +++ b/sys/rpicamsrc/RaspiCamControl.h @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2013-2015 Jan Schmidt <jan@centricular.com> +Portions: +Copyright (c) 2013, Broadcom Europe Ltd +Copyright (c) 2013, James Hughes +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef RASPICAMCONTROL_H_ +#define RASPICAMCONTROL_H_ + +/* Various parameters + * + * Exposure Mode + * MMAL_PARAM_EXPOSUREMODE_OFF, + MMAL_PARAM_EXPOSUREMODE_AUTO, + MMAL_PARAM_EXPOSUREMODE_NIGHT, + MMAL_PARAM_EXPOSUREMODE_NIGHTPREVIEW, + MMAL_PARAM_EXPOSUREMODE_BACKLIGHT, + MMAL_PARAM_EXPOSUREMODE_SPOTLIGHT, + MMAL_PARAM_EXPOSUREMODE_SPORTS, + MMAL_PARAM_EXPOSUREMODE_SNOW, + MMAL_PARAM_EXPOSUREMODE_BEACH, + MMAL_PARAM_EXPOSUREMODE_VERYLONG, + MMAL_PARAM_EXPOSUREMODE_FIXEDFPS, + MMAL_PARAM_EXPOSUREMODE_ANTISHAKE, + MMAL_PARAM_EXPOSUREMODE_FIREWORKS, + * + * AWB Mode + * MMAL_PARAM_AWBMODE_OFF, + MMAL_PARAM_AWBMODE_AUTO, + MMAL_PARAM_AWBMODE_SUNLIGHT, + MMAL_PARAM_AWBMODE_CLOUDY, + MMAL_PARAM_AWBMODE_SHADE, + MMAL_PARAM_AWBMODE_TUNGSTEN, + MMAL_PARAM_AWBMODE_FLUORESCENT, + MMAL_PARAM_AWBMODE_INCANDESCENT, + MMAL_PARAM_AWBMODE_FLASH, + MMAL_PARAM_AWBMODE_HORIZON, + * + * Image FX + MMAL_PARAM_IMAGEFX_NONE, + MMAL_PARAM_IMAGEFX_NEGATIVE, + MMAL_PARAM_IMAGEFX_SOLARIZE, + MMAL_PARAM_IMAGEFX_POSTERIZE, + MMAL_PARAM_IMAGEFX_WHITEBOARD, + MMAL_PARAM_IMAGEFX_BLACKBOARD, + MMAL_PARAM_IMAGEFX_SKETCH, + MMAL_PARAM_IMAGEFX_DENOISE, + MMAL_PARAM_IMAGEFX_EMBOSS, + MMAL_PARAM_IMAGEFX_OILPAINT, + MMAL_PARAM_IMAGEFX_HATCH, + MMAL_PARAM_IMAGEFX_GPEN, + MMAL_PARAM_IMAGEFX_PASTEL, + MMAL_PARAM_IMAGEFX_WATERCOLOUR, + MMAL_PARAM_IMAGEFX_FILM, + MMAL_PARAM_IMAGEFX_BLUR, + MMAL_PARAM_IMAGEFX_SATURATION, + MMAL_PARAM_IMAGEFX_COLOURSWAP, + MMAL_PARAM_IMAGEFX_WASHEDOUT, + MMAL_PARAM_IMAGEFX_POSTERISE, + MMAL_PARAM_IMAGEFX_COLOURPOINT, + MMAL_PARAM_IMAGEFX_COLOURBALANCE, + MMAL_PARAM_IMAGEFX_CARTOON, + + */ + +/// Annotate bitmask options +/// Supplied by user on command line +#define ANNOTATE_USER_TEXT 1 +/// Supplied by app using this module +#define ANNOTATE_APP_TEXT 2 +/// Insert current date +#define ANNOTATE_DATE_TEXT 4 +// Insert current time +#define ANNOTATE_TIME_TEXT 8 + +#define ANNOTATE_SHUTTER_SETTINGS 16 +#define ANNOTATE_CAF_SETTINGS 32 +#define ANNOTATE_GAIN_SETTINGS 64 +#define ANNOTATE_LENS_SETTINGS 128 +#define ANNOTATE_MOTION_SETTINGS 256 +#define ANNOTATE_FRAME_NUMBER 512 +#define ANNOTATE_BLACK_BACKGROUND 1024 + + +// There isn't actually a MMAL structure for the following, so make one +typedef struct +{ + int enable; /// Turn colourFX on or off + int u,v; /// U and V to use +} MMAL_PARAM_COLOURFX_T; + +typedef struct +{ + int enable; + int width,height; + int quality; +} MMAL_PARAM_THUMBNAIL_CONFIG_T; + +typedef struct +{ + double x; + double y; + double w; + double h; +} PARAM_FLOAT_RECT_T; + +/// struct contain camera settings +typedef struct +{ + int sharpness; /// -100 to 100 + int contrast; /// -100 to 100 + int brightness; /// 0 to 100 + int saturation; /// -100 to 100 + int ISO; /// TODO : what range? + int videoStabilisation; /// 0 or 1 (false or true) + int exposureCompensation; /// -10 to +10 ? + MMAL_PARAM_EXPOSUREMODE_T exposureMode; + MMAL_PARAM_EXPOSUREMETERINGMODE_T exposureMeterMode; + MMAL_PARAM_AWBMODE_T awbMode; + MMAL_PARAM_IMAGEFX_T imageEffect; + MMAL_PARAMETER_IMAGEFX_PARAMETERS_T imageEffectsParameters; + MMAL_PARAM_COLOURFX_T colourEffects; + int rotation; /// 0-359 + int hflip; /// 0 or 1 + int vflip; /// 0 or 1 + PARAM_FLOAT_RECT_T roi; /// region of interest to use on the sensor. Normalised [0,1] values in the rect + int shutter_speed; /// 0 = auto, otherwise the shutter speed in ms + float awb_gains_r; /// AWB red gain + float awb_gains_b; /// AWB blue gain + MMAL_PARAMETER_DRC_STRENGTH_T drc_level; // Strength of Dynamic Range compression to apply + MMAL_BOOL_T stats_pass; /// Stills capture statistics pass on/off + int enable_annotate; /// Flag to enable the annotate, 0 = disabled, otherwise a bitmask of what needs to be displayed + char annotate_string[MMAL_CAMERA_ANNOTATE_MAX_TEXT_LEN_V2]; /// String to use for annotate - overrides certain bitmask settings + int annotate_text_size; // Text size for annotation + int annotate_text_colour; // Text colour for annotation + int annotate_bg_colour; // Background colour for annotation + MMAL_PARAMETER_STEREOSCOPIC_MODE_T stereo_mode; +} RASPICAM_CAMERA_PARAMETERS; + + +void raspicamcontrol_check_configuration(int min_gpu_mem); + +int raspicamcontrol_parse_cmdline(RASPICAM_CAMERA_PARAMETERS *params, const char *arg1, const char *arg2); +void raspicamcontrol_display_help(); +int raspicamcontrol_cycle_test(MMAL_COMPONENT_T *camera); + +int raspicamcontrol_set_all_parameters(MMAL_COMPONENT_T *camera, const RASPICAM_CAMERA_PARAMETERS *params); +int raspicamcontrol_get_all_parameters(MMAL_COMPONENT_T *camera, RASPICAM_CAMERA_PARAMETERS *params); +void raspicamcontrol_dump_parameters(const RASPICAM_CAMERA_PARAMETERS *params); + +void raspicamcontrol_set_defaults(RASPICAM_CAMERA_PARAMETERS *params); + +void raspicamcontrol_check_configuration(int min_gpu_mem); +void raspicamcontrol_get_camera(int *supported, int *detected); + +// Individual setting functions +int raspicamcontrol_set_saturation(MMAL_COMPONENT_T *camera, int saturation); +int raspicamcontrol_set_sharpness(MMAL_COMPONENT_T *camera, int sharpness); +int raspicamcontrol_set_contrast(MMAL_COMPONENT_T *camera, int contrast); +int raspicamcontrol_set_brightness(MMAL_COMPONENT_T *camera, int brightness); +int raspicamcontrol_set_ISO(MMAL_COMPONENT_T *camera, int ISO); +int raspicamcontrol_set_metering_mode(MMAL_COMPONENT_T *camera, MMAL_PARAM_EXPOSUREMETERINGMODE_T mode); +int raspicamcontrol_set_video_stabilisation(MMAL_COMPONENT_T *camera, int vstabilisation); +int raspicamcontrol_set_exposure_compensation(MMAL_COMPONENT_T *camera, int exp_comp); +int raspicamcontrol_set_exposure_mode(MMAL_COMPONENT_T *camera, MMAL_PARAM_EXPOSUREMODE_T mode); +int raspicamcontrol_set_awb_mode(MMAL_COMPONENT_T *camera, MMAL_PARAM_AWBMODE_T awb_mode); +int raspicamcontrol_set_awb_gains(MMAL_COMPONENT_T *camera, float r_gain, float b_gain); +int raspicamcontrol_set_imageFX(MMAL_COMPONENT_T *camera, MMAL_PARAM_IMAGEFX_T imageFX); +int raspicamcontrol_set_colourFX(MMAL_COMPONENT_T *camera, const MMAL_PARAM_COLOURFX_T *colourFX); +int raspicamcontrol_set_rotation(MMAL_COMPONENT_T *camera, int rotation); +int raspicamcontrol_set_flips(MMAL_COMPONENT_T *camera, int hflip, int vflip); +int raspicamcontrol_set_ROI(MMAL_COMPONENT_T *camera, PARAM_FLOAT_RECT_T rect); +int raspicamcontrol_set_shutter_speed(MMAL_COMPONENT_T *camera, int speed_ms); +int raspicamcontrol_set_DRC(MMAL_COMPONENT_T *camera, MMAL_PARAMETER_DRC_STRENGTH_T strength); +int raspicamcontrol_set_stats_pass(MMAL_COMPONENT_T *camera, int stats_pass); +int raspicamcontrol_set_annotate(MMAL_COMPONENT_T *camera, const int bitmask, const char *string, + const int text_size, const int text_colour, const int bg_colour); +int raspicamcontrol_set_stereo_mode(MMAL_PORT_T *port, MMAL_PARAMETER_STEREOSCOPIC_MODE_T *stereo_mode); + +//Individual getting functions +int raspicamcontrol_get_saturation(MMAL_COMPONENT_T *camera); +int raspicamcontrol_get_sharpness(MMAL_COMPONENT_T *camera); +int raspicamcontrol_get_contrast(MMAL_COMPONENT_T *camera); +int raspicamcontrol_get_brightness(MMAL_COMPONENT_T *camera); +int raspicamcontrol_get_ISO(MMAL_COMPONENT_T *camera); +MMAL_PARAM_EXPOSUREMETERINGMODE_T raspicamcontrol_get_metering_mode(MMAL_COMPONENT_T *camera); +int raspicamcontrol_get_video_stabilisation(MMAL_COMPONENT_T *camera); +int raspicamcontrol_get_exposure_compensation(MMAL_COMPONENT_T *camera); +MMAL_PARAM_THUMBNAIL_CONFIG_T raspicamcontrol_get_thumbnail_parameters(MMAL_COMPONENT_T *camera); +MMAL_PARAM_EXPOSUREMODE_T raspicamcontrol_get_exposure_mode(MMAL_COMPONENT_T *camera); +MMAL_PARAM_AWBMODE_T raspicamcontrol_get_awb_mode(MMAL_COMPONENT_T *camera); +MMAL_PARAM_IMAGEFX_T raspicamcontrol_get_imageFX(MMAL_COMPONENT_T *camera); +MMAL_PARAM_COLOURFX_T raspicamcontrol_get_colourFX(MMAL_COMPONENT_T *camera); + + +#endif /* RASPICAMCONTROL_H_ */ diff --git a/sys/rpicamsrc/RaspiCapture.c b/sys/rpicamsrc/RaspiCapture.c new file mode 100644 index 000000000..975078f29 --- /dev/null +++ b/sys/rpicamsrc/RaspiCapture.c @@ -0,0 +1,2026 @@ +/* + * Copyright (c) 2013-2016 Jan Schmidt <jan@centricular.com> +Portions: +Copyright (c) 2013, Broadcom Europe Ltd +Copyright (c) 2013, James Hughes +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * \file RaspiCapture.c + * + * Modification of the RaspiVid command line capture program for GStreamer + * use. + * + * \date 28th Feb 2013, 11 Oct 2013, 5 Mar 2015 + * \Author: James Hughes, Jan Schmidt + * + * Description + * + * 3 components are created; camera, preview and video encoder. + * Camera component has three ports, preview, video and stills. + * This program connects preview and stills to the preview and video + * encoder. Using mmal we don't need to worry about buffers between these + * components, but we do need to handle buffers from the encoder, which + * are simply written straight to the file in the requisite buffer callback. + * + * We use the RaspiCamControl code to handle the specific camera settings. + * We use the RaspiPreview code to handle the (generic) preview window + */ + +// We use some GNU extensions (basename, asprintf) +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <memory.h> +#include <sysexits.h> + +#include <gst/gst.h> + +#include "bcm_host.h" +#include "interface/vcos/vcos.h" + +#include "interface/mmal/mmal.h" +#include "interface/mmal/mmal_logging.h" +#include "interface/mmal/mmal_buffer.h" +#include "interface/mmal/util/mmal_util.h" +#include "interface/mmal/util/mmal_util_params.h" +#include "interface/mmal/util/mmal_default_components.h" +#include "interface/mmal/util/mmal_connection.h" + +#include "RaspiCapture.h" +#include "RaspiCamControl.h" +#include "RaspiPreview.h" +#include "RaspiCLI.h" + +#include <semaphore.h> + +// Standard port setting for the camera component +#define MMAL_CAMERA_PREVIEW_PORT 0 +#define MMAL_CAMERA_VIDEO_PORT 1 +#define MMAL_CAMERA_CAPTURE_PORT 2 + +// Video format information +// 0 implies variable +#define VIDEO_FRAME_RATE_NUM 30 +#define VIDEO_FRAME_RATE_DEN 1 + +/// Video render needs at least 2 buffers. +#define VIDEO_OUTPUT_BUFFERS_NUM 3 + +// Max bitrate we allow for recording +const int MAX_BITRATE = 25000000; // 25Mbits/s + +/// Interval at which we check for an failure abort during capture +const int ABORT_INTERVAL = 100; // ms + + +int mmal_status_to_int(MMAL_STATUS_T status); + +/** Struct used to pass information in encoder port userdata to callback + */ +typedef struct +{ + RASPIVID_STATE *state; /// pointer to our state in case required in callback + int abort; /// Set to 1 in callback if an error occurs to attempt to abort the capture +} PORT_USERDATA; + +struct RASPIVID_STATE_T +{ + RASPIVID_CONFIG config; + + FILE *output_file; + + MMAL_COMPONENT_T *camera_component; /// Pointer to the camera component + MMAL_COMPONENT_T *encoder_component; /// Pointer to the encoder component + MMAL_CONNECTION_T *preview_connection; /// Pointer to the connection from camera to preview + MMAL_CONNECTION_T *encoder_connection; /// Pointer to the connection from camera to encoder + + MMAL_PORT_T *camera_video_port; + MMAL_PORT_T *camera_still_port; + MMAL_PORT_T *encoder_output_port; + + MMAL_POOL_T *encoder_pool; /// Pointer to the pool of buffers used by encoder output port + + PORT_USERDATA callback_data; + + MMAL_QUEUE_T *encoded_buffer_q; + + int64_t base_time; + int64_t last_second; + + RASPIPREVIEW_STATE preview_state; +}; + + +/// Structure to cross reference H264 profile strings against the MMAL parameter equivalent +static XREF_T profile_map[] = +{ + {"baseline", MMAL_VIDEO_PROFILE_H264_BASELINE}, + {"main", MMAL_VIDEO_PROFILE_H264_MAIN}, + {"high", MMAL_VIDEO_PROFILE_H264_HIGH}, +// {"constrained", MMAL_VIDEO_PROFILE_H264_CONSTRAINED_BASELINE} // Does anyone need this? +}; + +static int profile_map_size = sizeof(profile_map) / sizeof(profile_map[0]); + +#if 0 +static XREF_T initial_map[] = +{ + {"record", 0}, + {"pause", 1}, +}; + +static int initial_map_size = sizeof(initial_map) / sizeof(initial_map[0]); +#endif + +static XREF_T intra_refresh_map[] = +{ + {"cyclic", MMAL_VIDEO_INTRA_REFRESH_CYCLIC}, + {"adaptive", MMAL_VIDEO_INTRA_REFRESH_ADAPTIVE}, + {"both", MMAL_VIDEO_INTRA_REFRESH_BOTH}, + {"cyclicrows", MMAL_VIDEO_INTRA_REFRESH_CYCLIC_MROWS}, +// {"random", MMAL_VIDEO_INTRA_REFRESH_PSEUDO_RAND} Cannot use random, crashes the encoder. No idea why. +}; + +static int intra_refresh_map_size = sizeof(intra_refresh_map) / sizeof(intra_refresh_map[0]); + +#if 0 + +static void display_valid_parameters(char *app_name); + +/// Command ID's and Structure defining our command line options +#define CommandHelp 0 +#define CommandWidth 1 +#define CommandHeight 2 +#define CommandBitrate 3 +#define CommandOutput 4 +#define CommandVerbose 5 +#define CommandTimeout 6 +#define CommandDemoMode 7 +#define CommandFramerate 8 +#define CommandPreviewEnc 9 +#define CommandIntraPeriod 10 +#define CommandProfile 11 +#define CommandTimed 12 +#define CommandSignal 13 +#define CommandKeypress 14 +#define CommandInitialState 15 +#define CommandQP 16 +#define CommandInlineHeaders 17 +#define CommandSegmentFile 18 +#define CommandSegmentWrap 19 +#define CommandSegmentStart 20 +#define CommandSplitWait 21 +#define CommandCircular 22 +#define CommandIMV 23 +#define CommandCamSelect 24 +#define CommandSettings 25 +#define CommandSensorMode 26 +#define CommandIntraRefreshType 27 + +static COMMAND_LIST cmdline_commands[] = +{ + { CommandHelp, "-help", "?", "This help information", 0 }, + { CommandWidth, "-width", "w", "Set image width <size>. Default 1920", 1 }, + { CommandHeight, "-height", "h", "Set image height <size>. Default 1080", 1 }, + { CommandBitrate, "-bitrate", "b", "Set bitrate. Use bits per second (e.g. 10MBits/s would be -b 10000000)", 1 }, + { CommandOutput, "-output", "o", "Output filename <filename> (to write to stdout, use '-o -')", 1 }, + { CommandVerbose, "-verbose", "v", "Output verbose information during run", 0 }, + { CommandTimeout, "-timeout", "t", "Time (in ms) to capture for. If not specified, set to 5s. Zero to disable", 1 }, + { CommandDemoMode, "-demo", "d", "Run a demo mode (cycle through range of camera options, no capture)", 1}, + { CommandFramerate, "-framerate", "fps","Specify the frames per second to record", 1}, + { CommandPreviewEnc, "-penc", "e", "Display preview image *after* encoding (shows compression artifacts)", 0}, + { CommandIntraPeriod, "-intra", "g", "Specify the intra refresh period (key frame rate/GoP size). Zero to produce an initial I-frame and then just P-frames.", 1}, + { CommandProfile, "-profile", "pf", "Specify H264 profile to use for encoding", 1}, + { CommandTimed, "-timed", "td", "Cycle between capture and pause. -cycle on,off where on is record time and off is pause time in ms", 0}, + { CommandSignal, "-signal", "s", "Cycle between capture and pause on Signal", 0}, + { CommandKeypress, "-keypress", "k", "Cycle between capture and pause on ENTER", 0}, + { CommandInitialState, "-initial", "i", "Initial state. Use 'record' or 'pause'. Default 'record'", 1}, + { CommandQP, "-qp", "qp", "Quantisation parameter. Use approximately 10-40. Default 0 (off)", 1}, + { CommandInlineHeaders, "-inline", "ih", "Insert inline headers (SPS, PPS) to stream", 0}, + { CommandSegmentFile, "-segment", "sg", "Segment output file in to multiple files at specified interval <ms>", 1}, + { CommandSegmentWrap, "-wrap", "wr", "In segment mode, wrap any numbered filename back to 1 when reach number", 1}, + { CommandSegmentStart, "-start", "sn", "In segment mode, start with specified segment number", 1}, + { CommandSplitWait, "-split", "sp", "In wait mode, create new output file for each start event", 0}, + { CommandCircular, "-circular", "c", "Run encoded data through circular buffer until triggered then save", 0}, + { CommandIMV, "-vectors", "x", "Output filename <filename> for inline motion vectors", 1 }, + { CommandCamSelect, "-camselect", "cs", "Select camera <number>. Default 0", 1 }, + { CommandSettings, "-settings", "set","Retrieve camera settings and write to stdout", 0}, + { CommandSensorMode, "-mode", "md", "Force sensor mode. 0=auto. See docs for other modes available", 1}, + { CommandIntraRefreshType,"-irefresh", "if", "Set intra refresh type", 1}, +}; + +static int cmdline_commands_size = sizeof(cmdline_commands) / sizeof(cmdline_commands[0]); +#endif + +static void dump_state(RASPIVID_STATE *state); + +/** + * Assign a default set of parameters to the state passed in + * + * @param state Pointer to state structure to assign defaults to + */ +void raspicapture_default_config(RASPIVID_CONFIG *config) +{ + if (!config) + { + vcos_assert(0); + return; + } + + // Default everything to zero + memset(config, 0, sizeof(RASPIVID_CONFIG)); + + // Now set anything non-zero + config->timeout = 5000; // 5s delay before take image + config->width = 1920; // Default to 1080p + config->height = 1080; + config->bitrate = 17000000; // This is a decent default bitrate for 1080p + config->fps_n = VIDEO_FRAME_RATE_NUM; + config->fps_d = VIDEO_FRAME_RATE_DEN; + config->intraperiod = -1; // Not set + config->quantisationParameter = 0; + config->demoMode = 0; + config->demoInterval = 250; // ms + config->immutableInput = 1; + config->profile = MMAL_VIDEO_PROFILE_H264_HIGH; + config->encoding = MMAL_ENCODING_H264; + + config->bInlineHeaders = 0; + + config->inlineMotionVectors = 0; + + config->cameraNum = 0; + config->settings = 0; + config->sensor_mode = 0; + + config->intra_refresh_type = -1; + + // Setup preview window defaults + raspipreview_set_defaults(&config->preview_parameters); + + // Set up the camera_parameters to default + raspicamcontrol_set_defaults(&config->camera_parameters); + +} + + +/** + * Dump image state parameters to printf. Used for debugging + * + * @param state Pointer to state structure to assign defaults to + */ +static void dump_state(RASPIVID_STATE *state) +{ + RASPIVID_CONFIG *config; + + if (!state) + { + vcos_assert(0); + return; + } + + config = &state->config; + + fprintf(stderr, "Width %d, Height %d\n", config->width, config->height); + fprintf(stderr, "bitrate %d, framerate %d/%d, time delay %d\n", + config->bitrate, config->fps_n, config->fps_d, config->timeout); + //fprintf(stderr, "H264 Profile %s\n", raspicli_unmap_xref(config->profile, profile_map, profile_map_size)); + + raspipreview_dump_parameters(&config->preview_parameters); + raspicamcontrol_dump_parameters(&config->camera_parameters); +} + +#if 0 +/** + * Parse the incoming command line and put resulting parameters in to the state + * + * @param argc Number of arguments in command line + * @param argv Array of pointers to strings from command line + * @param state Pointer to state structure to assign any discovered parameters to + * @return Non-0 if failed for some reason, 0 otherwise + */ +static int parse_cmdline(int argc, const char **argv, RASPIVID_STATE *state) +{ + // Parse the command line arguments. + // We are looking for --<something> or -<abreviation of something> + + int valid = 1; + int i; + + for (i = 1; i < argc && valid; i++) + { + int command_id, num_parameters; + + if (!argv[i]) + continue; + + if (argv[i][0] != '-') + { + valid = 0; + continue; + } + + // Assume parameter is valid until proven otherwise + valid = 1; + + command_id = raspicli_get_command_id(cmdline_commands, cmdline_commands_size, &argv[i][1], &num_parameters); + + // If we found a command but are missing a parameter, continue (and we will drop out of the loop) + if (command_id != -1 && num_parameters > 0 && (i + 1 >= argc) ) + continue; + + // We are now dealing with a command line option + switch (command_id) + { + case CommandHelp: + display_valid_parameters(basename(argv[0])); + return -1; + + case CommandWidth: // Width > 0 + if (sscanf(argv[i + 1], "%u", &state->width) != 1) + valid = 0; + else + i++; + break; + + case CommandHeight: // Height > 0 + if (sscanf(argv[i + 1], "%u", &state->height) != 1) + valid = 0; + else + i++; + break; + + case CommandBitrate: // 1-100 + if (sscanf(argv[i + 1], "%u", &state->bitrate) == 1) + { + if (state->bitrate > MAX_BITRATE) + { + state->bitrate = MAX_BITRATE; + } + i++; + } + else + valid = 0; + + break; + + case CommandOutput: // output filename + { + int len = strlen(argv[i + 1]); + if (len) + { + state->filename = malloc(len + 1); + vcos_assert(state->filename); + if (state->filename) + strncpy(state->filename, argv[i + 1], len+1); + i++; + } + else + valid = 0; + break; + } + + case CommandVerbose: // display lots of data during run + state->verbose = 1; + break; + + case CommandTimeout: // Time to run viewfinder/capture + { + if (sscanf(argv[i + 1], "%u", &state->timeout) == 1) + { + // Ensure that if previously selected a waitMethod we dont overwrite it + if (state->timeout == 0 && state->waitMethod == WAIT_METHOD_NONE) + state->waitMethod = WAIT_METHOD_FOREVER; + + i++; + } + else + valid = 0; + break; + } + + case CommandDemoMode: // Run in demo mode - no capture + { + // Demo mode might have a timing parameter + // so check if a) we have another parameter, b) its not the start of the next option + if (i + 1 < argc && argv[i+1][0] != '-') + { + if (sscanf(argv[i + 1], "%u", &state->demoInterval) == 1) + { + // TODO : What limits do we need for timeout? + if (state->demoInterval == 0) + state->demoInterval = 250; // ms + + state->demoMode = 1; + i++; + } + else + valid = 0; + } + else + { + state->demoMode = 1; + } + + break; + } + + case CommandFramerate: // fps to record + { + if (sscanf(argv[i + 1], "%u", &state->framerate) == 1) + { + // TODO : What limits do we need for fps 1 - 30 - 120?? + i++; + } + else + valid = 0; + break; + } + + case CommandPreviewEnc: + state->immutableInput = 0; + break; + + case CommandIntraPeriod: // key frame rate + { + if (sscanf(argv[i + 1], "%u", &state->intraperiod) == 1) + i++; + else + valid = 0; + break; + } + + case CommandQP: // quantisation parameter + { + if (sscanf(argv[i + 1], "%u", &state->quantisationParameter) == 1) + i++; + else + valid = 0; + break; + } + + case CommandProfile: // H264 profile + { + state->profile = raspicli_map_xref(argv[i + 1], profile_map, profile_map_size); + + if( state->profile == -1) + state->profile = MMAL_VIDEO_PROFILE_H264_HIGH; + + i++; + break; + } + + case CommandInlineHeaders: // H264 inline headers + { + state->bInlineHeaders = 1; + break; + } + + case CommandTimed: + { + if (sscanf(argv[i + 1], "%u,%u", &state->onTime, &state->offTime) == 2) + { + i++; + + if (state->onTime < 1000) + state->onTime = 1000; + + if (state->offTime < 1000) + state->offTime = 1000; + + state->waitMethod = WAIT_METHOD_TIMED; + } + else + valid = 0; + break; + } + + case CommandKeypress: + state->waitMethod = WAIT_METHOD_KEYPRESS; + break; + + case CommandSignal: + state->waitMethod = WAIT_METHOD_SIGNAL; + // Reenable the signal + signal(SIGUSR1, signal_handler); + break; + + case CommandInitialState: + { + state->bCapturing = raspicli_map_xref(argv[i + 1], initial_map, initial_map_size); + + if( state->bCapturing == -1) + state->bCapturing = 0; + + i++; + break; + } + + case CommandSegmentFile: // Segment file in to chunks of specified time + { + if (sscanf(argv[i + 1], "%u", &state->segmentSize) == 1) + { + // Must enable inline headers for this to work + state->bInlineHeaders = 1; + i++; + } + else + valid = 0; + break; + } + + case CommandSegmentWrap: // segment wrap value + { + if (sscanf(argv[i + 1], "%u", &state->segmentWrap) == 1) + i++; + else + valid = 0; + break; + } + + case CommandSegmentStart: // initial segment number + { + if((sscanf(argv[i + 1], "%u", &state->segmentNumber) == 1) && (!state->segmentWrap || (state->segmentNumber <= state->segmentWrap))) + i++; + else + valid = 0; + break; + } + + case CommandSplitWait: // split files on restart + { + // Must enable inline headers for this to work + state->bInlineHeaders = 1; + state->splitWait = 1; + break; + } + + case CommandCircular: + { + state->bCircularBuffer = 1; + break; + } + + case CommandIMV: // output filename + { + state->inlineMotionVectors = 1; + int len = strlen(argv[i + 1]); + if (len) + { + state->imv_filename = malloc(len + 1); + vcos_assert(state->imv_filename); + if (state->imv_filename) + strncpy(state->imv_filename, argv[i + 1], len+1); + i++; + } + else + valid = 0; + break; + } + case CommandCamSelect: //Select camera input port + { + if (sscanf(argv[i + 1], "%u", &state->cameraNum) == 1) + { + i++; + } + else + valid = 0; + break; + } + + case CommandSettings: + state->settings = 1; + break; + + case CommandSensorMode: + { + if (sscanf(argv[i + 1], "%u", &state->sensor_mode) == 1) + { + i++; + } + else + valid = 0; + break; + } + + case CommandIntraRefreshType: + { + state->config.intra_refresh_type = raspicli_map_xref(argv[i + 1], intra_refresh_map, intra_refresh_map_size); + i++; + break; + } + + default: + { + // Try parsing for any image specific parameters + // result indicates how many parameters were used up, 0,1,2 + // but we adjust by -1 as we have used one already + const char *second_arg = (i + 1 < argc) ? argv[i + 1] : NULL; + int parms_used = (raspicamcontrol_parse_cmdline(&state->camera_parameters, &argv[i][1], second_arg)); + + // Still unused, try preview options + if (!parms_used) + parms_used = raspipreview_parse_cmdline(&state->preview_parameters, &argv[i][1], second_arg); + + + // If no parms were used, this must be a bad parameters + if (!parms_used) + valid = 0; + else + i += parms_used - 1; + + break; + } + } + } + + if (!valid) + { + fprintf(stderr, "Invalid command line option (%s)\n", argv[i-1]); + return 1; + } + + // Always disable verbose if output going to stdout + if (state->filename && state->filename[0] == '-') + { + state->verbose = 0; + } + + return 0; +} + +/** + * Display usage information for the application to stdout + * + * @param app_name String to display as the application name + */ +static void display_valid_parameters(char *app_name) +{ + int i; + + fprintf(stderr, "Display camera output to display, and optionally saves an H264 capture at requested bitrate\n\n"); + fprintf(stderr, "\nusage: %s [options]\n\n", app_name); + + fprintf(stderr, "Image parameter commands\n\n"); + + raspicli_display_help(cmdline_commands, cmdline_commands_size); + + // Profile options + fprintf(stderr, "\n\nH264 Profile options :\n%s", profile_map[0].mode ); + + for (i=1;i<profile_map_size;i++) + { + fprintf(stderr, ",%s", profile_map[i].mode); + } + + fprintf(stderr, "\n"); + + // Intra refresh options + fprintf(stderr, "\n\nH264 Intra refresh options :\n%s", intra_refresh_map[0].mode ); + + for (i=1;i<intra_refresh_map_size;i++) + { + fprintf(stderr, ",%s", intra_refresh_map[i].mode); + } + + fprintf(stderr, "\n"); + + // Help for preview options + raspipreview_display_help(); + + // Now display any help information from the camcontrol code + raspicamcontrol_display_help(); + + fprintf(stderr, "\n"); + + return; +} +#endif + +/** + * buffer header callback function for camera control + * + * Callback will dump buffer data to the specific file + * + * @param port Pointer to port from which callback originated + * @param buffer mmal buffer header pointer + */ +static void camera_control_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +{ + if (buffer->cmd == MMAL_EVENT_PARAMETER_CHANGED) + { + MMAL_EVENT_PARAMETER_CHANGED_T *param = (MMAL_EVENT_PARAMETER_CHANGED_T *)buffer->data; + switch (param->hdr.id) { + case MMAL_PARAMETER_CAMERA_SETTINGS: + { + MMAL_PARAMETER_CAMERA_SETTINGS_T *settings = (MMAL_PARAMETER_CAMERA_SETTINGS_T*)param; + vcos_log_error("Exposure now %u, analog gain %u/%u, digital gain %u/%u", + settings->exposure, + settings->analog_gain.num, settings->analog_gain.den, + settings->digital_gain.num, settings->digital_gain.den); + vcos_log_error("AWB R=%u/%u, B=%u/%u", + settings->awb_red_gain.num, settings->awb_red_gain.den, + settings->awb_blue_gain.num, settings->awb_blue_gain.den + ); + } + break; + } + } + else if (buffer->cmd == MMAL_EVENT_ERROR) { + vcos_log_error("Camera control callback got an error"); + } else { + vcos_log_error("Received unexpected camera control callback event, 0x%08x", buffer->cmd); + } + + mmal_buffer_header_release(buffer); +} + +#if 0 +/** + * Open a file based on the settings in state + * + * @param state Pointer to state + */ +static FILE *open_filename(RASPIVID_STATE *pState) +{ + FILE *new_handle = NULL; + char *tempname = NULL, *filename = NULL; + + if (pState->segmentSize || pState->splitWait) + { + // Create a new filename string + asprintf(&tempname, pState->filename, pState->segmentNumber); + filename = tempname; + } + else + { + filename = pState->filename; + } + + if (filename) + new_handle = fopen(filename, "wb"); + + if (pState->verbose) + { + if (new_handle) + fprintf(stderr, "Opening output file \"%s\"\n", filename); + else + fprintf(stderr, "Failed to open new file \"%s\"\n", filename); + } + + if (tempname) + free(tempname); + + return new_handle; +} + +/** + * Open a file based on the settings in state + * + * This time for the imv output file + * + * @param state Pointer to state + */ +static FILE *open_imv_filename(RASPIVID_STATE *pState) +{ + FILE *new_handle = NULL; + char *tempname = NULL, *filename = NULL; + + if (pState->segmentSize || pState->splitWait) + { + // Create a new filename string + asprintf(&tempname, pState->imv_filename, pState->segmentNumber); + filename = tempname; + } + else + { + filename = pState->imv_filename; + } + + if (filename) + new_handle = fopen(filename, "wb"); + + if (pState->verbose) + { + if (new_handle) + fprintf(stderr, "Opening imv output file \"%s\"\n", filename); + else + fprintf(stderr, "Failed to open new imv file \"%s\"\n", filename); + } + + if (tempname) + free(tempname); + + return new_handle; +} +#endif + +/** + * Update any annotation data specific to the video. + * This simply passes on the setting from cli, or + * if application defined annotate requested, updates + * with the H264 parameters + * + * @param state Pointer to state control struct + * + */ +static void update_annotation_data(RASPIVID_STATE *state) +{ + RASPIVID_CONFIG *config = &state->config; + + // So, if we have asked for a application supplied string, set it to the H264 parameters + if (config->camera_parameters.enable_annotate & ANNOTATE_APP_TEXT) + { + char *text; + const char *refresh = raspicli_unmap_xref(config->intra_refresh_type, intra_refresh_map, intra_refresh_map_size); + + asprintf(&text, "%dk,%ff,%s,%d,%s", + config->bitrate / 1000, ((float)(config->fps_n) / config->fps_d), + refresh ? refresh : "(none)", + config->intraperiod, + raspicli_unmap_xref(config->profile, profile_map, profile_map_size)); + + raspicamcontrol_set_annotate(state->camera_component, config->camera_parameters.enable_annotate, text, + config->camera_parameters.annotate_text_size, + config->camera_parameters.annotate_text_colour, + config->camera_parameters.annotate_bg_colour); + + free(text); + } + else + { + raspicamcontrol_set_annotate(state->camera_component, config->camera_parameters.enable_annotate, + config->camera_parameters.annotate_string, + config->camera_parameters.annotate_text_size, + config->camera_parameters.annotate_text_colour, + config->camera_parameters.annotate_bg_colour); + } +} + + + +/** + * buffer header callback function for encoder + * + * Callback will dump buffer data to the specific file + * + * @param port Pointer to port from which callback originated + * @param buffer mmal buffer header pointer + */ +static void encoder_buffer_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +{ + PORT_USERDATA *pData = (PORT_USERDATA *)port->userdata; + RASPIVID_STATE *state = pData->state; + int64_t current_time; + + // All our segment times based on the receipt of the first encoder callback + if (state->base_time == -1) + state->base_time = vcos_getmicrosecs64()/1000; + + if (pData == NULL) + { + vcos_log_error("Received a encoder buffer callback with no state"); + // release buffer back to the pool + mmal_buffer_header_release(buffer); + return; + } + + current_time = vcos_getmicrosecs64()/1000; + if (state->base_time == -1) + state->base_time = current_time; + + // See if the second count has changed and we need to update any annotation + if (current_time/1000 != state->last_second) + { + update_annotation_data(state); + state->last_second = current_time/1000; + } + + /* Send buffer to GStreamer element for pushing to the pipeline */ + mmal_queue_put(state->encoded_buffer_q, buffer); +} + +GstFlowReturn +raspi_capture_fill_buffer(RASPIVID_STATE *state, GstBuffer **bufp, + GstClock *clock, GstClockTime base_time) +{ + RASPIVID_CONFIG *config = &state->config; + GstBuffer *buf; + MMAL_BUFFER_HEADER_T *buffer; + GstFlowReturn ret = GST_FLOW_ERROR; + /* No timestamps if no clockm or invalid PTS */ + GstClockTime gst_pts = GST_CLOCK_TIME_NONE; + + do { + buffer = mmal_queue_timedwait(state->encoded_buffer_q, 500); + // Work around a bug where mmal_queue_timedwait() might return + // immediately if the internal timeout time aligns exactly + // with a 1 second rollover boundary by checking errno. + if (errno == EINVAL) { + GST_WARNING ("Retrying mmal_queue_timedwait() due to spurious failure."); + continue; + } + } while (0); + + if (G_UNLIKELY(buffer == NULL)) { + return GST_FLOW_ERROR_TIMEOUT; + } + + if (G_LIKELY (config->useSTC && clock)) { + MMAL_PARAMETER_INT64_T param; + GstClockTime runtime; + + runtime = gst_clock_get_time (clock) - base_time; + + param.hdr.id = MMAL_PARAMETER_SYSTEM_TIME; + param.hdr.size = sizeof(param); + param.value = -1; + + mmal_port_parameter_get(state->encoder_output_port, ¶m.hdr); + + if (buffer->pts != -1 && param.value != -1 && param.value >= buffer->pts) { + /* Convert microsecond RPi TS to GStreamer clock: */ + GstClockTime offset = (param.value - buffer->pts) * 1000; + if (runtime >= offset) + gst_pts = runtime - offset; + } + GST_LOG ("Buf (uS) PTS %" G_GINT64_FORMAT " DTS %" G_GINT64_FORMAT + " STC %" G_GINT64_FORMAT " (latency %" G_GINT64_FORMAT + "uS) TS %" GST_TIME_FORMAT, + buffer->pts, buffer->dts, param.value, param.value - buffer->pts, + GST_TIME_ARGS (gst_pts)); + } + else { + GST_LOG ("use-stc=false. Not applying STC to buffer"); + } + + mmal_buffer_header_mem_lock(buffer); + buf = gst_buffer_new_allocate(NULL, buffer->length, NULL); + if (buf) { + if (config->useSTC) + GST_BUFFER_DTS(buf) = GST_BUFFER_PTS(buf) = gst_pts; + /* FIXME: Can we avoid copies and give MMAL our own buffers to fill? */ + gst_buffer_fill(buf, 0, buffer->data, buffer->length); + ret = GST_FLOW_OK; + } + + mmal_buffer_header_mem_unlock(buffer); + + *bufp = buf; + // release buffer back to the pool + mmal_buffer_header_release(buffer); + + // and send one back to the port (if still open) + if (state->encoder_output_port->is_enabled) + { + MMAL_STATUS_T status = MMAL_SUCCESS; + + buffer = mmal_queue_get(state->encoder_pool->queue); + if (buffer) + status = mmal_port_send_buffer(state->encoder_output_port, buffer); + + if (!buffer || status != MMAL_SUCCESS) { + vcos_log_error("Unable to return a buffer to the encoder port"); + ret = GST_FLOW_ERROR; + } + } + + return ret; +} + +/** + * Create the camera component, set up its ports + * + * @param state Pointer to state control struct + * + * @return MMAL_SUCCESS if all OK, something else otherwise + * + */ +static MMAL_STATUS_T create_camera_component(RASPIVID_STATE *state) +{ + MMAL_COMPONENT_T *camera = NULL; + MMAL_STATUS_T status; + RASPIVID_CONFIG *config = &state->config; + + /* Create the component */ + status = mmal_component_create(MMAL_COMPONENT_DEFAULT_CAMERA, &camera); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Failed to create camera component"); + goto error; + } + + MMAL_PARAMETER_INT32_T camera_num = + {{MMAL_PARAMETER_CAMERA_NUM, sizeof(camera_num)}, config->cameraNum}; + + status = mmal_port_parameter_set(camera->control, &camera_num.hdr); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Could not select camera : error %d", status); + goto error; + } + + if (!camera->output_num) + { + status = MMAL_ENOSYS; + vcos_log_error("Camera doesn't have output ports"); + goto error; + } + + status = mmal_port_parameter_set_uint32(camera->control, MMAL_PARAMETER_CAMERA_CUSTOM_SENSOR_CONFIG, config->sensor_mode); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Could not set sensor mode : error %d", status); + goto error; + } + + if (config->settings) + { + MMAL_PARAMETER_CHANGE_EVENT_REQUEST_T change_event_request = + {{MMAL_PARAMETER_CHANGE_EVENT_REQUEST, sizeof(MMAL_PARAMETER_CHANGE_EVENT_REQUEST_T)}, + MMAL_PARAMETER_CAMERA_SETTINGS, 1}; + + status = mmal_port_parameter_set(camera->control, &change_event_request.hdr); + if ( status != MMAL_SUCCESS ) + { + vcos_log_error("No camera settings events"); + } + } + + // Enable the camera, and tell it its control callback function + status = mmal_port_enable(camera->control, camera_control_callback); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to enable control port : error %d", status); + goto error; + } + + state->camera_component = camera; + + return status; + +error: + if (camera) + mmal_component_destroy(camera); + + return status; +} + +MMAL_STATUS_T +raspi_capture_set_format_and_start(RASPIVID_STATE *state) +{ + MMAL_COMPONENT_T *camera = NULL; + MMAL_STATUS_T status; + MMAL_ES_FORMAT_T *format; + MMAL_PORT_T *preview_port = NULL, *video_port = NULL, *still_port = NULL; + RASPIVID_CONFIG *config = &state->config; + + // set up the camera configuration + + MMAL_PARAMETER_CAMERA_CONFIG_T cam_config = + { + { MMAL_PARAMETER_CAMERA_CONFIG, sizeof(cam_config) }, + .max_stills_w = config->width, + .max_stills_h = config->height, + .stills_yuv422 = 0, + .one_shot_stills = 0, + .max_preview_video_w = config->width, + .max_preview_video_h = config->height, + .num_preview_video_frames = 3, + .stills_capture_circular_buffer_height = 0, + .fast_preview_resume = 0, + .use_stc_timestamp = MMAL_PARAM_TIMESTAMP_MODE_RAW_STC + }; + + camera = state->camera_component; + preview_port = camera->output[MMAL_CAMERA_PREVIEW_PORT]; + video_port = camera->output[MMAL_CAMERA_VIDEO_PORT]; + still_port = camera->output[MMAL_CAMERA_CAPTURE_PORT]; + + mmal_port_parameter_set(camera->control, &cam_config.hdr); + + // Now set up the port formats + + // Set the encode format on the Preview port + // HW limitations mean we need the preview to be the same size as the required recorded output + + format = preview_port->format; + + if(config->camera_parameters.shutter_speed > 6000000) + { + MMAL_PARAMETER_FPS_RANGE_T fps_range = {{MMAL_PARAMETER_FPS_RANGE, sizeof(fps_range)}, + { 50, 1000 }, {166, 1000}}; + mmal_port_parameter_set(preview_port, &fps_range.hdr); + } + else if(config->camera_parameters.shutter_speed > 1000000) + { + MMAL_PARAMETER_FPS_RANGE_T fps_range = {{MMAL_PARAMETER_FPS_RANGE, sizeof(fps_range)}, + { 166, 1000 }, {999, 1000}}; + mmal_port_parameter_set(preview_port, &fps_range.hdr); + } + + //enable dynamic framerate if necessary + if (config->camera_parameters.shutter_speed) + { + if (((float)(config->fps_n) / config->fps_d) > 1000000.0 / config->camera_parameters.shutter_speed) + { + config->fps_n = 0; + config->fps_d = 1; + GST_INFO ("Enabling dynamic frame rate to fulfil shutter speed requirement"); + } + } + + 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; + format->es->video.crop.y = 0; + format->es->video.crop.width = config->width; + format->es->video.crop.height = config->height; + format->es->video.frame_rate.num = config->fps_n; + format->es->video.frame_rate.den = config->fps_d; + + status = mmal_port_format_commit(preview_port); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("camera viewfinder format couldn't be set"); + goto error; + } + + // Set the encode format on the video port + format = video_port->format; + + if(config->camera_parameters.shutter_speed > 6000000) + { + MMAL_PARAMETER_FPS_RANGE_T fps_range = {{MMAL_PARAMETER_FPS_RANGE, sizeof(fps_range)}, + { 50, 1000 }, {166, 1000}}; + mmal_port_parameter_set(video_port, &fps_range.hdr); + } + else if(config->camera_parameters.shutter_speed > 1000000) + { + MMAL_PARAMETER_FPS_RANGE_T fps_range = {{MMAL_PARAMETER_FPS_RANGE, sizeof(fps_range)}, + { 167, 1000 }, {999, 1000}}; + mmal_port_parameter_set(video_port, &fps_range.hdr); + } + + /* 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; + format->es->video.crop.y = 0; + format->es->video.crop.width = config->width; + format->es->video.crop.height = config->height; + format->es->video.frame_rate.num = config->fps_n; + format->es->video.frame_rate.den = config->fps_d; + + status = mmal_port_format_commit(video_port); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("camera video format couldn't be set"); + goto error; + } + + // Ensure there are enough buffers to avoid dropping frames + if (video_port->buffer_num < VIDEO_OUTPUT_BUFFERS_NUM) + video_port->buffer_num = VIDEO_OUTPUT_BUFFERS_NUM; + + + // Set the encode format on the still port + + format = still_port->format; + + 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; + format->es->video.crop.y = 0; + format->es->video.crop.width = config->width; + format->es->video.crop.height = config->height; + format->es->video.frame_rate.num = 0; + format->es->video.frame_rate.den = 1; + + status = mmal_port_format_commit(still_port); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("camera still format couldn't be set"); + goto error; + } + + /* Ensure there are enough buffers to avoid dropping frames */ + if (still_port->buffer_num < VIDEO_OUTPUT_BUFFERS_NUM) + still_port->buffer_num = VIDEO_OUTPUT_BUFFERS_NUM; + + /* Enable component */ + status = mmal_component_enable(camera); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("camera component couldn't be enabled"); + goto error; + } + + raspicamcontrol_set_all_parameters(camera, &config->camera_parameters); + + update_annotation_data(state); + + if (config->verbose) + fprintf(stderr, "Camera component done\n"); + + return status; + +error: + if (camera) + mmal_component_disable(camera); + + return status; +} + +/** + * Destroy the camera component + * + * @param state Pointer to state control struct + * + */ +static void destroy_camera_component(RASPIVID_STATE *state) +{ + if (state->camera_component) + { + mmal_component_destroy(state->camera_component); + state->camera_component = NULL; + } +} + +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, ¶m.hdr); + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to request I-frame"); + return FALSE; + } + return TRUE; +} + +/** + * Create the encoder component, set up its ports + * + * @param state Pointer to state control struct + * + * @return MMAL_SUCCESS if all OK, something else otherwise + * + */ +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; + RASPIVID_CONFIG *config = &state->config; + + gboolean encoded_format = + (config->encoding == MMAL_ENCODING_H264 || + config->encoding == MMAL_ENCODING_MJPEG || + config->encoding == MMAL_ENCODING_JPEG); + + 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) + { + status = MMAL_ENOSYS; + vcos_log_error("Video encoder doesn't have input/output ports"); + goto error; + } + + encoder_input = encoder->input[0]; + encoder_output = encoder->output[0]; + + // We want same format on input and output + mmal_format_copy(encoder_output->format, encoder_input->format); + + // Configure desired encoding + encoder_output->format->encoding = config->encoding; + + encoder_output->format->bitrate = config->bitrate; + + 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; + + 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; + encoder_output->format->es->video.frame_rate.den = 1; + + // Commit the port changes to the output port + status = mmal_port_format_commit(encoder_output); + if (status != MMAL_SUCCESS) { + vcos_log_error("Unable to set format on video encoder output port"); + goto error; + } + + // Set the rate control parameter + if (0) + { + MMAL_PARAMETER_VIDEO_RATECONTROL_T param = {{ MMAL_PARAMETER_RATECONTROL, sizeof(param)}, MMAL_VIDEO_RATECONTROL_DEFAULT}; + status = mmal_port_parameter_set(encoder_output, ¶m.hdr); + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to set ratecontrol"); + goto error; + } + + } + + 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, ¶m.hdr); + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to set intraperiod"); + goto error; + } + } + + 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, ¶m.hdr); + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to set initial QP"); + goto error; + } + + MMAL_PARAMETER_UINT32_T param2 = {{ MMAL_PARAMETER_VIDEO_ENCODE_MIN_QUANT, sizeof(param)}, config->quantisationParameter}; + status = mmal_port_parameter_set(encoder_output, ¶m2.hdr); + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to set min QP"); + goto error; + } + + MMAL_PARAMETER_UINT32_T param3 = {{ MMAL_PARAMETER_VIDEO_ENCODE_MAX_QUANT, sizeof(param)}, config->quantisationParameter}; + status = mmal_port_parameter_set(encoder_output, ¶m3.hdr); + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to set max QP"); + goto error; + } + } + + if (config->encoding == MMAL_ENCODING_H264) + { + MMAL_PARAMETER_VIDEO_PROFILE_T param; + param.hdr.id = MMAL_PARAMETER_PROFILE; + param.hdr.size = sizeof(param); + + param.profile[0].profile = config->profile; + param.profile[0].level = MMAL_VIDEO_LEVEL_H264_4; // This is the only value supported + + status = mmal_port_parameter_set(encoder_output, ¶m.hdr); + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to set H264 profile"); + goto error; + } + } + + if (config->encoding != MMAL_ENCODING_JPEG) + { + if (mmal_port_parameter_set_boolean(encoder_input, MMAL_PARAMETER_VIDEO_IMMUTABLE_INPUT, config->immutableInput) != MMAL_SUCCESS) + { + vcos_log_error("Unable to set immutable input flag"); + // Continue rather than abort.. + } + + //set INLINE HEADER flag to generate SPS and PPS for every IDR if requested + if (mmal_port_parameter_set_boolean(encoder_output, MMAL_PARAMETER_VIDEO_ENCODE_INLINE_HEADER, config->bInlineHeaders) != MMAL_SUCCESS) + { + vcos_log_error("failed to set INLINE HEADER FLAG parameters"); + // Continue rather than abort.. + } + } + + if (config->encoding == MMAL_ENCODING_H264) + { + //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) + { + vcos_log_error("failed to set INLINE VECTORS parameters"); + // Continue rather than abort.. + } + + // Adaptive intra refresh settings + if (config->intra_refresh_type != -1) + { + MMAL_PARAMETER_VIDEO_INTRA_REFRESH_T param; + + /* Need to memset, apparently mmal_port_parameter_get() + * doesn't retrieve all parameters, causing random failures + * when we set it + */ + memset (¶m, 0, sizeof (MMAL_PARAMETER_VIDEO_INTRA_REFRESH_T)); + + param.hdr.id = MMAL_PARAMETER_VIDEO_INTRA_REFRESH; + param.hdr.size = sizeof(param); + + // Get first so we don't overwrite anything unexpectedly + status = mmal_port_parameter_get(encoder_output, ¶m.hdr); + + param.refresh_mode = config->intra_refresh_type; + + //if (state->intra_refresh_type == MMAL_VIDEO_INTRA_REFRESH_CYCLIC_MROWS) + // param.cir_mbs = 10; + + status = mmal_port_parameter_set(encoder_output, ¶m.hdr); + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to set H264 intra-refresh values"); + goto error; + } + } + } + + 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"); + // Continue after warning + } + +#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"); + // Continue after warning + } +#endif + } + + // Enable component + status = mmal_component_enable(encoder); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to enable video encoder component"); + goto error; + } + + state->encoder_component = encoder; + + if (config->verbose) + fprintf(stderr, "Encoder component done\n"); + + return status; + + error: + if (encoder) + mmal_component_destroy(encoder); + + state->encoder_component = NULL; + + return status; +} + +/** + * Destroy the encoder component + * + * @param state Pointer to state control struct + * + */ +static void destroy_encoder_component(RASPIVID_STATE *state) +{ + /* Empty the buffer header q */ + if (state->encoded_buffer_q) { + while (mmal_queue_length(state->encoded_buffer_q)) { + MMAL_BUFFER_HEADER_T *buffer = mmal_queue_get(state->encoded_buffer_q); + mmal_buffer_header_release(buffer); + } + } + + // Get rid of any port buffers first + if (state->encoder_pool) + { + mmal_port_pool_destroy(state->encoder_output_port, state->encoder_pool); + state->encoder_pool = NULL; + } + + if (state->encoder_component) { + + mmal_component_destroy(state->encoder_component); + state->encoder_component = NULL; + } +} + +/** + * Connect two specific ports together + * + * @param output_port Pointer the output port + * @param input_port Pointer the input port + * @param Pointer to a mmal connection pointer, reassigned if function successful + * @return Returns a MMAL_STATUS_T giving result of operation + * + */ +static MMAL_STATUS_T connect_ports(MMAL_PORT_T *output_port, MMAL_PORT_T *input_port, MMAL_CONNECTION_T **connection) +{ + MMAL_STATUS_T status; + + status = mmal_connection_create(connection, output_port, input_port, MMAL_CONNECTION_FLAG_TUNNELLING | MMAL_CONNECTION_FLAG_ALLOCATION_ON_INPUT); + + if (status == MMAL_SUCCESS) + { + status = mmal_connection_enable(*connection); + if (status != MMAL_SUCCESS) + mmal_connection_destroy(*connection); + } + + return status; +} + +/** + * Checks if specified port is valid and enabled, then disables it + * + * @param port Pointer the port + * + */ +static void check_disable_port(MMAL_PORT_T *port) +{ + if (port && port->is_enabled) + mmal_port_disable(port); +} + +void raspicapture_init() +{ + bcm_host_init(); + + // Register our application with the logging system + vcos_log_register("RaspiVid", VCOS_LOG_CATEGORY); +} + +RASPIVID_STATE * +raspi_capture_setup(RASPIVID_CONFIG *config) +{ + // Our main data storage vessel.. + RASPIVID_STATE *state; + + MMAL_STATUS_T status = MMAL_SUCCESS; + + /* Default everything to zero */ + state = calloc(1, sizeof(RASPIVID_STATE)); + + /* Apply passed in config */ + state->config = *config; + + /* Initialize timestamping */ + state->base_time = state->last_second = -1; + + /* So far, all we can do is create the camera component. Actual + * config and connection of encoders etc happens in _start() + */ + // OK, we have a nice set of parameters. Now set up our components + // We have three components. Camera, Preview and encoder. + + if ((status = create_camera_component(state)) != MMAL_SUCCESS) + { + vcos_log_error("%s: Failed to create camera component", __func__); + return NULL; + } + + if ((status = raspipreview_create(&state->preview_state, &config->preview_parameters)) != MMAL_SUCCESS) + { + vcos_log_error("%s: Failed to create preview component", __func__); + destroy_camera_component(state); + return NULL; + } + + state->encoded_buffer_q = mmal_queue_create(); + + return state; +} + +gboolean +raspi_capture_start(RASPIVID_STATE *state) +{ + MMAL_STATUS_T status = MMAL_SUCCESS; + RASPIVID_CONFIG *config = &state->config; + + MMAL_PORT_T *camera_preview_port = NULL; + MMAL_PORT_T *preview_input_port = NULL; + MMAL_PORT_T *encoder_input_port = NULL; + + 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) + { + 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"); + + if (config->preview_parameters.wantPreview ) + { + if (config->verbose) + { + fprintf(stderr, "Connecting camera preview port to preview input port\n"); + fprintf(stderr, "Starting video preview\n"); + } + + // Connect camera to preview + status = connect_ports(camera_preview_port, preview_input_port, &state->preview_connection); + if (status != MMAL_SUCCESS) + { + vcos_log_error("%s: Failed to connect camera to preview", __func__); + return FALSE; + } + } + + 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; + } + } + + // Set up our userdata - this is passed though to the callback where we need the information. + state->callback_data.state = state; + state->callback_data.abort = 0; + + state->encoder_output_port->userdata = (struct MMAL_PORT_USERDATA_T *)&state->callback_data; + + if (config->verbose) + fprintf(stderr, "Enabling encoder output port\n"); + + // Enable the encoder output port and tell it its callback function + status = mmal_port_enable(state->encoder_output_port, encoder_buffer_callback); + if (status != MMAL_SUCCESS) + { + vcos_log_error("Failed to setup encoder output"); + goto error; + } + + if (config->demoMode) + { + // Run for the user specific time.. + int num_iterations = config->timeout / config->demoInterval; + int i; + + if (config->verbose) + fprintf(stderr, "Running in demo mode\n"); + + for (i=0;config->timeout == 0 || i<num_iterations;i++) + { + raspicamcontrol_cycle_test(state->camera_component); + vcos_sleep(state->config.demoInterval); + } + } + + if (config->verbose) + fprintf(stderr, "Starting video capture\n"); + + if (mmal_port_parameter_set_boolean(state->camera_video_port, MMAL_PARAMETER_CAPTURE, 1) != MMAL_SUCCESS) + { + goto error; + } + + // Send all the buffers to the encoder output port + { + int num = mmal_queue_length(state->encoder_pool->queue); + int q; + for (q=0;q<num;q++) + { + MMAL_BUFFER_HEADER_T *buffer = mmal_queue_get(state->encoder_pool->queue); + + if (!buffer) + vcos_log_error("Unable to get a required buffer %d from pool queue", q); + + if (mmal_port_send_buffer(state->encoder_output_port, buffer)!= MMAL_SUCCESS) + vcos_log_error("Unable to send a buffer to encoder output port (%d)", q); + + } + } + + // Now wait until we need to stop. Whilst waiting we do need to check to see if we have aborted (for example + // out of storage space) + // Going to check every ABORT_INTERVAL milliseconds + +#if 0 + for (wait = 0; config->timeout == 0 || wait < config->timeout; wait+= ABORT_INTERVAL) + { + vcos_sleep(ABORT_INTERVAL); + if (state->callback_data.abort) + break; + } + + if (config->verbose) + fprintf(stderr, "Finished capture\n"); +#endif + + return (status == MMAL_SUCCESS); + +error: + raspi_capture_stop(state); + + if (status != MMAL_SUCCESS) { + mmal_status_to_int(status); + raspicamcontrol_check_configuration(128); + } + + return FALSE; +} + +void +raspi_capture_stop(RASPIVID_STATE *state) +{ + RASPIVID_CONFIG *config = &state->config; + + if (config->verbose) + fprintf(stderr, "Closing down\n"); + + if (config->preview_parameters.wantPreview ) + mmal_connection_destroy(state->preview_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); + } +} + +void +raspi_capture_free(RASPIVID_STATE *state) +{ + RASPIVID_CONFIG *config = &state->config; + + // Can now close our file. Note disabling ports may flush buffers which causes + // problems if we have already closed the file! + if (state->output_file && state->output_file != stdout) + fclose(state->output_file); + + /* Disable components */ + if (state->encoder_component) + mmal_component_disable(state->encoder_component); + + if (state->preview_state.preview_component) + mmal_component_disable(state->preview_state.preview_component); + + if (state->camera_component) + mmal_component_disable(state->camera_component); + + destroy_encoder_component(state); + raspipreview_destroy(&state->preview_state); + destroy_camera_component(state); + + if (state->encoded_buffer_q) { + mmal_queue_destroy(state->encoded_buffer_q); + state->encoded_buffer_q = NULL; + } + + if (config->verbose) + fprintf(stderr, "Close down completed, all components disconnected, disabled and destroyed\n\n"); + + free(state); +} + +void +raspi_capture_update_config (RASPIVID_STATE *state, RASPIVID_CONFIG *config, gboolean dynamic) +{ + MMAL_STATUS_T status; + RASPICAM_CAMERA_PARAMETERS *params = &config->camera_parameters; + MMAL_COMPONENT_T *camera = state->camera_component; + + /* Store the new config */ + state->config = *config; + if (!dynamic) + return; + + 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]; + + status = mmal_port_parameter_set_uint32(encoder_output, MMAL_PARAMETER_VIDEO_BIT_RATE, config->bitrate); + if (status != MMAL_SUCCESS) + vcos_log_warn("Unable to change bitrate dynamically"); + + { + MMAL_PARAMETER_UINT32_T param = {{ MMAL_PARAMETER_INTRAPERIOD, sizeof(param)}, config->intraperiod}; + status = mmal_port_parameter_set(encoder_output, ¶m.hdr); + if (status != MMAL_SUCCESS) + vcos_log_warn("Unable to change intraperiod dynamically"); + } + +#if 0 /* not dynamically change-able */ + { + MMAL_PARAMETER_UINT32_T param = {{ MMAL_PARAMETER_VIDEO_ENCODE_INITIAL_QUANT, sizeof(param)}, config->quantisationParameter}; + status = mmal_port_parameter_set(encoder_output, ¶m.hdr); + if (status != MMAL_SUCCESS) + vcos_log_warn("Unable to change Initial Quantisation Parameter dynamically"); + + MMAL_PARAMETER_UINT32_T param2 = {{ MMAL_PARAMETER_VIDEO_ENCODE_MIN_QUANT, sizeof(param)}, config->quantisationParameter}; + status = mmal_port_parameter_set(encoder_output, ¶m2.hdr); + if (status != MMAL_SUCCESS) + vcos_log_warn("Unable to change Minimum Quantisation Parameter dynamically"); + + MMAL_PARAMETER_UINT32_T param3 = {{ MMAL_PARAMETER_VIDEO_ENCODE_MAX_QUANT, sizeof(param)}, config->quantisationParameter}; + status = mmal_port_parameter_set(encoder_output, ¶m3.hdr); + if (status != MMAL_SUCCESS) + vcos_log_warn("Unable to change Maximum Quantisation Parameter dynamically"); + } + + { + // Adaptive intra refresh settings + MMAL_PARAMETER_VIDEO_INTRA_REFRESH_T param; + param.hdr.id = MMAL_PARAMETER_VIDEO_INTRA_REFRESH; + param.hdr.size = sizeof(param); + + // Get first so we don't overwrite anything unexpectedly + status = mmal_port_parameter_get(encoder_output, ¶m.hdr); + if (state != MMAL_SUCCESS) { + /* Need to memset, apparently mmal_port_parameter_get() + * doesn't retrieve all parameters, causing random failures + * when we set it. On older firmware the get fails. + */ + memset (¶m, 0, sizeof (MMAL_PARAMETER_VIDEO_INTRA_REFRESH_T)); + } + param.refresh_mode = config->intra_refresh_type; + + status = mmal_port_parameter_set(encoder_output, ¶m.hdr); + if (status != MMAL_SUCCESS) + vcos_log_warn("Unable to set H264 intra-refresh values dynamically"); + } +#endif + } + if (config->change_flags & PROP_CHANGE_PREVIEW) { + /* Preview settings or fullscreen */ + status = raspipreview_update_config (&state->preview_state, + &config->preview_parameters); + if (status != MMAL_SUCCESS) + vcos_log_warn("Unable to change preview config dynamically"); + } + if (config->change_flags & PROP_CHANGE_COLOURBALANCE) { + raspicamcontrol_set_saturation(camera, params->saturation); + raspicamcontrol_set_sharpness(camera, params->sharpness); + raspicamcontrol_set_contrast(camera, params->contrast); + raspicamcontrol_set_brightness(camera, params->brightness); + } + if (config->change_flags & PROP_CHANGE_SENSOR_SETTINGS) { + /* ISO, EXPOSURE, SHUTTER, DRC, Sensor Mode */ + raspicamcontrol_set_ISO(camera, params->ISO); + raspicamcontrol_set_exposure_compensation(camera, params->exposureCompensation); + raspicamcontrol_set_exposure_mode(camera, params->exposureMode); + raspicamcontrol_set_metering_mode(camera, params->exposureMeterMode); + raspicamcontrol_set_shutter_speed(camera, params->shutter_speed); + raspicamcontrol_set_DRC(camera, params->drc_level); + + /* Can we change sensor mode on the fly? Disable if not */ + status = mmal_port_parameter_set_uint32(camera->control, + MMAL_PARAMETER_CAMERA_CUSTOM_SENSOR_CONFIG, config->sensor_mode); + if (status != MMAL_SUCCESS) + vcos_log_warn("Unable to change sensor mode dynamically"); + } + if (config->change_flags & PROP_CHANGE_VIDEO_STABILISATION) { + raspicamcontrol_set_video_stabilisation(camera, params->videoStabilisation); + } + if (config->change_flags & PROP_CHANGE_AWB) { + raspicamcontrol_set_awb_mode(camera, params->awbMode); + raspicamcontrol_set_awb_gains(camera, params->awb_gains_r, params->awb_gains_b); + } + if (config->change_flags & PROP_CHANGE_IMAGE_COLOUR_EFFECT) { + raspicamcontrol_set_imageFX(camera, params->imageEffect); + raspicamcontrol_set_colourFX(camera, ¶ms->colourEffects); + } + if (config->change_flags & PROP_CHANGE_ORIENTATION) { + raspicamcontrol_set_rotation(camera, params->rotation); + raspicamcontrol_set_flips(camera, params->hflip, params->vflip); + } + if (config->change_flags & PROP_CHANGE_ROI) { + raspicamcontrol_set_ROI(camera, params->roi); + } + if (config->change_flags & PROP_CHANGE_ANNOTATION) + update_annotation_data(state); +} diff --git a/sys/rpicamsrc/RaspiCapture.h b/sys/rpicamsrc/RaspiCapture.h new file mode 100644 index 000000000..71e79b26e --- /dev/null +++ b/sys/rpicamsrc/RaspiCapture.h @@ -0,0 +1,143 @@ +/* + * GStreamer + * Copyright (C) 2013-2015 Jan Schmidt <jan@centricular.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Alternatively, the contents of this file may be used under the + * GNU Lesser General Public License Version 2.1 (the "LGPL"), in + * which case the following provisions apply instead of the ones + * mentioned above: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __RASPICAPTURE_H__ +#define __RASPICAPTURE_H__ + +#include <glib.h> +#include <inttypes.h> + +#include "interface/mmal/mmal_common.h" +#include "interface/mmal/mmal_types.h" +#include "interface/mmal/mmal_parameters_camera.h" +#include "interface/mmal/mmal_component.h" +#include "RaspiCamControl.h" +#include "RaspiPreview.h" + +#define RPICAMSRC_MAX_FPS 1000 + +GST_DEBUG_CATEGORY_EXTERN (gst_rpi_cam_src_debug); +#define GST_CAT_DEFAULT gst_rpi_cam_src_debug + +#undef fprintf +#define fprintf(f,...) GST_LOG(__VA_ARGS__) +#undef vcos_log_error +#define vcos_log_error GST_ERROR +#undef vcos_log_warn +#define vcos_log_warn GST_WARNING + +#define GST_FLOW_ERROR_TIMEOUT GST_FLOW_CUSTOM_ERROR + +G_BEGIN_DECLS + +typedef enum +{ + PROP_CHANGE_ENCODING = (1 << 0), /* BITRATE or QUANT or KEY Interval, intra refresh */ + PROP_CHANGE_PREVIEW = (1 << 1), /* Preview opacity or fullscreen */ + PROP_CHANGE_COLOURBALANCE = (1 << 2), + PROP_CHANGE_SENSOR_SETTINGS = (1 << 3), /* ISO, EXPOSURE, SHUTTER, DRC, Sensor Mode */ + PROP_CHANGE_VIDEO_STABILISATION = (1 << 4), + PROP_CHANGE_AWB = (1 << 5), + PROP_CHANGE_IMAGE_COLOUR_EFFECT = (1 << 6), + PROP_CHANGE_ORIENTATION = (1 << 7), + PROP_CHANGE_ROI = (1 << 8), + PROP_CHANGE_ANNOTATION = (1 << 9) +} RpiPropChangeFlags; + +/** Structure containing all state information for the current run + */ +typedef struct +{ + RpiPropChangeFlags change_flags; + + int verbose; /// !0 if want detailed run information + + int timeout; /// Time taken before frame is grabbed and app then shuts down. Units are milliseconds + int width; /// Requested width of image + int height; /// requested height of image + int bitrate; /// Requested bitrate + int fps_n; /// Requested frame rate (fps) numerator + int fps_d; /// Requested frame rate (fps) denominator + int intraperiod; /// Intra-refresh period (key frame rate) + int quantisationParameter; /// Quantisation parameter - quality. Set bitrate 0 and set this for variable bitrate + int bInlineHeaders; /// Insert inline headers to stream (SPS, PPS) + int demoMode; /// Run app in demo mode + int demoInterval; /// Interval between camera settings changes + int immutableInput; /// Flag to specify whether encoder works in place or creates a new buffer. Result is preview can display either + /// the camera output or the encoder output (with compression artifacts) + int profile; /// H264 profile to use for encoding + RASPIPREVIEW_PARAMETERS preview_parameters; /// Preview setup parameters + RASPICAM_CAMERA_PARAMETERS camera_parameters; /// Camera setup parameters + + int inlineMotionVectors; /// Encoder outputs inline Motion Vectors + + int cameraNum; /// Camera number + 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; + + int useSTC; +} RASPIVID_CONFIG; + +typedef struct RASPIVID_STATE_T RASPIVID_STATE; + +void raspicapture_init(); +void raspicapture_default_config(RASPIVID_CONFIG *config); +RASPIVID_STATE *raspi_capture_setup(RASPIVID_CONFIG *config); +gboolean raspi_capture_start(RASPIVID_STATE *state); +void raspi_capture_update_config (RASPIVID_STATE *state, + RASPIVID_CONFIG *config, gboolean dynamic); +GstFlowReturn raspi_capture_fill_buffer(RASPIVID_STATE *state, GstBuffer **buf, + GstClock *clock, GstClockTime base_time); +void raspi_capture_stop(RASPIVID_STATE *state); +void raspi_capture_free(RASPIVID_STATE *state); +gboolean raspi_capture_request_i_frame(RASPIVID_STATE *state); + +G_END_DECLS + +#endif diff --git a/sys/rpicamsrc/RaspiPreview.c b/sys/rpicamsrc/RaspiPreview.c new file mode 100644 index 000000000..032f0f16a --- /dev/null +++ b/sys/rpicamsrc/RaspiPreview.c @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2013-2015 Jan Schmidt <jan@centricular.com> +Portions: +Copyright (c) 2013, Broadcom Europe Ltd +Copyright (c) 2013, James Hughes +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <memory.h> +#include <gst/gst.h> + +#include "interface/vcos/vcos.h" + +#include "interface/mmal/mmal.h" +#include "interface/mmal/mmal_logging.h" +#include "interface/mmal/mmal_buffer.h" +#include "interface/mmal/util/mmal_util.h" +#include "interface/mmal/util/mmal_util_params.h" +#include "interface/mmal/util/mmal_default_components.h" +#include "interface/mmal/util/mmal_connection.h" + +#include "RaspiPreview.h" +#include "RaspiCapture.h" + +#if 0 +#define CommandPreview 1 +#define CommandFullScreen 2 +#define CommandOpacity 3 +#define CommandDisablePreview 4 + +static COMMAND_LIST cmdline_commands[] = +{ + { CommandPreview, "-preview", "p", "Preview window settings <'x,y,w,h'>", 1 }, + { CommandFullScreen, "-fullscreen", "f", "Fullscreen preview mode", 0 }, + { CommandOpacity, "-opacity", "op", "Preview window opacity (0-255)", 1}, + { CommandDisablePreview,"-nopreview", "n", "Do not display a preview window", 0}, +}; + +static int cmdline_commands_size = sizeof(cmdline_commands) / sizeof(cmdline_commands[0]); +#endif + +/** + * Create the preview component, set up its ports + * + * @param state Pointer to state control struct + * + * @return MMAL_SUCCESS if all OK, something else otherwise + * + */ +MMAL_STATUS_T raspipreview_create(RASPIPREVIEW_STATE *state, + RASPIPREVIEW_PARAMETERS *config) +{ + MMAL_COMPONENT_T *preview = 0; + MMAL_STATUS_T status; + + state->havePreview = config->wantPreview; + + if (!config->wantPreview) + { + // No preview required, so create a null sink component to take its place + status = mmal_component_create("vc.null_sink", &preview); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to create null sink component"); + goto error; + } + + state->preview_component = preview; + } + else + { + status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, + &preview); + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to create preview component"); + goto error; + } + + if (!preview->input_num) + { + status = MMAL_ENOSYS; + vcos_log_error("No input ports found on component"); + goto error; + } + + state->preview_component = preview; + + raspipreview_update_config (state, config); + if (status != MMAL_SUCCESS && status != MMAL_ENOSYS) + { + vcos_log_error("unable to set preview port parameters (%u)", status); + goto error; + } + } + + /* Enable component */ + status = mmal_component_enable(preview); + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to enable preview/null sink component (%u)", status); + goto error; + } + + return status; + +error: + if (preview) { + mmal_component_destroy(preview); + state->preview_component = NULL; + } + + return status; +} + +MMAL_STATUS_T +raspipreview_update_config (RASPIPREVIEW_STATE *state, + RASPIPREVIEW_PARAMETERS *config) +{ + MMAL_COMPONENT_T *preview = state->preview_component; + MMAL_PORT_T *preview_port = NULL; + MMAL_DISPLAYREGION_T param; + MMAL_STATUS_T status; + + /* Can't update props on the null preview component */ + if (state->havePreview == 0) + return MMAL_SUCCESS; + + preview_port = preview->input[0]; + + param.hdr.id = MMAL_PARAMETER_DISPLAYREGION; + param.hdr.size = sizeof(MMAL_DISPLAYREGION_T); + + param.set = MMAL_DISPLAY_SET_LAYER; + param.layer = PREVIEW_LAYER; + + param.set |= MMAL_DISPLAY_SET_ALPHA; + param.alpha = config->opacity; + + if (config->wantFullScreenPreview) + { + param.set |= MMAL_DISPLAY_SET_FULLSCREEN; + param.fullscreen = 1; + } + else + { + param.set |= (MMAL_DISPLAY_SET_DEST_RECT | MMAL_DISPLAY_SET_FULLSCREEN); + param.fullscreen = 0; + param.dest_rect = config->previewWindow; + } + + status = mmal_port_parameter_set(preview_port, ¶m.hdr); + if (status == MMAL_ENOSYS) + status = MMAL_SUCCESS; + + return status; +} + +/** + * Destroy the preview component + * + * @param state Pointer to state control struct + * + */ +void raspipreview_destroy(RASPIPREVIEW_STATE *state) +{ + if (state->preview_component) + { + mmal_component_destroy(state->preview_component); + state->preview_component = NULL; + } +} + +/** + * Assign set of default parameters to the passed in parameter block + * + * @param state Pointer to parameter block + * + */ +void raspipreview_set_defaults(RASPIPREVIEW_PARAMETERS *config) +{ + config->wantPreview = 1; + config->wantFullScreenPreview = 1; + config->opacity = 255; + config->previewWindow.x = 0; + config->previewWindow.y = 0; + config->previewWindow.width = 1024; + config->previewWindow.height = 768; +} + +/** + * Dump parameters as human readable to stdout + * + * @param state Pointer to parameter block + * + */ +void raspipreview_dump_parameters(RASPIPREVIEW_PARAMETERS *config) +{ + fprintf(stderr, "Preview %s, Full screen %s\n", config->wantPreview ? "Yes" : "No", + config->wantFullScreenPreview ? "Yes" : "No"); + + fprintf(stderr, "Preview window %d,%d,%d,%d\nOpacity %d\n", config->previewWindow.x, + config->previewWindow.y, config->previewWindow.width, + config->previewWindow.height, config->opacity); +}; + +#if 0 +/** + * Parse a possible command pair - command and parameter + * @param arg1 Command + * @param arg2 Parameter (could be NULL) + * @return How many parameters were used, 0,1,2 + */ +int raspipreview_parse_cmdline(RASPIPREVIEW_PARAMETERS *params, const char *arg1, const char *arg2) +{ + int command_id, used = 0, num_parameters; + + if (!arg1) + return 0; + + command_id = raspicli_get_command_id(cmdline_commands, cmdline_commands_size, arg1, &num_parameters); + + // If invalid command, or we are missing a parameter, drop out + if (command_id==-1 || (command_id != -1 && num_parameters > 0 && arg2 == NULL)) + return 0; + + switch (command_id) + { + case CommandPreview: // Preview window + { + int tmp; + + params->wantPreview = 1; + + tmp = sscanf(arg2, "%d,%d,%d,%d", + ¶ms->previewWindow.x, ¶ms->previewWindow.y, + ¶ms->previewWindow.width, ¶ms->previewWindow.height); + + // Failed to get any window parameters, so revert to full screen + if (tmp == 0) + params->wantFullScreenPreview = 1; + else + params->wantFullScreenPreview = 0; + + used = 2; + + break; + } + + case CommandFullScreen: // Want full screen preview mode (overrides display rect) + params->wantPreview = 1; + params->wantFullScreenPreview = 1; + + used = 1; + break; + + case CommandOpacity: // Define preview window opacity + if (sscanf(arg2, "%u", ¶ms->opacity) != 1) + params->opacity = 255; + else + used = 2; + break; + + case CommandDisablePreview: // Turn off preview output + params->wantPreview = 0; + used = 1; + break; + } + + return used; +} + +/** + * Display help for command line options + */ +void raspipreview_display_help() +{ + fprintf(stderr, "\nPreview parameter commands\n\n"); + raspicli_display_help(cmdline_commands, cmdline_commands_size); +} +#endif diff --git a/sys/rpicamsrc/RaspiPreview.h b/sys/rpicamsrc/RaspiPreview.h new file mode 100644 index 000000000..f5e14daf4 --- /dev/null +++ b/sys/rpicamsrc/RaspiPreview.h @@ -0,0 +1,75 @@ +/* +Copyright (c) 2013, Broadcom Europe Ltd +Copyright (c) 2013, James Hughes +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef RASPIPREVIEW_H_ +#define RASPIPREVIEW_H_ + +/// Layer that preview window should be displayed on +#define PREVIEW_LAYER 2 + +// Frames rates of 0 implies variable, but denominator needs to be 1 to prevent div by 0 +#define PREVIEW_FRAME_RATE_NUM 0 +#define PREVIEW_FRAME_RATE_DEN 1 + +#define FULL_RES_PREVIEW_FRAME_RATE_NUM 0 +#define FULL_RES_PREVIEW_FRAME_RATE_DEN 1 + +#define FULL_FOV_PREVIEW_16x9_X 1280 +#define FULL_FOV_PREVIEW_16x9_Y 720 + +#define FULL_FOV_PREVIEW_4x3_X 1296 +#define FULL_FOV_PREVIEW_4x3_Y 972 + +#define FULL_FOV_PREVIEW_FRAME_RATE_NUM 0 +#define FULL_FOV_PREVIEW_FRAME_RATE_DEN 1 + +typedef struct +{ + MMAL_COMPONENT_T *preview_component; /// Pointer to the created preview display component + int havePreview; /// component is preview, else null sink +} RASPIPREVIEW_STATE; + +typedef struct +{ + int wantPreview; /// Display a preview + int wantFullScreenPreview; /// 0 is use previewRect, non-zero to use full screen + int opacity; /// Opacity of window - 0 = transparent, 255 = opaque + MMAL_RECT_T previewWindow; /// Destination rectangle for the preview window. +} RASPIPREVIEW_PARAMETERS; + +MMAL_STATUS_T raspipreview_create(RASPIPREVIEW_STATE *state, + RASPIPREVIEW_PARAMETERS *config); +void raspipreview_destroy(RASPIPREVIEW_STATE *state); +void raspipreview_set_defaults(RASPIPREVIEW_PARAMETERS *config); +void raspipreview_dump_parameters(RASPIPREVIEW_PARAMETERS *config); +int raspipreview_parse_cmdline(RASPIPREVIEW_PARAMETERS *config, const char *arg1, const char *arg2); +void raspipreview_display_help(); +MMAL_STATUS_T raspipreview_update_config (RASPIPREVIEW_STATE *state, + RASPIPREVIEW_PARAMETERS *config); + +#endif /* RASPIPREVIEW_H_ */ diff --git a/sys/rpicamsrc/RaspiStill.c b/sys/rpicamsrc/RaspiStill.c new file mode 100644 index 000000000..32747b866 --- /dev/null +++ b/sys/rpicamsrc/RaspiStill.c @@ -0,0 +1,1516 @@ +/* + * Copyright (c) 2013 Jan Schmidt <jan@centricular.com> +Portions: +Copyright (c) 2013, Broadcom Europe Ltd +Copyright (c) 2013, James Hughes +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * \file RaspiStill.c + * Command line program to capture a still frame and encode it to file. + * Also optionally display a preview/viewfinder of current camera input. + * + * \date 31 Jan 2013 + * \Author: James Hughes + * + * Description + * + * 3 components are created; camera, preview and JPG encoder. + * Camera component has three ports, preview, video and stills. + * This program connects preview and stills to the preview and jpg + * encoder. Using mmal we don't need to worry about buffers between these + * components, but we do need to handle buffers from the encoder, which + * are simply written straight to the file in the requisite buffer callback. + * + * We use the RaspiCamControl code to handle the specific camera settings. + */ + +// We use some GNU extensions (asprintf, basename) +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <memory.h> +#include <unistd.h> +#include <errno.h> +#include <sysexits.h> + +#define VERSION_STRING "v1.3.2" + +#include "bcm_host.h" +#include "interface/vcos/vcos.h" + +#include "interface/mmal/mmal.h" +#include "interface/mmal/mmal_logging.h" +#include "interface/mmal/mmal_buffer.h" +#include "interface/mmal/util/mmal_util.h" +#include "interface/mmal/util/mmal_util_params.h" +#include "interface/mmal/util/mmal_default_components.h" +#include "interface/mmal/util/mmal_connection.h" + + +#include "RaspiCamControl.h" +#include "RaspiPreview.h" +#include "RaspiCLI.h" + +#include <semaphore.h> + +/// Camera number to use - we only have one camera, indexed from 0. +#define CAMERA_NUMBER 0 + +// Standard port setting for the camera component +#define MMAL_CAMERA_PREVIEW_PORT 0 +#define MMAL_CAMERA_VIDEO_PORT 1 +#define MMAL_CAMERA_CAPTURE_PORT 2 + + +// Stills format information +#define STILLS_FRAME_RATE_NUM 15 +#define STILLS_FRAME_RATE_DEN 1 + +/// Video render needs at least 2 buffers. +#define VIDEO_OUTPUT_BUFFERS_NUM 3 + +#define MAX_USER_EXIF_TAGS 32 +#define MAX_EXIF_PAYLOAD_LENGTH 128 + +int mmal_status_to_int(MMAL_STATUS_T status); + +/** Structure containing all state information for the current run + */ +typedef struct +{ + int timeout; /// Time taken before frame is grabbed and app then shuts down. Units are milliseconds + int width; /// Requested width of image + int height; /// requested height of image + int quality; /// JPEG quality setting (1-100) + int wantRAW; /// Flag for whether the JPEG metadata also contains the RAW bayer image + char *filename; /// filename of output file + char *linkname; /// filename of output file + MMAL_PARAM_THUMBNAIL_CONFIG_T thumbnailConfig; + int verbose; /// !0 if want detailed run information + int demoMode; /// Run app in demo mode + int demoInterval; /// Interval between camera settings changes + MMAL_FOURCC_T encoding; /// Encoding to use for the output file. + const char *exifTags[MAX_USER_EXIF_TAGS]; /// Array of pointers to tags supplied from the command line + int numExifTags; /// Number of supplied tags + int timelapse; /// Delay between each picture in timelapse mode. If 0, disable timelapse + int fullResPreview; /// If set, the camera preview port runs at capture resolution. Reduces fps. + + RASPIPREVIEW_PARAMETERS preview_parameters; /// Preview setup parameters + RASPICAM_CAMERA_PARAMETERS camera_parameters; /// Camera setup parameters + + MMAL_COMPONENT_T *camera_component; /// Pointer to the camera component + MMAL_COMPONENT_T *encoder_component; /// Pointer to the encoder component + MMAL_COMPONENT_T *null_sink_component; /// Pointer to the null sink component + MMAL_CONNECTION_T *preview_connection; /// Pointer to the connection from camera to preview + MMAL_CONNECTION_T *encoder_connection; /// Pointer to the connection from camera to encoder + + MMAL_POOL_T *encoder_pool; /// Pointer to the pool of buffers used by encoder output port + +} RASPISTILL_STATE; + +/** Struct used to pass information in encoder port userdata to callback + */ +typedef struct +{ + FILE *file_handle; /// File handle to write buffer data to. + VCOS_SEMAPHORE_T complete_semaphore; /// semaphore which is posted when we reach end of frame (indicates end of capture or fault) + RASPISTILL_STATE *pstate; /// pointer to our state in case required in callback +} PORT_USERDATA; + +static void display_valid_parameters(char *app_name); +static void store_exif_tag(RASPISTILL_STATE *state, const char *exif_tag); + +/// Comamnd ID's and Structure defining our command line options +#define CommandHelp 0 +#define CommandWidth 1 +#define CommandHeight 2 +#define CommandQuality 3 +#define CommandRaw 4 +#define CommandOutput 5 +#define CommandVerbose 6 +#define CommandTimeout 7 +#define CommandThumbnail 8 +#define CommandDemoMode 9 +#define CommandEncoding 10 +#define CommandExifTag 11 +#define CommandTimelapse 12 +#define CommandFullResPreview 13 +#define CommandLink 14 + +static COMMAND_LIST cmdline_commands[] = +{ + { CommandHelp, "-help", "?", "This help information", 0 }, + { CommandWidth, "-width", "w", "Set image width <size>", 1 }, + { CommandHeight, "-height", "h", "Set image height <size>", 1 }, + { CommandQuality, "-quality", "q", "Set jpeg quality <0 to 100>", 1 }, + { CommandRaw, "-raw", "r", "Add raw bayer data to jpeg metadata", 0 }, + { CommandOutput, "-output", "o", "Output filename <filename> (to write to stdout, use '-o -'). If not specified, no file is saved", 1 }, + { CommandLink, "-latest", "l", "Link latest complete image to filename <filename>", 1}, + { CommandVerbose, "-verbose", "v", "Output verbose information during run", 0 }, + { CommandTimeout, "-timeout", "t", "Time (in ms) before takes picture and shuts down (if not specified, set to 5s)", 1 }, + { CommandThumbnail,"-thumb", "th", "Set thumbnail parameters (x:y:quality)", 1}, + { CommandDemoMode,"-demo", "d", "Run a demo mode (cycle through range of camera options, no capture)", 0}, + { CommandEncoding,"-encoding", "e", "Encoding to use for output file (jpg, bmp, gif, png)", 1}, + { CommandExifTag, "-exif", "x", "EXIF tag to apply to captures (format as 'key=value')", 1}, + { CommandTimelapse,"-timelapse", "tl", "Timelapse mode. Takes a picture every <t>ms", 1}, + { CommandFullResPreview,"-fullpreview", "fp", "Run the preview using the still capture resolution (may reduce preview fps)", 0}, +}; + +static int cmdline_commands_size = sizeof(cmdline_commands) / sizeof(cmdline_commands[0]); + +static struct +{ + char *format; + MMAL_FOURCC_T encoding; +} encoding_xref[] = +{ + {"jpg", MMAL_ENCODING_JPEG}, + {"bmp", MMAL_ENCODING_BMP}, + {"gif", MMAL_ENCODING_GIF}, + {"png", MMAL_ENCODING_PNG} +}; + +static int encoding_xref_size = sizeof(encoding_xref) / sizeof(encoding_xref[0]); + + +/** + * Assign a default set of parameters to the state passed in + * + * @param state Pointer to state structure to assign defaults to + */ +static void default_status(RASPISTILL_STATE *state) +{ + if (!state) + { + vcos_assert(0); + return; + } + + state->timeout = 5000; // 5s delay before take image + state->width = 2592; + state->height = 1944; + state->quality = 85; + state->wantRAW = 0; + state->filename = NULL; + state->linkname = NULL; + state->verbose = 0; + state->thumbnailConfig.enable = 1; + state->thumbnailConfig.width = 64; + state->thumbnailConfig.height = 48; + state->thumbnailConfig.quality = 35; + state->demoMode = 0; + state->demoInterval = 250; // ms + state->camera_component = NULL; + state->encoder_component = NULL; + state->preview_connection = NULL; + state->encoder_connection = NULL; + state->encoder_pool = NULL; + state->encoding = MMAL_ENCODING_JPEG; + state->numExifTags = 0; + state->timelapse = 0; + state->fullResPreview = 0; + + // Setup preview window defaults + raspipreview_set_defaults(&state->preview_parameters); + + // Set up the camera_parameters to default + raspicamcontrol_set_defaults(&state->camera_parameters); +} + +/** + * Dump image state parameters to stderr. Used for debugging + * + * @param state Pointer to state structure to assign defaults to + */ +static void dump_status(RASPISTILL_STATE *state) +{ + int i; + + if (!state) + { + vcos_assert(0); + return; + } + + fprintf(stderr, "Width %d, Height %d, quality %d, filename %s\n", state->width, + state->height, state->quality, state->filename); + fprintf(stderr, "Time delay %d, Raw %s\n", state->timeout, + state->wantRAW ? "yes" : "no"); + fprintf(stderr, "Thumbnail enabled %s, width %d, height %d, quality %d\n", + state->thumbnailConfig.enable ? "Yes":"No", state->thumbnailConfig.width, + state->thumbnailConfig.height, state->thumbnailConfig.quality); + fprintf(stderr, "Link to latest frame enabled "); + if (state->linkname) + { + fprintf(stderr, " yes, -> %s\n", state->linkname); + } + else + { + fprintf(stderr, " no\n"); + } + fprintf(stderr, "Full resolution preview %s\n\n", state->fullResPreview ? "Yes": "No"); + + if (state->numExifTags) + { + fprintf(stderr, "User supplied EXIF tags :\n"); + + for (i=0;i<state->numExifTags;i++) + { + fprintf(stderr, "%s", state->exifTags[i]); + if (i != state->numExifTags-1) + fprintf(stderr, ","); + } + fprintf(stderr, "\n\n"); + } + + raspipreview_dump_parameters(&state->preview_parameters); + //raspicamcontrol_dump_parameters(&state->camera_parameters); +} + +/** + * Parse the incoming command line and put resulting parameters in to the state + * + * @param argc Number of arguments in command line + * @param argv Array of pointers to strings from command line + * @param state Pointer to state structure to assign any discovered parameters to + * @return non-0 if failed for some reason, 0 otherwise + */ +static int parse_cmdline(int argc, const char **argv, RASPISTILL_STATE *state) +{ + // Parse the command line arguments. + // We are looking for --<something> or -<abreviation of something> + + int valid = 1; + int i; + + for (i = 1; i < argc && valid; i++) + { + int command_id, num_parameters; + + if (!argv[i]) + continue; + + if (argv[i][0] != '-') + { + valid = 0; + continue; + } + + // Assume parameter is valid until proven otherwise + valid = 1; + + command_id = raspicli_get_command_id(cmdline_commands, cmdline_commands_size, &argv[i][1], &num_parameters); + + // If we found a command but are missing a parameter, continue (and we will drop out of the loop) + if (command_id != -1 && num_parameters > 0 && (i + 1 >= argc) ) + continue; + + // We are now dealing with a command line option + switch (command_id) + { + case CommandHelp: + display_valid_parameters(basename(argv[0])); + // exit straight away if help requested + return -1; + + case CommandWidth: // Width > 0 + if (sscanf(argv[i + 1], "%u", &state->width) != 1) + valid = 0; + else + i++; + break; + + case CommandHeight: // Height > 0 + if (sscanf(argv[i + 1], "%u", &state->height) != 1) + valid = 0; + else + i++; + break; + + case CommandQuality: // Quality = 1-100 + if (sscanf(argv[i + 1], "%u", &state->quality) == 1) + { + if (state->quality > 100) + { + fprintf(stderr, "Setting max quality = 100\n"); + state->quality = 100; + } + i++; + } + else + valid = 0; + + break; + + case CommandRaw: // Add raw bayer data in metadata + state->wantRAW = 1; + break; + + case CommandOutput: // output filename + { + int len = strlen(argv[i + 1]); + if (len) + { + state->filename = malloc(len + 10); // leave enough space for any timelapse generated changes to filename + vcos_assert(state->filename); + if (state->filename) + strncpy(state->filename, argv[i + 1], len); + i++; + } + else + valid = 0; + break; + } + + case CommandLink : + { + int len = strlen(argv[i+1]); + if (len) + { + state->linkname = malloc(len + 10); + vcos_assert(state->linkname); + if (state->linkname) + strncpy(state->linkname, argv[i + 1], len); + i++; + } + else + valid = 0; + break; + + } + case CommandVerbose: // display lots of data during run + state->verbose = 1; + break; + + case CommandTimeout: // Time to run viewfinder for before taking picture, in seconds + { + if (sscanf(argv[i + 1], "%u", &state->timeout) == 1) + { + // TODO : What limits do we need for timeout? + i++; + } + else + valid = 0; + break; + } + case CommandThumbnail : // thumbnail parameters - needs string "x:y:quality" + sscanf(argv[i + 1], "%d:%d:%d", &state->thumbnailConfig.width,&state->thumbnailConfig.height, + &state->thumbnailConfig.quality); + i++; + break; + + case CommandDemoMode: // Run in demo mode - no capture + { + // Demo mode might have a timing parameter + // so check if a) we have another parameter, b) its not the start of the next option + if (i + 1 < argc && argv[i+1][0] != '-') + { + if (sscanf(argv[i + 1], "%u", &state->demoInterval) == 1) + { + // TODO : What limits do we need for timeout? + state->demoMode = 1; + i++; + } + else + valid = 0; + } + else + { + state->demoMode = 1; + } + + break; + } + + case CommandEncoding : + { + int len = strlen(argv[i + 1]); + valid = 0; + + if (len) + { + int j; + for (j=0;j<encoding_xref_size;j++) + { + if (strcmp(encoding_xref[j].format, argv[i+1]) == 0) + { + state->encoding = encoding_xref[j].encoding; + valid = 1; + i++; + break; + } + } + } + break; + } + + case CommandExifTag: + store_exif_tag(state, argv[i+1]); + i++; + break; + + case CommandTimelapse: + if (sscanf(argv[i + 1], "%u", &state->timelapse) != 1) + valid = 0; + else + i++; + break; + + case CommandFullResPreview: + state->fullResPreview = 1; + break; + + default: + { + // Try parsing for any image specific parameters + // result indicates how many parameters were used up, 0,1,2 + // but we adjust by -1 as we have used one already + const char *second_arg = (i + 1 < argc) ? argv[i + 1] : NULL; + int parms_used = raspicamcontrol_parse_cmdline(&state->camera_parameters, &argv[i][1], second_arg); + + // Still unused, try preview options + if (!parms_used) + parms_used = raspipreview_parse_cmdline(&state->preview_parameters, &argv[i][1], second_arg); + + // If no parms were used, this must be a bad parameters + if (!parms_used) + valid = 0; + else + i += parms_used - 1; + + break; + } + } + } + + if (!valid) + { + fprintf(stderr, "Invalid command line option (%s)\n", argv[i]); + return 1; + } + + return 0; +} + +/** + * Display usage information for the application to stdout + * + * @param app_name String to display as the application name + */ +static void display_valid_parameters(char *app_name) +{ + fprintf(stderr, "Runs camera for specific time, and take JPG capture at end if requested\n\n"); + fprintf(stderr, "usage: %s [options]\n\n", app_name); + + fprintf(stderr, "Image parameter commands\n\n"); + + raspicli_display_help(cmdline_commands, cmdline_commands_size); + + // Help for preview options + raspipreview_display_help(); + + // Now display any help information from the camcontrol code + raspicamcontrol_display_help(); + + fprintf(stderr, "\n"); + + return; +} + +/** + * buffer header callback function for camera control + * + * No actions taken in current version + * + * @param port Pointer to port from which callback originated + * @param buffer mmal buffer header pointer + */ +static void camera_control_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +{ + if (buffer->cmd == MMAL_EVENT_PARAMETER_CHANGED) + { + } + else + { + vcos_log_error("Received unexpected camera control callback event, 0x%08x", buffer->cmd); + } + + mmal_buffer_header_release(buffer); +} + +/** + * buffer header callback function for encoder + * + * Callback will dump buffer data to the specific file + * + * @param port Pointer to port from which callback originated + * @param buffer mmal buffer header pointer + */ +static void encoder_buffer_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +{ + int complete = 0; + + // We pass our file handle and other stuff in via the userdata field. + + PORT_USERDATA *pData = (PORT_USERDATA *)port->userdata; + + if (pData) + { + int bytes_written = buffer->length; + + if (buffer->length && pData->file_handle) + { + mmal_buffer_header_mem_lock(buffer); + + bytes_written = fwrite(buffer->data, 1, buffer->length, pData->file_handle); + + mmal_buffer_header_mem_unlock(buffer); + } + + // We need to check we wrote what we wanted - it's possible we have run out of storage. + if (bytes_written != buffer->length) + { + vcos_log_error("Unable to write buffer to file - aborting"); + complete = 1; + } + + // Now flag if we have completed + if (buffer->flags & (MMAL_BUFFER_HEADER_FLAG_FRAME_END | MMAL_BUFFER_HEADER_FLAG_TRANSMISSION_FAILED)) + complete = 1; + } + else + { + vcos_log_error("Received a encoder buffer callback with no state"); + } + + // release buffer back to the pool + mmal_buffer_header_release(buffer); + + // and send one back to the port (if still open) + if (port->is_enabled) + { + MMAL_STATUS_T status = MMAL_SUCCESS; + MMAL_BUFFER_HEADER_T *new_buffer; + + new_buffer = mmal_queue_get(pData->pstate->encoder_pool->queue); + + if (new_buffer) + { + status = mmal_port_send_buffer(port, new_buffer); + } + if (!new_buffer || status != MMAL_SUCCESS) + vcos_log_error("Unable to return a buffer to the encoder port"); + } + + if (complete) + vcos_semaphore_post(&(pData->complete_semaphore)); + +} + + +/** + * Create the camera component, set up its ports + * + * @param state Pointer to state control struct. camera_component member set to the created camera_component if successfull. + * + * @return MMAL_SUCCESS if all OK, something else otherwise + * + */ +static MMAL_STATUS_T create_camera_component(RASPISTILL_STATE *state) +{ + MMAL_COMPONENT_T *camera = 0; + MMAL_ES_FORMAT_T *format; + MMAL_PORT_T *preview_port = NULL, *video_port = NULL, *still_port = NULL; + MMAL_STATUS_T status; + + /* Create the component */ + status = mmal_component_create(MMAL_COMPONENT_DEFAULT_CAMERA, &camera); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Failed to create camera component"); + goto error; + } + + if (!camera->output_num) + { + status = MMAL_ENOSYS; + vcos_log_error("Camera doesn't have output ports"); + goto error; + } + + preview_port = camera->output[MMAL_CAMERA_PREVIEW_PORT]; + video_port = camera->output[MMAL_CAMERA_VIDEO_PORT]; + still_port = camera->output[MMAL_CAMERA_CAPTURE_PORT]; + + // Enable the camera, and tell it its control callback function + status = mmal_port_enable(camera->control, camera_control_callback); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to enable control port : error %d", status); + goto error; + } + + // set up the camera configuration + { + MMAL_PARAMETER_CAMERA_CONFIG_T cam_config = + { + { MMAL_PARAMETER_CAMERA_CONFIG, sizeof(cam_config) }, + .max_stills_w = state->width, + .max_stills_h = state->height, + .stills_yuv422 = 0, + .one_shot_stills = 1, + .max_preview_video_w = state->preview_parameters.previewWindow.width, + .max_preview_video_h = state->preview_parameters.previewWindow.height, + .num_preview_video_frames = 3, + .stills_capture_circular_buffer_height = 0, + .fast_preview_resume = 0, + .use_stc_timestamp = MMAL_PARAM_TIMESTAMP_MODE_RESET_STC + }; + + if (state->fullResPreview) + { + cam_config.max_preview_video_w = state->width; + cam_config.max_preview_video_h = state->height; + } + + mmal_port_parameter_set(camera->control, &cam_config.hdr); + } + + raspicamcontrol_set_all_parameters(camera, &state->camera_parameters); + + // Now set up the port formats + + format = preview_port->format; + + format->encoding = MMAL_ENCODING_OPAQUE; + format->encoding_variant = MMAL_ENCODING_I420; + + if (state->fullResPreview) + { + // In this mode we are forcing the preview to be generated from the full capture resolution. + // This runs at a max of 15fps with the OV5647 sensor. + format->es->video.width = state->width; + format->es->video.height = state->height; + format->es->video.crop.x = 0; + format->es->video.crop.y = 0; + format->es->video.crop.width = state->width; + format->es->video.crop.height = state->height; + format->es->video.frame_rate.num = FULL_RES_PREVIEW_FRAME_RATE_NUM; + format->es->video.frame_rate.den = FULL_RES_PREVIEW_FRAME_RATE_DEN; + } + else + { + // use our normal preview mode - probably 1080p30 + format->es->video.width = state->preview_parameters.previewWindow.width; + format->es->video.height = state->preview_parameters.previewWindow.height; + format->es->video.crop.x = 0; + format->es->video.crop.y = 0; + format->es->video.crop.width = state->preview_parameters.previewWindow.width; + format->es->video.crop.height = state->preview_parameters.previewWindow.height; + format->es->video.frame_rate.num = PREVIEW_FRAME_RATE_NUM; + format->es->video.frame_rate.den = PREVIEW_FRAME_RATE_DEN; + } + + status = mmal_port_format_commit(preview_port); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("camera viewfinder format couldn't be set"); + goto error; + } + + // Set the same format on the video port (which we dont use here) + mmal_format_full_copy(video_port->format, format); + status = mmal_port_format_commit(video_port); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("camera video format couldn't be set"); + goto error; + } + + // Ensure there are enough buffers to avoid dropping frames + if (video_port->buffer_num < VIDEO_OUTPUT_BUFFERS_NUM) + video_port->buffer_num = VIDEO_OUTPUT_BUFFERS_NUM; + + format = still_port->format; + + // Set our stills format on the stills (for encoder) port + format->encoding = MMAL_ENCODING_OPAQUE; + format->es->video.width = state->width; + format->es->video.height = state->height; + format->es->video.crop.x = 0; + format->es->video.crop.y = 0; + format->es->video.crop.width = state->width; + format->es->video.crop.height = state->height; + format->es->video.frame_rate.num = STILLS_FRAME_RATE_NUM; + format->es->video.frame_rate.den = STILLS_FRAME_RATE_DEN; + + + status = mmal_port_format_commit(still_port); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("camera still format couldn't be set"); + goto error; + } + + /* Ensure there are enough buffers to avoid dropping frames */ + if (still_port->buffer_num < VIDEO_OUTPUT_BUFFERS_NUM) + still_port->buffer_num = VIDEO_OUTPUT_BUFFERS_NUM; + + /* Enable component */ + status = mmal_component_enable(camera); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("camera component couldn't be enabled"); + goto error; + } + + state->camera_component = camera; + + if (state->verbose) + fprintf(stderr, "Camera component done\n"); + + return status; + +error: + + if (camera) + mmal_component_destroy(camera); + + return status; +} + +/** + * Destroy the camera component + * + * @param state Pointer to state control struct + * + */ +static void destroy_camera_component(RASPISTILL_STATE *state) +{ + if (state->camera_component) + { + mmal_component_destroy(state->camera_component); + state->camera_component = NULL; + } +} + + + +/** + * Create the encoder component, set up its ports + * + * @param state Pointer to state control struct. encoder_component member set to the created camera_component if successfull. + * + * @return a MMAL_STATUS, MMAL_SUCCESS if all OK, something else otherwise + */ +static MMAL_STATUS_T create_encoder_component(RASPISTILL_STATE *state) +{ + MMAL_COMPONENT_T *encoder = 0; + MMAL_PORT_T *encoder_input = NULL, *encoder_output = NULL; + MMAL_STATUS_T status; + MMAL_POOL_T *pool; + + status = mmal_component_create(MMAL_COMPONENT_DEFAULT_IMAGE_ENCODER, &encoder); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to create JPEG encoder component"); + goto error; + } + + if (!encoder->input_num || !encoder->output_num) + { + status = MMAL_ENOSYS; + vcos_log_error("JPEG encoder doesn't have input/output ports"); + goto error; + } + + encoder_input = encoder->input[0]; + encoder_output = encoder->output[0]; + + // We want same format on input and output + mmal_format_copy(encoder_output->format, encoder_input->format); + + // Specify out output format + encoder_output->format->encoding = state->encoding; + + encoder_output->buffer_size = encoder_output->buffer_size_recommended; + + if (encoder_output->buffer_size < encoder_output->buffer_size_min) + encoder_output->buffer_size = encoder_output->buffer_size_min; + + 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; + + // Commit the port changes to the output port + status = mmal_port_format_commit(encoder_output); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to set format on video encoder output port"); + goto error; + } + + // Set the JPEG quality level + status = mmal_port_parameter_set_uint32(encoder_output, MMAL_PARAMETER_JPEG_Q_FACTOR, state->quality); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to set JPEG quality"); + goto error; + } + + // Set up any required thumbnail + { + MMAL_PARAMETER_THUMBNAIL_CONFIG_T param_thumb = {{MMAL_PARAMETER_THUMBNAIL_CONFIGURATION, sizeof(MMAL_PARAMETER_THUMBNAIL_CONFIG_T)}, 0, 0, 0, 0}; + + if ( state->thumbnailConfig.width > 0 && state->thumbnailConfig.height > 0 ) + { + // Have a valid thumbnail defined + param_thumb.enable = 1; + param_thumb.width = state->thumbnailConfig.width; + param_thumb.height = state->thumbnailConfig.height; + param_thumb.quality = state->thumbnailConfig.quality; + } + status = mmal_port_parameter_set(encoder->control, ¶m_thumb.hdr); + } + + // Enable component + status = mmal_component_enable(encoder); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to enable video encoder component"); + 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 (state->verbose) + fprintf(stderr, "Encoder component done\n"); + + return status; + + error: + + if (encoder) + mmal_component_destroy(encoder); + + return status; +} + +/** + * Destroy the encoder component + * + * @param state Pointer to state control struct + * + */ +static void destroy_encoder_component(RASPISTILL_STATE *state) +{ + // Get rid of any port buffers first + if (state->encoder_pool) + { + mmal_port_pool_destroy(state->encoder_component->output[0], state->encoder_pool); + } + + if (state->encoder_component) + { + mmal_component_destroy(state->encoder_component); + state->encoder_component = NULL; + } +} + + +/** + * Add an exif tag to the capture + * + * @param state Pointer to state control struct + * @param exif_tag String containing a "key=value" pair. + * @return Returns a MMAL_STATUS_T giving result of operation + */ +static MMAL_STATUS_T add_exif_tag(RASPISTILL_STATE *state, const char *exif_tag) +{ + MMAL_STATUS_T status; + MMAL_PARAMETER_EXIF_T *exif_param = (MMAL_PARAMETER_EXIF_T*)calloc(sizeof(MMAL_PARAMETER_EXIF_T) + MAX_EXIF_PAYLOAD_LENGTH, 1); + + vcos_assert(state); + vcos_assert(state->encoder_component); + + // Check to see if the tag is present or is indeed a key=value pair. + if (!exif_tag || strchr(exif_tag, '=') == NULL || strlen(exif_tag) > MAX_EXIF_PAYLOAD_LENGTH-1) + return MMAL_EINVAL; + + exif_param->hdr.id = MMAL_PARAMETER_EXIF; + + strncpy((char*)exif_param->data, exif_tag, MAX_EXIF_PAYLOAD_LENGTH-1); + + exif_param->hdr.size = sizeof(MMAL_PARAMETER_EXIF_T) + strlen((char*)exif_param->data); + + status = mmal_port_parameter_set(state->encoder_component->output[0], &exif_param->hdr); + + free(exif_param); + + return status; +} + +/** + * Add a basic set of EXIF tags to the capture + * Make, Time etc + * + * @param state Pointer to state control struct + * + */ +static void add_exif_tags(RASPISTILL_STATE *state) +{ + time_t rawtime; + struct tm *timeinfo; + char time_buf[32]; + char exif_buf[128]; + int i; + + add_exif_tag(state, "IFD0.Model=RP_OV5647"); + add_exif_tag(state, "IFD0.Make=RaspberryPi"); + + time(&rawtime); + timeinfo = localtime(&rawtime); + + snprintf(time_buf, sizeof(time_buf), + "%04d:%02d:%02d %02d:%02d:%02d", + timeinfo->tm_year+1900, + timeinfo->tm_mon+1, + timeinfo->tm_mday, + timeinfo->tm_hour, + timeinfo->tm_min, + timeinfo->tm_sec); + + snprintf(exif_buf, sizeof(exif_buf), "EXIF.DateTimeDigitized=%s", time_buf); + add_exif_tag(state, exif_buf); + + snprintf(exif_buf, sizeof(exif_buf), "EXIF.DateTimeOriginal=%s", time_buf); + add_exif_tag(state, exif_buf); + + snprintf(exif_buf, sizeof(exif_buf), "IFD0.DateTime=%s", time_buf); + add_exif_tag(state, exif_buf); + + // Now send any user supplied tags + + for (i=0;i<state->numExifTags && i < MAX_USER_EXIF_TAGS;i++) + { + if (state->exifTags[i]) + { + add_exif_tag(state, state->exifTags[i]); + } + } +} + +/** + * Stores an EXIF tag in the state, incrementing various pointers as necessary. + * Any tags stored in this way will be added to the image file when add_exif_tags + * is called + * + * Will not store if run out of storage space + * + * @param state Pointer to state control struct + * @param exif_tag EXIF tag string + * + */ +static void store_exif_tag(RASPISTILL_STATE *state, const char *exif_tag) +{ + if (state->numExifTags < MAX_USER_EXIF_TAGS) + { + state->exifTags[state->numExifTags] = exif_tag; + state->numExifTags++; + } +} + +/** + * Connect two specific ports together + * + * @param output_port Pointer the output port + * @param input_port Pointer the input port + * @param Pointer to a mmal connection pointer, reassigned if function successful + * @return Returns a MMAL_STATUS_T giving result of operation + * + */ +static MMAL_STATUS_T connect_ports(MMAL_PORT_T *output_port, MMAL_PORT_T *input_port, MMAL_CONNECTION_T **connection) +{ + MMAL_STATUS_T status; + + status = mmal_connection_create(connection, output_port, input_port, MMAL_CONNECTION_FLAG_TUNNELLING | MMAL_CONNECTION_FLAG_ALLOCATION_ON_INPUT); + + if (status == MMAL_SUCCESS) + { + status = mmal_connection_enable(*connection); + if (status != MMAL_SUCCESS) + mmal_connection_destroy(*connection); + } + + return status; +} + + +/** + * Allocates and generates a filename based on the + * user-supplied pattern and the frame number. + * On successful return, finalName and tempName point to malloc()ed strings + * which must be freed externally. (On failure, returns nulls that + * don't need free()ing.) + * + * @param finalName pointer receives an + * @param pattern sprintf pattern with %d to be replaced by frame + * @param frame for timelapse, the frame number + * @return Returns a MMAL_STATUS_T giving result of operation +*/ + +MMAL_STATUS_T create_filenames(char** finalName, char** tempName, char * pattern, int frame) +{ + *finalName = NULL; + *tempName = NULL; + if (0 > asprintf(finalName, pattern, frame) || + 0 > asprintf(tempName, "%s~", *finalName)) + { + if (*finalName != NULL) + { + free(*finalName); + } + return MMAL_ENOMEM; // It may be some other error, but it is not worth getting it right + } + return MMAL_SUCCESS; +} + +/** + * Checks if specified port is valid and enabled, then disables it + * + * @param port Pointer the port + * + */ +static void check_disable_port(MMAL_PORT_T *port) +{ + if (port && port->is_enabled) + mmal_port_disable(port); +} + +/** + * Handler for sigint signals + * + * @param signal_number ID of incoming signal. + * + */ +static void signal_handler(int signal_number) +{ + // Going to abort on all signals + vcos_log_error("Aborting program\n"); + + // Need to close any open stuff... + + exit(130); +} + +/** + * main + */ +int main(int argc, const char **argv) +{ + // Our main data storage vessel.. + RASPISTILL_STATE state; + int exit_code = EX_OK; + + MMAL_STATUS_T status = MMAL_SUCCESS; + MMAL_PORT_T *camera_preview_port = NULL; + MMAL_PORT_T *camera_video_port = NULL; + MMAL_PORT_T *camera_still_port = NULL; + MMAL_PORT_T *preview_input_port = NULL; + MMAL_PORT_T *encoder_input_port = NULL; + MMAL_PORT_T *encoder_output_port = NULL; + + bcm_host_init(); + + // Register our application with the logging system + vcos_log_register("RaspiStill", VCOS_LOG_CATEGORY); + + signal(SIGINT, signal_handler); + + default_status(&state); + + // Do we have any parameters + if (argc == 1) + { + fprintf(stderr, "\%s Camera App %s\n\n", basename(argv[0]), VERSION_STRING); + + display_valid_parameters(basename(argv[0])); + exit(EX_USAGE); + } + + // Parse the command line and put options in to our status structure + if (parse_cmdline(argc, argv, &state)) + { + exit(EX_USAGE); + } + + if (state.verbose) + { + fprintf(stderr, "\n%s Camera App %s\n\n", basename(argv[0]), VERSION_STRING); + + dump_status(&state); + } + + // OK, we have a nice set of parameters. Now set up our components + // We have three components. Camera, Preview and encoder. + // Camera and encoder are different in stills/video, but preview + // is the same so handed off to a separate module + + if ((status = create_camera_component(&state)) != MMAL_SUCCESS) + { + vcos_log_error("%s: Failed to create camera component", __func__); + exit_code = EX_SOFTWARE; + } + else if ((status = raspipreview_create(&state.preview_parameters)) != MMAL_SUCCESS) + { + vcos_log_error("%s: Failed to create preview component", __func__); + destroy_camera_component(&state); + exit_code = EX_SOFTWARE; + } + else if ((status = create_encoder_component(&state)) != MMAL_SUCCESS) + { + vcos_log_error("%s: Failed to create encode component", __func__); + raspipreview_destroy(&state.preview_parameters); + destroy_camera_component(&state); + exit_code = EX_SOFTWARE; + } + else + { + PORT_USERDATA callback_data; + + if (state.verbose) + fprintf(stderr, "Starting component connection stage\n"); + + camera_preview_port = state.camera_component->output[MMAL_CAMERA_PREVIEW_PORT]; + camera_video_port = state.camera_component->output[MMAL_CAMERA_VIDEO_PORT]; + camera_still_port = state.camera_component->output[MMAL_CAMERA_CAPTURE_PORT]; + encoder_input_port = state.encoder_component->input[0]; + encoder_output_port = state.encoder_component->output[0]; + + // Note we are lucky that the preview and null sink components use the same input port + // so we can simple do this without conditionals + preview_input_port = state.preview_parameters.preview_component->input[0]; + + // Connect camera to preview (which might be a null_sink if no preview required) + status = connect_ports(camera_preview_port, preview_input_port, &state.preview_connection); + + if (status == MMAL_SUCCESS) + { + VCOS_STATUS_T vcos_status; + + if (state.verbose) + fprintf(stderr, "Connecting camera stills port to encoder input port\n"); + + // Now connect the camera to the encoder + status = connect_ports(camera_still_port, encoder_input_port, &state.encoder_connection); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("%s: Failed to connect camera video port to encoder input", __func__); + goto error; + } + + // Set up our userdata - this is passed though to the callback where we need the information. + // Null until we open our filename + callback_data.file_handle = NULL; + callback_data.pstate = &state; + vcos_status = vcos_semaphore_create(&callback_data.complete_semaphore, "RaspiStill-sem", 0); + + vcos_assert(vcos_status == VCOS_SUCCESS); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Failed to setup encoder output"); + goto error; + } + + if (state.demoMode) + { + // Run for the user specific time.. + int num_iterations = state.timeout / state.demoInterval; + int i; + for (i=0;i<num_iterations;i++) + { + raspicamcontrol_cycle_test(state.camera_component); + vcos_sleep(state.demoInterval); + } + } + else + { + int num_iterations = state.timelapse ? state.timeout / state.timelapse : 1; + int frame; + FILE *output_file = NULL; + char *use_filename = NULL; // Temporary filename while image being written + char *final_filename = NULL; // Name that gets file once complete + int64_t next_frame_ms = vcos_getmicrosecs64()/1000; + + // If in timelapse mode, and timeout set to zero (or less), then take frames forever + for (frame = 1; (num_iterations <= 0) || (frame<=num_iterations); frame++) + { + if (state.timelapse) + { + int64_t this_delay_ms = next_frame_ms - vcos_getmicrosecs64()/1000; + if (this_delay_ms < 0) + { // We are already past the next exposure time + if (-this_delay_ms < -state.timelapse/2) + { // Less than a half frame late, take a frame and hope to catch up next time + next_frame_ms += state.timelapse; + vcos_log_error("Frame %d is %d ms late", frame, (int)(-this_delay_ms)); + } + else + { + int nskip = 1 + (-this_delay_ms)/state.timelapse; + vcos_log_error("Skipping frame %d to restart at frame %d", frame, frame+nskip); + frame += nskip; + this_delay_ms += nskip * state.timelapse; + vcos_sleep(this_delay_ms); + next_frame_ms += (nskip + 1) * state.timelapse; + } + } + else + { + vcos_sleep(this_delay_ms); + next_frame_ms += state.timelapse; + } + } + else + vcos_sleep(state.timeout); + + // Open the file + if (state.filename) + { + if (state.filename[0] == '-') + { + output_file = stdout; + + // Ensure we don't upset the output stream with diagnostics/info + state.verbose = 0; + } + else + { + vcos_assert(use_filename == NULL && final_filename == NULL); + status = create_filenames(&final_filename, &use_filename, state.filename, frame); + if (status != MMAL_SUCCESS) + { + vcos_log_error("Unable to create filenames"); + goto error; + } + + if (state.verbose) + fprintf(stderr, "Opening output file %s\n", final_filename); + // Technically it is opening the temp~ filename which will be ranamed to the final filename + + output_file = fopen(use_filename, "wb"); + + if (!output_file) + { + // Notify user, carry on but discarding encoded output buffers + vcos_log_error("%s: Error opening output file: %s\nNo output file will be generated\n", __func__, use_filename); + } + + // asprintf used in timelapse mode allocates its own memory which we need to free + } + + callback_data.file_handle = output_file; + } + + // We only capture if a filename was specified and it opened + if (output_file) + { + int num, q; + + // Must do this before the encoder output port is enabled since + // once enabled no further exif data is accepted + add_exif_tags(&state); + + // Same with raw, apparently need to set it for each capture, whilst port + // is not enabled + if (state.wantRAW) + { + if (mmal_port_parameter_set_boolean(camera_still_port, MMAL_PARAMETER_ENABLE_RAW_CAPTURE, 1) != MMAL_SUCCESS) + { + vcos_log_error("RAW was requested, but failed to enable"); + } + } + + // Enable the encoder output port + encoder_output_port->userdata = (struct MMAL_PORT_USERDATA_T *)&callback_data; + + if (state.verbose) + fprintf(stderr, "Enabling encoder output port\n"); + + // Enable the encoder output port and tell it its callback function + status = mmal_port_enable(encoder_output_port, encoder_buffer_callback); + + // Send all the buffers to the encoder output port + num = mmal_queue_length(state.encoder_pool->queue); + + for (q=0;q<num;q++) + { + MMAL_BUFFER_HEADER_T *buffer = mmal_queue_get(state.encoder_pool->queue); + + if (!buffer) + vcos_log_error("Unable to get a required buffer %d from pool queue", q); + + if (mmal_port_send_buffer(encoder_output_port, buffer)!= MMAL_SUCCESS) + vcos_log_error("Unable to send a buffer to encoder output port (%d)", q); + } + + if (state.verbose) + fprintf(stderr, "Starting capture %d\n", frame); + + if (mmal_port_parameter_set_boolean(camera_still_port, MMAL_PARAMETER_CAPTURE, 1) != MMAL_SUCCESS) + { + vcos_log_error("%s: Failed to start capture", __func__); + } + else + { + // Wait for capture to complete + // For some reason using vcos_semaphore_wait_timeout sometimes returns immediately with bad parameter error + // even though it appears to be all correct, so reverting to untimed one until figure out why its erratic + vcos_semaphore_wait(&callback_data.complete_semaphore); + if (state.verbose) + fprintf(stderr, "Finished capture %d\n", frame); + } + + // Ensure we don't die if get callback with no open file + callback_data.file_handle = NULL; + + if (output_file != stdout) + { + fclose(output_file); + vcos_assert(use_filename != NULL && final_filename != NULL); + if (0 != rename(use_filename, final_filename)) + { + vcos_log_error("Could not rename temp file to: %s; %s", + final_filename,strerror(errno)); + } + if (state.linkname) + { + char *use_link; + char *final_link; + status = create_filenames(&final_link, &use_link, state.linkname, frame); + + // Create hard link if possible, symlink otherwise + if (status != MMAL_SUCCESS + || (0 != link(final_filename, use_link) + && 0 != symlink(final_filename, use_link)) + || 0 != rename(use_link, final_link)) + { + vcos_log_error("Could not link as filename: %s; %s", + state.linkname,strerror(errno)); + } + if (use_link) free(use_link); + if (final_link) free(final_link); + } + } + // Disable encoder output port + status = mmal_port_disable(encoder_output_port); + } + + if (use_filename) + { + free(use_filename); + use_filename = NULL; + } + if (final_filename) + { + free(final_filename); + final_filename = NULL; + } + } // end for (frame) + + vcos_semaphore_delete(&callback_data.complete_semaphore); + } + } + else + { + mmal_status_to_int(status); + vcos_log_error("%s: Failed to connect camera to preview", __func__); + } + +error: + + mmal_status_to_int(status); + + if (state.verbose) + fprintf(stderr, "Closing down\n"); + + // Disable all our ports that are not handled by connections + check_disable_port(camera_video_port); + check_disable_port(encoder_output_port); + + mmal_connection_destroy(state.preview_connection); + + mmal_connection_destroy(state.encoder_connection); + + /* Disable components */ + if (state.encoder_component) + mmal_component_disable(state.encoder_component); + + if (state.preview_parameters.preview_component) + mmal_component_disable(state.preview_parameters.preview_component); + + if (state.camera_component) + mmal_component_disable(state.camera_component); + + destroy_encoder_component(&state); + raspipreview_destroy(&state.preview_parameters); + destroy_camera_component(&state); + + if (state.verbose) + fprintf(stderr, "Close down completed, all components disconnected, disabled and destroyed\n\n"); + } + + if (status != MMAL_SUCCESS) + raspicamcontrol_check_configuration(128); + + return exit_code; +} + diff --git a/sys/rpicamsrc/RaspiStillYUV.c b/sys/rpicamsrc/RaspiStillYUV.c new file mode 100644 index 000000000..aad9edb7b --- /dev/null +++ b/sys/rpicamsrc/RaspiStillYUV.c @@ -0,0 +1,957 @@ +/* + * Copyright (c) 2013 Jan Schmidt <jan@centricular.com> +Portions: +Copyright (c) 2013, Broadcom Europe Ltd +Copyright (c) 2013, James Hughes +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + * \file RaspiStillYUV.c + * Command line program to capture a still frame and dump uncompressed it to file. + * Also optionally display a preview/viewfinder of current camera input. + * + * \date 4th March 2013 + * \Author: James Hughes + * + * Description + * + * 2 components are created; camera and preview. + * Camera component has three ports, preview, video and stills. + * Preview is connected using standard mmal connections, the stills output + * is written straight to the file in YUV 420 format via the requisite buffer + * callback. video port is not used + * + * We use the RaspiCamControl code to handle the specific camera settings. + * We use the RaspiPreview code to handle the generic preview + */ + +// We use some GNU extensions (basename) +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <memory.h> +#include <sysexits.h> + +#define VERSION_STRING "v1.3.2" + +#include "bcm_host.h" +#include "interface/vcos/vcos.h" + +#include "interface/mmal/mmal.h" +#include "interface/mmal/mmal_logging.h" +#include "interface/mmal/mmal_buffer.h" +#include "interface/mmal/util/mmal_util.h" +#include "interface/mmal/util/mmal_util_params.h" +#include "interface/mmal/util/mmal_default_components.h" +#include "interface/mmal/util/mmal_connection.h" + + +#include "RaspiCamControl.h" +#include "RaspiPreview.h" +#include "RaspiCLI.h" + +#include <semaphore.h> + +/// Camera number to use - we only have one camera, indexed from 0. +#define CAMERA_NUMBER 0 + +// Standard port setting for the camera component +#define MMAL_CAMERA_PREVIEW_PORT 0 +#define MMAL_CAMERA_VIDEO_PORT 1 +#define MMAL_CAMERA_CAPTURE_PORT 2 + + +// Stills format information +#define STILLS_FRAME_RATE_NUM 3 +#define STILLS_FRAME_RATE_DEN 1 + +/// Video render needs at least 2 buffers. +#define VIDEO_OUTPUT_BUFFERS_NUM 3 + +int mmal_status_to_int(MMAL_STATUS_T status); + +/** Structure containing all state information for the current run + */ +typedef struct +{ + int timeout; /// Time taken before frame is grabbed and app then shuts down. Units are milliseconds + int width; /// Requested width of image + int height; /// requested height of image + char *filename; /// filename of output file + int verbose; /// !0 if want detailed run information + int timelapse; /// Delay between each picture in timelapse mode. If 0, disable timelapse + int useRGB; /// Output RGB data rather than YUV + + RASPIPREVIEW_PARAMETERS preview_parameters; /// Preview setup parameters + RASPICAM_CAMERA_PARAMETERS camera_parameters; /// Camera setup parameters + + MMAL_COMPONENT_T *camera_component; /// Pointer to the camera component + MMAL_COMPONENT_T *null_sink_component; /// Pointer to the camera component + MMAL_CONNECTION_T *preview_connection; /// Pointer to the connection from camera to preview + MMAL_POOL_T *camera_pool; /// Pointer to the pool of buffers used by camera stills port +} RASPISTILLYUV_STATE; + + +/** Struct used to pass information in camera still port userdata to callback + */ +typedef struct +{ + FILE *file_handle; /// File handle to write buffer data to. + VCOS_SEMAPHORE_T complete_semaphore; /// semaphore which is posted when we reach end of frame (indicates end of capture or fault) + RASPISTILLYUV_STATE *pstate; /// pointer to our state in case required in callback +} PORT_USERDATA; + +static void display_valid_parameters(char *app_name); + +/// Comamnd ID's and Structure defining our command line options +#define CommandHelp 0 +#define CommandWidth 1 +#define CommandHeight 2 +#define CommandOutput 3 +#define CommandVerbose 4 +#define CommandTimeout 5 +#define CommandTimelapse 6 +#define CommandUseRGB 7 + +static COMMAND_LIST cmdline_commands[] = +{ + { CommandHelp, "-help", "?", "This help information", 0 }, + { CommandWidth, "-width", "w", "Set image width <size>", 1 }, + { CommandHeight, "-height", "h", "Set image height <size>", 1 }, + { CommandOutput, "-output", "o", "Output filename <filename>. If not specifed, no image is saved", 1 }, + { CommandVerbose, "-verbose", "v", "Output verbose information during run", 0 }, + { CommandTimeout, "-timeout", "t", "Time (in ms) before takes picture and shuts down. If not specified set to 5s", 1 }, + { CommandTimelapse,"-timelapse", "tl", "Timelapse mode. Takes a picture every <t>ms", 1}, + { CommandUseRGB, "-rgb", "rgb","Save as RGB data rather than YUV", 0}, +}; + +static int cmdline_commands_size = sizeof(cmdline_commands) / sizeof(cmdline_commands[0]); + +/** + * Assign a default set of parameters to the state passed in + * + * @param state Pointer to state structure to assign defaults to + */ +static void default_status(RASPISTILLYUV_STATE *state) +{ + if (!state) + { + vcos_assert(0); + return; + } + + // Default everything to zero + memset(state, 0, sizeof(RASPISTILLYUV_STATE)); + + // Now set anything non-zero + state->timeout = 5000; // 5s delay before take image + state->width = 2592; + state->height = 1944; + state->timelapse = 0; + + // Setup preview window defaults + raspipreview_set_defaults(&state->preview_parameters); + + // Set up the camera_parameters to default + raspicamcontrol_set_defaults(&state->camera_parameters); +} + +/** + * Dump image state parameters to stderr. Used for debugging + * + * @param state Pointer to state structure to assign defaults to + */ +static void dump_status(RASPISTILLYUV_STATE *state) +{ + if (!state) + { + vcos_assert(0); + return; + } + + fprintf(stderr, "Width %d, Height %d, filename %s\n", state->width, state->height, state->filename); + fprintf(stderr, "Time delay %d, Timelapse %d\n", state->timeout, state->timelapse); + + raspipreview_dump_parameters(&state->preview_parameters); + raspicamcontrol_dump_parameters(&state->camera_parameters); +} + +/** + * Parse the incoming command line and put resulting parameters in to the state + * + * @param argc Number of arguments in command line + * @param argv Array of pointers to strings from command line + * @param state Pointer to state structure to assign any discovered parameters to + * @return non-0 if failed for some reason, 0 otherwise + */ +static int parse_cmdline(int argc, const char **argv, RASPISTILLYUV_STATE *state) +{ + // Parse the command line arguments. + // We are looking for --<something> or -<abreviation of something> + + int valid = 1; // set 0 if we have a bad parameter + int i; + + for (i = 1; i < argc && valid; i++) + { + int command_id, num_parameters; + + if (!argv[i]) + continue; + + if (argv[i][0] != '-') + { + valid = 0; + continue; + } + + // Assume parameter is valid until proven otherwise + valid = 1; + + command_id = raspicli_get_command_id(cmdline_commands, cmdline_commands_size, &argv[i][1], &num_parameters); + + // If we found a command but are missing a parameter, continue (and we will drop out of the loop) + if (command_id != -1 && num_parameters > 0 && (i + 1 >= argc) ) + continue; + + // We are now dealing with a command line option + switch (command_id) + { + case CommandHelp: + display_valid_parameters(basename(argv[0])); + return -1; + + case CommandWidth: // Width > 0 + if (sscanf(argv[i + 1], "%u", &state->width) != 1) + valid = 0; + else + i++; + break; + + case CommandHeight: // Height > 0 + if (sscanf(argv[i + 1], "%u", &state->height) != 1) + valid = 0; + else + i++; + break; + + case CommandOutput: // output filename + { + int len = strlen(argv[i + 1]); + if (len) + { + state->filename = malloc(len + 1); + vcos_assert(state->filename); + if (state->filename) + strncpy(state->filename, argv[i + 1], len); + i++; + } + else + valid = 0; + break; + } + + case CommandVerbose: // display lots of data during run + state->verbose = 1; + break; + + case CommandTimeout: // Time to run viewfinder for before taking picture, in seconds + { + if (sscanf(argv[i + 1], "%u", &state->timeout) == 1) + { + // TODO : What limits do we need for timeout? + i++; + } + else + valid = 0; + break; + } + + case CommandTimelapse: + if (sscanf(argv[i + 1], "%u", &state->timelapse) != 1) + valid = 0; + else + i++; + break; + + case CommandUseRGB: // display lots of data during run + state->useRGB = 1; + break; + + default: + { + // Try parsing for any image specific parameters + // result indicates how many parameters were used up, 0,1,2 + // but we adjust by -1 as we have used one already + const char *second_arg = (i + 1 < argc) ? argv[i + 1] : NULL; + + int parms_used = (raspicamcontrol_parse_cmdline(&state->camera_parameters, &argv[i][1], second_arg)); + + // Still unused, try preview options + if (!parms_used) + parms_used = raspipreview_parse_cmdline(&state->preview_parameters, &argv[i][1], second_arg); + + + // If no parms were used, this must be a bad parameters + if (!parms_used) + valid = 0; + else + i += parms_used - 1; + + break; + } + } + } + + if (!valid) + { + fprintf(stderr, "Invalid command line option (%s)\n", argv[i]); + return 1; + } + + return 0; +} + +/** + * Display usage information for the application to stdout + * + * @param app_name String to display as the application name + * + */ +static void display_valid_parameters(char *app_name) +{ + fprintf(stderr, "Runs camera for specific time, and take uncompressed YUV capture at end if requested\n\n"); + fprintf(stderr, "usage: %s [options]\n\n", app_name); + + fprintf(stderr, "Image parameter commands\n\n"); + + raspicli_display_help(cmdline_commands, cmdline_commands_size); + + // Help for preview options + raspipreview_display_help(); + + // Now display any help information from the camcontrol code + raspicamcontrol_display_help(); + + fprintf(stderr, "\n"); + + return; +} + +/** + * buffer header callback function for camera control + * + * @param port Pointer to port from which callback originated + * @param buffer mmal buffer header pointer + */ +static void camera_control_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +{ + fprintf(stderr, "Camera control callback cmd=0x%08x", buffer->cmd); + + if (buffer->cmd == MMAL_EVENT_PARAMETER_CHANGED) + { + } + else + { + vcos_log_error("Received unexpected camera control callback event, 0x%08x", buffer->cmd); + } + + mmal_buffer_header_release(buffer); +} + +/** + * buffer header callback function for camera output port + * + * Callback will dump buffer data to the specific file + * + * @param port Pointer to port from which callback originated + * @param buffer mmal buffer header pointer + */ +static void camera_buffer_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer) +{ + int complete = 0; + // We pass our file handle and other stuff in via the userdata field. + + + PORT_USERDATA *pData = (PORT_USERDATA *)port->userdata; + + if (pData) + { + int bytes_written = buffer->length; + + if (buffer->length) + { + mmal_buffer_header_mem_lock(buffer); + + bytes_written = fwrite(buffer->data, 1, buffer->length, pData->file_handle); + + mmal_buffer_header_mem_unlock(buffer); + } + + // We need to check we wrote what we wanted - it's possible we have run out of storage. + if (bytes_written != buffer->length) + { + vcos_log_error("Unable to write buffer to file - aborting"); + complete = 1; + } + + // Check end of frame or error + if (buffer->flags & (MMAL_BUFFER_HEADER_FLAG_FRAME_END | MMAL_BUFFER_HEADER_FLAG_TRANSMISSION_FAILED)) + complete = 1; + } + else + { + vcos_log_error("Received a camera still buffer callback with no state"); + } + + // release buffer back to the pool + mmal_buffer_header_release(buffer); + + // and send one back to the port (if still open) + if (port->is_enabled) + { + MMAL_STATUS_T status; + MMAL_BUFFER_HEADER_T *new_buffer = mmal_queue_get(pData->pstate->camera_pool->queue); + + // and back to the port from there. + if (new_buffer) + { + status = mmal_port_send_buffer(port, new_buffer); + } + + if (!new_buffer || status != MMAL_SUCCESS) + vcos_log_error("Unable to return the buffer to the camera still port"); + } + + if (complete) + { + vcos_semaphore_post(&(pData->complete_semaphore)); + } +} + + +/** + * Create the camera component, set up its ports + * + * @param state Pointer to state control struct + * + * @return 0 if failed, pointer to component if successful + * + */ +static MMAL_STATUS_T create_camera_component(RASPISTILLYUV_STATE *state) +{ + MMAL_COMPONENT_T *camera = 0; + MMAL_ES_FORMAT_T *format; + MMAL_PORT_T *preview_port = NULL, *video_port = NULL, *still_port = NULL; + MMAL_STATUS_T status; + MMAL_POOL_T *pool; + + /* Create the component */ + status = mmal_component_create(MMAL_COMPONENT_DEFAULT_CAMERA, &camera); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Failed to create camera component"); + goto error; + } + + if (!camera->output_num) + { + vcos_log_error("Camera doesn't have output ports"); + goto error; + } + + preview_port = camera->output[MMAL_CAMERA_PREVIEW_PORT]; + video_port = camera->output[MMAL_CAMERA_VIDEO_PORT]; + still_port = camera->output[MMAL_CAMERA_CAPTURE_PORT]; + + // Enable the camera, and tell it its control callback function + status = mmal_port_enable(camera->control, camera_control_callback); + + if (status) + { + vcos_log_error("Unable to enable control port : error %d", status); + goto error; + } + + // set up the camera configuration + { + MMAL_PARAMETER_CAMERA_CONFIG_T cam_config = + { + { MMAL_PARAMETER_CAMERA_CONFIG, sizeof(cam_config) }, + .max_stills_w = state->width, + .max_stills_h = state->height, + .stills_yuv422 = 0, + .one_shot_stills = 1, + .max_preview_video_w = state->preview_parameters.previewWindow.width, + .max_preview_video_h = state->preview_parameters.previewWindow.height, + .num_preview_video_frames = 3, + .stills_capture_circular_buffer_height = 0, + .fast_preview_resume = 0, + .use_stc_timestamp = MMAL_PARAM_TIMESTAMP_MODE_RESET_STC + }; + mmal_port_parameter_set(camera->control, &cam_config.hdr); + } + + raspicamcontrol_set_all_parameters(camera, &state->camera_parameters); + + // Now set up the port formats + + format = preview_port->format; + + format->encoding = MMAL_ENCODING_OPAQUE; + format->encoding_variant = MMAL_ENCODING_I420; + + format->es->video.width = state->preview_parameters.previewWindow.width; + format->es->video.height = state->preview_parameters.previewWindow.height; + format->es->video.crop.x = 0; + format->es->video.crop.y = 0; + format->es->video.crop.width = state->preview_parameters.previewWindow.width; + format->es->video.crop.height = state->preview_parameters.previewWindow.height; + format->es->video.frame_rate.num = PREVIEW_FRAME_RATE_NUM; + format->es->video.frame_rate.den = PREVIEW_FRAME_RATE_DEN; + + status = mmal_port_format_commit(preview_port); + + if (status) + { + vcos_log_error("camera viewfinder format couldn't be set"); + goto error; + } + + // Set the same format on the video port (which we dont use here) + mmal_format_full_copy(video_port->format, format); + status = mmal_port_format_commit(video_port); + + if (status) + { + vcos_log_error("camera video format couldn't be set"); + goto error; + } + + // Ensure there are enough buffers to avoid dropping frames + if (video_port->buffer_num < VIDEO_OUTPUT_BUFFERS_NUM) + video_port->buffer_num = VIDEO_OUTPUT_BUFFERS_NUM; + + format = still_port->format; + + // Set our stills format on the stills port + if (state->useRGB) + { + format->encoding = MMAL_ENCODING_BGR24; + format->encoding_variant = MMAL_ENCODING_BGR24; + } + else + { + format->encoding = MMAL_ENCODING_I420; + format->encoding_variant = MMAL_ENCODING_I420; + } + format->es->video.width = state->width; + format->es->video.height = state->height; + format->es->video.crop.x = 0; + format->es->video.crop.y = 0; + format->es->video.crop.width = state->width; + format->es->video.crop.height = state->height; + format->es->video.frame_rate.num = STILLS_FRAME_RATE_NUM; + format->es->video.frame_rate.den = STILLS_FRAME_RATE_DEN; + + if (still_port->buffer_size < still_port->buffer_size_min) + still_port->buffer_size = still_port->buffer_size_min; + + still_port->buffer_num = still_port->buffer_num_recommended; + + status = mmal_port_format_commit(still_port); + + if (status) + { + vcos_log_error("camera still format couldn't be set"); + goto error; + } + + /* Enable component */ + status = mmal_component_enable(camera); + + if (status) + { + vcos_log_error("camera component couldn't be enabled"); + goto error; + } + + /* Create pool of buffer headers for the output port to consume */ + pool = mmal_port_pool_create(still_port, still_port->buffer_num, still_port->buffer_size); + + if (!pool) + { + vcos_log_error("Failed to create buffer header pool for camera still port %s", still_port->name); + } + + state->camera_pool = pool; + state->camera_component = camera; + + if (state->verbose) + fprintf(stderr, "Camera component done\n"); + + return status; + +error: + + if (camera) + mmal_component_destroy(camera); + + return status; +} + +/** + * Destroy the camera component + * + * @param state Pointer to state control struct + * + */ +static void destroy_camera_component(RASPISTILLYUV_STATE *state) +{ + if (state->camera_component) + { + mmal_component_destroy(state->camera_component); + state->camera_component = NULL; + } +} + +/** + * Connect two specific ports together + * + * @param output_port Pointer the output port + * @param input_port Pointer the input port + * @param Pointer to a mmal connection pointer, reassigned if function successful + * @return Returns a MMAL_STATUS_T giving result of operation + * + */ +static MMAL_STATUS_T connect_ports(MMAL_PORT_T *output_port, MMAL_PORT_T *input_port, MMAL_CONNECTION_T **connection) +{ + MMAL_STATUS_T status; + + status = mmal_connection_create(connection, output_port, input_port, MMAL_CONNECTION_FLAG_TUNNELLING | MMAL_CONNECTION_FLAG_ALLOCATION_ON_INPUT); + + if (status == MMAL_SUCCESS) + { + status = mmal_connection_enable(*connection); + if (status != MMAL_SUCCESS) + mmal_connection_destroy(*connection); + } + + return status; +} + +/** + * Checks if specified port is valid and enabled, then disables it + * + * @param port Pointer the port + * + */ +static void check_disable_port(MMAL_PORT_T *port) +{ + if (port && port->is_enabled) + mmal_port_disable(port); +} + +/** + * Handler for sigint signals + * + * @param signal_number ID of incoming signal. + * + */ +static void signal_handler(int signal_number) +{ + // Going to abort on all signals + vcos_log_error("Aborting program\n"); + + // Need to close any open stuff... + + exit(255); +} + +/** + * main + */ +int main(int argc, const char **argv) +{ + // Our main data storage vessel.. + RASPISTILLYUV_STATE state; + int exit_code = EX_OK; + + MMAL_STATUS_T status = MMAL_SUCCESS; + MMAL_PORT_T *camera_preview_port = NULL; + MMAL_PORT_T *camera_video_port = NULL; + MMAL_PORT_T *camera_still_port = NULL; + MMAL_PORT_T *preview_input_port = NULL; + FILE *output_file = NULL; + + bcm_host_init(); + + // Register our application with the logging system + vcos_log_register("RaspiStill", VCOS_LOG_CATEGORY); + + signal(SIGINT, signal_handler); + + // Do we have any parameters + if (argc == 1) + { + fprintf(stderr, "\n%s Camera App %s\n\n", basename(argv[0]), VERSION_STRING); + + display_valid_parameters(basename(argv[0])); + exit(EX_USAGE); + } + + default_status(&state); + + // Parse the command line and put options in to our status structure + if (parse_cmdline(argc, argv, &state)) + { + status = -1; + exit(EX_USAGE); + } + + if (state.verbose) + { + fprintf(stderr, "\n%s Camera App %s\n\n", basename(argv[0]), VERSION_STRING); + dump_status(&state); + } + + // OK, we have a nice set of parameters. Now set up our components + // We have two components. Camera and Preview + // Camera is different in stills/video, but preview + // is the same so handed off to a separate module + + if ((status = create_camera_component(&state)) != MMAL_SUCCESS) + { + vcos_log_error("%s: Failed to create camera component", __func__); + exit_code = EX_SOFTWARE; + } + else if ((status = raspipreview_create(&state.preview_parameters)) != MMAL_SUCCESS) + { + vcos_log_error("%s: Failed to create preview component", __func__); + destroy_camera_component(&state); + exit_code = EX_SOFTWARE; + } + else + { + PORT_USERDATA callback_data; + + if (state.verbose) + fprintf(stderr, "Starting component connection stage\n"); + + camera_preview_port = state.camera_component->output[MMAL_CAMERA_PREVIEW_PORT]; + camera_video_port = state.camera_component->output[MMAL_CAMERA_VIDEO_PORT]; + camera_still_port = state.camera_component->output[MMAL_CAMERA_CAPTURE_PORT]; + + // Note we are lucky that the preview and null sink components use the same input port + // so we can simple do this without conditionals + preview_input_port = state.preview_parameters.preview_component->input[0]; + + // Connect camera to preview (which might be a null_sink if no preview required) + status = connect_ports(camera_preview_port, preview_input_port, &state.preview_connection); + + if (status == MMAL_SUCCESS) + { + VCOS_STATUS_T vcos_status; + + if (state.filename) + { + if (state.verbose) + fprintf(stderr, "Opening output file %s\n", state.filename); + + output_file = fopen(state.filename, "wb"); + if (!output_file) + { + // Notify user, carry on but discarding output buffers + vcos_log_error("%s: Error opening output file: %s\nNo output file will be generated\n", __func__, state.filename); + } + } + + // Set up our userdata - this is passed though to the callback where we need the information. + callback_data.file_handle = output_file; + callback_data.pstate = &state; + + vcos_status = vcos_semaphore_create(&callback_data.complete_semaphore, "RaspiStill-sem", 0); + vcos_assert(vcos_status == VCOS_SUCCESS); + + camera_still_port->userdata = (struct MMAL_PORT_USERDATA_T *)&callback_data; + + if (state.verbose) + fprintf(stderr, "Enabling camera still output port\n"); + + // Enable the camera still output port and tell it its callback function + status = mmal_port_enable(camera_still_port, camera_buffer_callback); + + if (status != MMAL_SUCCESS) + { + vcos_log_error("Failed to setup camera output"); + goto error; + } + + if (state.verbose) + fprintf(stderr, "Starting video preview\n"); + + int num_iterations = state.timelapse ? state.timeout / state.timelapse : 1; + int frame; + FILE *output_file = NULL; + + for (frame = 1;frame<=num_iterations; frame++) + { + if (state.timelapse) + vcos_sleep(state.timelapse); + else + vcos_sleep(state.timeout); + + // Open the file + if (state.filename) + { + if (state.filename[0] == '-') + { + output_file = stdout; + + // Ensure we don't upset the output stream with diagnostics/info + state.verbose = 0; + } + else + { + char *use_filename = state.filename; + + if (state.timelapse) + asprintf(&use_filename, state.filename, frame); + + if (state.verbose) + fprintf(stderr, "Opening output file %s\n", use_filename); + + output_file = fopen(use_filename, "wb"); + + if (!output_file) + { + // Notify user, carry on but discarding encoded output buffers + vcos_log_error("%s: Error opening output file: %s\nNo output file will be generated\n", __func__, use_filename); + } + + // asprintf used in timelapse mode allocates its own memory which we need to free + if (state.timelapse) + free(use_filename); + } + + callback_data.file_handle = output_file; + } + + // And only do the capture if we have specified a filename and its opened OK + if (output_file) + { + // Send all the buffers to the camera output port + { + int num = mmal_queue_length(state.camera_pool->queue); + int q; + + for (q=0;q<num;q++) + { + MMAL_BUFFER_HEADER_T *buffer = mmal_queue_get(state.camera_pool->queue); + + if (!buffer) + vcos_log_error("Unable to get a required buffer %d from pool queue", q); + + if (mmal_port_send_buffer(camera_still_port, buffer)!= MMAL_SUCCESS) + vcos_log_error("Unable to send a buffer to camera output port (%d)", q); + } + } + + if (state.verbose) + fprintf(stderr, "Starting capture %d\n", frame); + + // Fire the capture + if (mmal_port_parameter_set_boolean(camera_still_port, MMAL_PARAMETER_CAPTURE, 1) != MMAL_SUCCESS) + { + vcos_log_error("%s: Failed to start capture", __func__); + } + else + { + // Wait for capture to complete + // For some reason using vcos_semaphore_wait_timeout sometimes returns immediately with bad parameter error + // even though it appears to be all correct, so reverting to untimed one until figure out why its erratic + vcos_semaphore_wait(&callback_data.complete_semaphore); + + if (state.verbose) + fprintf(stderr, "Finished capture %d\n", frame); + } + + // Ensure we don't die if get callback with no open file + callback_data.file_handle = NULL; + + if (output_file != stdout) + fclose(output_file); + } + } + vcos_semaphore_delete(&callback_data.complete_semaphore); + } + else + { + mmal_status_to_int(status); + vcos_log_error("%s: Failed to connect camera to preview", __func__); + } + +error: + + mmal_status_to_int(status); + + if (state.verbose) + fprintf(stderr, "Closing down\n"); + + if (output_file) + fclose(output_file); + + // Disable all our ports that are not handled by connections + check_disable_port(camera_video_port); + + mmal_connection_destroy(state.preview_connection); + + /* Disable components */ + if (state.preview_parameters.preview_component) + mmal_component_disable(state.preview_parameters.preview_component); + + if (state.camera_component) + mmal_component_disable(state.camera_component); + + raspipreview_destroy(&state.preview_parameters); + destroy_camera_component(&state); + + if (state.verbose) + fprintf(stderr, "Close down completed, all components disconnected, disabled and destroyed\n\n"); + } + + if (status != MMAL_SUCCESS) + raspicamcontrol_check_configuration(128); + + return exit_code; +} + + + + diff --git a/sys/rpicamsrc/gstrpicam-enums-template.c b/sys/rpicamsrc/gstrpicam-enums-template.c new file mode 100644 index 000000000..8631751a5 --- /dev/null +++ b/sys/rpicamsrc/gstrpicam-enums-template.c @@ -0,0 +1,40 @@ +/*** BEGIN file-header ***/ +#include "gstrpicam-enum-types.h" + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@basename@" */ +#include "@filename@" + +#define C_ENUM(v) ((gint) v) +#define C_FLAGS(v) ((guint) v) + +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type (void) +{ + static volatile gsize gtype_id = 0; + + if (g_once_init_enter (>ype_id)) { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { C_@TYPE@(@VALUENAME@), "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + + GType new_type = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + g_once_init_leave (>ype_id, new_type); + } + return (GType) gtype_id; +} + +/*** END value-tail ***/ + diff --git a/sys/rpicamsrc/gstrpicam-enums-template.h b/sys/rpicamsrc/gstrpicam-enums-template.h new file mode 100644 index 000000000..eb25042ba --- /dev/null +++ b/sys/rpicamsrc/gstrpicam-enums-template.h @@ -0,0 +1,24 @@ +/*** BEGIN file-header ***/ +#pragma once + +#include <glib-object.h> + +G_BEGIN_DECLS + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* Enumerations from "@basename@" */ + +/*** END file-production ***/ + +/*** BEGIN enumeration-production ***/ +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type()) +GType @enum_name@_get_type (void); + +/*** END enumeration-production ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +/*** END file-tail ***/ diff --git a/sys/rpicamsrc/gstrpicam_types.h b/sys/rpicamsrc/gstrpicam_types.h new file mode 100644 index 000000000..5baa491e7 --- /dev/null +++ b/sys/rpicamsrc/gstrpicam_types.h @@ -0,0 +1,101 @@ +#include "interface/mmal/util/mmal_util_params.h" +#include "interface/mmal/mmal_parameters_camera.h" +#include "RaspiCamControl.h" + +typedef enum { + GST_RPI_CAM_SRC_EXPOSURE_MODE_OFF = MMAL_PARAM_EXPOSUREMODE_OFF, + GST_RPI_CAM_SRC_EXPOSURE_MODE_AUTO = MMAL_PARAM_EXPOSUREMODE_AUTO, + GST_RPI_CAM_SRC_EXPOSURE_MODE_NIGHT = MMAL_PARAM_EXPOSUREMODE_NIGHT, + GST_RPI_CAM_SRC_EXPOSURE_MODE_NIGHTPREVIEW = MMAL_PARAM_EXPOSUREMODE_NIGHTPREVIEW, + GST_RPI_CAM_SRC_EXPOSURE_MODE_BACKLIGHT = MMAL_PARAM_EXPOSUREMODE_BACKLIGHT, + GST_RPI_CAM_SRC_EXPOSURE_MODE_SPOTLIGHT = MMAL_PARAM_EXPOSUREMODE_SPOTLIGHT, + GST_RPI_CAM_SRC_EXPOSURE_MODE_SPORTS = MMAL_PARAM_EXPOSUREMODE_SPORTS, + GST_RPI_CAM_SRC_EXPOSURE_MODE_SNOW = MMAL_PARAM_EXPOSUREMODE_SNOW, + GST_RPI_CAM_SRC_EXPOSURE_MODE_BEACH = MMAL_PARAM_EXPOSUREMODE_BEACH, + GST_RPI_CAM_SRC_EXPOSURE_MODE_VERYLONG = MMAL_PARAM_EXPOSUREMODE_VERYLONG, + GST_RPI_CAM_SRC_EXPOSURE_MODE_FIXEDFPS = MMAL_PARAM_EXPOSUREMODE_FIXEDFPS, + GST_RPI_CAM_SRC_EXPOSURE_MODE_ANTISHAKE = MMAL_PARAM_EXPOSUREMODE_ANTISHAKE, + GST_RPI_CAM_SRC_EXPOSURE_MODE_FIREWORKS = MMAL_PARAM_EXPOSUREMODE_FIREWORKS +} GstRpiCamSrcExposureMode; + +typedef enum { + GST_RPI_CAM_SRC_EXPOSURE_METERING_MODE_AVERAGE = MMAL_PARAM_EXPOSUREMETERINGMODE_AVERAGE, + GST_RPI_CAM_SRC_EXPOSURE_METERING_MODE_SPOT = MMAL_PARAM_EXPOSUREMETERINGMODE_SPOT, + GST_RPI_CAM_SRC_EXPOSURE_METERING_MODE_BACKLIST = MMAL_PARAM_EXPOSUREMETERINGMODE_BACKLIT, + GST_RPI_CAM_SRC_EXPOSURE_METERING_MODE_MATRIX = MMAL_PARAM_EXPOSUREMETERINGMODE_MATRIX +} GstRpiCamSrcExposureMeteringMode; + +typedef enum { + GST_RPI_CAM_SRC_AWB_MODE_OFF = MMAL_PARAM_AWBMODE_OFF, + GST_RPI_CAM_SRC_AWB_MODE_AUTO = MMAL_PARAM_AWBMODE_AUTO, + GST_RPI_CAM_SRC_AWB_MODE_SUNLIGHT = MMAL_PARAM_AWBMODE_SUNLIGHT, + GST_RPI_CAM_SRC_AWB_MODE_CLOUDY = MMAL_PARAM_AWBMODE_CLOUDY, + GST_RPI_CAM_SRC_AWB_MODE_SHADE = MMAL_PARAM_AWBMODE_SHADE, + GST_RPI_CAM_SRC_AWB_MODE_TUNGSTEN = MMAL_PARAM_AWBMODE_TUNGSTEN, + GST_RPI_CAM_SRC_AWB_MODE_FLUORESCENT = MMAL_PARAM_AWBMODE_FLUORESCENT, + GST_RPI_CAM_SRC_AWB_MODE_INCANDESCENT = MMAL_PARAM_AWBMODE_INCANDESCENT, + GST_RPI_CAM_SRC_AWB_MODE_FLASH = MMAL_PARAM_AWBMODE_FLASH, + GST_RPI_CAM_SRC_AWB_MODE_HORIZON = MMAL_PARAM_AWBMODE_HORIZON +} GstRpiCamSrcAWBMode; + +typedef enum { + GST_RPI_CAM_SRC_IMAGEFX_NONE = MMAL_PARAM_IMAGEFX_NONE, + GST_RPI_CAM_SRC_IMAGEFX_NEGATIVE = MMAL_PARAM_IMAGEFX_NEGATIVE, + GST_RPI_CAM_SRC_IMAGEFX_SOLARIZE = MMAL_PARAM_IMAGEFX_SOLARIZE, + GST_RPI_CAM_SRC_IMAGEFX_POSTERIZE = MMAL_PARAM_IMAGEFX_POSTERIZE, + GST_RPI_CAM_SRC_IMAGEFX_WHITEBOARD = MMAL_PARAM_IMAGEFX_WHITEBOARD, + GST_RPI_CAM_SRC_IMAGEFX_BLACKBOARD = MMAL_PARAM_IMAGEFX_BLACKBOARD, + GST_RPI_CAM_SRC_IMAGEFX_SKETCH = MMAL_PARAM_IMAGEFX_SKETCH, + GST_RPI_CAM_SRC_IMAGEFX_DENOISE = MMAL_PARAM_IMAGEFX_DENOISE, + GST_RPI_CAM_SRC_IMAGEFX_EMBOSS = MMAL_PARAM_IMAGEFX_EMBOSS, + GST_RPI_CAM_SRC_IMAGEFX_OILPAINT = MMAL_PARAM_IMAGEFX_OILPAINT, + GST_RPI_CAM_SRC_IMAGEFX_HATCH = MMAL_PARAM_IMAGEFX_HATCH, + GST_RPI_CAM_SRC_IMAGEFX_GPEN = MMAL_PARAM_IMAGEFX_GPEN, + GST_RPI_CAM_SRC_IMAGEFX_PASTEL = MMAL_PARAM_IMAGEFX_PASTEL, + GST_RPI_CAM_SRC_IMAGEFX_WATERCOLOUR = MMAL_PARAM_IMAGEFX_WATERCOLOUR, + GST_RPI_CAM_SRC_IMAGEFX_FILM = MMAL_PARAM_IMAGEFX_FILM, + GST_RPI_CAM_SRC_IMAGEFX_BLUR = MMAL_PARAM_IMAGEFX_BLUR, + GST_RPI_CAM_SRC_IMAGEFX_SATURATION = MMAL_PARAM_IMAGEFX_SATURATION, + GST_RPI_CAM_SRC_IMAGEFX_COLOURSWAP = MMAL_PARAM_IMAGEFX_COLOURSWAP, + GST_RPI_CAM_SRC_IMAGEFX_WASHEDOUT = MMAL_PARAM_IMAGEFX_WASHEDOUT, + GST_RPI_CAM_SRC_IMAGEFX_POSTERISE = MMAL_PARAM_IMAGEFX_POSTERISE, + GST_RPI_CAM_SRC_IMAGEFX_COLOURPOINT = MMAL_PARAM_IMAGEFX_COLOURPOINT, + GST_RPI_CAM_SRC_IMAGEFX_COLOURBALANCE = MMAL_PARAM_IMAGEFX_COLOURBALANCE, + GST_RPI_CAM_SRC_IMAGEFX_CARTOON = MMAL_PARAM_IMAGEFX_CARTOON +} GstRpiCamSrcImageEffect; + +typedef enum { + GST_RPI_CAM_SRC_FLICKERAVOID_OFF = MMAL_PARAM_FLICKERAVOID_OFF, + GST_RPI_CAM_SRC_FLICKERAVOID_AUTO = MMAL_PARAM_FLICKERAVOID_AUTO, + GST_RPI_CAM_SRC_FLICKERAVOID_50HZ = MMAL_PARAM_FLICKERAVOID_50HZ, + GST_RPI_CAM_SRC_FLICKERAVOID_60HZ = MMAL_PARAM_FLICKERAVOID_60HZ +} GstRpiCamSrcFlickerAvoidance; + +typedef enum { + GST_RPI_CAM_SRC_DRC_LEVEL_OFF = MMAL_PARAMETER_DRC_STRENGTH_OFF, + GST_RPI_CAM_SRC_DRC_LEVEL_LOW = MMAL_PARAMETER_DRC_STRENGTH_LOW, + GST_RPI_CAM_SRC_DRC_LEVEL_MEDIUM = MMAL_PARAMETER_DRC_STRENGTH_MEDIUM, + GST_RPI_CAM_SRC_DRC_LEVEL_HIGH = MMAL_PARAMETER_DRC_STRENGTH_HIGH +} GstRpiCamSrcDRCLevel; + +typedef enum /*< flags >*/ { + GST_RPI_CAM_SRC_ANNOTATION_MODE_CUSTOM_TEXT = ANNOTATE_USER_TEXT, + GST_RPI_CAM_SRC_ANNOTATION_MODE_TEXT = ANNOTATE_APP_TEXT, + GST_RPI_CAM_SRC_ANNOTATION_MODE_DATE = ANNOTATE_DATE_TEXT, + GST_RPI_CAM_SRC_ANNOTATION_MODE_TIME = ANNOTATE_TIME_TEXT, + GST_RPI_CAM_SRC_ANNOTATION_MODE_SHUTTER_SETTINGS = ANNOTATE_SHUTTER_SETTINGS, + GST_RPI_CAM_SRC_ANNOTATION_MODE_CAF_SETTINGS = ANNOTATE_CAF_SETTINGS, + GST_RPI_CAM_SRC_ANNOTATION_MODE_GAIN_SETTINGS = ANNOTATE_GAIN_SETTINGS, + GST_RPI_CAM_SRC_ANNOTATION_MODE_LENS_SETTINGS = ANNOTATE_LENS_SETTINGS, + GST_RPI_CAM_SRC_ANNOTATION_MODE_MOTION_SETTINGS = ANNOTATE_MOTION_SETTINGS, + GST_RPI_CAM_SRC_ANNOTATION_MODE_FRAME_NUMBER = ANNOTATE_FRAME_NUMBER, + GST_RPI_CAM_SRC_ANNOTATION_MODE_BLACK_BACKGROUND = ANNOTATE_BLACK_BACKGROUND +} GstRpiCamSrcAnnotationMode; + +typedef enum { + GST_RPI_CAM_SRC_INTRA_REFRESH_TYPE_NONE = -1, + GST_RPI_CAM_SRC_INTRA_REFRESH_TYPE_CYCLIC = MMAL_VIDEO_INTRA_REFRESH_CYCLIC, + GST_RPI_CAM_SRC_INTRA_REFRESH_TYPE_ADAPTIVE = MMAL_VIDEO_INTRA_REFRESH_ADAPTIVE, + GST_RPI_CAM_SRC_INTRA_REFRESH_TYPE_BOTH = MMAL_VIDEO_INTRA_REFRESH_BOTH, + GST_RPI_CAM_SRC_INTRA_REFRESH_TYPE_CYCLIC_ROWS = MMAL_VIDEO_INTRA_REFRESH_CYCLIC_MROWS +} GstRpiCamSrcIntraRefreshType; diff --git a/sys/rpicamsrc/gstrpicamsrc.c b/sys/rpicamsrc/gstrpicamsrc.c new file mode 100644 index 000000000..544efe346 --- /dev/null +++ b/sys/rpicamsrc/gstrpicamsrc.c @@ -0,0 +1,1477 @@ +/* + * GStreamer + * Copyright (C) 2013-2015 Jan Schmidt <jan@centricular.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Alternatively, the contents of this file may be used under the + * GNU Lesser General Public License Version 2.1 (the "LGPL"), in + * which case the following provisions apply instead of the ones + * mentioned above: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-rpicamsrc + * + * Source element for capturing from the Raspberry Pi camera module + * + * <refsect2> + * <title>Example launch line</title> + * |[ + * gst-launch -v -m rpicamsrc ! fakesink + * ]| + * </refsect2> + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <gst/video/video.h> + +#include "gstrpicamsrc.h" +#include "gstrpicam_types.h" +#include "gstrpicam-enum-types.h" +#include "gstrpicamsrcdeviceprovider.h" +#include "RaspiCapture.h" + +#include "bcm_host.h" +#include "interface/vcos/vcos.h" + +#include "interface/mmal/mmal.h" +#include "interface/mmal/mmal_logging.h" +#include "interface/mmal/mmal_buffer.h" +#include "interface/mmal/util/mmal_util.h" +#include "interface/mmal/util/mmal_util_params.h" +#include "interface/mmal/util/mmal_default_components.h" +#include "interface/mmal/util/mmal_connection.h" + +GST_DEBUG_CATEGORY (gst_rpi_cam_src_debug); + +/* comment out to use JPEG codec instead of MJPEG */ +// #define USE_JPEG_CODEC + +/* Filter signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_CAMERA_NUMBER, + PROP_BITRATE, + PROP_KEYFRAME_INTERVAL, + PROP_PREVIEW, + PROP_PREVIEW_ENCODED, + PROP_PREVIEW_OPACITY, + PROP_PREVIEW_X, + PROP_PREVIEW_Y, + PROP_PREVIEW_W, + PROP_PREVIEW_H, + PROP_FULLSCREEN, + PROP_SHARPNESS, + PROP_CONTRAST, + PROP_BRIGHTNESS, + PROP_SATURATION, + PROP_ISO, + PROP_VIDEO_STABILISATION, + PROP_EXPOSURE_COMPENSATION, + PROP_EXPOSURE_MODE, + PROP_EXPOSURE_METERING_MODE, + PROP_AWB_MODE, + PROP_AWB_GAIN_RED, + PROP_AWB_GAIN_BLUE, + PROP_IMAGE_EFFECT, + PROP_IMAGE_EFFECT_PARAMS, + PROP_COLOUR_EFFECTS, + PROP_ROTATION, + PROP_HFLIP, + PROP_VFLIP, + PROP_ROI_X, + PROP_ROI_Y, + PROP_ROI_W, + PROP_ROI_H, + PROP_QUANTISATION_PARAMETER, + PROP_INLINE_HEADERS, + PROP_SHUTTER_SPEED, + PROP_SENSOR_MODE, + PROP_DRC, + PROP_ANNOTATION_MODE, + PROP_ANNOTATION_TEXT, + PROP_ANNOTATION_TEXT_SIZE, + PROP_ANNOTATION_TEXT_COLOUR, + PROP_ANNOTATION_TEXT_BG_COLOUR, + PROP_INTRA_REFRESH_TYPE, +#ifdef GST_RPI_CAM_SRC_ENABLE_VIDEO_DIRECTION + PROP_VIDEO_DIRECTION, +#endif + PROP_JPEG_QUALITY, + PROP_USE_STC +}; + +#define CAMERA_DEFAULT 0 + +#define BITRATE_DEFAULT 17000000 /* 17Mbit/s default for 1080p */ +#define BITRATE_HIGHEST 25000000 + +#define QUANTISATION_DEFAULT 0 + +#define SHARPNESS_DEFAULT 0 +#define CONTRAST_DEFAULT 0 +#define BRIGHTNESS_DEFAULT 50 +#define SATURATION_DEFAULT 0 +#define ISO_DEFAULT 0 +#define VIDEO_STABILISATION_DEFAULT FALSE +#define EXPOSURE_COMPENSATION_DEFAULT 0 +#define KEYFRAME_INTERVAL_DEFAULT -1 + +#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; + params->awbMode = MMAL_PARAM_AWBMODE_AUTO; + params->imageEffect = MMAL_PARAM_IMAGEFX_NONE; + params->colourEffects.enable = 0; + params->colourEffects.u = 128; + params->colourEffects.v = 128; + params->rotation = 0; + params->hflip = params->vflip = 0; + params->roi.x = params->roi.y = 0.0; + params->roi.w = params->roi.h = 1.0; +*/ + +#define JPEG_CAPS \ + "image/jpeg," \ + "width = " GST_VIDEO_SIZE_RANGE "," \ + "height = " GST_VIDEO_SIZE_RANGE "," \ + "framerate = " GST_VIDEO_FPS_RANGE +#define H264_CAPS \ + "video/x-h264, " \ + "width = " GST_VIDEO_SIZE_RANGE ", " \ + "height = " GST_VIDEO_SIZE_RANGE ", " \ + "framerate = " GST_VIDEO_FPS_RANGE ", " \ + "stream-format = (string) byte-stream, " \ + "alignment = (string) nal, " \ + "profile = (string) { constrained-baseline, 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; } +#else +#define gst_rpi_cam_src_reset_custom_orientation(src) { } +#endif + +static GstStaticPadTemplate video_src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ( H264_CAPS "; " JPEG_CAPS "; " RAW_CAPS) + ); + + +static void gst_rpi_cam_src_finalize (GObject *object); + +static void gst_rpi_cam_src_colorbalance_init (GstColorBalanceInterface * + iface); +static void gst_rpi_cam_src_orientation_init (GstVideoOrientationInterface * iface); +#ifdef GST_RPI_CAM_SRC_ENABLE_VIDEO_DIRECTION +static void gst_rpi_cam_src_direction_init (GstVideoDirectionInterface * iface); +#endif + +static void gst_rpi_cam_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_rpi_cam_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static gboolean gst_rpi_cam_src_start (GstBaseSrc * parent); +static gboolean gst_rpi_cam_src_stop (GstBaseSrc * parent); +static gboolean gst_rpi_cam_src_decide_allocation (GstBaseSrc * src, + GstQuery * query); +static GstFlowReturn gst_rpi_cam_src_create (GstPushSrc * parent, + GstBuffer ** buf); +static GstCaps *gst_rpi_cam_src_get_caps (GstBaseSrc * src, GstCaps * filter); +static gboolean gst_rpi_cam_src_set_caps (GstBaseSrc * src, GstCaps * caps); +static GstCaps *gst_rpi_cam_src_fixate (GstBaseSrc * basesrc, GstCaps * caps); +static gboolean gst_rpi_cam_src_event (GstBaseSrc * src, GstEvent * event); +static gboolean gst_rpi_cam_src_send_event (GstElement * element, + GstEvent * event); + +#define gst_rpi_cam_src_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstRpiCamSrc, gst_rpi_cam_src, + GST_TYPE_PUSH_SRC, + G_IMPLEMENT_INTERFACE (GST_TYPE_COLOR_BALANCE, + gst_rpi_cam_src_colorbalance_init); +#ifdef GST_RPI_CAM_SRC_ENABLE_VIDEO_DIRECTION + G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_DIRECTION, + gst_rpi_cam_src_direction_init); +#endif + G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_ORIENTATION, + gst_rpi_cam_src_orientation_init)); + +#define C_ENUM(v) ((gint) v) + +GType +gst_rpi_cam_src_sensor_mode_get_type (void) +{ + static const GEnumValue values[] = { + {C_ENUM (GST_RPI_CAM_SRC_SENSOR_MODE_AUTOMATIC), "Automatic", "automatic"}, + {C_ENUM (GST_RPI_CAM_SRC_SENSOR_MODE_1920x1080), "1920x1080 16:9 1-30fps", + "1920x1080"}, + {C_ENUM (GST_RPI_CAM_SRC_SENSOR_MODE_2592x1944_FAST), + "2592x1944 4:3 1-15fps / 3240x2464 15fps w/ v.2 board", + "2592x1944-fast"}, + {C_ENUM (GST_RPI_CAM_SRC_SENSOR_MODE_2592x1944_SLOW), + "2592x1944 4:3 0.1666-1fps / 3240x2464 15fps w/ v.2 board", "2592x1944-slow"}, + {C_ENUM (GST_RPI_CAM_SRC_SENSOR_MODE_1296x972), "1296x972 4:3 1-42fps", + "1296x972"}, + {C_ENUM (GST_RPI_CAM_SRC_SENSOR_MODE_1296x730), "1296x730 16:9 1-49fps", + "1296x730"}, + {C_ENUM (GST_RPI_CAM_SRC_SENSOR_MODE_640x480_SLOW), + "640x480 4:3 42.1-60fps", "640x480-slow"}, + {C_ENUM (GST_RPI_CAM_SRC_SENSOR_MODE_640x480_FAST), + "640x480 4:3 60.1-90fps", "640x480-fast"}, + {0, NULL, NULL} + }; + static volatile GType id = 0; + if (g_once_init_enter ((gsize *) & id)) { + GType _id; + _id = g_enum_register_static ("GstRpiCamSrcSensorMode", values); + g_once_init_leave ((gsize *) & id, _id); + } + + return id; +} + + +static void +gst_rpi_cam_src_class_init (GstRpiCamSrcClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSrcClass *basesrc_class; + GstPushSrcClass *pushsrc_class; + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + basesrc_class = (GstBaseSrcClass *) klass; + pushsrc_class = (GstPushSrcClass *) klass; + + gobject_class->finalize = gst_rpi_cam_src_finalize; + gobject_class->set_property = gst_rpi_cam_src_set_property; + gobject_class->get_property = gst_rpi_cam_src_get_property; + g_object_class_install_property (gobject_class, PROP_CAMERA_NUMBER, + g_param_spec_int ("camera-number", "Camera Number", + "Which camera to use on a multi-camera system - 0 or 1", 0, + 1, CAMERA_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_BITRATE, + g_param_spec_int ("bitrate", "Bitrate", + "Bitrate for encoding. 0 for VBR using quantisation-parameter", 0, + BITRATE_HIGHEST, BITRATE_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#ifdef USE_JPEG_CODEC + 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)); +#endif + 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", + -1, G_MAXINT, KEYFRAME_INTERVAL_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_PREVIEW, + g_param_spec_boolean ("preview", "Preview Window", + "Display preview window overlay", TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_FULLSCREEN, + g_param_spec_boolean ("fullscreen", "Fullscreen Preview", + "Display preview window full screen", TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_PREVIEW_ENCODED, + g_param_spec_boolean ("preview-encoded", "Preview Encoded", + "Display encoder output in the preview", TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PREVIEW_OPACITY, + g_param_spec_int ("preview-opacity", "Preview Opacity", + "Opacity to use for the preview window", 0, 255, 255, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PREVIEW_X, + g_param_spec_int ("preview-x", "Preview window X position", + "Start X coordinate of the preview window (in pixels)", 0, 2048, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PREVIEW_Y, + g_param_spec_int ("preview-y", "Preview window Y position", + "Start Y coordinate of the preview window (in pixels)", 0, 2048, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PREVIEW_W, + g_param_spec_int ("preview-w", "Preview window width", + "Width of the preview window (in pixels)", 0, 2048, 1024, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PREVIEW_H, + g_param_spec_int ("preview-h", "Preview window height", + "Height of the preview window (in pixels)", 0, 2048, 768, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHARPNESS, + g_param_spec_int ("sharpness", "Sharpness", "Image capture sharpness", + -100, 100, SHARPNESS_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_CONTRAST, + g_param_spec_int ("contrast", "Contrast", "Image capture contrast", -100, + 100, CONTRAST_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_BRIGHTNESS, + g_param_spec_int ("brightness", "Brightness", "Image capture brightness", + 0, 100, BRIGHTNESS_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_SATURATION, + g_param_spec_int ("saturation", "Saturation", "Image capture saturation", + -100, 100, SATURATION_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ISO, + g_param_spec_int ("iso", "ISO", "ISO value to use (0 = Auto)", 0, 3200, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_VIDEO_STABILISATION, + g_param_spec_boolean ("video-stabilisation", "Video Stabilisation", + "Enable or disable video stabilisation", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_EXPOSURE_COMPENSATION, + g_param_spec_int ("exposure-compensation", "EV compensation", + "Exposure Value compensation", -10, 10, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_EXPOSURE_MODE, + g_param_spec_enum ("exposure-mode", "Exposure Mode", + "Camera exposure mode to use", + GST_RPI_CAM_SRC_TYPE_EXPOSURE_MODE, EXPOSURE_MODE_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_EXPOSURE_METERING_MODE, + g_param_spec_enum ("metering-mode", "Exposure Metering Mode", + "Camera exposure metering mode to use", + GST_RPI_CAM_SRC_TYPE_EXPOSURE_METERING_MODE, + EXPOSURE_METERING_MODE_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_DRC, + g_param_spec_enum ("drc", "DRC level", "Dynamic Range Control level", + GST_RPI_CAM_SRC_TYPE_DRC_LEVEL, GST_RPI_CAM_SRC_DRC_LEVEL_OFF, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_AWB_MODE, + g_param_spec_enum ("awb-mode", "Automatic White Balance Mode", + "White Balance mode", GST_RPI_CAM_SRC_TYPE_AWB_MODE, + GST_RPI_CAM_SRC_AWB_MODE_AUTO, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_AWB_GAIN_RED, + g_param_spec_float ("awb-gain-red", "AWB Red Gain", + "Manual AWB Gain for red channel when awb-mode=off", 0, 8.0, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_AWB_GAIN_BLUE, + g_param_spec_float ("awb-gain-blue", "AWB Blue Gain", + "Manual AWB Gain for blue channel when awb-mode=off", 0, 8.0, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_IMAGE_EFFECT, + g_param_spec_enum ("image-effect", "Image effect", + "Visual FX to apply to the image", + GST_RPI_CAM_SRC_TYPE_IMAGE_EFFECT, + GST_RPI_CAM_SRC_IMAGEFX_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#if 0 + PROP_IMAGE_EFFECT_PARAMS, PROP_COLOUR_EFFECTS, +#endif + g_object_class_install_property (gobject_class, PROP_ROTATION, + g_param_spec_int ("rotation", "Rotation", + "Rotate captured image (0, 90, 180, 270 degrees)", 0, 270, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_HFLIP, + g_param_spec_boolean ("hflip", "Horizontal Flip", + "Flip capture horizontally", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_VFLIP, + g_param_spec_boolean ("vflip", "Vertical Flip", + "Flip capture vertically", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ROI_X, + g_param_spec_float ("roi-x", "ROI X", + "Normalised region-of-interest X coord", 0, 1.0, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ROI_Y, + g_param_spec_float ("roi-y", "ROI Y", + "Normalised region-of-interest Y coord", 0, 1.0, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ROI_W, + g_param_spec_float ("roi-w", "ROI W", + "Normalised region-of-interest W coord", 0, 1.0, 1.0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ROI_H, + g_param_spec_float ("roi-h", "ROI H", + "Normalised region-of-interest H coord", 0, 1.0, 1.0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, + PROP_QUANTISATION_PARAMETER, + g_param_spec_int ("quantisation-parameter", + "Quantisation Parameter", + "Set a Quantisation Parameter approx 10-40 with bitrate=0 for VBR encoding. 0 = off", + 0, G_MAXINT, QUANTISATION_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_INLINE_HEADERS, + g_param_spec_boolean ("inline-headers", "Inline Headers", + "Set to TRUE to insert SPS/PPS before each IDR packet", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_SHUTTER_SPEED, + g_param_spec_int ("shutter-speed", "Shutter Speed", + "Set a fixed shutter speed, in microseconds. (0 = Auto)", 0, + 6000000, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_SENSOR_MODE, + g_param_spec_enum ("sensor-mode", "Camera Sensor Mode", + "Manually set the camera sensor mode", + gst_rpi_cam_src_sensor_mode_get_type (), + GST_RPI_CAM_SRC_SENSOR_MODE_AUTOMATIC, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ANNOTATION_MODE, + g_param_spec_flags ("annotation-mode", "Annotation Mode", + "Flags to control annotation of the output video", + GST_RPI_CAM_SRC_TYPE_ANNOTATION_MODE, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ANNOTATION_TEXT, + g_param_spec_string ("annotation-text", "Annotation Text", + "Text string to annotate onto video when annotation-mode flags include 'custom-text'", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_INTRA_REFRESH_TYPE, + g_param_spec_enum ("intra-refresh-type", "Intra Refresh Type", + "Type of Intra Refresh to use, -1 to disable intra refresh", + GST_RPI_CAM_SRC_TYPE_INTRA_REFRESH_TYPE, + GST_RPI_CAM_SRC_INTRA_REFRESH_TYPE_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ANNOTATION_TEXT_SIZE, + g_param_spec_int ("annotation-text-size", "Annotation text size", + "Set the size of annotation text (in pixels) (0 = Auto)", 0, + G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ANNOTATION_TEXT_COLOUR, + g_param_spec_int ("annotation-text-colour", "Annotation text colour (VUY)", + "Set the annotation text colour, as the integer corresponding to a VUY value eg 0x8080FF = 8421631, -1 for default", -1, + G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ANNOTATION_TEXT_BG_COLOUR, + g_param_spec_int ("annotation-text-bg-colour", "Annotation text background colour (VUY)", + "Set the annotation text background colour, as the integer corresponding to a VUY value eg 0x8080FF = 8421631, -1 for default", -1, + G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#ifdef GST_RPI_CAM_SRC_ENABLE_VIDEO_DIRECTION + g_object_class_override_property (gobject_class, PROP_VIDEO_DIRECTION, + "video-direction"); +#endif + g_object_class_install_property (gobject_class, PROP_USE_STC, + g_param_spec_boolean ("use-stc", "Use System Time Clock", + "Use the camera STC for timestamping buffers", TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gst_element_class_set_static_metadata (gstelement_class, + "Raspberry Pi Camera Source", "Source/Video", + "Raspberry Pi camera module source", "Jan Schmidt <jan@centricular.com>"); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&video_src_template)); + basesrc_class->start = GST_DEBUG_FUNCPTR (gst_rpi_cam_src_start); + basesrc_class->stop = GST_DEBUG_FUNCPTR (gst_rpi_cam_src_stop); + basesrc_class->decide_allocation = + GST_DEBUG_FUNCPTR (gst_rpi_cam_src_decide_allocation); + basesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_rpi_cam_src_get_caps); + basesrc_class->set_caps = GST_DEBUG_FUNCPTR (gst_rpi_cam_src_set_caps); + basesrc_class->fixate = GST_DEBUG_FUNCPTR (gst_rpi_cam_src_fixate); + basesrc_class->event = GST_DEBUG_FUNCPTR (gst_rpi_cam_src_event); + gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_rpi_cam_src_send_event); + pushsrc_class->create = GST_DEBUG_FUNCPTR (gst_rpi_cam_src_create); + raspicapture_init (); +} + +static void +gst_rpi_cam_src_init (GstRpiCamSrc * src) +{ + GstColorBalanceChannel *channel; + + gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME); + gst_base_src_set_live (GST_BASE_SRC (src), TRUE); + raspicapture_default_config (&src->capture_config); + src->capture_config.intraperiod = KEYFRAME_INTERVAL_DEFAULT; + src->capture_config.verbose = 1; + src->capture_config.useSTC = TRUE; + + g_mutex_init (&src->config_lock); + + /* basesrc will generate timestamps if use-stc = false */ + gst_base_src_set_do_timestamp (GST_BASE_SRC (src), TRUE); + + /* Generate the channels list */ + channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL); + channel->label = g_strdup ("CONTRAST"); + channel->min_value = -100; + channel->max_value = 100; + src->channels = g_list_append (src->channels, channel); + + channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL); + channel->label = g_strdup ("BRIGHTNESS"); + channel->min_value = 0; + channel->max_value = 100; + src->channels = g_list_append (src->channels, channel); + + channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL); + channel->label = g_strdup ("SATURATION"); + channel->min_value = -100; + channel->max_value = 100; + src->channels = g_list_append (src->channels, channel); +} + +static void +gst_rpi_cam_src_finalize (GObject *object) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (object); + GList *channels = NULL; + g_mutex_clear (&src->config_lock); + + channels = src->channels; + while (channels) { + GstColorBalanceChannel *channel = channels->data; + + g_object_unref (channel); + channels->data = NULL; + channels = g_list_next (channels); + } + + if (src->channels) + g_list_free (src->channels); + + G_OBJECT_CLASS (gst_rpi_cam_src_parent_class)->finalize (object); +} + +static const GList * +gst_rpi_cam_src_colorbalance_list_channels (GstColorBalance * balance) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (balance); + + g_return_val_if_fail (src != NULL, NULL); + g_return_val_if_fail (GST_IS_RPICAMSRC (src), NULL); + + return src->channels; +} + +static void +gst_rpi_cam_src_colorbalance_set_value (GstColorBalance * balance, + GstColorBalanceChannel * channel, gint value) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (balance); + gboolean changed = FALSE; + + g_return_if_fail (src != NULL); + g_return_if_fail (GST_IS_RPICAMSRC (src)); + g_return_if_fail (channel->label != NULL); + + g_mutex_lock (&src->config_lock); + if (!g_ascii_strcasecmp (channel->label, "SATURATION")) { + changed = value != src->capture_config.camera_parameters.saturation; + src->capture_config.camera_parameters.saturation = value; + } else if (!g_ascii_strcasecmp (channel->label, "BRIGHTNESS")) { + changed = value != src->capture_config.camera_parameters.brightness; + src->capture_config.camera_parameters.brightness = value; + } else if (!g_ascii_strcasecmp (channel->label, "CONTRAST")) { + changed = value != src->capture_config.camera_parameters.contrast; + src->capture_config.camera_parameters.contrast = value; + } + + if (changed) + src->capture_config.change_flags |= PROP_CHANGE_COLOURBALANCE; + + g_mutex_unlock (&src->config_lock); + + if (changed) { + gst_color_balance_value_changed (balance, channel, + gst_color_balance_get_value (balance, channel)); + } +} + +static gint +gst_rpi_cam_src_colorbalance_get_value (GstColorBalance * balance, + GstColorBalanceChannel * channel) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (balance); + gint value = 0; + + g_return_val_if_fail (src != NULL, 0); + g_return_val_if_fail (GST_IS_RPICAMSRC (src), 0); + g_return_val_if_fail (channel->label != NULL, 0); + + g_mutex_lock (&src->config_lock); + + if (!g_ascii_strcasecmp (channel->label, "SATURATION")) { + value = src->capture_config.camera_parameters.saturation; + } else if (!g_ascii_strcasecmp (channel->label, "BRIGHTNESS")) { + value = src->capture_config.camera_parameters.brightness; + } else if (!g_ascii_strcasecmp (channel->label, "CONTRAST")) { + value = src->capture_config.camera_parameters.contrast; + } + + g_mutex_unlock (&src->config_lock); + + return value; +} + +static GstColorBalanceType +gst_rpi_cam_src_colorbalance_get_balance_type (GstColorBalance * balance) +{ + return GST_COLOR_BALANCE_HARDWARE; +} + +#ifdef GST_RPI_CAM_SRC_ENABLE_VIDEO_DIRECTION +static void gst_rpi_cam_src_set_orientation (GstRpiCamSrc * src, GstVideoOrientationMethod orientation) +{ + switch (orientation) { + case GST_VIDEO_ORIENTATION_IDENTITY: + src->capture_config.camera_parameters.rotation = 0; + src->capture_config.camera_parameters.hflip = FALSE; + src->capture_config.camera_parameters.vflip = FALSE; + GST_DEBUG_OBJECT (src, "set orientation identity"); + break; + case GST_VIDEO_ORIENTATION_90R: + src->capture_config.camera_parameters.rotation = 90; + src->capture_config.camera_parameters.hflip = FALSE; + src->capture_config.camera_parameters.vflip = FALSE; + GST_DEBUG_OBJECT (src, "set orientation 90R"); + break; + case GST_VIDEO_ORIENTATION_180: + src->capture_config.camera_parameters.rotation = 180; + src->capture_config.camera_parameters.hflip = FALSE; + src->capture_config.camera_parameters.vflip = FALSE; + GST_DEBUG_OBJECT (src, "set orientation 180"); + break; + case GST_VIDEO_ORIENTATION_90L: + src->capture_config.camera_parameters.rotation = 270; + src->capture_config.camera_parameters.hflip = FALSE; + src->capture_config.camera_parameters.vflip = FALSE; + GST_DEBUG_OBJECT (src, "set orientation 90L"); + break; + case GST_VIDEO_ORIENTATION_HORIZ: + src->capture_config.camera_parameters.rotation = 0; + src->capture_config.camera_parameters.hflip = TRUE; + src->capture_config.camera_parameters.vflip = FALSE; + GST_DEBUG_OBJECT (src, "set orientation hflip"); + break; + case GST_VIDEO_ORIENTATION_VERT: + src->capture_config.camera_parameters.rotation = 0; + src->capture_config.camera_parameters.hflip = FALSE; + src->capture_config.camera_parameters.vflip = TRUE; + GST_DEBUG_OBJECT (src, "set orientation vflip"); + break; + case GST_VIDEO_ORIENTATION_UL_LR: + src->capture_config.camera_parameters.rotation = 90; + src->capture_config.camera_parameters.hflip = FALSE; + src->capture_config.camera_parameters.vflip = TRUE; + GST_DEBUG_OBJECT (src, "set orientation trans"); + break; + case GST_VIDEO_ORIENTATION_UR_LL: + src->capture_config.camera_parameters.rotation = 270; + src->capture_config.camera_parameters.hflip = FALSE; + src->capture_config.camera_parameters.vflip = TRUE; + GST_DEBUG_OBJECT (src, "set orientation trans"); + break; + case GST_VIDEO_ORIENTATION_CUSTOM: + break; + default: + GST_WARNING_OBJECT (src, "unsupported orientation %d", orientation); + break; + } + src->orientation = + orientation >= GST_VIDEO_ORIENTATION_IDENTITY && + orientation <= GST_VIDEO_ORIENTATION_CUSTOM ? + orientation : GST_VIDEO_ORIENTATION_CUSTOM; + src->capture_config.change_flags |= PROP_CHANGE_ORIENTATION; +} + +static void +gst_rpi_cam_src_direction_init (GstVideoDirectionInterface * iface) +{ + /* We implement the video-direction property */ +} +#endif + +static void +gst_rpi_cam_src_colorbalance_init (GstColorBalanceInterface * iface) +{ + iface->list_channels = gst_rpi_cam_src_colorbalance_list_channels; + iface->set_value = gst_rpi_cam_src_colorbalance_set_value; + iface->get_value = gst_rpi_cam_src_colorbalance_get_value; + iface->get_balance_type = gst_rpi_cam_src_colorbalance_get_balance_type; +} + +static gboolean +gst_rpi_cam_src_orientation_get_hflip (GstVideoOrientation * orientation, gboolean * flip) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (orientation); + + g_return_val_if_fail (src != NULL, FALSE); + g_return_val_if_fail (GST_IS_RPICAMSRC (src), FALSE); + + g_mutex_lock (&src->config_lock); + *flip = src->capture_config.camera_parameters.hflip; + g_mutex_unlock (&src->config_lock); + + return TRUE; +} + +static gboolean +gst_rpi_cam_src_orientation_get_vflip (GstVideoOrientation * orientation, gboolean * flip) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (orientation); + + g_return_val_if_fail (src != NULL, FALSE); + g_return_val_if_fail (GST_IS_RPICAMSRC (src), FALSE); + + g_mutex_lock (&src->config_lock); + *flip = src->capture_config.camera_parameters.vflip; + g_mutex_unlock (&src->config_lock); + + return TRUE; +} + +static gboolean +gst_rpi_cam_src_orientation_set_hflip (GstVideoOrientation * orientation, gboolean flip) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (orientation); + + g_return_val_if_fail (src != NULL, FALSE); + g_return_val_if_fail (GST_IS_RPICAMSRC (src), FALSE); + + g_mutex_lock (&src->config_lock); + gst_rpi_cam_src_reset_custom_orientation(src); + src->capture_config.camera_parameters.hflip = flip; + src->capture_config.change_flags |= PROP_CHANGE_ORIENTATION; + g_mutex_unlock (&src->config_lock); + + return TRUE; +} + +static gboolean +gst_rpi_cam_src_orientation_set_vflip (GstVideoOrientation * orientation, gboolean flip) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (orientation); + + g_return_val_if_fail (src != NULL, FALSE); + g_return_val_if_fail (GST_IS_RPICAMSRC (src), FALSE); + + g_mutex_lock (&src->config_lock); + gst_rpi_cam_src_reset_custom_orientation(src); + src->capture_config.camera_parameters.vflip = flip; + src->capture_config.change_flags |= PROP_CHANGE_ORIENTATION; + g_mutex_unlock (&src->config_lock); + + return TRUE; +} + +static void +gst_rpi_cam_src_orientation_init (GstVideoOrientationInterface * iface) +{ + iface->get_hflip = gst_rpi_cam_src_orientation_get_hflip; + iface->set_hflip = gst_rpi_cam_src_orientation_set_hflip; + iface->get_vflip = gst_rpi_cam_src_orientation_get_vflip; + iface->set_vflip = gst_rpi_cam_src_orientation_set_vflip; + + /* TODO: hcenter / vcenter support */ +} + +static void +gst_rpi_cam_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (object); + + g_mutex_lock (&src->config_lock); + + switch (prop_id) { + case PROP_CAMERA_NUMBER: + src->capture_config.cameraNum = g_value_get_int (value); + break; + case PROP_BITRATE: + 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; + break; + case PROP_PREVIEW: + src->capture_config.preview_parameters.wantPreview = + g_value_get_boolean (value); + src->capture_config.change_flags |= PROP_CHANGE_PREVIEW; + break; + case PROP_PREVIEW_ENCODED: + src->capture_config.immutableInput = g_value_get_boolean (value); + src->capture_config.change_flags |= PROP_CHANGE_PREVIEW; + break; + case PROP_FULLSCREEN: + src->capture_config.preview_parameters.wantFullScreenPreview = + g_value_get_boolean (value); + src->capture_config.change_flags |= PROP_CHANGE_PREVIEW; + break; + case PROP_PREVIEW_OPACITY: + src->capture_config.preview_parameters.opacity = g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_PREVIEW; + break; + case PROP_PREVIEW_X: + src->capture_config.preview_parameters.previewWindow.x = g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_PREVIEW; + break; + case PROP_PREVIEW_Y: + src->capture_config.preview_parameters.previewWindow.y = g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_PREVIEW; + break; + case PROP_PREVIEW_W: + src->capture_config.preview_parameters.previewWindow.width = g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_PREVIEW; + break; + case PROP_PREVIEW_H: + src->capture_config.preview_parameters.previewWindow.height = g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_PREVIEW; + break; + case PROP_SHARPNESS: + src->capture_config.camera_parameters.sharpness = g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_COLOURBALANCE; + break; + case PROP_CONTRAST: + src->capture_config.camera_parameters.contrast = g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_COLOURBALANCE; + break; + case PROP_BRIGHTNESS: + src->capture_config.camera_parameters.brightness = + g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_COLOURBALANCE; + break; + case PROP_SATURATION: + src->capture_config.camera_parameters.saturation = + g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_COLOURBALANCE; + break; + case PROP_ISO: + src->capture_config.camera_parameters.ISO = g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_SENSOR_SETTINGS; + break; + case PROP_VIDEO_STABILISATION: + src->capture_config.camera_parameters.videoStabilisation = + g_value_get_boolean (value); + src->capture_config.change_flags |= PROP_CHANGE_VIDEO_STABILISATION; + break; + case PROP_EXPOSURE_COMPENSATION: + src->capture_config.camera_parameters.exposureCompensation = + g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_SENSOR_SETTINGS; + break; + case PROP_EXPOSURE_MODE: + src->capture_config.camera_parameters.exposureMode = + g_value_get_enum (value); + src->capture_config.change_flags |= PROP_CHANGE_SENSOR_SETTINGS; + break; + case PROP_EXPOSURE_METERING_MODE: + src->capture_config.camera_parameters.exposureMeterMode = + g_value_get_enum (value); + src->capture_config.change_flags |= PROP_CHANGE_SENSOR_SETTINGS; + break; + case PROP_ROTATION: + gst_rpi_cam_src_reset_custom_orientation(src); + src->capture_config.camera_parameters.rotation = g_value_get_int (value); + break; + case PROP_AWB_MODE: + src->capture_config.camera_parameters.awbMode = g_value_get_enum (value); + src->capture_config.change_flags |= PROP_CHANGE_AWB; + break; + case PROP_AWB_GAIN_RED: + src->capture_config.camera_parameters.awb_gains_r = + g_value_get_float (value); + src->capture_config.change_flags |= PROP_CHANGE_AWB; + break; + case PROP_AWB_GAIN_BLUE: + src->capture_config.camera_parameters.awb_gains_b = + g_value_get_float (value); + src->capture_config.change_flags |= PROP_CHANGE_AWB; + break; + case PROP_IMAGE_EFFECT: + src->capture_config.camera_parameters.imageEffect = + g_value_get_enum (value); + src->capture_config.change_flags |= PROP_CHANGE_IMAGE_COLOUR_EFFECT; + break; + case PROP_HFLIP: + gst_rpi_cam_src_reset_custom_orientation(src); + src->capture_config.camera_parameters.hflip = g_value_get_boolean (value); + src->capture_config.change_flags |= PROP_CHANGE_ORIENTATION; + break; + case PROP_VFLIP: + gst_rpi_cam_src_reset_custom_orientation(src); + src->capture_config.camera_parameters.vflip = g_value_get_boolean (value); + src->capture_config.change_flags |= PROP_CHANGE_ORIENTATION; + break; + case PROP_ROI_X: + src->capture_config.camera_parameters.roi.x = g_value_get_float (value); + src->capture_config.change_flags |= PROP_CHANGE_ROI; + break; + case PROP_ROI_Y: + src->capture_config.camera_parameters.roi.y = g_value_get_float (value); + src->capture_config.change_flags |= PROP_CHANGE_ROI; + break; + case PROP_ROI_W: + src->capture_config.camera_parameters.roi.w = g_value_get_float (value); + src->capture_config.change_flags |= PROP_CHANGE_ROI; + break; + case PROP_ROI_H: + src->capture_config.camera_parameters.roi.h = g_value_get_float (value); + src->capture_config.change_flags |= PROP_CHANGE_ROI; + break; + case PROP_QUANTISATION_PARAMETER: + src->capture_config.quantisationParameter = g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_ENCODING; + break; + case PROP_INLINE_HEADERS: + src->capture_config.bInlineHeaders = g_value_get_boolean (value); + break; + case PROP_SHUTTER_SPEED: + src->capture_config.camera_parameters.shutter_speed = + g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_SENSOR_SETTINGS; + break; + case PROP_DRC: + src->capture_config.camera_parameters.drc_level = + g_value_get_enum (value); + src->capture_config.change_flags |= PROP_CHANGE_SENSOR_SETTINGS; + break; + case PROP_SENSOR_MODE: + src->capture_config.sensor_mode = g_value_get_enum (value); + src->capture_config.change_flags |= PROP_CHANGE_SENSOR_SETTINGS; + break; + case PROP_ANNOTATION_MODE: + src->capture_config.camera_parameters.enable_annotate = + g_value_get_flags (value); + src->capture_config.change_flags |= PROP_CHANGE_ANNOTATION; + break; + case PROP_ANNOTATION_TEXT: + strncpy (src->capture_config.camera_parameters.annotate_string, + g_value_get_string (value), MMAL_CAMERA_ANNOTATE_MAX_TEXT_LEN_V2); + src->capture_config. + camera_parameters.annotate_string[MMAL_CAMERA_ANNOTATE_MAX_TEXT_LEN_V2 + - 1] = '\0'; + src->capture_config.change_flags |= PROP_CHANGE_ANNOTATION; + break; + case PROP_ANNOTATION_TEXT_SIZE: + src->capture_config. + camera_parameters.annotate_text_size = g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_ANNOTATION; + break; + case PROP_ANNOTATION_TEXT_COLOUR: + src->capture_config. + camera_parameters.annotate_text_colour = g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_ANNOTATION; + break; + case PROP_ANNOTATION_TEXT_BG_COLOUR: + src->capture_config. + camera_parameters.annotate_bg_colour = g_value_get_int (value); + src->capture_config.change_flags |= PROP_CHANGE_ANNOTATION; + break; + case PROP_INTRA_REFRESH_TYPE: + src->capture_config.intra_refresh_type = g_value_get_enum (value); + src->capture_config.change_flags |= PROP_CHANGE_ENCODING; + break; +#ifdef GST_RPI_CAM_SRC_ENABLE_VIDEO_DIRECTION + case PROP_VIDEO_DIRECTION: + gst_rpi_cam_src_set_orientation (src, g_value_get_enum (value)); + break; +#endif + case PROP_USE_STC: + src->capture_config.useSTC = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + g_mutex_unlock (&src->config_lock); +} + +static void +gst_rpi_cam_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (object); + + g_mutex_lock (&src->config_lock); + switch (prop_id) { + case PROP_CAMERA_NUMBER: + g_value_set_int (value, src->capture_config.cameraNum); + break; + 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; + case PROP_PREVIEW: + g_value_set_boolean (value, + src->capture_config.preview_parameters.wantPreview); + break; + case PROP_PREVIEW_ENCODED: + g_value_set_boolean (value, src->capture_config.immutableInput); + break; + case PROP_FULLSCREEN: + g_value_set_boolean (value, + src->capture_config.preview_parameters.wantFullScreenPreview); + break; + case PROP_PREVIEW_OPACITY: + g_value_set_int (value, src->capture_config.preview_parameters.opacity); + break; + case PROP_PREVIEW_X: + g_value_set_int (value, src->capture_config.preview_parameters.previewWindow.x); + break; + case PROP_PREVIEW_Y: + g_value_set_int (value, src->capture_config.preview_parameters.previewWindow.y); + break; + case PROP_PREVIEW_W: + g_value_set_int (value, src->capture_config.preview_parameters.previewWindow.width); + break; + case PROP_PREVIEW_H: + g_value_set_int (value, src->capture_config.preview_parameters.previewWindow.height); + break; + case PROP_SHARPNESS: + g_value_set_int (value, src->capture_config.camera_parameters.sharpness); + break; + case PROP_CONTRAST: + g_value_set_int (value, src->capture_config.camera_parameters.contrast); + break; + case PROP_BRIGHTNESS: + g_value_set_int (value, src->capture_config.camera_parameters.brightness); + break; + case PROP_SATURATION: + g_value_set_int (value, src->capture_config.camera_parameters.saturation); + break; + case PROP_ISO: + g_value_set_int (value, src->capture_config.camera_parameters.ISO); + break; + case PROP_VIDEO_STABILISATION: + g_value_set_boolean (value, + ! !(src->capture_config.camera_parameters.videoStabilisation)); + break; + case PROP_EXPOSURE_COMPENSATION: + g_value_set_int (value, + src->capture_config.camera_parameters.exposureCompensation); + break; + case PROP_EXPOSURE_MODE: + g_value_set_enum (value, + src->capture_config.camera_parameters.exposureMode); + break; + case PROP_EXPOSURE_METERING_MODE: + g_value_set_enum (value, + src->capture_config.camera_parameters.exposureMeterMode); + break; + case PROP_ROTATION: + g_value_set_int (value, src->capture_config.camera_parameters.rotation); + break; + case PROP_AWB_MODE: + g_value_set_enum (value, src->capture_config.camera_parameters.awbMode); + break; + case PROP_AWB_GAIN_RED: + g_value_set_float (value, + src->capture_config.camera_parameters.awb_gains_r); + break; + case PROP_AWB_GAIN_BLUE: + g_value_set_float (value, + src->capture_config.camera_parameters.awb_gains_b); + break; + case PROP_IMAGE_EFFECT: + g_value_set_enum (value, + src->capture_config.camera_parameters.imageEffect); + break; + case PROP_HFLIP: + g_value_set_boolean (value, + ! !(src->capture_config.camera_parameters.hflip)); + break; + case PROP_VFLIP: + g_value_set_boolean (value, + ! !(src->capture_config.camera_parameters.vflip)); + break; + case PROP_ROI_X: + g_value_set_float (value, src->capture_config.camera_parameters.roi.x); + break; + case PROP_ROI_Y: + g_value_set_float (value, src->capture_config.camera_parameters.roi.y); + break; + case PROP_ROI_W: + g_value_set_float (value, src->capture_config.camera_parameters.roi.w); + break; + case PROP_ROI_H: + g_value_set_float (value, src->capture_config.camera_parameters.roi.h); + break; + case PROP_QUANTISATION_PARAMETER: + g_value_set_int (value, src->capture_config.quantisationParameter); + break; + case PROP_INLINE_HEADERS: + g_value_set_boolean (value, src->capture_config.bInlineHeaders); + break; + case PROP_SHUTTER_SPEED: + g_value_set_int (value, + src->capture_config.camera_parameters.shutter_speed); + break; + case PROP_DRC: + g_value_set_enum (value, src->capture_config.camera_parameters.drc_level); + break; + case PROP_SENSOR_MODE: + g_value_set_enum (value, src->capture_config.sensor_mode); + break; + case PROP_ANNOTATION_MODE: + g_value_set_flags (value, + src->capture_config.camera_parameters.enable_annotate); + break; + case PROP_ANNOTATION_TEXT: + g_value_set_string (value, + src->capture_config.camera_parameters.annotate_string); + break; + case PROP_ANNOTATION_TEXT_SIZE: + g_value_set_int (value, src->capture_config.camera_parameters.annotate_text_size); + break; + case PROP_ANNOTATION_TEXT_COLOUR: + g_value_set_int (value, src->capture_config.camera_parameters.annotate_text_colour); + break; + case PROP_ANNOTATION_TEXT_BG_COLOUR: + g_value_set_int (value, src->capture_config.camera_parameters.annotate_bg_colour); + break; + case PROP_INTRA_REFRESH_TYPE: + g_value_set_enum (value, src->capture_config.intra_refresh_type); + break; +#ifdef GST_RPI_CAM_SRC_ENABLE_VIDEO_DIRECTION + case PROP_VIDEO_DIRECTION: + g_value_set_enum (value, src->orientation); + break; +#endif + case PROP_USE_STC: + g_value_set_boolean (value, src->capture_config.useSTC); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + g_mutex_unlock (&src->config_lock); +} + +static gboolean +gst_rpi_cam_src_start (GstBaseSrc * parent) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (parent); + GST_LOG_OBJECT (src, "In src_start()"); + /* Ensure basesrc timestamping is off is use-stc is on */ + if (src->capture_config.useSTC) + gst_base_src_set_do_timestamp (GST_BASE_SRC (src), FALSE); + g_mutex_lock (&src->config_lock); + src->capture_state = raspi_capture_setup (&src->capture_config); + /* Clear all capture flags */ + src->capture_config.change_flags = 0; + g_mutex_unlock (&src->config_lock); + if (src->capture_state == NULL) + return FALSE; + return TRUE; +} + +static gboolean +gst_rpi_cam_src_stop (GstBaseSrc * parent) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (parent); + if (src->started) + raspi_capture_stop (src->capture_state); + raspi_capture_free (src->capture_state); + src->capture_state = NULL; + return TRUE; +} + +static gboolean +gst_rpi_cam_src_send_event (GstElement * parent, GstEvent * event) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (parent); + gboolean ret; + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CUSTOM_DOWNSTREAM: + case GST_EVENT_CUSTOM_UPSTREAM: + if (gst_video_event_is_force_key_unit (event)) { + if (src->started) { + ret = raspi_capture_request_i_frame (src->capture_state); + } else { + ret = FALSE; + } + gst_event_unref (event); + } else { + ret = GST_ELEMENT_CLASS (parent_class)->send_event (parent, event); + } + break; + default: + ret = GST_ELEMENT_CLASS (parent_class)->send_event (parent, event); + break; + } + return ret; +} + +static gboolean +gst_rpi_cam_src_event (GstBaseSrc * parent, GstEvent * event) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (parent); + gboolean ret; + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CUSTOM_DOWNSTREAM: + case GST_EVENT_CUSTOM_UPSTREAM: + if (gst_video_event_is_force_key_unit (event)) { + if (src->started) { + ret = raspi_capture_request_i_frame (src->capture_state); + } else { + ret = FALSE; + } + } else { + ret = GST_BASE_SRC_CLASS (parent_class)->event (parent, event); + } + break; + default: + ret = GST_BASE_SRC_CLASS (parent_class)->event (parent, event); + break; + } + return ret; +} + +static GstCaps * +gst_rpi_cam_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (bsrc); + GstCaps *caps; + gint i; + + caps = gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (bsrc)); + if (src->capture_state == NULL) + goto done; + /* FIXME: Retrieve limiting parameters from the camera module, max width/height fps-range */ + caps = gst_caps_make_writable (caps); + for (i = 0; i < gst_caps_get_size (caps); i++) { + GstStructure *s = gst_caps_get_structure (caps, i); + if (gst_structure_has_name (s, "video/x-h264")) { + gst_caps_set_simple (caps, "width", GST_TYPE_INT_RANGE, 1, 1920, "height", + GST_TYPE_INT_RANGE, 1, 1080, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, + RPICAMSRC_MAX_FPS, 1, NULL); + } + else { + gst_caps_set_simple (caps, "width", GST_TYPE_INT_RANGE, 1, 3240, "height", + GST_TYPE_INT_RANGE, 1, 2464, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, + RPICAMSRC_MAX_FPS, 1, NULL); + } + } +done: + GST_DEBUG_OBJECT (src, "get_caps returning %" GST_PTR_FORMAT, caps); + return caps; +} + +static gboolean +gst_rpi_cam_src_set_caps (GstBaseSrc * bsrc, GstCaps * caps) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (bsrc); + GstVideoInfo info; + GstStructure *structure; + const gchar *profile_str = NULL; + + GST_DEBUG_OBJECT (src, "In set_caps %" GST_PTR_FORMAT, caps); + if (!gst_video_info_from_caps (&info, caps)) + return FALSE; + + structure = gst_caps_get_structure (caps, 0); + 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, "constrained-baseline")) + src->capture_config.profile = MMAL_VIDEO_PROFILE_H264_CONSTRAINED_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")) { +#ifdef USE_JPEG_CODEC + src->capture_config.encoding = MMAL_ENCODING_JPEG; +#else + src->capture_config.encoding = MMAL_ENCODING_MJPEG; +#endif + } + 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; + src->capture_config.height = info.height; + src->capture_config.fps_n = info.fps_n; + src->capture_config.fps_d = info.fps_d; + + if (info.fps_n != 0 && info.fps_d != 0) + src->duration = gst_util_uint64_scale_int (GST_SECOND, info.fps_d, + info.fps_n); + else + src->duration = GST_CLOCK_TIME_NONE; + + return TRUE; +} + +static gboolean +gst_rpi_cam_src_decide_allocation (GstBaseSrc * bsrc, GstQuery * query) +{ + GST_LOG_OBJECT (bsrc, "In decide_allocation"); + return GST_BASE_SRC_CLASS (parent_class)->decide_allocation (bsrc, query); +} + +static GstCaps * +gst_rpi_cam_src_fixate (GstBaseSrc * basesrc, GstCaps * caps) +{ + GstStructure *structure; + gint i; + GST_DEBUG_OBJECT (basesrc, "fixating caps %" GST_PTR_FORMAT, caps); + caps = gst_caps_make_writable (caps); + for (i = 0; i < gst_caps_get_size (caps); ++i) { + structure = gst_caps_get_structure (caps, i); + /* Fixate to 1920x1080 resolution if possible */ + gst_structure_fixate_field_nearest_int (structure, "width", 1920); + gst_structure_fixate_field_nearest_int (structure, "height", 1080); + gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1); + gst_structure_fixate_field (structure, "format"); + } + + GST_DEBUG_OBJECT (basesrc, "fixated caps %" GST_PTR_FORMAT, caps); + caps = GST_BASE_SRC_CLASS (parent_class)->fixate (basesrc, caps); + return caps; +} + +static GstFlowReturn +gst_rpi_cam_src_create (GstPushSrc * parent, GstBuffer ** buf) +{ + GstRpiCamSrc *src = GST_RPICAMSRC (parent); + GstFlowReturn ret; + GstClock *clock = NULL; + GstClockTime base_time; + + if (!src->started) { + g_mutex_lock (&src->config_lock); + raspi_capture_update_config (src->capture_state, &src->capture_config, FALSE); + src->capture_config.change_flags = 0; + g_mutex_unlock (&src->config_lock); + + if (!raspi_capture_start (src->capture_state)) + return GST_FLOW_ERROR; + src->started = TRUE; + } + + GST_OBJECT_LOCK (src); + if ((clock = GST_ELEMENT_CLOCK (src)) != NULL) + gst_object_ref (clock); + base_time = GST_ELEMENT_CAST (src)->base_time; + GST_OBJECT_UNLOCK (src); + + g_mutex_lock (&src->config_lock); + if (src->capture_config.change_flags) { + raspi_capture_update_config (src->capture_state, &src->capture_config, TRUE); + src->capture_config.change_flags = 0; + } + g_mutex_unlock (&src->config_lock); + + /* FIXME: Use custom allocator */ + ret = raspi_capture_fill_buffer (src->capture_state, buf, clock, base_time); + if (*buf) { + GST_LOG_OBJECT (src, "Made buffer of size %" G_GSIZE_FORMAT, + gst_buffer_get_size (*buf)); + /* Only set the duration when we have a PTS update from the rpi encoder. + * not every buffer is a frame */ + if (GST_BUFFER_PTS_IS_VALID (*buf)) + GST_BUFFER_DURATION (*buf) = src->duration; + } + + if (ret == GST_FLOW_ERROR_TIMEOUT) { + GST_ELEMENT_ERROR (src, STREAM, FAILED, + ("Camera capture timed out."), + ("Waiting for a buffer from the camera took too long.")); + ret = GST_FLOW_ERROR; + } + + if (clock) + gst_object_unref (clock); + return ret; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean ret; + GST_DEBUG_CATEGORY_INIT (gst_rpi_cam_src_debug, "rpicamsrc", + 0, "rpicamsrc debug"); + ret = gst_element_register (plugin, "rpicamsrc", GST_RANK_NONE, + GST_TYPE_RPICAMSRC); +#if GST_CHECK_VERSION (1,4,0) + ret &= gst_device_provider_register (plugin, "rpicamsrcdeviceprovider", + GST_RANK_PRIMARY, GST_TYPE_RPICAMSRC_DEVICE_PROVIDER); +#endif + return ret; +} + +#ifndef PACKAGE +#define PACKAGE "gstrpicamsrc" +#endif + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + rpicamsrc, + "Raspberry Pi Camera Source", + plugin_init, VERSION, "LGPL", "GStreamer", "http://gstreamer.net/") diff --git a/sys/rpicamsrc/gstrpicamsrc.h b/sys/rpicamsrc/gstrpicamsrc.h new file mode 100644 index 000000000..68fdc1829 --- /dev/null +++ b/sys/rpicamsrc/gstrpicamsrc.h @@ -0,0 +1,113 @@ +/* + * GStreamer + * Copyright (C) 2013 Jan Schmidt <jan@centricular.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Alternatively, the contents of this file may be used under the + * GNU Lesser General Public License Version 2.1 (the "LGPL"), in + * which case the following provisions apply instead of the ones + * mentioned above: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_RPICAMSRC_H__ +#define __GST_RPICAMSRC_H__ + +#include <gst/gst.h> +#include <gst/base/gstpushsrc.h> +#include <gst/pbutils/pbutils.h> /* only used for GST_PLUGINS_BASE_VERSION_* */ +#include "RaspiCapture.h" + +G_BEGIN_DECLS + +#define GST_TYPE_RPICAMSRC (gst_rpi_cam_src_get_type()) +#define GST_RPICAMSRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RPICAMSRC,GstRpiCamSrc)) +#define GST_RPICAMSRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RPICAMSRC,GstRpiCamSrcClass)) +#define GST_IS_RPICAMSRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RPICAMSRC)) +#define GST_IS_RPICAMSRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RPICAMSRC)) + +#if GST_CHECK_PLUGINS_BASE_VERSION(1, 9, 2) +#define GST_RPI_CAM_SRC_ENABLE_VIDEO_DIRECTION +#endif + +typedef struct _GstRpiCamSrc GstRpiCamSrc; +typedef struct _GstRpiCamSrcClass GstRpiCamSrcClass; + +struct _GstRpiCamSrc +{ + GstPushSrc parent; + + GstPad *video_srcpad; + + RASPIVID_CONFIG capture_config; + RASPIVID_STATE *capture_state; + gboolean started; + + GMutex config_lock; + + /* channels for interface */ + GList *channels; + +#ifdef GST_RPI_CAM_SRC_ENABLE_VIDEO_DIRECTION + GstVideoOrientationMethod orientation; +#endif + + GstClockTime duration; +}; + +struct _GstRpiCamSrcClass +{ + GstPushSrcClass parent_class; +}; + +GType gst_rpi_cam_src_get_type (void); + +typedef enum { + GST_RPI_CAM_SRC_SENSOR_MODE_AUTOMATIC = 0, + GST_RPI_CAM_SRC_SENSOR_MODE_1920x1080 = 1, + GST_RPI_CAM_SRC_SENSOR_MODE_2592x1944_FAST = 2, + GST_RPI_CAM_SRC_SENSOR_MODE_2592x1944_SLOW = 3, + GST_RPI_CAM_SRC_SENSOR_MODE_1296x972 = 4, + GST_RPI_CAM_SRC_SENSOR_MODE_1296x730 = 5, + GST_RPI_CAM_SRC_SENSOR_MODE_640x480_SLOW = 6, + GST_RPI_CAM_SRC_SENSOR_MODE_640x480_FAST = 7 +} GstRpiCamSrcSensorMode; + +G_END_DECLS + +#endif /* __GST_RPICAMSRC_H__ */ diff --git a/sys/rpicamsrc/gstrpicamsrcdeviceprovider.c b/sys/rpicamsrc/gstrpicamsrcdeviceprovider.c new file mode 100644 index 000000000..ff8083227 --- /dev/null +++ b/sys/rpicamsrc/gstrpicamsrcdeviceprovider.c @@ -0,0 +1,149 @@ +/* GStreamer Raspberry Pi Camera Source Device Provider + * Copyright (C) 2014 Tim-Philipp Müller <tim@centricular.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstrpicamsrcdeviceprovider.h" + +#include <string.h> + +#if GST_CHECK_VERSION (1,4,0) + +#include "RaspiCapture.h" + +/* FIXME: translations */ +#define _(s) s + +static GstRpiCamSrcDevice * gst_rpi_cam_src_device_new (void); + +G_DEFINE_TYPE (GstRpiCamSrcDeviceProvider, gst_rpi_cam_src_device_provider, + GST_TYPE_DEVICE_PROVIDER); + + +static GList *gst_rpi_cam_src_device_provider_probe (GstDeviceProvider * provider); + +static void +gst_rpi_cam_src_device_provider_class_init (GstRpiCamSrcDeviceProviderClass * klass) +{ + GstDeviceProviderClass *dprovider_class = GST_DEVICE_PROVIDER_CLASS (klass); + + dprovider_class->probe = gst_rpi_cam_src_device_provider_probe; + + gst_device_provider_class_set_static_metadata (dprovider_class, + "Raspberry Pi Camera Source Device Provider", "Source/Video", + "Lists Raspberry Pi camera devices", + "Tim-Philipp Müller <tim@centricular.com>"); +} + +static void +gst_rpi_cam_src_device_provider_init (GstRpiCamSrcDeviceProvider * provider) +{ + raspicapture_init (); +} + +static GList * +gst_rpi_cam_src_device_provider_probe (GstDeviceProvider * provider) +{ + GstRpiCamSrcDevice *device; + int supported = 0, detected = 0; + + raspicamcontrol_get_camera (&supported, &detected); + + if (!detected) { + GST_INFO ("No Raspberry Pi camera module detected."); + return NULL; + } else if (!supported) { + GST_WARNING ("Raspberry Pi camera module not supported, make sure to enable it."); + return NULL; + } + + GST_INFO ("Raspberry Pi camera module detected and supported."); + + device = gst_rpi_cam_src_device_new (); + + return g_list_append (NULL, device); +} + +G_DEFINE_TYPE (GstRpiCamSrcDevice, gst_rpi_cam_src_device, GST_TYPE_DEVICE); + +static GstElement *gst_rpi_cam_src_device_create_element (GstDevice * device, + const gchar * name); + +static void +gst_rpi_cam_src_device_class_init (GstRpiCamSrcDeviceClass * klass) +{ + GstDeviceClass *device_class = GST_DEVICE_CLASS (klass); + + device_class->create_element = gst_rpi_cam_src_device_create_element; +} + +static void +gst_rpi_cam_src_device_init (GstRpiCamSrcDevice * device) +{ + /* nothing to do here */ +} + +static GstElement * +gst_rpi_cam_src_device_create_element (GstDevice * device, const gchar * name) +{ + return gst_element_factory_make ("rpicamsrc", name); +} + +static GstRpiCamSrcDevice * +gst_rpi_cam_src_device_new (void) +{ + GstRpiCamSrcDevice *device; + GValue profiles = G_VALUE_INIT; + GValue val = G_VALUE_INIT; + GstStructure *s; + GstCaps *caps; + + /* FIXME: retrieve limits from the camera module, max width/height/fps etc. */ + s = gst_structure_new ("video/x-h264", + "width", GST_TYPE_INT_RANGE, 1, 1920, + "height", GST_TYPE_INT_RANGE, 1, 1080, + "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, RPICAMSRC_MAX_FPS, 1, + "stream-format", G_TYPE_STRING, "byte-stream", + "alignment", G_TYPE_STRING, "au", + NULL); + + g_value_init (&profiles, GST_TYPE_LIST); + g_value_init (&val, G_TYPE_STRING); + g_value_set_static_string (&val, "high"); + gst_value_list_append_value (&profiles, &val); + g_value_set_static_string (&val, "main"); + gst_value_list_append_value (&profiles, &val); + g_value_set_static_string (&val, "baseline"); + gst_value_list_append_and_take_value (&profiles, &val); + gst_structure_take_value (s, "profiles", &profiles); + + caps = gst_caps_new_full (s, NULL); + + device = g_object_new (GST_TYPE_RPICAMSRC_DEVICE, + "display-name", _("Raspberry Pi Camera Module"), + "device-class", "Video/Source", "caps", caps, NULL); + + gst_caps_unref (caps); + + return device; +} + +#endif /* GST_CHECK_VERSION (1,4,0) */ diff --git a/sys/rpicamsrc/gstrpicamsrcdeviceprovider.h b/sys/rpicamsrc/gstrpicamsrcdeviceprovider.h new file mode 100644 index 000000000..cd5e9aad3 --- /dev/null +++ b/sys/rpicamsrc/gstrpicamsrcdeviceprovider.h @@ -0,0 +1,79 @@ +/* GStreamer Raspberry Pi Camera Source Device Provider + * Copyright (C) 2014 Tim-Philipp Müller <tim@centricular.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_RPICAMSRC_DEVICE_PROVIDER_H__ +#define __GST_RPICAMSRC_DEVICE_PROVIDER_H__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> + +#if GST_CHECK_VERSION (1,4,0) + +G_BEGIN_DECLS + +typedef struct _GstRpiCamSrcDeviceProvider GstRpiCamSrcDeviceProvider; +typedef struct _GstRpiCamSrcDeviceProviderClass GstRpiCamSrcDeviceProviderClass; + +#define GST_TYPE_RPICAMSRC_DEVICE_PROVIDER (gst_rpi_cam_src_device_provider_get_type()) +#define GST_IS_RPICAMSRC_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RPICAMSRC_DEVICE_PROVIDER)) +#define GST_IS_RPICAMSRC_DEVICE_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RPICAMSRC_DEVICE_PROVIDER)) +#define GST_RPICAMSRC_DEVICE_PROVIDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RPICAMSRC_DEVICE_PROVIDER, GstRpiCamSrcDeviceProviderClass)) +#define GST_RPICAMSRC_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RPICAMSRC_DEVICE_PROVIDER, GstRpiCamSrcDeviceProvider)) +#define GST_RPICAMSRC_DEVICE_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE_PROVIDER, GstRpiCamSrcDeviceProviderClass)) +#define GST_RPICAMSRC_DEVICE_PROVIDER_CAST(obj) ((GstRpiCamSrcDeviceProvider *)(obj)) + +struct _GstRpiCamSrcDeviceProvider { + GstDeviceProvider parent; +}; + +struct _GstRpiCamSrcDeviceProviderClass { + GstDeviceProviderClass parent_class; +}; + +GType gst_rpi_cam_src_device_provider_get_type (void); + +typedef struct _GstRpiCamSrcDevice GstRpiCamSrcDevice; +typedef struct _GstRpiCamSrcDeviceClass GstRpiCamSrcDeviceClass; + +#define GST_TYPE_RPICAMSRC_DEVICE (gst_rpi_cam_src_device_get_type()) +#define GST_IS_RPICAMSRC_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RPICAMSRC_DEVICE)) +#define GST_IS_RPICAMSRC_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RPICAMSRC_DEVICE)) +#define GST_RPICAMSRC_DEVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RPICAMSRC_DEVICE, GstRpiCamSrcDeviceClass)) +#define GST_RPICAMSRC_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RPICAMSRC_DEVICE, GstRpiCamSrcDevice)) +#define GST_RPICAMSRC_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE, GstRpiCamSrcDeviceClass)) +#define GST_RPICAMSRC_DEVICE_CAST(obj) ((GstRpiCamSrcDevice *)(obj)) + +struct _GstRpiCamSrcDevice { + GstDevice parent; +}; + +struct _GstRpiCamSrcDeviceClass { + GstDeviceClass parent_class; +}; + +GType gst_rpi_cam_src_device_get_type (void); + +G_END_DECLS + +#endif /* GST_CHECK_VERSION (1,4,0) */ + +#endif /* __GST_RPICAMSRC_DEVICE_PROVIDER_H__ */ diff --git a/sys/rpicamsrc/meson.build b/sys/rpicamsrc/meson.build new file mode 100644 index 000000000..c3a1651ab --- /dev/null +++ b/sys/rpicamsrc/meson.build @@ -0,0 +1,24 @@ +rpicamsrc_sources = [ + 'gstrpicamsrc.c', + 'gstrpicamsrcdeviceprovider.c', + 'RaspiCapture.c', + 'RaspiCamControl.c', + 'RaspiPreview.c', + 'RaspiCLI.c', +] + +# glib-mkenums +gnome = import('gnome') + +enums = gnome.mkenums_simple('gstrpicam-enum-types', + sources: 'gstrpicam_types.h', + identifier_prefix: 'GstRpiCamSrc', + symbol_prefix: 'gst_rpi_cam_src') + +library('gstrpicamsrc', + rpicamsrc_sources, enums, + c_args : gst_rpicamsrc_args, + include_directories : config_inc, + dependencies : [gst_dep, gstbase_dep, gstvideo_dep] + mmal_deps, + install : true, + install_dir : plugins_install_dir) |