RaspiVid: Add raw (YUV420, RGB or grayscale) video output

The raw video output is useful for image processing applications where
both motion vectors from encoder and image data are needed. This commit
adds a new --raw option which inserts a video splitter component between
camera's preview output and preview's input, which allows us to set
callback to capture raw video data into a file.

Another option --raw-format is introduced which allows us to choose
YUV420, RGB or grayscale format.

Parts of the code (video output format configuration and
splitter_buffer_callback()) come from RaspiYUV.c.

The splitter component is created only if the --raw option is specified.

Signed-off-by: Adam Heinrich <adam@adamh.cz>
This commit is contained in:
Adam Heinrich
2016-11-11 14:30:48 +01:00
committed by popcornmix
parent 666704c08c
commit 95b306cca4

View File

@@ -43,6 +43,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* components, but we do need to handle buffers from the encoder, which
* are simply written straight to the file in the requisite buffer callback.
*
* If raw option is selected, a video splitter component is connected between
* camera and preview. This allows us to set up callback for raw camera data
* (in YUV420 or RGB format) which might be useful for further image processing.
*
* We use the RaspiCamControl code to handle the specific camera settings.
* We use the RaspiPreview code to handle the (generic) preview window
*/
@@ -85,6 +89,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define MMAL_CAMERA_VIDEO_PORT 1
#define MMAL_CAMERA_CAPTURE_PORT 2
// Port configuration for the splitter component
#define SPLITTER_OUTPUT_PORT 0
#define SPLITTER_PREVIEW_PORT 1
// Video format information
// 0 implies variable
#define VIDEO_FRAME_RATE_NUM 30
@@ -141,10 +149,19 @@ typedef struct
char header_bytes[29];
int header_wptr;
FILE *imv_file_handle; /// File handle to write inline motion vectors to.
FILE *raw_file_handle; /// File handle to write raw data to.
int flush_buffers;
FILE *pts_file_handle; /// File timestamps
} PORT_USERDATA;
/** Possible raw output formats
*/
typedef enum {
RAW_OUTPUT_FMT_YUV = 1,
RAW_OUTPUT_FMT_RGB,
RAW_OUTPUT_FMT_GRAY,
} RAW_OUTPUT_FMT;
/** Structure containing all state information for the current run
*/
struct RASPIVID_STATE_S
@@ -181,10 +198,13 @@ struct RASPIVID_STATE_S
RASPICAM_CAMERA_PARAMETERS camera_parameters; /// Camera setup parameters
MMAL_COMPONENT_T *camera_component; /// Pointer to the camera component
MMAL_COMPONENT_T *splitter_component; /// Pointer to the splitter component
MMAL_COMPONENT_T *encoder_component; /// Pointer to the encoder component
MMAL_CONNECTION_T *preview_connection; /// Pointer to the connection from camera to preview
MMAL_CONNECTION_T *preview_connection; /// Pointer to the connection from camera or splitter to preview
MMAL_CONNECTION_T *splitter_connection;/// Pointer to the connection from camera to splitter
MMAL_CONNECTION_T *encoder_connection; /// Pointer to the connection from camera to encoder
MMAL_POOL_T *splitter_pool; /// Pointer to the pool of buffers used by splitter output port 0
MMAL_POOL_T *encoder_pool; /// Pointer to the pool of buffers used by encoder output port
PORT_USERDATA callback_data; /// Used to move data to the encoder callback
@@ -194,6 +214,9 @@ struct RASPIVID_STATE_S
int inlineMotionVectors; /// Encoder outputs inline Motion Vectors
char *imv_filename; /// filename of inline Motion Vectors output
int raw_output; /// Output raw video from camera as well
RAW_OUTPUT_FMT raw_output_fmt; /// The raw video format
char *raw_filename; /// Filename for raw video output
int cameraNum; /// Camera number
int settings; /// Request settings from the camera
int sensor_mode; /// Sensor mode. 0=auto. Check docs/forum for modes selected by other values.
@@ -247,6 +270,14 @@ static XREF_T intra_refresh_map[] =
static int intra_refresh_map_size = sizeof(intra_refresh_map) / sizeof(intra_refresh_map[0]);
static XREF_T raw_output_fmt_map[] =
{
{"yuv", RAW_OUTPUT_FMT_YUV},
{"rgb", RAW_OUTPUT_FMT_RGB},
{"gray", RAW_OUTPUT_FMT_GRAY},
};
static int raw_output_fmt_map_size = sizeof(raw_output_fmt_map) / sizeof(raw_output_fmt_map[0]);
static void display_valid_parameters(char *app_name);
@@ -283,6 +314,8 @@ static void display_valid_parameters(char *app_name);
#define CommandSavePTS 29
#define CommandCodec 30
#define CommandLevel 31
#define CommandRaw 32
#define CommandRawFormat 33
static COMMAND_LIST cmdline_commands[] =
{
@@ -318,6 +351,8 @@ static COMMAND_LIST cmdline_commands[] =
{ CommandSavePTS, "-save-pts", "pts","Save Timestamps to file for mkvmerge", 1 },
{ CommandCodec, "-codec", "cd", "Specify the codec to use - H264 (default) or MJPEG", 1 },
{ CommandLevel, "-level", "lev","Specify H264 level to use for encoding", 1},
{ CommandRaw, "-raw", "r", "Output filename <filename> for raw video", 1 },
{ CommandRawFormat, "-raw-format", "rf", "Specify output format for raw video. Default is yuv", 1},
};
static int cmdline_commands_size = sizeof(cmdline_commands) / sizeof(cmdline_commands[0]);
@@ -428,6 +463,9 @@ static void dump_status(RASPIVID_STATE *state)
if (state->segmentSize)
fprintf(stderr, "Segment size %d, segment wrap value %d, initial segment number %d\n", state->segmentSize, state->segmentWrap, state->segmentNumber);
if (state->raw_output)
fprintf(stderr, "Raw output enabled, format %s\n", raspicli_unmap_xref(state->raw_output_fmt, raw_output_fmt_map, raw_output_fmt_map_size));
fprintf(stderr, "Wait method : ");
for (i=0;i<wait_method_description_size;i++)
{
@@ -806,6 +844,35 @@ static int parse_cmdline(int argc, const char **argv, RASPIVID_STATE *state)
break;
}
case CommandRaw: // output filename
{
state->raw_output = 1;
state->raw_output_fmt = RAW_OUTPUT_FMT_YUV;
int len = strlen(argv[i + 1]);
if (len)
{
state->raw_filename = malloc(len + 1);
vcos_assert(state->raw_filename);
if (state->raw_filename)
strncpy(state->raw_filename, argv[i + 1], len+1);
i++;
}
else
valid = 0;
break;
}
case CommandRawFormat:
{
state->raw_output_fmt = raspicli_map_xref(argv[i + 1], raw_output_fmt_map, raw_output_fmt_map_size);
if (state->raw_output_fmt == -1)
valid = 0;
i++;
break;
}
default:
{
// Try parsing for any image specific parameters
@@ -869,8 +936,6 @@ static void display_valid_parameters(char *app_name)
fprintf(stdout, ",%s", profile_map[i].mode);
}
fprintf(stdout, "\n");
// Level options
fprintf(stdout, "\n\nH264 Level options :\n%s", level_map[0].mode );
@@ -887,6 +952,14 @@ static void display_valid_parameters(char *app_name)
fprintf(stdout, ",%s", intra_refresh_map[i].mode);
}
// Raw output format options
fprintf(stdout, "\n\nRaw output format options :\n%s", raw_output_fmt_map[0].mode );
for (i=1;i<raw_output_fmt_map_size;i++)
{
fprintf(stdout, ",%s", raw_output_fmt_map[i].mode);
}
fprintf(stdout, "\n");
// Help for preview options
@@ -1305,6 +1378,66 @@ static void encoder_buffer_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf
}
}
/**
* buffer header callback function for splitter
*
* 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 splitter_buffer_callback(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
{
MMAL_BUFFER_HEADER_T *new_buffer;
PORT_USERDATA *pData = (PORT_USERDATA *)port->userdata;
if (pData)
{
int bytes_written = 0;
int bytes_to_write = buffer->length;
/* Write only luma component to get grayscale image: */
if (buffer->length && pData->pstate->raw_output_fmt == RAW_OUTPUT_FMT_GRAY)
bytes_to_write = port->format->es->video.width * port->format->es->video.height;
vcos_assert(pData->raw_file_handle);
if (bytes_to_write)
{
mmal_buffer_header_mem_lock(buffer);
bytes_written = fwrite(buffer->data, 1, bytes_to_write, pData->raw_file_handle);
mmal_buffer_header_mem_unlock(buffer);
if (bytes_written != bytes_to_write)
{
vcos_log_error("Failed to write raw buffer data (%d from %d)- aborting", bytes_written, bytes_to_write);
pData->abort = 1;
}
}
}
else
{
vcos_log_error("Received a camera buffer callback with no state");
}
// release buffer back to the pool
mmal_buffer_header_release(buffer);
// and send one back to the port (if still open)
if (port->is_enabled)
{
MMAL_STATUS_T status;
new_buffer = mmal_queue_get(pData->pstate->splitter_pool->queue);
if (new_buffer)
status = mmal_port_send_buffer(port, new_buffer);
if (!new_buffer || status != MMAL_SUCCESS)
vcos_log_error("Unable to return a buffer to the splitter port");
}
}
/**
* Create the camera component, set up its ports
*
@@ -1574,6 +1707,161 @@ static void destroy_camera_component(RASPIVID_STATE *state)
}
}
/**
* Create the splitter component, set up its ports
*
* @param state Pointer to state control struct
*
* @return MMAL_SUCCESS if all OK, something else otherwise
*
*/
static MMAL_STATUS_T create_splitter_component(RASPIVID_STATE *state)
{
MMAL_COMPONENT_T *splitter = 0;
MMAL_PORT_T *splitter_output = NULL;
MMAL_ES_FORMAT_T *format;
MMAL_STATUS_T status;
MMAL_POOL_T *pool;
int i;
if (state->camera_component == NULL)
{
status = MMAL_ENOSYS;
vcos_log_error("Camera component must be created before splitter");
goto error;
}
/* Create the component */
status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_SPLITTER, &splitter);
if (status != MMAL_SUCCESS)
{
vcos_log_error("Failed to create splitter component");
goto error;
}
if (!splitter->input_num)
{
status = MMAL_ENOSYS;
vcos_log_error("Splitter doesn't have any input port");
goto error;
}
if (splitter->output_num < 2)
{
status = MMAL_ENOSYS;
vcos_log_error("Splitter doesn't have enough output ports");
goto error;
}
/* Ensure there are enough buffers to avoid dropping frames: */
mmal_format_copy(splitter->input[0]->format, state->camera_component->output[MMAL_CAMERA_PREVIEW_PORT]->format);
if (splitter->input[0]->buffer_num < VIDEO_OUTPUT_BUFFERS_NUM)
splitter->input[0]->buffer_num = VIDEO_OUTPUT_BUFFERS_NUM;
status = mmal_port_format_commit(splitter->input[0]);
if (status != MMAL_SUCCESS)
{
vcos_log_error("Unable to set format on splitter input port");
goto error;
}
/* Splitter can do format conversions, configure format for its output port: */
for (i = 0; i < splitter->output_num; i++)
{
mmal_format_copy(splitter->output[i]->format, splitter->input[0]->format);
if (i == SPLITTER_OUTPUT_PORT)
{
format = splitter->output[i]->format;
switch (state->raw_output_fmt)
{
case RAW_OUTPUT_FMT_YUV:
case RAW_OUTPUT_FMT_GRAY: /* Grayscale image contains only luma (Y) component */
format->encoding = MMAL_ENCODING_I420;
format->encoding_variant = MMAL_ENCODING_I420;
break;
case RAW_OUTPUT_FMT_RGB:
if (mmal_util_rgb_order_fixed(state->camera_component->output[MMAL_CAMERA_CAPTURE_PORT]))
format->encoding = MMAL_ENCODING_RGB24;
else
format->encoding = MMAL_ENCODING_BGR24;
format->encoding_variant = 0; /* Irrelevant when not in opaque mode */
break;
default:
status = MMAL_EINVAL;
vcos_log_error("unknown raw output format");
goto error;
}
}
status = mmal_port_format_commit(splitter->output[i]);
if (status != MMAL_SUCCESS)
{
vcos_log_error("Unable to set format on splitter output port %d", i);
goto error;
}
}
/* Enable component */
status = mmal_component_enable(splitter);
if (status != MMAL_SUCCESS)
{
vcos_log_error("splitter component couldn't be enabled");
goto error;
}
/* Create pool of buffer headers for the output port to consume */
splitter_output = splitter->output[SPLITTER_OUTPUT_PORT];
pool = mmal_port_pool_create(splitter_output, splitter_output->buffer_num, splitter_output->buffer_size);
if (!pool)
{
vcos_log_error("Failed to create buffer header pool for splitter output port %s", splitter_output->name);
}
state->splitter_pool = pool;
state->splitter_component = splitter;
if (state->verbose)
fprintf(stderr, "Splitter component done\n");
return status;
error:
if (splitter)
mmal_component_destroy(splitter);
return status;
}
/**
* Destroy the splitter component
*
* @param state Pointer to state control struct
*
*/
static void destroy_splitter_component(RASPIVID_STATE *state)
{
// Get rid of any port buffers first
if (state->splitter_pool)
{
mmal_port_pool_destroy(state->splitter_component->output[SPLITTER_OUTPUT_PORT], state->splitter_pool);
}
if (state->splitter_component)
{
mmal_component_destroy(state->splitter_component);
state->splitter_component = NULL;
}
}
/**
* Create the encoder component, set up its ports
*
@@ -2064,6 +2352,9 @@ int main(int argc, const char **argv)
MMAL_PORT_T *preview_input_port = NULL;
MMAL_PORT_T *encoder_input_port = NULL;
MMAL_PORT_T *encoder_output_port = NULL;
MMAL_PORT_T *splitter_input_port = NULL;
MMAL_PORT_T *splitter_output_port = NULL;
MMAL_PORT_T *splitter_preview_port = NULL;
bcm_host_init();
@@ -2120,6 +2411,14 @@ int main(int argc, const char **argv)
destroy_camera_component(&state);
exit_code = EX_SOFTWARE;
}
else if (state.raw_output && (status = create_splitter_component(&state)) != MMAL_SUCCESS)
{
vcos_log_error("%s: Failed to create splitter component", __func__);
raspipreview_destroy(&state.preview_parameters);
destroy_camera_component(&state);
destroy_encoder_component(&state);
exit_code = EX_SOFTWARE;
}
else
{
if (state.verbose)
@@ -2132,23 +2431,75 @@ int main(int argc, const char **argv)
encoder_input_port = state.encoder_component->input[0];
encoder_output_port = state.encoder_component->output[0];
if (state.raw_output)
{
splitter_input_port = state.splitter_component->input[0];
splitter_output_port = state.splitter_component->output[SPLITTER_OUTPUT_PORT];
splitter_preview_port = state.splitter_component->output[SPLITTER_PREVIEW_PORT];
}
if (state.preview_parameters.wantPreview )
{
if (state.verbose)
if (state.raw_output)
{
fprintf(stderr, "Connecting camera preview port to preview input port\n");
fprintf(stderr, "Starting video preview\n");
}
if (state.verbose)
fprintf(stderr, "Connecting camera preview port to splitter input port\n");
// Connect camera to preview
status = connect_ports(camera_preview_port, preview_input_port, &state.preview_connection);
// Connect camera to splitter
status = connect_ports(camera_preview_port, splitter_input_port, &state.splitter_connection);
if (status != MMAL_SUCCESS)
{
state.splitter_connection = NULL;
vcos_log_error("%s: Failed to connect camera preview port to splitter input", __func__);
goto error;
}
if (state.verbose)
{
fprintf(stderr, "Connecting splitter preview port to preview input port\n");
fprintf(stderr, "Starting video preview\n");
}
// Connect splitter to preview
status = connect_ports(splitter_preview_port, preview_input_port, &state.preview_connection);
}
else
{
if (state.verbose)
{
fprintf(stderr, "Connecting camera preview port to preview input port\n");
fprintf(stderr, "Starting video preview\n");
}
// Connect camera to preview
status = connect_ports(camera_preview_port, preview_input_port, &state.preview_connection);
}
if (status != MMAL_SUCCESS)
state.preview_connection = NULL;
}
else
{
status = MMAL_SUCCESS;
if (state.raw_output)
{
if (state.verbose)
fprintf(stderr, "Connecting camera preview port to splitter input port\n");
// Connect camera to splitter
status = connect_ports(camera_preview_port, splitter_input_port, &state.splitter_connection);
if (status != MMAL_SUCCESS)
{
state.splitter_connection = NULL;
vcos_log_error("%s: Failed to connect camera preview port to splitter input", __func__);
goto error;
}
}
else
{
status = MMAL_SUCCESS;
}
}
if (status == MMAL_SUCCESS)
@@ -2165,6 +2516,30 @@ int main(int argc, const char **argv)
vcos_log_error("%s: Failed to connect camera video port to encoder input", __func__);
goto error;
}
}
if (status == MMAL_SUCCESS)
{
// Set up our userdata - this is passed though to the callback where we need the information.
state.callback_data.pstate = &state;
state.callback_data.abort = 0;
if (state.raw_output)
{
splitter_output_port->userdata = (struct MMAL_PORT_USERDATA_T *)&state.callback_data;
if (state.verbose)
fprintf(stderr, "Enabling splitter output port\n");
// Enable the splitter output port and tell it its callback function
status = mmal_port_enable(splitter_output_port, splitter_buffer_callback);
if (status != MMAL_SUCCESS)
{
vcos_log_error("%s: Failed to setup splitter output port", __func__);
goto error;
}
}
state.callback_data.file_handle = NULL;
@@ -2233,6 +2608,27 @@ int main(int argc, const char **argv)
}
}
state.callback_data.raw_file_handle = NULL;
if (state.raw_filename)
{
if (state.raw_filename[0] == '-')
{
state.callback_data.raw_file_handle = stdout;
}
else
{
state.callback_data.raw_file_handle = open_filename(&state, state.raw_filename);
}
if (!state.callback_data.raw_file_handle)
{
// Notify user, carry on but discarding encoded output buffers
fprintf(stderr, "Error opening output file: %s\nNo output file will be generated\n", state.raw_filename);
state.raw_output = 0;
}
}
if(state.bCircularBuffer)
{
if(state.bitrate == 0)
@@ -2279,9 +2675,6 @@ int main(int argc, const char **argv)
}
// Set up our userdata - this is passed though to the callback where we need the information.
state.callback_data.pstate = &state;
state.callback_data.abort = 0;
encoder_output_port->userdata = (struct MMAL_PORT_USERDATA_T *)&state.callback_data;
if (state.verbose)
@@ -2335,6 +2728,22 @@ int main(int argc, const char **argv)
}
}
// Send all the buffers to the splitter output port
if (state.raw_output) {
int num = mmal_queue_length(state.splitter_pool->queue);
int q;
for (q = 0; q < num; q++)
{
MMAL_BUFFER_HEADER_T *buffer = mmal_queue_get(state.splitter_pool->queue);
if (!buffer)
vcos_log_error("Unable to get a required buffer %d from pool queue", q);
if (mmal_port_send_buffer(splitter_output_port, buffer)!= MMAL_SUCCESS)
vcos_log_error("Unable to send a buffer to splitter output port (%d)", q);
}
}
int initialCapturing=state.bCapturing;
while (running)
{
@@ -2432,6 +2841,7 @@ error:
// Disable all our ports that are not handled by connections
check_disable_port(camera_still_port);
check_disable_port(encoder_output_port);
check_disable_port(splitter_output_port);
if (state.preview_parameters.wantPreview && state.preview_connection)
mmal_connection_destroy(state.preview_connection);
@@ -2439,6 +2849,9 @@ error:
if (state.encoder_connection)
mmal_connection_destroy(state.encoder_connection);
if (state.splitter_connection)
mmal_connection_destroy(state.splitter_connection);
// Can now close our file. Note disabling ports may flush buffers which causes
// problems if we have already closed the file!
if (state.callback_data.file_handle && state.callback_data.file_handle != stdout)
@@ -2447,6 +2860,8 @@ error:
fclose(state.callback_data.imv_file_handle);
if (state.callback_data.pts_file_handle && state.callback_data.pts_file_handle != stdout)
fclose(state.callback_data.pts_file_handle);
if (state.callback_data.raw_file_handle && state.callback_data.raw_file_handle != stdout)
fclose(state.callback_data.raw_file_handle);
/* Disable components */
if (state.encoder_component)
@@ -2455,11 +2870,15 @@ error:
if (state.preview_parameters.preview_component)
mmal_component_disable(state.preview_parameters.preview_component);
if (state.splitter_component)
mmal_component_disable(state.splitter_component);
if (state.camera_component)
mmal_component_disable(state.camera_component);
destroy_encoder_component(&state);
raspipreview_destroy(&state.preview_parameters);
destroy_splitter_component(&state);
destroy_camera_component(&state);
if (state.verbose)