/* *INDENT-OFF* */ /* * Copyright (c) 2013 Jan Schmidt 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 #include #include #include #include #include #include #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 /// 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 ", 1 }, { CommandHeight, "-height", "h", "Set image height ", 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 (to write to stdout, use '-o -'). If not specified, no file is saved", 1 }, { CommandLink, "-latest", "l", "Link latest complete image to 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 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;inumExifTags;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 -- or - 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;jencoding = 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;inumExifTags && 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;iuserdata = (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;qqueue); 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; } /* *INDENT-ON* */