mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-03-10 09:33:43 +00:00
refactoring and test recorder
This commit is contained in:
27
src/libs/common/config.h
Normal file
27
src/libs/common/config.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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
|
||||
|
||||
#ifndef VERSION
|
||||
# define VERSION "2.2"
|
||||
#endif
|
||||
119
src/libs/common/frame.c
Normal file
119
src/libs/common/frame.c
Normal file
@@ -0,0 +1,119 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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 "frame.h"
|
||||
|
||||
|
||||
frame_s *frame_init(const char *role) {
|
||||
frame_s *frame;
|
||||
|
||||
A_CALLOC(frame, 1);
|
||||
frame->role = role;
|
||||
frame->managed = true;
|
||||
frame_realloc_data(frame, 500 * 1024);
|
||||
return frame;
|
||||
}
|
||||
|
||||
void frame_destroy(frame_s *frame) {
|
||||
assert(frame->managed);
|
||||
if (frame->data) {
|
||||
free(frame->data);
|
||||
}
|
||||
free(frame);
|
||||
}
|
||||
|
||||
void frame_realloc_data(frame_s *frame, size_t size) {
|
||||
assert(frame->managed);
|
||||
if (frame->allocated < size) {
|
||||
LOG_DEBUG("Increasing frame buffer '%s': %zu -> %zu (+%zu)",
|
||||
frame->role, frame->allocated, size, size - frame->allocated);
|
||||
A_REALLOC(frame->data, size);
|
||||
frame->allocated = size;
|
||||
}
|
||||
}
|
||||
|
||||
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size) {
|
||||
assert(frame->managed);
|
||||
frame_realloc_data(frame, size);
|
||||
memcpy(frame->data, data, size);
|
||||
frame->used = size;
|
||||
}
|
||||
|
||||
void frame_append_data(frame_s *frame, const uint8_t *data, size_t size) {
|
||||
assert(frame->managed);
|
||||
|
||||
size_t new_used = frame->used + size;
|
||||
|
||||
frame_realloc_data(frame, new_used);
|
||||
memcpy(frame->data + frame->used, data, size);
|
||||
frame->used = new_used;
|
||||
}
|
||||
|
||||
#define COPY(_field) dest->_field = src->_field
|
||||
|
||||
void frame_copy(const frame_s *src, frame_s *dest) {
|
||||
assert(dest->managed);
|
||||
frame_set_data(dest, src->data, src->used);
|
||||
COPY(used);
|
||||
frame_copy_meta(src, dest);
|
||||
}
|
||||
|
||||
void frame_copy_meta(const frame_s *src, frame_s *dest) {
|
||||
// Don't copy the role
|
||||
COPY(width);
|
||||
COPY(height);
|
||||
COPY(format);
|
||||
COPY(online);
|
||||
COPY(grab_ts);
|
||||
COPY(encode_begin_ts);
|
||||
COPY(encode_end_ts);
|
||||
}
|
||||
|
||||
#undef COPY
|
||||
|
||||
bool frame_compare(const frame_s *a, const frame_s *b) {
|
||||
return (
|
||||
a->allocated && b->allocated
|
||||
&& a->used == b->used
|
||||
&& a->width == b->width
|
||||
&& a->height == b->height
|
||||
&& a->online == b->online
|
||||
&& !memcmp(a->data, b->data, b->used)
|
||||
);
|
||||
}
|
||||
|
||||
const char *fourcc_to_string(unsigned format, char *buf, size_t size) {
|
||||
assert(size >= 8);
|
||||
buf[0] = format & 0x7F;
|
||||
buf[1] = (format >> 8) & 0x7F;
|
||||
buf[2] = (format >> 16) & 0x7F;
|
||||
buf[3] = (format >> 24) & 0x7F;
|
||||
if (format & ((unsigned)1 << 31)) {
|
||||
buf[4] = '-';
|
||||
buf[5] = 'B';
|
||||
buf[6] = 'E';
|
||||
buf[7] = '\0';
|
||||
} else {
|
||||
buf[4] = '\0';
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
66
src/libs/common/frame.h
Normal file
66
src/libs/common/frame.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
const char *role;
|
||||
|
||||
uint8_t *data;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
bool online;
|
||||
|
||||
long double grab_ts;
|
||||
long double encode_begin_ts;
|
||||
long double encode_end_ts;
|
||||
|
||||
bool managed;
|
||||
} frame_s;
|
||||
|
||||
|
||||
frame_s *frame_init(const char *role);
|
||||
void frame_destroy(frame_s *frame);
|
||||
|
||||
void frame_realloc_data(frame_s *frame, size_t size);
|
||||
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size);
|
||||
void frame_append_data(frame_s *frame, const uint8_t *data, size_t size);
|
||||
|
||||
void frame_copy(const frame_s *src, frame_s *dest);
|
||||
void frame_copy_meta(const frame_s *src, frame_s *dest);
|
||||
bool frame_compare(const frame_s *a, const frame_s *b);
|
||||
|
||||
const char *fourcc_to_string(unsigned format, char *buf, size_t size);
|
||||
30
src/libs/common/logging.c
Normal file
30
src/libs/common/logging.c
Normal file
@@ -0,0 +1,30 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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 "logging.h"
|
||||
|
||||
|
||||
enum log_level_t log_level;
|
||||
|
||||
bool log_colored;
|
||||
|
||||
pthread_mutex_t log_mutex;
|
||||
171
src/libs/common/logging.h
Normal file
171
src/libs/common/logging.h
Normal file
@@ -0,0 +1,171 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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 <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
|
||||
|
||||
enum log_level_t {
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_PERF,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
LOG_LEVEL_DEBUG,
|
||||
};
|
||||
|
||||
|
||||
extern enum log_level_t log_level;
|
||||
|
||||
extern bool log_colored;
|
||||
|
||||
extern pthread_mutex_t log_mutex;
|
||||
|
||||
|
||||
#define LOGGING_INIT { \
|
||||
log_level = LOG_LEVEL_INFO; \
|
||||
log_colored = isatty(1); \
|
||||
A_MUTEX_INIT(&log_mutex); \
|
||||
}
|
||||
|
||||
#define LOGGING_DESTROY A_MUTEX_DESTROY(&log_mutex)
|
||||
|
||||
#define LOGGING_LOCK A_MUTEX_LOCK(&log_mutex)
|
||||
#define LOGGING_UNLOCK A_MUTEX_UNLOCK(&log_mutex)
|
||||
|
||||
|
||||
#define COLOR_GRAY "\x1b[30;1m"
|
||||
#define COLOR_RED "\x1b[31;1m"
|
||||
#define COLOR_GREEN "\x1b[32;1m"
|
||||
#define COLOR_YELLOW "\x1b[33;1m"
|
||||
#define COLOR_BLUE "\x1b[34;1m"
|
||||
#define COLOR_CYAN "\x1b[36;1m"
|
||||
#define COLOR_RESET "\x1b[0m"
|
||||
|
||||
|
||||
#define SEP_INFO(_ch) { \
|
||||
LOGGING_LOCK; \
|
||||
for (int _i = 0; _i < 80; ++_i) { \
|
||||
putchar(_ch); \
|
||||
} \
|
||||
putchar('\n'); \
|
||||
fflush(stdout); \
|
||||
LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
#define SEP_DEBUG(_ch) { \
|
||||
if (log_level >= LOG_LEVEL_DEBUG) { \
|
||||
SEP_INFO(_ch); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \
|
||||
char _tname_buf[MAX_THREAD_NAME] = {0}; \
|
||||
thread_get_name(_tname_buf); \
|
||||
if (log_colored) { \
|
||||
printf(COLOR_GRAY "-- " _label_color _label COLOR_GRAY " [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
|
||||
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
|
||||
} else { \
|
||||
printf("-- " _label " [%.03Lf %9s] -- " _msg, \
|
||||
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
|
||||
} \
|
||||
putchar('\n'); \
|
||||
fflush(stdout); \
|
||||
}
|
||||
|
||||
#define LOG_PRINTF(_label_color, _label, _msg_color, _msg, ...) { \
|
||||
LOGGING_LOCK; \
|
||||
LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ##__VA_ARGS__); \
|
||||
LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
#define LOG_ERROR(_msg, ...) { \
|
||||
LOG_PRINTF(COLOR_RED, "ERROR", COLOR_RED, _msg, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LOG_PERROR(_msg, ...) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
|
||||
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _perror_ptr); \
|
||||
}
|
||||
|
||||
#define LOG_INFO(_msg, ...) { \
|
||||
LOG_PRINTF(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LOG_INFO_NOLOCK(_msg, ...) { \
|
||||
LOG_PRINTF_NOLOCK(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LOG_PERF(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_PERF) { \
|
||||
LOG_PRINTF(COLOR_CYAN, "PERF ", COLOR_CYAN, _msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_PERF_FPS(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_PERF) { \
|
||||
LOG_PRINTF(COLOR_YELLOW, "PERF ", COLOR_YELLOW, _msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_VERBOSE(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_VERBOSE) { \
|
||||
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_VERBOSE_PERROR(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_VERBOSE) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
|
||||
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_DEBUG(_msg, ...) { \
|
||||
if (log_level >= LOG_LEVEL_DEBUG) { \
|
||||
LOG_PRINTF(COLOR_GRAY, "DEBUG", COLOR_GRAY, _msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
INLINE char *errno_to_string(int error, char *buf, size_t size) {
|
||||
# if defined(__GLIBC__) && defined(_GNU_SOURCE)
|
||||
return strerror_r(error, buf, size);
|
||||
# else
|
||||
strerror_r(error, buf, size);
|
||||
return buf;
|
||||
# endif
|
||||
}
|
||||
139
src/libs/common/process.h
Normal file
139
src/libs/common/process.h
Normal file
@@ -0,0 +1,139 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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 <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
#if defined(__linux__)
|
||||
# define HAS_PDEATHSIG
|
||||
#elif defined(__FreeBSD__)
|
||||
# include <sys/param.h>
|
||||
# if __FreeBSD_version >= 1102000
|
||||
# define HAS_PDEATHSIG
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef WITH_SETPROCTITLE
|
||||
# include <stdlib.h>
|
||||
# include <string.h>
|
||||
# if defined(__linux__)
|
||||
# include <bsd/unistd.h>
|
||||
# elif (defined(__FreeBSD__) || defined(__DragonFly__))
|
||||
//# include <unistd.h>
|
||||
//# include <sys/types.h>
|
||||
# elif (defined(__NetBSD__) || defined(__OpenBSD__)) // setproctitle() placed in stdlib.h
|
||||
# else
|
||||
# error setproctitle() not implemented, you can disable it using WITH_SETPROCTITLE=0
|
||||
# endif
|
||||
#endif
|
||||
#ifdef HAS_PDEATHSIG
|
||||
# if defined(__linux__)
|
||||
# include <sys/prctl.h>
|
||||
# elif defined(__FreeBSD__)
|
||||
# include <sys/procctl.h>
|
||||
# endif
|
||||
#endif
|
||||
#ifdef WITH_SETPROCTITLE
|
||||
# include "tools.h"
|
||||
#endif
|
||||
#ifdef HAS_PDEATHSIG
|
||||
# include "logging.h"
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef WITH_SETPROCTITLE
|
||||
extern char **environ;
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef HAS_PDEATHSIG
|
||||
INLINE int process_track_parent_death(void) {
|
||||
pid_t parent = getppid();
|
||||
int signum = SIGTERM;
|
||||
# if defined(__linux__)
|
||||
int retval = prctl(PR_SET_PDEATHSIG, signum);
|
||||
# elif defined(__FreeBSD__)
|
||||
int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
|
||||
# else
|
||||
# error WTF?
|
||||
# endif
|
||||
if (retval < 0) {
|
||||
LOG_PERROR("Can't set to receive SIGTERM on parent process death");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (kill(parent, 0) < 0) {
|
||||
LOG_PERROR("The parent process %d is already dead", parent);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_SETPROCTITLE
|
||||
# pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
# pragma GCC diagnostic push
|
||||
INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix) {
|
||||
# pragma GCC diagnostic pop
|
||||
|
||||
char *cmdline = NULL;
|
||||
size_t allocated = 2048;
|
||||
size_t used = 0;
|
||||
|
||||
A_REALLOC(cmdline, allocated);
|
||||
cmdline[0] = '\0';
|
||||
|
||||
for (int index = 0; index < argc; ++index) {
|
||||
size_t arg_len = strlen(argv[index]);
|
||||
if (used + arg_len + 16 >= allocated) {
|
||||
allocated += arg_len + 2048;
|
||||
A_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
|
||||
}
|
||||
|
||||
strcat(cmdline, " ");
|
||||
strcat(cmdline, argv[index]);
|
||||
used = strlen(cmdline); // Не считаем вручную, так надежнее
|
||||
}
|
||||
|
||||
# ifdef __linux__
|
||||
setproctitle_init(argc, argv, environ);
|
||||
# endif
|
||||
setproctitle("-%s:%s", prefix, cmdline);
|
||||
|
||||
free(cmdline);
|
||||
}
|
||||
#endif
|
||||
|
||||
INLINE void process_notify_parent(void) {
|
||||
pid_t parent = getppid();
|
||||
|
||||
if (kill(parent, SIGUSR2) < 0) {
|
||||
LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent);
|
||||
}
|
||||
}
|
||||
125
src/libs/common/threading.h
Normal file
125
src/libs/common/threading.h
Normal file
@@ -0,0 +1,125 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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 <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
# if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
||||
# include <pthread_np.h>
|
||||
# include <sys/param.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
#ifdef PTHREAD_MAX_NAMELEN_NP
|
||||
# define MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
|
||||
#else
|
||||
# define MAX_THREAD_NAME ((size_t)16)
|
||||
#endif
|
||||
|
||||
#define A_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg))
|
||||
#define A_THREAD_JOIN(_tid) assert(!pthread_join(_tid, NULL))
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
# define A_THREAD_RENAME(_fmt, ...) { \
|
||||
char _new_tname_buf[MAX_THREAD_NAME] = {0}; \
|
||||
assert(snprintf(_new_tname_buf, MAX_THREAD_NAME, _fmt, ##__VA_ARGS__) > 0); \
|
||||
thread_set_name(_new_tname_buf); \
|
||||
}
|
||||
#else
|
||||
# define A_THREAD_RENAME(_fmt, ...)
|
||||
#endif
|
||||
|
||||
#define A_MUTEX_INIT(_mutex) assert(!pthread_mutex_init(_mutex, NULL))
|
||||
#define A_MUTEX_DESTROY(_mutex) assert(!pthread_mutex_destroy(_mutex))
|
||||
#define A_MUTEX_LOCK(_mutex) assert(!pthread_mutex_lock(_mutex))
|
||||
#define A_MUTEX_UNLOCK(_mutex) assert(!pthread_mutex_unlock(_mutex))
|
||||
|
||||
#define A_COND_INIT(_cond) assert(!pthread_cond_init(_cond, NULL))
|
||||
#define A_COND_DESTROY(_cond) assert(!pthread_cond_destroy(_cond))
|
||||
#define A_COND_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__))
|
||||
#define A_COND_WAIT_TRUE(_var, _cond, _mutex) { while(!_var) assert(!pthread_cond_wait(_cond, _mutex)); }
|
||||
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
INLINE void thread_set_name(const char *name) {
|
||||
# if defined(__linux__)
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
# elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
||||
pthread_set_name_np(pthread_self(), name);
|
||||
# elif defined(__NetBSD__)
|
||||
pthread_setname_np(pthread_self(), "%s", (void *)name);
|
||||
# else
|
||||
# error thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
INLINE void thread_get_name(char *name) { // Always required for logging
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
int retval = -1;
|
||||
# if defined(__linux__) || defined (__NetBSD__)
|
||||
retval = pthread_getname_np(pthread_self(), name, MAX_THREAD_NAME);
|
||||
# elif \
|
||||
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|
||||
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|
||||
|| defined(__DragonFly__)
|
||||
pthread_get_name_np(pthread_self(), name, MAX_THREAD_NAME);
|
||||
if (name[0] != '\0') {
|
||||
retval = 0;
|
||||
}
|
||||
# else
|
||||
# error thread_get_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
|
||||
# endif
|
||||
if (retval < 0) {
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
pid_t tid = syscall(SYS_gettid);
|
||||
#elif defined(__FreeBSD__)
|
||||
pid_t tid = syscall(SYS_thr_self);
|
||||
#elif defined(__OpenBSD__)
|
||||
pid_t tid = syscall(SYS_getthrid);
|
||||
#elif defined(__NetBSD__)
|
||||
pid_t tid = syscall(SYS__lwp_self);
|
||||
#elif defined(__DragonFly__)
|
||||
pid_t tid = syscall(SYS_lwp_gettid);
|
||||
#else
|
||||
pid_t tid = 0; // Makes cppcheck happy
|
||||
# warning gettid() not implemented
|
||||
#endif
|
||||
assert(snprintf(name, MAX_THREAD_NAME, "tid=%d", tid) > 0);
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
}
|
||||
#endif
|
||||
}
|
||||
106
src/libs/common/tools.h
Normal file
106
src/libs/common/tools.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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 <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
|
||||
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
|
||||
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
|
||||
|
||||
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
|
||||
|
||||
#define INLINE inline __attribute__((always_inline))
|
||||
#define UNUSED __attribute__((unused))
|
||||
|
||||
|
||||
INLINE char *bool_to_string(bool flag) {
|
||||
return (flag ? "true" : "false");
|
||||
}
|
||||
|
||||
INLINE size_t align_size(size_t size, size_t to) {
|
||||
return ((size + (to - 1)) & ~(to - 1));
|
||||
}
|
||||
|
||||
INLINE unsigned min_u(unsigned a, unsigned b) {
|
||||
return (a < b ? a : b);
|
||||
}
|
||||
|
||||
INLINE unsigned max_u(unsigned a, unsigned b) {
|
||||
return (a > b ? a : b);
|
||||
}
|
||||
|
||||
INLINE long long floor_ms(long double now) {
|
||||
return (long long)now - (now < (long long)now); // floor()
|
||||
}
|
||||
|
||||
INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
struct timespec spec;
|
||||
|
||||
assert(!clock_gettime(clk_id, &spec));
|
||||
*sec = spec.tv_sec;
|
||||
*msec = round(spec.tv_nsec / 1.0e6);
|
||||
|
||||
if (*msec > 999) {
|
||||
*sec += 1;
|
||||
*msec = 0;
|
||||
}
|
||||
}
|
||||
|
||||
INLINE long double get_now_monotonic(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
|
||||
# if defined(CLOCK_MONOTONIC_RAW)
|
||||
get_now(CLOCK_MONOTONIC_RAW, &sec, &msec);
|
||||
# elif defined(CLOCK_MONOTONIC_FAST)
|
||||
get_now(CLOCK_MONOTONIC_FAST, &sec, &msec);
|
||||
# else
|
||||
get_now(CLOCK_MONOTONIC, &sec, &msec);
|
||||
# endif
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE long double get_now_real(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
|
||||
get_now(CLOCK_REALTIME, &sec, &msec);
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE unsigned get_cores_available(void) {
|
||||
long cores_sysconf;
|
||||
|
||||
cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
|
||||
return max_u(min_u(cores_sysconf, 4), 1);
|
||||
}
|
||||
390
src/libs/h264/encoder.c
Normal file
390
src/libs/h264/encoder.c
Normal file
@@ -0,0 +1,390 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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 "encoder.h"
|
||||
|
||||
|
||||
static int _h264_encoder_configure(h264_encoder_s *encoder, const frame_s *frame);
|
||||
static void _h264_encoder_cleanup(h264_encoder_s *encoder);
|
||||
static int _h264_encoder_compress_raw(h264_encoder_s *encoder, const frame_s *src, frame_s *dest, bool force_key);
|
||||
|
||||
static void _mmal_callback(MMAL_WRAPPER_T *wrapper);
|
||||
static const char *_mmal_error_to_string(MMAL_STATUS_T error);
|
||||
|
||||
|
||||
#define RUN(_next) encoder->run->_next
|
||||
|
||||
#define LOG_ERROR_MMAL(_error, _msg, ...) { \
|
||||
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _mmal_error_to_string(_error)); \
|
||||
}
|
||||
|
||||
|
||||
h264_encoder_s *h264_encoder_init(void) {
|
||||
h264_encoder_runtime_s *run;
|
||||
h264_encoder_s *encoder;
|
||||
|
||||
A_CALLOC(run, 1);
|
||||
run->tmp = frame_init("h264_tmp");
|
||||
|
||||
A_CALLOC(encoder, 1);
|
||||
encoder->gop = 60;
|
||||
encoder->bps = 5000 * 1000; // Kbps * 1000
|
||||
encoder->fps = 30;
|
||||
encoder->run = run;
|
||||
return encoder;
|
||||
}
|
||||
|
||||
void h264_encoder_destroy(h264_encoder_s *encoder) {
|
||||
_h264_encoder_cleanup(encoder);
|
||||
if (RUN(i_handler_sem)) {
|
||||
vcos_semaphore_delete(&RUN(handler_sem));
|
||||
}
|
||||
if (RUN(i_bcm_host)) {
|
||||
bcm_host_deinit();
|
||||
}
|
||||
frame_destroy(RUN(tmp));
|
||||
free(encoder);
|
||||
}
|
||||
|
||||
int h264_encoder_compress(h264_encoder_s *encoder, const frame_s *src, frame_s *dest, bool force_key) {
|
||||
assert(src->used > 0);
|
||||
assert(src->width > 0);
|
||||
assert(src->height > 0);
|
||||
assert(src->format > 0);
|
||||
|
||||
if (!RUN(i_bcm_host)) {
|
||||
LOG_INFO("Initializing BCM ...");
|
||||
bcm_host_init();
|
||||
RUN(i_bcm_host) = true;
|
||||
}
|
||||
|
||||
if (!RUN(i_handler_sem)) {
|
||||
LOG_INFO("Creating VCOS semaphore ...");
|
||||
if (vcos_semaphore_create(&RUN(handler_sem), "h264_handler_sem", 0) != VCOS_SUCCESS) {
|
||||
LOG_PERROR("Can't create VCOS semaphore");
|
||||
return -1;
|
||||
}
|
||||
RUN(i_handler_sem) = true;
|
||||
}
|
||||
|
||||
if (src->format == V4L2_PIX_FMT_MJPEG || src->format == V4L2_PIX_FMT_JPEG) {
|
||||
LOG_DEBUG("Input frame format is JPEG; decoding ...");
|
||||
if (unjpeg(src, RUN(tmp)) < 0) {
|
||||
return -1;
|
||||
}
|
||||
src = RUN(tmp);
|
||||
}
|
||||
|
||||
if (RUN(width) != src->width || RUN(height) != src->height || RUN(format) != src->format) {
|
||||
if (_h264_encoder_configure(encoder, src) < 0) {
|
||||
return -1;
|
||||
}
|
||||
force_key = true;
|
||||
}
|
||||
|
||||
if (_h264_encoder_compress_raw(encoder, src, dest, force_key) < 0) {
|
||||
_h264_encoder_cleanup(encoder);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _h264_encoder_configure(h264_encoder_s *encoder, const frame_s *frame) {
|
||||
MMAL_STATUS_T error;
|
||||
|
||||
# define PREPARE_PORT(_id) { \
|
||||
RUN(_id##_port) = RUN(wrapper->_id[0]); \
|
||||
if (RUN(_id##_port->is_enabled)) { \
|
||||
if ((error = mmal_wrapper_port_disable(RUN(_id##_port))) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "Can't disable MMAL %s port while configuring", #_id); \
|
||||
goto error; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
# define COMMIT_PORT(_id) { \
|
||||
if ((error = mmal_port_format_commit(RUN(_id##_port))) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "Can't commit MMAL %s port", #_id); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
# define SET_PORT_PARAM(_id, _type, _key, _value) { \
|
||||
if ((error = mmal_port_parameter_set_##_type(RUN(_id##_port), _key, _value)) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "Can't set %s for the %s port", #_key, #_id); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
# define ENABLE_PORT(_id) { \
|
||||
if ((error = mmal_wrapper_port_enable(RUN(_id##_port), MMAL_WRAPPER_FLAG_PAYLOAD_ALLOCATE)) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "Can't enable MMAL %s port", #_id); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
_h264_encoder_cleanup(encoder);
|
||||
|
||||
LOG_INFO("Applying H264 configuration ...");
|
||||
|
||||
if ((error = mmal_wrapper_create(&RUN(wrapper), MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "Can't create MMAL wrapper");
|
||||
goto error;
|
||||
}
|
||||
RUN(wrapper->status) = MMAL_SUCCESS;
|
||||
|
||||
{
|
||||
PREPARE_PORT(input);
|
||||
|
||||
# define IFMT(_next) RUN(input_port->format->_next)
|
||||
IFMT(type) = MMAL_ES_TYPE_VIDEO;
|
||||
if (frame->format == V4L2_PIX_FMT_UYVY) {
|
||||
LOG_INFO("Using pixelformat: UYVY");
|
||||
IFMT(encoding) = MMAL_ENCODING_UYVY;
|
||||
} else if (frame->format == V4L2_PIX_FMT_RGB24) {
|
||||
LOG_INFO("Using pixelformat: RGB24");
|
||||
IFMT(encoding) = MMAL_ENCODING_RGB24;
|
||||
} else {
|
||||
char fourcc_buf[8];
|
||||
LOG_ERROR("Unsupported pixelformat (fourcc): %s", fourcc_to_string(frame->format, fourcc_buf, 8);
|
||||
goto error;
|
||||
}
|
||||
LOG_INFO("Using resolution: %ux%u", frame->width, frame->height);
|
||||
IFMT(es->video.width) = align_size(frame->width, 32);
|
||||
IFMT(es->video.height) = align_size(frame->height, 16);
|
||||
IFMT(es->video.crop.x) = 0;
|
||||
IFMT(es->video.crop.y) = 0;
|
||||
IFMT(es->video.crop.width) = frame->width;
|
||||
IFMT(es->video.crop.height) = frame->height;
|
||||
IFMT(flags) = MMAL_ES_FORMAT_FLAG_FRAMED;
|
||||
RUN(input_port->buffer_size) = 1000 * 1000;
|
||||
RUN(input_port->buffer_num) = RUN(input_port->buffer_num_recommended) * 4;
|
||||
# undef IFMT
|
||||
|
||||
COMMIT_PORT(input);
|
||||
SET_PORT_PARAM(input, boolean, MMAL_PARAMETER_ZERO_COPY, MMAL_FALSE);
|
||||
}
|
||||
|
||||
{
|
||||
PREPARE_PORT(output);
|
||||
|
||||
# define OFMT(_next) RUN(output_port->format->_next)
|
||||
OFMT(type) = MMAL_ES_TYPE_VIDEO;
|
||||
OFMT(encoding) = MMAL_ENCODING_H264;
|
||||
OFMT(encoding_variant) = MMAL_ENCODING_VARIANT_H264_DEFAULT;
|
||||
OFMT(bitrate) = encoder->bps;
|
||||
OFMT(es->video.frame_rate.num) = encoder->fps;
|
||||
OFMT(es->video.frame_rate.den) = 1;
|
||||
RUN(output_port->buffer_size) = RUN(output_port->buffer_size_recommended) * 4;
|
||||
RUN(output_port->buffer_num) = RUN(output_port->buffer_num_recommended);
|
||||
# undef OFMT
|
||||
|
||||
COMMIT_PORT(output);
|
||||
{
|
||||
MMAL_PARAMETER_VIDEO_PROFILE_T profile;
|
||||
MEMSET_ZERO(profile);
|
||||
profile.hdr.id = MMAL_PARAMETER_PROFILE;
|
||||
profile.hdr.size = sizeof(profile);
|
||||
// http://blog.mediacoderhq.com/h264-profiles-and-levels
|
||||
profile.profile[0].profile = MMAL_VIDEO_PROFILE_H264_CONSTRAINED_BASELINE;
|
||||
profile.profile[0].level = MMAL_VIDEO_LEVEL_H264_4; // Supports 1080p
|
||||
if ((error = mmal_port_parameter_set(RUN(output_port), &profile.hdr)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "Can't set MMAL_PARAMETER_PROFILE for the output port");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
SET_PORT_PARAM(output, boolean, MMAL_PARAMETER_ZERO_COPY, MMAL_TRUE);
|
||||
SET_PORT_PARAM(output, uint32, MMAL_PARAMETER_INTRAPERIOD, encoder->gop);
|
||||
SET_PORT_PARAM(output, uint32, MMAL_PARAMETER_NALUNITFORMAT, MMAL_VIDEO_NALUNITFORMAT_STARTCODES);
|
||||
SET_PORT_PARAM(output, boolean, MMAL_PARAMETER_MINIMISE_FRAGMENTATION, MMAL_TRUE);
|
||||
SET_PORT_PARAM(output, uint32, MMAL_PARAMETER_MB_ROWS_PER_SLICE, 0);
|
||||
SET_PORT_PARAM(output, boolean, MMAL_PARAMETER_VIDEO_IMMUTABLE_INPUT, MMAL_FALSE);
|
||||
SET_PORT_PARAM(output, boolean, MMAL_PARAMETER_VIDEO_DROPPABLE_PFRAMES, MMAL_FALSE);
|
||||
SET_PORT_PARAM(output, uint32, MMAL_PARAMETER_VIDEO_BIT_RATE, encoder->bps);
|
||||
SET_PORT_PARAM(output, uint32, MMAL_PARAMETER_VIDEO_ENCODE_PEAK_RATE, encoder->bps);
|
||||
SET_PORT_PARAM(output, uint32, MMAL_PARAMETER_VIDEO_ENCODE_MIN_QUANT, 16);
|
||||
SET_PORT_PARAM(output, uint32, MMAL_PARAMETER_VIDEO_ENCODE_MAX_QUANT, 34);
|
||||
SET_PORT_PARAM(output, uint32, MMAL_PARAMETER_VIDEO_ENCODE_FRAME_LIMIT_BITS, 1000000);
|
||||
SET_PORT_PARAM(output, uint32, MMAL_PARAMETER_VIDEO_ENCODE_H264_AU_DELIMITERS, MMAL_FALSE);
|
||||
}
|
||||
|
||||
RUN(wrapper->user_data) = (void *)encoder;
|
||||
RUN(wrapper->callback) = _mmal_callback;
|
||||
|
||||
ENABLE_PORT(input);
|
||||
ENABLE_PORT(output);
|
||||
|
||||
RUN(width) = frame->width;
|
||||
RUN(height) = frame->height;
|
||||
RUN(format) = frame->format;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
_h264_encoder_cleanup(encoder);
|
||||
return -1;
|
||||
|
||||
# undef ENABLE_PORT
|
||||
# undef SET_PORT_PARAM
|
||||
# undef COMMIT_PORT
|
||||
# undef PREPARE_PORT
|
||||
}
|
||||
|
||||
static void _h264_encoder_cleanup(h264_encoder_s *encoder) {
|
||||
MMAL_STATUS_T error;
|
||||
|
||||
# define DISABLE_PORT(_id) { \
|
||||
if (RUN(_id##_port)) { \
|
||||
if ((error = mmal_wrapper_port_disable(RUN(_id##_port))) != MMAL_SUCCESS) { \
|
||||
LOG_ERROR_MMAL(error, "Can't disable MMAL %s port", #_id); \
|
||||
} \
|
||||
RUN(_id##_port) = NULL; \
|
||||
} \
|
||||
}
|
||||
|
||||
DISABLE_PORT(input);
|
||||
DISABLE_PORT(output);
|
||||
|
||||
# undef DISABLE_PORT
|
||||
|
||||
if (RUN(wrapper)) {
|
||||
if ((error = mmal_wrapper_destroy(RUN(wrapper))) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "Can't destroy MMAL encoder");
|
||||
}
|
||||
RUN(wrapper) = NULL;
|
||||
}
|
||||
|
||||
RUN(width) = 0;
|
||||
RUN(height) = 0;
|
||||
RUN(format) = 0;
|
||||
}
|
||||
|
||||
static int _h264_encoder_compress_raw(h264_encoder_s *encoder, const frame_s *src, frame_s *dest, bool force_key) {
|
||||
MMAL_STATUS_T error;
|
||||
MMAL_BUFFER_HEADER_T *out = NULL;
|
||||
MMAL_BUFFER_HEADER_T *in = NULL;
|
||||
bool eos = false;
|
||||
bool sent = false;
|
||||
|
||||
assert(src->used > 0);
|
||||
assert(src->width == encoder->width);
|
||||
assert(src->height == encoder->height);
|
||||
assert(src->format == encoder->format);
|
||||
|
||||
LOG_DEBUG("Compressing new H264 frame; force_key=%d ...", force_key);
|
||||
|
||||
frame_copy_meta(src, dest);
|
||||
dest->format = V4L2_PIX_FMT_H264;
|
||||
dest->encode_begin_ts = get_now_monotonic();
|
||||
dest->used = 0;
|
||||
|
||||
if (force_key) {
|
||||
if ((error = mmal_port_parameter_set_boolean(
|
||||
RUN(output_port),
|
||||
MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME,
|
||||
MMAL_TRUE
|
||||
)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "Can't request keyframe");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
while (!eos) {
|
||||
out = NULL;
|
||||
while (mmal_wrapper_buffer_get_empty(RUN(output_port), &out, 0) == MMAL_SUCCESS) {
|
||||
if ((error = mmal_port_send_buffer(RUN(output_port), out)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "Can't send MMAL output buffer");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
in = NULL;
|
||||
if (!sent && mmal_wrapper_buffer_get_empty(RUN(input_port), &in, 0) == MMAL_SUCCESS) {
|
||||
in->data = src->data;
|
||||
in->length = src->used;
|
||||
in->offset = 0;
|
||||
in->flags = MMAL_BUFFER_HEADER_FLAG_EOS;
|
||||
if ((error = mmal_port_send_buffer(RUN(input_port), in)) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "Can't send MMAL input buffer");
|
||||
return -1;
|
||||
}
|
||||
sent = true;
|
||||
}
|
||||
|
||||
error = mmal_wrapper_buffer_get_full(RUN(output_port), &out, 0);
|
||||
if (error == MMAL_EAGAIN) {
|
||||
vcos_semaphore_wait(&RUN(handler_sem));
|
||||
continue;
|
||||
} else if (error != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "Can't get MMAL output buffer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
frame_append_data(dest, out->data, out->length);
|
||||
|
||||
eos = out->flags & MMAL_BUFFER_HEADER_FLAG_EOS;
|
||||
mmal_buffer_header_release(out);
|
||||
}
|
||||
|
||||
if ((error = mmal_port_flush(RUN(output_port))) != MMAL_SUCCESS) {
|
||||
LOG_ERROR_MMAL(error, "Can't flush MMAL output buffer; ignored");
|
||||
}
|
||||
|
||||
dest->encode_end_ts = get_now_monotonic();
|
||||
LOG_VERBOSE("Compressed new H264 frame: force_key=%d, size=%zu, time=%0.3Lf",
|
||||
force_key, dest->used, dest->encode_end_ts - dest->encode_begin_ts);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _mmal_callback(MMAL_WRAPPER_T *wrapper) {
|
||||
vcos_semaphore_post(&((h264_encoder_s *)(wrapper->user_data))->run->handler_sem);
|
||||
}
|
||||
|
||||
static const char *_mmal_error_to_string(MMAL_STATUS_T error) {
|
||||
// http://www.jvcref.com/files/PI/documentation/html/group___mmal_types.html
|
||||
# define CASE_ERROR(_name, _msg) case MMAL_##_name: return "MMAL_" #_name " [" _msg "]"
|
||||
switch (error) {
|
||||
case MMAL_SUCCESS: return "MMAL_SUCCESS";
|
||||
CASE_ERROR(ENOMEM, "Out of memory");
|
||||
CASE_ERROR(ENOSPC, "Out of resources");
|
||||
CASE_ERROR(EINVAL, "Invalid argument");
|
||||
CASE_ERROR(ENOSYS, "Function not implemented");
|
||||
CASE_ERROR(ENOENT, "No such file or directory");
|
||||
CASE_ERROR(ENXIO, "No such device or address");
|
||||
CASE_ERROR(EIO, "IO error");
|
||||
CASE_ERROR(ESPIPE, "Illegal seek");
|
||||
CASE_ERROR(ECORRUPT, "Data is corrupt");
|
||||
CASE_ERROR(ENOTREADY, "Component is not ready");
|
||||
CASE_ERROR(ECONFIG, "Component is not configured");
|
||||
CASE_ERROR(EISCONN, "Port is already connected");
|
||||
CASE_ERROR(ENOTCONN, "Port is disconnected");
|
||||
CASE_ERROR(EAGAIN, "Resource temporarily unavailable");
|
||||
CASE_ERROR(EFAULT, "Bad address");
|
||||
case MMAL_STATUS_MAX: break; // Makes cpplint happy
|
||||
}
|
||||
return "Unknown error";
|
||||
# undef CASE_ERROR
|
||||
}
|
||||
|
||||
#undef LOG_ERROR_MMAL
|
||||
#undef RUN
|
||||
70
src/libs/h264/encoder.h
Normal file
70
src/libs/h264/encoder.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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 <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <bcm_host.h>
|
||||
#include <interface/mmal/mmal.h>
|
||||
#include <interface/mmal/mmal_format.h>
|
||||
#include <interface/mmal/util/mmal_default_components.h>
|
||||
#include <interface/mmal/util/mmal_component_wrapper.h>
|
||||
#include <interface/mmal/util/mmal_util_params.h>
|
||||
|
||||
#include "../common/tools.h"
|
||||
#include "../common/logging.h"
|
||||
#include "../common/frame.h"
|
||||
|
||||
#include "unjpeg.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
MMAL_WRAPPER_T *wrapper;
|
||||
MMAL_PORT_T *input_port;
|
||||
MMAL_PORT_T *output_port;
|
||||
VCOS_SEMAPHORE_T handler_sem;
|
||||
bool i_bcm_host;
|
||||
bool i_handler_sem;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
frame_s *tmp;
|
||||
} h264_encoder_runtime_s;
|
||||
|
||||
typedef struct {
|
||||
unsigned gop; // Interval between keyframes
|
||||
unsigned bps; // Bit-per-sec
|
||||
unsigned fps;
|
||||
h264_encoder_runtime_s *run;
|
||||
} h264_encoder_s;
|
||||
|
||||
|
||||
h264_encoder_s *h264_encoder_init(void);
|
||||
void h264_encoder_destroy(h264_encoder_s *encoder);
|
||||
|
||||
int h264_encoder_compress(h264_encoder_s *encoder, const frame_s *src, frame_s *dest, bool force_key);
|
||||
87
src/libs/h264/unjpeg.c
Normal file
87
src/libs/h264/unjpeg.c
Normal file
@@ -0,0 +1,87 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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 "unjpeg.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_error_mgr mgr; // Default manager
|
||||
jmp_buf jmp;
|
||||
} _jpeg_error_manager_s;
|
||||
|
||||
|
||||
static void _jpeg_error_handler(j_common_ptr jpeg);
|
||||
|
||||
|
||||
int unjpeg(const frame_s *src, frame_s *dest) {
|
||||
struct jpeg_decompress_struct jpeg;
|
||||
_jpeg_error_manager_s jpeg_error;
|
||||
JSAMPARRAY scanlines;
|
||||
unsigned row_stride;
|
||||
volatile int retval = 0;
|
||||
|
||||
frame_realloc_data(dest, ((src->width * src->height) << 1) * 2);
|
||||
frame_copy_meta(src, dest);
|
||||
dest->format = V4L2_PIX_FMT_RGB24;
|
||||
dest->used = 0;
|
||||
|
||||
jpeg_create_decompress(&jpeg);
|
||||
|
||||
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
|
||||
jpeg.err = jpeg_std_error((struct jpeg_error_mgr *)&jpeg_error);
|
||||
jpeg_error.mgr.error_exit = _jpeg_error_handler;
|
||||
if (setjmp(jpeg_error.jmp) < 0) {
|
||||
retval = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
jpeg_mem_src(&jpeg, src->data, src->used);
|
||||
jpeg_read_header(&jpeg, TRUE);
|
||||
jpeg.out_color_space = JCS_RGB;
|
||||
|
||||
jpeg_start_decompress(&jpeg);
|
||||
row_stride = jpeg.output_width * jpeg.output_components;
|
||||
scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr) &jpeg, JPOOL_IMAGE, row_stride, 1);
|
||||
|
||||
while (jpeg.output_scanline < jpeg.output_height) {
|
||||
jpeg_read_scanlines(&jpeg, scanlines, 1);
|
||||
frame_append_data(dest, scanlines[0], row_stride);
|
||||
}
|
||||
|
||||
jpeg_finish_decompress(&jpeg);
|
||||
|
||||
dest->width = jpeg.output_width;
|
||||
dest->height = jpeg.output_height;
|
||||
|
||||
done:
|
||||
jpeg_destroy_decompress(&jpeg);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void _jpeg_error_handler(j_common_ptr jpeg) {
|
||||
_jpeg_error_manager_s *jpeg_error = (_jpeg_error_manager_s *)jpeg->err;
|
||||
char msg[JMSG_LENGTH_MAX];
|
||||
|
||||
(*jpeg_error->mgr.format_message)(jpeg, msg);
|
||||
LOG_ERROR("Can't decompress JPEG: %s", msg);
|
||||
longjmp(jpeg_error->jmp, -1);
|
||||
}
|
||||
39
src/libs/h264/unjpeg.h
Normal file
39
src/libs/h264/unjpeg.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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 <stdint.h>
|
||||
#include <setjmp.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../common/logging.h"
|
||||
#include "../common/frame.h"
|
||||
|
||||
|
||||
int unjpeg(const frame_s *src, frame_s *dest);
|
||||
240
src/libs/rawsink/rawsink.c
Normal file
240
src/libs/rawsink/rawsink.c
Normal file
@@ -0,0 +1,240 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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 "rawsink.h"
|
||||
|
||||
|
||||
static int _sem_timedwait_monotonic(sem_t *sem, long double timeout);
|
||||
static int _flock_timedwait_monotonic(int fd, long double timeout);
|
||||
|
||||
|
||||
rawsink_s *rawsink_open(const char *name, bool server, mode_t mode, bool rm, unsigned timeout) {
|
||||
rawsink_s *rawsink;
|
||||
int flags = (server ? O_RDWR | O_CREAT : O_RDWR);
|
||||
|
||||
A_CALLOC(rawsink, 1);
|
||||
rawsink->server = server;
|
||||
rawsink->rm = rm;
|
||||
rawsink->timeout = timeout;
|
||||
rawsink->fd = -1;
|
||||
rawsink->mem = MAP_FAILED;
|
||||
rawsink->sig_sem = SEM_FAILED;
|
||||
|
||||
A_CALLOC(rawsink->mem_name, strlen(name) + 8);
|
||||
A_CALLOC(rawsink->sig_name, strlen(name) + 8);
|
||||
|
||||
sprintf(rawsink->mem_name, "%s.mem", name);
|
||||
sprintf(rawsink->sig_name, "%s.sig", name);
|
||||
|
||||
LOG_INFO("Using RAW sink: %s.{mem,sig}", name);
|
||||
|
||||
# define OPEN_SIGNAL { \
|
||||
if ((rawsink->sig_sem = sem_open(rawsink->sig_name, flags, mode, 0)) == SEM_FAILED) { \
|
||||
LOG_PERROR("Can't open RAW sink signal semaphore"); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
if (!server) {
|
||||
OPEN_SIGNAL;
|
||||
}
|
||||
|
||||
{ // Shared memory
|
||||
if ((rawsink->fd = shm_open(rawsink->mem_name, flags, mode)) == -1) {
|
||||
LOG_PERROR("Can't open RAW sink memory");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (rawsink->server && ftruncate(rawsink->fd, sizeof(rawsink_shared_s)) < 0) {
|
||||
LOG_PERROR("Can't truncate RAW sink memory");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((rawsink->mem = mmap(
|
||||
NULL,
|
||||
sizeof(rawsink_shared_s),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
rawsink->fd,
|
||||
0
|
||||
)) == MAP_FAILED) {
|
||||
LOG_PERROR("Can't mmap RAW sink memory");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (server) {
|
||||
OPEN_SIGNAL;
|
||||
}
|
||||
|
||||
# undef OPEN_SIGNAL
|
||||
|
||||
return rawsink;
|
||||
|
||||
error:
|
||||
rawsink_close(rawsink);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void rawsink_close(rawsink_s *rawsink) {
|
||||
if (rawsink->sig_sem != SEM_FAILED) {
|
||||
if (sem_close(rawsink->sig_sem) < 0) {
|
||||
LOG_PERROR("Can't close RAW sink signal semaphore");
|
||||
}
|
||||
if (rawsink->rm && sem_unlink(rawsink->sig_name) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
LOG_PERROR("Can't remove RAW sink signal semaphore");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rawsink->mem != MAP_FAILED) {
|
||||
if (munmap(rawsink->mem, sizeof(rawsink_shared_s)) < 0) {
|
||||
LOG_PERROR("Can't unmap RAW sink memory");
|
||||
}
|
||||
}
|
||||
|
||||
if (rawsink->fd >= 0) {
|
||||
if (close(rawsink->fd) < 0) {
|
||||
LOG_PERROR("Can't close RAW sink fd");
|
||||
}
|
||||
if (rawsink->rm && shm_unlink(rawsink->mem_name) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
LOG_PERROR("Can't remove RAW sink memory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(rawsink->sig_name);
|
||||
free(rawsink->mem_name);
|
||||
free(rawsink);
|
||||
}
|
||||
|
||||
int rawsink_server_put(rawsink_s *rawsink, frame_s *frame) {
|
||||
long double now = get_now_monotonic();
|
||||
|
||||
assert(rawsink->server);
|
||||
|
||||
if (frame->used > RAWSINK_MAX_DATA) {
|
||||
LOG_ERROR("RAWSINK: Can't put RAW frame: is too big (%zu > %zu)", frame->used, RAWSINK_MAX_DATA);
|
||||
return 0; // -2
|
||||
}
|
||||
|
||||
if (_flock_timedwait_monotonic(rawsink->fd, 1) == 0) {
|
||||
LOG_PERF("RAWSINK: >>>>> Exposing new frame ...");
|
||||
|
||||
if (sem_trywait(rawsink->sig_sem) < 0 && errno != EAGAIN) {
|
||||
LOG_PERROR("RAWSINK: Can't wait signal semaphore");
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define COPY(_field) rawsink->mem->_field = frame->_field
|
||||
COPY(used);
|
||||
COPY(width);
|
||||
COPY(height);
|
||||
COPY(format);
|
||||
COPY(online);
|
||||
COPY(grab_ts);
|
||||
memcpy(rawsink->mem->data, frame->data, frame->used);
|
||||
# undef COPY
|
||||
|
||||
if (sem_post(rawsink->sig_sem) < 0) {
|
||||
LOG_PERROR("RAWSINK: Can't post signal semaphore");
|
||||
return -1;
|
||||
}
|
||||
if (flock(rawsink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("RAWSINK: Can't unlock memory");
|
||||
return -1;
|
||||
}
|
||||
LOG_VERBOSE("RAWSINK: Exposed new frame; full exposition time = %Lf", get_now_monotonic() - now);
|
||||
|
||||
} else if (errno == EWOULDBLOCK) {
|
||||
LOG_PERF("RAWSINK: ===== Shared memory is busy now; frame skipped");
|
||||
|
||||
} else {
|
||||
LOG_PERROR("RAWSINK: Can't lock memory");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rawsink_client_get(rawsink_s *rawsink, frame_s *frame) { // cppcheck-suppress unusedFunction
|
||||
assert(!rawsink->server); // Client only
|
||||
|
||||
if (_sem_timedwait_monotonic(rawsink->sig_sem, rawsink->timeout) < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
return -2;
|
||||
}
|
||||
LOG_PERROR("RAWSRC: Can't wait signal semaphore");
|
||||
return -1;
|
||||
}
|
||||
if (_flock_timedwait_monotonic(rawsink->fd, rawsink->timeout) < 0) {
|
||||
if (errno == EWOULDBLOCK) {
|
||||
return -2;
|
||||
}
|
||||
LOG_PERROR("RAWSRC: Can't lock memory");
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define COPY(_field) frame->_field = rawsink->mem->_field
|
||||
COPY(width);
|
||||
COPY(height);
|
||||
COPY(format);
|
||||
COPY(online);
|
||||
COPY(grab_ts);
|
||||
frame_set_data(frame, rawsink->mem->data, rawsink->mem->used);
|
||||
# undef COPY
|
||||
|
||||
if (flock(rawsink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("RAWSRC: Can't unlock memory");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _sem_timedwait_monotonic(sem_t *sem, long double timeout) {
|
||||
long double deadline_ts = get_now_monotonic() + timeout;
|
||||
int retval = -1;
|
||||
|
||||
while (true) {
|
||||
retval = sem_trywait(sem);
|
||||
if (retval == 0 || errno != EAGAIN || get_now_monotonic() > deadline_ts) {
|
||||
break;
|
||||
}
|
||||
usleep(1000);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int _flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
long double deadline_ts = get_now_monotonic() + timeout;
|
||||
int retval = -1;
|
||||
|
||||
while (true) {
|
||||
retval = flock(fd, LOCK_EX | LOCK_NB);
|
||||
if (retval == 0 || errno != EWOULDBLOCK || get_now_monotonic() > deadline_ts) {
|
||||
break;
|
||||
}
|
||||
usleep(1000);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
77
src/libs/rawsink/rawsink.h
Normal file
77
src/libs/rawsink/rawsink.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018 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 <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <semaphore.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "../common/tools.h"
|
||||
#include "../common/logging.h"
|
||||
#include "../common/frame.h"
|
||||
|
||||
|
||||
#ifndef CFG_RAWSINK_MAX_DATA
|
||||
# define CFG_RAWSINK_MAX_DATA 33554432
|
||||
#endif
|
||||
#define RAWSINK_MAX_DATA ((size_t)(CFG_RAWSINK_MAX_DATA))
|
||||
|
||||
|
||||
typedef struct {
|
||||
size_t used;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
bool online;
|
||||
long double grab_ts;
|
||||
uint8_t data[RAWSINK_MAX_DATA];
|
||||
} rawsink_shared_s;
|
||||
|
||||
typedef struct {
|
||||
bool server;
|
||||
bool rm;
|
||||
unsigned timeout;
|
||||
|
||||
char *mem_name;
|
||||
char *sig_name;
|
||||
|
||||
int fd;
|
||||
rawsink_shared_s *mem;
|
||||
sem_t *sig_sem;
|
||||
} rawsink_s;
|
||||
|
||||
|
||||
rawsink_s *rawsink_open(const char *name, bool server, mode_t mode, bool rm, unsigned timeout);
|
||||
void rawsink_close(rawsink_s *rawsink);
|
||||
|
||||
int rawsink_server_put(rawsink_s *rawsink, frame_s *frame);
|
||||
int rawsink_client_get(rawsink_s *rawsink, frame_s *frame);
|
||||
Reference in New Issue
Block a user