1 /*
2 Copyright (c) 2018, Raspberry Pi (Trading) Ltd.
3 Copyright (c) 2013, Broadcom Europe Ltd
4 Copyright (c) 2013, James Hughes
5 All rights reserved.
6 
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9     * Redistributions of source code must retain the above copyright
10       notice, this list of conditions and the following disclaimer.
11     * Redistributions in binary form must reproduce the above copyright
12       notice, this list of conditions and the following disclaimer in the
13       documentation and/or other materials provided with the distribution.
14     * Neither the name of the copyright holder nor the
15       names of its contributors may be used to endorse or promote products
16       derived from this software without specific prior written permission.
17 
18 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
22 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29 
30 /*
31  * \file RaspiStillYUV.c
32  * Command line program to capture a still frame and dump uncompressed it to file.
33  * Also optionally display a preview/viewfinder of current camera input.
34  *
35  * Description
36  *
37  * 2 components are created; camera and preview.
38  * Camera component has three ports, preview, video and stills.
39  * Preview is connected using standard mmal connections, the stills output
40  * is written straight to the file in YUV 420 format via the requisite buffer
41  * callback. video port is not used
42  *
43  * We use the RaspiCamControl code to handle the specific camera settings.
44  * We use the RaspiPreview code to handle the generic preview
45  */
46 
47 // We use some GNU extensions (basename)
48 #ifndef _GNU_SOURCE
49 #define _GNU_SOURCE
50 #endif
51 
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <memory.h>
56 #include <sysexits.h>
57 #include <unistd.h>
58 #include <errno.h>
59 
60 #include "bcm_host.h"
61 #include "interface/vcos/vcos.h"
62 
63 #include "interface/mmal/mmal.h"
64 #include "interface/mmal/mmal_logging.h"
65 #include "interface/mmal/mmal_buffer.h"
66 #include "interface/mmal/util/mmal_util.h"
67 #include "interface/mmal/util/mmal_util_params.h"
68 #include "interface/mmal/util/mmal_default_components.h"
69 #include "interface/mmal/util/mmal_connection.h"
70 
71 #include "RaspiCamControl.h"
72 #include "RaspiPreview.h"
73 #include "RaspiCLI.h"
74 #include "RaspiCommonSettings.h"
75 #include "RaspiHelpers.h"
76 #include "RaspiGPS.h"
77 
78 #include <semaphore.h>
79 
80 // Standard port setting for the camera component
81 #define MMAL_CAMERA_PREVIEW_PORT 0
82 #define MMAL_CAMERA_VIDEO_PORT 1
83 #define MMAL_CAMERA_CAPTURE_PORT 2
84 
85 // Stills format information
86 // 0 implies variable
87 #define STILLS_FRAME_RATE_NUM 0
88 #define STILLS_FRAME_RATE_DEN 1
89 
90 /// Video render needs at least 2 buffers.
91 #define VIDEO_OUTPUT_BUFFERS_NUM 3
92 
93 /// Frame advance method
94 enum
95 {
96    FRAME_NEXT_SINGLE,
97    FRAME_NEXT_TIMELAPSE,
98    FRAME_NEXT_KEYPRESS,
99    FRAME_NEXT_FOREVER,
100    FRAME_NEXT_GPIO,
101    FRAME_NEXT_SIGNAL,
102    FRAME_NEXT_IMMEDIATELY
103 };
104 
105 #define CAMERA_SETTLE_TIME       1000
106 
107 int mmal_status_to_int(MMAL_STATUS_T status);
108 
109 /** Structure containing all state information for the current run
110  */
111 typedef struct
112 {
113    RASPICOMMONSETTINGS_PARAMETERS common_settings; /// Non-app specific settings
114    int timeout;                        /// Time taken before frame is grabbed and app then shuts down. Units are milliseconds
115    char *linkname;                     /// filename of output file
116    int timelapse;                      /// Delay between each picture in timelapse mode. If 0, disable timelapse
117    MMAL_FOURCC_T encoding;             /// Use a MMAL encoding other than YUV
118    int fullResPreview;                 /// If set, the camera preview port runs at capture resolution. Reduces fps.
119    int frameNextMethod;                /// Which method to use to advance to next frame
120    int burstCaptureMode;               /// Enable burst mode
121    int onlyLuma;                       /// Only output the luma / Y plane of the YUV data
122 
123    RASPIPREVIEW_PARAMETERS preview_parameters;    /// Preview setup parameters
124    RASPICAM_CAMERA_PARAMETERS camera_parameters; /// Camera setup parameters
125 
126    MMAL_COMPONENT_T *camera_component;    /// Pointer to the camera component
127    MMAL_COMPONENT_T *null_sink_component;    /// Pointer to the camera component
128    MMAL_CONNECTION_T *preview_connection; /// Pointer to the connection from camera to preview
129    MMAL_POOL_T *camera_pool;              /// Pointer to the pool of buffers used by camera stills port
130 } RASPISTILLYUV_STATE;
131 
132 /** Struct used to pass information in camera still port userdata to callback
133  */
134 typedef struct
135 {
136    FILE *file_handle;                   /// File handle to write buffer data to.
137    VCOS_SEMAPHORE_T complete_semaphore; /// semaphore which is posted when we reach end of frame (indicates end of capture or fault)
138    RASPISTILLYUV_STATE *pstate;            /// pointer to our state in case required in callback
139 } PORT_USERDATA;
140 
141 /// Comamnd ID's and Structure defining our command line options
142 enum
143 {
144    CommandTimeout,
145    CommandTimelapse,
146    CommandUseRGB,
147    CommandFullResPreview,
148    CommandLink,
149    CommandKeypress,
150    CommandSignal,
151    CommandBurstMode,
152    CommandOnlyLuma,
153    CommandUseBGR
154 };
155 
156 static COMMAND_LIST cmdline_commands[] =
157 {
158    { CommandTimeout, "-timeout",    "t",  "Time (in ms) before takes picture and shuts down. If not specified set to 5s", 1 },
159    { CommandTimelapse,"-timelapse", "tl", "Timelapse mode. Takes a picture every <t>ms", 1},
160    { CommandUseRGB,  "-rgb",        "rgb","Save as RGB data rather than YUV", 0},
161    { CommandFullResPreview,"-fullpreview","fp", "Run the preview using the still capture resolution (may reduce preview fps)", 0},
162    { CommandLink,    "-latest",     "l",  "Link latest complete image to filename <filename>", 1},
163    { CommandKeypress,"-keypress",   "k",  "Wait between captures for a ENTER, X then ENTER to exit", 0},
164    { CommandSignal,  "-signal",     "s",  "Wait between captures for a SIGUSR1 from another process", 0},
165    { CommandBurstMode, "-burst",    "bm", "Enable 'burst capture mode'", 0},
166    { CommandOnlyLuma,  "-luma",     "y",  "Only output the luma / Y of the YUV data'", 0},
167    { CommandUseBGR,  "-bgr",        "bgr","Save as BGR data rather than YUV", 0},
168 };
169 
170 static int cmdline_commands_size = sizeof(cmdline_commands) / sizeof(cmdline_commands[0]);
171 
172 static struct
173 {
174    char *description;
175    int nextFrameMethod;
176 } next_frame_description[] =
177 {
178    {"Single capture",         FRAME_NEXT_SINGLE},
179    {"Capture on timelapse",   FRAME_NEXT_TIMELAPSE},
180    {"Capture on keypress",    FRAME_NEXT_KEYPRESS},
181    {"Run forever",            FRAME_NEXT_FOREVER},
182    {"Capture on GPIO",        FRAME_NEXT_GPIO},
183    {"Capture on signal",      FRAME_NEXT_SIGNAL},
184 };
185 
186 static int next_frame_description_size = sizeof(next_frame_description) / sizeof(next_frame_description[0]);
187 
188 /**
189  * Assign a default set of parameters to the state passed in
190  *
191  * @param state Pointer to state structure to assign defaults to
192  */
default_status(RASPISTILLYUV_STATE * state)193 static void default_status(RASPISTILLYUV_STATE *state)
194 {
195    if (!state)
196    {
197       vcos_assert(0);
198       return;
199    }
200 
201    // Default everything to zero
202    memset(state, 0, sizeof(RASPISTILLYUV_STATE));
203 
204    raspicommonsettings_set_defaults(&state->common_settings);
205 
206    // Now set anything non-zero
207    state->timeout = -1; // replaced with 5000ms later if unset
208    state->timelapse = 0;
209    state->linkname = NULL;
210    state->fullResPreview = 0;
211    state->frameNextMethod = FRAME_NEXT_SINGLE;
212    state->burstCaptureMode=0;
213    state->onlyLuma = 0;
214 
215    // Setup preview window defaults
216    raspipreview_set_defaults(&state->preview_parameters);
217 
218    // Set up the camera_parameters to default
219    raspicamcontrol_set_defaults(&state->camera_parameters);
220 }
221 
222 /**
223  * Dump image state parameters to stderr. Used for debugging
224  *
225  * @param state Pointer to state structure to assign defaults to
226  */
dump_status(RASPISTILLYUV_STATE * state)227 static void dump_status(RASPISTILLYUV_STATE *state)
228 {
229    int i;
230    if (!state)
231    {
232       vcos_assert(0);
233       return;
234    }
235 
236    raspicommonsettings_dump_parameters(&state->common_settings);
237 
238    fprintf(stderr, "Time delay %d, Timelapse %d\n", state->timeout, state->timelapse);
239    fprintf(stderr, "Link to latest frame enabled ");
240    if (state->linkname)
241    {
242       fprintf(stderr, " yes, -> %s\n", state->linkname);
243    }
244    else
245    {
246       fprintf(stderr, " no\n");
247    }
248    fprintf(stderr, "Full resolution preview %s\n", state->fullResPreview ? "Yes": "No");
249 
250    fprintf(stderr, "Capture method : ");
251    for (i=0; i<next_frame_description_size; i++)
252    {
253       if (state->frameNextMethod == next_frame_description[i].nextFrameMethod)
254          fprintf(stderr, "%s", next_frame_description[i].description);
255    }
256    fprintf(stderr, "\n\n");
257 
258    raspipreview_dump_parameters(&state->preview_parameters);
259    raspicamcontrol_dump_parameters(&state->camera_parameters);
260 }
261 
262 /**
263  * Display usage information for the application to stdout
264  *
265  * @param app_name String to display as the application name
266  *
267  */
application_help_message(char * app_name)268 static void application_help_message(char *app_name)
269 {
270    fprintf(stdout, "Runs camera for specific time, and take uncompressed YUV or RGB capture at end if requested\n\n");
271    fprintf(stdout, "usage: %s [options]\n\n", app_name);
272 
273    fprintf(stdout, "Image parameter commands\n\n");
274 
275    raspicli_display_help(cmdline_commands, cmdline_commands_size);
276 
277    return;
278 }
279 
280 /**
281  * Parse the incoming command line and put resulting parameters in to the state
282  *
283  * @param argc Number of arguments in command line
284  * @param argv Array of pointers to strings from command line
285  * @param state Pointer to state structure to assign any discovered parameters to
286  * @return non-0 if failed for some reason, 0 otherwise
287  */
parse_cmdline(int argc,const char ** argv,RASPISTILLYUV_STATE * state)288 static int parse_cmdline(int argc, const char **argv, RASPISTILLYUV_STATE *state)
289 {
290    // Parse the command line arguments.
291    // We are looking for --<something> or -<abbreviation of something>
292 
293    int valid = 1; // set 0 if we have a bad parameter
294    int i;
295 
296    for (i = 1; i < argc && valid; i++)
297    {
298       int command_id, num_parameters;
299 
300       if (!argv[i])
301          continue;
302 
303       if (argv[i][0] != '-')
304       {
305          valid = 0;
306          continue;
307       }
308 
309       // Assume parameter is valid until proven otherwise
310       valid = 1;
311 
312       command_id = raspicli_get_command_id(cmdline_commands, cmdline_commands_size, &argv[i][1], &num_parameters);
313 
314       // If we found a command but are missing a parameter, continue (and we will drop out of the loop)
315       if (command_id != -1 && num_parameters > 0 && (i + 1 >= argc) )
316          continue;
317 
318       //  We are now dealing with a command line option
319       switch (command_id)
320       {
321       case CommandLink :
322       {
323          int len = strlen(argv[i+1]);
324          if (len)
325          {
326             state->linkname = malloc(len + 10);
327             vcos_assert(state->linkname);
328             if (state->linkname)
329                strncpy(state->linkname, argv[i + 1], len+1);
330             i++;
331          }
332          else
333             valid = 0;
334          break;
335 
336       }
337 
338       case CommandTimeout: // Time to run viewfinder for before taking picture, in seconds
339       {
340          if (sscanf(argv[i + 1], "%d", &state->timeout) == 1)
341          {
342             // Ensure that if previously selected CommandKeypress we don't overwrite it
343             if (state->timeout == 0 && state->frameNextMethod == FRAME_NEXT_SINGLE)
344                state->frameNextMethod = FRAME_NEXT_FOREVER;
345 
346             i++;
347          }
348          else
349             valid = 0;
350          break;
351       }
352 
353       case CommandTimelapse:
354          if (sscanf(argv[i + 1], "%u", &state->timelapse) != 1)
355             valid = 0;
356          else
357          {
358             if (state->timelapse)
359                state->frameNextMethod = FRAME_NEXT_TIMELAPSE;
360             else
361                state->frameNextMethod = FRAME_NEXT_IMMEDIATELY;
362 
363             i++;
364          }
365          break;
366 
367       case CommandUseRGB: // display lots of data during run
368          if (state->onlyLuma)
369          {
370             fprintf(stderr, "--luma and --rgb/--bgr are mutually exclusive\n");
371             valid = 0;
372          }
373          state->encoding = MMAL_ENCODING_RGB24;
374          break;
375 
376       case CommandFullResPreview:
377          state->fullResPreview = 1;
378          break;
379 
380       case CommandKeypress: // Set keypress between capture mode
381          state->frameNextMethod = FRAME_NEXT_KEYPRESS;
382 
383          if (state->timeout == -1)
384             state->timeout = 0;
385 
386          break;
387 
388       case CommandSignal:   // Set SIGUSR1 between capture mode
389          state->frameNextMethod = FRAME_NEXT_SIGNAL;
390          // Reenable the signal
391          signal(SIGUSR1, default_signal_handler);
392 
393          if (state->timeout == -1)
394             state->timeout = 0;
395 
396          break;
397 
398       case CommandBurstMode:
399          state->burstCaptureMode=1;
400          break;
401 
402       case CommandOnlyLuma:
403          if (state->encoding)
404          {
405             fprintf(stderr, "--luma and --rgb are mutually exclusive\n");
406             valid = 0;
407          }
408          state->onlyLuma = 1;
409          break;
410 
411       case CommandUseBGR:
412          if (state->onlyLuma)
413          {
414             fprintf(stderr, "--luma and --rgb/--bgr are mutually exclusive\n");
415             valid = 0;
416          }
417          state->encoding = MMAL_ENCODING_BGR24;
418          break;
419 
420       default:
421       {
422          // Try parsing for any image specific parameters
423          // result indicates how many parameters were used up, 0,1,2
424          // but we adjust by -1 as we have used one already
425          const char *second_arg = (i + 1 < argc) ? argv[i + 1] : NULL;
426 
427          int parms_used = (raspicamcontrol_parse_cmdline(&state->camera_parameters, &argv[i][1], second_arg));
428 
429          // Still unused, try common settings
430          if (!parms_used)
431             parms_used = raspicommonsettings_parse_cmdline(&state->common_settings, &argv[i][1], second_arg, &application_help_message);
432 
433          // Still unused, try preview options
434          if (!parms_used)
435             parms_used = raspipreview_parse_cmdline(&state->preview_parameters, &argv[i][1], second_arg);
436 
437          // If no parms were used, this must be a bad parameters
438          if (!parms_used)
439             valid = 0;
440          else
441             i += parms_used - 1;
442 
443          break;
444       }
445       }
446    }
447 
448    if (!valid)
449    {
450       fprintf(stderr, "Invalid command line option (%s)\n", argv[i-1]);
451       return 1;
452    }
453 
454    return 0;
455 }
456 
457 /**
458  *  buffer header callback function for camera output port
459  *
460  *  Callback will dump buffer data to the specific file
461  *
462  * @param port Pointer to port from which callback originated
463  * @param buffer mmal buffer header pointer
464  */
camera_buffer_callback(MMAL_PORT_T * port,MMAL_BUFFER_HEADER_T * buffer)465 static void camera_buffer_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
466 {
467    int complete = 0;
468    // We pass our file handle and other stuff in via the userdata field.
469 
470    PORT_USERDATA *pData = (PORT_USERDATA *)port->userdata;
471 
472    if (pData)
473    {
474       int bytes_written = 0;
475       int bytes_to_write = buffer->length;
476 
477       if (pData->pstate->onlyLuma)
478          bytes_to_write = vcos_min(buffer->length, port->format->es->video.width * port->format->es->video.height);
479 
480       if (bytes_to_write && pData->file_handle)
481       {
482          mmal_buffer_header_mem_lock(buffer);
483 
484          bytes_written = fwrite(buffer->data, 1, bytes_to_write, pData->file_handle);
485 
486          mmal_buffer_header_mem_unlock(buffer);
487       }
488 
489       // We need to check we wrote what we wanted - it's possible we have run out of storage.
490       if (buffer->length && bytes_written != bytes_to_write)
491       {
492          vcos_log_error("Unable to write buffer to file - aborting %d vs %d", bytes_written, bytes_to_write);
493          complete = 1;
494       }
495 
496       // Check end of frame or error
497       if (buffer->flags & (MMAL_BUFFER_HEADER_FLAG_FRAME_END | MMAL_BUFFER_HEADER_FLAG_TRANSMISSION_FAILED))
498          complete = 1;
499    }
500    else
501    {
502       vcos_log_error("Received a camera still buffer callback with no state");
503    }
504 
505    // release buffer back to the pool
506    mmal_buffer_header_release(buffer);
507 
508    // and send one back to the port (if still open)
509    if (port->is_enabled)
510    {
511       MMAL_STATUS_T status;
512       MMAL_BUFFER_HEADER_T *new_buffer = mmal_queue_get(pData->pstate->camera_pool->queue);
513 
514       // and back to the port from there.
515       if (new_buffer)
516       {
517          status = mmal_port_send_buffer(port, new_buffer);
518       }
519 
520       if (!new_buffer || status != MMAL_SUCCESS)
521          vcos_log_error("Unable to return the buffer to the camera still port");
522    }
523 
524    if (complete)
525    {
526       vcos_semaphore_post(&(pData->complete_semaphore));
527    }
528 }
529 
530 /**
531  * Create the camera component, set up its ports
532  *
533  * @param state Pointer to state control struct
534  *
535  * @return 0 if failed, pointer to component if successful
536  *
537  */
create_camera_component(RASPISTILLYUV_STATE * state)538 static MMAL_STATUS_T create_camera_component(RASPISTILLYUV_STATE *state)
539 {
540    MMAL_COMPONENT_T *camera = 0;
541    MMAL_ES_FORMAT_T *format;
542    MMAL_PORT_T *preview_port = NULL, *video_port = NULL, *still_port = NULL;
543    MMAL_STATUS_T status;
544    MMAL_POOL_T *pool;
545 
546    /* Create the component */
547    status = mmal_component_create(MMAL_COMPONENT_DEFAULT_CAMERA, &camera);
548 
549    if (status != MMAL_SUCCESS)
550    {
551       vcos_log_error("Failed to create camera component");
552       goto error;
553    }
554 
555    MMAL_PARAMETER_INT32_T camera_num =
556    {{MMAL_PARAMETER_CAMERA_NUM, sizeof(camera_num)}, state->common_settings.cameraNum};
557 
558    status = mmal_port_parameter_set(camera->control, &camera_num.hdr);
559 
560    if (status != MMAL_SUCCESS)
561    {
562       vcos_log_error("Could not select camera : error %d", status);
563       goto error;
564    }
565 
566    if (!camera->output_num)
567    {
568       status = MMAL_ENOSYS;
569       vcos_log_error("Camera doesn't have output ports");
570       goto error;
571    }
572 
573    status = mmal_port_parameter_set_uint32(camera->control, MMAL_PARAMETER_CAMERA_CUSTOM_SENSOR_CONFIG, state->common_settings.sensor_mode);
574 
575    if (status != MMAL_SUCCESS)
576    {
577       vcos_log_error("Could not set sensor mode : error %d", status);
578       goto error;
579    }
580 
581    preview_port = camera->output[MMAL_CAMERA_PREVIEW_PORT];
582    video_port = camera->output[MMAL_CAMERA_VIDEO_PORT];
583    still_port = camera->output[MMAL_CAMERA_CAPTURE_PORT];
584 
585    // Enable the camera, and tell it its control callback function
586    status = mmal_port_enable(camera->control, default_camera_control_callback);
587 
588    if (status != MMAL_SUCCESS )
589    {
590       vcos_log_error("Unable to enable control port : error %d", status);
591       goto error;
592    }
593 
594    //  set up the camera configuration
595    {
596       MMAL_PARAMETER_CAMERA_CONFIG_T cam_config =
597       {
598          { MMAL_PARAMETER_CAMERA_CONFIG, sizeof(cam_config) },
599          .max_stills_w = state->common_settings.width,
600          .max_stills_h = state->common_settings.height,
601          .stills_yuv422 = 0,
602          .one_shot_stills = 1,
603          .max_preview_video_w = state->preview_parameters.previewWindow.width,
604          .max_preview_video_h = state->preview_parameters.previewWindow.height,
605          .num_preview_video_frames = 3,
606          .stills_capture_circular_buffer_height = 0,
607          .fast_preview_resume = 0,
608          .use_stc_timestamp = MMAL_PARAM_TIMESTAMP_MODE_RESET_STC
609       };
610 
611       if (state->fullResPreview)
612       {
613          cam_config.max_preview_video_w = state->common_settings.width;
614          cam_config.max_preview_video_h = state->common_settings.height;
615       }
616 
617       mmal_port_parameter_set(camera->control, &cam_config.hdr);
618    }
619 
620    raspicamcontrol_set_all_parameters(camera, &state->camera_parameters);
621 
622    // Now set up the port formats
623 
624    format = preview_port->format;
625 
626    format->encoding = MMAL_ENCODING_OPAQUE;
627    format->encoding_variant = MMAL_ENCODING_I420;
628 
629    if(state->camera_parameters.shutter_speed > 6000000)
630    {
631       MMAL_PARAMETER_FPS_RANGE_T fps_range = {{MMAL_PARAMETER_FPS_RANGE, sizeof(fps_range)},
632          { 50, 1000 }, {166, 1000}
633       };
634       mmal_port_parameter_set(preview_port, &fps_range.hdr);
635    }
636    else if(state->camera_parameters.shutter_speed > 1000000)
637    {
638       MMAL_PARAMETER_FPS_RANGE_T fps_range = {{MMAL_PARAMETER_FPS_RANGE, sizeof(fps_range)},
639          { 166, 1000 }, {999, 1000}
640       };
641       mmal_port_parameter_set(preview_port, &fps_range.hdr);
642    }
643    if (state->fullResPreview)
644    {
645       // In this mode we are forcing the preview to be generated from the full capture resolution.
646       // This runs at a max of 15fps with the OV5647 sensor.
647       format->es->video.width = VCOS_ALIGN_UP(state->common_settings.width, 32);
648       format->es->video.height = VCOS_ALIGN_UP(state->common_settings.height, 16);
649       format->es->video.crop.x = 0;
650       format->es->video.crop.y = 0;
651       format->es->video.crop.width = state->common_settings.width;
652       format->es->video.crop.height = state->common_settings.height;
653       format->es->video.frame_rate.num = FULL_RES_PREVIEW_FRAME_RATE_NUM;
654       format->es->video.frame_rate.den = FULL_RES_PREVIEW_FRAME_RATE_DEN;
655    }
656    else
657    {
658       // Use a full FOV 4:3 mode
659       format->es->video.width = VCOS_ALIGN_UP(state->preview_parameters.previewWindow.width, 32);
660       format->es->video.height = VCOS_ALIGN_UP(state->preview_parameters.previewWindow.height, 16);
661       format->es->video.crop.x = 0;
662       format->es->video.crop.y = 0;
663       format->es->video.crop.width = state->preview_parameters.previewWindow.width;
664       format->es->video.crop.height = state->preview_parameters.previewWindow.height;
665       format->es->video.frame_rate.num = PREVIEW_FRAME_RATE_NUM;
666       format->es->video.frame_rate.den = PREVIEW_FRAME_RATE_DEN;
667    }
668 
669    status = mmal_port_format_commit(preview_port);
670 
671    if (status != MMAL_SUCCESS )
672    {
673       vcos_log_error("camera viewfinder format couldn't be set");
674       goto error;
675    }
676 
677    // Set the same format on the video  port (which we don't use here)
678    mmal_format_full_copy(video_port->format, format);
679    status = mmal_port_format_commit(video_port);
680 
681    if (status != MMAL_SUCCESS )
682    {
683       vcos_log_error("camera video format couldn't be set");
684       goto error;
685    }
686 
687    // Ensure there are enough buffers to avoid dropping frames
688    if (video_port->buffer_num < VIDEO_OUTPUT_BUFFERS_NUM)
689       video_port->buffer_num = VIDEO_OUTPUT_BUFFERS_NUM;
690 
691    format = still_port->format;
692 
693    if(state->camera_parameters.shutter_speed > 6000000)
694    {
695       MMAL_PARAMETER_FPS_RANGE_T fps_range = {{MMAL_PARAMETER_FPS_RANGE, sizeof(fps_range)},
696          { 50, 1000 }, {166, 1000}
697       };
698       mmal_port_parameter_set(still_port, &fps_range.hdr);
699    }
700    else if(state->camera_parameters.shutter_speed > 1000000)
701    {
702       MMAL_PARAMETER_FPS_RANGE_T fps_range = {{MMAL_PARAMETER_FPS_RANGE, sizeof(fps_range)},
703          { 167, 1000 }, {999, 1000}
704       };
705       mmal_port_parameter_set(still_port, &fps_range.hdr);
706    }
707    // Set our stills format on the stills  port
708    if (state->encoding)
709    {
710       format->encoding = state->encoding;
711       if (!mmal_util_rgb_order_fixed(still_port))
712       {
713          if (format->encoding == MMAL_ENCODING_RGB24)
714             format->encoding = MMAL_ENCODING_BGR24;
715          else if (format->encoding == MMAL_ENCODING_BGR24)
716             format->encoding = MMAL_ENCODING_RGB24;
717       }
718       format->encoding_variant = 0;  //Irrelevant when not in opaque mode
719    }
720    else
721    {
722       format->encoding = MMAL_ENCODING_I420;
723       format->encoding_variant = MMAL_ENCODING_I420;
724    }
725    format->es->video.width = VCOS_ALIGN_UP(state->common_settings.width, 32);
726    format->es->video.height = VCOS_ALIGN_UP(state->common_settings.height, 16);
727    format->es->video.crop.x = 0;
728    format->es->video.crop.y = 0;
729    format->es->video.crop.width = state->common_settings.width;
730    format->es->video.crop.height = state->common_settings.height;
731    format->es->video.frame_rate.num = STILLS_FRAME_RATE_NUM;
732    format->es->video.frame_rate.den = STILLS_FRAME_RATE_DEN;
733 
734    if (still_port->buffer_size < still_port->buffer_size_min)
735       still_port->buffer_size = still_port->buffer_size_min;
736 
737    still_port->buffer_num = still_port->buffer_num_recommended;
738 
739    status = mmal_port_parameter_set_boolean(video_port, MMAL_PARAMETER_ZERO_COPY, MMAL_TRUE);
740    if (status != MMAL_SUCCESS)
741    {
742       vcos_log_error("Failed to select zero copy");
743       goto error;
744    }
745 
746    status = mmal_port_format_commit(still_port);
747 
748    if (status != MMAL_SUCCESS )
749    {
750       vcos_log_error("camera still format couldn't be set");
751       goto error;
752    }
753 
754    /* Enable component */
755    status = mmal_component_enable(camera);
756 
757    if (status != MMAL_SUCCESS )
758    {
759       vcos_log_error("camera component couldn't be enabled");
760       goto error;
761    }
762 
763    /* Create pool of buffer headers for the output port to consume */
764    pool = mmal_port_pool_create(still_port, still_port->buffer_num, still_port->buffer_size);
765 
766    if (!pool)
767    {
768       vcos_log_error("Failed to create buffer header pool for camera still port %s", still_port->name);
769    }
770 
771    state->camera_pool = pool;
772    state->camera_component = camera;
773 
774    if (state->common_settings.verbose)
775       fprintf(stderr, "Camera component done\n");
776 
777    return status;
778 
779 error:
780 
781    if (camera)
782       mmal_component_destroy(camera);
783 
784    return status;
785 }
786 
787 /**
788  * Destroy the camera component
789  *
790  * @param state Pointer to state control struct
791  *
792  */
destroy_camera_component(RASPISTILLYUV_STATE * state)793 static void destroy_camera_component(RASPISTILLYUV_STATE *state)
794 {
795    if (state->camera_component)
796    {
797       mmal_component_destroy(state->camera_component);
798       state->camera_component = NULL;
799    }
800 }
801 
802 /**
803  * Allocates and generates a filename based on the
804  * user-supplied pattern and the frame number.
805  * On successful return, finalName and tempName point to malloc()ed strings
806  * which must be freed externally.  (On failure, returns nulls that
807  * don't need free()ing.)
808  *
809  * @param finalName pointer receives an
810  * @param pattern sprintf pattern with %d to be replaced by frame
811  * @param frame for timelapse, the frame number
812  * @return Returns a MMAL_STATUS_T giving result of operation
813 */
814 
create_filenames(char ** finalName,char ** tempName,char * pattern,int frame)815 MMAL_STATUS_T create_filenames(char** finalName, char** tempName, char * pattern, int frame)
816 {
817    *finalName = NULL;
818    *tempName = NULL;
819    if (0 > asprintf(finalName, pattern, frame) ||
820          0 > asprintf(tempName, "%s~", *finalName))
821    {
822       if (*finalName != NULL)
823       {
824          free(*finalName);
825       }
826       return MMAL_ENOMEM;    // It may be some other error, but it is not worth getting it right
827    }
828    return MMAL_SUCCESS;
829 }
830 
831 /**
832  * Function to wait in various ways (depending on settings) for the next frame
833  *
834  * @param state Pointer to the state data
835  * @param [in][out] frame The last frame number, adjusted to next frame number on output
836  * @return !0 if to continue, 0 if reached end of run
837  */
wait_for_next_frame(RASPISTILLYUV_STATE * state,int * frame)838 static int wait_for_next_frame(RASPISTILLYUV_STATE *state, int *frame)
839 {
840    static int64_t complete_time = -1;
841    int keep_running = 1;
842 
843    int64_t current_time =  get_microseconds64()/1000;
844 
845    if (complete_time == -1)
846       complete_time =  current_time + state->timeout;
847 
848    // if we have run out of time, flag we need to exit
849    // If timeout = 0 then always continue
850    if (current_time >= complete_time && state->timeout != 0)
851       keep_running = 0;
852 
853    switch (state->frameNextMethod)
854    {
855    case FRAME_NEXT_SINGLE :
856       // simple timeout for a single capture
857       vcos_sleep(state->timeout);
858       return 0;
859 
860    case FRAME_NEXT_FOREVER :
861    {
862       *frame+=1;
863 
864       // Have a sleep so we don't hog the CPU.
865       vcos_sleep(10000);
866 
867       // Run forever so never indicate end of loop
868       return 1;
869    }
870 
871    case FRAME_NEXT_TIMELAPSE :
872    {
873       static int64_t next_frame_ms = -1;
874 
875       // Always need to increment by at least one, may add a skip later
876       *frame += 1;
877 
878       if (next_frame_ms == -1)
879       {
880          vcos_sleep(CAMERA_SETTLE_TIME);
881 
882          // Update our current time after the sleep
883          current_time =  get_microseconds64()/1000;
884 
885          // Set our initial 'next frame time'
886          next_frame_ms = current_time + state->timelapse;
887       }
888       else
889       {
890          int64_t this_delay_ms = next_frame_ms - current_time;
891 
892          if (this_delay_ms < 0)
893          {
894             // We are already past the next exposure time
895             if (-this_delay_ms < -state->timelapse/2)
896             {
897                // Less than a half frame late, take a frame and hope to catch up next time
898                next_frame_ms += state->timelapse;
899                vcos_log_error("Frame %d is %d ms late", *frame, (int)(-this_delay_ms));
900             }
901             else
902             {
903                int nskip = 1 + (-this_delay_ms)/state->timelapse;
904                vcos_log_error("Skipping frame %d to restart at frame %d", *frame, *frame+nskip);
905                *frame += nskip;
906                this_delay_ms += nskip * state->timelapse;
907                vcos_sleep(this_delay_ms);
908                next_frame_ms += (nskip + 1) * state->timelapse;
909             }
910          }
911          else
912          {
913             vcos_sleep(this_delay_ms);
914             next_frame_ms += state->timelapse;
915          }
916       }
917 
918       return keep_running;
919    }
920 
921    case FRAME_NEXT_KEYPRESS :
922    {
923       int ch;
924 
925       if (state->common_settings.verbose)
926          fprintf(stderr, "Press Enter to capture, X then ENTER to exit\n");
927 
928       ch = getchar();
929       *frame+=1;
930       if (ch == 'x' || ch == 'X')
931          return 0;
932       else
933       {
934          return keep_running;
935       }
936    }
937 
938    case FRAME_NEXT_IMMEDIATELY :
939    {
940       // Not waiting, just go to next frame.
941       // Actually, we do need a slight delay here otherwise exposure goes
942       // badly wrong since we never allow it frames to work it out
943       // This could probably be tuned down.
944       // First frame has a much longer delay to ensure we get exposure to a steady state
945       if (*frame == 0)
946          vcos_sleep(CAMERA_SETTLE_TIME);
947       else
948          vcos_sleep(30);
949 
950       *frame+=1;
951 
952       return keep_running;
953    }
954 
955    case FRAME_NEXT_GPIO :
956    {
957       // Intended for GPIO firing of a capture
958       return 0;
959    }
960 
961    case FRAME_NEXT_SIGNAL :
962    {
963       // Need to wait for a SIGUSR1 signal
964       sigset_t waitset;
965       int sig;
966       int result = 0;
967 
968       sigemptyset( &waitset );
969       sigaddset( &waitset, SIGUSR1 );
970 
971       // We are multi threaded because we use mmal, so need to use the pthread
972       // variant of procmask to block SIGUSR1 so we can wait on it.
973       pthread_sigmask( SIG_BLOCK, &waitset, NULL );
974 
975       if (state->common_settings.verbose)
976       {
977          fprintf(stderr, "Waiting for SIGUSR1 to initiate capture\n");
978       }
979 
980       result = sigwait( &waitset, &sig );
981 
982       if (state->common_settings.verbose)
983       {
984          if( result == 0)
985          {
986             fprintf(stderr, "Received SIGUSR1\n");
987          }
988          else
989          {
990             fprintf(stderr, "Bad signal received - error %d\n", errno);
991          }
992       }
993 
994       *frame+=1;
995 
996       return keep_running;
997    }
998    } // end of switch
999 
1000    // Should have returned by now, but default to timeout
1001    return keep_running;
1002 }
1003 
rename_file(RASPISTILLYUV_STATE * state,FILE * output_file,const char * final_filename,const char * use_filename,int frame)1004 static void rename_file(RASPISTILLYUV_STATE *state, FILE *output_file,
1005                         const char *final_filename, const char *use_filename, int frame)
1006 {
1007    MMAL_STATUS_T status;
1008 
1009    fclose(output_file);
1010    vcos_assert(use_filename != NULL && final_filename != NULL);
1011    if (0 != rename(use_filename, final_filename))
1012    {
1013       vcos_log_error("Could not rename temp file to: %s; %s",
1014                      final_filename,strerror(errno));
1015    }
1016    if (state->linkname)
1017    {
1018       char *use_link;
1019       char *final_link;
1020       status = create_filenames(&final_link, &use_link, state->linkname, frame);
1021 
1022       // Create hard link if possible, symlink otherwise
1023       if (status != MMAL_SUCCESS
1024             || (0 != link(final_filename, use_link)
1025                 &&  0 != symlink(final_filename, use_link))
1026             || 0 != rename(use_link, final_link))
1027       {
1028          vcos_log_error("Could not link as filename: %s; %s",
1029                         state->linkname,strerror(errno));
1030       }
1031       if (use_link) free(use_link);
1032       if (final_link) free(final_link);
1033    }
1034 }
1035 
1036 /**
1037  * main
1038  */
main(int argc,const char ** argv)1039 int main(int argc, const char **argv)
1040 {
1041    // Our main data storage vessel..
1042    RASPISTILLYUV_STATE state;
1043    int exit_code = EX_OK;
1044 
1045    MMAL_STATUS_T status = MMAL_SUCCESS;
1046    MMAL_PORT_T *camera_preview_port = NULL;
1047    MMAL_PORT_T *camera_video_port = NULL;
1048    MMAL_PORT_T *camera_still_port = NULL;
1049    MMAL_PORT_T *preview_input_port = NULL;
1050    FILE *output_file = NULL;
1051 
1052    bcm_host_init();
1053 
1054    // Register our application with the logging system
1055    vcos_log_register("RaspiStill", VCOS_LOG_CATEGORY);
1056 
1057    signal(SIGINT, default_signal_handler);
1058 
1059    // Disable USR1 for the moment - may be reenabled if go in to signal capture mode
1060    signal(SIGUSR1, SIG_IGN);
1061 
1062    set_app_name(argv[0]);
1063 
1064    // Do we have any parameters
1065    if (argc == 1)
1066    {
1067       display_valid_parameters(basename(argv[0]), &application_help_message);
1068       exit(EX_USAGE);
1069    }
1070 
1071    default_status(&state);
1072 
1073    // Parse the command line and put options in to our status structure
1074    if (parse_cmdline(argc, argv, &state))
1075    {
1076       status = -1;
1077       exit(EX_USAGE);
1078    }
1079 
1080    if (state.timeout == -1)
1081       state.timeout = 5000;
1082 
1083    if (state.common_settings.gps)
1084       if (raspi_gps_setup(state.common_settings.verbose))
1085          state.common_settings.gps = false;
1086 
1087    // Setup for sensor specific parameters
1088    get_sensor_defaults(state.common_settings.cameraNum, state.common_settings.camera_name,
1089                        &state.common_settings.width, &state.common_settings.height);
1090 
1091    if (state.common_settings.verbose)
1092    {
1093       print_app_details(stderr);
1094       dump_status(&state);
1095    }
1096 
1097    // OK, we have a nice set of parameters. Now set up our components
1098    // We have two components. Camera and Preview
1099    // Camera is different in stills/video, but preview
1100    // is the same so handed off to a separate module
1101 
1102    if ((status = create_camera_component(&state)) != MMAL_SUCCESS)
1103    {
1104       vcos_log_error("%s: Failed to create camera component", __func__);
1105       exit_code = EX_SOFTWARE;
1106    }
1107    else if ((status = raspipreview_create(&state.preview_parameters)) != MMAL_SUCCESS)
1108    {
1109       vcos_log_error("%s: Failed to create preview component", __func__);
1110       destroy_camera_component(&state);
1111       exit_code = EX_SOFTWARE;
1112    }
1113    else
1114    {
1115       PORT_USERDATA callback_data;
1116 
1117       if (state.common_settings.verbose)
1118          fprintf(stderr, "Starting component connection stage\n");
1119 
1120       camera_preview_port = state.camera_component->output[MMAL_CAMERA_PREVIEW_PORT];
1121       camera_video_port   = state.camera_component->output[MMAL_CAMERA_VIDEO_PORT];
1122       camera_still_port   = state.camera_component->output[MMAL_CAMERA_CAPTURE_PORT];
1123 
1124       // Note we are lucky that the preview and null sink components use the same input port
1125       // so we can simple do this without conditionals
1126       preview_input_port  = state.preview_parameters.preview_component->input[0];
1127 
1128       // Connect camera to preview (which might be a null_sink if no preview required)
1129       status = connect_ports(camera_preview_port, preview_input_port, &state.preview_connection);
1130 
1131       if (status == MMAL_SUCCESS)
1132       {
1133          VCOS_STATUS_T vcos_status;
1134 
1135          // Set up our userdata - this is passed though to the callback where we need the information.
1136          // Null until we open our filename
1137          callback_data.file_handle = NULL;
1138          callback_data.pstate = &state;
1139 
1140          vcos_status = vcos_semaphore_create(&callback_data.complete_semaphore, "RaspiStill-sem", 0);
1141          vcos_assert(vcos_status == VCOS_SUCCESS);
1142 
1143          camera_still_port->userdata = (struct MMAL_PORT_USERDATA_T *)&callback_data;
1144 
1145          if (state.common_settings.verbose)
1146             fprintf(stderr, "Enabling camera still output port\n");
1147 
1148          // Enable the camera still output port and tell it its callback function
1149          status = mmal_port_enable(camera_still_port, camera_buffer_callback);
1150 
1151          if (status != MMAL_SUCCESS)
1152          {
1153             vcos_log_error("Failed to setup camera output");
1154             goto error;
1155          }
1156 
1157          if (state.common_settings.verbose)
1158             fprintf(stderr, "Starting video preview\n");
1159 
1160          int frame, keep_looping = 1;
1161          FILE *output_file = NULL;
1162          char *use_filename = NULL;      // Temporary filename while image being written
1163          char *final_filename = NULL;    // Name that file gets once writing complete
1164 
1165          frame = 0;
1166 
1167          while (keep_looping)
1168          {
1169             keep_looping = wait_for_next_frame(&state, &frame);
1170 
1171             // Open the file
1172             if (state.common_settings.filename)
1173             {
1174                if (state.common_settings.filename[0] == '-')
1175                {
1176                   output_file = stdout;
1177 
1178                   // Ensure we don't upset the output stream with diagnostics/info
1179                   state.common_settings.verbose = 0;
1180                }
1181                else
1182                {
1183                   vcos_assert(use_filename == NULL && final_filename == NULL);
1184                   status = create_filenames(&final_filename, &use_filename, state.common_settings.filename, frame);
1185                   if (status  != MMAL_SUCCESS)
1186                   {
1187                      vcos_log_error("Unable to create filenames");
1188                      goto error;
1189                   }
1190 
1191                   if (state.common_settings.verbose)
1192                      fprintf(stderr, "Opening output file %s\n", final_filename);
1193                   // Technically it is opening the temp~ filename which will be ranamed to the final filename
1194 
1195                   output_file = fopen(use_filename, "wb");
1196 
1197                   if (!output_file)
1198                   {
1199                      // Notify user, carry on but discarding encoded output buffers
1200                      vcos_log_error("%s: Error opening output file: %s\nNo output file will be generated\n", __func__, use_filename);
1201                   }
1202                }
1203 
1204                callback_data.file_handle = output_file;
1205             }
1206 
1207             if (output_file)
1208             {
1209                int num, q;
1210 
1211                // There is a possibility that shutter needs to be set each loop.
1212                if (mmal_status_to_int(mmal_port_parameter_set_uint32(state.camera_component->control, MMAL_PARAMETER_SHUTTER_SPEED, state.camera_parameters.shutter_speed) != MMAL_SUCCESS))
1213                   vcos_log_error("Unable to set shutter speed");
1214 
1215                // Send all the buffers to the camera output port
1216                num = mmal_queue_length(state.camera_pool->queue);
1217 
1218                for (q=0; q<num; q++)
1219                {
1220                   MMAL_BUFFER_HEADER_T *buffer = mmal_queue_get(state.camera_pool->queue);
1221 
1222                   if (!buffer)
1223                      vcos_log_error("Unable to get a required buffer %d from pool queue", q);
1224 
1225                   if (mmal_port_send_buffer(camera_still_port, buffer)!= MMAL_SUCCESS)
1226                      vcos_log_error("Unable to send a buffer to camera output port (%d)", q);
1227                }
1228 
1229                if (state.burstCaptureMode && frame==1)
1230                {
1231                   mmal_port_parameter_set_boolean(state.camera_component->control,  MMAL_PARAMETER_CAMERA_BURST_CAPTURE, 1);
1232                }
1233 
1234                if(state.camera_parameters.enable_annotate)
1235                {
1236                   if ((state.camera_parameters.enable_annotate & ANNOTATE_APP_TEXT) && state.common_settings.gps)
1237                   {
1238                      char *text = raspi_gps_location_string();
1239                      raspicamcontrol_set_annotate(state.camera_component, state.camera_parameters.enable_annotate,
1240                                                   text,
1241                                                   state.camera_parameters.annotate_text_size,
1242                                                   state.camera_parameters.annotate_text_colour,
1243                                                   state.camera_parameters.annotate_bg_colour,
1244                                                   state.camera_parameters.annotate_justify,
1245                                                   state.camera_parameters.annotate_x,
1246                                                   state.camera_parameters.annotate_y
1247                                                  );
1248                      free(text);
1249                   }
1250                   else
1251                      raspicamcontrol_set_annotate(state.camera_component, state.camera_parameters.enable_annotate,
1252                                                   state.camera_parameters.annotate_string,
1253                                                   state.camera_parameters.annotate_text_size,
1254                                                   state.camera_parameters.annotate_text_colour,
1255                                                   state.camera_parameters.annotate_bg_colour,
1256                                                   state.camera_parameters.annotate_justify,
1257                                                   state.camera_parameters.annotate_x,
1258                                                   state.camera_parameters.annotate_y
1259                                                  );
1260                }
1261 
1262                if (state.common_settings.verbose)
1263                   fprintf(stderr, "Starting capture %d\n", frame);
1264 
1265                if (mmal_port_parameter_set_boolean(camera_still_port, MMAL_PARAMETER_CAPTURE, 1) != MMAL_SUCCESS)
1266                {
1267                   vcos_log_error("%s: Failed to start capture", __func__);
1268                }
1269                else
1270                {
1271                   // Wait for capture to complete
1272                   // For some reason using vcos_semaphore_wait_timeout sometimes returns immediately with bad parameter error
1273                   // even though it appears to be all correct, so reverting to untimed one until figure out why its erratic
1274                   vcos_semaphore_wait(&callback_data.complete_semaphore);
1275                   if (state.common_settings.verbose)
1276                      fprintf(stderr, "Finished capture %d\n", frame);
1277                }
1278 
1279                // Ensure we don't die if get callback with no open file
1280                callback_data.file_handle = NULL;
1281 
1282                if (output_file != stdout)
1283                {
1284                   rename_file(&state, output_file, final_filename, use_filename, frame);
1285                }
1286                else
1287                {
1288                   fflush(output_file);
1289                }
1290             }
1291 
1292             if (use_filename)
1293             {
1294                free(use_filename);
1295                use_filename = NULL;
1296             }
1297             if (final_filename)
1298             {
1299                free(final_filename);
1300                final_filename = NULL;
1301             }
1302          } // end for (frame)
1303 
1304          vcos_semaphore_delete(&callback_data.complete_semaphore);
1305       }
1306       else
1307       {
1308          mmal_status_to_int(status);
1309          vcos_log_error("%s: Failed to connect camera to preview", __func__);
1310       }
1311 
1312 error:
1313 
1314       mmal_status_to_int(status);
1315 
1316       if (state.common_settings.verbose)
1317          fprintf(stderr, "Closing down\n");
1318 
1319       if (output_file)
1320          fclose(output_file);
1321 
1322       // Disable all our ports that are not handled by connections
1323       check_disable_port(camera_video_port);
1324 
1325       if (state.preview_connection)
1326          mmal_connection_destroy(state.preview_connection);
1327 
1328       /* Disable components */
1329       if (state.preview_parameters.preview_component)
1330          mmal_component_disable(state.preview_parameters.preview_component);
1331 
1332       if (state.camera_component)
1333          mmal_component_disable(state.camera_component);
1334 
1335       raspipreview_destroy(&state.preview_parameters);
1336       destroy_camera_component(&state);
1337 
1338       if (state.common_settings.gps)
1339          raspi_gps_shutdown(state.common_settings.verbose);
1340 
1341       if (state.common_settings.verbose)
1342          fprintf(stderr, "Close down completed, all components disconnected, disabled and destroyed\n\n");
1343    }
1344 
1345    if (status != MMAL_SUCCESS)
1346       raspicamcontrol_check_configuration(128);
1347 
1348    return exit_code;
1349 }
1350 
1351