mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-03-14 03:23:43 +00:00
pluggable outputs for the future
This commit is contained in:
79
src/dump/file.c
Normal file
79
src/dump/file.c
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include "file.h"
|
||||||
|
|
||||||
|
|
||||||
|
output_file_s *output_file_init(const char *path, bool json) {
|
||||||
|
output_file_s *output;
|
||||||
|
A_CALLOC(output, 1);
|
||||||
|
|
||||||
|
if (!strcmp(path, "-")) {
|
||||||
|
LOG_INFO("Using output: <stdout>");
|
||||||
|
output->fp = stdout;
|
||||||
|
} else {
|
||||||
|
LOG_INFO("Using output: %s", path);
|
||||||
|
if ((output->fp = fopen(path, "wb")) == NULL) {
|
||||||
|
LOG_PERROR("Can't open output file");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output->json = json;
|
||||||
|
return output;
|
||||||
|
|
||||||
|
error:
|
||||||
|
output_file_destroy(output);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void output_file_write(void *v_output, const frame_s *frame) {
|
||||||
|
output_file_s *output = (output_file_s *)v_output;
|
||||||
|
if (output->json) {
|
||||||
|
base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
|
||||||
|
fprintf(output->fp,
|
||||||
|
"{\"size\": %zu, \"width\": %u, \"height\": %u,"
|
||||||
|
" \"format\": %u, \"stride\": %u, \"online\": %u,"
|
||||||
|
" \"grab_ts\": %.3Lf, \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf,"
|
||||||
|
" \"data\": \"%s\"}\n",
|
||||||
|
frame->used, frame->width, frame->height,
|
||||||
|
frame->format, frame->stride, frame->online,
|
||||||
|
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts,
|
||||||
|
output->base64_data);
|
||||||
|
} else {
|
||||||
|
fwrite(frame->data, 1, frame->used, output->fp);
|
||||||
|
}
|
||||||
|
fflush(output->fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void output_file_destroy(void *v_output) {
|
||||||
|
output_file_s *output = (output_file_s *)v_output;
|
||||||
|
if (output->base64_data) {
|
||||||
|
free(output->base64_data);
|
||||||
|
}
|
||||||
|
if (output->fp && output->fp != stdout) {
|
||||||
|
if (fclose(output->fp) < 0) {
|
||||||
|
LOG_PERROR("Can't close output file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(output);
|
||||||
|
}
|
||||||
49
src/dump/file.h
Normal file
49
src/dump/file.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include "../libs/tools.h"
|
||||||
|
#include "../libs/logging.h"
|
||||||
|
#include "../libs/frame.h"
|
||||||
|
#include "../libs/base64.h"
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *path;
|
||||||
|
bool json;
|
||||||
|
|
||||||
|
FILE *fp;
|
||||||
|
char *base64_data;
|
||||||
|
size_t base64_allocated;
|
||||||
|
} output_file_s;
|
||||||
|
|
||||||
|
|
||||||
|
output_file_s *output_file_init(const char *path, bool json);
|
||||||
|
void output_file_write(void *v_output, const frame_s *frame);
|
||||||
|
void output_file_destroy(void *v_output);
|
||||||
@@ -33,7 +33,8 @@
|
|||||||
#include "../libs/logging.h"
|
#include "../libs/logging.h"
|
||||||
#include "../libs/frame.h"
|
#include "../libs/frame.h"
|
||||||
#include "../libs/memsink.h"
|
#include "../libs/memsink.h"
|
||||||
#include "../libs/base64.h"
|
|
||||||
|
#include "file.h"
|
||||||
|
|
||||||
|
|
||||||
enum _OPT_VALUES {
|
enum _OPT_VALUES {
|
||||||
@@ -73,13 +74,20 @@ static const struct option _LONG_OPTS[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
volatile bool stop = false;
|
volatile bool global_stop = false;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *v_output;
|
||||||
|
void (*write)(void *v_output, const frame_s *frame);
|
||||||
|
void (*destroy)(void *v_output);
|
||||||
|
} _output_context_s;
|
||||||
|
|
||||||
|
|
||||||
static void _signal_handler(int signum);
|
static void _signal_handler(int signum);
|
||||||
static void _install_signal_handlers(void);
|
static void _install_signal_handlers(void);
|
||||||
|
|
||||||
static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *output_path, bool output_json);
|
static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_context_s *ctx);
|
||||||
|
|
||||||
static void _help(FILE *fp);
|
static void _help(FILE *fp);
|
||||||
|
|
||||||
@@ -138,8 +146,23 @@ int main(int argc, char *argv[]) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_output_context_s ctx;
|
||||||
|
MEMSET_ZERO(ctx);
|
||||||
|
|
||||||
|
if (output_path && output_path[0] != '\0') {
|
||||||
|
if ((ctx.v_output = (void *)output_file_init(output_path, output_json)) == NULL) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
ctx.write = output_file_write;
|
||||||
|
ctx.destroy = output_file_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
_install_signal_handlers();
|
_install_signal_handlers();
|
||||||
return abs(_dump_sink(sink_name, sink_timeout, output_path, output_json));
|
int retval = abs(_dump_sink(sink_name, sink_timeout, &ctx));
|
||||||
|
if (ctx.v_output && ctx.destroy) {
|
||||||
|
ctx.destroy(ctx.v_output);
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -150,7 +173,7 @@ static void _signal_handler(int signum) {
|
|||||||
case SIGPIPE: LOG_INFO_NOLOCK("===== Stopping by SIGPIPE ====="); break;
|
case SIGPIPE: LOG_INFO_NOLOCK("===== Stopping by SIGPIPE ====="); break;
|
||||||
default: LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
|
default: LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
|
||||||
}
|
}
|
||||||
stop = true;
|
global_stop = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _install_signal_handlers(void) {
|
static void _install_signal_handlers(void) {
|
||||||
@@ -173,25 +196,9 @@ static void _install_signal_handlers(void) {
|
|||||||
assert(!sigaction(SIGPIPE, &sig_act, NULL));
|
assert(!sigaction(SIGPIPE, &sig_act, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *output_path, bool output_json) {
|
static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_context_s *ctx) {
|
||||||
frame_s *frame = frame_init("input");
|
frame_s *frame = frame_init("input");
|
||||||
memsink_s *sink = NULL;
|
memsink_s *sink = NULL;
|
||||||
FILE *output_fp = NULL;
|
|
||||||
char *base64_data = NULL;
|
|
||||||
size_t base64_allocated = 0;
|
|
||||||
|
|
||||||
if (output_path && output_path[0] != '\0') {
|
|
||||||
if (!strcmp(output_path, "-")) {
|
|
||||||
LOG_INFO("Using output: <stdout>");
|
|
||||||
output_fp = stdout;
|
|
||||||
} else {
|
|
||||||
LOG_INFO("Using output: %s", output_path);
|
|
||||||
if ((output_fp = fopen(output_path, "wb")) == NULL) {
|
|
||||||
LOG_PERROR("Can't open output file");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((sink = memsink_init("input", sink_name, false, 0, false, sink_timeout)) == NULL) {
|
if ((sink = memsink_init("input", sink_name, false, 0, false, sink_timeout)) == NULL) {
|
||||||
goto error;
|
goto error;
|
||||||
@@ -201,7 +208,7 @@ static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *
|
|||||||
unsigned fps_accum = 0;
|
unsigned fps_accum = 0;
|
||||||
long long fps_second = 0;
|
long long fps_second = 0;
|
||||||
|
|
||||||
while (!stop) {
|
while (!global_stop) {
|
||||||
int error = memsink_client_get(sink, frame);
|
int error = memsink_client_get(sink, frame);
|
||||||
if (error == 0) {
|
if (error == 0) {
|
||||||
const long double now = get_now_monotonic();
|
const long double now = get_now_monotonic();
|
||||||
@@ -225,22 +232,8 @@ static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *
|
|||||||
}
|
}
|
||||||
fps_accum += 1;
|
fps_accum += 1;
|
||||||
|
|
||||||
if (output_fp) {
|
if (ctx->v_output) {
|
||||||
if (output_json) {
|
ctx->write(ctx->v_output, frame);
|
||||||
base64_encode(frame->data, frame->used, &base64_data, &base64_allocated);
|
|
||||||
fprintf(output_fp,
|
|
||||||
"{\"size\": %zu, \"width\": %u, \"height\": %u,"
|
|
||||||
" \"format\": %u, \"stride\": %u, \"online\": %u,"
|
|
||||||
" \"grab_ts\": %.3Lf, \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf,"
|
|
||||||
" \"data\": \"%s\"}\n",
|
|
||||||
frame->used, frame->width, frame->height,
|
|
||||||
frame->format, frame->stride, frame->online,
|
|
||||||
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts,
|
|
||||||
base64_data);
|
|
||||||
} else {
|
|
||||||
fwrite(frame->data, 1, frame->used, output_fp);
|
|
||||||
}
|
|
||||||
fflush(output_fp);
|
|
||||||
}
|
}
|
||||||
} else if (error != -2) {
|
} else if (error != -2) {
|
||||||
goto error;
|
goto error;
|
||||||
@@ -254,14 +247,6 @@ static int _dump_sink(const char *sink_name, unsigned sink_timeout, const char *
|
|||||||
retval = -1;
|
retval = -1;
|
||||||
|
|
||||||
ok:
|
ok:
|
||||||
if (base64_data) {
|
|
||||||
free(base64_data);
|
|
||||||
}
|
|
||||||
if (output_fp && output_fp != stdout) {
|
|
||||||
if (fclose(output_fp) < 0) {
|
|
||||||
LOG_PERROR("Can't close output file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sink) {
|
if (sink) {
|
||||||
memsink_destroy(sink);
|
memsink_destroy(sink);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user