/*
- * Copyright (c) 2013 Jan Schmidt <jan@centricular.com>
+ * Copyright (c) 2013-2016 Jan Schmidt <jan@centricular.com>
Portions:
Copyright (c) 2013, Broadcom Europe Ltd
Copyright (c) 2013, James Hughes
* Modification of the RaspiVid command line capture program for GStreamer
* use.
*
- * \date 28th Feb 2013, 11 Oct 2013
+ * \date 28th Feb 2013, 11 Oct 2013, 5 Mar 2015
* \Author: James Hughes, Jan Schmidt
*
* Description
* 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 <memory.h>
#include <sysexits.h>
+#include <gst/gst.h>
#include "bcm_host.h"
#include "interface/vcos/vcos.h"
#include "RaspiCapture.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
// Video format information
+// 0 implies variable
#define VIDEO_FRAME_RATE_NUM 30
#define VIDEO_FRAME_RATE_DEN 1
#define VIDEO_OUTPUT_BUFFERS_NUM 3
// Max bitrate we allow for recording
-const int MAX_BITRATE = 30000000; // 30Mbits/s
+const int MAX_BITRATE = 25000000; // 25Mbits/s
/// Interval at which we check for an failure abort during capture
const int ABORT_INTERVAL = 100; // ms
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;
};
-#if 0
+
/// Structure to cross reference H264 profile strings against the MMAL parameter equivalent
static XREF_T profile_map[] =
{
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);
#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)", 1},
- { CommandProfile, "-profile", "pf", "Specify H264 profile to use for encoding", 1},
+ { 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]);
*/
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->framerate = VIDEO_FRAME_RATE_NUM;
- config->intraperiod = 0; // Not set
+ 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);
}
+
/**
* Dump image state parameters to printf. Used for debugging
*
*/
static void dump_state(RASPIVID_STATE *state)
{
+ RASPIVID_CONFIG *config;
+
if (!state)
{
vcos_assert(0);
return;
}
- fprintf(stderr, "Width %d, Height %d, filename %s\n", state->config.width, state->config.height, state->config.filename);
- fprintf(stderr, "bitrate %d, framerate %d, time delay %d\n", state->config.bitrate, state->config.framerate, state->config.timeout);
- //fprintf(stderr, "H264 Profile %s\n", raspicli_unmap_xref(state->config.profile, profile_map, profile_map_size));
+ config = &state->config;
- raspipreview_dump_parameters(&state->config.preview_parameters);
- //raspicamcontrol_dump_parameters(&state->config.camera_parameters);
+ 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
state->filename = malloc(len + 1);
vcos_assert(state->filename);
if (state->filename)
- strncpy(state->filename, argv[i + 1], len);
+ strncpy(state->filename, argv[i + 1], len+1);
i++;
}
else
{
if (sscanf(argv[i + 1], "%u", &state->timeout) == 1)
{
- // TODO : What limits do we need for timeout?
+ // 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
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);
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
if (!valid)
{
- fprintf(stderr, "Invalid command line option (%s)\n", argv[i]);
+ fprintf(stderr, "Invalid command line option (%s)\n", argv[i-1]);
return 1;
}
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();
{
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
- {
+ 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
/**
- * buffer header callback function for encoder
+ * Open a file based on the settings in state
*
- * Callback will dump buffer data to the specific file
+ * @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
*
- * @param port Pointer to port from which callback originated
- * @param buffer mmal buffer header pointer
+ * This time for the imv output file
+ *
+ * @param state Pointer to state
*/
-static void encoder_buffer_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+static FILE *open_imv_filename(RASPIVID_STATE *pState)
{
- MMAL_BUFFER_HEADER_T *new_buffer;
+ FILE *new_handle = NULL;
+ char *tempname = NULL, *filename = NULL;
- // We pass our file handle and other stuff in via the userdata field.
+ if (pState->segmentSize || pState->splitWait)
+ {
+ // Create a new filename string
+ asprintf(&tempname, pState->imv_filename, pState->segmentNumber);
+ filename = tempname;
+ }
+ else
+ {
+ filename = pState->imv_filename;
+ }
- PORT_USERDATA *pData = (PORT_USERDATA *)port->userdata;
- RASPIVID_STATE *state = pData->state;
+ if (filename)
+ new_handle = fopen(filename, "wb");
- if (pData)
+ if (pState->verbose)
{
- int bytes_written = buffer->length;
+ if (new_handle)
+ fprintf(stderr, "Opening imv output file \"%s\"\n", filename);
+ else
+ fprintf(stderr, "Failed to open new imv file \"%s\"\n", filename);
+ }
- vcos_assert(state->output_file);
+ if (tempname)
+ free(tempname);
- if (buffer->length)
- {
- mmal_buffer_header_mem_lock(buffer);
+ 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;
- bytes_written = fwrite(buffer->data, 1, buffer->length, state->output_file);
+ // 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);
- mmal_buffer_header_mem_unlock(buffer);
- }
+ 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));
- if (bytes_written != buffer->length)
- {
- vcos_log_error("Failed to write buffer data (%d from %d)- aborting", bytes_written, buffer->length);
- pData->abort = 1;
- }
+ 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
{
- vcos_log_error("Received a encoder buffer callback with no state");
+ 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);
}
+}
- // 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;
- new_buffer = mmal_queue_get(state->encoder_pool->queue);
+/**
+ * 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 (new_buffer)
- status = mmal_port_send_buffer(port, new_buffer);
+ 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;
+ }
- if (!new_buffer || status != MMAL_SUCCESS)
- vcos_log_error("Unable to return a buffer to the encoder port");
+ 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;
+
+ /* FIXME: Use our own interruptible cond wait: */
+ buffer = mmal_queue_wait(state->encoded_buffer_q);
+
+
+ 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
*/
static MMAL_STATUS_T create_camera_component(RASPIVID_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_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);
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;
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];
+ 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);
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_T cam_config =
- {
- { MMAL_PARAMETER_CAMERA_CONFIG, sizeof(cam_config) },
- .max_stills_w = state->config.width,
- .max_stills_h = state->config.height,
- .stills_yuv422 = 0,
- .one_shot_stills = 0,
- .max_preview_video_w = state->config.width,
- .max_preview_video_h = state->config.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);
- }
+ { 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
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->encoding = MMAL_ENCODING_OPAQUE;
- format->es->video.width = state->config.width;
- format->es->video.height = state->config.height;
+ 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 = state->config.width;
- format->es->video.crop.height = state->config.height;
- format->es->video.frame_rate.num = state->config.framerate;
- format->es->video.frame_rate.den = VIDEO_FRAME_RATE_DEN;
+ 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);
}
// Set the encode format on the video port
-
format = video_port->format;
- format->encoding_variant = MMAL_ENCODING_I420;
- format->encoding = MMAL_ENCODING_OPAQUE;
- format->es->video.width = state->config.width;
- format->es->video.height = state->config.height;
+ 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 = state->config.width;
- format->es->video.crop.height = state->config.height;
- format->es->video.frame_rate.num = state->config.framerate;
- format->es->video.frame_rate.den = VIDEO_FRAME_RATE_DEN;
+ 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);
format->encoding = MMAL_ENCODING_OPAQUE;
format->encoding_variant = MMAL_ENCODING_I420;
- format->es->video.width = state->config.width;
- format->es->video.height = state->config.height;
+ 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 = state->config.width;
- format->es->video.crop.height = state->config.height;
- format->es->video.frame_rate.num = 1;
+ 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);
goto error;
}
- raspicamcontrol_set_all_parameters(camera, &state->config.camera_parameters);
+ raspicamcontrol_set_all_parameters(camera, &config->camera_parameters);
- state->camera_component = camera;
+ update_annotation_data(state);
- if (state->config.verbose)
+ if (config->verbose)
fprintf(stderr, "Camera component done\n");
return status;
}
}
+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
*
MMAL_COMPONENT_T *encoder = 0;
MMAL_PORT_T *encoder_input = NULL, *encoder_output = NULL;
MMAL_STATUS_T status;
- MMAL_POOL_T *pool;
+ RASPIVID_CONFIG *config = &state->config;
- status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER, &encoder);
+ gboolean encoded_format =
+ (config->encoding == MMAL_ENCODING_H264 ||
+ config->encoding == MMAL_ENCODING_MJPEG ||
+ config->encoding == MMAL_ENCODING_JPEG);
- if (status != MMAL_SUCCESS)
- {
- vcos_log_error("Unable to create video encoder component");
- goto error;
+ if (!encoded_format)
+ return MMAL_SUCCESS;
+
+ if (config->encoding == MMAL_ENCODING_JPEG)
+ status = mmal_component_create(MMAL_COMPONENT_DEFAULT_IMAGE_ENCODER, &encoder);
+ else
+ status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER, &encoder);
+
+ if (status != MMAL_SUCCESS) {
+ vcos_log_error("Unable to create video encoder component");
+ goto error;
}
if (!encoder->input_num || !encoder->output_num)
// We want same format on input and output
mmal_format_copy(encoder_output->format, encoder_input->format);
- // Only supporting H264 at the moment
- encoder_output->format->encoding = MMAL_ENCODING_H264;
+ // Configure desired encoding
+ encoder_output->format->encoding = config->encoding;
- encoder_output->format->bitrate = state->config.bitrate;
+ encoder_output->format->bitrate = config->bitrate;
- encoder_output->buffer_size = encoder_output->buffer_size_recommended;
+ if (config->encoding == MMAL_ENCODING_H264)
+ encoder_output->buffer_size = encoder_output->buffer_size_recommended;
+ else
+ encoder_output->buffer_size = 256<<10;
if (encoder_output->buffer_size < encoder_output->buffer_size_min)
encoder_output->buffer_size = encoder_output->buffer_size_min;
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)
- {
+ 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)
{
}
- if (state->config.intraperiod)
+ if (config->encoding == MMAL_ENCODING_H264 && config->intraperiod != -1)
{
- MMAL_PARAMETER_UINT32_T param = {{ MMAL_PARAMETER_INTRAPERIOD, sizeof(param)}, state->config.intraperiod};
+ 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 = state->config.profile;
+ 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 (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..
+ }
- if (mmal_port_parameter_set_boolean(encoder_input, MMAL_PARAMETER_VIDEO_IMMUTABLE_INPUT, state->config.immutableInput) != MMAL_SUCCESS)
+ // 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)
{
- vcos_log_error("Unable to set immutable input flag");
- // Continue rather than abort..
+ 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
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->config.verbose)
+ if (config->verbose)
fprintf(stderr, "Encoder component done\n");
return status;
if (encoder)
mmal_component_destroy(encoder);
+ state->encoder_component = NULL;
+
return status;
}
*/
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_component->output[0], state->encoder_pool);
+ mmal_port_pool_destroy(state->encoder_output_port, state->encoder_pool);
+ state->encoder_pool = NULL;
}
- if (state->encoder_component)
- {
+ if (state->encoder_component) {
+
mmal_component_destroy(state->encoder_component);
state->encoder_component = NULL;
}
}
RASPIVID_STATE *
-raspi_capture_start(RASPIVID_CONFIG *config)
+raspi_capture_setup(RASPIVID_CONFIG *config)
{
// Our main data storage vessel..
RASPIVID_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 *preview_input_port = NULL;
- MMAL_PORT_T *encoder_input_port = NULL;
/* Default everything to zero */
state = calloc(1, sizeof(RASPIVID_STATE));
/* Apply passed in config */
state->config = *config;
- if (state->config.verbose)
- {
- dump_state(state);
- }
+ /* 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.
return NULL;
}
- if ((status = raspipreview_create(&state->config.preview_parameters)) != MMAL_SUCCESS)
+ 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;
}
- if ((status = create_encoder_component(state)) != MMAL_SUCCESS)
+ 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)
{
- vcos_log_error("%s: Failed to create encode component", __func__);
- raspipreview_destroy(&state->config.preview_parameters);
- destroy_camera_component(state);
- return NULL;
+ dump_state(state);
+ }
+
+ state->camera_video_port = state->camera_component->output[MMAL_CAMERA_VIDEO_PORT];
+ state->camera_still_port = state->camera_component->output[MMAL_CAMERA_CAPTURE_PORT];
+ camera_preview_port = state->camera_component->output[MMAL_CAMERA_PREVIEW_PORT];
+ preview_input_port = state->preview_state.preview_component->input[0];
+
+ if (state->encoder_component) {
+ encoder_input_port = state->encoder_component->input[0];
+ state->encoder_output_port = state->encoder_component->output[0];
+ } else {
+ state->encoder_output_port = state->camera_video_port;
+ }
+
+ if ((status = raspi_capture_set_format_and_start(state)) != MMAL_SUCCESS) {
+ return FALSE;
+ }
+
+ GST_DEBUG ("Creating pool of %d buffers of size %d",
+ state->encoder_output_port->buffer_num, state->encoder_output_port->buffer_size);
+ /* Create pool of buffer headers for the output port to consume */
+ pool = mmal_port_pool_create(state->encoder_output_port,
+ state->encoder_output_port->buffer_num, state->encoder_output_port->buffer_size);
+ if (!pool)
+ {
+ vcos_log_error("Failed to create buffer header pool for encoder output port %s",
+ state->encoder_output_port->name);
+ return FALSE;
}
+ state->encoder_pool = pool;
if (state->config.verbose)
fprintf(stderr, "Starting component connection stage\n");
- camera_preview_port = state->camera_component->output[MMAL_CAMERA_PREVIEW_PORT];
- camera_video_port = state->camera_component->output[MMAL_CAMERA_VIDEO_PORT];
- preview_input_port = state->config.preview_parameters.preview_component->input[0];
- encoder_input_port = state->encoder_component->input[0];
- state->camera_still_port = state->camera_component->output[MMAL_CAMERA_CAPTURE_PORT];
- state->encoder_output_port = state->encoder_component->output[0];
-
- if (state->config.preview_parameters.wantPreview )
+ if (config->preview_parameters.wantPreview )
{
- if (state->config.verbose)
+ if (config->verbose)
{
fprintf(stderr, "Connecting camera preview port to preview input port\n");
fprintf(stderr, "Starting video preview\n");
status = connect_ports(camera_preview_port, preview_input_port, &state->preview_connection);
if (status != MMAL_SUCCESS)
{
- mmal_status_to_int(status);
vcos_log_error("%s: Failed to connect camera to preview", __func__);
- goto error;
+ return FALSE;
}
}
- if (state->config.verbose)
- fprintf(stderr, "Connecting camera stills port to encoder input port\n");
-
- // Now connect the camera to the encoder
- status = connect_ports(camera_video_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;
- }
-
- if (state->config.filename)
- {
- if (state->config.filename[0] == '-')
- {
- state->output_file = stdout;
-
- // Ensure we don't upset the output stream with diagnostics/info
- state->config.verbose = 0;
- }
- else
- {
- if (state->config.verbose)
- fprintf(stderr, "Opening output file \"%s\"\n", state->config.filename);
-
- state->output_file = fopen(state->config.filename, "wb");
- }
-
- if (!state->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__, state->config.filename);
- }
+ 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->encoder_output_port->userdata = (struct MMAL_PORT_USERDATA_T *)&state->callback_data;
- if (state->config.verbose)
+ 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 (state->config.demoMode)
+ if (config->demoMode)
{
// Run for the user specific time..
- int num_iterations = state->config.timeout / state->config.demoInterval;
+ int num_iterations = config->timeout / config->demoInterval;
int i;
- if (state->config.verbose)
+ if (config->verbose)
fprintf(stderr, "Running in demo mode\n");
- for (i=0;state->config.timeout == 0 || i<num_iterations;i++)
+ for (i=0;config->timeout == 0 || i<num_iterations;i++)
{
raspicamcontrol_cycle_test(state->camera_component);
vcos_sleep(state->config.demoInterval);
}
}
- else
- {
- // Only encode stuff if we have a filename and it opened
- if (state->output_file)
- {
- int wait;
-
- if (state->config.verbose)
- fprintf(stderr, "Starting video capture\n");
- if (mmal_port_parameter_set_boolean(camera_video_port, MMAL_PARAMETER_CAPTURE, 1) != MMAL_SUCCESS)
- {
- goto error;
- }
+ if (config->verbose)
+ fprintf(stderr, "Starting video capture\n");
- // 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 (mmal_port_parameter_set_boolean(state->camera_video_port, MMAL_PARAMETER_CAPTURE, 1) != MMAL_SUCCESS)
+ {
+ goto error;
+ }
- if (!buffer)
- vcos_log_error("Unable to get a required buffer %d from pool queue", q);
+ // 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 (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);
+ 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
+ }
+ }
- for (wait = 0; state->config.timeout == 0 || wait < state->config.timeout; wait+= ABORT_INTERVAL)
- {
- vcos_sleep(ABORT_INTERVAL);
- if (state->callback_data.abort)
- break;
- }
+ // 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 (state->config.verbose)
- fprintf(stderr, "Finished capture\n");
- }
- else
- {
- if (state->config.timeout)
- vcos_sleep(state->config.timeout);
- else
- for (;;) vcos_sleep(ABORT_INTERVAL);
- }
+#if 0
+ for (wait = 0; config->timeout == 0 || wait < config->timeout; wait+= ABORT_INTERVAL)
+ {
+ vcos_sleep(ABORT_INTERVAL);
+ if (state->callback_data.abort)
+ break;
}
- return state;
+ if (config->verbose)
+ fprintf(stderr, "Finished capture\n");
+#endif
+
+ return (status == MMAL_SUCCESS);
error:
raspi_capture_stop(state);
raspicamcontrol_check_configuration(128);
}
- return NULL;
+ return FALSE;
}
void
raspi_capture_stop(RASPIVID_STATE *state)
{
- if (state->config.verbose)
+ 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->config.preview_parameters.wantPreview )
- mmal_connection_destroy(state->preview_connection);
- mmal_connection_destroy(state->encoder_connection);
+ 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->encoder_component)
mmal_component_disable(state->encoder_component);
- if (state->config.preview_parameters.preview_component)
- mmal_component_disable(state->config.preview_parameters.preview_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->config.preview_parameters);
+ raspipreview_destroy(&state->preview_state);
destroy_camera_component(state);
- if (state->config.verbose)
+ 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);
+}