mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-18 02:55:46 +00:00
using us_ prefixes
This commit is contained in:
parent
e3293d6887
commit
cbee3adb2e
@ -23,29 +23,29 @@
|
||||
#include "audio.h"
|
||||
|
||||
|
||||
#define JLOG_PERROR_ALSA(_err, _prefix, _msg, ...) JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, snd_strerror(_err))
|
||||
#define JLOG_PERROR_RES(_err, _prefix, _msg, ...) JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(_err))
|
||||
#define JLOG_PERROR_OPUS(_err, _prefix, _msg, ...) JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, opus_strerror(_err))
|
||||
#define _JLOG_PERROR_ALSA(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, snd_strerror(_err))
|
||||
#define _JLOG_PERROR_RES(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(_err))
|
||||
#define _JLOG_PERROR_OPUS(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, opus_strerror(_err))
|
||||
|
||||
// A number of frames per 1 channel:
|
||||
// - https://github.com/xiph/opus/blob/7b05f44/src/opus_demo.c#L368
|
||||
#define HZ_TO_FRAMES(_hz) (6 * (_hz) / 50) // 120ms
|
||||
#define HZ_TO_BUF16(_hz) (HZ_TO_FRAMES(_hz) * 2) // One stereo frame = (16bit L) + (16bit R)
|
||||
#define HZ_TO_BUF8(_hz) (HZ_TO_BUF16(_hz) * sizeof(int16_t))
|
||||
#define _HZ_TO_FRAMES(_hz) (6 * (_hz) / 50) // 120ms
|
||||
#define _HZ_TO_BUF16(_hz) (_HZ_TO_FRAMES(_hz) * 2) // One stereo frame = (16bit L) + (16bit R)
|
||||
#define _HZ_TO_BUF8(_hz) (_HZ_TO_BUF16(_hz) * sizeof(int16_t))
|
||||
|
||||
#define MIN_PCM_HZ 8000
|
||||
#define MAX_PCM_HZ 192000
|
||||
#define MAX_BUF16 HZ_TO_BUF16(MAX_PCM_HZ)
|
||||
#define MAX_BUF8 HZ_TO_BUF8(MAX_PCM_HZ)
|
||||
#define ENCODER_INPUT_HZ 48000
|
||||
#define _MIN_PCM_HZ 8000
|
||||
#define _MAX_PCM_HZ 192000
|
||||
#define _MAX_BUF16 _HZ_TO_BUF16(_MAX_PCM_HZ)
|
||||
#define _MAX_BUF8 _HZ_TO_BUF8(_MAX_PCM_HZ)
|
||||
#define _ENCODER_INPUT_HZ 48000
|
||||
|
||||
|
||||
typedef struct {
|
||||
int16_t data[MAX_BUF16];
|
||||
int16_t data[_MAX_BUF16];
|
||||
} _pcm_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MAX_BUF8]; // Worst case
|
||||
uint8_t data[_MAX_BUF8]; // Worst case
|
||||
size_t used;
|
||||
uint64_t pts;
|
||||
} _enc_buffer_s;
|
||||
@ -55,12 +55,12 @@ static void *_pcm_thread(void *v_audio);
|
||||
static void *_encoder_thread(void *v_audio);
|
||||
|
||||
|
||||
audio_s *audio_init(const char *name, unsigned pcm_hz) {
|
||||
audio_s *audio;
|
||||
A_CALLOC(audio, 1);
|
||||
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz) {
|
||||
us_audio_s *audio;
|
||||
US_CALLOC(audio, 1);
|
||||
audio->pcm_hz = pcm_hz;
|
||||
audio->pcm_queue = queue_init(8);
|
||||
audio->enc_queue = queue_init(8);
|
||||
audio->pcm_queue = us_queue_init(8);
|
||||
audio->enc_queue = us_queue_init(8);
|
||||
atomic_init(&audio->stop, false);
|
||||
|
||||
int err;
|
||||
@ -68,14 +68,14 @@ audio_s *audio_init(const char *name, unsigned pcm_hz) {
|
||||
{
|
||||
if ((err = snd_pcm_open(&audio->pcm, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
|
||||
audio->pcm = NULL;
|
||||
JLOG_PERROR_ALSA(err, "audio", "Can't open PCM capture");
|
||||
_JLOG_PERROR_ALSA(err, "audio", "Can't open PCM capture");
|
||||
goto error;
|
||||
}
|
||||
assert(!snd_pcm_hw_params_malloc(&audio->pcm_params));
|
||||
|
||||
# define SET_PARAM(_msg, _func, ...) { \
|
||||
if ((err = _func(audio->pcm, audio->pcm_params, ##__VA_ARGS__)) < 0) { \
|
||||
JLOG_PERROR_ALSA(err, "audio", _msg); \
|
||||
_JLOG_PERROR_ALSA(err, "audio", _msg); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
@ -85,30 +85,30 @@ audio_s *audio_init(const char *name, unsigned pcm_hz) {
|
||||
SET_PARAM("Can't set PCM channels numbre", snd_pcm_hw_params_set_channels, 2);
|
||||
SET_PARAM("Can't set PCM sampling format", snd_pcm_hw_params_set_format, SND_PCM_FORMAT_S16_LE);
|
||||
SET_PARAM("Can't set PCM sampling rate", snd_pcm_hw_params_set_rate_near, &audio->pcm_hz, 0);
|
||||
if (audio->pcm_hz < MIN_PCM_HZ || audio->pcm_hz > MAX_PCM_HZ) {
|
||||
JLOG_ERROR("audio", "Unsupported PCM freq: %u; should be: %u <= F <= %u",
|
||||
audio->pcm_hz, MIN_PCM_HZ, MAX_PCM_HZ);
|
||||
if (audio->pcm_hz < _MIN_PCM_HZ || audio->pcm_hz > _MAX_PCM_HZ) {
|
||||
US_JLOG_ERROR("audio", "Unsupported PCM freq: %u; should be: %u <= F <= %u",
|
||||
audio->pcm_hz, _MIN_PCM_HZ, _MAX_PCM_HZ);
|
||||
goto error;
|
||||
}
|
||||
audio->pcm_frames = HZ_TO_FRAMES(audio->pcm_hz);
|
||||
audio->pcm_size = HZ_TO_BUF8(audio->pcm_hz);
|
||||
audio->pcm_frames = _HZ_TO_FRAMES(audio->pcm_hz);
|
||||
audio->pcm_size = _HZ_TO_BUF8(audio->pcm_hz);
|
||||
SET_PARAM("Can't apply PCM params", snd_pcm_hw_params);
|
||||
|
||||
# undef SET_PARAM
|
||||
}
|
||||
|
||||
if (audio->pcm_hz != ENCODER_INPUT_HZ) {
|
||||
audio->res = speex_resampler_init(2, audio->pcm_hz, ENCODER_INPUT_HZ, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
|
||||
if (audio->pcm_hz != _ENCODER_INPUT_HZ) {
|
||||
audio->res = speex_resampler_init(2, audio->pcm_hz, _ENCODER_INPUT_HZ, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
|
||||
if (err < 0) {
|
||||
audio->res = NULL;
|
||||
JLOG_PERROR_RES(err, "audio", "Can't create resampler");
|
||||
_JLOG_PERROR_RES(err, "audio", "Can't create resampler");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY
|
||||
audio->enc = opus_encoder_create(ENCODER_INPUT_HZ, 2, OPUS_APPLICATION_AUDIO, &err);
|
||||
audio->enc = opus_encoder_create(_ENCODER_INPUT_HZ, 2, OPUS_APPLICATION_AUDIO, &err);
|
||||
assert(err == 0);
|
||||
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_BITRATE(48000)));
|
||||
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)));
|
||||
@ -116,23 +116,23 @@ audio_s *audio_init(const char *name, unsigned pcm_hz) {
|
||||
// OPUS_SET_INBAND_FEC(1), OPUS_SET_PACKET_LOSS_PERC(10): see rtpa.c
|
||||
}
|
||||
|
||||
JLOG_INFO("audio", "Pipeline configured on %uHz; capturing ...", audio->pcm_hz);
|
||||
US_JLOG_INFO("audio", "Pipeline configured on %uHz; capturing ...", audio->pcm_hz);
|
||||
audio->tids_created = true;
|
||||
A_THREAD_CREATE(&audio->enc_tid, _encoder_thread, audio);
|
||||
A_THREAD_CREATE(&audio->pcm_tid, _pcm_thread, audio);
|
||||
US_THREAD_CREATE(&audio->enc_tid, _encoder_thread, audio);
|
||||
US_THREAD_CREATE(&audio->pcm_tid, _pcm_thread, audio);
|
||||
|
||||
return audio;
|
||||
|
||||
error:
|
||||
audio_destroy(audio);
|
||||
us_audio_destroy(audio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void audio_destroy(audio_s *audio) {
|
||||
void us_audio_destroy(us_audio_s *audio) {
|
||||
if (audio->tids_created) {
|
||||
atomic_store(&audio->stop, true);
|
||||
A_THREAD_JOIN(audio->pcm_tid);
|
||||
A_THREAD_JOIN(audio->enc_tid);
|
||||
US_THREAD_JOIN(audio->pcm_tid);
|
||||
US_THREAD_JOIN(audio->enc_tid);
|
||||
}
|
||||
if (audio->enc) {
|
||||
opus_encoder_destroy(audio->enc);
|
||||
@ -146,20 +146,20 @@ void audio_destroy(audio_s *audio) {
|
||||
if (audio->pcm_params) {
|
||||
snd_pcm_hw_params_free(audio->pcm_params);
|
||||
}
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(audio->enc_queue, free);
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(audio->pcm_queue, free);
|
||||
US_QUEUE_FREE_ITEMS_AND_DESTROY(audio->enc_queue, free);
|
||||
US_QUEUE_FREE_ITEMS_AND_DESTROY(audio->pcm_queue, free);
|
||||
if (audio->tids_created) {
|
||||
JLOG_INFO("audio", "Pipeline closed");
|
||||
US_JLOG_INFO("audio", "Pipeline closed");
|
||||
}
|
||||
free(audio);
|
||||
}
|
||||
|
||||
int audio_get_encoded(audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts) {
|
||||
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts) {
|
||||
if (atomic_load(&audio->stop)) {
|
||||
return -1;
|
||||
}
|
||||
_enc_buffer_s *buf;
|
||||
if (!queue_get(audio->enc_queue, (void **)&buf, 0.1)) {
|
||||
if (!us_queue_get(audio->enc_queue, (void **)&buf, 0.1)) {
|
||||
if (*size < buf->used) {
|
||||
free(buf);
|
||||
return -3;
|
||||
@ -174,28 +174,28 @@ int audio_get_encoded(audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts
|
||||
}
|
||||
|
||||
static void *_pcm_thread(void *v_audio) {
|
||||
A_THREAD_RENAME("us_a_pcm");
|
||||
US_THREAD_RENAME("us_a_pcm");
|
||||
|
||||
audio_s *audio = (audio_s *)v_audio;
|
||||
uint8_t in[MAX_BUF8];
|
||||
us_audio_s *audio = (us_audio_s *)v_audio;
|
||||
uint8_t in[_MAX_BUF8];
|
||||
|
||||
while (!atomic_load(&audio->stop)) {
|
||||
int frames = snd_pcm_readi(audio->pcm, in, audio->pcm_frames);
|
||||
if (frames < 0) {
|
||||
JLOG_PERROR_ALSA(frames, "audio", "Fatal: Can't capture PCM frames");
|
||||
_JLOG_PERROR_ALSA(frames, "audio", "Fatal: Can't capture PCM frames");
|
||||
break;
|
||||
} else if (frames < (int)audio->pcm_frames) {
|
||||
JLOG_ERROR("audio", "Fatal: Too few PCM frames captured");
|
||||
US_JLOG_ERROR("audio", "Fatal: Too few PCM frames captured");
|
||||
break;
|
||||
}
|
||||
|
||||
if (queue_get_free(audio->pcm_queue)) {
|
||||
if (us_queue_get_free(audio->pcm_queue)) {
|
||||
_pcm_buffer_s *out;
|
||||
A_CALLOC(out, 1);
|
||||
US_CALLOC(out, 1);
|
||||
memcpy(out->data, in, audio->pcm_size);
|
||||
assert(!queue_put(audio->pcm_queue, out, 0));
|
||||
assert(!us_queue_put(audio->pcm_queue, out, 0));
|
||||
} else {
|
||||
JLOG_ERROR("audio", "PCM queue is full");
|
||||
US_JLOG_ERROR("audio", "PCM queue is full");
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,42 +204,42 @@ static void *_pcm_thread(void *v_audio) {
|
||||
}
|
||||
|
||||
static void *_encoder_thread(void *v_audio) {
|
||||
A_THREAD_RENAME("us_a_enc");
|
||||
US_THREAD_RENAME("us_a_enc");
|
||||
|
||||
audio_s *audio = (audio_s *)v_audio;
|
||||
int16_t in_res[MAX_BUF16];
|
||||
us_audio_s *audio = (us_audio_s *)v_audio;
|
||||
int16_t in_res[_MAX_BUF16];
|
||||
|
||||
while (!atomic_load(&audio->stop)) {
|
||||
_pcm_buffer_s *in;
|
||||
if (!queue_get(audio->pcm_queue, (void **)&in, 0.1)) {
|
||||
if (!us_queue_get(audio->pcm_queue, (void **)&in, 0.1)) {
|
||||
int16_t *in_ptr;
|
||||
if (audio->res) {
|
||||
assert(audio->pcm_hz != ENCODER_INPUT_HZ);
|
||||
assert(audio->pcm_hz != _ENCODER_INPUT_HZ);
|
||||
uint32_t in_count = audio->pcm_frames;
|
||||
uint32_t out_count = HZ_TO_FRAMES(ENCODER_INPUT_HZ);
|
||||
uint32_t out_count = _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
|
||||
speex_resampler_process_interleaved_int(audio->res, in->data, &in_count, in_res, &out_count);
|
||||
in_ptr = in_res;
|
||||
} else {
|
||||
assert(audio->pcm_hz == ENCODER_INPUT_HZ);
|
||||
assert(audio->pcm_hz == _ENCODER_INPUT_HZ);
|
||||
in_ptr = in->data;
|
||||
}
|
||||
|
||||
_enc_buffer_s *out;
|
||||
A_CALLOC(out, 1);
|
||||
int size = opus_encode(audio->enc, in_ptr, HZ_TO_FRAMES(ENCODER_INPUT_HZ), out->data, ARRAY_LEN(out->data));
|
||||
US_CALLOC(out, 1);
|
||||
int size = opus_encode(audio->enc, in_ptr, _HZ_TO_FRAMES(_ENCODER_INPUT_HZ), out->data, US_ARRAY_LEN(out->data));
|
||||
free(in);
|
||||
if (size < 0) {
|
||||
JLOG_PERROR_OPUS(size, "audio", "Fatal: Can't encode PCM frame to OPUS");
|
||||
_JLOG_PERROR_OPUS(size, "audio", "Fatal: Can't encode PCM frame to OPUS");
|
||||
free(out);
|
||||
break;
|
||||
}
|
||||
out->used = size;
|
||||
out->pts = audio->pts;
|
||||
// https://datatracker.ietf.org/doc/html/rfc7587#section-4.2
|
||||
audio->pts += HZ_TO_FRAMES(ENCODER_INPUT_HZ);
|
||||
audio->pts += _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
|
||||
|
||||
if (queue_put(audio->enc_queue, out, 0) != 0) {
|
||||
JLOG_ERROR("audio", "OPUS encoder queue is full");
|
||||
if (us_queue_put(audio->enc_queue, out, 0) != 0) {
|
||||
US_JLOG_ERROR("audio", "OPUS encoder queue is full");
|
||||
free(out);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,18 +51,18 @@ typedef struct {
|
||||
SpeexResamplerState *res;
|
||||
OpusEncoder *enc;
|
||||
|
||||
queue_s *pcm_queue;
|
||||
queue_s *enc_queue;
|
||||
us_queue_s *pcm_queue;
|
||||
us_queue_s *enc_queue;
|
||||
uint32_t pts;
|
||||
|
||||
pthread_t pcm_tid;
|
||||
pthread_t enc_tid;
|
||||
bool tids_created;
|
||||
atomic_bool stop;
|
||||
} audio_s;
|
||||
} us_audio_s;
|
||||
|
||||
|
||||
audio_s *audio_init(const char *name, unsigned pcm_hz);
|
||||
void audio_destroy(audio_s *audio);
|
||||
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz);
|
||||
void us_audio_destroy(us_audio_s *audio);
|
||||
|
||||
int audio_get_encoded(audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts);
|
||||
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts);
|
||||
|
||||
@ -28,53 +28,53 @@ static void *_audio_thread(void *v_client);
|
||||
static void *_common_thread(void *v_client, bool video);
|
||||
|
||||
|
||||
client_s *client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio) {
|
||||
client_s *client;
|
||||
A_CALLOC(client, 1);
|
||||
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio) {
|
||||
us_janus_client_s *client;
|
||||
US_CALLOC(client, 1);
|
||||
client->gw = gw;
|
||||
client->session = session;
|
||||
atomic_init(&client->transmit, true);
|
||||
|
||||
atomic_init(&client->stop, false);
|
||||
|
||||
client->video_queue = queue_init(1024);
|
||||
A_THREAD_CREATE(&client->video_tid, _video_thread, client);
|
||||
client->video_queue = us_queue_init(1024);
|
||||
US_THREAD_CREATE(&client->video_tid, _video_thread, client);
|
||||
|
||||
if (has_audio) {
|
||||
client->audio_queue = queue_init(64);
|
||||
A_THREAD_CREATE(&client->audio_tid, _audio_thread, client);
|
||||
client->audio_queue = us_queue_init(64);
|
||||
US_THREAD_CREATE(&client->audio_tid, _audio_thread, client);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
void client_destroy(client_s *client) {
|
||||
void us_janus_client_destroy(us_janus_client_s *client) {
|
||||
atomic_store(&client->stop, true);
|
||||
queue_put(client->video_queue, NULL, 0);
|
||||
us_queue_put(client->video_queue, NULL, 0);
|
||||
if (client->audio_queue != NULL) {
|
||||
queue_put(client->audio_queue, NULL, 0);
|
||||
us_queue_put(client->audio_queue, NULL, 0);
|
||||
}
|
||||
|
||||
A_THREAD_JOIN(client->video_tid);
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(client->video_queue, rtp_destroy);
|
||||
US_THREAD_JOIN(client->video_tid);
|
||||
US_QUEUE_FREE_ITEMS_AND_DESTROY(client->video_queue, us_rtp_destroy);
|
||||
if (client->audio_queue != NULL) {
|
||||
A_THREAD_JOIN(client->audio_tid);
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(client->audio_queue, rtp_destroy);
|
||||
US_THREAD_JOIN(client->audio_tid);
|
||||
US_QUEUE_FREE_ITEMS_AND_DESTROY(client->audio_queue, us_rtp_destroy);
|
||||
}
|
||||
free(client);
|
||||
}
|
||||
|
||||
void client_send(client_s *client, const rtp_s *rtp) {
|
||||
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp) {
|
||||
if (
|
||||
!atomic_load(&client->transmit)
|
||||
|| (!rtp->video && client->audio_queue == NULL)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
rtp_s *new = rtp_dup(rtp);
|
||||
if (queue_put((new->video ? client->video_queue : client->audio_queue), new, 0) != 0) {
|
||||
JLOG_ERROR("client", "Session %p %s queue is full",
|
||||
us_rtp_s *new = us_rtp_dup(rtp);
|
||||
if (us_queue_put((new->video ? client->video_queue : client->audio_queue), new, 0) != 0) {
|
||||
US_JLOG_ERROR("client", "Session %p %s queue is full",
|
||||
client->session, (new->video ? "video" : "audio"));
|
||||
rtp_destroy(new);
|
||||
us_rtp_destroy(new);
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,13 +87,13 @@ static void *_audio_thread(void *v_client) {
|
||||
}
|
||||
|
||||
static void *_common_thread(void *v_client, bool video) {
|
||||
client_s *client = (client_s *)v_client;
|
||||
queue_s *queue = (video ? client->video_queue : client->audio_queue);
|
||||
us_janus_client_s *client = (us_janus_client_s *)v_client;
|
||||
us_queue_s *queue = (video ? client->video_queue : client->audio_queue);
|
||||
assert(queue != NULL); // Audio may be NULL
|
||||
|
||||
while (!atomic_load(&client->stop)) {
|
||||
rtp_s *rtp;
|
||||
if (!queue_get(queue, (void **)&rtp, 0.1)) {
|
||||
us_rtp_s *rtp;
|
||||
if (!us_queue_get(queue, (void **)&rtp, 0.1)) {
|
||||
if (rtp == NULL) {
|
||||
break;
|
||||
}
|
||||
@ -112,7 +112,7 @@ static void *_common_thread(void *v_client, bool video) {
|
||||
}
|
||||
client->gw->relay_rtp(client->session, &packet);
|
||||
}
|
||||
rtp_destroy(rtp);
|
||||
us_rtp_destroy(rtp);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
typedef struct client_sx {
|
||||
typedef struct us_janus_client_sx {
|
||||
janus_callbacks *gw;
|
||||
janus_plugin_session *session;
|
||||
atomic_bool transmit;
|
||||
@ -48,14 +48,14 @@ typedef struct client_sx {
|
||||
pthread_t audio_tid;
|
||||
atomic_bool stop;
|
||||
|
||||
queue_s *video_queue;
|
||||
queue_s *audio_queue;
|
||||
us_queue_s *video_queue;
|
||||
us_queue_s *audio_queue;
|
||||
|
||||
LIST_STRUCT(struct client_sx);
|
||||
} client_s;
|
||||
US_LIST_STRUCT(struct us_janus_client_sx);
|
||||
} us_janus_client_s;
|
||||
|
||||
|
||||
client_s *client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio);
|
||||
void client_destroy(client_s *client);
|
||||
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio);
|
||||
void us_janus_client_destroy(us_janus_client_s *client);
|
||||
|
||||
void client_send(client_s *client, const rtp_s *rtp);
|
||||
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp);
|
||||
|
||||
@ -27,19 +27,19 @@ static char *_get_value(janus_config *jcfg, const char *section, const char *opt
|
||||
static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
|
||||
|
||||
|
||||
plugin_config_s *plugin_config_init(const char *config_dir_path) {
|
||||
plugin_config_s *config;
|
||||
A_CALLOC(config, 1);
|
||||
us_config_s *us_config_init(const char *config_dir_path) {
|
||||
us_config_s *config;
|
||||
US_CALLOC(config, 1);
|
||||
|
||||
char *config_file_path;
|
||||
janus_config *jcfg = NULL;
|
||||
|
||||
A_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, PLUGIN_PACKAGE);
|
||||
JLOG_INFO("config", "Reading config file '%s' ...", config_file_path);
|
||||
US_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, US_PLUGIN_PACKAGE);
|
||||
US_JLOG_INFO("config", "Reading config file '%s' ...", config_file_path);
|
||||
|
||||
jcfg = janus_config_parse(config_file_path);
|
||||
if (jcfg == NULL) {
|
||||
JLOG_ERROR("config", "Can't read config");
|
||||
US_JLOG_ERROR("config", "Can't read config");
|
||||
goto error;
|
||||
}
|
||||
janus_config_print(jcfg);
|
||||
@ -48,23 +48,23 @@ plugin_config_s *plugin_config_init(const char *config_dir_path) {
|
||||
(config->video_sink_name = _get_value(jcfg, "memsink", "object")) == NULL
|
||||
&& (config->video_sink_name = _get_value(jcfg, "video", "sink")) == NULL
|
||||
) {
|
||||
JLOG_ERROR("config", "Missing config value: video.sink (ex. memsink.object)");
|
||||
US_JLOG_ERROR("config", "Missing config value: video.sink (ex. memsink.object)");
|
||||
goto error;
|
||||
}
|
||||
if ((config->video_zero_playout_delay = _get_bool(jcfg, "video", "zero_playout_delay", false)) == true) {
|
||||
JLOG_INFO("config", "Enabled the experimental Playout-Delay=0 RTP extension for VIDEO");
|
||||
US_JLOG_INFO("config", "Enabled the experimental Playout-Delay=0 RTP extension for VIDEO");
|
||||
}
|
||||
if ((config->audio_dev_name = _get_value(jcfg, "audio", "device")) != NULL) {
|
||||
JLOG_INFO("config", "Enabled the experimental AUDIO feature");
|
||||
US_JLOG_INFO("config", "Enabled the experimental AUDIO feature");
|
||||
if ((config->tc358743_dev_path = _get_value(jcfg, "audio", "tc358743")) == NULL) {
|
||||
JLOG_INFO("config", "Missing config value: audio.tc358743");
|
||||
US_JLOG_INFO("config", "Missing config value: audio.tc358743");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
goto ok;
|
||||
error:
|
||||
plugin_config_destroy(config);
|
||||
us_config_destroy(config);
|
||||
config = NULL;
|
||||
ok:
|
||||
if (jcfg) {
|
||||
@ -74,10 +74,10 @@ plugin_config_s *plugin_config_init(const char *config_dir_path) {
|
||||
return config;
|
||||
}
|
||||
|
||||
void plugin_config_destroy(plugin_config_s *config) {
|
||||
DELETE(config->video_sink_name, free);
|
||||
DELETE(config->audio_dev_name, free);
|
||||
DELETE(config->tc358743_dev_path, free);
|
||||
void us_config_destroy(us_config_s *config) {
|
||||
US_DELETE(config->video_sink_name, free);
|
||||
US_DELETE(config->audio_dev_name, free);
|
||||
US_DELETE(config->tc358743_dev_path, free);
|
||||
free(config);
|
||||
}
|
||||
|
||||
|
||||
@ -40,9 +40,8 @@ typedef struct {
|
||||
|
||||
char *audio_dev_name;
|
||||
char *tc358743_dev_path;
|
||||
} plugin_config_s;
|
||||
} us_config_s;
|
||||
|
||||
|
||||
// config_init() conflicts with something
|
||||
plugin_config_s *plugin_config_init(const char *config_dir_path);
|
||||
void plugin_config_destroy(plugin_config_s *config);
|
||||
us_config_s *us_config_init(const char *config_dir_path);
|
||||
void us_config_destroy(us_config_s *config);
|
||||
|
||||
@ -22,5 +22,5 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define PLUGIN_NAME "ustreamer"
|
||||
#define PLUGIN_PACKAGE "janus.plugin.ustreamer"
|
||||
#define US_PLUGIN_NAME "ustreamer"
|
||||
#define US_PLUGIN_PACKAGE "janus.plugin.ustreamer"
|
||||
|
||||
@ -27,12 +27,12 @@
|
||||
#include "const.h"
|
||||
|
||||
|
||||
#define JLOG_INFO(_prefix, _msg, ...) JANUS_LOG(LOG_INFO, "== %s/%-9s -- " _msg "\n", PLUGIN_NAME, _prefix, ##__VA_ARGS__)
|
||||
#define JLOG_WARN(_prefix, _msg, ...) JANUS_LOG(LOG_WARN, "== %s/%-9s -- " _msg "\n", PLUGIN_NAME, _prefix, ##__VA_ARGS__)
|
||||
#define JLOG_ERROR(_prefix, _msg, ...) JANUS_LOG(LOG_ERR, "== %s/%-9s -- " _msg "\n", PLUGIN_NAME, _prefix, ##__VA_ARGS__)
|
||||
#define US_JLOG_INFO(x_prefix, x_msg, ...) JANUS_LOG(LOG_INFO, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
|
||||
#define US_JLOG_WARN(x_prefix, x_msg, ...) JANUS_LOG(LOG_WARN, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
|
||||
#define US_JLOG_ERROR(x_prefix, x_msg, ...) JANUS_LOG(LOG_ERR, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
|
||||
|
||||
#define JLOG_PERROR(_prefix, _msg, ...) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
|
||||
JANUS_LOG(LOG_ERR, "[%s/%-9s] " _msg ": %s\n", PLUGIN_NAME, _prefix, ##__VA_ARGS__, _perror_ptr); \
|
||||
#define US_JLOG_PERROR(x_prefix, x_msg, ...) { \
|
||||
char m_perror_buf[1024] = {0}; \
|
||||
char *m_perror_ptr = us_errno_to_string(errno, m_perror_buf, 1023); \
|
||||
JANUS_LOG(LOG_ERR, "[%s/%-9s] " x_msg ": %s\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__, m_perror_ptr); \
|
||||
}
|
||||
|
||||
@ -23,21 +23,21 @@
|
||||
#include "memsinkfd.h"
|
||||
|
||||
|
||||
int memsink_fd_wait_frame(int fd, memsink_shared_s* mem, uint64_t last_id) {
|
||||
long double deadline_ts = get_now_monotonic() + 1; // wait_timeout
|
||||
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id) {
|
||||
long double deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
|
||||
long double now;
|
||||
do {
|
||||
int result = flock_timedwait_monotonic(fd, 1); // lock_timeout
|
||||
now = get_now_monotonic();
|
||||
int result = us_flock_timedwait_monotonic(fd, 1); // lock_timeout
|
||||
now = us_get_now_monotonic();
|
||||
if (result < 0 && errno != EWOULDBLOCK) {
|
||||
JLOG_PERROR("video", "Can't lock memsink");
|
||||
US_JLOG_PERROR("video", "Can't lock memsink");
|
||||
return -1;
|
||||
} else if (result == 0) {
|
||||
if (mem->magic == MEMSINK_MAGIC && mem->version == MEMSINK_VERSION && mem->id != last_id) {
|
||||
if (mem->magic == US_MEMSINK_MAGIC && mem->version == US_MEMSINK_VERSION && mem->id != last_id) {
|
||||
return 0;
|
||||
}
|
||||
if (flock(fd, LOCK_UN) < 0) {
|
||||
JLOG_PERROR("video", "Can't unlock memsink");
|
||||
US_JLOG_PERROR("video", "Can't unlock memsink");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@ -46,24 +46,24 @@ int memsink_fd_wait_frame(int fd, memsink_shared_s* mem, uint64_t last_id) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
frame_s *memsink_fd_get_frame(int fd, memsink_shared_s *mem, uint64_t *frame_id) {
|
||||
frame_s *frame = frame_init();
|
||||
frame_set_data(frame, mem->data, mem->used);
|
||||
FRAME_COPY_META(mem, frame);
|
||||
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id) {
|
||||
us_frame_s *frame = us_frame_init();
|
||||
us_frame_set_data(frame, mem->data, mem->used);
|
||||
US_FRAME_COPY_META(mem, frame);
|
||||
*frame_id = mem->id;
|
||||
mem->last_client_ts = get_now_monotonic();
|
||||
mem->last_client_ts = us_get_now_monotonic();
|
||||
|
||||
bool ok = true;
|
||||
if (frame->format != V4L2_PIX_FMT_H264) {
|
||||
JLOG_ERROR("video", "Got non-H264 frame from memsink");
|
||||
US_JLOG_ERROR("video", "Got non-H264 frame from memsink");
|
||||
ok = false;
|
||||
}
|
||||
if (flock(fd, LOCK_UN) < 0) {
|
||||
JLOG_PERROR("video", "Can't unlock memsink");
|
||||
US_JLOG_PERROR("video", "Can't unlock memsink");
|
||||
ok = false;
|
||||
}
|
||||
if (!ok) {
|
||||
frame_destroy(frame);
|
||||
us_frame_destroy(frame);
|
||||
frame = NULL;
|
||||
}
|
||||
return frame;
|
||||
|
||||
@ -34,5 +34,5 @@
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
int memsink_fd_wait_frame(int fd, memsink_shared_s* mem, uint64_t last_id);
|
||||
frame_s *memsink_fd_get_frame(int fd, memsink_shared_s *mem, uint64_t *frame_id);
|
||||
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id);
|
||||
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id);
|
||||
|
||||
@ -55,14 +55,14 @@
|
||||
#include "config.h"
|
||||
|
||||
|
||||
static plugin_config_s *_g_config = NULL;
|
||||
const useconds_t _g_watchers_polling = 100000;
|
||||
static us_config_s *_g_config = NULL;
|
||||
const useconds_t _g_watchers_polling = 100000;
|
||||
|
||||
static client_s *_g_clients = NULL;
|
||||
static janus_callbacks *_g_gw = NULL;
|
||||
static queue_s *_g_video_queue = NULL;
|
||||
static rtpv_s *_g_rtpv = NULL;
|
||||
static rtpa_s *_g_rtpa = NULL;
|
||||
static us_janus_client_s *_g_clients = NULL;
|
||||
static janus_callbacks *_g_gw = NULL;
|
||||
static us_queue_s *_g_video_queue = NULL;
|
||||
static us_rtpv_s *_g_rtpv = NULL;
|
||||
static us_rtpa_s *_g_rtpa = NULL;
|
||||
|
||||
static pthread_t _g_video_rtp_tid;
|
||||
static atomic_bool _g_video_rtp_tid_created = false;
|
||||
@ -78,84 +78,84 @@ static atomic_bool _g_stop = false;
|
||||
static atomic_bool _g_has_watchers = false;
|
||||
|
||||
|
||||
#define LOCK_VIDEO A_MUTEX_LOCK(&_g_video_lock)
|
||||
#define UNLOCK_VIDEO A_MUTEX_UNLOCK(&_g_video_lock)
|
||||
#define _LOCK_VIDEO US_MUTEX_LOCK(&_g_video_lock)
|
||||
#define _UNLOCK_VIDEO US_MUTEX_UNLOCK(&_g_video_lock)
|
||||
|
||||
#define LOCK_AUDIO A_MUTEX_LOCK(&_g_audio_lock)
|
||||
#define UNLOCK_AUDIO A_MUTEX_UNLOCK(&_g_audio_lock)
|
||||
#define _LOCK_AUDIO US_MUTEX_LOCK(&_g_audio_lock)
|
||||
#define _UNLOCK_AUDIO US_MUTEX_UNLOCK(&_g_audio_lock)
|
||||
|
||||
#define LOCK_ALL { LOCK_VIDEO; LOCK_AUDIO; }
|
||||
#define UNLOCK_ALL { UNLOCK_AUDIO; UNLOCK_VIDEO; }
|
||||
#define _LOCK_ALL { _LOCK_VIDEO; _LOCK_AUDIO; }
|
||||
#define _UNLOCK_ALL { _UNLOCK_AUDIO; _UNLOCK_VIDEO; }
|
||||
|
||||
#define READY atomic_load(&_g_ready)
|
||||
#define STOP atomic_load(&_g_stop)
|
||||
#define HAS_WATCHERS atomic_load(&_g_has_watchers)
|
||||
#define _READY atomic_load(&_g_ready)
|
||||
#define _STOP atomic_load(&_g_stop)
|
||||
#define _HAS_WATCHERS atomic_load(&_g_has_watchers)
|
||||
|
||||
|
||||
janus_plugin *create(void);
|
||||
|
||||
|
||||
#define IF_NOT_REPORTED(...) { \
|
||||
#define _IF_NOT_REPORTED(...) { \
|
||||
unsigned _error_code = __LINE__; \
|
||||
if (error_reported != _error_code) { __VA_ARGS__; error_reported = _error_code; } \
|
||||
}
|
||||
|
||||
static void *_video_rtp_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_video_rtp");
|
||||
US_THREAD_RENAME("us_video_rtp");
|
||||
atomic_store(&_g_video_rtp_tid_created, true);
|
||||
|
||||
while (!STOP) {
|
||||
frame_s *frame;
|
||||
if (queue_get(_g_video_queue, (void **)&frame, 0.1) == 0) {
|
||||
LOCK_VIDEO;
|
||||
rtpv_wrap(_g_rtpv, frame);
|
||||
UNLOCK_VIDEO;
|
||||
frame_destroy(frame);
|
||||
while (!_STOP) {
|
||||
us_frame_s *frame;
|
||||
if (us_queue_get(_g_video_queue, (void **)&frame, 0.1) == 0) {
|
||||
_LOCK_VIDEO;
|
||||
us_rtpv_wrap(_g_rtpv, frame);
|
||||
_UNLOCK_VIDEO;
|
||||
us_frame_destroy(frame);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_video_sink_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_video_sink");
|
||||
US_THREAD_RENAME("us_video_sink");
|
||||
atomic_store(&_g_video_sink_tid_created, true);
|
||||
|
||||
uint64_t frame_id = 0;
|
||||
unsigned error_reported = 0;
|
||||
|
||||
while (!STOP) {
|
||||
if (!HAS_WATCHERS) {
|
||||
IF_NOT_REPORTED({ JLOG_INFO("video", "No active watchers, memsink disconnected"); });
|
||||
while (!_STOP) {
|
||||
if (!_HAS_WATCHERS) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_INFO("video", "No active watchers, memsink disconnected"); });
|
||||
usleep(_g_watchers_polling);
|
||||
continue;
|
||||
}
|
||||
|
||||
int fd = -1;
|
||||
memsink_shared_s *mem = NULL;
|
||||
us_memsink_shared_s *mem = NULL;
|
||||
|
||||
if ((fd = shm_open(_g_config->video_sink_name, O_RDWR, 0)) <= 0) {
|
||||
IF_NOT_REPORTED({ JLOG_PERROR("video", "Can't open memsink"); });
|
||||
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Can't open memsink"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
|
||||
if ((mem = memsink_shared_map(fd)) == NULL) {
|
||||
IF_NOT_REPORTED({ JLOG_PERROR("video", "Can't map memsink"); });
|
||||
if ((mem = us_memsink_shared_map(fd)) == NULL) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Can't map memsink"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
|
||||
error_reported = 0;
|
||||
|
||||
JLOG_INFO("video", "Memsink opened; reading frames ...");
|
||||
while (!STOP && HAS_WATCHERS) {
|
||||
int result = memsink_fd_wait_frame(fd, mem, frame_id);
|
||||
US_JLOG_INFO("video", "Memsink opened; reading frames ...");
|
||||
while (!_STOP && _HAS_WATCHERS) {
|
||||
int result = us_memsink_fd_wait_frame(fd, mem, frame_id);
|
||||
if (result == 0) {
|
||||
frame_s *frame = memsink_fd_get_frame(fd, mem, &frame_id);
|
||||
us_frame_s *frame = us_memsink_fd_get_frame(fd, mem, &frame_id);
|
||||
if (frame == NULL) {
|
||||
goto close_memsink;
|
||||
}
|
||||
if (queue_put(_g_video_queue, frame, 0) != 0) {
|
||||
IF_NOT_REPORTED({ JLOG_PERROR("video", "Video queue is full"); });
|
||||
frame_destroy(frame);
|
||||
if (us_queue_put(_g_video_queue, frame, 0) != 0) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Video queue is full"); });
|
||||
us_frame_destroy(frame);
|
||||
}
|
||||
} else if (result == -1) {
|
||||
goto close_memsink;
|
||||
@ -164,8 +164,8 @@ static void *_video_sink_thread(UNUSED void *arg) {
|
||||
|
||||
close_memsink:
|
||||
if (mem != NULL) {
|
||||
JLOG_INFO("video", "Memsink closed");
|
||||
memsink_shared_unmap(mem);
|
||||
US_JLOG_INFO("video", "Memsink closed");
|
||||
us_memsink_shared_unmap(mem);
|
||||
mem = NULL;
|
||||
}
|
||||
if (fd > 0) {
|
||||
@ -178,53 +178,53 @@ static void *_video_sink_thread(UNUSED void *arg) {
|
||||
}
|
||||
|
||||
static void *_audio_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_audio");
|
||||
US_THREAD_RENAME("us_audio");
|
||||
atomic_store(&_g_audio_tid_created, true);
|
||||
assert(_g_config->audio_dev_name);
|
||||
assert(_g_config->tc358743_dev_path);
|
||||
|
||||
unsigned error_reported = 0;
|
||||
|
||||
while (!STOP) {
|
||||
if (!HAS_WATCHERS) {
|
||||
while (!_STOP) {
|
||||
if (!_HAS_WATCHERS) {
|
||||
usleep(_g_watchers_polling);
|
||||
continue;
|
||||
}
|
||||
|
||||
tc358743_info_s info = {0};
|
||||
audio_s *audio = NULL;
|
||||
us_tc358743_info_s info = {0};
|
||||
us_audio_s *audio = NULL;
|
||||
|
||||
if (tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0) {
|
||||
if (us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0) {
|
||||
goto close_audio;
|
||||
}
|
||||
if (!info.has_audio) {
|
||||
IF_NOT_REPORTED({ JLOG_INFO("audio", "No audio presented from the host"); });
|
||||
_IF_NOT_REPORTED({ US_JLOG_INFO("audio", "No audio presented from the host"); });
|
||||
goto close_audio;
|
||||
}
|
||||
IF_NOT_REPORTED({ JLOG_INFO("audio", "Detected host audio"); });
|
||||
if ((audio = audio_init(_g_config->audio_dev_name, info.audio_hz)) == NULL) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_INFO("audio", "Detected host audio"); });
|
||||
if ((audio = us_audio_init(_g_config->audio_dev_name, info.audio_hz)) == NULL) {
|
||||
goto close_audio;
|
||||
}
|
||||
|
||||
error_reported = 0;
|
||||
|
||||
while (!STOP && HAS_WATCHERS) {
|
||||
while (!_STOP && _HAS_WATCHERS) {
|
||||
if (
|
||||
tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0
|
||||
us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0
|
||||
|| !info.has_audio
|
||||
|| audio->pcm_hz != info.audio_hz
|
||||
) {
|
||||
goto close_audio;
|
||||
}
|
||||
|
||||
size_t size = RTP_DATAGRAM_SIZE - RTP_HEADER_SIZE;
|
||||
size_t size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
|
||||
uint8_t data[size];
|
||||
uint64_t pts;
|
||||
int result = audio_get_encoded(audio, data, &size, &pts);
|
||||
int result = us_audio_get_encoded(audio, data, &size, &pts);
|
||||
if (result == 0) {
|
||||
LOCK_AUDIO;
|
||||
rtpa_wrap(_g_rtpa, data, size, pts);
|
||||
UNLOCK_AUDIO;
|
||||
_LOCK_AUDIO;
|
||||
us_rtpa_wrap(_g_rtpa, data, size, pts);
|
||||
_UNLOCK_AUDIO;
|
||||
} else if (result == -1) {
|
||||
goto close_audio;
|
||||
}
|
||||
@ -232,18 +232,18 @@ static void *_audio_thread(UNUSED void *arg) {
|
||||
|
||||
close_audio:
|
||||
if (audio != NULL) {
|
||||
audio_destroy(audio);
|
||||
us_audio_destroy(audio);
|
||||
}
|
||||
sleep(1); // error_delay
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#undef IF_NOT_REPORTED
|
||||
#undef _IF_NOT_REPORTED
|
||||
|
||||
static void _relay_rtp_clients(const rtp_s *rtp) {
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
client_send(client, rtp);
|
||||
static void _relay_rtp_clients(const us_rtp_s *rtp) {
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
us_janus_client_send(client, rtp);
|
||||
});
|
||||
}
|
||||
|
||||
@ -254,117 +254,117 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
|
||||
// sysctl -w net.core.rmem_max=1000000
|
||||
// sysctl -w net.core.wmem_max=1000000
|
||||
|
||||
JLOG_INFO("main", "Initializing plugin ...");
|
||||
if (gw == NULL || config_dir_path == NULL || ((_g_config = plugin_config_init(config_dir_path)) == NULL)) {
|
||||
US_JLOG_INFO("main", "Initializing plugin ...");
|
||||
if (gw == NULL || config_dir_path == NULL || ((_g_config = us_config_init(config_dir_path)) == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
_g_gw = gw;
|
||||
|
||||
_g_video_queue = queue_init(1024);
|
||||
_g_rtpv = rtpv_init(_relay_rtp_clients, _g_config->video_zero_playout_delay);
|
||||
_g_video_queue = us_queue_init(1024);
|
||||
_g_rtpv = us_rtpv_init(_relay_rtp_clients, _g_config->video_zero_playout_delay);
|
||||
if (_g_config->audio_dev_name) {
|
||||
_g_rtpa = rtpa_init(_relay_rtp_clients);
|
||||
A_THREAD_CREATE(&_g_audio_tid, _audio_thread, NULL);
|
||||
_g_rtpa = us_rtpa_init(_relay_rtp_clients);
|
||||
US_THREAD_CREATE(&_g_audio_tid, _audio_thread, NULL);
|
||||
}
|
||||
A_THREAD_CREATE(&_g_video_rtp_tid, _video_rtp_thread, NULL);
|
||||
A_THREAD_CREATE(&_g_video_sink_tid, _video_sink_thread, NULL);
|
||||
US_THREAD_CREATE(&_g_video_rtp_tid, _video_rtp_thread, NULL);
|
||||
US_THREAD_CREATE(&_g_video_sink_tid, _video_sink_thread, NULL);
|
||||
|
||||
atomic_store(&_g_ready, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _plugin_destroy(void) {
|
||||
JLOG_INFO("main", "Destroying plugin ...");
|
||||
US_JLOG_INFO("main", "Destroying plugin ...");
|
||||
|
||||
atomic_store(&_g_stop, true);
|
||||
# define JOIN(_tid) { if (atomic_load(&_tid##_created)) { A_THREAD_JOIN(_tid); } }
|
||||
# define JOIN(_tid) { if (atomic_load(&_tid##_created)) { US_THREAD_JOIN(_tid); } }
|
||||
JOIN(_g_video_sink_tid);
|
||||
JOIN(_g_video_rtp_tid);
|
||||
JOIN(_g_audio_tid);
|
||||
# undef JOIN
|
||||
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
LIST_REMOVE(_g_clients, client);
|
||||
client_destroy(client);
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
US_LIST_REMOVE(_g_clients, client);
|
||||
us_janus_client_destroy(client);
|
||||
});
|
||||
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(_g_video_queue, frame_destroy);
|
||||
US_QUEUE_FREE_ITEMS_AND_DESTROY(_g_video_queue, us_frame_destroy);
|
||||
|
||||
DELETE(_g_rtpa, rtpa_destroy);
|
||||
DELETE(_g_rtpv, rtpv_destroy);
|
||||
DELETE(_g_config, plugin_config_destroy);
|
||||
US_DELETE(_g_rtpa, us_rtpa_destroy);
|
||||
US_DELETE(_g_rtpv, us_rtpv_destroy);
|
||||
US_DELETE(_g_config, us_config_destroy);
|
||||
}
|
||||
|
||||
#define IF_DISABLED(...) { if (!READY || STOP) { __VA_ARGS__ } }
|
||||
#define _IF_DISABLED(...) { if (!_READY || _STOP) { __VA_ARGS__ } }
|
||||
|
||||
static void _plugin_create_session(janus_plugin_session *session, int *err) {
|
||||
IF_DISABLED({ *err = -1; return; });
|
||||
LOCK_ALL;
|
||||
JLOG_INFO("main", "Creating session %p ...", session);
|
||||
client_s *client = client_init(_g_gw, session, (_g_config->audio_dev_name != NULL));
|
||||
LIST_APPEND(_g_clients, client);
|
||||
_IF_DISABLED({ *err = -1; return; });
|
||||
_LOCK_ALL;
|
||||
US_JLOG_INFO("main", "Creating session %p ...", session);
|
||||
us_janus_client_s *client = us_janus_client_init(_g_gw, session, (_g_config->audio_dev_name != NULL));
|
||||
US_LIST_APPEND(_g_clients, client);
|
||||
atomic_store(&_g_has_watchers, true);
|
||||
UNLOCK_ALL;
|
||||
_UNLOCK_ALL;
|
||||
}
|
||||
|
||||
static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
|
||||
IF_DISABLED({ *err = -1; return; });
|
||||
LOCK_ALL;
|
||||
_IF_DISABLED({ *err = -1; return; });
|
||||
_LOCK_ALL;
|
||||
bool found = false;
|
||||
bool has_watchers = false;
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
JLOG_INFO("main", "Removing session %p ...", session);
|
||||
LIST_REMOVE(_g_clients, client);
|
||||
client_destroy(client);
|
||||
US_JLOG_INFO("main", "Removing session %p ...", session);
|
||||
US_LIST_REMOVE(_g_clients, client);
|
||||
us_janus_client_destroy(client);
|
||||
found = true;
|
||||
} else {
|
||||
has_watchers = (has_watchers || atomic_load(&client->transmit));
|
||||
}
|
||||
});
|
||||
if (!found) {
|
||||
JLOG_WARN("main", "No session %p", session);
|
||||
US_JLOG_WARN("main", "No session %p", session);
|
||||
*err = -2;
|
||||
}
|
||||
atomic_store(&_g_has_watchers, has_watchers);
|
||||
UNLOCK_ALL;
|
||||
_UNLOCK_ALL;
|
||||
}
|
||||
|
||||
static json_t *_plugin_query_session(janus_plugin_session *session) {
|
||||
IF_DISABLED({ return NULL; });
|
||||
_IF_DISABLED({ return NULL; });
|
||||
json_t *info = NULL;
|
||||
LOCK_ALL;
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
_LOCK_ALL;
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
info = json_string("session_found");
|
||||
break;
|
||||
}
|
||||
});
|
||||
UNLOCK_ALL;
|
||||
_UNLOCK_ALL;
|
||||
return info;
|
||||
}
|
||||
|
||||
static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg, bool transmit) {
|
||||
IF_DISABLED({ return; });
|
||||
LOCK_ALL;
|
||||
_IF_DISABLED({ return; });
|
||||
_LOCK_ALL;
|
||||
bool found = false;
|
||||
bool has_watchers = false;
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
atomic_store(&client->transmit, transmit);
|
||||
// JLOG_INFO("main", "%s session %p", msg, session);
|
||||
// US_JLOG_INFO("main", "%s session %p", msg, session);
|
||||
found = true;
|
||||
}
|
||||
has_watchers = (has_watchers || atomic_load(&client->transmit));
|
||||
});
|
||||
if (!found) {
|
||||
JLOG_WARN("main", "No session %p", session);
|
||||
US_JLOG_WARN("main", "No session %p", session);
|
||||
}
|
||||
atomic_store(&_g_has_watchers, has_watchers);
|
||||
UNLOCK_ALL;
|
||||
_UNLOCK_ALL;
|
||||
}
|
||||
|
||||
#undef IF_DISABLED
|
||||
#undef _IF_DISABLED
|
||||
|
||||
static void _plugin_setup_media(janus_plugin_session *session) { _set_transmit(session, "Unmuted", true); }
|
||||
static void _plugin_hangup_media(janus_plugin_session *session) { _set_transmit(session, "Muted", false); }
|
||||
@ -385,14 +385,14 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
return janus_plugin_result_new(JANUS_PLUGIN_ERROR, (msg ? "No session" : "No message"), NULL);
|
||||
}
|
||||
|
||||
# define PUSH_ERROR(_error, _reason) { \
|
||||
/*JLOG_ERROR("main", "Message error in session %p: %s", session, _reason);*/ \
|
||||
json_t *_event = json_object(); \
|
||||
json_object_set_new(_event, "ustreamer", json_string("event")); \
|
||||
json_object_set_new(_event, "error_code", json_integer(_error)); \
|
||||
json_object_set_new(_event, "error", json_string(_reason)); \
|
||||
_g_gw->push_event(session, create(), transaction, _event, NULL); \
|
||||
json_decref(_event); \
|
||||
# define PUSH_ERROR(x_error, x_reason) { \
|
||||
/*US_JLOG_ERROR("main", "Message error in session %p: %s", session, x_reason);*/ \
|
||||
json_t *m_event = json_object(); \
|
||||
json_object_set_new(m_event, "ustreamer", json_string("event")); \
|
||||
json_object_set_new(m_event, "error_code", json_integer(x_error)); \
|
||||
json_object_set_new(m_event, "error", json_string(x_reason)); \
|
||||
_g_gw->push_event(session, create(), transaction, m_event, NULL); \
|
||||
json_decref(m_event); \
|
||||
}
|
||||
|
||||
json_t *request_obj = json_object_get(msg, "request");
|
||||
@ -406,16 +406,16 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
PUSH_ERROR(400, "Request not a string");
|
||||
goto ok_wait;
|
||||
}
|
||||
// JLOG_INFO("main", "Message: %s", request_str);
|
||||
// US_JLOG_INFO("main", "Message: %s", request_str);
|
||||
|
||||
# define PUSH_STATUS(_status, _jsep) { \
|
||||
json_t *_event = json_object(); \
|
||||
json_object_set_new(_event, "ustreamer", json_string("event")); \
|
||||
json_t *_result = json_object(); \
|
||||
json_object_set_new(_result, "status", json_string(_status)); \
|
||||
json_object_set_new(_event, "result", _result); \
|
||||
_g_gw->push_event(session, create(), transaction, _event, _jsep); \
|
||||
json_decref(_event); \
|
||||
# define PUSH_STATUS(x_status, x_jsep) { \
|
||||
json_t *m_event = json_object(); \
|
||||
json_object_set_new(m_event, "ustreamer", json_string("event")); \
|
||||
json_t *m_result = json_object(); \
|
||||
json_object_set_new(m_result, "status", json_string(x_status)); \
|
||||
json_object_set_new(m_event, "result", m_result); \
|
||||
_g_gw->push_event(session, create(), transaction, m_event, x_jsep); \
|
||||
json_decref(m_event); \
|
||||
}
|
||||
|
||||
if (!strcmp(request_str, "start")) {
|
||||
@ -427,19 +427,19 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
} else if (!strcmp(request_str, "watch")) {
|
||||
char *sdp;
|
||||
{
|
||||
char *video_sdp = rtpv_make_sdp(_g_rtpv);
|
||||
char *video_sdp = us_rtpv_make_sdp(_g_rtpv);
|
||||
if (video_sdp == NULL) {
|
||||
PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet");
|
||||
goto ok_wait;
|
||||
}
|
||||
char *audio_sdp = (_g_rtpa ? rtpa_make_sdp(_g_rtpa) : strdup(""));
|
||||
A_ASPRINTF(sdp,
|
||||
char *audio_sdp = (_g_rtpa ? us_rtpa_make_sdp(_g_rtpa) : strdup(""));
|
||||
US_ASPRINTF(sdp,
|
||||
"v=0" RN
|
||||
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
|
||||
"s=PiKVM uStreamer" RN
|
||||
"t=0 0" RN
|
||||
"%s%s",
|
||||
get_now_id() >> 1, audio_sdp, video_sdp
|
||||
us_get_now_id() >> 1, audio_sdp, video_sdp
|
||||
);
|
||||
free(audio_sdp);
|
||||
free(video_sdp);
|
||||
@ -466,12 +466,12 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
// ***** Plugin *****
|
||||
|
||||
static int _plugin_get_api_compatibility(void) { return JANUS_PLUGIN_API_VERSION; }
|
||||
static int _plugin_get_version(void) { return VERSION_U; }
|
||||
static const char *_plugin_get_version_string(void) { return VERSION; }
|
||||
static int _plugin_get_version(void) { return US_VERSION_U; }
|
||||
static const char *_plugin_get_version_string(void) { return US_VERSION; }
|
||||
static const char *_plugin_get_description(void) { return "PiKVM uStreamer Janus plugin for H.264 video"; }
|
||||
static const char *_plugin_get_name(void) { return PLUGIN_NAME; }
|
||||
static const char *_plugin_get_name(void) { return US_PLUGIN_NAME; }
|
||||
static const char *_plugin_get_author(void) { return "Maxim Devaev <mdevaev@gmail.com>"; }
|
||||
static const char *_plugin_get_package(void) { return PLUGIN_PACKAGE; }
|
||||
static const char *_plugin_get_package(void) { return US_PLUGIN_PACKAGE; }
|
||||
|
||||
static void _plugin_incoming_rtp(UNUSED janus_plugin_session *handle, UNUSED janus_plugin_rtp *packet) {
|
||||
// Just a stub to avoid logging spam about the plugin's purpose
|
||||
|
||||
@ -23,12 +23,12 @@
|
||||
#include "queue.h"
|
||||
|
||||
|
||||
queue_s *queue_init(unsigned capacity) {
|
||||
queue_s *queue;
|
||||
A_CALLOC(queue, 1);
|
||||
A_CALLOC(queue->items, capacity);
|
||||
us_queue_s *us_queue_init(unsigned capacity) {
|
||||
us_queue_s *queue;
|
||||
US_CALLOC(queue, 1);
|
||||
US_CALLOC(queue->items, capacity);
|
||||
queue->capacity = capacity;
|
||||
A_MUTEX_INIT(&queue->mutex);
|
||||
US_MUTEX_INIT(&queue->mutex);
|
||||
|
||||
pthread_condattr_t attrs;
|
||||
assert(!pthread_condattr_init(&attrs));
|
||||
@ -39,61 +39,61 @@ queue_s *queue_init(unsigned capacity) {
|
||||
return queue;
|
||||
}
|
||||
|
||||
void queue_destroy(queue_s *queue) {
|
||||
void us_queue_destroy(us_queue_s *queue) {
|
||||
free(queue->items);
|
||||
free(queue);
|
||||
}
|
||||
|
||||
#define WAIT_OR_UNLOCK(_var, _cond) { \
|
||||
struct timespec _ts; \
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &_ts)); \
|
||||
ld_to_timespec(timespec_to_ld(&_ts) + timeout, &_ts); \
|
||||
while (_var) { \
|
||||
int err = pthread_cond_timedwait(_cond, &queue->mutex, &_ts); \
|
||||
#define _WAIT_OR_UNLOCK(x_var, x_cond) { \
|
||||
struct timespec m_ts; \
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \
|
||||
us_ld_to_timespec(us_timespec_to_ld(&m_ts) + timeout, &m_ts); \
|
||||
while (x_var) { \
|
||||
int err = pthread_cond_timedwait(x_cond, &queue->mutex, &m_ts); \
|
||||
if (err == ETIMEDOUT) { \
|
||||
A_MUTEX_UNLOCK(&queue->mutex); \
|
||||
US_MUTEX_UNLOCK(&queue->mutex); \
|
||||
return -1; \
|
||||
} \
|
||||
assert(!err); \
|
||||
} \
|
||||
}
|
||||
|
||||
int queue_put(queue_s *queue, void *item, long double timeout) {
|
||||
A_MUTEX_LOCK(&queue->mutex);
|
||||
int us_queue_put(us_queue_s *queue, void *item, long double timeout) {
|
||||
US_MUTEX_LOCK(&queue->mutex);
|
||||
if (timeout == 0) {
|
||||
if (queue->size == queue->capacity) {
|
||||
A_MUTEX_UNLOCK(&queue->mutex);
|
||||
US_MUTEX_UNLOCK(&queue->mutex);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
WAIT_OR_UNLOCK(queue->size == queue->capacity, &queue->full_cond);
|
||||
_WAIT_OR_UNLOCK(queue->size == queue->capacity, &queue->full_cond);
|
||||
}
|
||||
queue->items[queue->in] = item;
|
||||
++queue->size;
|
||||
++queue->in;
|
||||
queue->in %= queue->capacity;
|
||||
A_MUTEX_UNLOCK(&queue->mutex);
|
||||
US_MUTEX_UNLOCK(&queue->mutex);
|
||||
assert(!pthread_cond_broadcast(&queue->empty_cond));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int queue_get(queue_s *queue, void **item, long double timeout) {
|
||||
A_MUTEX_LOCK(&queue->mutex);
|
||||
WAIT_OR_UNLOCK(queue->size == 0, &queue->empty_cond);
|
||||
int us_queue_get(us_queue_s *queue, void **item, long double timeout) {
|
||||
US_MUTEX_LOCK(&queue->mutex);
|
||||
_WAIT_OR_UNLOCK(queue->size == 0, &queue->empty_cond);
|
||||
*item = queue->items[queue->out];
|
||||
--queue->size;
|
||||
++queue->out;
|
||||
queue->out %= queue->capacity;
|
||||
A_MUTEX_UNLOCK(&queue->mutex);
|
||||
US_MUTEX_UNLOCK(&queue->mutex);
|
||||
assert(!pthread_cond_broadcast(&queue->full_cond));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef WAIT_OR_UNLOCK
|
||||
#undef _WAIT_OR_UNLOCK
|
||||
|
||||
int queue_get_free(queue_s *queue) {
|
||||
A_MUTEX_LOCK(&queue->mutex);
|
||||
int us_queue_get_free(us_queue_s *queue) {
|
||||
US_MUTEX_LOCK(&queue->mutex);
|
||||
unsigned size = queue->size;
|
||||
A_MUTEX_UNLOCK(&queue->mutex);
|
||||
US_MUTEX_UNLOCK(&queue->mutex);
|
||||
return queue->capacity - size;
|
||||
}
|
||||
|
||||
@ -44,26 +44,26 @@ typedef struct {
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t full_cond;
|
||||
pthread_cond_t empty_cond;
|
||||
} queue_s;
|
||||
} us_queue_s;
|
||||
|
||||
|
||||
#define QUEUE_FREE_ITEMS_AND_DESTROY(_queue, _free_item) { \
|
||||
if (_queue) { \
|
||||
while (!queue_get_free(_queue)) { \
|
||||
void *_ptr; \
|
||||
assert(!queue_get(_queue, &_ptr, 0.1)); \
|
||||
if (_ptr != NULL) { \
|
||||
_free_item(_ptr); \
|
||||
#define US_QUEUE_FREE_ITEMS_AND_DESTROY(x_queue, x_free_item) { \
|
||||
if (x_queue) { \
|
||||
while (!us_queue_get_free(x_queue)) { \
|
||||
void *m_ptr; \
|
||||
assert(!us_queue_get(x_queue, &m_ptr, 0.1)); \
|
||||
if (m_ptr != NULL) { \
|
||||
x_free_item(m_ptr); \
|
||||
} \
|
||||
} \
|
||||
queue_destroy(_queue); \
|
||||
us_queue_destroy(x_queue); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
queue_s *queue_init(unsigned capacity);
|
||||
void queue_destroy(queue_s *queue);
|
||||
us_queue_s *us_queue_init(unsigned capacity);
|
||||
void us_queue_destroy(us_queue_s *queue);
|
||||
|
||||
int queue_put(queue_s *queue, void *item, long double timeout);
|
||||
int queue_get(queue_s *queue, void **item, long double timeout);
|
||||
int queue_get_free(queue_s *queue);
|
||||
int us_queue_put(us_queue_s *queue, void *item, long double timeout);
|
||||
int us_queue_get(us_queue_s *queue, void **item, long double timeout);
|
||||
int us_queue_get_free(us_queue_s *queue);
|
||||
|
||||
@ -26,28 +26,28 @@
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
rtp_s *rtp_init(unsigned payload, bool video, bool zero_playout_delay) {
|
||||
rtp_s *rtp;
|
||||
A_CALLOC(rtp, 1);
|
||||
us_rtp_s *us_rtp_init(unsigned payload, bool video, bool zero_playout_delay) {
|
||||
us_rtp_s *rtp;
|
||||
US_CALLOC(rtp, 1);
|
||||
rtp->payload = payload;
|
||||
rtp->video = video;
|
||||
rtp->zero_playout_delay = zero_playout_delay; // See client.c
|
||||
rtp->ssrc = triple_u32(get_now_monotonic_u64());
|
||||
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
|
||||
return rtp;
|
||||
}
|
||||
|
||||
rtp_s *rtp_dup(const rtp_s *rtp) {
|
||||
rtp_s *new;
|
||||
A_CALLOC(new, 1);
|
||||
memcpy(new, rtp, sizeof(rtp_s));
|
||||
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp) {
|
||||
us_rtp_s *new;
|
||||
US_CALLOC(new, 1);
|
||||
memcpy(new, rtp, sizeof(us_rtp_s));
|
||||
return new;
|
||||
}
|
||||
|
||||
void rtp_destroy(rtp_s *rtp) {
|
||||
void us_rtp_destroy(us_rtp_s *rtp) {
|
||||
free(rtp);
|
||||
}
|
||||
|
||||
void rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked) {
|
||||
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked) {
|
||||
uint32_t word0 = 0x80000000;
|
||||
if (marked) {
|
||||
word0 |= 1 << 23;
|
||||
|
||||
@ -32,8 +32,8 @@
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
|
||||
#define RTP_DATAGRAM_SIZE 1200
|
||||
#define RTP_HEADER_SIZE 12
|
||||
#define US_RTP_DATAGRAM_SIZE 1200
|
||||
#define US_RTP_HEADER_SIZE 12
|
||||
|
||||
|
||||
typedef struct {
|
||||
@ -43,15 +43,15 @@ typedef struct {
|
||||
uint32_t ssrc;
|
||||
|
||||
uint16_t seq;
|
||||
uint8_t datagram[RTP_DATAGRAM_SIZE];
|
||||
uint8_t datagram[US_RTP_DATAGRAM_SIZE];
|
||||
size_t used;
|
||||
} rtp_s;
|
||||
} us_rtp_s;
|
||||
|
||||
typedef void (*rtp_callback_f)(const rtp_s *rtp);
|
||||
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
|
||||
|
||||
|
||||
rtp_s *rtp_init(unsigned payload, bool video, bool zero_playout_delay);
|
||||
rtp_s *rtp_dup(const rtp_s *rtp);
|
||||
void rtp_destroy(rtp_s *rtp);
|
||||
us_rtp_s *us_rtp_init(unsigned payload, bool video, bool zero_playout_delay);
|
||||
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp);
|
||||
void us_rtp_destroy(us_rtp_s *rtp);
|
||||
|
||||
void rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked);
|
||||
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked);
|
||||
|
||||
@ -23,23 +23,23 @@
|
||||
#include "rtpa.h"
|
||||
|
||||
|
||||
rtpa_s *rtpa_init(rtp_callback_f callback) {
|
||||
rtpa_s *rtpa;
|
||||
A_CALLOC(rtpa, 1);
|
||||
rtpa->rtp = rtp_init(111, false, false);
|
||||
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
|
||||
us_rtpa_s *rtpa;
|
||||
US_CALLOC(rtpa, 1);
|
||||
rtpa->rtp = us_rtp_init(111, false, false);
|
||||
rtpa->callback = callback;
|
||||
return rtpa;
|
||||
}
|
||||
|
||||
void rtpa_destroy(rtpa_s *rtpa) {
|
||||
rtp_destroy(rtpa->rtp);
|
||||
void us_rtpa_destroy(us_rtpa_s *rtpa) {
|
||||
us_rtp_destroy(rtpa->rtp);
|
||||
free(rtpa);
|
||||
}
|
||||
|
||||
char *rtpa_make_sdp(rtpa_s *rtpa) {
|
||||
char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
|
||||
# define PAYLOAD rtpa->rtp->payload
|
||||
char *sdp;
|
||||
A_ASPRINTF(sdp,
|
||||
US_ASPRINTF(sdp,
|
||||
"m=audio 1 RTP/SAVPF %u" RN
|
||||
"c=IN IP4 0.0.0.0" RN
|
||||
"a=rtpmap:%u OPUS/48000/2" RN
|
||||
@ -56,11 +56,11 @@ char *rtpa_make_sdp(rtpa_s *rtpa) {
|
||||
return sdp;
|
||||
}
|
||||
|
||||
void rtpa_wrap(rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts) {
|
||||
if (size + RTP_HEADER_SIZE <= RTP_DATAGRAM_SIZE) {
|
||||
rtp_write_header(rtpa->rtp, pts, false);
|
||||
memcpy(rtpa->rtp->datagram + RTP_HEADER_SIZE, data, size);
|
||||
rtpa->rtp->used = size + RTP_HEADER_SIZE;
|
||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts) {
|
||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
||||
us_rtp_write_header(rtpa->rtp, pts, false);
|
||||
memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, size);
|
||||
rtpa->rtp->used = size + US_RTP_HEADER_SIZE;
|
||||
rtpa->callback(rtpa->rtp);
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,13 +36,13 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
rtp_s *rtp;
|
||||
rtp_callback_f callback;
|
||||
} rtpa_s;
|
||||
us_rtp_s *rtp;
|
||||
us_rtp_callback_f callback;
|
||||
} us_rtpa_s;
|
||||
|
||||
|
||||
rtpa_s *rtpa_init(rtp_callback_f callback);
|
||||
void rtpa_destroy(rtpa_s *rtpa);
|
||||
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback);
|
||||
void us_rtpa_destroy(us_rtpa_s *rtpa);
|
||||
|
||||
char *rtpa_make_sdp(rtpa_s *rtpa);
|
||||
void rtpa_wrap(rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts);
|
||||
char *us_rtpa_make_sdp(us_rtpa_s *rtpa);
|
||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts);
|
||||
|
||||
@ -26,50 +26,50 @@
|
||||
#include "rtpv.h"
|
||||
|
||||
|
||||
void _rtpv_process_nalu(rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked);
|
||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked);
|
||||
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size);
|
||||
|
||||
|
||||
rtpv_s *rtpv_init(rtp_callback_f callback, bool zero_playout_delay) {
|
||||
rtpv_s *rtpv;
|
||||
A_CALLOC(rtpv, 1);
|
||||
rtpv->rtp = rtp_init(96, true, zero_playout_delay);
|
||||
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay) {
|
||||
us_rtpv_s *rtpv;
|
||||
US_CALLOC(rtpv, 1);
|
||||
rtpv->rtp = us_rtp_init(96, true, zero_playout_delay);
|
||||
rtpv->callback = callback;
|
||||
rtpv->sps = frame_init();
|
||||
rtpv->pps = frame_init();
|
||||
A_MUTEX_INIT(&rtpv->mutex);
|
||||
rtpv->sps = us_frame_init();
|
||||
rtpv->pps = us_frame_init();
|
||||
US_MUTEX_INIT(&rtpv->mutex);
|
||||
return rtpv;
|
||||
}
|
||||
|
||||
void rtpv_destroy(rtpv_s *rtpv) {
|
||||
A_MUTEX_DESTROY(&rtpv->mutex);
|
||||
frame_destroy(rtpv->pps);
|
||||
frame_destroy(rtpv->sps);
|
||||
rtp_destroy(rtpv->rtp);
|
||||
void us_rtpv_destroy(us_rtpv_s *rtpv) {
|
||||
US_MUTEX_DESTROY(&rtpv->mutex);
|
||||
us_frame_destroy(rtpv->pps);
|
||||
us_frame_destroy(rtpv->sps);
|
||||
us_rtp_destroy(rtpv->rtp);
|
||||
free(rtpv);
|
||||
}
|
||||
|
||||
char *rtpv_make_sdp(rtpv_s *rtpv) {
|
||||
A_MUTEX_LOCK(&rtpv->mutex);
|
||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
||||
US_MUTEX_LOCK(&rtpv->mutex);
|
||||
|
||||
if (rtpv->sps->used == 0 || rtpv->pps->used == 0) {
|
||||
A_MUTEX_UNLOCK(&rtpv->mutex);
|
||||
US_MUTEX_UNLOCK(&rtpv->mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *sps = NULL;
|
||||
char *pps = NULL;
|
||||
base64_encode(rtpv->sps->data, rtpv->sps->used, &sps, NULL);
|
||||
base64_encode(rtpv->pps->data, rtpv->pps->used, &pps, NULL);
|
||||
us_base64_encode(rtpv->sps->data, rtpv->sps->used, &sps, NULL);
|
||||
us_base64_encode(rtpv->pps->data, rtpv->pps->used, &pps, NULL);
|
||||
|
||||
A_MUTEX_UNLOCK(&rtpv->mutex);
|
||||
US_MUTEX_UNLOCK(&rtpv->mutex);
|
||||
|
||||
# define PAYLOAD rtpv->rtp->payload
|
||||
// https://tools.ietf.org/html/rfc6184
|
||||
// https://github.com/meetecho/janus-gateway/issues/2443
|
||||
char *sdp;
|
||||
A_ASPRINTF(sdp,
|
||||
US_ASPRINTF(sdp,
|
||||
"m=video 1 RTP/SAVPF %u" RN
|
||||
"c=IN IP4 0.0.0.0" RN
|
||||
"a=rtpmap:%u H264/90000" RN
|
||||
@ -97,19 +97,19 @@ char *rtpv_make_sdp(rtpv_s *rtpv) {
|
||||
return sdp;
|
||||
}
|
||||
|
||||
#define PRE 3 // Annex B prefix length
|
||||
#define _PRE 3 // Annex B prefix length
|
||||
|
||||
void rtpv_wrap(rtpv_s *rtpv, const frame_s *frame) {
|
||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame) {
|
||||
// There is a complicated logic here but everything works as it should:
|
||||
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
|
||||
|
||||
assert(frame->format == V4L2_PIX_FMT_H264);
|
||||
|
||||
const uint32_t pts = get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||
ssize_t last_offset = -PRE;
|
||||
const uint32_t pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||
ssize_t last_offset = -_PRE;
|
||||
|
||||
while (true) { // Find and iterate by nalus
|
||||
const size_t next_start = last_offset + PRE;
|
||||
const size_t next_start = last_offset + _PRE;
|
||||
ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start);
|
||||
if (offset < 0) {
|
||||
break;
|
||||
@ -117,8 +117,8 @@ void rtpv_wrap(rtpv_s *rtpv, const frame_s *frame) {
|
||||
offset += next_start;
|
||||
|
||||
if (last_offset >= 0) {
|
||||
const uint8_t *data = frame->data + last_offset + PRE;
|
||||
size_t size = offset - last_offset - PRE;
|
||||
const uint8_t *data = frame->data + last_offset + _PRE;
|
||||
size_t size = offset - last_offset - _PRE;
|
||||
if (data[size - 1] == 0) { // Check for extra 00
|
||||
--size;
|
||||
}
|
||||
@ -129,53 +129,53 @@ void rtpv_wrap(rtpv_s *rtpv, const frame_s *frame) {
|
||||
}
|
||||
|
||||
if (last_offset >= 0) {
|
||||
const uint8_t *data = frame->data + last_offset + PRE;
|
||||
size_t size = frame->used - last_offset - PRE;
|
||||
const uint8_t *data = frame->data + last_offset + _PRE;
|
||||
size_t size = frame->used - last_offset - _PRE;
|
||||
_rtpv_process_nalu(rtpv, data, size, pts, true);
|
||||
}
|
||||
}
|
||||
|
||||
void _rtpv_process_nalu(rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked) {
|
||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked) {
|
||||
const unsigned ref_idc = (data[0] >> 5) & 3;
|
||||
const unsigned type = data[0] & 0x1F;
|
||||
|
||||
frame_s *ps = NULL;
|
||||
us_frame_s *ps = NULL;
|
||||
switch (type) {
|
||||
case 7: ps = rtpv->sps; break;
|
||||
case 8: ps = rtpv->pps; break;
|
||||
}
|
||||
if (ps) {
|
||||
A_MUTEX_LOCK(&rtpv->mutex);
|
||||
frame_set_data(ps, data, size);
|
||||
A_MUTEX_UNLOCK(&rtpv->mutex);
|
||||
US_MUTEX_LOCK(&rtpv->mutex);
|
||||
us_frame_set_data(ps, data, size);
|
||||
US_MUTEX_UNLOCK(&rtpv->mutex);
|
||||
}
|
||||
|
||||
# define DG rtpv->rtp->datagram
|
||||
|
||||
if (size + RTP_HEADER_SIZE <= RTP_DATAGRAM_SIZE) {
|
||||
rtp_write_header(rtpv->rtp, pts, marked);
|
||||
memcpy(DG + RTP_HEADER_SIZE, data, size);
|
||||
rtpv->rtp->used = size + RTP_HEADER_SIZE;
|
||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
||||
us_rtp_write_header(rtpv->rtp, pts, marked);
|
||||
memcpy(DG + US_RTP_HEADER_SIZE, data, size);
|
||||
rtpv->rtp->used = size + US_RTP_HEADER_SIZE;
|
||||
rtpv->callback(rtpv->rtp);
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t fu_overhead = RTP_HEADER_SIZE + 2; // FU-A overhead
|
||||
const size_t fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
|
||||
|
||||
const uint8_t *src = data + 1;
|
||||
ssize_t remaining = size - 1;
|
||||
|
||||
bool first = true;
|
||||
while (remaining > 0) {
|
||||
ssize_t frag_size = RTP_DATAGRAM_SIZE - fu_overhead;
|
||||
ssize_t frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
|
||||
const bool last = (remaining <= frag_size);
|
||||
if (last) {
|
||||
frag_size = remaining;
|
||||
}
|
||||
|
||||
rtp_write_header(rtpv->rtp, pts, (marked && last));
|
||||
us_rtp_write_header(rtpv->rtp, pts, (marked && last));
|
||||
|
||||
DG[RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
|
||||
DG[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
|
||||
|
||||
uint8_t fu = type;
|
||||
if (first) {
|
||||
@ -184,7 +184,7 @@ void _rtpv_process_nalu(rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t
|
||||
if (last) {
|
||||
fu |= 0x40;
|
||||
}
|
||||
DG[RTP_HEADER_SIZE + 1] = fu;
|
||||
DG[US_RTP_HEADER_SIZE + 1] = fu;
|
||||
|
||||
memcpy(DG + fu_overhead, src, frag_size);
|
||||
rtpv->rtp->used = fu_overhead + frag_size;
|
||||
@ -200,8 +200,8 @@ void _rtpv_process_nalu(rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t
|
||||
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size) {
|
||||
// Parses buffer for 00 00 01 start codes
|
||||
if (size >= PRE) {
|
||||
for (size_t index = 0; index <= size - PRE; ++index) {
|
||||
if (size >= _PRE) {
|
||||
for (size_t index = 0; index <= size - _PRE; ++index) {
|
||||
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
|
||||
return index;
|
||||
}
|
||||
@ -210,4 +210,4 @@ static ssize_t _find_annexb(const uint8_t *data, size_t size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#undef PRE
|
||||
#undef _PRE
|
||||
|
||||
@ -42,16 +42,16 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
rtp_s *rtp;
|
||||
rtp_callback_f callback;
|
||||
frame_s *sps; // Actually not a frame, just a bytes storage
|
||||
frame_s *pps;
|
||||
pthread_mutex_t mutex;
|
||||
} rtpv_s;
|
||||
us_rtp_s *rtp;
|
||||
us_rtp_callback_f callback;
|
||||
us_frame_s *sps; // Actually not a frame, just a bytes storage
|
||||
us_frame_s *pps;
|
||||
pthread_mutex_t mutex;
|
||||
} us_rtpv_s;
|
||||
|
||||
|
||||
rtpv_s *rtpv_init(rtp_callback_f callback, bool zero_playout_delay);
|
||||
void rtpv_destroy(rtpv_s *rtpv);
|
||||
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay);
|
||||
void us_rtpv_destroy(us_rtpv_s *rtpv);
|
||||
|
||||
char *rtpv_make_sdp(rtpv_s *rtpv);
|
||||
void rtpv_wrap(rtpv_s *rtpv, const frame_s *frame);
|
||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv);
|
||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame);
|
||||
|
||||
@ -34,24 +34,24 @@
|
||||
#endif
|
||||
|
||||
|
||||
int tc358743_read_info(const char *path, tc358743_info_s *info) {
|
||||
MEMSET_ZERO(*info);
|
||||
int us_tc358743_read_info(const char *path, us_tc358743_info_s *info) {
|
||||
US_MEMSET_ZERO(*info);
|
||||
|
||||
int fd = -1;
|
||||
if ((fd = open(path, O_RDWR)) < 0) {
|
||||
JLOG_PERROR("audio", "Can't open TC358743 V4L2 device");
|
||||
US_JLOG_PERROR("audio", "Can't open TC358743 V4L2 device");
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define READ_CID(_cid, _field) { \
|
||||
struct v4l2_control ctl = {0}; \
|
||||
ctl.id = _cid; \
|
||||
if (xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) { \
|
||||
JLOG_PERROR("audio", "Can't get value of " #_cid); \
|
||||
# define READ_CID(x_cid, x_field) { \
|
||||
struct v4l2_control m_ctl = {0}; \
|
||||
m_ctl.id = x_cid; \
|
||||
if (us_xioctl(fd, VIDIOC_G_CTRL, &m_ctl) < 0) { \
|
||||
US_JLOG_PERROR("audio", "Can't get value of " #x_cid); \
|
||||
close(fd); \
|
||||
return -1; \
|
||||
} \
|
||||
info->_field = ctl.value; \
|
||||
info->x_field = m_ctl.value; \
|
||||
}
|
||||
|
||||
READ_CID(TC358743_CID_AUDIO_PRESENT, has_audio);
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
typedef struct {
|
||||
bool has_audio;
|
||||
unsigned audio_hz;
|
||||
} tc358743_info_s;
|
||||
} us_tc358743_info_s;
|
||||
|
||||
|
||||
int tc358743_read_info(const char *path, tc358743_info_s *info);
|
||||
int us_tc358743_read_info(const char *path, us_tc358743_info_s *info);
|
||||
|
||||
@ -27,21 +27,21 @@ typedef struct {
|
||||
double drop_same_frames;
|
||||
|
||||
int fd;
|
||||
memsink_shared_s *mem;
|
||||
us_memsink_shared_s *mem;
|
||||
|
||||
uint64_t frame_id;
|
||||
long double frame_ts;
|
||||
frame_s *frame;
|
||||
} MemsinkObject;
|
||||
us_frame_s *frame;
|
||||
} _MemsinkObject;
|
||||
|
||||
|
||||
#define MEM(_next) self->mem->_next
|
||||
#define FRAME(_next) self->frame->_next
|
||||
#define _MEM(_next) self->mem->_next
|
||||
#define _FRAME(_next) self->frame->_next
|
||||
|
||||
|
||||
static void MemsinkObject_destroy_internals(MemsinkObject *self) {
|
||||
static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
|
||||
if (self->mem != NULL) {
|
||||
memsink_shared_unmap(self->mem);
|
||||
us_memsink_shared_unmap(self->mem);
|
||||
self->mem = NULL;
|
||||
}
|
||||
if (self->fd > 0) {
|
||||
@ -49,12 +49,12 @@ static void MemsinkObject_destroy_internals(MemsinkObject *self) {
|
||||
self->fd = -1;
|
||||
}
|
||||
if (self->frame) {
|
||||
frame_destroy(self->frame);
|
||||
us_frame_destroy(self->frame);
|
||||
self->frame = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
||||
static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
||||
self->lock_timeout = 1;
|
||||
self->wait_timeout = 1;
|
||||
|
||||
@ -78,14 +78,14 @@ static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwa
|
||||
|
||||
# undef SET_DOUBLE
|
||||
|
||||
self->frame = frame_init();
|
||||
self->frame = us_frame_init();
|
||||
|
||||
if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((self->mem = memsink_shared_map(self->fd)) == NULL) {
|
||||
if ((self->mem = us_memsink_shared_map(self->fd)) == NULL) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
@ -93,37 +93,37 @@ static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwa
|
||||
return 0;
|
||||
|
||||
error:
|
||||
MemsinkObject_destroy_internals(self);
|
||||
_MemsinkObject_destroy_internals(self);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_repr(MemsinkObject *self) {
|
||||
static PyObject *_MemsinkObject_repr(_MemsinkObject *self) {
|
||||
char repr[1024];
|
||||
snprintf(repr, 1023, "<Memsink(%s)>", self->obj);
|
||||
return Py_BuildValue("s", repr);
|
||||
}
|
||||
|
||||
static void MemsinkObject_dealloc(MemsinkObject *self) {
|
||||
MemsinkObject_destroy_internals(self);
|
||||
static void _MemsinkObject_dealloc(_MemsinkObject *self) {
|
||||
_MemsinkObject_destroy_internals(self);
|
||||
PyObject_Del(self);
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_close(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
MemsinkObject_destroy_internals(self);
|
||||
static PyObject *_MemsinkObject_close(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
_MemsinkObject_destroy_internals(self);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_enter(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
static PyObject *_MemsinkObject_enter(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
Py_INCREF(self);
|
||||
return (PyObject *)self;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_exit(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
static PyObject *_MemsinkObject_exit(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
return PyObject_CallMethod((PyObject *)self, "close", "");
|
||||
}
|
||||
|
||||
static int wait_frame(MemsinkObject *self) {
|
||||
long double deadline_ts = get_now_monotonic() + self->wait_timeout;
|
||||
static int _wait_frame(_MemsinkObject *self) {
|
||||
long double deadline_ts = us_get_now_monotonic() + self->wait_timeout;
|
||||
|
||||
# define RETURN_OS_ERROR { \
|
||||
Py_BLOCK_THREADS \
|
||||
@ -135,21 +135,21 @@ static int wait_frame(MemsinkObject *self) {
|
||||
do {
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
|
||||
int retval = flock_timedwait_monotonic(self->fd, self->lock_timeout);
|
||||
now = get_now_monotonic();
|
||||
int retval = us_flock_timedwait_monotonic(self->fd, self->lock_timeout);
|
||||
now = us_get_now_monotonic();
|
||||
|
||||
if (retval < 0 && errno != EWOULDBLOCK) {
|
||||
RETURN_OS_ERROR;
|
||||
|
||||
} else if (retval == 0) {
|
||||
if (MEM(magic) == MEMSINK_MAGIC && MEM(version) == MEMSINK_VERSION && MEM(id) != self->frame_id) {
|
||||
if (_MEM(magic) == US_MEMSINK_MAGIC && _MEM(version) == US_MEMSINK_VERSION && _MEM(id) != self->frame_id) {
|
||||
if (self->drop_same_frames > 0) {
|
||||
if (
|
||||
FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
|
||||
US_FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
|
||||
&& (self->frame_ts + self->drop_same_frames > now)
|
||||
&& !memcmp(FRAME(data), MEM(data), MEM(used))
|
||||
&& !memcmp(_FRAME(data), _MEM(data), _MEM(used))
|
||||
) {
|
||||
self->frame_id = MEM(id);
|
||||
self->frame_id = _MEM(id);
|
||||
goto drop;
|
||||
}
|
||||
}
|
||||
@ -181,23 +181,23 @@ static int wait_frame(MemsinkObject *self) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
if (self->mem == NULL || self->fd <= 0) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Closed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (wait_frame(self)) {
|
||||
switch (_wait_frame(self)) {
|
||||
case 0: break;
|
||||
case -2: Py_RETURN_NONE;
|
||||
default: return NULL;
|
||||
}
|
||||
|
||||
frame_set_data(self->frame, MEM(data), MEM(used));
|
||||
FRAME_COPY_META(self->mem, self->frame);
|
||||
self->frame_id = MEM(id);
|
||||
self->frame_ts = get_now_monotonic();
|
||||
MEM(last_client_ts) = self->frame_ts;
|
||||
us_frame_set_data(self->frame, _MEM(data), _MEM(used));
|
||||
US_FRAME_COPY_META(self->mem, self->frame);
|
||||
self->frame_id = _MEM(id);
|
||||
self->frame_ts = us_get_now_monotonic();
|
||||
_MEM(last_client_ts) = self->frame_ts;
|
||||
|
||||
if (flock(self->fd, LOCK_UN) < 0) {
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
@ -219,7 +219,7 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
|
||||
} \
|
||||
Py_DECREF(_tmp); \
|
||||
}
|
||||
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(FRAME(_key)))
|
||||
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(_FRAME(_key)))
|
||||
|
||||
SET_NUMBER(width, Long, Long);
|
||||
SET_NUMBER(height, Long, Long);
|
||||
@ -230,7 +230,7 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
|
||||
SET_NUMBER(grab_ts, Double, Float);
|
||||
SET_NUMBER(encode_begin_ts, Double, Float);
|
||||
SET_NUMBER(encode_end_ts, Double, Float);
|
||||
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)FRAME(data), FRAME(used)));
|
||||
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)_FRAME(data), _FRAME(used)));
|
||||
|
||||
# undef SET_NUMBER
|
||||
# undef SET_VALUE
|
||||
@ -238,12 +238,12 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
|
||||
return dict_frame;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_is_opened(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
static PyObject *_MemsinkObject_is_opened(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
return PyBool_FromLong(self->mem != NULL && self->fd > 0);
|
||||
}
|
||||
|
||||
#define FIELD_GETTER(_field, _from, _to) \
|
||||
static PyObject *MemsinkObject_getter_##_field(MemsinkObject *self, void *Py_UNUSED(closure)) { \
|
||||
static PyObject *_MemsinkObject_getter_##_field(_MemsinkObject *self, void *Py_UNUSED(closure)) { \
|
||||
return Py##_to##_From##_from(self->_field); \
|
||||
}
|
||||
|
||||
@ -254,9 +254,9 @@ FIELD_GETTER(drop_same_frames, Double, Float)
|
||||
|
||||
#undef FIELD_GETTER
|
||||
|
||||
static PyMethodDef MemsinkObject_methods[] = {
|
||||
static PyMethodDef _MemsinkObject_methods[] = {
|
||||
# define ADD_METHOD(_name, _method, _flags) \
|
||||
{.ml_name = _name, .ml_meth = (PyCFunction)MemsinkObject_##_method, .ml_flags = (_flags)}
|
||||
{.ml_name = _name, .ml_meth = (PyCFunction)_MemsinkObject_##_method, .ml_flags = (_flags)}
|
||||
ADD_METHOD("close", close, METH_NOARGS),
|
||||
ADD_METHOD("__enter__", enter, METH_NOARGS),
|
||||
ADD_METHOD("__exit__", exit, METH_VARARGS),
|
||||
@ -266,8 +266,8 @@ static PyMethodDef MemsinkObject_methods[] = {
|
||||
# undef ADD_METHOD
|
||||
};
|
||||
|
||||
static PyGetSetDef MemsinkObject_getsets[] = {
|
||||
# define ADD_GETTER(_field) {.name = #_field, .get = (getter)MemsinkObject_getter_##_field}
|
||||
static PyGetSetDef _MemsinkObject_getsets[] = {
|
||||
# define ADD_GETTER(_field) {.name = #_field, .get = (getter)_MemsinkObject_getter_##_field}
|
||||
ADD_GETTER(obj),
|
||||
ADD_GETTER(lock_timeout),
|
||||
ADD_GETTER(wait_timeout),
|
||||
@ -276,43 +276,40 @@ static PyGetSetDef MemsinkObject_getsets[] = {
|
||||
# undef ADD_GETTER
|
||||
};
|
||||
|
||||
static PyTypeObject MemsinkType = {
|
||||
static PyTypeObject _MemsinkType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "ustreamer.Memsink",
|
||||
.tp_basicsize = sizeof(MemsinkObject),
|
||||
.tp_basicsize = sizeof(_MemsinkObject),
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_new = PyType_GenericNew,
|
||||
.tp_init = (initproc)MemsinkObject_init,
|
||||
.tp_dealloc = (destructor)MemsinkObject_dealloc,
|
||||
.tp_repr = (reprfunc)MemsinkObject_repr,
|
||||
.tp_methods = MemsinkObject_methods,
|
||||
.tp_getset = MemsinkObject_getsets,
|
||||
.tp_init = (initproc)_MemsinkObject_init,
|
||||
.tp_dealloc = (destructor)_MemsinkObject_dealloc,
|
||||
.tp_repr = (reprfunc)_MemsinkObject_repr,
|
||||
.tp_methods = _MemsinkObject_methods,
|
||||
.tp_getset = _MemsinkObject_getsets,
|
||||
};
|
||||
|
||||
static PyModuleDef ustreamer_Module = {
|
||||
static PyModuleDef _Module = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
.m_name = "ustreamer",
|
||||
.m_size = -1,
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction
|
||||
PyObject *module = PyModule_Create(&ustreamer_Module);
|
||||
PyObject *module = PyModule_Create(&_Module);
|
||||
if (module == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (PyType_Ready(&MemsinkType) < 0) {
|
||||
if (PyType_Ready(&_MemsinkType) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_INCREF(&MemsinkType);
|
||||
Py_INCREF(&_MemsinkType);
|
||||
|
||||
if (PyModule_AddObject(module, "Memsink", (PyObject *)&MemsinkType) < 0) {
|
||||
if (PyModule_AddObject(module, "Memsink", (PyObject *)&_MemsinkType) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
#undef FRAME
|
||||
#undef MEM
|
||||
|
||||
@ -23,17 +23,17 @@
|
||||
#include "file.h"
|
||||
|
||||
|
||||
output_file_s *output_file_init(const char *path, bool json) {
|
||||
output_file_s *output;
|
||||
A_CALLOC(output, 1);
|
||||
us_output_file_s *us_output_file_init(const char *path, bool json) {
|
||||
us_output_file_s *output;
|
||||
US_CALLOC(output, 1);
|
||||
|
||||
if (!strcmp(path, "-")) {
|
||||
LOG_INFO("Using output: <stdout>");
|
||||
US_LOG_INFO("Using output: <stdout>");
|
||||
output->fp = stdout;
|
||||
} else {
|
||||
LOG_INFO("Using output: %s", path);
|
||||
US_LOG_INFO("Using output: %s", path);
|
||||
if ((output->fp = fopen(path, "wb")) == NULL) {
|
||||
LOG_PERROR("Can't open output file");
|
||||
US_LOG_PERROR("Can't open output file");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
@ -42,14 +42,14 @@ output_file_s *output_file_init(const char *path, bool json) {
|
||||
return output;
|
||||
|
||||
error:
|
||||
output_file_destroy(output);
|
||||
us_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;
|
||||
void us_output_file_write(void *v_output, const us_frame_s *frame) {
|
||||
us_output_file_s *output = (us_output_file_s *)v_output;
|
||||
if (output->json) {
|
||||
base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
|
||||
us_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,"
|
||||
@ -65,14 +65,14 @@ void output_file_write(void *v_output, const frame_s *frame) {
|
||||
fflush(output->fp);
|
||||
}
|
||||
|
||||
void output_file_destroy(void *v_output) {
|
||||
output_file_s *output = (output_file_s *)v_output;
|
||||
void us_output_file_destroy(void *v_output) {
|
||||
us_output_file_s *output = (us_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");
|
||||
US_LOG_PERROR("Can't close output file");
|
||||
}
|
||||
}
|
||||
free(output);
|
||||
|
||||
@ -41,9 +41,9 @@ typedef struct {
|
||||
FILE *fp;
|
||||
char *base64_data;
|
||||
size_t base64_allocated;
|
||||
} output_file_s;
|
||||
} us_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);
|
||||
us_output_file_s *us_output_file_init(const char *path, bool json);
|
||||
void us_output_file_write(void *v_output, const us_frame_s *frame);
|
||||
void us_output_file_destroy(void *v_output);
|
||||
|
||||
@ -82,12 +82,12 @@ static const struct option _LONG_OPTS[] = {
|
||||
};
|
||||
|
||||
|
||||
volatile bool global_stop = false;
|
||||
volatile bool _g_stop = false;
|
||||
|
||||
|
||||
typedef struct {
|
||||
void *v_output;
|
||||
void (*write)(void *v_output, const frame_s *frame);
|
||||
void (*write)(void *v_output, const us_frame_s *frame);
|
||||
void (*destroy)(void *v_output);
|
||||
} _output_context_s;
|
||||
|
||||
@ -104,8 +104,8 @@ static void _help(FILE *fp);
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
LOGGING_INIT;
|
||||
A_THREAD_RENAME("main");
|
||||
US_LOGGING_INIT;
|
||||
US_THREAD_RENAME("main");
|
||||
|
||||
char *sink_name = NULL;
|
||||
unsigned sink_timeout = 1;
|
||||
@ -140,7 +140,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
char short_opts[128];
|
||||
build_short_options(_LONG_OPTS, short_opts, 128);
|
||||
us_build_short_options(_LONG_OPTS, short_opts, 128);
|
||||
|
||||
for (int ch; (ch = getopt_long(argc, argv, short_opts, _LONG_OPTS, NULL)) >= 0;) {
|
||||
switch (ch) {
|
||||
@ -151,15 +151,15 @@ int main(int argc, char *argv[]) {
|
||||
case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0);
|
||||
case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60);
|
||||
|
||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
|
||||
case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF);
|
||||
case _O_VERBOSE: OPT_SET(us_log_level, LOG_LEVEL_VERBOSE);
|
||||
case _O_DEBUG: OPT_SET(us_log_level, LOG_LEVEL_DEBUG);
|
||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
|
||||
case _O_PERF: OPT_SET(us_log_level, US_LOG_LEVEL_PERF);
|
||||
case _O_VERBOSE: OPT_SET(us_log_level, US_LOG_LEVEL_VERBOSE);
|
||||
case _O_DEBUG: OPT_SET(us_log_level, US_LOG_LEVEL_DEBUG);
|
||||
case _O_FORCE_LOG_COLORS: OPT_SET(us_log_colored, true);
|
||||
case _O_NO_LOG_COLORS: OPT_SET(us_log_colored, false);
|
||||
|
||||
case _O_HELP: _help(stdout); return 0;
|
||||
case _O_VERSION: puts(VERSION); return 0;
|
||||
case _O_VERSION: puts(US_VERSION); return 0;
|
||||
|
||||
case 0: break;
|
||||
default: return 1;
|
||||
@ -178,11 +178,11 @@ int main(int argc, char *argv[]) {
|
||||
_output_context_s ctx = {0};
|
||||
|
||||
if (output_path && output_path[0] != '\0') {
|
||||
if ((ctx.v_output = (void *)output_file_init(output_path, output_json)) == NULL) {
|
||||
if ((ctx.v_output = (void *)us_output_file_init(output_path, output_json)) == NULL) {
|
||||
return 1;
|
||||
}
|
||||
ctx.write = output_file_write;
|
||||
ctx.destroy = output_file_destroy;
|
||||
ctx.write = us_output_file_write;
|
||||
ctx.destroy = us_output_file_destroy;
|
||||
}
|
||||
|
||||
_install_signal_handlers();
|
||||
@ -196,12 +196,12 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
static void _signal_handler(int signum) {
|
||||
switch (signum) {
|
||||
case SIGTERM: LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break;
|
||||
case SIGINT: LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break;
|
||||
case SIGPIPE: LOG_INFO_NOLOCK("===== Stopping by SIGPIPE ====="); break;
|
||||
default: LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
|
||||
case SIGTERM: US_LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break;
|
||||
case SIGINT: US_LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break;
|
||||
case SIGPIPE: US_LOG_INFO_NOLOCK("===== Stopping by SIGPIPE ====="); break;
|
||||
default: US_LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
|
||||
}
|
||||
global_stop = true;
|
||||
_g_stop = true;
|
||||
}
|
||||
|
||||
static void _install_signal_handlers(void) {
|
||||
@ -213,13 +213,13 @@ static void _install_signal_handlers(void) {
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGPIPE));
|
||||
|
||||
LOG_DEBUG("Installing SIGINT handler ...");
|
||||
US_LOG_DEBUG("Installing SIGINT handler ...");
|
||||
assert(!sigaction(SIGINT, &sig_act, NULL));
|
||||
|
||||
LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
assert(!sigaction(SIGTERM, &sig_act, NULL));
|
||||
|
||||
LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
assert(!sigaction(SIGPIPE, &sig_act, NULL));
|
||||
}
|
||||
|
||||
@ -234,10 +234,10 @@ static int _dump_sink(
|
||||
|
||||
useconds_t interval_us = interval * 1000000;
|
||||
|
||||
frame_s *frame = frame_init();
|
||||
memsink_s *sink = NULL;
|
||||
us_frame_s *frame = us_frame_init();
|
||||
us_memsink_s *sink = NULL;
|
||||
|
||||
if ((sink = memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
|
||||
if ((sink = us_memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
@ -247,28 +247,28 @@ static int _dump_sink(
|
||||
|
||||
long double last_ts = 0;
|
||||
|
||||
while (!global_stop) {
|
||||
int error = memsink_client_get(sink, frame);
|
||||
while (!_g_stop) {
|
||||
int error = us_memsink_client_get(sink, frame);
|
||||
if (error == 0) {
|
||||
const long double now = get_now_monotonic();
|
||||
const long long now_second = floor_ms(now);
|
||||
const long double now = us_get_now_monotonic();
|
||||
const long long now_second = us_floor_ms(now);
|
||||
|
||||
char fourcc_str[8];
|
||||
LOG_VERBOSE("Frame: size=%zu, res=%ux%u, fourcc=%s, stride=%u, online=%d, key=%d, latency=%.3Lf, diff=%.3Lf",
|
||||
US_LOG_VERBOSE("Frame: size=%zu, res=%ux%u, fourcc=%s, stride=%u, online=%d, key=%d, latency=%.3Lf, diff=%.3Lf",
|
||||
frame->used, frame->width, frame->height,
|
||||
fourcc_to_string(frame->format, fourcc_str, 8),
|
||||
us_fourcc_to_string(frame->format, fourcc_str, 8),
|
||||
frame->stride, frame->online, frame->key,
|
||||
now - frame->grab_ts, (last_ts ? now - last_ts : 0));
|
||||
last_ts = now;
|
||||
|
||||
LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
|
||||
US_LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
|
||||
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts);
|
||||
|
||||
if (now_second != fps_second) {
|
||||
fps = fps_accum;
|
||||
fps_accum = 0;
|
||||
fps_second = now_second;
|
||||
LOG_PERF_FPS("A new second has come; captured_fps=%u", fps);
|
||||
US_LOG_PERF_FPS("A new second has come; captured_fps=%u", fps);
|
||||
}
|
||||
fps_accum += 1;
|
||||
|
||||
@ -301,11 +301,11 @@ static int _dump_sink(
|
||||
|
||||
ok:
|
||||
if (sink) {
|
||||
memsink_destroy(sink);
|
||||
us_memsink_destroy(sink);
|
||||
}
|
||||
frame_destroy(frame);
|
||||
us_frame_destroy(frame);
|
||||
|
||||
LOG_INFO("Bye-bye");
|
||||
US_LOG_INFO("Bye-bye");
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -313,7 +313,7 @@ static void _help(FILE *fp) {
|
||||
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
|
||||
SAY("\nuStreamer-dump - Dump uStreamer's memory sink to file");
|
||||
SAY("═════════════════════════════════════════════════════");
|
||||
SAY("Version: %s; license: GPLv3", VERSION);
|
||||
SAY("Version: %s; license: GPLv3", US_VERSION);
|
||||
SAY("Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
SAY("Example:");
|
||||
SAY("════════");
|
||||
|
||||
@ -37,11 +37,11 @@ static const char _ENCODING_TABLE[] = {
|
||||
static const unsigned _MOD_TABLE[] = {0, 2, 1};
|
||||
|
||||
|
||||
void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) {
|
||||
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) {
|
||||
const size_t encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0'
|
||||
|
||||
if (*encoded == NULL || (allocated && *allocated < encoded_size)) {
|
||||
A_REALLOC(*encoded, encoded_size);
|
||||
US_REALLOC(*encoded, encoded_size);
|
||||
if (allocated) {
|
||||
*allocated = encoded_size;
|
||||
}
|
||||
|
||||
@ -31,4 +31,4 @@
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated);
|
||||
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated);
|
||||
|
||||
@ -22,11 +22,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VERSION_MAJOR 5
|
||||
#define VERSION_MINOR 16
|
||||
#define US_VERSION_MAJOR 5
|
||||
#define US_VERSION_MINOR 16
|
||||
|
||||
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)
|
||||
#define VERSION MAKE_VERSION1(VERSION_MAJOR, VERSION_MINOR)
|
||||
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||
#define US_VERSION US_MAKE_VERSION1(US_VERSION_MAJOR, US_VERSION_MINOR)
|
||||
|
||||
#define VERSION_U ((unsigned)(VERSION_MAJOR * 1000 + VERSION_MINOR))
|
||||
#define US_VERSION_U ((unsigned)(US_VERSION_MAJOR * 1000 + US_VERSION_MINOR))
|
||||
|
||||
@ -23,55 +23,55 @@
|
||||
#include "frame.h"
|
||||
|
||||
|
||||
frame_s *frame_init(void) {
|
||||
frame_s *frame;
|
||||
A_CALLOC(frame, 1);
|
||||
frame_realloc_data(frame, 512 * 1024);
|
||||
us_frame_s *us_frame_init(void) {
|
||||
us_frame_s *frame;
|
||||
US_CALLOC(frame, 1);
|
||||
us_frame_realloc_data(frame, 512 * 1024);
|
||||
frame->dma_fd = -1;
|
||||
return frame;
|
||||
}
|
||||
|
||||
void frame_destroy(frame_s *frame) {
|
||||
void us_frame_destroy(us_frame_s *frame) {
|
||||
if (frame->data) {
|
||||
free(frame->data);
|
||||
}
|
||||
free(frame);
|
||||
}
|
||||
|
||||
void frame_realloc_data(frame_s *frame, size_t size) {
|
||||
void us_frame_realloc_data(us_frame_s *frame, size_t size) {
|
||||
if (frame->allocated < size) {
|
||||
A_REALLOC(frame->data, size);
|
||||
US_REALLOC(frame->data, size);
|
||||
frame->allocated = size;
|
||||
}
|
||||
}
|
||||
|
||||
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size) {
|
||||
frame_realloc_data(frame, size);
|
||||
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size) {
|
||||
us_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) {
|
||||
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size) {
|
||||
size_t new_used = frame->used + size;
|
||||
frame_realloc_data(frame, new_used);
|
||||
us_frame_realloc_data(frame, new_used);
|
||||
memcpy(frame->data + frame->used, data, size);
|
||||
frame->used = new_used;
|
||||
}
|
||||
|
||||
void frame_copy(const frame_s *src, frame_s *dest) {
|
||||
frame_set_data(dest, src->data, src->used);
|
||||
FRAME_COPY_META(src, dest);
|
||||
void us_frame_copy(const us_frame_s *src, us_frame_s *dest) {
|
||||
us_frame_set_data(dest, src->data, src->used);
|
||||
US_FRAME_COPY_META(src, dest);
|
||||
}
|
||||
|
||||
bool frame_compare(const frame_s *a, const frame_s *b) {
|
||||
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b) {
|
||||
return (
|
||||
a->allocated && b->allocated
|
||||
&& FRAME_COMPARE_META_USED_NOTS(a, b)
|
||||
&& US_FRAME_COMPARE_META_USED_NOTS(a, b)
|
||||
&& !memcmp(a->data, b->data, b->used)
|
||||
);
|
||||
}
|
||||
|
||||
unsigned frame_get_padding(const frame_s *frame) {
|
||||
unsigned us_frame_get_padding(const us_frame_s *frame) {
|
||||
unsigned bytes_per_pixel = 0;
|
||||
switch (frame->format) {
|
||||
case V4L2_PIX_FMT_YUYV:
|
||||
@ -89,7 +89,7 @@ unsigned frame_get_padding(const frame_s *frame) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *fourcc_to_string(unsigned format, char *buf, size_t size) {
|
||||
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size) {
|
||||
assert(size >= 8);
|
||||
buf[0] = format & 0x7F;
|
||||
buf[1] = (format >> 8) & 0x7F;
|
||||
|
||||
@ -35,84 +35,84 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t *data;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
int dma_fd;
|
||||
uint8_t *data;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
int dma_fd;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
// Stride is a bytesperline in V4L2
|
||||
// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/pixfmt-v4l2.html
|
||||
// https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd
|
||||
|
||||
bool online;
|
||||
bool key;
|
||||
bool online;
|
||||
bool key;
|
||||
|
||||
long double grab_ts;
|
||||
long double encode_begin_ts;
|
||||
long double encode_end_ts;
|
||||
} frame_s;
|
||||
} us_frame_s;
|
||||
|
||||
|
||||
#define FRAME_COPY_META(_src, _dest) { \
|
||||
_dest->width = _src->width; \
|
||||
_dest->height = _src->height; \
|
||||
_dest->format = _src->format; \
|
||||
_dest->stride = _src->stride; \
|
||||
_dest->online = _src->online; \
|
||||
_dest->key = _src->key; \
|
||||
_dest->grab_ts = _src->grab_ts; \
|
||||
_dest->encode_begin_ts = _src->encode_begin_ts; \
|
||||
_dest->encode_end_ts = _src->encode_end_ts; \
|
||||
#define US_FRAME_COPY_META(x_src, x_dest) { \
|
||||
x_dest->width = x_src->width; \
|
||||
x_dest->height = x_src->height; \
|
||||
x_dest->format = x_src->format; \
|
||||
x_dest->stride = x_src->stride; \
|
||||
x_dest->online = x_src->online; \
|
||||
x_dest->key = x_src->key; \
|
||||
x_dest->grab_ts = x_src->grab_ts; \
|
||||
x_dest->encode_begin_ts = x_src->encode_begin_ts; \
|
||||
x_dest->encode_end_ts = x_src->encode_end_ts; \
|
||||
}
|
||||
|
||||
static inline void frame_copy_meta(const frame_s *src, frame_s *dest) {
|
||||
FRAME_COPY_META(src, dest);
|
||||
static inline void us_frame_copy_meta(const us_frame_s *src, us_frame_s *dest) {
|
||||
US_FRAME_COPY_META(src, dest);
|
||||
}
|
||||
|
||||
#define FRAME_COMPARE_META_USED_NOTS(_a, _b) ( \
|
||||
_a->used == _b->used \
|
||||
&& _a->width == _b->width \
|
||||
&& _a->height == _b->height \
|
||||
&& _a->format == _b->format \
|
||||
&& _a->stride == _b->stride \
|
||||
&& _a->online == _b->online \
|
||||
&& _a->key == _b->key \
|
||||
#define US_FRAME_COMPARE_META_USED_NOTS(x_a, x_b) ( \
|
||||
x_a->used == x_b->used \
|
||||
&& x_a->width == x_b->width \
|
||||
&& x_a->height == x_b->height \
|
||||
&& x_a->format == x_b->format \
|
||||
&& x_a->stride == x_b->stride \
|
||||
&& x_a->online == x_b->online \
|
||||
&& x_a->key == x_b->key \
|
||||
)
|
||||
|
||||
|
||||
static inline void frame_encoding_begin(const frame_s *src, frame_s *dest, unsigned format) {
|
||||
static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, unsigned format) {
|
||||
assert(src->used > 0);
|
||||
frame_copy_meta(src, dest);
|
||||
dest->encode_begin_ts = get_now_monotonic();
|
||||
us_frame_copy_meta(src, dest);
|
||||
dest->encode_begin_ts = us_get_now_monotonic();
|
||||
dest->format = format;
|
||||
dest->stride = 0;
|
||||
dest->used = 0;
|
||||
}
|
||||
|
||||
static inline void frame_encoding_end(frame_s *dest) {
|
||||
static inline void us_frame_encoding_end(us_frame_s *dest) {
|
||||
assert(dest->used > 0);
|
||||
dest->encode_end_ts = get_now_monotonic();
|
||||
dest->encode_end_ts = us_get_now_monotonic();
|
||||
}
|
||||
|
||||
|
||||
frame_s *frame_init(void);
|
||||
void frame_destroy(frame_s *frame);
|
||||
us_frame_s *us_frame_init(void);
|
||||
void us_frame_destroy(us_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 us_frame_realloc_data(us_frame_s *frame, size_t size);
|
||||
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size);
|
||||
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size);
|
||||
|
||||
void frame_copy(const frame_s *src, frame_s *dest);
|
||||
bool frame_compare(const frame_s *a, const frame_s *b);
|
||||
void us_frame_copy(const us_frame_s *src, us_frame_s *dest);
|
||||
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b);
|
||||
|
||||
unsigned frame_get_padding(const frame_s *frame);
|
||||
unsigned us_frame_get_padding(const us_frame_s *frame);
|
||||
|
||||
const char *fourcc_to_string(unsigned format, char *buf, size_t size);
|
||||
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size);
|
||||
|
||||
static inline bool is_jpeg(unsigned format) {
|
||||
static inline bool us_is_jpeg(unsigned format) {
|
||||
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
|
||||
}
|
||||
|
||||
@ -25,47 +25,47 @@
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
#define LIST_STRUCT(...) \
|
||||
#define US_LIST_STRUCT(...) \
|
||||
__VA_ARGS__ *prev; \
|
||||
__VA_ARGS__ *next;
|
||||
|
||||
#define LIST_ITERATE(_first, _item, ...) { \
|
||||
for (__typeof__(_first) _item = _first; _item;) { \
|
||||
__typeof__(_first) _next = _item->next; \
|
||||
#define US_LIST_ITERATE(x_first, x_item, ...) { \
|
||||
for (__typeof__(x_first) x_item = x_first; x_item;) { \
|
||||
__typeof__(x_first) m_next = x_item->next; \
|
||||
__VA_ARGS__ \
|
||||
_item = _next; \
|
||||
x_item = m_next; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LIST_APPEND(_first, _item) { \
|
||||
if (_first == NULL) { \
|
||||
_first = _item; \
|
||||
#define US_LIST_APPEND(x_first, x_item) { \
|
||||
if (x_first == NULL) { \
|
||||
x_first = x_item; \
|
||||
} else { \
|
||||
__typeof__(_first) _last = _first; \
|
||||
for (; _last->next; _last = _last->next); \
|
||||
_item->prev = _last; \
|
||||
_last->next = _item; \
|
||||
__typeof__(x_first) m_last = x_first; \
|
||||
for (; m_last->next; m_last = m_last->next); \
|
||||
x_item->prev = m_last; \
|
||||
m_last->next = x_item; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LIST_APPEND_C(_first, _item, _count) { \
|
||||
LIST_APPEND(_first, _item); \
|
||||
++(_count); \
|
||||
#define US_LIST_APPEND_C(x_first, x_item, x_count) { \
|
||||
US_LIST_APPEND(x_first, x_item); \
|
||||
++(x_count); \
|
||||
}
|
||||
|
||||
#define LIST_REMOVE(_first, _item) { \
|
||||
if (_item->prev == NULL) { \
|
||||
_first = _item->next; \
|
||||
#define US_LIST_REMOVE(x_first, x_item) { \
|
||||
if (x_item->prev == NULL) { \
|
||||
x_first = x_item->next; \
|
||||
} else { \
|
||||
_item->prev->next = _item->next; \
|
||||
x_item->prev->next = x_item->next; \
|
||||
} \
|
||||
if (_item->next != NULL) { \
|
||||
_item->next->prev = _item->prev; \
|
||||
if (x_item->next != NULL) { \
|
||||
x_item->next->prev = x_item->prev; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LIST_REMOVE_C(_first, _item, _count) { \
|
||||
LIST_REMOVE(_first, _item); \
|
||||
assert((_count) >= 1); \
|
||||
--(_count); \
|
||||
#define US_LIST_REMOVE_C(x_first, x_item, x_count) { \
|
||||
US_LIST_REMOVE(x_first, x_item); \
|
||||
assert((x_count) >= 1); \
|
||||
--(x_count); \
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
enum log_level_t us_log_level;
|
||||
enum us_log_level_t us_log_level;
|
||||
|
||||
bool us_log_colored;
|
||||
|
||||
|
||||
@ -37,126 +37,126 @@
|
||||
#include "threading.h"
|
||||
|
||||
|
||||
enum log_level_t {
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_PERF,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
LOG_LEVEL_DEBUG,
|
||||
enum us_log_level_t {
|
||||
US_LOG_LEVEL_INFO,
|
||||
US_LOG_LEVEL_PERF,
|
||||
US_LOG_LEVEL_VERBOSE,
|
||||
US_LOG_LEVEL_DEBUG,
|
||||
};
|
||||
|
||||
|
||||
extern enum log_level_t us_log_level;
|
||||
extern enum us_log_level_t us_log_level;
|
||||
|
||||
extern bool us_log_colored;
|
||||
|
||||
extern pthread_mutex_t us_log_mutex;
|
||||
|
||||
|
||||
#define LOGGING_INIT { \
|
||||
us_log_level = LOG_LEVEL_INFO; \
|
||||
#define US_LOGGING_INIT { \
|
||||
us_log_level = US_LOG_LEVEL_INFO; \
|
||||
us_log_colored = isatty(2); \
|
||||
A_MUTEX_INIT(&us_log_mutex); \
|
||||
US_MUTEX_INIT(&us_log_mutex); \
|
||||
}
|
||||
|
||||
#define LOGGING_DESTROY A_MUTEX_DESTROY(&us_log_mutex)
|
||||
#define US_LOGGING_DESTROY US_MUTEX_DESTROY(&us_log_mutex)
|
||||
|
||||
#define LOGGING_LOCK A_MUTEX_LOCK(&us_log_mutex)
|
||||
#define LOGGING_UNLOCK A_MUTEX_UNLOCK(&us_log_mutex)
|
||||
#define US_LOGGING_LOCK US_MUTEX_LOCK(&us_log_mutex)
|
||||
#define US_LOGGING_UNLOCK US_MUTEX_UNLOCK(&us_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 US_COLOR_GRAY "\x1b[30;1m"
|
||||
#define US_COLOR_RED "\x1b[31;1m"
|
||||
#define US_COLOR_GREEN "\x1b[32;1m"
|
||||
#define US_COLOR_YELLOW "\x1b[33;1m"
|
||||
#define US_COLOR_BLUE "\x1b[34;1m"
|
||||
#define US_COLOR_CYAN "\x1b[36;1m"
|
||||
#define US_COLOR_RESET "\x1b[0m"
|
||||
|
||||
|
||||
#define SEP_INFO(_ch) { \
|
||||
LOGGING_LOCK; \
|
||||
for (int _i = 0; _i < 80; ++_i) { \
|
||||
fputc(_ch, stderr); \
|
||||
#define US_SEP_INFO(x_ch) { \
|
||||
US_LOGGING_LOCK; \
|
||||
for (int m_count = 0; m_count < 80; ++m_count) { \
|
||||
fputc((x_ch), stderr); \
|
||||
} \
|
||||
fputc('\n', stderr); \
|
||||
fflush(stderr); \
|
||||
LOGGING_UNLOCK; \
|
||||
US_LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
#define SEP_DEBUG(_ch) { \
|
||||
if (us_log_level >= LOG_LEVEL_DEBUG) { \
|
||||
SEP_INFO(_ch); \
|
||||
#define US_SEP_DEBUG(x_ch) { \
|
||||
if (us_log_level >= US_LOG_LEVEL_DEBUG) { \
|
||||
US_SEP_INFO(x_ch); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \
|
||||
char _tname_buf[MAX_THREAD_NAME] = {0}; \
|
||||
thread_get_name(_tname_buf); \
|
||||
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
|
||||
char m_tname_buf[US_MAX_THREAD_NAME] = {0}; \
|
||||
us_thread_get_name(m_tname_buf); \
|
||||
if (us_log_colored) { \
|
||||
fprintf(stderr, COLOR_GRAY "-- " _label_color _label COLOR_GRAY \
|
||||
" [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
|
||||
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
|
||||
fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \
|
||||
" [%.03Lf %9s]" " -- " US_COLOR_RESET x_msg_color x_msg US_COLOR_RESET, \
|
||||
us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
|
||||
} else { \
|
||||
fprintf(stderr, "-- " _label " [%.03Lf %9s] -- " _msg, \
|
||||
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
|
||||
fprintf(stderr, "-- " x_label " [%.03Lf %9s] -- " x_msg, \
|
||||
us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
|
||||
} \
|
||||
fputc('\n', stderr); \
|
||||
fflush(stderr); \
|
||||
}
|
||||
|
||||
#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 US_LOG_PRINTF(x_label_color, x_label, x_msg_color, x_msg, ...) { \
|
||||
US_LOGGING_LOCK; \
|
||||
US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ##__VA_ARGS__); \
|
||||
US_LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
#define LOG_ERROR(_msg, ...) { \
|
||||
LOG_PRINTF(COLOR_RED, "ERROR", COLOR_RED, _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_ERROR(x_msg, ...) { \
|
||||
US_LOG_PRINTF(US_COLOR_RED, "ERROR", US_COLOR_RED, x_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 US_LOG_PERROR(x_msg, ...) { \
|
||||
char m_perror_buf[1024] = {0}; \
|
||||
char *m_perror_ptr = us_errno_to_string(errno, m_perror_buf, 1024); \
|
||||
US_LOG_ERROR(x_msg ": %s", ##__VA_ARGS__, m_perror_ptr); \
|
||||
}
|
||||
|
||||
#define LOG_INFO(_msg, ...) { \
|
||||
LOG_PRINTF(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_INFO(x_msg, ...) { \
|
||||
US_LOG_PRINTF(US_COLOR_GREEN, "INFO ", "", x_msg, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LOG_INFO_NOLOCK(_msg, ...) { \
|
||||
LOG_PRINTF_NOLOCK(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_INFO_NOLOCK(x_msg, ...) { \
|
||||
US_LOG_PRINTF_NOLOCK(US_COLOR_GREEN, "INFO ", "", x_msg, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LOG_PERF(_msg, ...) { \
|
||||
if (us_log_level >= LOG_LEVEL_PERF) { \
|
||||
LOG_PRINTF(COLOR_CYAN, "PERF ", COLOR_CYAN, _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_PERF(x_msg, ...) { \
|
||||
if (us_log_level >= US_LOG_LEVEL_PERF) { \
|
||||
US_LOG_PRINTF(US_COLOR_CYAN, "PERF ", US_COLOR_CYAN, x_msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_PERF_FPS(_msg, ...) { \
|
||||
if (us_log_level >= LOG_LEVEL_PERF) { \
|
||||
LOG_PRINTF(COLOR_YELLOW, "PERF ", COLOR_YELLOW, _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_PERF_FPS(x_msg, ...) { \
|
||||
if (us_log_level >= US_LOG_LEVEL_PERF) { \
|
||||
US_LOG_PRINTF(US_COLOR_YELLOW, "PERF ", US_COLOR_YELLOW, x_msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_VERBOSE(_msg, ...) { \
|
||||
if (us_log_level >= LOG_LEVEL_VERBOSE) { \
|
||||
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_VERBOSE(x_msg, ...) { \
|
||||
if (us_log_level >= US_LOG_LEVEL_VERBOSE) { \
|
||||
US_LOG_PRINTF(US_COLOR_BLUE, "VERB ", US_COLOR_BLUE, x_msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_VERBOSE_PERROR(_msg, ...) { \
|
||||
if (us_log_level >= LOG_LEVEL_VERBOSE) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
|
||||
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \
|
||||
#define US_LOG_VERBOSE_PERROR(x_msg, ...) { \
|
||||
if (us_log_level >= US_LOG_LEVEL_VERBOSE) { \
|
||||
char m_perror_buf[1024] = {0}; \
|
||||
char *m_perror_ptr = us_errno_to_string(errno, m_perror_buf, 1023); \
|
||||
US_LOG_PRINTF(US_COLOR_BLUE, "VERB ", US_COLOR_BLUE, x_msg ": %s", ##__VA_ARGS__, m_perror_ptr); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_DEBUG(_msg, ...) { \
|
||||
if (us_log_level >= LOG_LEVEL_DEBUG) { \
|
||||
LOG_PRINTF(COLOR_GRAY, "DEBUG", COLOR_GRAY, _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_DEBUG(x_msg, ...) { \
|
||||
if (us_log_level >= US_LOG_LEVEL_DEBUG) { \
|
||||
US_LOG_PRINTF(US_COLOR_GRAY, "DEBUG", US_COLOR_GRAY, x_msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
@ -23,12 +23,12 @@
|
||||
#include "memsink.h"
|
||||
|
||||
|
||||
memsink_s *memsink_init(
|
||||
us_memsink_s *us_memsink_init(
|
||||
const char *name, const char *obj, bool server,
|
||||
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) {
|
||||
|
||||
memsink_s *sink;
|
||||
A_CALLOC(sink, 1);
|
||||
us_memsink_s *sink;
|
||||
US_CALLOC(sink, 1);
|
||||
sink->name = name;
|
||||
sink->obj = obj;
|
||||
sink->server = server;
|
||||
@ -39,54 +39,54 @@ memsink_s *memsink_init(
|
||||
sink->mem = MAP_FAILED;
|
||||
atomic_init(&sink->has_clients, false);
|
||||
|
||||
LOG_INFO("Using %s-sink: %s", name, obj);
|
||||
US_LOG_INFO("Using %s-sink: %s", name, obj);
|
||||
|
||||
mode_t mask = umask(0);
|
||||
sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode);
|
||||
umask(mask);
|
||||
if (sink->fd == -1) {
|
||||
umask(mask);
|
||||
LOG_PERROR("%s-sink: Can't open shared memory", name);
|
||||
US_LOG_PERROR("%s-sink: Can't open shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (sink->server && ftruncate(sink->fd, sizeof(memsink_shared_s)) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't truncate shared memory", name);
|
||||
if (sink->server && ftruncate(sink->fd, sizeof(us_memsink_shared_s)) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't truncate shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((sink->mem = memsink_shared_map(sink->fd)) == NULL) {
|
||||
LOG_PERROR("%s-sink: Can't mmap shared memory", name);
|
||||
if ((sink->mem = us_memsink_shared_map(sink->fd)) == NULL) {
|
||||
US_LOG_PERROR("%s-sink: Can't mmap shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return sink;
|
||||
|
||||
error:
|
||||
memsink_destroy(sink);
|
||||
us_memsink_destroy(sink);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void memsink_destroy(memsink_s *sink) {
|
||||
void us_memsink_destroy(us_memsink_s *sink) {
|
||||
if (sink->mem != MAP_FAILED) {
|
||||
if (memsink_shared_unmap(sink->mem) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
|
||||
if (us_memsink_shared_unmap(sink->mem) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
|
||||
}
|
||||
}
|
||||
if (sink->fd >= 0) {
|
||||
if (close(sink->fd) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't close shared memory fd", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't close shared memory fd", sink->name);
|
||||
}
|
||||
if (sink->rm && shm_unlink(sink->obj) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
LOG_PERROR("%s-sink: Can't remove shared memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't remove shared memory", sink->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(sink);
|
||||
}
|
||||
|
||||
bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
||||
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
|
||||
// Return true (the need to write to memsink) on any of these conditions:
|
||||
// - EWOULDBLOCK - we have an active client;
|
||||
// - Incorrect magic or version - need to first write;
|
||||
@ -100,97 +100,97 @@ bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
||||
atomic_store(&sink->has_clients, true);
|
||||
return true;
|
||||
}
|
||||
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sink->mem->magic != MEMSINK_MAGIC || sink->mem->version != MEMSINK_VERSION) {
|
||||
if (sink->mem->magic != US_MEMSINK_MAGIC || sink->mem->version != US_MEMSINK_VERSION) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic());
|
||||
bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic());
|
||||
atomic_store(&sink->has_clients, has_clients);
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return false;
|
||||
}
|
||||
return (has_clients || !FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
|
||||
return (has_clients || !US_FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
|
||||
}
|
||||
|
||||
int memsink_server_put(memsink_s *sink, const frame_s *frame) {
|
||||
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame) {
|
||||
assert(sink->server);
|
||||
|
||||
const long double now = get_now_monotonic();
|
||||
const long double now = us_get_now_monotonic();
|
||||
|
||||
if (frame->used > MEMSINK_MAX_DATA) {
|
||||
LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)",
|
||||
sink->name, frame->used, MEMSINK_MAX_DATA);
|
||||
if (frame->used > US_MEMSINK_MAX_DATA) {
|
||||
US_LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)",
|
||||
sink->name, frame->used, US_MEMSINK_MAX_DATA);
|
||||
return 0; // -2
|
||||
}
|
||||
|
||||
if (flock_timedwait_monotonic(sink->fd, 1) == 0) {
|
||||
LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
|
||||
if (us_flock_timedwait_monotonic(sink->fd, 1) == 0) {
|
||||
US_LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
|
||||
|
||||
sink->last_id = get_now_id();
|
||||
sink->last_id = us_get_now_id();
|
||||
sink->mem->id = sink->last_id;
|
||||
|
||||
memcpy(sink->mem->data, frame->data, frame->used);
|
||||
sink->mem->used = frame->used;
|
||||
FRAME_COPY_META(frame, sink->mem);
|
||||
US_FRAME_COPY_META(frame, sink->mem);
|
||||
|
||||
sink->mem->magic = MEMSINK_MAGIC;
|
||||
sink->mem->version = MEMSINK_VERSION;
|
||||
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic()));
|
||||
sink->mem->magic = US_MEMSINK_MAGIC;
|
||||
sink->mem->version = US_MEMSINK_VERSION;
|
||||
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic()));
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf",
|
||||
sink->name, get_now_monotonic() - now);
|
||||
US_LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf",
|
||||
sink->name, us_get_now_monotonic() - now);
|
||||
|
||||
} else if (errno == EWOULDBLOCK) {
|
||||
LOG_VERBOSE("%s-sink: ===== Shared memory is busy now; frame skipped", sink->name);
|
||||
US_LOG_VERBOSE("%s-sink: ===== Shared memory is busy now; frame skipped", sink->name);
|
||||
|
||||
} else {
|
||||
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int memsink_client_get(memsink_s *sink, frame_s *frame) { // cppcheck-suppress unusedFunction
|
||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame) { // cppcheck-suppress unusedFunction
|
||||
assert(!sink->server); // Client only
|
||||
|
||||
if (flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
|
||||
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
|
||||
if (errno == EWOULDBLOCK) {
|
||||
return -2;
|
||||
}
|
||||
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int retval = -2; // Not updated
|
||||
if (sink->mem->magic == MEMSINK_MAGIC) {
|
||||
if (sink->mem->version != MEMSINK_VERSION) {
|
||||
LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
|
||||
sink->name, sink->mem->version, MEMSINK_VERSION);
|
||||
if (sink->mem->magic == US_MEMSINK_MAGIC) {
|
||||
if (sink->mem->version != US_MEMSINK_VERSION) {
|
||||
US_LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
|
||||
sink->name, sink->mem->version, US_MEMSINK_VERSION);
|
||||
retval = -1;
|
||||
goto done;
|
||||
}
|
||||
if (sink->mem->id != sink->last_id) { // When updated
|
||||
sink->last_id = sink->mem->id;
|
||||
frame_set_data(frame, sink->mem->data, sink->mem->used);
|
||||
FRAME_COPY_META(sink->mem, frame);
|
||||
us_frame_set_data(frame, sink->mem->data, sink->mem->used);
|
||||
US_FRAME_COPY_META(sink->mem, frame);
|
||||
retval = 0;
|
||||
}
|
||||
sink->mem->last_client_ts = get_now_monotonic();
|
||||
sink->mem->last_client_ts = us_get_now_monotonic();
|
||||
}
|
||||
|
||||
done:
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
return retval;
|
||||
|
||||
@ -42,27 +42,27 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *obj;
|
||||
bool server;
|
||||
bool rm;
|
||||
unsigned client_ttl; // Only for server
|
||||
unsigned timeout;
|
||||
const char *name;
|
||||
const char *obj;
|
||||
bool server;
|
||||
bool rm;
|
||||
unsigned client_ttl; // Only for server
|
||||
unsigned timeout;
|
||||
|
||||
int fd;
|
||||
memsink_shared_s *mem;
|
||||
us_memsink_shared_s *mem;
|
||||
uint64_t last_id;
|
||||
atomic_bool has_clients; // Only for server
|
||||
} memsink_s;
|
||||
} us_memsink_s;
|
||||
|
||||
|
||||
memsink_s *memsink_init(
|
||||
us_memsink_s *us_memsink_init(
|
||||
const char *name, const char *obj, bool server,
|
||||
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout);
|
||||
|
||||
void memsink_destroy(memsink_s *sink);
|
||||
void us_memsink_destroy(us_memsink_s *sink);
|
||||
|
||||
bool memsink_server_check(memsink_s *sink, const frame_s *frame);
|
||||
int memsink_server_put(memsink_s *sink, const frame_s *frame);
|
||||
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame);
|
||||
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame);
|
||||
|
||||
int memsink_client_get(memsink_s *sink, frame_s *frame);
|
||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame);
|
||||
|
||||
@ -29,13 +29,13 @@
|
||||
#include <sys/mman.h>
|
||||
|
||||
|
||||
#define MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
||||
#define MEMSINK_VERSION ((uint32_t)2)
|
||||
#define US_MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
||||
#define US_MEMSINK_VERSION ((uint32_t)2)
|
||||
|
||||
#ifndef CFG_MEMSINK_MAX_DATA
|
||||
# define CFG_MEMSINK_MAX_DATA 33554432
|
||||
#ifndef US_CFG_MEMSINK_MAX_DATA
|
||||
# define US_CFG_MEMSINK_MAX_DATA 33554432
|
||||
#endif
|
||||
#define MEMSINK_MAX_DATA ((size_t)(CFG_MEMSINK_MAX_DATA))
|
||||
#define US_MEMSINK_MAX_DATA ((size_t)(US_CFG_MEMSINK_MAX_DATA))
|
||||
|
||||
|
||||
typedef struct {
|
||||
@ -58,14 +58,14 @@ typedef struct {
|
||||
|
||||
long double last_client_ts;
|
||||
|
||||
uint8_t data[MEMSINK_MAX_DATA];
|
||||
} memsink_shared_s;
|
||||
uint8_t data[US_MEMSINK_MAX_DATA];
|
||||
} us_memsink_shared_s;
|
||||
|
||||
|
||||
INLINE memsink_shared_s *memsink_shared_map(int fd) {
|
||||
memsink_shared_s *mem = mmap(
|
||||
INLINE us_memsink_shared_s *us_memsink_shared_map(int fd) {
|
||||
us_memsink_shared_s *mem = mmap(
|
||||
NULL,
|
||||
sizeof(memsink_shared_s),
|
||||
sizeof(us_memsink_shared_s),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
fd,
|
||||
@ -78,7 +78,7 @@ INLINE memsink_shared_s *memsink_shared_map(int fd) {
|
||||
return mem;
|
||||
}
|
||||
|
||||
INLINE int memsink_shared_unmap(memsink_shared_s *mem) {
|
||||
INLINE int us_memsink_shared_unmap(us_memsink_shared_s *mem) {
|
||||
assert(mem != NULL);
|
||||
return munmap(mem, sizeof(memsink_shared_s));
|
||||
return munmap(mem, sizeof(us_memsink_shared_s));
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
#include "options.h"
|
||||
|
||||
|
||||
void build_short_options(const struct option opts[], char *short_opts, size_t size) {
|
||||
void us_build_short_options(const struct option opts[], char *short_opts, size_t size) {
|
||||
memset(short_opts, 0, size);
|
||||
for (unsigned short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) {
|
||||
assert(short_index < size - 3);
|
||||
|
||||
@ -30,4 +30,4 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
void build_short_options(const struct option opts[], char *short_opts, size_t size);
|
||||
void us_build_short_options(const struct option opts[], char *short_opts, size_t size);
|
||||
|
||||
@ -72,7 +72,7 @@ extern char **environ;
|
||||
|
||||
|
||||
#ifdef HAS_PDEATHSIG
|
||||
INLINE int process_track_parent_death(void) {
|
||||
INLINE int us_process_track_parent_death(void) {
|
||||
pid_t parent = getppid();
|
||||
int signum = SIGTERM;
|
||||
# if defined(__linux__)
|
||||
@ -83,12 +83,12 @@ INLINE int process_track_parent_death(void) {
|
||||
# error WTF?
|
||||
# endif
|
||||
if (retval < 0) {
|
||||
LOG_PERROR("Can't set to receive SIGTERM on parent process death");
|
||||
US_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);
|
||||
US_LOG_PERROR("The parent process %d is already dead", parent);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -99,21 +99,21 @@ INLINE int process_track_parent_death(void) {
|
||||
#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) {
|
||||
INLINE void us_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);
|
||||
US_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)
|
||||
US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
|
||||
}
|
||||
|
||||
strcat(cmdline, " ");
|
||||
@ -130,18 +130,18 @@ INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix)
|
||||
}
|
||||
#endif
|
||||
|
||||
INLINE void process_notify_parent(void) {
|
||||
INLINE void us_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);
|
||||
US_LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent);
|
||||
}
|
||||
}
|
||||
|
||||
INLINE void process_suicide(void) {
|
||||
INLINE void us_process_suicide(void) {
|
||||
pid_t pid = getpid();
|
||||
|
||||
if (kill(pid, SIGTERM) < 0) {
|
||||
LOG_PERROR("Can't send SIGTERM to own pid %d", pid);
|
||||
US_LOG_PERROR("Can't send SIGTERM to own pid %d", pid);
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,37 +41,37 @@
|
||||
|
||||
|
||||
#ifdef PTHREAD_MAX_NAMELEN_NP
|
||||
# define MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
|
||||
# define US_MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
|
||||
#else
|
||||
# define MAX_THREAD_NAME ((size_t)16)
|
||||
# define US_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))
|
||||
#define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create((x_tid), NULL, (x_func), (x_arg)))
|
||||
#define US_THREAD_JOIN(x_tid) assert(!pthread_join((x_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); \
|
||||
# define US_THREAD_RENAME(x_fmt, ...) { \
|
||||
char m_new_tname_buf[US_MAX_THREAD_NAME] = {0}; \
|
||||
assert(snprintf(m_new_tname_buf, US_MAX_THREAD_NAME, (x_fmt), ##__VA_ARGS__) > 0); \
|
||||
us_thread_set_name(m_new_tname_buf); \
|
||||
}
|
||||
#else
|
||||
# define A_THREAD_RENAME(_fmt, ...)
|
||||
# define US_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 US_MUTEX_INIT(x_mutex) assert(!pthread_mutex_init((x_mutex), NULL))
|
||||
#define US_MUTEX_DESTROY(x_mutex) assert(!pthread_mutex_destroy(x_mutex))
|
||||
#define US_MUTEX_LOCK(x_mutex) assert(!pthread_mutex_lock(x_mutex))
|
||||
#define US_MUTEX_UNLOCK(x_mutex) assert(!pthread_mutex_unlock(x_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)); }
|
||||
#define US_COND_INIT(x_cond) assert(!pthread_cond_init((x_cond), NULL))
|
||||
#define US_COND_DESTROY(x_cond) assert(!pthread_cond_destroy(x_cond))
|
||||
#define US_COND_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__))
|
||||
#define US_COND_WAIT_TRUE(x_var, x_cond, x_mutex) { while(!(x_var)) assert(!pthread_cond_wait((x_cond), (x_mutex))); }
|
||||
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
INLINE void thread_set_name(const char *name) {
|
||||
INLINE void us_thread_set_name(const char *name) {
|
||||
# if defined(__linux__)
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
# elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
||||
@ -79,26 +79,26 @@ INLINE void thread_set_name(const char *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
|
||||
# error us_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
|
||||
INLINE void us_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);
|
||||
retval = pthread_getname_np(pthread_self(), name, US_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);
|
||||
pthread_get_name_np(pthread_self(), name, US_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
|
||||
# error us_thread_get_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
|
||||
# endif
|
||||
if (retval < 0) {
|
||||
#endif
|
||||
@ -117,7 +117,7 @@ INLINE void thread_get_name(char *name) { // Always required for logging
|
||||
pid_t tid = 0; // Makes cppcheck happy
|
||||
# warning gettid() not implemented
|
||||
#endif
|
||||
assert(snprintf(name, MAX_THREAD_NAME, "tid=%d", tid) > 0);
|
||||
assert(snprintf(name, US_MAX_THREAD_NAME, "tid=%d", tid) > 0);
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
}
|
||||
|
||||
@ -53,37 +53,37 @@
|
||||
#define INLINE inline __attribute__((always_inline))
|
||||
#define UNUSED __attribute__((unused))
|
||||
|
||||
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
|
||||
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
|
||||
#define DELETE(_dest, _free) { if (_dest) { _free(_dest); } }
|
||||
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
|
||||
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))))
|
||||
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))))
|
||||
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); } }
|
||||
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
|
||||
|
||||
#define A_ASPRINTF(_dest, _fmt, ...) assert(asprintf(&(_dest), _fmt, ##__VA_ARGS__) >= 0)
|
||||
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) >= 0)
|
||||
|
||||
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
|
||||
#define US_ARRAY_LEN(x_array) (sizeof(x_array) / sizeof((x_array)[0]))
|
||||
|
||||
|
||||
INLINE const char *bool_to_string(bool flag) {
|
||||
INLINE const char *us_bool_to_string(bool flag) {
|
||||
return (flag ? "true" : "false");
|
||||
}
|
||||
|
||||
INLINE size_t align_size(size_t size, size_t to) {
|
||||
INLINE size_t us_align_size(size_t size, size_t to) {
|
||||
return ((size + (to - 1)) & ~(to - 1));
|
||||
}
|
||||
|
||||
INLINE unsigned min_u(unsigned a, unsigned b) {
|
||||
INLINE unsigned us_min_u(unsigned a, unsigned b) {
|
||||
return (a < b ? a : b);
|
||||
}
|
||||
|
||||
INLINE unsigned max_u(unsigned a, unsigned b) {
|
||||
INLINE unsigned us_max_u(unsigned a, unsigned b) {
|
||||
return (a > b ? a : b);
|
||||
}
|
||||
|
||||
INLINE long long floor_ms(long double now) {
|
||||
INLINE long long us_floor_ms(long double now) {
|
||||
return (long long)now - (now < (long long)now); // floor()
|
||||
}
|
||||
|
||||
INLINE uint32_t triple_u32(uint32_t x) {
|
||||
INLINE uint32_t us_triple_u32(uint32_t x) {
|
||||
// https://nullprogram.com/blog/2018/07/31/
|
||||
x ^= x >> 17;
|
||||
x *= UINT32_C(0xED5AD4BB);
|
||||
@ -95,7 +95,7 @@ INLINE uint32_t triple_u32(uint32_t x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
struct timespec ts;
|
||||
assert(!clock_gettime(clk_id, &ts));
|
||||
*sec = ts.tv_sec;
|
||||
@ -108,47 +108,47 @@ INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
}
|
||||
|
||||
#if defined(CLOCK_MONOTONIC_RAW)
|
||||
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW
|
||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW
|
||||
#elif defined(CLOCK_MONOTONIC_FAST)
|
||||
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST
|
||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST
|
||||
#else
|
||||
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC
|
||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC
|
||||
#endif
|
||||
|
||||
INLINE long double get_now_monotonic(void) {
|
||||
INLINE long double us_get_now_monotonic(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
get_now(X_CLOCK_MONOTONIC, &sec, &msec);
|
||||
us_get_now(_X_CLOCK_MONOTONIC, &sec, &msec);
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE uint64_t get_now_monotonic_u64(void) {
|
||||
INLINE uint64_t us_get_now_monotonic_u64(void) {
|
||||
struct timespec ts;
|
||||
assert(!clock_gettime(X_CLOCK_MONOTONIC, &ts));
|
||||
assert(!clock_gettime(_X_CLOCK_MONOTONIC, &ts));
|
||||
return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000;
|
||||
}
|
||||
|
||||
#undef X_CLOCK_MONOTONIC
|
||||
#undef _X_CLOCK_MONOTONIC
|
||||
|
||||
INLINE uint64_t get_now_id(void) {
|
||||
uint64_t now = get_now_monotonic_u64();
|
||||
return (uint64_t)triple_u32(now) | ((uint64_t)triple_u32(now + 12345) << 32);
|
||||
INLINE uint64_t us_get_now_id(void) {
|
||||
uint64_t now = us_get_now_monotonic_u64();
|
||||
return (uint64_t)us_triple_u32(now) | ((uint64_t)us_triple_u32(now + 12345) << 32);
|
||||
}
|
||||
|
||||
INLINE long double get_now_real(void) {
|
||||
INLINE long double us_get_now_real(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
get_now(CLOCK_REALTIME, &sec, &msec);
|
||||
us_get_now(CLOCK_REALTIME, &sec, &msec);
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE unsigned get_cores_available(void) {
|
||||
INLINE unsigned us_get_cores_available(void) {
|
||||
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
|
||||
return max_u(min_u(cores_sysconf, 4), 1);
|
||||
return us_max_u(us_min_u(cores_sysconf, 4), 1);
|
||||
}
|
||||
|
||||
INLINE void ld_to_timespec(long double ld, struct timespec *ts) {
|
||||
INLINE void us_ld_to_timespec(long double ld, struct timespec *ts) {
|
||||
ts->tv_sec = (long)ld;
|
||||
ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L;
|
||||
if (ts->tv_nsec > 999999999L) {
|
||||
@ -157,17 +157,17 @@ INLINE void ld_to_timespec(long double ld, struct timespec *ts) {
|
||||
}
|
||||
}
|
||||
|
||||
INLINE long double timespec_to_ld(const struct timespec *ts) {
|
||||
INLINE long double us_timespec_to_ld(const struct timespec *ts) {
|
||||
return ts->tv_sec + ((long double)ts->tv_nsec) / 1000000000;
|
||||
}
|
||||
|
||||
INLINE int flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
long double deadline_ts = get_now_monotonic() + timeout;
|
||||
INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
long double deadline_ts = us_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) {
|
||||
if (retval == 0 || errno != EWOULDBLOCK || us_get_now_monotonic() > deadline_ts) {
|
||||
break;
|
||||
}
|
||||
if (usleep(1000) < 0) {
|
||||
@ -177,7 +177,7 @@ INLINE int flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
INLINE char *errno_to_string(int error, char *buf, size_t size) {
|
||||
INLINE char *us_errno_to_string(int error, char *buf, size_t size) {
|
||||
assert(buf);
|
||||
assert(size > 0);
|
||||
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
|
||||
|
||||
@ -26,15 +26,15 @@
|
||||
typedef struct {
|
||||
struct jpeg_error_mgr mgr; // Default manager
|
||||
jmp_buf jmp;
|
||||
const frame_s *frame;
|
||||
const us_frame_s *frame;
|
||||
} _jpeg_error_manager_s;
|
||||
|
||||
|
||||
static void _jpeg_error_handler(j_common_ptr jpeg);
|
||||
|
||||
|
||||
int unjpeg(const frame_s *src, frame_s *dest, bool decode) {
|
||||
assert(is_jpeg(src->format));
|
||||
int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
|
||||
assert(us_is_jpeg(src->format));
|
||||
|
||||
volatile int retval = 0;
|
||||
|
||||
@ -57,7 +57,7 @@ int unjpeg(const frame_s *src, frame_s *dest, bool decode) {
|
||||
|
||||
jpeg_start_decompress(&jpeg);
|
||||
|
||||
frame_copy_meta(src, dest);
|
||||
us_frame_copy_meta(src, dest);
|
||||
dest->format = V4L2_PIX_FMT_RGB24;
|
||||
dest->width = jpeg.output_width;
|
||||
dest->height = jpeg.output_height;
|
||||
@ -68,10 +68,10 @@ int unjpeg(const frame_s *src, frame_s *dest, bool decode) {
|
||||
JSAMPARRAY scanlines;
|
||||
scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr) &jpeg, JPOOL_IMAGE, dest->stride, 1);
|
||||
|
||||
frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2);
|
||||
us_frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2);
|
||||
while (jpeg.output_scanline < jpeg.output_height) {
|
||||
jpeg_read_scanlines(&jpeg, scanlines, 1);
|
||||
frame_append_data(dest, scanlines[0], dest->stride);
|
||||
us_frame_append_data(dest, scanlines[0], dest->stride);
|
||||
}
|
||||
|
||||
jpeg_finish_decompress(&jpeg);
|
||||
@ -87,6 +87,6 @@ static void _jpeg_error_handler(j_common_ptr jpeg) {
|
||||
char msg[JMSG_LENGTH_MAX];
|
||||
|
||||
(*jpeg_error->mgr.format_message)(jpeg, msg);
|
||||
LOG_ERROR("Can't decompress JPEG: %s", msg);
|
||||
US_LOG_ERROR("Can't decompress JPEG: %s", msg);
|
||||
longjmp(jpeg_error->jmp, -1);
|
||||
}
|
||||
|
||||
@ -37,4 +37,4 @@
|
||||
#include "frame.h"
|
||||
|
||||
|
||||
int unjpeg(const frame_s *src, frame_s *dest, bool decode);
|
||||
int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode);
|
||||
|
||||
@ -26,17 +26,15 @@
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
#ifndef CFG_XIOCTL_RETRIES
|
||||
# define CFG_XIOCTL_RETRIES 4
|
||||
#ifndef US_CFG_XIOCTL_RETRIES
|
||||
# define US_CFG_XIOCTL_RETRIES 4
|
||||
#endif
|
||||
#define XIOCTL_RETRIES ((unsigned)(CFG_XIOCTL_RETRIES))
|
||||
#define _XIOCTL_RETRIES ((unsigned)(US_CFG_XIOCTL_RETRIES))
|
||||
|
||||
|
||||
INLINE int xioctl(int fd, int request, void *arg) {
|
||||
int retries = XIOCTL_RETRIES;
|
||||
INLINE int us_xioctl(int fd, int request, void *arg) {
|
||||
int retries = _XIOCTL_RETRIES;
|
||||
int retval = -1;
|
||||
|
||||
do {
|
||||
|
||||
@ -23,50 +23,50 @@
|
||||
#include "blank.h"
|
||||
|
||||
|
||||
static frame_s *_init_internal(void);
|
||||
static frame_s *_init_external(const char *path);
|
||||
static us_frame_s *_init_internal(void);
|
||||
static us_frame_s *_init_external(const char *path);
|
||||
|
||||
|
||||
frame_s *blank_frame_init(const char *path) {
|
||||
frame_s *blank = NULL;
|
||||
us_frame_s *us_blank_frame_init(const char *path) {
|
||||
us_frame_s *blank = NULL;
|
||||
|
||||
if (path && path[0] != '\0') {
|
||||
blank = _init_external(path);
|
||||
}
|
||||
|
||||
if (blank) {
|
||||
LOG_INFO("Using external blank placeholder: %s", path);
|
||||
US_LOG_INFO("Using external blank placeholder: %s", path);
|
||||
} else {
|
||||
blank = _init_internal();
|
||||
LOG_INFO("Using internal blank placeholder");
|
||||
US_LOG_INFO("Using internal blank placeholder");
|
||||
}
|
||||
return blank;
|
||||
}
|
||||
|
||||
static frame_s *_init_internal(void) {
|
||||
frame_s *blank = frame_init();
|
||||
frame_set_data(blank, BLANK_JPEG_DATA, BLANK_JPEG_DATA_SIZE);
|
||||
blank->width = BLANK_JPEG_WIDTH;
|
||||
blank->height = BLANK_JPEG_HEIGHT;
|
||||
static us_frame_s *_init_internal(void) {
|
||||
us_frame_s *blank = us_frame_init();
|
||||
us_frame_set_data(blank, US_BLANK_JPEG_DATA, US_BLANK_JPEG_DATA_SIZE);
|
||||
blank->width = US_BLANK_JPEG_WIDTH;
|
||||
blank->height = US_BLANK_JPEG_HEIGHT;
|
||||
blank->format = V4L2_PIX_FMT_JPEG;
|
||||
return blank;
|
||||
}
|
||||
|
||||
static frame_s *_init_external(const char *path) {
|
||||
static us_frame_s *_init_external(const char *path) {
|
||||
FILE *fp = NULL;
|
||||
|
||||
frame_s *blank = frame_init();
|
||||
us_frame_s *blank = us_frame_init();
|
||||
blank->format = V4L2_PIX_FMT_JPEG;
|
||||
|
||||
if ((fp = fopen(path, "rb")) == NULL) {
|
||||
LOG_PERROR("Can't open blank placeholder '%s'", path);
|
||||
US_LOG_PERROR("Can't open blank placeholder '%s'", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
# define CHUNK_SIZE ((size_t)(100 * 1024))
|
||||
while (true) {
|
||||
if (blank->used + CHUNK_SIZE >= blank->allocated) {
|
||||
frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
|
||||
us_frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
|
||||
}
|
||||
|
||||
size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp);
|
||||
@ -76,26 +76,26 @@ static frame_s *_init_external(const char *path) {
|
||||
if (feof(fp)) {
|
||||
break;
|
||||
} else {
|
||||
LOG_PERROR("Can't read blank placeholder");
|
||||
US_LOG_PERROR("Can't read blank placeholder");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
# undef CHUNK_SIZE
|
||||
|
||||
frame_s *decoded = frame_init();
|
||||
if (unjpeg(blank, decoded, false) < 0) {
|
||||
frame_destroy(decoded);
|
||||
us_frame_s *decoded = us_frame_init();
|
||||
if (us_unjpeg(blank, decoded, false) < 0) {
|
||||
us_frame_destroy(decoded);
|
||||
goto error;
|
||||
}
|
||||
blank->width = decoded->width;
|
||||
blank->height = decoded->height;
|
||||
frame_destroy(decoded);
|
||||
us_frame_destroy(decoded);
|
||||
|
||||
goto ok;
|
||||
|
||||
error:
|
||||
frame_destroy(blank);
|
||||
us_frame_destroy(blank);
|
||||
blank = NULL;
|
||||
|
||||
ok:
|
||||
|
||||
@ -35,4 +35,4 @@
|
||||
#include "data/blank_jpeg.h"
|
||||
|
||||
|
||||
frame_s *blank_frame_init(const char *path);
|
||||
us_frame_s *us_blank_frame_init(const char *path);
|
||||
|
||||
@ -22,11 +22,11 @@
|
||||
#include "blank_jpeg.h"
|
||||
|
||||
|
||||
const unsigned BLANK_JPEG_WIDTH = 640;
|
||||
const unsigned BLANK_JPEG_HEIGHT = 480;
|
||||
const unsigned US_BLANK_JPEG_WIDTH = 640;
|
||||
const unsigned US_BLANK_JPEG_HEIGHT = 480;
|
||||
|
||||
const size_t BLANK_JPEG_DATA_SIZE = 13845;
|
||||
const uint8_t BLANK_JPEG_DATA[] = {
|
||||
const size_t US_BLANK_JPEG_DATA_SIZE = 13845;
|
||||
const uint8_t US_BLANK_JPEG_DATA[] = {
|
||||
0xFF, 0xD8, 0xFF, 0xE1, 0x09, 0x50, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6E, 0x73, 0x2E, 0x61, 0x64, 0x6F, 0x62,
|
||||
0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x78, 0x61, 0x70, 0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x00, 0x3C, 0x3F, 0x78, 0x70, 0x61,
|
||||
0x63, 0x6B, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6E, 0x3D, 0x22, 0xEF, 0xBB, 0xBF, 0x22, 0x20, 0x69, 0x64, 0x3D,
|
||||
|
||||
@ -27,8 +27,8 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
extern const unsigned BLANK_JPEG_WIDTH;
|
||||
extern const unsigned BLANK_JPEG_HEIGHT;
|
||||
extern const unsigned US_BLANK_JPEG_WIDTH;
|
||||
extern const unsigned US_BLANK_JPEG_HEIGHT;
|
||||
|
||||
extern const size_t BLANK_JPEG_DATA_SIZE;
|
||||
extern const uint8_t BLANK_JPEG_DATA[];
|
||||
extern const size_t US_BLANK_JPEG_DATA_SIZE;
|
||||
extern const uint8_t US_BLANK_JPEG_DATA[];
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
#include "index_html.h"
|
||||
|
||||
|
||||
const char *const HTML_INDEX_PAGE = " \
|
||||
const char *const US_HTML_INDEX_PAGE = " \
|
||||
<!DOCTYPE html> \
|
||||
\
|
||||
<html> \
|
||||
@ -33,7 +33,7 @@ const char *const HTML_INDEX_PAGE = " \
|
||||
</head> \
|
||||
\
|
||||
<body> \
|
||||
<h3>µStreamer v" VERSION "</h3> \
|
||||
<h3>µStreamer v" US_VERSION "</h3> \
|
||||
<hr> \
|
||||
<ul> \
|
||||
<li> \
|
||||
|
||||
@ -27,4 +27,4 @@
|
||||
#include "../../libs/const.h"
|
||||
|
||||
|
||||
extern const char *const HTML_INDEX_PAGE;
|
||||
extern const char *const US_HTML_INDEX_PAGE;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -48,73 +48,73 @@
|
||||
#include "../libs/xioctl.h"
|
||||
|
||||
|
||||
#define VIDEO_MIN_WIDTH ((unsigned)160)
|
||||
#define VIDEO_MAX_WIDTH ((unsigned)10240)
|
||||
#define US_VIDEO_MIN_WIDTH ((unsigned)160)
|
||||
#define US_VIDEO_MAX_WIDTH ((unsigned)10240)
|
||||
|
||||
#define VIDEO_MIN_HEIGHT ((unsigned)120)
|
||||
#define VIDEO_MAX_HEIGHT ((unsigned)4320)
|
||||
#define US_VIDEO_MIN_HEIGHT ((unsigned)120)
|
||||
#define US_VIDEO_MAX_HEIGHT ((unsigned)4320)
|
||||
|
||||
#define VIDEO_MAX_FPS ((unsigned)120)
|
||||
#define US_VIDEO_MAX_FPS ((unsigned)120)
|
||||
|
||||
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN
|
||||
#define STANDARDS_STR "PAL, NTSC, SECAM"
|
||||
#define US_STANDARD_UNKNOWN V4L2_STD_UNKNOWN
|
||||
#define US_STANDARDS_STR "PAL, NTSC, SECAM"
|
||||
|
||||
#define FORMAT_UNKNOWN -1
|
||||
#define FORMATS_STR "YUYV, UYVY, RGB565, RGB24, MJPEG, JPEG"
|
||||
#define US_FORMAT_UNKNOWN -1
|
||||
#define US_FORMATS_STR "YUYV, UYVY, RGB565, RGB24, MJPEG, JPEG"
|
||||
|
||||
#define IO_METHOD_UNKNOWN -1
|
||||
#define IO_METHODS_STR "MMAP, USERPTR"
|
||||
#define US_IO_METHOD_UNKNOWN -1
|
||||
#define US_IO_METHODS_STR "MMAP, USERPTR"
|
||||
|
||||
|
||||
typedef struct {
|
||||
frame_s raw;
|
||||
us_frame_s raw;
|
||||
struct v4l2_buffer buf;
|
||||
int dma_fd;
|
||||
bool grabbed;
|
||||
} hw_buffer_s;
|
||||
} us_hw_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
unsigned hw_fps;
|
||||
unsigned jpeg_quality;
|
||||
size_t raw_size;
|
||||
unsigned n_bufs;
|
||||
hw_buffer_s *hw_bufs;
|
||||
bool capturing;
|
||||
bool persistent_timeout_reported;
|
||||
} device_runtime_s;
|
||||
int fd;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
unsigned hw_fps;
|
||||
unsigned jpeg_quality;
|
||||
size_t raw_size;
|
||||
unsigned n_bufs;
|
||||
us_hw_buffer_s *hw_bufs;
|
||||
bool capturing;
|
||||
bool persistent_timeout_reported;
|
||||
} us_device_runtime_s;
|
||||
|
||||
typedef enum {
|
||||
CTL_MODE_NONE = 0,
|
||||
CTL_MODE_VALUE,
|
||||
CTL_MODE_AUTO,
|
||||
CTL_MODE_DEFAULT,
|
||||
} control_mode_e;
|
||||
} us_control_mode_e;
|
||||
|
||||
typedef struct {
|
||||
control_mode_e mode;
|
||||
int value;
|
||||
} control_s;
|
||||
us_control_mode_e mode;
|
||||
int value;
|
||||
} us_control_s;
|
||||
|
||||
typedef struct {
|
||||
control_s brightness;
|
||||
control_s contrast;
|
||||
control_s saturation;
|
||||
control_s hue;
|
||||
control_s gamma;
|
||||
control_s sharpness;
|
||||
control_s backlight_compensation;
|
||||
control_s white_balance;
|
||||
control_s gain;
|
||||
control_s color_effect;
|
||||
control_s rotate;
|
||||
control_s flip_vertical;
|
||||
control_s flip_horizontal;
|
||||
} controls_s;
|
||||
us_control_s brightness;
|
||||
us_control_s contrast;
|
||||
us_control_s saturation;
|
||||
us_control_s hue;
|
||||
us_control_s gamma;
|
||||
us_control_s sharpness;
|
||||
us_control_s backlight_compensation;
|
||||
us_control_s white_balance;
|
||||
us_control_s gain;
|
||||
us_control_s color_effect;
|
||||
us_control_s rotate;
|
||||
us_control_s flip_vertical;
|
||||
us_control_s flip_horizontal;
|
||||
} us_controls_s;
|
||||
|
||||
typedef struct {
|
||||
char *path;
|
||||
@ -132,25 +132,25 @@ typedef struct {
|
||||
bool persistent;
|
||||
unsigned timeout;
|
||||
|
||||
controls_s ctl;
|
||||
us_controls_s ctl;
|
||||
|
||||
device_runtime_s *run;
|
||||
} device_s;
|
||||
us_device_runtime_s *run;
|
||||
} us_device_s;
|
||||
|
||||
|
||||
device_s *device_init(void);
|
||||
void device_destroy(device_s *dev);
|
||||
us_device_s *us_device_init(void);
|
||||
void us_device_destroy(us_device_s *dev);
|
||||
|
||||
int device_parse_format(const char *str);
|
||||
v4l2_std_id device_parse_standard(const char *str);
|
||||
int device_parse_io_method(const char *str);
|
||||
int us_device_parse_format(const char *str);
|
||||
v4l2_std_id us_device_parse_standard(const char *str);
|
||||
int us_device_parse_io_method(const char *str);
|
||||
|
||||
int device_open(device_s *dev);
|
||||
void device_close(device_s *dev);
|
||||
int us_device_open(us_device_s *dev);
|
||||
void us_device_close(us_device_s *dev);
|
||||
|
||||
int device_export_to_dma(device_s *dev);
|
||||
int device_switch_capturing(device_s *dev, bool enable);
|
||||
int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_error);
|
||||
int device_grab_buffer(device_s *dev, hw_buffer_s **hw);
|
||||
int device_release_buffer(device_s *dev, hw_buffer_s *hw);
|
||||
int device_consume_event(device_s *dev);
|
||||
int us_device_export_to_dma(us_device_s *dev);
|
||||
int us_device_switch_capturing(us_device_s *dev, bool enable);
|
||||
int us_device_select(us_device_s *dev, bool *has_read, bool *has_write, bool *has_error);
|
||||
int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw);
|
||||
int us_device_release_buffer(us_device_s *dev, us_hw_buffer_s *hw);
|
||||
int us_device_consume_event(us_device_s *dev);
|
||||
|
||||
@ -25,67 +25,67 @@
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
const encoder_type_e type;
|
||||
const us_encoder_type_e type;
|
||||
} _ENCODER_TYPES[] = {
|
||||
{"CPU", ENCODER_TYPE_CPU},
|
||||
{"HW", ENCODER_TYPE_HW},
|
||||
{"M2M-VIDEO", ENCODER_TYPE_M2M_VIDEO},
|
||||
{"M2M-IMAGE", ENCODER_TYPE_M2M_IMAGE},
|
||||
{"M2M-MJPEG", ENCODER_TYPE_M2M_VIDEO},
|
||||
{"M2M-JPEG", ENCODER_TYPE_M2M_IMAGE},
|
||||
{"OMX", ENCODER_TYPE_M2M_IMAGE},
|
||||
{"NOOP", ENCODER_TYPE_NOOP},
|
||||
{"CPU", US_ENCODER_TYPE_CPU},
|
||||
{"HW", US_ENCODER_TYPE_HW},
|
||||
{"M2M-VIDEO", US_ENCODER_TYPE_M2M_VIDEO},
|
||||
{"M2M-IMAGE", US_ENCODER_TYPE_M2M_IMAGE},
|
||||
{"M2M-MJPEG", US_ENCODER_TYPE_M2M_VIDEO},
|
||||
{"M2M-JPEG", US_ENCODER_TYPE_M2M_IMAGE},
|
||||
{"OMX", US_ENCODER_TYPE_M2M_IMAGE},
|
||||
{"NOOP", US_ENCODER_TYPE_NOOP},
|
||||
};
|
||||
|
||||
|
||||
static void *_worker_job_init(void *v_enc);
|
||||
static void _worker_job_destroy(void *v_job);
|
||||
static bool _worker_run_job(worker_s *wr);
|
||||
static bool _worker_run_job(us_worker_s *wr);
|
||||
|
||||
|
||||
#define ER(_next) enc->run->_next
|
||||
#define _ER(x_next) enc->run->x_next
|
||||
|
||||
|
||||
encoder_s *encoder_init(void) {
|
||||
encoder_runtime_s *run;
|
||||
A_CALLOC(run, 1);
|
||||
run->type = ENCODER_TYPE_CPU;
|
||||
us_encoder_s *us_encoder_init(void) {
|
||||
us_encoder_runtime_s *run;
|
||||
US_CALLOC(run, 1);
|
||||
run->type = US_ENCODER_TYPE_CPU;
|
||||
run->quality = 80;
|
||||
A_MUTEX_INIT(&run->mutex);
|
||||
US_MUTEX_INIT(&run->mutex);
|
||||
|
||||
encoder_s *enc;
|
||||
A_CALLOC(enc, 1);
|
||||
us_encoder_s *enc;
|
||||
US_CALLOC(enc, 1);
|
||||
enc->type = run->type;
|
||||
enc->n_workers = get_cores_available();
|
||||
enc->n_workers = us_get_cores_available();
|
||||
enc->run = run;
|
||||
return enc;
|
||||
}
|
||||
|
||||
void encoder_destroy(encoder_s *enc) {
|
||||
if (ER(m2ms)) {
|
||||
for (unsigned index = 0; index < ER(n_m2ms); ++index) {
|
||||
if (ER(m2ms[index])) {
|
||||
m2m_encoder_destroy(ER(m2ms[index]));
|
||||
void us_encoder_destroy(us_encoder_s *enc) {
|
||||
if (_ER(m2ms)) {
|
||||
for (unsigned index = 0; index < _ER(n_m2ms); ++index) {
|
||||
if (_ER(m2ms[index])) {
|
||||
us_m2m_encoder_destroy(_ER(m2ms[index]));
|
||||
}
|
||||
}
|
||||
free(ER(m2ms));
|
||||
free(_ER(m2ms));
|
||||
}
|
||||
A_MUTEX_DESTROY(&ER(mutex));
|
||||
US_MUTEX_DESTROY(&_ER(mutex));
|
||||
free(enc->run);
|
||||
free(enc);
|
||||
}
|
||||
|
||||
encoder_type_e encoder_parse_type(const char *str) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
|
||||
us_encoder_type_e us_encoder_parse_type(const char *str) {
|
||||
for (unsigned index = 0; index < US_ARRAY_LEN(_ENCODER_TYPES); ++index) {
|
||||
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
|
||||
return _ENCODER_TYPES[index].type;
|
||||
}
|
||||
}
|
||||
return ENCODER_TYPE_UNKNOWN;
|
||||
return US_ENCODER_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
const char *encoder_type_to_string(encoder_type_e type) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
|
||||
const char *us_encoder_type_to_string(us_encoder_type_e type) {
|
||||
for (unsigned index = 0; index < US_ARRAY_LEN(_ENCODER_TYPES); ++index) {
|
||||
if (_ENCODER_TYPES[index].type == type) {
|
||||
return _ENCODER_TYPES[index].name;
|
||||
}
|
||||
@ -93,44 +93,44 @@ const char *encoder_type_to_string(encoder_type_e type) {
|
||||
return _ENCODER_TYPES[0].name;
|
||||
}
|
||||
|
||||
workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
|
||||
# define DR(_next) dev->run->_next
|
||||
us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *dev) {
|
||||
# define DR(x_next) dev->run->x_next
|
||||
|
||||
encoder_type_e type = (ER(cpu_forced) ? ENCODER_TYPE_CPU : enc->type);
|
||||
us_encoder_type_e type = (_ER(cpu_forced) ? US_ENCODER_TYPE_CPU : enc->type);
|
||||
unsigned quality = dev->jpeg_quality;
|
||||
unsigned n_workers = min_u(enc->n_workers, DR(n_bufs));
|
||||
unsigned n_workers = us_min_u(enc->n_workers, DR(n_bufs));
|
||||
bool cpu_forced = false;
|
||||
|
||||
if (is_jpeg(DR(format)) && type != ENCODER_TYPE_HW) {
|
||||
LOG_INFO("Switching to HW encoder: the input is (M)JPEG ...");
|
||||
type = ENCODER_TYPE_HW;
|
||||
if (us_is_jpeg(DR(format)) && type != US_ENCODER_TYPE_HW) {
|
||||
US_LOG_INFO("Switching to HW encoder: the input is (M)JPEG ...");
|
||||
type = US_ENCODER_TYPE_HW;
|
||||
}
|
||||
|
||||
if (type == ENCODER_TYPE_HW) {
|
||||
if (!is_jpeg(DR(format))) {
|
||||
LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ...");
|
||||
if (type == US_ENCODER_TYPE_HW) {
|
||||
if (!us_is_jpeg(DR(format))) {
|
||||
US_LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ...");
|
||||
goto use_cpu;
|
||||
}
|
||||
quality = DR(jpeg_quality);
|
||||
n_workers = 1;
|
||||
|
||||
} else if (type == ENCODER_TYPE_M2M_VIDEO || type == ENCODER_TYPE_M2M_IMAGE) {
|
||||
LOG_DEBUG("Preparing M2M-%s encoder ...", (type == ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"));
|
||||
if (ER(m2ms) == NULL) {
|
||||
A_CALLOC(ER(m2ms), n_workers);
|
||||
} else if (type == US_ENCODER_TYPE_M2M_VIDEO || type == US_ENCODER_TYPE_M2M_IMAGE) {
|
||||
US_LOG_DEBUG("Preparing M2M-%s encoder ...", (type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"));
|
||||
if (_ER(m2ms) == NULL) {
|
||||
US_CALLOC(_ER(m2ms), n_workers);
|
||||
}
|
||||
for (; ER(n_m2ms) < n_workers; ++ER(n_m2ms)) {
|
||||
for (; _ER(n_m2ms) < n_workers; ++_ER(n_m2ms)) {
|
||||
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||
char name[32];
|
||||
snprintf(name, 32, "JPEG-%u", ER(n_m2ms));
|
||||
if (type == ENCODER_TYPE_M2M_VIDEO) {
|
||||
ER(m2ms[ER(n_m2ms)]) = m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
snprintf(name, 32, "JPEG-%u", _ER(n_m2ms));
|
||||
if (type == US_ENCODER_TYPE_M2M_VIDEO) {
|
||||
_ER(m2ms[_ER(n_m2ms)]) = us_m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
} else {
|
||||
ER(m2ms[ER(n_m2ms)]) = m2m_jpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
_ER(m2ms[_ER(n_m2ms)]) = us_m2m_jpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (type == ENCODER_TYPE_NOOP) {
|
||||
} else if (type == US_ENCODER_TYPE_NOOP) {
|
||||
n_workers = 1;
|
||||
quality = 0;
|
||||
}
|
||||
@ -138,32 +138,32 @@ workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
|
||||
goto ok;
|
||||
|
||||
use_cpu:
|
||||
type = ENCODER_TYPE_CPU;
|
||||
type = US_ENCODER_TYPE_CPU;
|
||||
quality = dev->jpeg_quality;
|
||||
|
||||
ok:
|
||||
if (type == ENCODER_TYPE_NOOP) {
|
||||
LOG_INFO("Using JPEG NOOP encoder");
|
||||
if (type == US_ENCODER_TYPE_NOOP) {
|
||||
US_LOG_INFO("Using JPEG NOOP encoder");
|
||||
} else if (quality == 0) {
|
||||
LOG_INFO("Using JPEG quality: encoder default");
|
||||
US_LOG_INFO("Using JPEG quality: encoder default");
|
||||
} else {
|
||||
LOG_INFO("Using JPEG quality: %u%%", quality);
|
||||
US_LOG_INFO("Using JPEG quality: %u%%", quality);
|
||||
}
|
||||
|
||||
A_MUTEX_LOCK(&ER(mutex));
|
||||
ER(type) = type;
|
||||
ER(quality) = quality;
|
||||
US_MUTEX_LOCK(&_ER(mutex));
|
||||
_ER(type) = type;
|
||||
_ER(quality) = quality;
|
||||
if (cpu_forced) {
|
||||
ER(cpu_forced) = true;
|
||||
_ER(cpu_forced) = true;
|
||||
}
|
||||
A_MUTEX_UNLOCK(&ER(mutex));
|
||||
US_MUTEX_UNLOCK(&_ER(mutex));
|
||||
|
||||
long double desired_interval = 0;
|
||||
if (dev->desired_fps > 0 && (dev->desired_fps < dev->run->hw_fps || dev->run->hw_fps == 0)) {
|
||||
desired_interval = (long double)1 / dev->desired_fps;
|
||||
}
|
||||
|
||||
return workers_pool_init(
|
||||
return us_workers_pool_init(
|
||||
"JPEG", "jw", n_workers, desired_interval,
|
||||
_worker_job_init, (void *)enc,
|
||||
_worker_job_destroy,
|
||||
@ -172,64 +172,61 @@ workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
|
||||
# undef DR
|
||||
}
|
||||
|
||||
void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality) {
|
||||
A_MUTEX_LOCK(&ER(mutex));
|
||||
*type = ER(type);
|
||||
*quality = ER(quality);
|
||||
A_MUTEX_UNLOCK(&ER(mutex));
|
||||
void us_encoder_get_runtime_params(us_encoder_s *enc, us_encoder_type_e *type, unsigned *quality) {
|
||||
US_MUTEX_LOCK(&_ER(mutex));
|
||||
*type = _ER(type);
|
||||
*quality = _ER(quality);
|
||||
US_MUTEX_UNLOCK(&_ER(mutex));
|
||||
}
|
||||
|
||||
static void *_worker_job_init(void *v_enc) {
|
||||
encoder_job_s *job;
|
||||
A_CALLOC(job, 1);
|
||||
job->enc = (encoder_s *)v_enc;
|
||||
job->dest = frame_init();
|
||||
us_encoder_job_s *job;
|
||||
US_CALLOC(job, 1);
|
||||
job->enc = (us_encoder_s *)v_enc;
|
||||
job->dest = us_frame_init();
|
||||
return (void *)job;
|
||||
}
|
||||
|
||||
static void _worker_job_destroy(void *v_job) {
|
||||
encoder_job_s *job = (encoder_job_s *)v_job;
|
||||
frame_destroy(job->dest);
|
||||
us_encoder_job_s *job = (us_encoder_job_s *)v_job;
|
||||
us_frame_destroy(job->dest);
|
||||
free(job);
|
||||
}
|
||||
|
||||
#undef ER
|
||||
static bool _worker_run_job(us_worker_s *wr) {
|
||||
us_encoder_job_s *job = (us_encoder_job_s *)wr->job;
|
||||
us_encoder_s *enc = job->enc; // Just for _ER()
|
||||
us_frame_s *src = &job->hw->raw;
|
||||
us_frame_s *dest = job->dest;
|
||||
|
||||
static bool _worker_run_job(worker_s *wr) {
|
||||
encoder_job_s *job = (encoder_job_s *)wr->job;
|
||||
frame_s *src = &job->hw->raw;
|
||||
frame_s *dest = job->dest;
|
||||
assert(_ER(type) != US_ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
# define ER(_next) job->enc->run->_next
|
||||
|
||||
assert(ER(type) != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
if (ER(type) == ENCODER_TYPE_CPU) {
|
||||
LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u",
|
||||
if (_ER(type) == US_ENCODER_TYPE_CPU) {
|
||||
US_LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
cpu_encoder_compress(src, dest, ER(quality));
|
||||
us_cpu_encoder_compress(src, dest, _ER(quality));
|
||||
|
||||
} else if (ER(type) == ENCODER_TYPE_HW) {
|
||||
LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u",
|
||||
} else if (_ER(type) == US_ENCODER_TYPE_HW) {
|
||||
US_LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
hw_encoder_compress(src, dest);
|
||||
us_hw_encoder_compress(src, dest);
|
||||
|
||||
} else if (ER(type) == ENCODER_TYPE_M2M_VIDEO || ER(type) == ENCODER_TYPE_M2M_IMAGE) {
|
||||
LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u",
|
||||
(ER(type) == ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"), wr->name, job->hw->buf.index);
|
||||
if (m2m_encoder_compress(ER(m2ms[wr->number]), src, dest, false) < 0) {
|
||||
} else if (_ER(type) == US_ENCODER_TYPE_M2M_VIDEO || _ER(type) == US_ENCODER_TYPE_M2M_IMAGE) {
|
||||
US_LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u",
|
||||
(_ER(type) == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"), wr->name, job->hw->buf.index);
|
||||
if (us_m2m_encoder_compress(_ER(m2ms[wr->number]), src, dest, false) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
} else if (ER(type) == ENCODER_TYPE_NOOP) {
|
||||
LOG_VERBOSE("Compressing JPEG using NOOP (do nothing): worker=%s, buffer=%u",
|
||||
} else if (_ER(type) == US_ENCODER_TYPE_NOOP) {
|
||||
US_LOG_VERBOSE("Compressing JPEG using NOOP (do nothing): worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
usleep(5000); // Просто чтобы работала логика desired_fps
|
||||
dest->encode_end_ts = get_now_monotonic(); // frame_encoding_end()
|
||||
dest->encode_end_ts = us_get_now_monotonic(); // us_frame_encoding_end()
|
||||
}
|
||||
|
||||
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
|
||||
US_LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
|
||||
job->dest->used,
|
||||
job->dest->encode_end_ts - job->dest->encode_begin_ts,
|
||||
wr->name,
|
||||
@ -238,12 +235,10 @@ static bool _worker_run_job(worker_s *wr) {
|
||||
return true;
|
||||
|
||||
error:
|
||||
LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index);
|
||||
LOG_ERROR("Error while compressing buffer, falling back to CPU");
|
||||
A_MUTEX_LOCK(&ER(mutex));
|
||||
ER(cpu_forced) = true;
|
||||
A_MUTEX_UNLOCK(&ER(mutex));
|
||||
US_LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index);
|
||||
US_LOG_ERROR("Error while compressing buffer, falling back to CPU");
|
||||
US_MUTEX_LOCK(&_ER(mutex));
|
||||
_ER(cpu_forced) = true;
|
||||
US_MUTEX_UNLOCK(&_ER(mutex));
|
||||
return false;
|
||||
|
||||
# undef ER
|
||||
}
|
||||
|
||||
@ -46,46 +46,46 @@
|
||||
#define ENCODER_TYPES_STR "CPU, HW, M2M-VIDEO, M2M-IMAGE, NOOP"
|
||||
|
||||
typedef enum {
|
||||
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main()
|
||||
ENCODER_TYPE_CPU,
|
||||
ENCODER_TYPE_HW,
|
||||
ENCODER_TYPE_M2M_VIDEO,
|
||||
ENCODER_TYPE_M2M_IMAGE,
|
||||
ENCODER_TYPE_NOOP,
|
||||
} encoder_type_e;
|
||||
US_ENCODER_TYPE_UNKNOWN, // Only for us_encoder_parse_type() and main()
|
||||
US_ENCODER_TYPE_CPU,
|
||||
US_ENCODER_TYPE_HW,
|
||||
US_ENCODER_TYPE_M2M_VIDEO,
|
||||
US_ENCODER_TYPE_M2M_IMAGE,
|
||||
US_ENCODER_TYPE_NOOP,
|
||||
} us_encoder_type_e;
|
||||
|
||||
typedef struct {
|
||||
encoder_type_e type;
|
||||
unsigned quality;
|
||||
bool cpu_forced;
|
||||
pthread_mutex_t mutex;
|
||||
us_encoder_type_e type;
|
||||
unsigned quality;
|
||||
bool cpu_forced;
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
unsigned n_m2ms;
|
||||
m2m_encoder_s **m2ms;
|
||||
} encoder_runtime_s;
|
||||
unsigned n_m2ms;
|
||||
us_m2m_encoder_s **m2ms;
|
||||
} us_encoder_runtime_s;
|
||||
|
||||
typedef struct {
|
||||
encoder_type_e type;
|
||||
unsigned n_workers;
|
||||
char *m2m_path;
|
||||
us_encoder_type_e type;
|
||||
unsigned n_workers;
|
||||
char *m2m_path;
|
||||
|
||||
encoder_runtime_s *run;
|
||||
} encoder_s;
|
||||
us_encoder_runtime_s *run;
|
||||
} us_encoder_s;
|
||||
|
||||
typedef struct {
|
||||
encoder_s *enc;
|
||||
hw_buffer_s *hw;
|
||||
frame_s *dest;
|
||||
} encoder_job_s;
|
||||
us_encoder_s *enc;
|
||||
us_hw_buffer_s *hw;
|
||||
us_frame_s *dest;
|
||||
} us_encoder_job_s;
|
||||
|
||||
|
||||
encoder_s *encoder_init(void);
|
||||
void encoder_destroy(encoder_s *enc);
|
||||
us_encoder_s *us_encoder_init(void);
|
||||
void us_encoder_destroy(us_encoder_s *enc);
|
||||
|
||||
encoder_type_e encoder_parse_type(const char *str);
|
||||
const char *encoder_type_to_string(encoder_type_e type);
|
||||
us_encoder_type_e us_encoder_parse_type(const char *str);
|
||||
const char *us_encoder_type_to_string(us_encoder_type_e type);
|
||||
|
||||
workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev);
|
||||
void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality);
|
||||
us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *dev);
|
||||
void us_encoder_get_runtime_params(us_encoder_s *enc, us_encoder_type_e *type, unsigned *quality);
|
||||
|
||||
int encoder_compress(encoder_s *enc, unsigned worker_number, frame_s *src, frame_s *dest);
|
||||
int us_encoder_compress(us_encoder_s *enc, unsigned worker_number, us_frame_s *src, us_frame_s *dest);
|
||||
|
||||
@ -31,26 +31,26 @@
|
||||
typedef struct {
|
||||
struct jpeg_destination_mgr mgr; // Default manager
|
||||
JOCTET *buf; // Start of buffer
|
||||
frame_s *frame;
|
||||
us_frame_s *frame;
|
||||
} _jpeg_dest_manager_s;
|
||||
|
||||
|
||||
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame);
|
||||
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame);
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const frame_s *frame);
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const frame_s *frame);
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame);
|
||||
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame);
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg);
|
||||
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
|
||||
static void _jpeg_term_destination(j_compress_ptr jpeg);
|
||||
|
||||
|
||||
void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
|
||||
void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned quality) {
|
||||
// This function based on compress_image_to_jpeg() from mjpg-streamer
|
||||
|
||||
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
|
||||
struct jpeg_compress_struct jpeg;
|
||||
struct jpeg_error_mgr jpeg_error;
|
||||
@ -70,8 +70,8 @@ void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
|
||||
|
||||
jpeg_start_compress(&jpeg, TRUE);
|
||||
|
||||
# define WRITE_SCANLINES(_format, _func) \
|
||||
case _format: { _func(&jpeg, src); break; }
|
||||
# define WRITE_SCANLINES(x_format, x_func) \
|
||||
case x_format: { x_func(&jpeg, src); break; }
|
||||
|
||||
switch (src->format) {
|
||||
// https://www.fourcc.org/yuv.php
|
||||
@ -87,10 +87,10 @@ void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
|
||||
jpeg_finish_compress(&jpeg);
|
||||
jpeg_destroy_compress(&jpeg);
|
||||
|
||||
frame_encoding_end(dest);
|
||||
us_frame_encoding_end(dest);
|
||||
}
|
||||
|
||||
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame) {
|
||||
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame) {
|
||||
if (jpeg->dest == NULL) {
|
||||
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(_jpeg_dest_manager_s)
|
||||
@ -111,11 +111,11 @@ static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame) {
|
||||
#define YUV_B(_y, _u, _) (((_y) + (454 * (_u))) >> 8)
|
||||
#define NORM_COMPONENT(_x) (((_x) > 255) ? 255 : (((_x) < 0) ? 0 : (_x)))
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||
uint8_t *line_buf;
|
||||
A_CALLOC(line_buf, frame->width * 3);
|
||||
US_CALLOC(line_buf, frame->width * 3);
|
||||
|
||||
const unsigned padding = frame_get_padding(frame);
|
||||
const unsigned padding = us_frame_get_padding(frame);
|
||||
const uint8_t *data = frame->data;
|
||||
unsigned z = 0;
|
||||
|
||||
@ -149,11 +149,11 @@ static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const
|
||||
free(line_buf);
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||
uint8_t *line_buf;
|
||||
A_CALLOC(line_buf, frame->width * 3);
|
||||
US_CALLOC(line_buf, frame->width * 3);
|
||||
|
||||
const unsigned padding = frame_get_padding(frame);
|
||||
const unsigned padding = us_frame_get_padding(frame);
|
||||
const uint8_t *data = frame->data;
|
||||
unsigned z = 0;
|
||||
|
||||
@ -192,11 +192,11 @@ static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const
|
||||
#undef YUV_G
|
||||
#undef YUV_R
|
||||
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||
uint8_t *line_buf;
|
||||
A_CALLOC(line_buf, frame->width * 3);
|
||||
US_CALLOC(line_buf, frame->width * 3);
|
||||
|
||||
const unsigned padding = frame_get_padding(frame);
|
||||
const unsigned padding = us_frame_get_padding(frame);
|
||||
const uint8_t *data = frame->data;
|
||||
|
||||
while (jpeg->next_scanline < frame->height) {
|
||||
@ -220,8 +220,8 @@ static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, cons
|
||||
free(line_buf);
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
|
||||
const unsigned padding = frame_get_padding(frame);
|
||||
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||
const unsigned padding = us_frame_get_padding(frame);
|
||||
uint8_t *data = frame->data;
|
||||
|
||||
while (jpeg->next_scanline < frame->height) {
|
||||
@ -251,7 +251,7 @@ static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
|
||||
|
||||
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
|
||||
|
||||
frame_append_data(dest->frame, dest->buf, JPEG_OUTPUT_BUFFER_SIZE);
|
||||
us_frame_append_data(dest->frame, dest->buf, JPEG_OUTPUT_BUFFER_SIZE);
|
||||
|
||||
dest->mgr.next_output_byte = dest->buf;
|
||||
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
|
||||
@ -267,7 +267,7 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
|
||||
size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
|
||||
|
||||
// Write any data remaining in the buffer.
|
||||
frame_append_data(dest->frame, dest->buf, final);
|
||||
us_frame_append_data(dest->frame, dest->buf, final);
|
||||
}
|
||||
|
||||
#undef JPEG_OUTPUT_BUFFER_SIZE
|
||||
|
||||
@ -35,4 +35,4 @@
|
||||
#include "../../../libs/frame.h"
|
||||
|
||||
|
||||
void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality);
|
||||
void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned quality);
|
||||
|
||||
@ -28,17 +28,17 @@
|
||||
#include "encoder.h"
|
||||
|
||||
|
||||
void _copy_plus_huffman(const frame_s *src, frame_s *dest);
|
||||
void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest);
|
||||
static bool _is_huffman(const uint8_t *data);
|
||||
|
||||
|
||||
void hw_encoder_compress(const frame_s *src, frame_s *dest) {
|
||||
assert(is_jpeg(src->format));
|
||||
void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest) {
|
||||
assert(us_is_jpeg(src->format));
|
||||
_copy_plus_huffman(src, dest);
|
||||
}
|
||||
|
||||
void _copy_plus_huffman(const frame_s *src, frame_s *dest) {
|
||||
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest) {
|
||||
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
|
||||
if (!_is_huffman(src->data)) {
|
||||
const uint8_t *src_ptr = src->data;
|
||||
@ -54,15 +54,15 @@ void _copy_plus_huffman(const frame_s *src, frame_s *dest) {
|
||||
|
||||
const size_t paste = src_ptr - src->data;
|
||||
|
||||
frame_set_data(dest, src->data, paste);
|
||||
frame_append_data(dest, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE));
|
||||
frame_append_data(dest, src_ptr, src->used - paste);
|
||||
us_frame_set_data(dest, src->data, paste);
|
||||
us_frame_append_data(dest, US_HUFFMAN_TABLE, sizeof(US_HUFFMAN_TABLE));
|
||||
us_frame_append_data(dest, src_ptr, src->used - paste);
|
||||
|
||||
} else {
|
||||
frame_set_data(dest, src->data, src->used);
|
||||
us_frame_set_data(dest, src->data, src->used);
|
||||
}
|
||||
|
||||
frame_encoding_end(dest);
|
||||
us_frame_encoding_end(dest);
|
||||
}
|
||||
|
||||
static bool _is_huffman(const uint8_t *data) {
|
||||
|
||||
@ -34,4 +34,4 @@
|
||||
#include "huffman.h"
|
||||
|
||||
|
||||
void hw_encoder_compress(const frame_s *src, frame_s *dest);
|
||||
void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest);
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
static const uint8_t HUFFMAN_TABLE[] = {
|
||||
static const uint8_t US_HUFFMAN_TABLE[] = {
|
||||
0xFF, 0xC4, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
|
||||
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x01, 0x00, 0x03,
|
||||
|
||||
@ -23,13 +23,13 @@
|
||||
#include "gpio.h"
|
||||
|
||||
|
||||
gpio_s us_gpio = {
|
||||
us_gpio_s us_gpio = {
|
||||
.path = "/dev/gpiochip0",
|
||||
.consumer_prefix = "ustreamer",
|
||||
|
||||
# define MAKE_OUTPUT(_role) { \
|
||||
# define MAKE_OUTPUT(x_role) { \
|
||||
.pin = -1, \
|
||||
.role = _role, \
|
||||
.role = x_role, \
|
||||
.consumer = NULL, \
|
||||
.line = NULL, \
|
||||
.state = false \
|
||||
@ -46,77 +46,77 @@ gpio_s us_gpio = {
|
||||
};
|
||||
|
||||
|
||||
static void _gpio_output_init(gpio_output_s *output);
|
||||
static void _gpio_output_destroy(gpio_output_s *output);
|
||||
static void _gpio_output_init(us_gpio_output_s *output);
|
||||
static void _gpio_output_destroy(us_gpio_output_s *output);
|
||||
|
||||
|
||||
void gpio_init(void) {
|
||||
void us_gpio_init(void) {
|
||||
assert(us_gpio.chip == NULL);
|
||||
if (
|
||||
us_gpio.prog_running.pin >= 0
|
||||
|| us_gpio.stream_online.pin >= 0
|
||||
|| us_gpio.has_http_clients.pin >= 0
|
||||
) {
|
||||
A_MUTEX_INIT(&us_gpio.mutex);
|
||||
LOG_INFO("GPIO: Using chip device: %s", us_gpio.path);
|
||||
US_MUTEX_INIT(&us_gpio.mutex);
|
||||
US_LOG_INFO("GPIO: Using chip device: %s", us_gpio.path);
|
||||
if ((us_gpio.chip = gpiod_chip_open(us_gpio.path)) != NULL) {
|
||||
_gpio_output_init(&us_gpio.prog_running);
|
||||
_gpio_output_init(&us_gpio.stream_online);
|
||||
_gpio_output_init(&us_gpio.has_http_clients);
|
||||
} else {
|
||||
LOG_PERROR("GPIO: Can't initialize chip device %s", us_gpio.path);
|
||||
US_LOG_PERROR("GPIO: Can't initialize chip device %s", us_gpio.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gpio_destroy(void) {
|
||||
void us_gpio_destroy(void) {
|
||||
_gpio_output_destroy(&us_gpio.prog_running);
|
||||
_gpio_output_destroy(&us_gpio.stream_online);
|
||||
_gpio_output_destroy(&us_gpio.has_http_clients);
|
||||
if (us_gpio.chip) {
|
||||
gpiod_chip_close(us_gpio.chip);
|
||||
us_gpio.chip = NULL;
|
||||
A_MUTEX_DESTROY(&us_gpio.mutex);
|
||||
US_MUTEX_DESTROY(&us_gpio.mutex);
|
||||
}
|
||||
}
|
||||
|
||||
int gpio_inner_set(gpio_output_s *output, bool state) {
|
||||
int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
|
||||
int retval = 0;
|
||||
|
||||
assert(us_gpio.chip);
|
||||
assert(output->line);
|
||||
assert(output->state != state); // Must be checked in macro for the performance
|
||||
A_MUTEX_LOCK(&us_gpio.mutex);
|
||||
US_MUTEX_LOCK(&us_gpio.mutex);
|
||||
|
||||
if (gpiod_line_set_value(output->line, (int)state) < 0) { \
|
||||
LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \
|
||||
US_LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \
|
||||
_gpio_output_destroy(output);
|
||||
retval = -1;
|
||||
}
|
||||
|
||||
A_MUTEX_UNLOCK(&us_gpio.mutex);
|
||||
US_MUTEX_UNLOCK(&us_gpio.mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void _gpio_output_init(gpio_output_s *output) {
|
||||
static void _gpio_output_init(us_gpio_output_s *output) {
|
||||
assert(us_gpio.chip);
|
||||
assert(output->line == NULL);
|
||||
|
||||
A_ASPRINTF(output->consumer, "%s::%s", us_gpio.consumer_prefix, output->role);
|
||||
US_ASPRINTF(output->consumer, "%s::%s", us_gpio.consumer_prefix, output->role);
|
||||
|
||||
if (output->pin >= 0) {
|
||||
if ((output->line = gpiod_chip_get_line(us_gpio.chip, output->pin)) != NULL) {
|
||||
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) {
|
||||
LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
|
||||
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
|
||||
_gpio_output_destroy(output);
|
||||
}
|
||||
} else {
|
||||
LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer);
|
||||
US_LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _gpio_output_destroy(gpio_output_s *output) {
|
||||
static void _gpio_output_destroy(us_gpio_output_s *output) {
|
||||
if (output->line) {
|
||||
gpiod_line_release(output->line);
|
||||
output->line = NULL;
|
||||
|
||||
@ -41,46 +41,46 @@ typedef struct {
|
||||
char *consumer;
|
||||
struct gpiod_line *line;
|
||||
bool state;
|
||||
} gpio_output_s;
|
||||
} us_gpio_output_s;
|
||||
|
||||
typedef struct {
|
||||
char *path;
|
||||
char *consumer_prefix;
|
||||
char *path;
|
||||
char *consumer_prefix;
|
||||
|
||||
gpio_output_s prog_running;
|
||||
gpio_output_s stream_online;
|
||||
gpio_output_s has_http_clients;
|
||||
us_gpio_output_s prog_running;
|
||||
us_gpio_output_s stream_online;
|
||||
us_gpio_output_s has_http_clients;
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
struct gpiod_chip *chip;
|
||||
} gpio_s;
|
||||
} us_gpio_s;
|
||||
|
||||
|
||||
extern gpio_s us_gpio;
|
||||
extern us_gpio_s us_gpio;
|
||||
|
||||
|
||||
void gpio_init(void);
|
||||
void gpio_destroy(void);
|
||||
int gpio_inner_set(gpio_output_s *output, bool state);
|
||||
void us_gpio_init(void);
|
||||
void us_gpio_destroy(void);
|
||||
int us_gpio_inner_set(us_gpio_output_s *output, bool state);
|
||||
|
||||
|
||||
#define SET_STATE(_output, _state) { \
|
||||
if (_output.line && _output.state != _state) { \
|
||||
if (!gpio_inner_set(&_output, _state)) { \
|
||||
_output.state = _state; \
|
||||
#define SET_STATE(x_output, x_state) { \
|
||||
if (x_output.line && x_output.state != x_state) { \
|
||||
if (!us_gpio_inner_set(&x_output, x_state)) { \
|
||||
x_output.state = x_state; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
INLINE void gpio_set_prog_running(bool state) {
|
||||
INLINE void us_gpio_set_prog_running(bool state) {
|
||||
SET_STATE(us_gpio.prog_running, state);
|
||||
}
|
||||
|
||||
INLINE void gpio_set_stream_online(bool state) {
|
||||
INLINE void us_gpio_set_stream_online(bool state) {
|
||||
SET_STATE(us_gpio.stream_online, state);
|
||||
}
|
||||
|
||||
INLINE void gpio_set_has_http_clients(bool state) {
|
||||
INLINE void us_gpio_set_has_http_clients(bool state) {
|
||||
SET_STATE(us_gpio.has_http_clients, state);
|
||||
}
|
||||
|
||||
|
||||
@ -23,42 +23,42 @@
|
||||
#include "stream.h"
|
||||
|
||||
|
||||
h264_stream_s *h264_stream_init(memsink_s *sink, const char *path, unsigned bitrate, unsigned gop) {
|
||||
h264_stream_s *h264;
|
||||
A_CALLOC(h264, 1);
|
||||
us_h264_stream_s *us_h264_stream_init(us_memsink_s *sink, const char *path, unsigned bitrate, unsigned gop) {
|
||||
us_h264_stream_s *h264;
|
||||
US_CALLOC(h264, 1);
|
||||
h264->sink = sink;
|
||||
h264->tmp_src = frame_init();
|
||||
h264->dest = frame_init();
|
||||
h264->tmp_src = us_frame_init();
|
||||
h264->dest = us_frame_init();
|
||||
atomic_init(&h264->online, false);
|
||||
h264->enc = m2m_h264_encoder_init("H264", path, bitrate, gop);
|
||||
h264->enc = us_m2m_h264_encoder_init("H264", path, bitrate, gop);
|
||||
return h264;
|
||||
}
|
||||
|
||||
void h264_stream_destroy(h264_stream_s *h264) {
|
||||
m2m_encoder_destroy(h264->enc);
|
||||
frame_destroy(h264->dest);
|
||||
frame_destroy(h264->tmp_src);
|
||||
void us_h264_stream_destroy(us_h264_stream_s *h264) {
|
||||
us_m2m_encoder_destroy(h264->enc);
|
||||
us_frame_destroy(h264->dest);
|
||||
us_frame_destroy(h264->tmp_src);
|
||||
free(h264);
|
||||
}
|
||||
|
||||
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, bool force_key) {
|
||||
if (!memsink_server_check(h264->sink, frame)) {
|
||||
void us_h264_stream_process(us_h264_stream_s *h264, const us_frame_s *frame, bool force_key) {
|
||||
if (!us_memsink_server_check(h264->sink, frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_jpeg(frame->format)) {
|
||||
long double now = get_now_monotonic();
|
||||
LOG_DEBUG("H264: Input frame is JPEG; decoding ...");
|
||||
if (unjpeg(frame, h264->tmp_src, true) < 0) {
|
||||
if (us_is_jpeg(frame->format)) {
|
||||
long double now = us_get_now_monotonic();
|
||||
US_LOG_DEBUG("H264: Input frame is JPEG; decoding ...");
|
||||
if (us_unjpeg(frame, h264->tmp_src, true) < 0) {
|
||||
return;
|
||||
}
|
||||
frame = h264->tmp_src;
|
||||
LOG_VERBOSE("H264: JPEG decoded; time=%.3Lf", get_now_monotonic() - now);
|
||||
US_LOG_VERBOSE("H264: JPEG decoded; time=%.3Lf", us_get_now_monotonic() - now);
|
||||
}
|
||||
|
||||
bool online = false;
|
||||
if (!m2m_encoder_compress(h264->enc, frame, h264->dest, force_key)) {
|
||||
online = !memsink_server_put(h264->sink, h264->dest);
|
||||
if (!us_m2m_encoder_compress(h264->enc, frame, h264->dest, force_key)) {
|
||||
online = !us_memsink_server_put(h264->sink, h264->dest);
|
||||
}
|
||||
atomic_store(&h264->online, online);
|
||||
}
|
||||
|
||||
@ -35,14 +35,14 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
memsink_s *sink;
|
||||
frame_s *tmp_src;
|
||||
frame_s *dest;
|
||||
m2m_encoder_s *enc;
|
||||
atomic_bool online;
|
||||
} h264_stream_s;
|
||||
us_memsink_s *sink;
|
||||
us_frame_s *tmp_src;
|
||||
us_frame_s *dest;
|
||||
us_m2m_encoder_s *enc;
|
||||
atomic_bool online;
|
||||
} us_h264_stream_s;
|
||||
|
||||
|
||||
h264_stream_s *h264_stream_init(memsink_s *sink, const char *path, unsigned bitrate, unsigned gop);
|
||||
void h264_stream_destroy(h264_stream_s *h264);
|
||||
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, bool force_key);
|
||||
us_h264_stream_s *us_h264_stream_init(us_memsink_s *sink, const char *path, unsigned bitrate, unsigned gop);
|
||||
void us_h264_stream_destroy(us_h264_stream_s *h264);
|
||||
void us_h264_stream_process(us_h264_stream_s *h264, const us_frame_s *frame, bool force_key);
|
||||
|
||||
@ -23,25 +23,25 @@
|
||||
#include "bev.h"
|
||||
|
||||
|
||||
char *bufferevent_my_format_reason(short what) {
|
||||
char *us_bufferevent_format_reason(short what) {
|
||||
char *reason;
|
||||
A_CALLOC(reason, 2048);
|
||||
US_CALLOC(reason, 2048);
|
||||
|
||||
char perror_buf[1024] = {0};
|
||||
char *perror_ptr = errno_to_string(EVUTIL_SOCKET_ERROR(), perror_buf, 1024); // evutil_socket_error_to_string() is not thread-safe
|
||||
char *perror_ptr = us_errno_to_string(EVUTIL_SOCKET_ERROR(), perror_buf, 1024); // evutil_socket_error_to_string() is not thread-safe
|
||||
bool first = true;
|
||||
|
||||
strcat(reason, perror_ptr);
|
||||
strcat(reason, " (");
|
||||
|
||||
# define FILL_REASON(_bev, _name) { \
|
||||
if (what & _bev) { \
|
||||
# define FILL_REASON(x_bev, x_name) { \
|
||||
if (what & x_bev) { \
|
||||
if (first) { \
|
||||
first = false; \
|
||||
} else { \
|
||||
strcat(reason, ","); \
|
||||
} \
|
||||
strcat(reason, _name); \
|
||||
strcat(reason, x_name); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
@ -32,4 +32,4 @@
|
||||
#include "../../libs/logging.h"
|
||||
|
||||
|
||||
char *bufferevent_my_format_reason(short what);
|
||||
char *us_bufferevent_format_reason(short what);
|
||||
|
||||
@ -46,7 +46,7 @@ static const struct {
|
||||
};
|
||||
|
||||
|
||||
const char *guess_mime_type(const char *path) {
|
||||
const char *us_guess_mime_type(const char *path) {
|
||||
// FIXME: false-positive cppcheck
|
||||
char *dot = strrchr(path, '.'); // cppcheck-suppress ctunullpointer
|
||||
if (dot == NULL || strchr(dot, '/') != NULL) {
|
||||
@ -54,7 +54,7 @@ const char *guess_mime_type(const char *path) {
|
||||
}
|
||||
|
||||
char *ext = dot + 1;
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_MIME_TYPES); ++index) {
|
||||
for (unsigned index = 0; index < US_ARRAY_LEN(_MIME_TYPES); ++index) {
|
||||
if (!evutil_ascii_strcasecmp(ext, _MIME_TYPES[index].ext)) {
|
||||
return _MIME_TYPES[index].mime;
|
||||
}
|
||||
|
||||
@ -29,4 +29,4 @@
|
||||
#include "../../libs/tools.h"
|
||||
|
||||
|
||||
const char *guess_mime_type(const char *str);
|
||||
const char *us_guess_mime_type(const char *str);
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
#include "path.h"
|
||||
|
||||
|
||||
char *simplify_request_path(const char *str) {
|
||||
char *us_simplify_request_path(const char *str) {
|
||||
// Based on Lighttpd sources:
|
||||
// - https://github.com/lighttpd/lighttpd1.4/blob/b31e7840d5403bc640579135b7004793b9ccd6c0/src/buffer.c#L840
|
||||
// - https://github.com/lighttpd/lighttpd1.4/blob/77c01f981725512653c01cde5ca74c11633dfec4/src/t/test_buffer.c
|
||||
@ -36,7 +36,7 @@ char *simplify_request_path(const char *str) {
|
||||
char *out;
|
||||
char *slash;
|
||||
|
||||
A_CALLOC(simplified, strlen(str) + 1);
|
||||
US_CALLOC(simplified, strlen(str) + 1);
|
||||
|
||||
if (str[0] == '\0') {
|
||||
simplified[0] = '\0';
|
||||
@ -112,7 +112,7 @@ char *simplify_request_path(const char *str) {
|
||||
#ifdef TEST_HTTP_PATH
|
||||
|
||||
int test_simplify_request_path(const char *sample, const char *expected) {
|
||||
char *result = simplify_request_path(sample);
|
||||
char *result = us_simplify_request_path(sample);
|
||||
int retval = -!!strcmp(result, expected);
|
||||
|
||||
printf("Testing '%s' -> '%s' ... ", sample, expected);
|
||||
|
||||
@ -31,4 +31,4 @@
|
||||
#include "../../libs/tools.h"
|
||||
|
||||
|
||||
char *simplify_request_path(const char *str);
|
||||
char *us_simplify_request_path(const char *str);
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
#include "server.h"
|
||||
|
||||
|
||||
static int _http_preprocess_request(struct evhttp_request *request, server_s *server);
|
||||
static int _http_preprocess_request(struct evhttp_request *request, us_server_s *server);
|
||||
|
||||
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server);
|
||||
|
||||
@ -38,31 +38,32 @@ static void _http_callback_stream_error(struct bufferevent *buf_event, short wha
|
||||
|
||||
static void _http_request_watcher(int fd, short event, void *v_server);
|
||||
static void _http_refresher(int fd, short event, void *v_server);
|
||||
static void _http_queue_send_stream(server_s *server, bool stream_updated, bool frame_updated);
|
||||
static void _http_queue_send_stream(us_server_s *server, bool stream_updated, bool frame_updated);
|
||||
|
||||
static bool _expose_new_frame(server_s *server);
|
||||
static bool _expose_new_frame(us_server_s *server);
|
||||
|
||||
static const char *_http_get_header(struct evhttp_request *request, const char *key);
|
||||
static char *_http_get_client_hostport(struct evhttp_request *request);
|
||||
|
||||
|
||||
#define RUN(_next) server->run->_next
|
||||
#define STREAM(_next) RUN(stream->_next)
|
||||
#define VID(_next) STREAM(run->video->_next)
|
||||
#define EX(_next) RUN(exposed->_next)
|
||||
#define _RUN(x_next) server->run->x_next
|
||||
#define _STREAM(x_next) _RUN(stream->x_next)
|
||||
#define _VID(x_next) _STREAM(run->video->x_next)
|
||||
#define _EX(x_next) _RUN(exposed->x_next)
|
||||
|
||||
|
||||
server_s *server_init(stream_s *stream) {
|
||||
exposed_s *exposed;
|
||||
A_CALLOC(exposed, 1);
|
||||
exposed->frame = frame_init();
|
||||
us_server_s *us_server_init(us_stream_s *stream) {
|
||||
us_exposed_s *exposed;
|
||||
US_CALLOC(exposed, 1);
|
||||
exposed->frame = us_frame_init();
|
||||
|
||||
server_runtime_s *run;
|
||||
A_CALLOC(run, 1);
|
||||
us_server_runtime_s *run;
|
||||
US_CALLOC(run, 1);
|
||||
run->stream = stream;
|
||||
run->exposed = exposed;
|
||||
|
||||
server_s *server;
|
||||
A_CALLOC(server, 1);
|
||||
us_server_s *server;
|
||||
US_CALLOC(server, 1);
|
||||
server->host = "127.0.0.1";
|
||||
server->port = 8080;
|
||||
server->unix_path = "";
|
||||
@ -80,150 +81,146 @@ server_s *server_init(stream_s *stream) {
|
||||
return server;
|
||||
}
|
||||
|
||||
void server_destroy(server_s *server) {
|
||||
if (RUN(refresher)) {
|
||||
event_del(RUN(refresher));
|
||||
event_free(RUN(refresher));
|
||||
void us_server_destroy(us_server_s *server) {
|
||||
if (_RUN(refresher)) {
|
||||
event_del(_RUN(refresher));
|
||||
event_free(_RUN(refresher));
|
||||
}
|
||||
|
||||
if (RUN(request_watcher)) {
|
||||
event_del(RUN(request_watcher));
|
||||
event_free(RUN(request_watcher));
|
||||
if (_RUN(request_watcher)) {
|
||||
event_del(_RUN(request_watcher));
|
||||
event_free(_RUN(request_watcher));
|
||||
}
|
||||
|
||||
evhttp_free(RUN(http));
|
||||
if (RUN(ext_fd)) {
|
||||
close(RUN(ext_fd));
|
||||
evhttp_free(_RUN(http));
|
||||
if (_RUN(ext_fd)) {
|
||||
close(_RUN(ext_fd));
|
||||
}
|
||||
event_base_free(RUN(base));
|
||||
event_base_free(_RUN(base));
|
||||
|
||||
# if LIBEVENT_VERSION_NUMBER >= 0x02010100
|
||||
libevent_global_shutdown();
|
||||
# endif
|
||||
|
||||
LIST_ITERATE(RUN(stream_clients), client, {
|
||||
US_LIST_ITERATE(_RUN(stream_clients), client, {
|
||||
free(client->key);
|
||||
free(client->hostport);
|
||||
free(client);
|
||||
});
|
||||
|
||||
if (RUN(auth_token)) {
|
||||
free(RUN(auth_token));
|
||||
if (_RUN(auth_token)) {
|
||||
free(_RUN(auth_token));
|
||||
}
|
||||
|
||||
frame_destroy(EX(frame));
|
||||
free(RUN(exposed));
|
||||
us_frame_destroy(_EX(frame));
|
||||
free(_RUN(exposed));
|
||||
free(server->run);
|
||||
free(server);
|
||||
}
|
||||
|
||||
int server_listen(server_s *server) {
|
||||
int us_server_listen(us_server_s *server) {
|
||||
{
|
||||
if (server->static_path[0] != '\0') {
|
||||
LOG_INFO("Enabling HTTP file server: %s", server->static_path);
|
||||
evhttp_set_gencb(RUN(http), _http_callback_static, (void *)server);
|
||||
US_LOG_INFO("Enabling HTTP file server: %s", server->static_path);
|
||||
evhttp_set_gencb(_RUN(http), _http_callback_static, (void *)server);
|
||||
} else {
|
||||
assert(!evhttp_set_cb(RUN(http), "/", _http_callback_root, (void *)server));
|
||||
assert(!evhttp_set_cb(_RUN(http), "/", _http_callback_root, (void *)server));
|
||||
}
|
||||
assert(!evhttp_set_cb(RUN(http), "/state", _http_callback_state, (void *)server));
|
||||
assert(!evhttp_set_cb(RUN(http), "/snapshot", _http_callback_snapshot, (void *)server));
|
||||
assert(!evhttp_set_cb(RUN(http), "/stream", _http_callback_stream, (void *)server));
|
||||
assert(!evhttp_set_cb(_RUN(http), "/state", _http_callback_state, (void *)server));
|
||||
assert(!evhttp_set_cb(_RUN(http), "/snapshot", _http_callback_snapshot, (void *)server));
|
||||
assert(!evhttp_set_cb(_RUN(http), "/stream", _http_callback_stream, (void *)server));
|
||||
}
|
||||
|
||||
frame_copy(STREAM(blank), EX(frame));
|
||||
EX(notify_last_width) = EX(frame->width);
|
||||
EX(notify_last_height) = EX(frame->height);
|
||||
us_frame_copy(_STREAM(blank), _EX(frame));
|
||||
_EX(notify_last_width) = _EX(frame->width);
|
||||
_EX(notify_last_height) = _EX(frame->height);
|
||||
|
||||
if (server->exit_on_no_clients > 0) {
|
||||
RUN(last_request_ts) = get_now_monotonic();
|
||||
_RUN(last_request_ts) = us_get_now_monotonic();
|
||||
struct timeval interval = {0};
|
||||
interval.tv_usec = 100000;
|
||||
assert((RUN(request_watcher) = event_new(RUN(base), -1, EV_PERSIST, _http_request_watcher, server)));
|
||||
assert(!event_add(RUN(request_watcher), &interval));
|
||||
assert((_RUN(request_watcher) = event_new(_RUN(base), -1, EV_PERSIST, _http_request_watcher, server)));
|
||||
assert(!event_add(_RUN(request_watcher), &interval));
|
||||
}
|
||||
|
||||
{
|
||||
struct timeval interval = {0};
|
||||
if (STREAM(dev->desired_fps) > 0) {
|
||||
interval.tv_usec = 1000000 / (STREAM(dev->desired_fps) * 2);
|
||||
if (_STREAM(dev->desired_fps) > 0) {
|
||||
interval.tv_usec = 1000000 / (_STREAM(dev->desired_fps) * 2);
|
||||
} else {
|
||||
interval.tv_usec = 16000; // ~60fps
|
||||
}
|
||||
assert((RUN(refresher) = event_new(RUN(base), -1, EV_PERSIST, _http_refresher, server)));
|
||||
assert(!event_add(RUN(refresher), &interval));
|
||||
assert((_RUN(refresher) = event_new(_RUN(base), -1, EV_PERSIST, _http_refresher, server)));
|
||||
assert(!event_add(_RUN(refresher), &interval));
|
||||
}
|
||||
|
||||
evhttp_set_timeout(RUN(http), server->timeout);
|
||||
evhttp_set_timeout(_RUN(http), server->timeout);
|
||||
|
||||
if (server->user[0] != '\0') {
|
||||
char *encoded_token = NULL;
|
||||
|
||||
char *raw_token;
|
||||
A_ASPRINTF(raw_token, "%s:%s", server->user, server->passwd);
|
||||
base64_encode((uint8_t *)raw_token, strlen(raw_token), &encoded_token, NULL);
|
||||
US_ASPRINTF(raw_token, "%s:%s", server->user, server->passwd);
|
||||
us_base64_encode((uint8_t *)raw_token, strlen(raw_token), &encoded_token, NULL);
|
||||
free(raw_token);
|
||||
|
||||
A_ASPRINTF(RUN(auth_token), "Basic %s", encoded_token);
|
||||
US_ASPRINTF(_RUN(auth_token), "Basic %s", encoded_token);
|
||||
free(encoded_token);
|
||||
|
||||
LOG_INFO("Using HTTP basic auth");
|
||||
US_LOG_INFO("Using HTTP basic auth");
|
||||
}
|
||||
|
||||
if (server->unix_path[0] != '\0') {
|
||||
LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
|
||||
if ((RUN(ext_fd) = evhttp_my_bind_unix(
|
||||
RUN(http),
|
||||
US_LOG_DEBUG("Binding HTTP to UNIX socket '%s' ...", server->unix_path);
|
||||
if ((_RUN(ext_fd) = us_evhttp_bind_unix(
|
||||
_RUN(http),
|
||||
server->unix_path,
|
||||
server->unix_rm,
|
||||
server->unix_mode)) < 0
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
LOG_INFO("Listening HTTP on UNIX socket '%s'", server->unix_path);
|
||||
US_LOG_INFO("Listening HTTP on UNIX socket '%s'", server->unix_path);
|
||||
|
||||
# ifdef WITH_SYSTEMD
|
||||
} else if (server->systemd) {
|
||||
LOG_DEBUG("Binding HTTP to systemd socket ...");
|
||||
if ((RUN(ext_fd) = evhttp_my_bind_systemd(RUN(http))) < 0) {
|
||||
US_LOG_DEBUG("Binding HTTP to systemd socket ...");
|
||||
if ((_RUN(ext_fd) = us_evhttp_bind_systemd(_RUN(http))) < 0) {
|
||||
return -1;
|
||||
}
|
||||
LOG_INFO("Listening systemd socket ...");
|
||||
US_LOG_INFO("Listening systemd socket ...");
|
||||
# endif
|
||||
|
||||
} else {
|
||||
LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
|
||||
if (evhttp_bind_socket(RUN(http), server->host, server->port) < 0) {
|
||||
LOG_PERROR("Can't bind HTTP on [%s]:%u", server->host, server->port)
|
||||
US_LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
|
||||
if (evhttp_bind_socket(_RUN(http), server->host, server->port) < 0) {
|
||||
US_LOG_PERROR("Can't bind HTTP on [%s]:%u", server->host, server->port)
|
||||
return -1;
|
||||
}
|
||||
LOG_INFO("Listening HTTP on [%s]:%u", server->host, server->port);
|
||||
US_LOG_INFO("Listening HTTP on [%s]:%u", server->host, server->port);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void server_loop(server_s *server) {
|
||||
LOG_INFO("Starting HTTP eventloop ...");
|
||||
event_base_dispatch(RUN(base));
|
||||
LOG_INFO("HTTP eventloop stopped");
|
||||
void us_server_loop(us_server_s *server) {
|
||||
US_LOG_INFO("Starting HTTP eventloop ...");
|
||||
event_base_dispatch(_RUN(base));
|
||||
US_LOG_INFO("HTTP eventloop stopped");
|
||||
}
|
||||
|
||||
void server_loop_break(server_s *server) {
|
||||
event_base_loopbreak(RUN(base));
|
||||
void us_server_loop_break(us_server_s *server) {
|
||||
event_base_loopbreak(_RUN(base));
|
||||
}
|
||||
|
||||
#define GET_HEADER(_key) \
|
||||
evhttp_find_header(evhttp_request_get_input_headers(request), _key)
|
||||
#define ADD_HEADER(x_key, x_value) assert(!evhttp_add_header(evhttp_request_get_output_headers(request), x_key, x_value))
|
||||
|
||||
#define ADD_HEADER(_key, _value) \
|
||||
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
|
||||
|
||||
static int _http_preprocess_request(struct evhttp_request *request, server_s *server) {
|
||||
RUN(last_request_ts) = get_now_monotonic();
|
||||
static int _http_preprocess_request(struct evhttp_request *request, us_server_s *server) {
|
||||
_RUN(last_request_ts) = us_get_now_monotonic();
|
||||
|
||||
if (server->allow_origin[0] != '\0') {
|
||||
const char *cors_headers = GET_HEADER("Access-Control-Request-Headers");
|
||||
const char *cors_method = GET_HEADER("Access-Control-Request-Method");
|
||||
const char *cors_headers = _http_get_header(request, "Access-Control-Request-Headers");
|
||||
const char *cors_method = _http_get_header(request, "Access-Control-Request-Method");
|
||||
|
||||
ADD_HEADER("Access-Control-Allow-Origin", server->allow_origin);
|
||||
ADD_HEADER("Access-Control-Allow-Credentials", "true");
|
||||
@ -240,10 +237,10 @@ static int _http_preprocess_request(struct evhttp_request *request, server_s *se
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (RUN(auth_token)) {
|
||||
const char *token = GET_HEADER("Authorization");
|
||||
if (_RUN(auth_token)) {
|
||||
const char *token = _http_get_header(request, "Authorization");
|
||||
|
||||
if (token == NULL || strcmp(token, RUN(auth_token)) != 0) {
|
||||
if (token == NULL || strcmp(token, _RUN(auth_token)) != 0) {
|
||||
ADD_HEADER("WWW-Authenticate", "Basic realm=\"Restricted area\"");
|
||||
evhttp_send_reply(request, 401, "Unauthorized", NULL);
|
||||
return -1;
|
||||
@ -294,7 +291,7 @@ static int _http_check_run_compat_action(struct evhttp_request *request, void *v
|
||||
}
|
||||
|
||||
static void _http_callback_root(struct evhttp_request *request, void *v_server) {
|
||||
server_s *server = (server_s *)v_server;
|
||||
us_server_s *server = (us_server_s *)v_server;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
COMPAT_REQUEST;
|
||||
@ -302,7 +299,7 @@ static void _http_callback_root(struct evhttp_request *request, void *v_server)
|
||||
struct evbuffer *buf;
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
|
||||
assert(evbuffer_add_printf(buf, "%s", US_HTML_INDEX_PAGE));
|
||||
ADD_HEADER("Content-Type", "text/html");
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
|
||||
@ -310,7 +307,7 @@ static void _http_callback_root(struct evhttp_request *request, void *v_server)
|
||||
}
|
||||
|
||||
static void _http_callback_static(struct evhttp_request *request, void *v_server) {
|
||||
server_s *server = (server_s *)v_server;
|
||||
us_server_s *server = (us_server_s *)v_server;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
COMPAT_REQUEST;
|
||||
@ -338,12 +335,12 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
|
||||
if ((static_path = find_static_file_path(server->static_path, decoded_path)) == NULL) {
|
||||
if ((static_path = us_find_static_file_path(server->static_path, decoded_path)) == NULL) {
|
||||
goto not_found;
|
||||
}
|
||||
|
||||
if ((fd = open(static_path, O_RDONLY)) < 0) {
|
||||
LOG_PERROR("HTTP: Can't open found static file %s", static_path);
|
||||
US_LOG_PERROR("HTTP: Can't open found static file %s", static_path);
|
||||
goto not_found;
|
||||
}
|
||||
|
||||
@ -351,11 +348,11 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
||||
struct stat st;
|
||||
|
||||
if (fstat(fd, &st) < 0) {
|
||||
LOG_PERROR("HTTP: Can't stat() found static file %s", static_path);
|
||||
US_LOG_PERROR("HTTP: Can't stat() found static file %s", static_path);
|
||||
goto not_found;
|
||||
}
|
||||
if (st.st_size > 0 && evbuffer_add_file(buf, fd, 0, st.st_size) < 0) {
|
||||
LOG_ERROR("HTTP: Can't serve static file %s", static_path);
|
||||
US_LOG_ERROR("HTTP: Can't serve static file %s", static_path);
|
||||
goto not_found;
|
||||
}
|
||||
|
||||
@ -363,7 +360,7 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
||||
// and will close it when finished transferring data
|
||||
fd = -1;
|
||||
|
||||
ADD_HEADER("Content-Type", guess_mime_type(static_path));
|
||||
ADD_HEADER("Content-Type", us_guess_mime_type(static_path));
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
goto cleanup;
|
||||
}
|
||||
@ -397,13 +394,13 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
||||
#undef COMPAT_REQUEST
|
||||
|
||||
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
|
||||
server_s *server = (server_s *)v_server;
|
||||
us_server_s *server = (us_server_s *)v_server;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
|
||||
encoder_type_e enc_type;
|
||||
us_encoder_type_e enc_type;
|
||||
unsigned enc_quality;
|
||||
encoder_get_runtime_params(STREAM(enc), &enc_type, &enc_quality);
|
||||
us_encoder_get_runtime_params(_STREAM(enc), &enc_type, &enc_quality);
|
||||
|
||||
struct evbuffer *buf;
|
||||
assert((buf = evbuffer_new()));
|
||||
@ -411,32 +408,32 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"{\"ok\": true, \"result\": {"
|
||||
" \"encoder\": {\"type\": \"%s\", \"quality\": %u},",
|
||||
encoder_type_to_string(enc_type),
|
||||
us_encoder_type_to_string(enc_type),
|
||||
enc_quality
|
||||
));
|
||||
|
||||
if (STREAM(run->h264)) {
|
||||
if (_STREAM(run->h264)) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
" \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s},",
|
||||
STREAM(h264_bitrate),
|
||||
STREAM(h264_gop),
|
||||
bool_to_string(atomic_load(&STREAM(run->h264->online)))
|
||||
_STREAM(h264_bitrate),
|
||||
_STREAM(h264_gop),
|
||||
us_bool_to_string(atomic_load(&_STREAM(run->h264->online)))
|
||||
));
|
||||
}
|
||||
|
||||
if (STREAM(sink) || STREAM(h264_sink)) {
|
||||
if (_STREAM(sink) || _STREAM(h264_sink)) {
|
||||
assert(evbuffer_add_printf(buf, " \"sinks\": {"));
|
||||
if (STREAM(sink)) {
|
||||
if (_STREAM(sink)) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"\"jpeg\": {\"has_clients\": %s}",
|
||||
bool_to_string(atomic_load(&STREAM(sink->has_clients)))
|
||||
us_bool_to_string(atomic_load(&_STREAM(sink->has_clients)))
|
||||
));
|
||||
}
|
||||
if (STREAM(h264_sink)) {
|
||||
if (_STREAM(h264_sink)) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"%s\"h264\": {\"has_clients\": %s}",
|
||||
(STREAM(sink) ? ", " : ""),
|
||||
bool_to_string(atomic_load(&STREAM(h264_sink->has_clients)))
|
||||
(_STREAM(sink) ? ", " : ""),
|
||||
us_bool_to_string(atomic_load(&_STREAM(h264_sink->has_clients)))
|
||||
));
|
||||
}
|
||||
assert(evbuffer_add_printf(buf, "},"));
|
||||
@ -446,25 +443,25 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
" \"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
|
||||
" \"online\": %s, \"desired_fps\": %u, \"captured_fps\": %u},"
|
||||
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
|
||||
(server->fake_width ? server->fake_width : EX(frame->width)),
|
||||
(server->fake_height ? server->fake_height : EX(frame->height)),
|
||||
bool_to_string(EX(frame->online)),
|
||||
STREAM(dev->desired_fps),
|
||||
EX(captured_fps),
|
||||
EX(queued_fps),
|
||||
RUN(stream_clients_count)
|
||||
(server->fake_width ? server->fake_width : _EX(frame->width)),
|
||||
(server->fake_height ? server->fake_height : _EX(frame->height)),
|
||||
us_bool_to_string(_EX(frame->online)),
|
||||
_STREAM(dev->desired_fps),
|
||||
_EX(captured_fps),
|
||||
_EX(queued_fps),
|
||||
_RUN(stream_clients_count)
|
||||
));
|
||||
|
||||
LIST_ITERATE(RUN(stream_clients), client, {
|
||||
US_LIST_ITERATE(_RUN(stream_clients), client, {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
|
||||
" \"dual_final_frames\": %s, \"zero_data\": %s, \"key\": \"%s\"}%s",
|
||||
client->id,
|
||||
client->fps,
|
||||
bool_to_string(client->extra_headers),
|
||||
bool_to_string(client->advance_headers),
|
||||
bool_to_string(client->dual_final_frames),
|
||||
bool_to_string(client->zero_data),
|
||||
us_bool_to_string(client->extra_headers),
|
||||
us_bool_to_string(client->advance_headers),
|
||||
us_bool_to_string(client->dual_final_frames),
|
||||
us_bool_to_string(client->zero_data),
|
||||
(client->key != NULL ? client->key : "0"),
|
||||
(client->next ? ", " : "")
|
||||
));
|
||||
@ -478,13 +475,13 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
}
|
||||
|
||||
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server) {
|
||||
server_s *server = (server_s *)v_server;
|
||||
us_server_s *server = (us_server_s *)v_server;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
|
||||
struct evbuffer *buf;
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(!evbuffer_add(buf, (const void *)EX(frame->data), EX(frame->used)));
|
||||
assert(!evbuffer_add(buf, (const void *)_EX(frame->data), _EX(frame->used)));
|
||||
|
||||
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
|
||||
ADD_HEADER("Pragma", "no-cache");
|
||||
@ -492,29 +489,29 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
||||
|
||||
char header_buf[256];
|
||||
|
||||
# define ADD_TIME_HEADER(_key, _value) { \
|
||||
snprintf(header_buf, 255, "%.06Lf", _value); \
|
||||
ADD_HEADER(_key, header_buf); \
|
||||
# define ADD_TIME_HEADER(x_key, x_value) { \
|
||||
snprintf(header_buf, 255, "%.06Lf", x_value); \
|
||||
ADD_HEADER(x_key, header_buf); \
|
||||
}
|
||||
|
||||
# define ADD_UNSIGNED_HEADER(_key, _value) { \
|
||||
snprintf(header_buf, 255, "%u", _value); \
|
||||
ADD_HEADER(_key, header_buf); \
|
||||
# define ADD_UNSIGNED_HEADER(x_key, x_value) { \
|
||||
snprintf(header_buf, 255, "%u", x_value); \
|
||||
ADD_HEADER(x_key, header_buf); \
|
||||
}
|
||||
|
||||
ADD_TIME_HEADER("X-Timestamp", get_now_real());
|
||||
ADD_TIME_HEADER("X-Timestamp", us_get_now_real());
|
||||
|
||||
ADD_HEADER("X-UStreamer-Online", bool_to_string(EX(frame->online)));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Dropped", EX(dropped));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Width", EX(frame->width));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Height", EX(frame->height));
|
||||
ADD_TIME_HEADER("X-UStreamer-Grab-Timestamp", EX(frame->grab_ts));
|
||||
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Timestamp", EX(frame->encode_begin_ts));
|
||||
ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", EX(frame->encode_end_ts));
|
||||
ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Timestamp", EX(expose_begin_ts));
|
||||
ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Timestamp", EX(expose_cmp_ts));
|
||||
ADD_TIME_HEADER("X-UStreamer-Expose-End-Timestamp", EX(expose_end_ts));
|
||||
ADD_TIME_HEADER("X-UStreamer-Send-Timestamp", get_now_monotonic());
|
||||
ADD_HEADER("X-UStreamer-Online", us_bool_to_string(_EX(frame->online)));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Dropped", _EX(dropped));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Width", _EX(frame->width));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Height", _EX(frame->height));
|
||||
ADD_TIME_HEADER("X-UStreamer-Grab-Timestamp", _EX(frame->grab_ts));
|
||||
ADD_TIME_HEADER("X-UStreamer-Encode-Begin-Timestamp", _EX(frame->encode_begin_ts));
|
||||
ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", _EX(frame->encode_end_ts));
|
||||
ADD_TIME_HEADER("X-UStreamer-Expose-Begin-Timestamp", _EX(expose_begin_ts));
|
||||
ADD_TIME_HEADER("X-UStreamer-Expose-Cmp-Timestamp", _EX(expose_cmp_ts));
|
||||
ADD_TIME_HEADER("X-UStreamer-Expose-End-Timestamp", _EX(expose_end_ts));
|
||||
ADD_TIME_HEADER("X-UStreamer-Send-Timestamp", us_get_now_monotonic());
|
||||
|
||||
# undef ADD_UNSUGNED_HEADER
|
||||
# undef ADD_TIME_HEADER
|
||||
@ -534,7 +531,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L791
|
||||
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L1458
|
||||
|
||||
server_s *server = (server_s *)v_server;
|
||||
us_server_s *server = (us_server_s *)v_server;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
|
||||
@ -542,8 +539,8 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
conn = evhttp_request_get_connection(request);
|
||||
|
||||
if (conn) {
|
||||
stream_client_s *client;
|
||||
A_CALLOC(client, 1);
|
||||
us_stream_client_s *client;
|
||||
US_CALLOC(client, 1);
|
||||
client->server = server;
|
||||
client->request = request;
|
||||
client->need_initial = true;
|
||||
@ -551,7 +548,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
|
||||
struct evkeyvalq params;
|
||||
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
||||
# define PARSE_PARAM(_type, _name) client->_name = uri_get_##_type(¶ms, #_name)
|
||||
# define PARSE_PARAM(x_type, x_name) client->x_name = us_uri_get_##x_type(¶ms, #x_name)
|
||||
PARSE_PARAM(string, key);
|
||||
PARSE_PARAM(true, extra_headers);
|
||||
PARSE_PARAM(true, advance_headers);
|
||||
@ -561,29 +558,29 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
evhttp_clear_headers(¶ms);
|
||||
|
||||
client->hostport = _http_get_client_hostport(request);
|
||||
client->id = get_now_id();
|
||||
client->id = us_get_now_id();
|
||||
|
||||
LIST_APPEND_C(RUN(stream_clients), client, RUN(stream_clients_count));
|
||||
US_LIST_APPEND_C(_RUN(stream_clients), client, _RUN(stream_clients_count));
|
||||
|
||||
if (RUN(stream_clients_count) == 1) {
|
||||
atomic_store(&VID(has_clients), true);
|
||||
if (_RUN(stream_clients_count) == 1) {
|
||||
atomic_store(&_VID(has_clients), true);
|
||||
# ifdef WITH_GPIO
|
||||
gpio_set_has_http_clients(true);
|
||||
us_gpio_set_has_http_clients(true);
|
||||
# endif
|
||||
}
|
||||
|
||||
LOG_INFO("HTTP: Registered client: %s, id=%" PRIx64 "; clients now: %u",
|
||||
client->hostport, client->id, RUN(stream_clients_count));
|
||||
US_LOG_INFO("HTTP: Registered client: %s, id=%" PRIx64 "; clients now: %u",
|
||||
client->hostport, client->id, _RUN(stream_clients_count));
|
||||
|
||||
struct bufferevent *buf_event = evhttp_connection_get_bufferevent(conn);
|
||||
if (server->tcp_nodelay && !RUN(ext_fd)) {
|
||||
if (server->tcp_nodelay && !_RUN(ext_fd)) {
|
||||
evutil_socket_t fd;
|
||||
int on = 1;
|
||||
|
||||
LOG_DEBUG("HTTP: Setting up TCP_NODELAY to the client %s ...", client->hostport);
|
||||
US_LOG_DEBUG("HTTP: Setting up TCP_NODELAY to the client %s ...", client->hostport);
|
||||
assert((fd = bufferevent_getfd(buf_event)) >= 0);
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)) != 0) {
|
||||
LOG_PERROR("HTTP: Can't set TCP_NODELAY to the client %s", client->hostport);
|
||||
US_LOG_PERROR("HTTP: Can't set TCP_NODELAY to the client %s", client->hostport);
|
||||
}
|
||||
}
|
||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
|
||||
@ -598,12 +595,11 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
|
||||
# define BOUNDARY "boundarydonotcross"
|
||||
|
||||
stream_client_s *client = (stream_client_s *)v_client;
|
||||
struct evhttp_request *request = client->request; // for GET_HEADER
|
||||
server_s *server = client->server;
|
||||
us_stream_client_s *client = (us_stream_client_s *)v_client;
|
||||
us_server_s *server = client->server;
|
||||
|
||||
long double now = get_now_monotonic();
|
||||
long long now_second = floor_ms(now);
|
||||
long double now = us_get_now_monotonic();
|
||||
long long now_second = us_floor_ms(now);
|
||||
|
||||
if (now_second != client->fps_accum_second) {
|
||||
client->fps = client->fps_accum;
|
||||
@ -636,14 +632,14 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
|
||||
# define ADD_ADVANCE_HEADERS \
|
||||
assert(evbuffer_add_printf(buf, \
|
||||
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, get_now_real()))
|
||||
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, us_get_now_real()))
|
||||
|
||||
if (client->need_initial) {
|
||||
assert(evbuffer_add_printf(buf, "HTTP/1.0 200 OK" RN));
|
||||
|
||||
if (client->server->allow_origin[0] != '\0') {
|
||||
const char *cors_headers = GET_HEADER("Access-Control-Request-Headers");
|
||||
const char *cors_method = GET_HEADER("Access-Control-Request-Method");
|
||||
const char *cors_headers = _http_get_header(client->request, "Access-Control-Request-Headers");
|
||||
const char *cors_method = _http_get_header(client->request, "Access-Control-Request-Method");
|
||||
|
||||
assert(evbuffer_add_printf(buf,
|
||||
"Access-Control-Allow-Origin: %s" RN
|
||||
@ -684,8 +680,8 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
"Content-Length: %zu" RN
|
||||
"X-Timestamp: %.06Lf" RN
|
||||
"%s",
|
||||
(!client->zero_data ? EX(frame->used) : 0),
|
||||
get_now_real(),
|
||||
(!client->zero_data ? _EX(frame->used) : 0),
|
||||
us_get_now_real(),
|
||||
(client->extra_headers ? "" : RN)
|
||||
));
|
||||
if (client->extra_headers) {
|
||||
@ -704,25 +700,25 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
"X-UStreamer-Send-Time: %.06Lf" RN
|
||||
"X-UStreamer-Latency: %.06Lf" RN
|
||||
RN,
|
||||
bool_to_string(EX(frame->online)),
|
||||
EX(dropped),
|
||||
EX(frame->width),
|
||||
EX(frame->height),
|
||||
us_bool_to_string(_EX(frame->online)),
|
||||
_EX(dropped),
|
||||
_EX(frame->width),
|
||||
_EX(frame->height),
|
||||
client->fps,
|
||||
EX(frame->grab_ts),
|
||||
EX(frame->encode_begin_ts),
|
||||
EX(frame->encode_end_ts),
|
||||
EX(expose_begin_ts),
|
||||
EX(expose_cmp_ts),
|
||||
EX(expose_end_ts),
|
||||
_EX(frame->grab_ts),
|
||||
_EX(frame->encode_begin_ts),
|
||||
_EX(frame->encode_end_ts),
|
||||
_EX(expose_begin_ts),
|
||||
_EX(expose_cmp_ts),
|
||||
_EX(expose_end_ts),
|
||||
now,
|
||||
now - EX(frame->grab_ts)
|
||||
now - _EX(frame->grab_ts)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (!client->zero_data) {
|
||||
assert(!evbuffer_add(buf, (void *)EX(frame->data), EX(frame->used)));
|
||||
assert(!evbuffer_add(buf, (void *)_EX(frame->data), _EX(frame->used)));
|
||||
}
|
||||
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
||||
|
||||
@ -741,21 +737,21 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
}
|
||||
|
||||
static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UNUSED short what, void *v_client) {
|
||||
stream_client_s *client = (stream_client_s *)v_client;
|
||||
server_s *server = client->server;
|
||||
us_stream_client_s *client = (us_stream_client_s *)v_client;
|
||||
us_server_s *server = client->server;
|
||||
|
||||
LIST_REMOVE_C(RUN(stream_clients), client, RUN(stream_clients_count));
|
||||
US_LIST_REMOVE_C(_RUN(stream_clients), client, _RUN(stream_clients_count));
|
||||
|
||||
if (RUN(stream_clients_count) == 0) {
|
||||
atomic_store(&VID(has_clients), false);
|
||||
if (_RUN(stream_clients_count) == 0) {
|
||||
atomic_store(&_VID(has_clients), false);
|
||||
# ifdef WITH_GPIO
|
||||
gpio_set_has_http_clients(false);
|
||||
us_gpio_set_has_http_clients(false);
|
||||
# endif
|
||||
}
|
||||
|
||||
char *reason = bufferevent_my_format_reason(what);
|
||||
LOG_INFO("HTTP: Disconnected client: %s, id=%" PRIx64 ", %s; clients now: %u",
|
||||
client->hostport, client->id, reason, RUN(stream_clients_count));
|
||||
char *reason = us_bufferevent_format_reason(what);
|
||||
US_LOG_INFO("HTTP: Disconnected client: %s, id=%" PRIx64 ", %s; clients now: %u",
|
||||
client->hostport, client->id, reason, _RUN(stream_clients_count));
|
||||
free(reason);
|
||||
|
||||
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
|
||||
@ -768,11 +764,11 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
free(client);
|
||||
}
|
||||
|
||||
static void _http_queue_send_stream(server_s *server, bool stream_updated, bool frame_updated) {
|
||||
static void _http_queue_send_stream(us_server_s *server, bool stream_updated, bool frame_updated) {
|
||||
bool has_clients = false;
|
||||
bool queued = false;
|
||||
|
||||
LIST_ITERATE(RUN(stream_clients), client, {
|
||||
US_LIST_ITERATE(_RUN(stream_clients), client, {
|
||||
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
|
||||
if (conn) {
|
||||
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
||||
@ -808,45 +804,45 @@ static void _http_queue_send_stream(server_s *server, bool stream_updated, bool
|
||||
if (queued) {
|
||||
static unsigned queued_fps_accum = 0;
|
||||
static long long queued_fps_second = 0;
|
||||
long long now = floor_ms(get_now_monotonic());
|
||||
long long now = us_floor_ms(us_get_now_monotonic());
|
||||
if (now != queued_fps_second) {
|
||||
EX(queued_fps) = queued_fps_accum;
|
||||
_EX(queued_fps) = queued_fps_accum;
|
||||
queued_fps_accum = 0;
|
||||
queued_fps_second = now;
|
||||
}
|
||||
queued_fps_accum += 1;
|
||||
} else if (!has_clients) {
|
||||
EX(queued_fps) = 0;
|
||||
_EX(queued_fps) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void _http_request_watcher(UNUSED int fd, UNUSED short what, void *v_server) {
|
||||
server_s *server = (server_s *)v_server;
|
||||
const long double now = get_now_monotonic();
|
||||
us_server_s *server = (us_server_s *)v_server;
|
||||
const long double now = us_get_now_monotonic();
|
||||
|
||||
if (stream_has_clients(RUN(stream))) {
|
||||
RUN(last_request_ts) = now;
|
||||
} else if (RUN(last_request_ts) + server->exit_on_no_clients < now) {
|
||||
LOG_INFO("HTTP: No requests or HTTP/sink clients found in last %u seconds, exiting ...",
|
||||
if (us_stream_has_clients(_RUN(stream))) {
|
||||
_RUN(last_request_ts) = now;
|
||||
} else if (_RUN(last_request_ts) + server->exit_on_no_clients < now) {
|
||||
US_LOG_INFO("HTTP: No requests or HTTP/sink clients found in last %u seconds, exiting ...",
|
||||
server->exit_on_no_clients);
|
||||
process_suicide();
|
||||
RUN(last_request_ts) = now;
|
||||
us_process_suicide();
|
||||
_RUN(last_request_ts) = now;
|
||||
}
|
||||
}
|
||||
|
||||
static void _http_refresher(UNUSED int fd, UNUSED short what, void *v_server) {
|
||||
server_s *server = (server_s *)v_server;
|
||||
us_server_s *server = (us_server_s *)v_server;
|
||||
bool stream_updated = false;
|
||||
bool frame_updated = false;
|
||||
|
||||
if (atomic_load(&VID(updated))) {
|
||||
if (atomic_load(&_VID(updated))) {
|
||||
frame_updated = _expose_new_frame(server);
|
||||
stream_updated = true;
|
||||
} else if (EX(expose_end_ts) + 1 < get_now_monotonic()) {
|
||||
LOG_DEBUG("HTTP: Repeating exposed ...");
|
||||
EX(expose_begin_ts) = get_now_monotonic();
|
||||
EX(expose_cmp_ts) = EX(expose_begin_ts);
|
||||
EX(expose_end_ts) = EX(expose_begin_ts);
|
||||
} else if (_EX(expose_end_ts) + 1 < us_get_now_monotonic()) {
|
||||
US_LOG_DEBUG("HTTP: Repeating exposed ...");
|
||||
_EX(expose_begin_ts) = us_get_now_monotonic();
|
||||
_EX(expose_cmp_ts) = _EX(expose_begin_ts);
|
||||
_EX(expose_end_ts) = _EX(expose_begin_ts);
|
||||
frame_updated = true;
|
||||
stream_updated = true;
|
||||
}
|
||||
@ -857,68 +853,67 @@ static void _http_refresher(UNUSED int fd, UNUSED short what, void *v_server) {
|
||||
frame_updated
|
||||
&& server->notify_parent
|
||||
&& (
|
||||
EX(notify_last_online) != EX(frame->online)
|
||||
|| EX(notify_last_width) != EX(frame->width)
|
||||
|| EX(notify_last_height) != EX(frame->height)
|
||||
_EX(notify_last_online) != _EX(frame->online)
|
||||
|| _EX(notify_last_width) != _EX(frame->width)
|
||||
|| _EX(notify_last_height) != _EX(frame->height)
|
||||
)
|
||||
) {
|
||||
EX(notify_last_online) = EX(frame->online);
|
||||
EX(notify_last_width) = EX(frame->width);
|
||||
EX(notify_last_height) = EX(frame->height);
|
||||
process_notify_parent();
|
||||
_EX(notify_last_online) = _EX(frame->online);
|
||||
_EX(notify_last_width) = _EX(frame->width);
|
||||
_EX(notify_last_height) = _EX(frame->height);
|
||||
us_process_notify_parent();
|
||||
}
|
||||
}
|
||||
|
||||
static bool _expose_new_frame(server_s *server) {
|
||||
static bool _expose_new_frame(us_server_s *server) {
|
||||
bool updated = false;
|
||||
|
||||
A_MUTEX_LOCK(&VID(mutex));
|
||||
US_MUTEX_LOCK(&_VID(mutex));
|
||||
|
||||
LOG_DEBUG("HTTP: Updating exposed frame (online=%d) ...", VID(frame->online));
|
||||
US_LOG_DEBUG("HTTP: Updating exposed frame (online=%d) ...", _VID(frame->online));
|
||||
|
||||
EX(captured_fps) = VID(captured_fps);
|
||||
EX(expose_begin_ts) = get_now_monotonic();
|
||||
_EX(captured_fps) = _VID(captured_fps);
|
||||
_EX(expose_begin_ts) = us_get_now_monotonic();
|
||||
|
||||
if (server->drop_same_frames && VID(frame->online)) {
|
||||
if (server->drop_same_frames && _VID(frame->online)) {
|
||||
bool need_drop = false;
|
||||
bool maybe_same = false;
|
||||
if (
|
||||
(need_drop = (EX(dropped) < server->drop_same_frames))
|
||||
&& (maybe_same = frame_compare(EX(frame), VID(frame)))
|
||||
(need_drop = (_EX(dropped) < server->drop_same_frames))
|
||||
&& (maybe_same = us_frame_compare(_EX(frame), _VID(frame)))
|
||||
) {
|
||||
EX(expose_cmp_ts) = get_now_monotonic();
|
||||
EX(expose_end_ts) = EX(expose_cmp_ts);
|
||||
LOG_VERBOSE("HTTP: Dropped same frame number %u; cmp_time=%.06Lf",
|
||||
EX(dropped), EX(expose_cmp_ts) - EX(expose_begin_ts));
|
||||
EX(dropped) += 1;
|
||||
_EX(expose_cmp_ts) = us_get_now_monotonic();
|
||||
_EX(expose_end_ts) = _EX(expose_cmp_ts);
|
||||
US_LOG_VERBOSE("HTTP: Dropped same frame number %u; cmp_time=%.06Lf",
|
||||
_EX(dropped), _EX(expose_cmp_ts) - _EX(expose_begin_ts));
|
||||
_EX(dropped) += 1;
|
||||
goto not_updated;
|
||||
} else {
|
||||
EX(expose_cmp_ts) = get_now_monotonic();
|
||||
LOG_VERBOSE("HTTP: Passed same frame check (need_drop=%d, maybe_same=%d); cmp_time=%.06Lf",
|
||||
need_drop, maybe_same, (EX(expose_cmp_ts) - EX(expose_begin_ts)));
|
||||
_EX(expose_cmp_ts) = us_get_now_monotonic();
|
||||
US_LOG_VERBOSE("HTTP: Passed same frame check (need_drop=%d, maybe_same=%d); cmp_time=%.06Lf",
|
||||
need_drop, maybe_same, (_EX(expose_cmp_ts) - _EX(expose_begin_ts)));
|
||||
}
|
||||
}
|
||||
|
||||
frame_copy(VID(frame), EX(frame));
|
||||
us_frame_copy(_VID(frame), _EX(frame));
|
||||
|
||||
EX(dropped) = 0;
|
||||
EX(expose_cmp_ts) = EX(expose_begin_ts);
|
||||
EX(expose_end_ts) = get_now_monotonic();
|
||||
_EX(dropped) = 0;
|
||||
_EX(expose_cmp_ts) = _EX(expose_begin_ts);
|
||||
_EX(expose_end_ts) = us_get_now_monotonic();
|
||||
|
||||
LOG_VERBOSE("HTTP: Exposed frame: online=%d, exp_time=%.06Lf",
|
||||
EX(frame->online), EX(expose_end_ts) - EX(expose_begin_ts));
|
||||
US_LOG_VERBOSE("HTTP: Exposed frame: online=%d, exp_time=%.06Lf",
|
||||
_EX(frame->online), _EX(expose_end_ts) - _EX(expose_begin_ts));
|
||||
|
||||
updated = true;
|
||||
not_updated:
|
||||
atomic_store(&VID(updated), false);
|
||||
A_MUTEX_UNLOCK(&VID(mutex));
|
||||
atomic_store(&_VID(updated), false);
|
||||
US_MUTEX_UNLOCK(&_VID(mutex));
|
||||
return updated;
|
||||
}
|
||||
|
||||
#undef EX
|
||||
#undef VID
|
||||
#undef STREAM
|
||||
#undef RUN
|
||||
static const char *_http_get_header(struct evhttp_request *request, const char *key) {
|
||||
return evhttp_find_header(evhttp_request_get_input_headers(request), key);
|
||||
}
|
||||
|
||||
static char *_http_get_client_hostport(struct evhttp_request *request) {
|
||||
char *addr = NULL;
|
||||
@ -930,7 +925,7 @@ static char *_http_get_client_hostport(struct evhttp_request *request) {
|
||||
assert(addr = strdup(peer));
|
||||
}
|
||||
|
||||
const char *xff = GET_HEADER("X-Forwarded-For");
|
||||
const char *xff = _http_get_header(request, "X-Forwarded-For");
|
||||
if (xff) {
|
||||
if (addr) {
|
||||
free(addr);
|
||||
@ -949,9 +944,7 @@ static char *_http_get_client_hostport(struct evhttp_request *request) {
|
||||
}
|
||||
|
||||
char *hostport;
|
||||
A_ASPRINTF(hostport, "[%s]:%u", addr, port);
|
||||
US_ASPRINTF(hostport, "[%s]:%u", addr, port);
|
||||
free(addr);
|
||||
return hostport;
|
||||
}
|
||||
|
||||
#undef GET_HEADER
|
||||
|
||||
@ -76,30 +76,30 @@
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct stream_client_sx {
|
||||
struct server_sx *server;
|
||||
struct evhttp_request *request;
|
||||
typedef struct us_stream_client_sx {
|
||||
struct us_server_sx *server;
|
||||
struct evhttp_request *request;
|
||||
|
||||
char *key;
|
||||
bool extra_headers;
|
||||
bool advance_headers;
|
||||
bool dual_final_frames;
|
||||
bool zero_data;
|
||||
char *key;
|
||||
bool extra_headers;
|
||||
bool advance_headers;
|
||||
bool dual_final_frames;
|
||||
bool zero_data;
|
||||
|
||||
char *hostport;
|
||||
uint64_t id;
|
||||
bool need_initial;
|
||||
bool need_first_frame;
|
||||
bool updated_prev;
|
||||
unsigned fps;
|
||||
unsigned fps_accum;
|
||||
long long fps_accum_second;
|
||||
char *hostport;
|
||||
uint64_t id;
|
||||
bool need_initial;
|
||||
bool need_first_frame;
|
||||
bool updated_prev;
|
||||
unsigned fps;
|
||||
unsigned fps_accum;
|
||||
long long fps_accum_second;
|
||||
|
||||
LIST_STRUCT(struct stream_client_sx);
|
||||
} stream_client_s;
|
||||
US_LIST_STRUCT(struct us_stream_client_sx);
|
||||
} us_stream_client_s;
|
||||
|
||||
typedef struct {
|
||||
frame_s *frame;
|
||||
us_frame_s *frame;
|
||||
unsigned captured_fps;
|
||||
unsigned queued_fps;
|
||||
unsigned dropped;
|
||||
@ -107,10 +107,10 @@ typedef struct {
|
||||
long double expose_cmp_ts;
|
||||
long double expose_end_ts;
|
||||
|
||||
bool notify_last_online;
|
||||
unsigned notify_last_width;
|
||||
unsigned notify_last_height;
|
||||
} exposed_s;
|
||||
bool notify_last_online;
|
||||
unsigned notify_last_width;
|
||||
unsigned notify_last_height;
|
||||
} us_exposed_s;
|
||||
|
||||
typedef struct {
|
||||
struct event_base *base;
|
||||
@ -123,14 +123,14 @@ typedef struct {
|
||||
long double last_request_ts;
|
||||
|
||||
struct event *refresher;
|
||||
stream_s *stream;
|
||||
exposed_s *exposed;
|
||||
us_stream_s *stream;
|
||||
us_exposed_s *exposed;
|
||||
|
||||
stream_client_s *stream_clients;
|
||||
us_stream_client_s *stream_clients;
|
||||
unsigned stream_clients_count;
|
||||
} server_runtime_s;
|
||||
} us_server_runtime_s;
|
||||
|
||||
typedef struct server_sx {
|
||||
typedef struct us_server_sx {
|
||||
char *host;
|
||||
unsigned port;
|
||||
|
||||
@ -157,13 +157,13 @@ typedef struct server_sx {
|
||||
bool notify_parent;
|
||||
unsigned exit_on_no_clients;
|
||||
|
||||
server_runtime_s *run;
|
||||
} server_s;
|
||||
us_server_runtime_s *run;
|
||||
} us_server_s;
|
||||
|
||||
|
||||
server_s *server_init(stream_s *stream);
|
||||
void server_destroy(server_s *server);
|
||||
us_server_s *us_server_init(us_stream_s *stream);
|
||||
void us_server_destroy(us_server_s *server);
|
||||
|
||||
int server_listen(server_s *server);
|
||||
void server_loop(server_s *server);
|
||||
void server_loop_break(server_s *server);
|
||||
int us_server_listen(us_server_s *server);
|
||||
void us_server_loop(us_server_s *server);
|
||||
void us_server_loop_break(us_server_s *server);
|
||||
|
||||
@ -23,29 +23,29 @@
|
||||
#include "static.h"
|
||||
|
||||
|
||||
char *find_static_file_path(const char *root_path, const char *request_path) {
|
||||
char *us_find_static_file_path(const char *root_path, const char *request_path) {
|
||||
char *path = NULL;
|
||||
|
||||
char *simplified_path = simplify_request_path(request_path);
|
||||
char *simplified_path = us_simplify_request_path(request_path);
|
||||
if (simplified_path[0] == '\0') {
|
||||
LOG_VERBOSE("HTTP: Invalid request path %s to static", request_path);
|
||||
US_LOG_VERBOSE("HTTP: Invalid request path %s to static", request_path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
|
||||
US_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
|
||||
sprintf(path, "%s/%s", root_path, simplified_path);
|
||||
|
||||
struct stat st;
|
||||
# define LOAD_STAT { \
|
||||
if (lstat(path, &st) < 0) { \
|
||||
LOG_VERBOSE_PERROR("HTTP: Can't stat() static path %s", path); \
|
||||
US_LOG_VERBOSE_PERROR("HTTP: Can't stat() static path %s", path); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
LOAD_STAT;
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
LOG_VERBOSE("HTTP: Requested static path %s is a directory, trying %s/index.html", path, path);
|
||||
US_LOG_VERBOSE("HTTP: Requested static path %s is a directory, trying %s/index.html", path, path);
|
||||
strcat(path, "/index.html");
|
||||
LOAD_STAT;
|
||||
}
|
||||
@ -53,12 +53,12 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
|
||||
# undef LOAD_STAT
|
||||
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
LOG_VERBOSE("HTTP: Not a regular file: %s", path);
|
||||
US_LOG_VERBOSE("HTTP: Not a regular file: %s", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (access(path, R_OK) < 0) {
|
||||
LOG_VERBOSE_PERROR("HTTP: Can't access() R_OK file %s", path);
|
||||
US_LOG_VERBOSE_PERROR("HTTP: Can't access() R_OK file %s", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
@ -35,4 +35,4 @@
|
||||
#include "path.h"
|
||||
|
||||
|
||||
char *find_static_file_path(const char *root_path, const char *request_path);
|
||||
char *us_find_static_file_path(const char *root_path, const char *request_path);
|
||||
|
||||
@ -23,10 +23,10 @@
|
||||
#include "systemd.h"
|
||||
|
||||
|
||||
evutil_socket_t evhttp_my_bind_systemd(struct evhttp *http) {
|
||||
evutil_socket_t us_evhttp_bind_systemd(struct evhttp *http) {
|
||||
int fds = sd_listen_fds(1);
|
||||
if (fds < 1) {
|
||||
LOG_ERROR("No available systemd sockets");
|
||||
US_LOG_ERROR("No available systemd sockets");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ evutil_socket_t evhttp_my_bind_systemd(struct evhttp *http) {
|
||||
assert(!evutil_make_socket_nonblocking(fd));
|
||||
|
||||
if (evhttp_accept_socket(http, fd) < 0) {
|
||||
LOG_PERROR("Can't evhttp_accept_socket() systemd socket");
|
||||
US_LOG_PERROR("Can't evhttp_accept_socket() systemd socket");
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
|
||||
@ -34,4 +34,4 @@
|
||||
#include "../../../libs/logging.h"
|
||||
|
||||
|
||||
evutil_socket_t evhttp_my_bind_systemd(struct evhttp *http);
|
||||
evutil_socket_t us_evhttp_bind_systemd(struct evhttp *http);
|
||||
|
||||
@ -23,13 +23,13 @@
|
||||
#include "unix.h"
|
||||
|
||||
|
||||
evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode) {
|
||||
evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode) {
|
||||
struct sockaddr_un addr = {0};
|
||||
|
||||
# define MAX_SUN_PATH (sizeof(addr.sun_path) - 1)
|
||||
|
||||
if (strlen(path) > MAX_SUN_PATH) {
|
||||
LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH);
|
||||
US_LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -44,24 +44,24 @@ evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool
|
||||
|
||||
if (rm && unlink(path) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
LOG_PERROR("Can't remove old UNIX socket '%s'", path);
|
||||
US_LOG_PERROR("Can't remove old UNIX socket '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
|
||||
LOG_PERROR("Can't bind HTTP to UNIX socket '%s'", path);
|
||||
US_LOG_PERROR("Can't bind HTTP to UNIX socket '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
if (mode && chmod(path, mode) < 0) {
|
||||
LOG_PERROR("Can't set permissions %o to UNIX socket '%s'", mode, path);
|
||||
US_LOG_PERROR("Can't set permissions %o to UNIX socket '%s'", mode, path);
|
||||
return -1;
|
||||
}
|
||||
if (listen(fd, 128) < 0) {
|
||||
LOG_PERROR("Can't listen UNIX socket '%s'", path);
|
||||
US_LOG_PERROR("Can't listen UNIX socket '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
if (evhttp_accept_socket(http, fd) < 0) {
|
||||
LOG_PERROR("Can't evhttp_accept_socket() UNIX socket '%s'", path);
|
||||
US_LOG_PERROR("Can't evhttp_accept_socket() UNIX socket '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
|
||||
@ -39,4 +39,4 @@
|
||||
#include "../../libs/logging.h"
|
||||
|
||||
|
||||
evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode);
|
||||
evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode);
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
#include "uri.h"
|
||||
|
||||
|
||||
bool uri_get_true(struct evkeyvalq *params, const char *key) {
|
||||
bool us_uri_get_true(struct evkeyvalq *params, const char *key) {
|
||||
const char *value_str = evhttp_find_header(params, key);
|
||||
if (value_str != NULL) {
|
||||
if (
|
||||
@ -37,7 +37,7 @@ bool uri_get_true(struct evkeyvalq *params, const char *key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char *uri_get_string(struct evkeyvalq *params, const char *key) {
|
||||
char *us_uri_get_string(struct evkeyvalq *params, const char *key) {
|
||||
const char *value_str = evhttp_find_header(params, key);
|
||||
if (value_str != NULL) {
|
||||
return evhttp_encode_uri(value_str);
|
||||
|
||||
@ -29,5 +29,5 @@
|
||||
#include <event2/keyvalq_struct.h>
|
||||
|
||||
|
||||
bool uri_get_true(struct evkeyvalq *params, const char *key);
|
||||
char *uri_get_string(struct evkeyvalq *params, const char *key);
|
||||
bool us_uri_get_true(struct evkeyvalq *params, const char *key);
|
||||
char *us_uri_get_string(struct evkeyvalq *params, const char *key);
|
||||
|
||||
@ -23,29 +23,31 @@
|
||||
#include "m2m.h"
|
||||
|
||||
|
||||
static m2m_encoder_s *_m2m_encoder_init(
|
||||
static us_m2m_encoder_s *_m2m_encoder_init(
|
||||
const char *name, const char *path, unsigned output_format,
|
||||
unsigned fps, unsigned bitrate, unsigned gop, unsigned quality, bool allow_dma);
|
||||
|
||||
static void _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame);
|
||||
static void _m2m_encoder_prepare(us_m2m_encoder_s *enc, const us_frame_s *frame);
|
||||
|
||||
static int _m2m_encoder_init_buffers(
|
||||
m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
m2m_buffer_s **bufs_ptr, unsigned *n_bufs_ptr, bool dma);
|
||||
us_m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
us_m2m_buffer_s **bufs_ptr, unsigned *n_bufs_ptr, bool dma);
|
||||
|
||||
static void _m2m_encoder_cleanup(m2m_encoder_s *enc);
|
||||
static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc);
|
||||
|
||||
static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, frame_s *dest, bool force_key);
|
||||
static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key);
|
||||
|
||||
|
||||
#define E_LOG_ERROR(_msg, ...) LOG_ERROR("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define E_LOG_PERROR(_msg, ...) LOG_PERROR("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define E_LOG_INFO(_msg, ...) LOG_INFO("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define E_LOG_VERBOSE(_msg, ...) LOG_VERBOSE("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define E_LOG_DEBUG(_msg, ...) LOG_DEBUG("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_ERROR(x_msg, ...) US_LOG_ERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_PERROR(x_msg, ...) US_LOG_PERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_INFO(x_msg, ...) US_LOG_INFO("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
|
||||
#define _RUN(x_next) enc->run->x_next
|
||||
|
||||
|
||||
m2m_encoder_s *m2m_h264_encoder_init(const char *name, const char *path, unsigned bitrate, unsigned gop) {
|
||||
us_m2m_encoder_s *us_m2m_h264_encoder_init(const char *name, const char *path, unsigned bitrate, unsigned gop) {
|
||||
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L2100
|
||||
// По логике вещей правильно 0, но почему-то на низких разрешениях типа 640x480
|
||||
// енкодер через несколько секунд перестает производить корректные фреймы.
|
||||
@ -53,7 +55,7 @@ m2m_encoder_s *m2m_h264_encoder_init(const char *name, const char *path, unsigne
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_H264, 30, bitrate, gop, 0, true);
|
||||
}
|
||||
|
||||
m2m_encoder_s *m2m_mjpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
||||
us_m2m_encoder_s *us_m2m_mjpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
||||
const double b_min = 25;
|
||||
const double b_max = 20000;
|
||||
const double step = 25;
|
||||
@ -65,67 +67,65 @@ m2m_encoder_s *m2m_mjpeg_encoder_init(const char *name, const char *path, unsign
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_MJPEG, 30, bitrate, 0, 0, true);
|
||||
}
|
||||
|
||||
m2m_encoder_s *m2m_jpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
||||
us_m2m_encoder_s *us_m2m_jpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
||||
// FIXME: DMA не работает
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_JPEG, 30, 0, 0, quality, false);
|
||||
}
|
||||
|
||||
void m2m_encoder_destroy(m2m_encoder_s *enc) {
|
||||
E_LOG_INFO("Destroying encoder ...");
|
||||
void us_m2m_encoder_destroy(us_m2m_encoder_s *enc) {
|
||||
_E_LOG_INFO("Destroying encoder ...");
|
||||
_m2m_encoder_cleanup(enc);
|
||||
free(enc->path);
|
||||
free(enc->name);
|
||||
free(enc);
|
||||
}
|
||||
|
||||
#define RUN(_next) enc->run->_next
|
||||
|
||||
int m2m_encoder_compress(m2m_encoder_s *enc, const frame_s *src, frame_s *dest, bool force_key) {
|
||||
frame_encoding_begin(src, dest, (enc->output_format == V4L2_PIX_FMT_MJPEG ? V4L2_PIX_FMT_JPEG : enc->output_format));
|
||||
int us_m2m_encoder_compress(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key) {
|
||||
us_frame_encoding_begin(src, dest, (enc->output_format == V4L2_PIX_FMT_MJPEG ? V4L2_PIX_FMT_JPEG : enc->output_format));
|
||||
|
||||
if (
|
||||
RUN(width) != src->width
|
||||
|| RUN(height) != src->height
|
||||
|| RUN(input_format) != src->format
|
||||
|| RUN(stride) != src->stride
|
||||
|| RUN(dma) != (enc->allow_dma && src->dma_fd >= 0)
|
||||
_RUN(width) != src->width
|
||||
|| _RUN(height) != src->height
|
||||
|| _RUN(input_format) != src->format
|
||||
|| _RUN(stride) != src->stride
|
||||
|| _RUN(dma) != (enc->allow_dma && src->dma_fd >= 0)
|
||||
) {
|
||||
_m2m_encoder_prepare(enc, src);
|
||||
}
|
||||
if (!RUN(ready)) { // Already prepared but failed
|
||||
if (!_RUN(ready)) { // Already prepared but failed
|
||||
return -1;
|
||||
}
|
||||
|
||||
force_key = (enc->output_format == V4L2_PIX_FMT_H264 && (force_key || RUN(last_online) != src->online));
|
||||
force_key = (enc->output_format == V4L2_PIX_FMT_H264 && (force_key || _RUN(last_online) != src->online));
|
||||
|
||||
if (_m2m_encoder_compress_raw(enc, src, dest, force_key) < 0) {
|
||||
_m2m_encoder_cleanup(enc);
|
||||
E_LOG_ERROR("Encoder destroyed due an error (compress)");
|
||||
_E_LOG_ERROR("Encoder destroyed due an error (compress)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
frame_encoding_end(dest);
|
||||
us_frame_encoding_end(dest);
|
||||
|
||||
E_LOG_VERBOSE("Compressed new frame: size=%zu, time=%0.3Lf, force_key=%d",
|
||||
_E_LOG_VERBOSE("Compressed new frame: size=%zu, time=%0.3Lf, force_key=%d",
|
||||
dest->used, dest->encode_end_ts - dest->encode_begin_ts, force_key);
|
||||
|
||||
RUN(last_online) = src->online;
|
||||
_RUN(last_online) = src->online;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static m2m_encoder_s *_m2m_encoder_init(
|
||||
static us_m2m_encoder_s *_m2m_encoder_init(
|
||||
const char *name, const char *path, unsigned output_format,
|
||||
unsigned fps, unsigned bitrate, unsigned gop, unsigned quality, bool allow_dma) {
|
||||
|
||||
LOG_INFO("%s: Initializing encoder ...", name);
|
||||
US_LOG_INFO("%s: Initializing encoder ...", name);
|
||||
|
||||
m2m_encoder_runtime_s *run;
|
||||
A_CALLOC(run, 1);
|
||||
us_m2m_encoder_runtime_s *run;
|
||||
US_CALLOC(run, 1);
|
||||
run->last_online = -1;
|
||||
run->fd = -1;
|
||||
|
||||
m2m_encoder_s *enc;
|
||||
A_CALLOC(enc, 1);
|
||||
us_m2m_encoder_s *enc;
|
||||
US_CALLOC(enc, 1);
|
||||
assert(enc->name = strdup(name));
|
||||
if (path == NULL) {
|
||||
assert(enc->path = strdup(output_format == V4L2_PIX_FMT_JPEG ? "/dev/video31" : "/dev/video11"));
|
||||
@ -142,45 +142,45 @@ static m2m_encoder_s *_m2m_encoder_init(
|
||||
return enc;
|
||||
}
|
||||
|
||||
#define E_XIOCTL(_request, _value, _msg, ...) { \
|
||||
if (xioctl(RUN(fd), _request, _value) < 0) { \
|
||||
E_LOG_PERROR(_msg, ##__VA_ARGS__); \
|
||||
#define _E_XIOCTL(x_request, x_value, x_msg, ...) { \
|
||||
if (us_xioctl(_RUN(fd), x_request, x_value) < 0) { \
|
||||
_E_LOG_PERROR(x_msg, ##__VA_ARGS__); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
static void _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame) {
|
||||
static void _m2m_encoder_prepare(us_m2m_encoder_s *enc, const us_frame_s *frame) {
|
||||
bool dma = (enc->allow_dma && frame->dma_fd >= 0);
|
||||
|
||||
E_LOG_INFO("Configuring encoder: DMA=%d ...", dma);
|
||||
_E_LOG_INFO("Configuring encoder: DMA=%d ...", dma);
|
||||
|
||||
_m2m_encoder_cleanup(enc);
|
||||
|
||||
RUN(width) = frame->width;
|
||||
RUN(height) = frame->height;
|
||||
RUN(input_format) = frame->format;
|
||||
RUN(stride) = frame->stride;
|
||||
RUN(dma) = dma;
|
||||
_RUN(width) = frame->width;
|
||||
_RUN(height) = frame->height;
|
||||
_RUN(input_format) = frame->format;
|
||||
_RUN(stride) = frame->stride;
|
||||
_RUN(dma) = dma;
|
||||
|
||||
if ((RUN(fd) = open(enc->path, O_RDWR)) < 0) {
|
||||
E_LOG_PERROR("Can't open encoder device");
|
||||
if ((_RUN(fd) = open(enc->path, O_RDWR)) < 0) {
|
||||
_E_LOG_PERROR("Can't open encoder device");
|
||||
goto error;
|
||||
}
|
||||
E_LOG_DEBUG("Encoder device fd=%d opened", RUN(fd));
|
||||
_E_LOG_DEBUG("Encoder device fd=%d opened", _RUN(fd));
|
||||
|
||||
# define SET_OPTION(_cid, _value) { \
|
||||
struct v4l2_control _ctl = {0}; \
|
||||
_ctl.id = _cid; \
|
||||
_ctl.value = _value; \
|
||||
E_LOG_DEBUG("Configuring option " #_cid " ..."); \
|
||||
E_XIOCTL(VIDIOC_S_CTRL, &_ctl, "Can't set option " #_cid); \
|
||||
# define SET_OPTION(x_cid, x_value) { \
|
||||
struct v4l2_control m_ctl = {0}; \
|
||||
m_ctl.id = x_cid; \
|
||||
m_ctl.value = x_value; \
|
||||
_E_LOG_DEBUG("Configuring option " #x_cid " ..."); \
|
||||
_E_XIOCTL(VIDIOC_S_CTRL, &m_ctl, "Can't set option " #x_cid); \
|
||||
}
|
||||
|
||||
if (enc->output_format == V4L2_PIX_FMT_H264) {
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, enc->gop);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_PROFILE, V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE);
|
||||
if (RUN(width) * RUN(height) <= 1920 * 1080) { // https://forums.raspberrypi.com/viewtopic.php?t=291447#p1762296
|
||||
if (_RUN(width) * _RUN(height) <= 1920 * 1080) { // https://forums.raspberrypi.com/viewtopic.php?t=291447#p1762296
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_4_0);
|
||||
} else {
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_5_1);
|
||||
@ -199,35 +199,35 @@ static void _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame) {
|
||||
{
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
fmt.fmt.pix_mp.width = RUN(width);
|
||||
fmt.fmt.pix_mp.height = RUN(height);
|
||||
fmt.fmt.pix_mp.pixelformat = RUN(input_format);
|
||||
fmt.fmt.pix_mp.width = _RUN(width);
|
||||
fmt.fmt.pix_mp.height = _RUN(height);
|
||||
fmt.fmt.pix_mp.pixelformat = _RUN(input_format);
|
||||
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
|
||||
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_JPEG; // libcamera currently has no means to request the right colour space
|
||||
fmt.fmt.pix_mp.num_planes = 1;
|
||||
// fmt.fmt.pix_mp.plane_fmt[0].bytesperline = RUN(stride);
|
||||
E_LOG_DEBUG("Configuring INPUT format ...");
|
||||
E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set INPUT format");
|
||||
// fmt.fmt.pix_mp.plane_fmt[0].bytesperline = _RUN(stride);
|
||||
_E_LOG_DEBUG("Configuring INPUT format ...");
|
||||
_E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set INPUT format");
|
||||
}
|
||||
|
||||
{
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
fmt.fmt.pix_mp.width = RUN(width);
|
||||
fmt.fmt.pix_mp.height = RUN(height);
|
||||
fmt.fmt.pix_mp.width = _RUN(width);
|
||||
fmt.fmt.pix_mp.height = _RUN(height);
|
||||
fmt.fmt.pix_mp.pixelformat = enc->output_format;
|
||||
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
|
||||
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT;
|
||||
fmt.fmt.pix_mp.num_planes = 1;
|
||||
// fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0;
|
||||
// fmt.fmt.pix_mp.plane_fmt[0].sizeimage = 512 << 10;
|
||||
E_LOG_DEBUG("Configuring OUTPUT format ...");
|
||||
E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format");
|
||||
_E_LOG_DEBUG("Configuring OUTPUT format ...");
|
||||
_E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format");
|
||||
if (fmt.fmt.pix_mp.pixelformat != enc->output_format) {
|
||||
char fourcc_str[8];
|
||||
E_LOG_ERROR("The OUTPUT format can't be configured as %s",
|
||||
fourcc_to_string(enc->output_format, fourcc_str, 8));
|
||||
E_LOG_ERROR("In case of Raspberry Pi, try to append 'start_x=1' to /boot/config.txt");
|
||||
_E_LOG_ERROR("The OUTPUT format can't be configured as %s",
|
||||
us_fourcc_to_string(enc->output_format, fourcc_str, 8));
|
||||
_E_LOG_ERROR("In case of Raspberry Pi, try to append 'start_x=1' to /boot/config.txt");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
@ -237,61 +237,61 @@ static void _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame) {
|
||||
setfps.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
setfps.parm.output.timeperframe.numerator = 1;
|
||||
setfps.parm.output.timeperframe.denominator = enc->fps;
|
||||
E_LOG_DEBUG("Configuring INPUT FPS ...");
|
||||
E_XIOCTL(VIDIOC_S_PARM, &setfps, "Can't set INPUT FPS");
|
||||
_E_LOG_DEBUG("Configuring INPUT FPS ...");
|
||||
_E_XIOCTL(VIDIOC_S_PARM, &setfps, "Can't set INPUT FPS");
|
||||
}
|
||||
|
||||
if (_m2m_encoder_init_buffers(enc, (dma ? "INPUT-DMA" : "INPUT"), V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
|
||||
&RUN(input_bufs), &RUN(n_input_bufs), dma) < 0) {
|
||||
&_RUN(input_bufs), &_RUN(n_input_bufs), dma) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (_m2m_encoder_init_buffers(enc, "OUTPUT", V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
|
||||
&RUN(output_bufs), &RUN(n_output_bufs), false) < 0) {
|
||||
&_RUN(output_bufs), &_RUN(n_output_bufs), false) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
{
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
E_LOG_DEBUG("Starting INPUT ...");
|
||||
E_XIOCTL(VIDIOC_STREAMON, &type, "Can't start INPUT");
|
||||
_E_LOG_DEBUG("Starting INPUT ...");
|
||||
_E_XIOCTL(VIDIOC_STREAMON, &type, "Can't start INPUT");
|
||||
|
||||
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
E_LOG_DEBUG("Starting OUTPUT ...");
|
||||
E_XIOCTL(VIDIOC_STREAMON, &type, "Can't start OUTPUT");
|
||||
_E_LOG_DEBUG("Starting OUTPUT ...");
|
||||
_E_XIOCTL(VIDIOC_STREAMON, &type, "Can't start OUTPUT");
|
||||
}
|
||||
|
||||
RUN(ready) = true;
|
||||
E_LOG_DEBUG("Encoder state: *** READY ***");
|
||||
_RUN(ready) = true;
|
||||
_E_LOG_DEBUG("Encoder state: *** READY ***");
|
||||
return;
|
||||
|
||||
error:
|
||||
_m2m_encoder_cleanup(enc);
|
||||
E_LOG_ERROR("Encoder destroyed due an error (prepare)");
|
||||
_E_LOG_ERROR("Encoder destroyed due an error (prepare)");
|
||||
}
|
||||
|
||||
static int _m2m_encoder_init_buffers(
|
||||
m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
m2m_buffer_s **bufs_ptr, unsigned *n_bufs_ptr, bool dma) {
|
||||
us_m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
us_m2m_buffer_s **bufs_ptr, unsigned *n_bufs_ptr, bool dma) {
|
||||
|
||||
E_LOG_DEBUG("Initializing %s buffers ...", name);
|
||||
_E_LOG_DEBUG("Initializing %s buffers ...", name);
|
||||
|
||||
struct v4l2_requestbuffers req = {0};
|
||||
req.count = 1;
|
||||
req.type = type;
|
||||
req.memory = (dma ? V4L2_MEMORY_DMABUF : V4L2_MEMORY_MMAP);
|
||||
|
||||
E_LOG_DEBUG("Requesting %u %s buffers ...", req.count, name);
|
||||
E_XIOCTL(VIDIOC_REQBUFS, &req, "Can't request %s buffers", name);
|
||||
_E_LOG_DEBUG("Requesting %u %s buffers ...", req.count, name);
|
||||
_E_XIOCTL(VIDIOC_REQBUFS, &req, "Can't request %s buffers", name);
|
||||
if (req.count < 1) {
|
||||
E_LOG_ERROR("Insufficient %s buffer memory: %u", name, req.count);
|
||||
_E_LOG_ERROR("Insufficient %s buffer memory: %u", name, req.count);
|
||||
goto error;
|
||||
}
|
||||
E_LOG_DEBUG("Got %u %s buffers", req.count, name);
|
||||
_E_LOG_DEBUG("Got %u %s buffers", req.count, name);
|
||||
|
||||
if (dma) {
|
||||
*n_bufs_ptr = req.count;
|
||||
} else {
|
||||
A_CALLOC(*bufs_ptr, req.count);
|
||||
US_CALLOC(*bufs_ptr, req.count);
|
||||
for (*n_bufs_ptr = 0; *n_bufs_ptr < req.count; ++(*n_bufs_ptr)) {
|
||||
struct v4l2_buffer buf = {0};
|
||||
struct v4l2_plane plane = {0};
|
||||
@ -301,25 +301,25 @@ static int _m2m_encoder_init_buffers(
|
||||
buf.length = 1;
|
||||
buf.m.planes = &plane;
|
||||
|
||||
E_LOG_DEBUG("Querying %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
E_XIOCTL(VIDIOC_QUERYBUF, &buf, "Can't query %s buffer=%u", name, *n_bufs_ptr);
|
||||
_E_LOG_DEBUG("Querying %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
_E_XIOCTL(VIDIOC_QUERYBUF, &buf, "Can't query %s buffer=%u", name, *n_bufs_ptr);
|
||||
|
||||
E_LOG_DEBUG("Mapping %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
_E_LOG_DEBUG("Mapping %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
if (((*bufs_ptr)[*n_bufs_ptr].data = mmap(
|
||||
NULL,
|
||||
plane.length,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
RUN(fd),
|
||||
_RUN(fd),
|
||||
plane.m.mem_offset
|
||||
)) == MAP_FAILED) {
|
||||
E_LOG_PERROR("Can't map %s buffer=%u", name, *n_bufs_ptr);
|
||||
_E_LOG_PERROR("Can't map %s buffer=%u", name, *n_bufs_ptr);
|
||||
goto error;
|
||||
}
|
||||
(*bufs_ptr)[*n_bufs_ptr].allocated = plane.length;
|
||||
|
||||
E_LOG_DEBUG("Queuing %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
E_XIOCTL(VIDIOC_QBUF, &buf, "Can't queue %s buffer=%u", name, *n_bufs_ptr);
|
||||
_E_LOG_DEBUG("Queuing %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
_E_XIOCTL(VIDIOC_QBUF, &buf, "Can't queue %s buffer=%u", name, *n_bufs_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,13 +328,13 @@ static int _m2m_encoder_init_buffers(
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void _m2m_encoder_cleanup(m2m_encoder_s *enc) {
|
||||
if (RUN(ready)) {
|
||||
# define STOP_STREAM(_name, _type) { \
|
||||
enum v4l2_buf_type _type_var = _type; \
|
||||
E_LOG_DEBUG("Stopping %s ...", _name); \
|
||||
if (xioctl(RUN(fd), VIDIOC_STREAMOFF, &_type_var) < 0) { \
|
||||
E_LOG_PERROR("Can't stop %s", _name); \
|
||||
static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
|
||||
if (_RUN(ready)) {
|
||||
# define STOP_STREAM(x_name, x_type) { \
|
||||
enum v4l2_buf_type m_type_var = x_type; \
|
||||
_E_LOG_DEBUG("Stopping %s ...", x_name); \
|
||||
if (us_xioctl(_RUN(fd), VIDIOC_STREAMOFF, &m_type_var) < 0) { \
|
||||
_E_LOG_PERROR("Can't stop %s", x_name); \
|
||||
} \
|
||||
}
|
||||
|
||||
@ -344,19 +344,19 @@ static void _m2m_encoder_cleanup(m2m_encoder_s *enc) {
|
||||
# undef STOP_STREAM
|
||||
}
|
||||
|
||||
# define DESTROY_BUFFERS(_name, _target) { \
|
||||
if (RUN(_target##_bufs)) { \
|
||||
for (unsigned index = 0; index < RUN(n_##_target##_bufs); ++index) { \
|
||||
if (RUN(_target##_bufs[index].allocated) > 0 && RUN(_target##_bufs[index].data) != MAP_FAILED) { \
|
||||
if (munmap(RUN(_target##_bufs[index].data), RUN(_target##_bufs[index].allocated)) < 0) { \
|
||||
E_LOG_PERROR("Can't unmap %s buffer=%u", #_name, index); \
|
||||
# define DESTROY_BUFFERS(x_name, x_target) { \
|
||||
if (_RUN(x_target##_bufs)) { \
|
||||
for (unsigned m_index = 0; m_index < _RUN(n_##x_target##_bufs); ++m_index) { \
|
||||
if (_RUN(x_target##_bufs[m_index].allocated) > 0 && _RUN(x_target##_bufs[m_index].data) != MAP_FAILED) { \
|
||||
if (munmap(_RUN(x_target##_bufs[m_index].data), _RUN(x_target##_bufs[m_index].allocated)) < 0) { \
|
||||
_E_LOG_PERROR("Can't unmap %s buffer=%u", #x_name, m_index); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
free(RUN(_target##_bufs)); \
|
||||
RUN(_target##_bufs) = NULL; \
|
||||
free(_RUN(x_target##_bufs)); \
|
||||
_RUN(x_target##_bufs) = NULL; \
|
||||
} \
|
||||
RUN(n_##_target##_bufs) = 0; \
|
||||
_RUN(n_##x_target##_bufs) = 0; \
|
||||
}
|
||||
|
||||
DESTROY_BUFFERS("OUTPUT", output);
|
||||
@ -364,30 +364,30 @@ static void _m2m_encoder_cleanup(m2m_encoder_s *enc) {
|
||||
|
||||
# undef DESTROY_BUFFERS
|
||||
|
||||
if (RUN(fd) >= 0) {
|
||||
if (close(RUN(fd)) < 0) {
|
||||
E_LOG_PERROR("Can't close encoder device");
|
||||
if (_RUN(fd) >= 0) {
|
||||
if (close(_RUN(fd)) < 0) {
|
||||
_E_LOG_PERROR("Can't close encoder device");
|
||||
}
|
||||
RUN(fd) = -1;
|
||||
_RUN(fd) = -1;
|
||||
}
|
||||
|
||||
RUN(last_online) = -1;
|
||||
RUN(ready) = false;
|
||||
_RUN(last_online) = -1;
|
||||
_RUN(ready) = false;
|
||||
|
||||
E_LOG_DEBUG("Encoder state: ~~~ NOT READY ~~~");
|
||||
_E_LOG_DEBUG("Encoder state: ~~~ NOT READY ~~~");
|
||||
}
|
||||
|
||||
static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, frame_s *dest, bool force_key) {
|
||||
assert(RUN(ready));
|
||||
static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key) {
|
||||
assert(_RUN(ready));
|
||||
|
||||
E_LOG_DEBUG("Compressing new frame; force_key=%d ...", force_key);
|
||||
_E_LOG_DEBUG("Compressing new frame; force_key=%d ...", force_key);
|
||||
|
||||
if (force_key) {
|
||||
struct v4l2_control ctl = {0};
|
||||
ctl.id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME;
|
||||
ctl.value = 1;
|
||||
E_LOG_DEBUG("Forcing keyframe ...")
|
||||
E_XIOCTL(VIDIOC_S_CTRL, &ctl, "Can't force keyframe");
|
||||
_E_LOG_DEBUG("Forcing keyframe ...")
|
||||
_E_XIOCTL(VIDIOC_S_CTRL, &ctl, "Can't force keyframe");
|
||||
}
|
||||
|
||||
struct v4l2_buffer input_buf = {0};
|
||||
@ -396,25 +396,25 @@ static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, fra
|
||||
input_buf.length = 1;
|
||||
input_buf.m.planes = &input_plane;
|
||||
|
||||
if (RUN(dma)) {
|
||||
if (_RUN(dma)) {
|
||||
input_buf.index = 0;
|
||||
input_buf.memory = V4L2_MEMORY_DMABUF;
|
||||
input_buf.field = V4L2_FIELD_NONE;
|
||||
input_plane.m.fd = src->dma_fd;
|
||||
E_LOG_DEBUG("Using INPUT-DMA buffer=%u", input_buf.index);
|
||||
_E_LOG_DEBUG("Using INPUT-DMA buffer=%u", input_buf.index);
|
||||
} else {
|
||||
input_buf.memory = V4L2_MEMORY_MMAP;
|
||||
E_LOG_DEBUG("Grabbing INPUT buffer ...");
|
||||
E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't grab INPUT buffer");
|
||||
if (input_buf.index >= RUN(n_input_bufs)) {
|
||||
E_LOG_ERROR("V4L2 error: grabbed invalid INPUT: buffer=%u, n_bufs=%u",
|
||||
input_buf.index, RUN(n_input_bufs));
|
||||
_E_LOG_DEBUG("Grabbing INPUT buffer ...");
|
||||
_E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't grab INPUT buffer");
|
||||
if (input_buf.index >= _RUN(n_input_bufs)) {
|
||||
_E_LOG_ERROR("V4L2 error: grabbed invalid INPUT: buffer=%u, n_bufs=%u",
|
||||
input_buf.index, _RUN(n_input_bufs));
|
||||
goto error;
|
||||
}
|
||||
E_LOG_DEBUG("Grabbed INPUT buffer=%u", input_buf.index);
|
||||
_E_LOG_DEBUG("Grabbed INPUT buffer=%u", input_buf.index);
|
||||
}
|
||||
|
||||
uint64_t now = get_now_monotonic_u64();
|
||||
uint64_t now = us_get_now_monotonic_u64();
|
||||
struct timeval ts = {
|
||||
.tv_sec = now / 1000000,
|
||||
.tv_usec = now % 1000000,
|
||||
@ -424,31 +424,31 @@ static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, fra
|
||||
input_buf.timestamp.tv_usec = ts.tv_usec;
|
||||
input_plane.bytesused = src->used;
|
||||
input_plane.length = src->used;
|
||||
if (!RUN(dma)) {
|
||||
memcpy(RUN(input_bufs[input_buf.index].data), src->data, src->used);
|
||||
if (!_RUN(dma)) {
|
||||
memcpy(_RUN(input_bufs[input_buf.index].data), src->data, src->used);
|
||||
}
|
||||
|
||||
const char *input_name = (RUN(dma) ? "INPUT-DMA" : "INPUT");
|
||||
const char *input_name = (_RUN(dma) ? "INPUT-DMA" : "INPUT");
|
||||
|
||||
E_LOG_DEBUG("Sending%s %s buffer ...", (!RUN(dma) ? " (releasing)" : ""), input_name);
|
||||
E_XIOCTL(VIDIOC_QBUF, &input_buf, "Can't send %s buffer", input_name);
|
||||
_E_LOG_DEBUG("Sending%s %s buffer ...", (!_RUN(dma) ? " (releasing)" : ""), input_name);
|
||||
_E_XIOCTL(VIDIOC_QBUF, &input_buf, "Can't send %s buffer", input_name);
|
||||
|
||||
// Для не-DMA отправка буфера по факту являтся освобождением этого буфера
|
||||
bool input_released = !RUN(dma);
|
||||
bool input_released = !_RUN(dma);
|
||||
|
||||
while (true) {
|
||||
struct pollfd enc_poll = {RUN(fd), POLLIN, 0};
|
||||
struct pollfd enc_poll = {_RUN(fd), POLLIN, 0};
|
||||
|
||||
E_LOG_DEBUG("Polling encoder ...");
|
||||
_E_LOG_DEBUG("Polling encoder ...");
|
||||
if (poll(&enc_poll, 1, 1000) < 0 && errno != EINTR) {
|
||||
E_LOG_PERROR("Can't poll encoder");
|
||||
_E_LOG_PERROR("Can't poll encoder");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (enc_poll.revents & POLLIN) {
|
||||
if (!input_released) {
|
||||
E_LOG_DEBUG("Releasing %s buffer=%u ...", input_name, input_buf.index);
|
||||
E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't release %s buffer=%u",
|
||||
_E_LOG_DEBUG("Releasing %s buffer=%u ...", input_name, input_buf.index);
|
||||
_E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't release %s buffer=%u",
|
||||
input_name, input_buf.index);
|
||||
input_released = true;
|
||||
}
|
||||
@ -459,23 +459,23 @@ static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, fra
|
||||
output_buf.memory = V4L2_MEMORY_MMAP;
|
||||
output_buf.length = 1;
|
||||
output_buf.m.planes = &output_plane;
|
||||
E_LOG_DEBUG("Fetching OUTPUT buffer ...");
|
||||
E_XIOCTL(VIDIOC_DQBUF, &output_buf, "Can't fetch OUTPUT buffer");
|
||||
_E_LOG_DEBUG("Fetching OUTPUT buffer ...");
|
||||
_E_XIOCTL(VIDIOC_DQBUF, &output_buf, "Can't fetch OUTPUT buffer");
|
||||
|
||||
bool done = false;
|
||||
if (ts.tv_sec != output_buf.timestamp.tv_sec || ts.tv_usec != output_buf.timestamp.tv_usec) {
|
||||
// Енкодер первый раз может выдать буфер с мусором и нулевым таймстампом,
|
||||
// так что нужно убедиться, что мы читаем выходной буфер, соответствующий
|
||||
// входному (с тем же таймстампом).
|
||||
E_LOG_DEBUG("Need to retry OUTPUT buffer due timestamp mismatch");
|
||||
_E_LOG_DEBUG("Need to retry OUTPUT buffer due timestamp mismatch");
|
||||
} else {
|
||||
frame_set_data(dest, RUN(output_bufs[output_buf.index].data), output_plane.bytesused);
|
||||
us_frame_set_data(dest, _RUN(output_bufs[output_buf.index].data), output_plane.bytesused);
|
||||
dest->key = output_buf.flags & V4L2_BUF_FLAG_KEYFRAME;
|
||||
done = true;
|
||||
}
|
||||
|
||||
E_LOG_DEBUG("Releasing OUTPUT buffer=%u ...", output_buf.index);
|
||||
E_XIOCTL(VIDIOC_QBUF, &output_buf, "Can't release OUTPUT buffer=%u", output_buf.index);
|
||||
_E_LOG_DEBUG("Releasing OUTPUT buffer=%u ...", output_buf.index);
|
||||
_E_XIOCTL(VIDIOC_QBUF, &output_buf, "Can't release OUTPUT buffer=%u", output_buf.index);
|
||||
|
||||
if (done) {
|
||||
break;
|
||||
@ -488,12 +488,4 @@ static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, fra
|
||||
return -1;
|
||||
}
|
||||
|
||||
#undef E_XIOCTL
|
||||
|
||||
#undef RUN
|
||||
|
||||
#undef E_LOG_DEBUG
|
||||
#undef E_LOG_VERBOSE
|
||||
#undef E_LOG_INFO
|
||||
#undef E_LOG_PERROR
|
||||
#undef E_LOG_ERROR
|
||||
#undef _E_XIOCTL
|
||||
|
||||
@ -45,24 +45,24 @@
|
||||
typedef struct {
|
||||
uint8_t *data;
|
||||
size_t allocated;
|
||||
} m2m_buffer_s;
|
||||
} us_m2m_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
m2m_buffer_s *input_bufs;
|
||||
us_m2m_buffer_s *input_bufs;
|
||||
unsigned n_input_bufs;
|
||||
m2m_buffer_s *output_bufs;
|
||||
us_m2m_buffer_s *output_bufs;
|
||||
unsigned n_output_bufs;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned input_format;
|
||||
unsigned stride;
|
||||
bool dma;
|
||||
bool ready;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned input_format;
|
||||
unsigned stride;
|
||||
bool dma;
|
||||
bool ready;
|
||||
|
||||
int last_online;
|
||||
} m2m_encoder_runtime_s;
|
||||
int last_online;
|
||||
} us_m2m_encoder_runtime_s;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
@ -74,13 +74,13 @@ typedef struct {
|
||||
unsigned quality;
|
||||
bool allow_dma;
|
||||
|
||||
m2m_encoder_runtime_s *run;
|
||||
} m2m_encoder_s;
|
||||
us_m2m_encoder_runtime_s *run;
|
||||
} us_m2m_encoder_s;
|
||||
|
||||
|
||||
m2m_encoder_s *m2m_h264_encoder_init(const char *name, const char *path, unsigned bitrate, unsigned gop);
|
||||
m2m_encoder_s *m2m_mjpeg_encoder_init(const char *name, const char *path, unsigned quality);
|
||||
m2m_encoder_s *m2m_jpeg_encoder_init(const char *name, const char *path, unsigned quality);
|
||||
void m2m_encoder_destroy(m2m_encoder_s *enc);
|
||||
us_m2m_encoder_s *us_m2m_h264_encoder_init(const char *name, const char *path, unsigned bitrate, unsigned gop);
|
||||
us_m2m_encoder_s *us_m2m_mjpeg_encoder_init(const char *name, const char *path, unsigned quality);
|
||||
us_m2m_encoder_s *us_m2m_jpeg_encoder_init(const char *name, const char *path, unsigned quality);
|
||||
void us_m2m_encoder_destroy(us_m2m_encoder_s *enc);
|
||||
|
||||
int m2m_encoder_compress(m2m_encoder_s *enc, const frame_s *src, frame_s *dest, bool force_key);
|
||||
int us_m2m_encoder_compress(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key);
|
||||
|
||||
@ -41,8 +41,8 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
stream_s *stream;
|
||||
server_s *server;
|
||||
us_stream_s *stream;
|
||||
us_server_s *server;
|
||||
} _main_context_s;
|
||||
|
||||
static _main_context_s *_ctx;
|
||||
@ -56,27 +56,27 @@ static void _block_thread_signals(void) {
|
||||
}
|
||||
|
||||
static void *_stream_loop_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("stream");
|
||||
US_THREAD_RENAME("stream");
|
||||
_block_thread_signals();
|
||||
stream_loop(_ctx->stream);
|
||||
us_stream_loop(_ctx->stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_server_loop_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("http");
|
||||
US_THREAD_RENAME("http");
|
||||
_block_thread_signals();
|
||||
server_loop(_ctx->server);
|
||||
us_server_loop(_ctx->server);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void _signal_handler(int signum) {
|
||||
switch (signum) {
|
||||
case SIGTERM: LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break;
|
||||
case SIGINT: LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break;
|
||||
default: LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
|
||||
case SIGTERM: US_LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break;
|
||||
case SIGINT: US_LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break;
|
||||
default: US_LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
|
||||
}
|
||||
stream_loop_break(_ctx->stream);
|
||||
server_loop_break(_ctx->server);
|
||||
us_stream_loop_break(_ctx->stream);
|
||||
us_server_loop_break(_ctx->server);
|
||||
}
|
||||
|
||||
static void _install_signal_handlers(void) {
|
||||
@ -87,13 +87,13 @@ static void _install_signal_handlers(void) {
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGINT));
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
|
||||
|
||||
LOG_DEBUG("Installing SIGINT handler ...");
|
||||
US_LOG_DEBUG("Installing SIGINT handler ...");
|
||||
assert(!sigaction(SIGINT, &sig_act, NULL));
|
||||
|
||||
LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
assert(!sigaction(SIGTERM, &sig_act, NULL));
|
||||
|
||||
LOG_DEBUG("Ignoring SIGPIPE ...");
|
||||
US_LOG_DEBUG("Ignoring SIGPIPE ...");
|
||||
assert(signal(SIGPIPE, SIG_IGN) != SIG_ERR);
|
||||
}
|
||||
|
||||
@ -101,18 +101,18 @@ int main(int argc, char *argv[]) {
|
||||
assert(argc >= 0);
|
||||
int exit_code = 0;
|
||||
|
||||
LOGGING_INIT;
|
||||
A_THREAD_RENAME("main");
|
||||
US_LOGGING_INIT;
|
||||
US_THREAD_RENAME("main");
|
||||
|
||||
options_s *options = options_init(argc, argv);
|
||||
device_s *dev = device_init();
|
||||
encoder_s *enc = encoder_init();
|
||||
stream_s *stream = stream_init(dev, enc);
|
||||
server_s *server = server_init(stream);
|
||||
us_options_s *options = us_options_init(argc, argv);
|
||||
us_device_s *dev = us_device_init();
|
||||
us_encoder_s *enc = us_encoder_init();
|
||||
us_stream_s *stream = us_stream_init(dev, enc);
|
||||
us_server_s *server = us_server_init(stream);
|
||||
|
||||
if ((exit_code = options_parse(options, dev, enc, stream, server)) == 0) {
|
||||
# ifdef WITH_GPIO
|
||||
gpio_init();
|
||||
us_gpio_init();
|
||||
# endif
|
||||
|
||||
_install_signal_handlers();
|
||||
@ -122,34 +122,34 @@ int main(int argc, char *argv[]) {
|
||||
ctx.server = server;
|
||||
_ctx = &ctx;
|
||||
|
||||
if ((exit_code = server_listen(server)) == 0) {
|
||||
if ((exit_code = us_server_listen(server)) == 0) {
|
||||
# ifdef WITH_GPIO
|
||||
gpio_set_prog_running(true);
|
||||
us_gpio_set_prog_running(true);
|
||||
# endif
|
||||
|
||||
pthread_t stream_loop_tid;
|
||||
pthread_t server_loop_tid;
|
||||
A_THREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL);
|
||||
A_THREAD_CREATE(&server_loop_tid, _server_loop_thread, NULL);
|
||||
A_THREAD_JOIN(server_loop_tid);
|
||||
A_THREAD_JOIN(stream_loop_tid);
|
||||
US_THREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL);
|
||||
US_THREAD_CREATE(&server_loop_tid, _server_loop_thread, NULL);
|
||||
US_THREAD_JOIN(server_loop_tid);
|
||||
US_THREAD_JOIN(stream_loop_tid);
|
||||
}
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
gpio_set_prog_running(false);
|
||||
gpio_destroy();
|
||||
us_gpio_set_prog_running(false);
|
||||
us_gpio_destroy();
|
||||
# endif
|
||||
}
|
||||
|
||||
server_destroy(server);
|
||||
stream_destroy(stream);
|
||||
encoder_destroy(enc);
|
||||
device_destroy(dev);
|
||||
options_destroy(options);
|
||||
us_server_destroy(server);
|
||||
us_stream_destroy(stream);
|
||||
us_encoder_destroy(enc);
|
||||
us_device_destroy(dev);
|
||||
us_options_destroy(options);
|
||||
|
||||
if (exit_code == 0) {
|
||||
LOG_INFO("Bye-bye");
|
||||
US_LOG_INFO("Bye-bye");
|
||||
}
|
||||
LOGGING_DESTROY;
|
||||
US_LOGGING_DESTROY;
|
||||
return (exit_code < 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
#include "options.h"
|
||||
|
||||
|
||||
enum _OPT_VALUES {
|
||||
enum _US_OPT_VALUES {
|
||||
_O_DEVICE = 'd',
|
||||
_O_INPUT = 'i',
|
||||
_O_RESOLUTION = 'r',
|
||||
@ -85,12 +85,12 @@ enum _OPT_VALUES {
|
||||
_O_TCP_NODELAY,
|
||||
_O_SERVER_TIMEOUT,
|
||||
|
||||
# define ADD_SINK(_prefix) \
|
||||
_O_##_prefix, \
|
||||
_O_##_prefix##_MODE, \
|
||||
_O_##_prefix##_RM, \
|
||||
_O_##_prefix##_CLIENT_TTL, \
|
||||
_O_##_prefix##_TIMEOUT,
|
||||
# define ADD_SINK(x_prefix) \
|
||||
_O_##x_prefix, \
|
||||
_O_##x_prefix##_MODE, \
|
||||
_O_##x_prefix##_RM, \
|
||||
_O_##x_prefix##_CLIENT_TTL, \
|
||||
_O_##x_prefix##_TIMEOUT,
|
||||
ADD_SINK(SINK)
|
||||
ADD_SINK(RAW_SINK)
|
||||
ADD_SINK(H264_SINK)
|
||||
@ -181,12 +181,12 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"tcp-nodelay", no_argument, NULL, _O_TCP_NODELAY},
|
||||
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
|
||||
|
||||
# define ADD_SINK(_opt, _prefix) \
|
||||
{_opt "sink", required_argument, NULL, _O_##_prefix}, \
|
||||
{_opt "sink-mode", required_argument, NULL, _O_##_prefix##_MODE}, \
|
||||
{_opt "sink-rm", no_argument, NULL, _O_##_prefix##_RM}, \
|
||||
{_opt "sink-client-ttl", required_argument, NULL, _O_##_prefix##_CLIENT_TTL}, \
|
||||
{_opt "sink-timeout", required_argument, NULL, _O_##_prefix##_TIMEOUT},
|
||||
# define ADD_SINK(x_opt, x_prefix) \
|
||||
{x_opt "sink", required_argument, NULL, _O_##x_prefix}, \
|
||||
{x_opt "sink-mode", required_argument, NULL, _O_##x_prefix##_MODE}, \
|
||||
{x_opt "sink-rm", no_argument, NULL, _O_##x_prefix##_RM}, \
|
||||
{x_opt "sink-client-ttl", required_argument, NULL, _O_##x_prefix##_CLIENT_TTL}, \
|
||||
{x_opt "sink-timeout", required_argument, NULL, _O_##x_prefix##_TIMEOUT},
|
||||
ADD_SINK("", SINK)
|
||||
ADD_SINK("raw-", RAW_SINK)
|
||||
ADD_SINK("h264-", H264_SINK)
|
||||
@ -230,36 +230,28 @@ static const struct option _LONG_OPTS[] = {
|
||||
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited);
|
||||
|
||||
static void _features(void);
|
||||
static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server);
|
||||
static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server);
|
||||
|
||||
|
||||
options_s *options_init(unsigned argc, char *argv[]) {
|
||||
options_s *options;
|
||||
A_CALLOC(options, 1);
|
||||
us_options_s *us_options_init(unsigned argc, char *argv[]) {
|
||||
us_options_s *options;
|
||||
US_CALLOC(options, 1);
|
||||
options->argc = argc;
|
||||
options->argv = argv;
|
||||
|
||||
A_CALLOC(options->argv_copy, argc);
|
||||
US_CALLOC(options->argv_copy, argc);
|
||||
for (unsigned index = 0; index < argc; ++index) {
|
||||
assert(options->argv_copy[index] = strdup(argv[index]));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
void options_destroy(options_s *options) {
|
||||
# define ADD_SINK(_prefix) { \
|
||||
if (options->_prefix) { \
|
||||
memsink_destroy(options->_prefix); \
|
||||
} \
|
||||
}
|
||||
ADD_SINK(sink);
|
||||
ADD_SINK(raw_sink);
|
||||
ADD_SINK(h264_sink);
|
||||
# undef ADD_SINK
|
||||
void us_options_destroy(us_options_s *options) {
|
||||
US_DELETE(options->sink, us_memsink_destroy);
|
||||
US_DELETE(options->raw_sink, us_memsink_destroy);
|
||||
US_DELETE(options->h264_sink, us_memsink_destroy);
|
||||
|
||||
if (options->blank) {
|
||||
frame_destroy(options->blank);
|
||||
}
|
||||
US_DELETE(options->blank, us_frame_destroy);
|
||||
|
||||
for (unsigned index = 0; index < options->argc; ++index) {
|
||||
free(options->argv_copy[index]);
|
||||
@ -270,32 +262,32 @@ void options_destroy(options_s *options) {
|
||||
}
|
||||
|
||||
|
||||
int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server) {
|
||||
# define OPT_SET(_dest, _value) { \
|
||||
_dest = _value; \
|
||||
int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server) {
|
||||
# define OPT_SET(x_dest, x_value) { \
|
||||
x_dest = x_value; \
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
|
||||
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
|
||||
if (errno || *_end || _tmp < _min || _tmp > _max) { \
|
||||
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \
|
||||
# define OPT_NUMBER(x_name, x_dest, x_min, x_max, x_base) { \
|
||||
errno = 0; char *m_end = NULL; long long m_tmp = strtoll(optarg, &m_end, x_base); \
|
||||
if (errno || *m_end || m_tmp < x_min || m_tmp > x_max) { \
|
||||
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", x_name, optarg, (long long)x_min, (long long)x_max); \
|
||||
return -1; \
|
||||
} \
|
||||
_dest = _tmp; \
|
||||
x_dest = m_tmp; \
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_RESOLUTION(_name, _dest_width, _dest_height, _limited) { \
|
||||
switch (_parse_resolution(optarg, &_dest_width, &_dest_height, _limited)) { \
|
||||
# define OPT_RESOLUTION(x_name, x_dest_width, x_dest_height, x_limited) { \
|
||||
switch (_parse_resolution(optarg, &x_dest_width, &x_dest_height, x_limited)) { \
|
||||
case -1: \
|
||||
printf("Invalid resolution format for '%s=%s'\n", _name, optarg); \
|
||||
printf("Invalid resolution format for '%s=%s'\n", x_name, optarg); \
|
||||
return -1; \
|
||||
case -2: \
|
||||
printf("Invalid width of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH); \
|
||||
printf("Invalid width of '%s=%s': min=%u, max=%u\n", x_name, optarg, US_VIDEO_MIN_WIDTH, US_VIDEO_MAX_WIDTH); \
|
||||
return -1; \
|
||||
case -3: \
|
||||
printf("Invalid height of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT); \
|
||||
printf("Invalid height of '%s=%s': min=%u, max=%u\n", x_name, optarg, US_VIDEO_MIN_HEIGHT, US_VIDEO_MAX_HEIGHT); \
|
||||
return -1; \
|
||||
case 0: break; \
|
||||
default: assert(0 && "Unknown error"); \
|
||||
@ -303,48 +295,48 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_PARSE(_name, _dest, _func, _invalid, _available) { \
|
||||
if ((_dest = _func(optarg)) == _invalid) { \
|
||||
printf("Unknown " _name ": %s; available: %s\n", optarg, _available); \
|
||||
# define OPT_PARSE(x_name, x_dest, x_func, x_invalid, x_available) { \
|
||||
if ((x_dest = x_func(optarg)) == x_invalid) { \
|
||||
printf("Unknown " x_name ": %s; available: %s\n", optarg, x_available); \
|
||||
return -1; \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_CTL_DEFAULT_NOBREAK(_dest) { \
|
||||
dev->ctl._dest.mode = CTL_MODE_DEFAULT; \
|
||||
# define OPT_CTL_DEFAULT_NOBREAK(x_dest) { \
|
||||
dev->ctl.x_dest.mode = CTL_MODE_DEFAULT; \
|
||||
}
|
||||
|
||||
# define OPT_CTL_MANUAL(_dest) { \
|
||||
# define OPT_CTL_MANUAL(x_dest) { \
|
||||
if (!strcasecmp(optarg, "default")) { \
|
||||
OPT_CTL_DEFAULT_NOBREAK(_dest); \
|
||||
OPT_CTL_DEFAULT_NOBREAK(x_dest); \
|
||||
} else { \
|
||||
dev->ctl._dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
|
||||
dev->ctl.x_dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#x_dest, dev->ctl.x_dest.value, INT_MIN, INT_MAX, 0); \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_CTL_AUTO(_dest) { \
|
||||
# define OPT_CTL_AUTO(x_dest) { \
|
||||
if (!strcasecmp(optarg, "default")) { \
|
||||
OPT_CTL_DEFAULT_NOBREAK(_dest); \
|
||||
OPT_CTL_DEFAULT_NOBREAK(x_dest); \
|
||||
} else if (!strcasecmp(optarg, "auto")) { \
|
||||
dev->ctl._dest.mode = CTL_MODE_AUTO; \
|
||||
dev->ctl.x_dest.mode = CTL_MODE_AUTO; \
|
||||
} else { \
|
||||
dev->ctl._dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
|
||||
dev->ctl.x_dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#x_dest, dev->ctl.x_dest.value, INT_MIN, INT_MAX, 0); \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
|
||||
char *blank_path = NULL;
|
||||
|
||||
# define ADD_SINK(_prefix) \
|
||||
char *_prefix##_name = NULL; \
|
||||
mode_t _prefix##_mode = 0660; \
|
||||
bool _prefix##_rm = false; \
|
||||
unsigned _prefix##_client_ttl = 10; \
|
||||
unsigned _prefix##_timeout = 1;
|
||||
# define ADD_SINK(x_prefix) \
|
||||
char *x_prefix##_name = NULL; \
|
||||
mode_t x_prefix##_mode = 0660; \
|
||||
bool x_prefix##_rm = false; \
|
||||
unsigned x_prefix##_client_ttl = 10; \
|
||||
unsigned x_prefix##_timeout = 1;
|
||||
ADD_SINK(sink);
|
||||
ADD_SINK(raw_sink);
|
||||
ADD_SINK(h264_sink);
|
||||
@ -355,27 +347,27 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
# endif
|
||||
|
||||
char short_opts[128];
|
||||
build_short_options(_LONG_OPTS, short_opts, 128);
|
||||
us_build_short_options(_LONG_OPTS, short_opts, 128);
|
||||
|
||||
for (int ch; (ch = getopt_long(options->argc, options->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0;) {
|
||||
switch (ch) {
|
||||
case _O_DEVICE: OPT_SET(dev->path, optarg);
|
||||
case _O_INPUT: OPT_NUMBER("--input", dev->input, 0, 128, 0);
|
||||
case _O_RESOLUTION: OPT_RESOLUTION("--resolution", dev->width, dev->height, true);
|
||||
case _O_DEVICE: OPT_SET(dev->path, optarg);
|
||||
case _O_INPUT: OPT_NUMBER("--input", dev->input, 0, 128, 0);
|
||||
case _O_RESOLUTION: OPT_RESOLUTION("--resolution", dev->width, dev->height, true);
|
||||
# pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
# pragma GCC diagnostic push
|
||||
case _O_FORMAT: OPT_PARSE("pixel format", dev->format, device_parse_format, FORMAT_UNKNOWN, FORMATS_STR);
|
||||
case _O_FORMAT: OPT_PARSE("pixel format", dev->format, us_device_parse_format, US_FORMAT_UNKNOWN, US_FORMATS_STR);
|
||||
# pragma GCC diagnostic pop
|
||||
case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN, STANDARDS_STR);
|
||||
case _O_IO_METHOD: OPT_PARSE("IO method", dev->io_method, device_parse_io_method, IO_METHOD_UNKNOWN, IO_METHODS_STR);
|
||||
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", dev->desired_fps, 0, VIDEO_MAX_FPS, 0);
|
||||
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 1, 8192, 0);
|
||||
case _O_PERSISTENT: OPT_SET(dev->persistent, true);
|
||||
case _O_DV_TIMINGS: OPT_SET(dev->dv_timings, true);
|
||||
case _O_BUFFERS: OPT_NUMBER("--buffers", dev->n_bufs, 1, 32, 0);
|
||||
case _O_WORKERS: OPT_NUMBER("--workers", enc->n_workers, 1, 32, 0);
|
||||
case _O_QUALITY: OPT_NUMBER("--quality", dev->jpeg_quality, 1, 100, 0);
|
||||
case _O_ENCODER: OPT_PARSE("encoder type", enc->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, ENCODER_TYPES_STR);
|
||||
case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, us_device_parse_standard, US_STANDARD_UNKNOWN, US_STANDARDS_STR);
|
||||
case _O_IO_METHOD: OPT_PARSE("IO method", dev->io_method, us_device_parse_io_method, US_IO_METHOD_UNKNOWN, US_IO_METHODS_STR);
|
||||
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", dev->desired_fps, 0, US_VIDEO_MAX_FPS, 0);
|
||||
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 1, 8192, 0);
|
||||
case _O_PERSISTENT: OPT_SET(dev->persistent, true);
|
||||
case _O_DV_TIMINGS: OPT_SET(dev->dv_timings, true);
|
||||
case _O_BUFFERS: OPT_NUMBER("--buffers", dev->n_bufs, 1, 32, 0);
|
||||
case _O_WORKERS: OPT_NUMBER("--workers", enc->n_workers, 1, 32, 0);
|
||||
case _O_QUALITY: OPT_NUMBER("--quality", dev->jpeg_quality, 1, 100, 0);
|
||||
case _O_ENCODER: OPT_PARSE("encoder type", enc->type, us_encoder_parse_type, US_ENCODER_TYPE_UNKNOWN, ENCODER_TYPES_STR);
|
||||
case _O_GLITCHED_RESOLUTIONS: break; // Deprecated
|
||||
case _O_BLANK: OPT_SET(blank_path, optarg);
|
||||
case _O_LAST_AS_BLANK: OPT_NUMBER("--last-as-blank", stream->last_as_blank, 0, 86400, 0);
|
||||
@ -409,7 +401,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
case _O_WHITE_BALANCE: OPT_CTL_AUTO(white_balance);
|
||||
case _O_GAIN: OPT_CTL_AUTO(gain);
|
||||
case _O_COLOR_EFFECT: OPT_CTL_MANUAL(color_effect);
|
||||
case _O_ROTATE: OPT_CTL_MANUAL(rotate);
|
||||
case _O_ROTATE: OPT_CTL_MANUAL(rotate);
|
||||
case _O_FLIP_VERTICAL: OPT_CTL_MANUAL(flip_vertical);
|
||||
case _O_FLIP_HORIZONTAL: OPT_CTL_MANUAL(flip_horizontal);
|
||||
|
||||
@ -424,24 +416,24 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
case _O_USER: OPT_SET(server->user, optarg);
|
||||
case _O_PASSWD: OPT_SET(server->passwd, optarg);
|
||||
case _O_STATIC: OPT_SET(server->static_path, optarg);
|
||||
case _O_DROP_SAME_FRAMES: OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, VIDEO_MAX_FPS, 0);
|
||||
case _O_DROP_SAME_FRAMES: OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, US_VIDEO_MAX_FPS, 0);
|
||||
case _O_FAKE_RESOLUTION: OPT_RESOLUTION("--fake-resolution", server->fake_width, server->fake_height, false);
|
||||
case _O_ALLOW_ORIGIN: OPT_SET(server->allow_origin, optarg);
|
||||
case _O_TCP_NODELAY: OPT_SET(server->tcp_nodelay, true);
|
||||
case _O_SERVER_TIMEOUT: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
|
||||
|
||||
# define ADD_SINK(_opt, _lp, _up) \
|
||||
case _O_##_up: OPT_SET(_lp##_name, optarg); \
|
||||
case _O_##_up##_MODE: OPT_NUMBER("--" #_opt "sink-mode", _lp##_mode, INT_MIN, INT_MAX, 8); \
|
||||
case _O_##_up##_RM: OPT_SET(_lp##_rm, true); \
|
||||
case _O_##_up##_CLIENT_TTL: OPT_NUMBER("--" #_opt "sink-client-ttl", _lp##_client_ttl, 1, 60, 0); \
|
||||
case _O_##_up##_TIMEOUT: OPT_NUMBER("--" #_opt "sink-timeout", _lp##_timeout, 1, 60, 0);
|
||||
# define ADD_SINK(x_opt, x_lp, x_up) \
|
||||
case _O_##x_up: OPT_SET(x_lp##_name, optarg); \
|
||||
case _O_##x_up##_MODE: OPT_NUMBER("--" #x_opt "sink-mode", x_lp##_mode, INT_MIN, INT_MAX, 8); \
|
||||
case _O_##x_up##_RM: OPT_SET(x_lp##_rm, true); \
|
||||
case _O_##x_up##_CLIENT_TTL: OPT_NUMBER("--" #x_opt "sink-client-ttl", x_lp##_client_ttl, 1, 60, 0); \
|
||||
case _O_##x_up##_TIMEOUT: OPT_NUMBER("--" #x_opt "sink-timeout", x_lp##_timeout, 1, 60, 0);
|
||||
ADD_SINK("", sink, SINK)
|
||||
ADD_SINK("raw-", raw_sink, RAW_SINK)
|
||||
ADD_SINK("h264-", h264_sink, H264_SINK)
|
||||
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 25, 20000, 0);
|
||||
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
||||
case _O_H264_M2M_DEVICE: OPT_SET(stream->h264_m2m_path, optarg);
|
||||
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 25, 20000, 0);
|
||||
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
||||
case _O_H264_M2M_DEVICE: OPT_SET(stream->h264_m2m_path, optarg);
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
@ -454,7 +446,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
|
||||
# ifdef HAS_PDEATHSIG
|
||||
case _O_EXIT_ON_PARENT_DEATH:
|
||||
if (process_track_parent_death() < 0) {
|
||||
if (us_process_track_parent_death() < 0) {
|
||||
return -1;
|
||||
};
|
||||
break;
|
||||
@ -465,15 +457,15 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
# endif
|
||||
case _O_NOTIFY_PARENT: OPT_SET(server->notify_parent, true);
|
||||
|
||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
|
||||
case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF);
|
||||
case _O_VERBOSE: OPT_SET(us_log_level, LOG_LEVEL_VERBOSE);
|
||||
case _O_DEBUG: OPT_SET(us_log_level, LOG_LEVEL_DEBUG);
|
||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
|
||||
case _O_PERF: OPT_SET(us_log_level, US_LOG_LEVEL_PERF);
|
||||
case _O_VERBOSE: OPT_SET(us_log_level, US_LOG_LEVEL_VERBOSE);
|
||||
case _O_DEBUG: OPT_SET(us_log_level, US_LOG_LEVEL_DEBUG);
|
||||
case _O_FORCE_LOG_COLORS: OPT_SET(us_log_colored, true);
|
||||
case _O_NO_LOG_COLORS: OPT_SET(us_log_colored, false);
|
||||
|
||||
case _O_HELP: _help(stdout, dev, enc, stream, server); return 1;
|
||||
case _O_VERSION: puts(VERSION); return 1;
|
||||
case _O_VERSION: puts(US_VERSION); return 1;
|
||||
case _O_FEATURES: _features(); return 1;
|
||||
|
||||
case 0: break;
|
||||
@ -481,22 +473,22 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
}
|
||||
}
|
||||
|
||||
options->blank = blank_frame_init(blank_path);
|
||||
options->blank = us_blank_frame_init(blank_path);
|
||||
stream->blank = options->blank;
|
||||
|
||||
# define ADD_SINK(_label, _prefix) { \
|
||||
if (_prefix##_name && _prefix##_name[0] != '\0') { \
|
||||
options->_prefix = memsink_init( \
|
||||
_label, \
|
||||
_prefix##_name, \
|
||||
# define ADD_SINK(x_label, x_prefix) { \
|
||||
if (x_prefix##_name && x_prefix##_name[0] != '\0') { \
|
||||
options->x_prefix = us_memsink_init( \
|
||||
x_label, \
|
||||
x_prefix##_name, \
|
||||
true, \
|
||||
_prefix##_mode, \
|
||||
_prefix##_rm, \
|
||||
_prefix##_client_ttl, \
|
||||
_prefix##_timeout \
|
||||
x_prefix##_mode, \
|
||||
x_prefix##_rm, \
|
||||
x_prefix##_client_ttl, \
|
||||
x_prefix##_timeout \
|
||||
); \
|
||||
} \
|
||||
stream->_prefix = options->_prefix; \
|
||||
stream->x_prefix = options->x_prefix; \
|
||||
}
|
||||
ADD_SINK("JPEG", sink);
|
||||
ADD_SINK("RAW", raw_sink);
|
||||
@ -505,7 +497,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
|
||||
# ifdef WITH_SETPROCTITLE
|
||||
if (process_name_prefix != NULL) {
|
||||
process_set_name_prefix(options->argc, options->argv, process_name_prefix);
|
||||
us_process_set_name_prefix(options->argc, options->argv, process_name_prefix);
|
||||
}
|
||||
# endif
|
||||
|
||||
@ -526,10 +518,10 @@ static int _parse_resolution(const char *str, unsigned *width, unsigned *height,
|
||||
return -1;
|
||||
}
|
||||
if (limited) {
|
||||
if (tmp_width < VIDEO_MIN_WIDTH || tmp_width > VIDEO_MAX_WIDTH) {
|
||||
if (tmp_width < US_VIDEO_MIN_WIDTH || tmp_width > US_VIDEO_MAX_WIDTH) {
|
||||
return -2;
|
||||
}
|
||||
if (tmp_height < VIDEO_MIN_HEIGHT || tmp_height > VIDEO_MAX_HEIGHT) {
|
||||
if (tmp_height < US_VIDEO_MIN_HEIGHT || tmp_height > US_VIDEO_MAX_HEIGHT) {
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
@ -570,11 +562,11 @@ static void _features(void) {
|
||||
# endif
|
||||
}
|
||||
|
||||
static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server) {
|
||||
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
|
||||
static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server) {
|
||||
# define SAY(x_msg, ...) fprintf(fp, x_msg "\n", ##__VA_ARGS__)
|
||||
SAY("\nuStreamer - Lightweight and fast MJPEG-HTTP streamer");
|
||||
SAY("═══════════════════════════════════════════════════");
|
||||
SAY("Version: %s; license: GPLv3", VERSION);
|
||||
SAY("Version: %s; license: GPLv3", US_VERSION);
|
||||
SAY("Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
SAY("Capturing options:");
|
||||
SAY("══════════════════");
|
||||
@ -582,12 +574,12 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
SAY(" -i|--input <N> ────────────────────── Input channel. Default: %u.\n", dev->input);
|
||||
SAY(" -r|--resolution <WxH> ─────────────── Initial image resolution. Default: %ux%u.\n", dev->width, dev->height);
|
||||
SAY(" -m|--format <fmt> ─────────────────── Image format.");
|
||||
SAY(" Available: %s; default: YUYV.\n", FORMATS_STR);
|
||||
SAY(" Available: %s; default: YUYV.\n", US_FORMATS_STR);
|
||||
SAY(" -a|--tv-standard <std> ────────────── Force TV standard.");
|
||||
SAY(" Available: %s; default: disabled.\n", STANDARDS_STR);
|
||||
SAY(" Available: %s; default: disabled.\n", US_STANDARDS_STR);
|
||||
SAY(" -I|--io-method <method> ───────────── Set V4L2 IO method (see kernel documentation).");
|
||||
SAY(" Changing of this parameter may increase the performance. Or not.");
|
||||
SAY(" Available: %s; default: MMAP.\n", IO_METHODS_STR);
|
||||
SAY(" Available: %s; default: MMAP.\n", US_IO_METHODS_STR);
|
||||
SAY(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n");
|
||||
SAY(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device");
|
||||
SAY(" produces small-sized garbage frames. Default: %zu bytes.\n", dev->min_frame_size);
|
||||
@ -666,14 +658,14 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
SAY(" Default: disabled.\n");
|
||||
SAY(" --allow-origin <str> ─────── Set Access-Control-Allow-Origin header. Default: disabled.\n");
|
||||
SAY(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n", server->timeout);
|
||||
# define ADD_SINK(_name, _opt) \
|
||||
SAY(_name " sink options:"); \
|
||||
# define ADD_SINK(x_name, x_opt) \
|
||||
SAY(x_name " sink options:"); \
|
||||
SAY("══════════════════"); \
|
||||
SAY(" --" _opt "sink <name> ──────────── Use the shared memory to sink " _name " frames. Default: disabled.\n"); \
|
||||
SAY(" --" _opt "sink-mode <mode> ─────── Set " _name " sink permissions (like 777). Default: 660.\n"); \
|
||||
SAY(" --" _opt "sink-rm ──────────────── Remove shared memory on stop. Default: disabled.\n"); \
|
||||
SAY(" --" _opt "sink-client-ttl <sec> ── Client TTL. Default: 10.\n"); \
|
||||
SAY(" --" _opt "sink-timeout <sec> ───── Timeout for lock. Default: 1.\n");
|
||||
SAY(" --" x_opt "sink <name> ──────────── Use the shared memory to sink " x_name " frames. Default: disabled.\n"); \
|
||||
SAY(" --" x_opt "sink-mode <mode> ─────── Set " x_name " sink permissions (like 777). Default: 660.\n"); \
|
||||
SAY(" --" x_opt "sink-rm ──────────────── Remove shared memory on stop. Default: disabled.\n"); \
|
||||
SAY(" --" x_opt "sink-client-ttl <sec> ── Client TTL. Default: 10.\n"); \
|
||||
SAY(" --" x_opt "sink-timeout <sec> ───── Timeout for lock. Default: 1.\n");
|
||||
ADD_SINK("JPEG", "")
|
||||
ADD_SINK("RAW", "raw-")
|
||||
ADD_SINK("H264", "h264-")
|
||||
|
||||
@ -50,17 +50,17 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned argc;
|
||||
char **argv;
|
||||
char **argv_copy;
|
||||
frame_s *blank;
|
||||
memsink_s *sink;
|
||||
memsink_s *raw_sink;
|
||||
memsink_s *h264_sink;
|
||||
} options_s;
|
||||
unsigned argc;
|
||||
char **argv;
|
||||
char **argv_copy;
|
||||
us_frame_s *blank;
|
||||
us_memsink_s *sink;
|
||||
us_memsink_s *raw_sink;
|
||||
us_memsink_s *h264_sink;
|
||||
} us_options_s;
|
||||
|
||||
|
||||
options_s *options_init(unsigned argc, char *argv[]);
|
||||
void options_destroy(options_s *options);
|
||||
us_options_s *us_options_init(unsigned argc, char *argv[]);
|
||||
void us_options_destroy(us_options_s *options);
|
||||
|
||||
int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server);
|
||||
int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server);
|
||||
|
||||
@ -23,41 +23,41 @@
|
||||
#include "stream.h"
|
||||
|
||||
|
||||
static workers_pool_s *_stream_init_loop(stream_s *stream);
|
||||
static workers_pool_s *_stream_init_one(stream_s *stream);
|
||||
static void _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned captured_fps);
|
||||
static us_workers_pool_s *_stream_init_loop(us_stream_s *stream);
|
||||
static us_workers_pool_s *_stream_init_one(us_stream_s *stream);
|
||||
static void _stream_expose_frame(us_stream_s *stream, us_frame_s *frame, unsigned captured_fps);
|
||||
|
||||
|
||||
#define RUN(_next) stream->run->_next
|
||||
#define _RUN(x_next) stream->run->x_next
|
||||
|
||||
#define SINK_PUT(_sink, _frame) { \
|
||||
if (stream->_sink && memsink_server_check(stream->_sink, _frame)) {\
|
||||
memsink_server_put(stream->_sink, _frame); \
|
||||
#define _SINK_PUT(x_sink, x_frame) { \
|
||||
if (stream->x_sink && us_memsink_server_check(stream->x_sink, x_frame)) {\
|
||||
us_memsink_server_put(stream->x_sink, x_frame); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define H264_PUT(_frame, _force_key) { \
|
||||
if (RUN(h264)) { \
|
||||
h264_stream_process(RUN(h264), _frame, _force_key); \
|
||||
#define _H264_PUT(x_frame, x_force_key) { \
|
||||
if (_RUN(h264)) { \
|
||||
us_h264_stream_process(_RUN(h264), x_frame, x_force_key); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
stream_s *stream_init(device_s *dev, encoder_s *enc) {
|
||||
stream_runtime_s *run;
|
||||
A_CALLOC(run, 1);
|
||||
us_stream_s *us_stream_init(us_device_s *dev, us_encoder_s *enc) {
|
||||
us_stream_runtime_s *run;
|
||||
US_CALLOC(run, 1);
|
||||
atomic_init(&run->stop, false);
|
||||
|
||||
video_s *video;
|
||||
A_CALLOC(video, 1);
|
||||
video->frame = frame_init();
|
||||
us_video_s *video;
|
||||
US_CALLOC(video, 1);
|
||||
video->frame = us_frame_init();
|
||||
atomic_init(&video->updated, false);
|
||||
A_MUTEX_INIT(&video->mutex);
|
||||
US_MUTEX_INIT(&video->mutex);
|
||||
atomic_init(&video->has_clients, false);
|
||||
run->video = video;
|
||||
|
||||
stream_s *stream;
|
||||
A_CALLOC(stream, 1);
|
||||
us_stream_s *stream;
|
||||
US_CALLOC(stream, 1);
|
||||
stream->dev = dev;
|
||||
stream->enc = enc;
|
||||
stream->last_as_blank = -1;
|
||||
@ -68,42 +68,42 @@ stream_s *stream_init(device_s *dev, encoder_s *enc) {
|
||||
return stream;
|
||||
}
|
||||
|
||||
void stream_destroy(stream_s *stream) {
|
||||
A_MUTEX_DESTROY(&RUN(video->mutex));
|
||||
frame_destroy(RUN(video->frame));
|
||||
free(RUN(video));
|
||||
void us_stream_destroy(us_stream_s *stream) {
|
||||
US_MUTEX_DESTROY(&_RUN(video->mutex));
|
||||
us_frame_destroy(_RUN(video->frame));
|
||||
free(_RUN(video));
|
||||
free(stream->run);
|
||||
free(stream);
|
||||
}
|
||||
|
||||
void stream_loop(stream_s *stream) {
|
||||
void us_stream_loop(us_stream_s *stream) {
|
||||
assert(stream->blank);
|
||||
|
||||
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
|
||||
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
|
||||
US_LOG_INFO("Using V4L2 device: %s", stream->dev->path);
|
||||
US_LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
|
||||
|
||||
if (stream->h264_sink) {
|
||||
RUN(h264) = h264_stream_init(stream->h264_sink, stream->h264_m2m_path, stream->h264_bitrate, stream->h264_gop);
|
||||
_RUN(h264) = us_h264_stream_init(stream->h264_sink, stream->h264_m2m_path, stream->h264_bitrate, stream->h264_gop);
|
||||
}
|
||||
|
||||
for (workers_pool_s *pool; (pool = _stream_init_loop(stream)) != NULL;) {
|
||||
for (us_workers_pool_s *pool; (pool = _stream_init_loop(stream)) != NULL;) {
|
||||
long double grab_after = 0;
|
||||
unsigned fluency_passed = 0;
|
||||
unsigned captured_fps = 0;
|
||||
unsigned captured_fps_accum = 0;
|
||||
long long captured_fps_second = 0;
|
||||
|
||||
LOG_INFO("Capturing ...");
|
||||
US_LOG_INFO("Capturing ...");
|
||||
|
||||
while (!atomic_load(&RUN(stop))) {
|
||||
SEP_DEBUG('-');
|
||||
LOG_DEBUG("Waiting for worker ...");
|
||||
while (!atomic_load(&_RUN(stop))) {
|
||||
US_SEP_DEBUG('-');
|
||||
US_LOG_DEBUG("Waiting for worker ...");
|
||||
|
||||
worker_s *ready_wr = workers_pool_wait(pool);
|
||||
encoder_job_s *ready_job = (encoder_job_s *)(ready_wr->job);
|
||||
us_worker_s *ready_wr = us_workers_pool_wait(pool);
|
||||
us_encoder_job_s *ready_job = (us_encoder_job_s *)(ready_wr->job);
|
||||
|
||||
if (ready_job->hw) {
|
||||
if (device_release_buffer(stream->dev, ready_job->hw) < 0) {
|
||||
if (us_device_release_buffer(stream->dev, ready_job->hw) < 0) {
|
||||
ready_wr->job_failed = true;
|
||||
}
|
||||
ready_job->hw = NULL;
|
||||
@ -111,9 +111,9 @@ void stream_loop(stream_s *stream) {
|
||||
if (!ready_wr->job_failed) {
|
||||
if (ready_wr->job_timely) {
|
||||
_stream_expose_frame(stream, ready_job->dest, captured_fps);
|
||||
LOG_PERF("##### Encoded frame exposed; worker=%s", ready_wr->name);
|
||||
US_LOG_PERF("##### Encoded frame exposed; worker=%s", ready_wr->name);
|
||||
} else {
|
||||
LOG_PERF("----- Encoded frame dropped; worker=%s", ready_wr->name);
|
||||
US_LOG_PERF("----- Encoded frame dropped; worker=%s", ready_wr->name);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
@ -123,51 +123,51 @@ void stream_loop(stream_s *stream) {
|
||||
bool h264_force_key = false;
|
||||
if (stream->slowdown) {
|
||||
unsigned slc = 0;
|
||||
for (; slc < 10 && !atomic_load(&RUN(stop)) && !stream_has_clients(stream); ++slc) {
|
||||
for (; slc < 10 && !atomic_load(&_RUN(stop)) && !us_stream_has_clients(stream); ++slc) {
|
||||
usleep(100000);
|
||||
++slc;
|
||||
}
|
||||
h264_force_key = (slc == 10);
|
||||
}
|
||||
|
||||
if (atomic_load(&RUN(stop))) {
|
||||
if (atomic_load(&_RUN(stop))) {
|
||||
break;
|
||||
}
|
||||
|
||||
bool has_read;
|
||||
bool has_write;
|
||||
bool has_error;
|
||||
int selected = device_select(stream->dev, &has_read, &has_write, &has_error);
|
||||
int selected = us_device_select(stream->dev, &has_read, &has_write, &has_error);
|
||||
|
||||
if (selected < 0) {
|
||||
if (errno != EINTR) {
|
||||
LOG_PERROR("Mainloop select() error");
|
||||
US_LOG_PERROR("Mainloop select() error");
|
||||
break;
|
||||
}
|
||||
} else if (selected == 0) { // Persistent timeout
|
||||
# ifdef WITH_GPIO
|
||||
gpio_set_stream_online(false);
|
||||
us_gpio_set_stream_online(false);
|
||||
# endif
|
||||
} else {
|
||||
if (has_read) {
|
||||
LOG_DEBUG("Frame is ready");
|
||||
US_LOG_DEBUG("Frame is ready");
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
gpio_set_stream_online(true);
|
||||
us_gpio_set_stream_online(true);
|
||||
# endif
|
||||
|
||||
const long double now = get_now_monotonic();
|
||||
const long long now_second = floor_ms(now);
|
||||
const long double now = us_get_now_monotonic();
|
||||
const long long now_second = us_floor_ms(now);
|
||||
|
||||
hw_buffer_s *hw;
|
||||
int buf_index = device_grab_buffer(stream->dev, &hw);
|
||||
us_hw_buffer_s *hw;
|
||||
int buf_index = us_device_grab_buffer(stream->dev, &hw);
|
||||
|
||||
if (buf_index >= 0) {
|
||||
if (now < grab_after) {
|
||||
fluency_passed += 1;
|
||||
LOG_VERBOSE("Passed %u frames for fluency: now=%.03Lf, grab_after=%.03Lf",
|
||||
US_LOG_VERBOSE("Passed %u frames for fluency: now=%.03Lf, grab_after=%.03Lf",
|
||||
fluency_passed, now, grab_after);
|
||||
if (device_release_buffer(stream->dev, hw) < 0) {
|
||||
if (us_device_release_buffer(stream->dev, hw) < 0) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@ -177,20 +177,20 @@ void stream_loop(stream_s *stream) {
|
||||
captured_fps = captured_fps_accum;
|
||||
captured_fps_accum = 0;
|
||||
captured_fps_second = now_second;
|
||||
LOG_PERF_FPS("A new second has come; captured_fps=%u", captured_fps);
|
||||
US_LOG_PERF_FPS("A new second has come; captured_fps=%u", captured_fps);
|
||||
}
|
||||
captured_fps_accum += 1;
|
||||
|
||||
const long double fluency_delay = workers_pool_get_fluency_delay(pool, ready_wr);
|
||||
const long double fluency_delay = us_workers_pool_get_fluency_delay(pool, ready_wr);
|
||||
grab_after = now + fluency_delay;
|
||||
LOG_VERBOSE("Fluency: delay=%.03Lf, grab_after=%.03Lf", fluency_delay, grab_after);
|
||||
US_LOG_VERBOSE("Fluency: delay=%.03Lf, grab_after=%.03Lf", fluency_delay, grab_after);
|
||||
|
||||
ready_job->hw = hw;
|
||||
workers_pool_assign(pool, ready_wr);
|
||||
LOG_DEBUG("Assigned new frame in buffer=%d to worker=%s", buf_index, ready_wr->name);
|
||||
us_workers_pool_assign(pool, ready_wr);
|
||||
US_LOG_DEBUG("Assigned new frame in buffer=%d to worker=%s", buf_index, ready_wr->name);
|
||||
|
||||
SINK_PUT(raw_sink, &hw->raw);
|
||||
H264_PUT(&hw->raw, h264_force_key);
|
||||
_SINK_PUT(raw_sink, &hw->raw);
|
||||
_H264_PUT(&hw->raw, h264_force_key);
|
||||
}
|
||||
} else if (buf_index != -2) { // -2 for broken frame
|
||||
break;
|
||||
@ -198,72 +198,72 @@ void stream_loop(stream_s *stream) {
|
||||
}
|
||||
|
||||
if (has_write) {
|
||||
LOG_ERROR("Got unexpected writing event, seems device was disconnected");
|
||||
US_LOG_ERROR("Got unexpected writing event, seems device was disconnected");
|
||||
break;
|
||||
}
|
||||
|
||||
if (has_error) {
|
||||
LOG_INFO("Got V4L2 event");
|
||||
if (device_consume_event(stream->dev) < 0) {
|
||||
US_LOG_INFO("Got V4L2 event");
|
||||
if (us_device_consume_event(stream->dev) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
workers_pool_destroy(pool);
|
||||
device_switch_capturing(stream->dev, false);
|
||||
device_close(stream->dev);
|
||||
us_workers_pool_destroy(pool);
|
||||
us_device_switch_capturing(stream->dev, false);
|
||||
us_device_close(stream->dev);
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
gpio_set_stream_online(false);
|
||||
us_gpio_set_stream_online(false);
|
||||
# endif
|
||||
}
|
||||
|
||||
if (RUN(h264)) {
|
||||
h264_stream_destroy(RUN(h264));
|
||||
if (_RUN(h264)) {
|
||||
us_h264_stream_destroy(_RUN(h264));
|
||||
}
|
||||
}
|
||||
|
||||
void stream_loop_break(stream_s *stream) {
|
||||
atomic_store(&RUN(stop), true);
|
||||
void us_stream_loop_break(us_stream_s *stream) {
|
||||
atomic_store(&_RUN(stop), true);
|
||||
}
|
||||
|
||||
bool stream_has_clients(stream_s *stream) {
|
||||
bool us_stream_has_clients(us_stream_s *stream) {
|
||||
return (
|
||||
atomic_load(&RUN(video->has_clients))
|
||||
atomic_load(&_RUN(video->has_clients))
|
||||
// has_clients синков НЕ обновляются в реальном времени
|
||||
|| (stream->sink != NULL && atomic_load(&stream->sink->has_clients))
|
||||
|| (RUN(h264) != NULL && /*RUN(h264->sink) == NULL ||*/ atomic_load(&RUN(h264->sink->has_clients)))
|
||||
|| (_RUN(h264) != NULL && /*_RUN(h264->sink) == NULL ||*/ atomic_load(&_RUN(h264->sink->has_clients)))
|
||||
);
|
||||
}
|
||||
|
||||
static workers_pool_s *_stream_init_loop(stream_s *stream) {
|
||||
static us_workers_pool_s *_stream_init_loop(us_stream_s *stream) {
|
||||
|
||||
workers_pool_s *pool = NULL;
|
||||
us_workers_pool_s *pool = NULL;
|
||||
int access_error = 0;
|
||||
|
||||
LOG_DEBUG("%s: stream->run->stop=%d", __FUNCTION__, atomic_load(&RUN(stop)));
|
||||
US_LOG_DEBUG("%s: stream->run->stop=%d", __FUNCTION__, atomic_load(&_RUN(stop)));
|
||||
|
||||
while (!atomic_load(&RUN(stop))) {
|
||||
while (!atomic_load(&_RUN(stop))) {
|
||||
_stream_expose_frame(stream, NULL, 0);
|
||||
|
||||
if (access(stream->dev->path, R_OK|W_OK) < 0) {
|
||||
if (access_error != errno) {
|
||||
SEP_INFO('=');
|
||||
LOG_PERROR("Can't access device");
|
||||
LOG_INFO("Waiting for the device access ...");
|
||||
US_SEP_INFO('=');
|
||||
US_LOG_PERROR("Can't access device");
|
||||
US_LOG_INFO("Waiting for the device access ...");
|
||||
access_error = errno;
|
||||
}
|
||||
sleep(stream->error_delay);
|
||||
continue;
|
||||
} else {
|
||||
SEP_INFO('=');
|
||||
US_SEP_INFO('=');
|
||||
access_error = 0;
|
||||
}
|
||||
|
||||
if ((pool = _stream_init_one(stream)) == NULL) {
|
||||
LOG_INFO("Sleeping %u seconds before new stream init ...", stream->error_delay);
|
||||
US_LOG_INFO("Sleeping %u seconds before new stream init ...", stream->error_delay);
|
||||
sleep(stream->error_delay);
|
||||
} else {
|
||||
break;
|
||||
@ -272,90 +272,86 @@ static workers_pool_s *_stream_init_loop(stream_s *stream) {
|
||||
return pool;
|
||||
}
|
||||
|
||||
static workers_pool_s *_stream_init_one(stream_s *stream) {
|
||||
if (device_open(stream->dev) < 0) {
|
||||
static us_workers_pool_s *_stream_init_one(us_stream_s *stream) {
|
||||
if (us_device_open(stream->dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (
|
||||
stream->enc->type == ENCODER_TYPE_M2M_VIDEO
|
||||
|| stream->enc->type == ENCODER_TYPE_M2M_IMAGE
|
||||
|| (RUN(h264) && !is_jpeg(stream->dev->run->format))
|
||||
stream->enc->type == US_ENCODER_TYPE_M2M_VIDEO
|
||||
|| stream->enc->type == US_ENCODER_TYPE_M2M_IMAGE
|
||||
|| (_RUN(h264) && !us_is_jpeg(stream->dev->run->format))
|
||||
) {
|
||||
device_export_to_dma(stream->dev);
|
||||
us_device_export_to_dma(stream->dev);
|
||||
}
|
||||
if (device_switch_capturing(stream->dev, true) < 0) {
|
||||
if (us_device_switch_capturing(stream->dev, true) < 0) {
|
||||
goto error;
|
||||
}
|
||||
return encoder_workers_pool_init(stream->enc, stream->dev);
|
||||
return us_encoder_workers_pool_init(stream->enc, stream->dev);
|
||||
error:
|
||||
device_close(stream->dev);
|
||||
us_device_close(stream->dev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void _stream_expose_frame(stream_s *stream, frame_s *frame, unsigned captured_fps) {
|
||||
# define VID(_next) RUN(video->_next)
|
||||
static void _stream_expose_frame(us_stream_s *stream, us_frame_s *frame, unsigned captured_fps) {
|
||||
# define VID(x_next) _RUN(video->x_next)
|
||||
|
||||
frame_s *new = NULL;
|
||||
us_frame_s *new = NULL;
|
||||
|
||||
A_MUTEX_LOCK(&VID(mutex));
|
||||
US_MUTEX_LOCK(&VID(mutex));
|
||||
|
||||
if (frame) {
|
||||
new = frame;
|
||||
RUN(last_as_blank_ts) = 0; // Останавливаем таймер
|
||||
LOG_DEBUG("Exposed ALIVE video frame");
|
||||
_RUN(last_as_blank_ts) = 0; // Останавливаем таймер
|
||||
US_LOG_DEBUG("Exposed ALIVE video frame");
|
||||
|
||||
} else {
|
||||
if (VID(frame->used == 0)) {
|
||||
new = stream->blank; // Инициализация
|
||||
RUN(last_as_blank_ts) = 0;
|
||||
_RUN(last_as_blank_ts) = 0;
|
||||
|
||||
} else if (VID(frame->online)) { // Если переходим из online в offline
|
||||
if (stream->last_as_blank < 0) { // Если last_as_blank выключен, просто покажем старую картинку
|
||||
new = stream->blank;
|
||||
LOG_INFO("Changed video frame to BLANK");
|
||||
US_LOG_INFO("Changed video frame to BLANK");
|
||||
} else if (stream->last_as_blank > 0) { // // Если нужен таймер - запустим
|
||||
RUN(last_as_blank_ts) = get_now_monotonic() + stream->last_as_blank;
|
||||
LOG_INFO("Freezed last ALIVE video frame for %d seconds", stream->last_as_blank);
|
||||
_RUN(last_as_blank_ts) = us_get_now_monotonic() + stream->last_as_blank;
|
||||
US_LOG_INFO("Freezed last ALIVE video frame for %d seconds", stream->last_as_blank);
|
||||
} else { // last_as_blank == 0 - показываем последний фрейм вечно
|
||||
LOG_INFO("Freezed last ALIVE video frame forever");
|
||||
US_LOG_INFO("Freezed last ALIVE video frame forever");
|
||||
}
|
||||
|
||||
} else if (stream->last_as_blank < 0) {
|
||||
new = stream->blank;
|
||||
// LOG_INFO("Changed video frame to BLANK");
|
||||
// US_LOG_INFO("Changed video frame to BLANK");
|
||||
}
|
||||
|
||||
if ( // Если уже оффлайн, включена фича last_as_blank с таймером и он запущен
|
||||
stream->last_as_blank > 0
|
||||
&& RUN(last_as_blank_ts) != 0
|
||||
&& RUN(last_as_blank_ts) < get_now_monotonic()
|
||||
&& _RUN(last_as_blank_ts) != 0
|
||||
&& _RUN(last_as_blank_ts) < us_get_now_monotonic()
|
||||
) {
|
||||
new = stream->blank;
|
||||
RUN(last_as_blank_ts) = 0; // // Останавливаем таймер
|
||||
LOG_INFO("Changed last ALIVE video frame to BLANK");
|
||||
_RUN(last_as_blank_ts) = 0; // // Останавливаем таймер
|
||||
US_LOG_INFO("Changed last ALIVE video frame to BLANK");
|
||||
}
|
||||
}
|
||||
|
||||
if (new) {
|
||||
frame_copy(new, VID(frame));
|
||||
us_frame_copy(new, VID(frame));
|
||||
}
|
||||
VID(frame->online) = (bool)frame;
|
||||
VID(captured_fps) = captured_fps;
|
||||
atomic_store(&VID(updated), true);
|
||||
|
||||
A_MUTEX_UNLOCK(&VID(mutex));
|
||||
US_MUTEX_UNLOCK(&VID(mutex));
|
||||
|
||||
new = (frame ? frame : stream->blank);
|
||||
SINK_PUT(sink, new);
|
||||
_SINK_PUT(sink, new);
|
||||
|
||||
if (frame == NULL) {
|
||||
SINK_PUT(raw_sink, stream->blank);
|
||||
H264_PUT(stream->blank, false);
|
||||
_SINK_PUT(raw_sink, stream->blank);
|
||||
_H264_PUT(stream->blank, false);
|
||||
}
|
||||
|
||||
# undef VID
|
||||
}
|
||||
|
||||
#undef H264_PUT
|
||||
#undef SINK_PUT
|
||||
#undef RUN
|
||||
|
||||
@ -49,48 +49,48 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
frame_s *frame;
|
||||
us_frame_s *frame;
|
||||
unsigned captured_fps;
|
||||
atomic_bool updated;
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
atomic_bool has_clients; // For slowdown
|
||||
} video_s;
|
||||
} us_video_s;
|
||||
|
||||
typedef struct {
|
||||
video_s *video;
|
||||
long double last_as_blank_ts;
|
||||
us_video_s *video;
|
||||
long double last_as_blank_ts;
|
||||
|
||||
h264_stream_s *h264;
|
||||
us_h264_stream_s *h264;
|
||||
|
||||
atomic_bool stop;
|
||||
} stream_runtime_s;
|
||||
atomic_bool stop;
|
||||
} us_stream_runtime_s;
|
||||
|
||||
typedef struct {
|
||||
device_s *dev;
|
||||
encoder_s *enc;
|
||||
us_device_s *dev;
|
||||
us_encoder_s *enc;
|
||||
|
||||
frame_s *blank;
|
||||
int last_as_blank;
|
||||
bool slowdown;
|
||||
unsigned error_delay;
|
||||
us_frame_s *blank;
|
||||
int last_as_blank;
|
||||
bool slowdown;
|
||||
unsigned error_delay;
|
||||
|
||||
memsink_s *sink;
|
||||
memsink_s *raw_sink;
|
||||
us_memsink_s *sink;
|
||||
us_memsink_s *raw_sink;
|
||||
|
||||
memsink_s *h264_sink;
|
||||
unsigned h264_bitrate;
|
||||
unsigned h264_gop;
|
||||
char *h264_m2m_path;
|
||||
us_memsink_s *h264_sink;
|
||||
unsigned h264_bitrate;
|
||||
unsigned h264_gop;
|
||||
char *h264_m2m_path;
|
||||
|
||||
stream_runtime_s *run;
|
||||
} stream_s;
|
||||
us_stream_runtime_s *run;
|
||||
} us_stream_s;
|
||||
|
||||
|
||||
stream_s *stream_init(device_s *dev, encoder_s *enc);
|
||||
void stream_destroy(stream_s *stream);
|
||||
us_stream_s *us_stream_init(us_device_s *dev, us_encoder_s *enc);
|
||||
void us_stream_destroy(us_stream_s *stream);
|
||||
|
||||
void stream_loop(stream_s *stream);
|
||||
void stream_loop_break(stream_s *stream);
|
||||
void us_stream_loop(us_stream_s *stream);
|
||||
void us_stream_loop_break(us_stream_s *stream);
|
||||
|
||||
bool stream_has_clients(stream_s *stream);
|
||||
bool us_stream_has_clients(us_stream_s *stream);
|
||||
|
||||
@ -26,16 +26,16 @@
|
||||
static void *_worker_thread(void *v_worker);
|
||||
|
||||
|
||||
workers_pool_s *workers_pool_init(
|
||||
us_workers_pool_s *us_workers_pool_init(
|
||||
const char *name, const char *wr_prefix, unsigned n_workers, long double desired_interval,
|
||||
workers_pool_job_init_f job_init, void *job_init_arg,
|
||||
workers_pool_job_destroy_f job_destroy,
|
||||
workers_pool_run_job_f run_job) {
|
||||
us_workers_pool_job_init_f job_init, void *job_init_arg,
|
||||
us_workers_pool_job_destroy_f job_destroy,
|
||||
us_workers_pool_run_job_f run_job) {
|
||||
|
||||
LOG_INFO("Creating pool %s with %u workers ...", name, n_workers);
|
||||
US_LOG_INFO("Creating pool %s with %u workers ...", name, n_workers);
|
||||
|
||||
workers_pool_s *pool;
|
||||
A_CALLOC(pool, 1);
|
||||
us_workers_pool_s *pool;
|
||||
US_CALLOC(pool, 1);
|
||||
pool->name = name;
|
||||
pool->desired_interval = desired_interval;
|
||||
pool->job_destroy = job_destroy;
|
||||
@ -44,25 +44,25 @@ workers_pool_s *workers_pool_init(
|
||||
atomic_init(&pool->stop, false);
|
||||
|
||||
pool->n_workers = n_workers;
|
||||
A_CALLOC(pool->workers, pool->n_workers);
|
||||
US_CALLOC(pool->workers, pool->n_workers);
|
||||
|
||||
A_MUTEX_INIT(&pool->free_workers_mutex);
|
||||
A_COND_INIT(&pool->free_workers_cond);
|
||||
US_MUTEX_INIT(&pool->free_workers_mutex);
|
||||
US_COND_INIT(&pool->free_workers_cond);
|
||||
|
||||
for (unsigned number = 0; number < pool->n_workers; ++number) {
|
||||
# define WR(_next) pool->workers[number]._next
|
||||
# define WR(x_next) pool->workers[number].x_next
|
||||
|
||||
WR(number) = number;
|
||||
A_ASPRINTF(WR(name), "%s-%u", wr_prefix, number);
|
||||
US_ASPRINTF(WR(name), "%s-%u", wr_prefix, number);
|
||||
|
||||
A_MUTEX_INIT(&WR(has_job_mutex));
|
||||
US_MUTEX_INIT(&WR(has_job_mutex));
|
||||
atomic_init(&WR(has_job), false);
|
||||
A_COND_INIT(&WR(has_job_cond));
|
||||
US_COND_INIT(&WR(has_job_cond));
|
||||
|
||||
WR(pool) = pool;
|
||||
WR(job) = job_init(job_init_arg);
|
||||
|
||||
A_THREAD_CREATE(&WR(tid), _worker_thread, (void *)&(pool->workers[number]));
|
||||
US_THREAD_CREATE(&WR(tid), _worker_thread, (void *)&(pool->workers[number]));
|
||||
pool->free_workers += 1;
|
||||
|
||||
# undef WR
|
||||
@ -70,21 +70,21 @@ workers_pool_s *workers_pool_init(
|
||||
return pool;
|
||||
}
|
||||
|
||||
void workers_pool_destroy(workers_pool_s *pool) {
|
||||
LOG_INFO("Destroying workers pool %s ...", pool->name);
|
||||
void us_workers_pool_destroy(us_workers_pool_s *pool) {
|
||||
US_LOG_INFO("Destroying workers pool %s ...", pool->name);
|
||||
|
||||
atomic_store(&pool->stop, true);
|
||||
for (unsigned number = 0; number < pool->n_workers; ++number) {
|
||||
# define WR(_next) pool->workers[number]._next
|
||||
# define WR(x_next) pool->workers[number].x_next
|
||||
|
||||
A_MUTEX_LOCK(&WR(has_job_mutex));
|
||||
US_MUTEX_LOCK(&WR(has_job_mutex));
|
||||
atomic_store(&WR(has_job), true); // Final job: die
|
||||
A_MUTEX_UNLOCK(&WR(has_job_mutex));
|
||||
A_COND_SIGNAL(&WR(has_job_cond));
|
||||
US_MUTEX_UNLOCK(&WR(has_job_mutex));
|
||||
US_COND_SIGNAL(&WR(has_job_cond));
|
||||
|
||||
A_THREAD_JOIN(WR(tid));
|
||||
A_MUTEX_DESTROY(&WR(has_job_mutex));
|
||||
A_COND_DESTROY(&WR(has_job_cond));
|
||||
US_THREAD_JOIN(WR(tid));
|
||||
US_MUTEX_DESTROY(&WR(has_job_mutex));
|
||||
US_COND_DESTROY(&WR(has_job_cond));
|
||||
|
||||
free(WR(name));
|
||||
|
||||
@ -93,19 +93,19 @@ void workers_pool_destroy(workers_pool_s *pool) {
|
||||
# undef WR
|
||||
}
|
||||
|
||||
A_MUTEX_DESTROY(&pool->free_workers_mutex);
|
||||
A_COND_DESTROY(&pool->free_workers_cond);
|
||||
US_MUTEX_DESTROY(&pool->free_workers_mutex);
|
||||
US_COND_DESTROY(&pool->free_workers_cond);
|
||||
|
||||
free(pool->workers);
|
||||
free(pool);
|
||||
}
|
||||
|
||||
worker_s *workers_pool_wait(workers_pool_s *pool) {
|
||||
worker_s *ready_wr = NULL;
|
||||
us_worker_s *us_workers_pool_wait(us_workers_pool_s *pool) {
|
||||
us_worker_s *ready_wr = NULL;
|
||||
|
||||
A_MUTEX_LOCK(&pool->free_workers_mutex);
|
||||
A_COND_WAIT_TRUE(pool->free_workers, &pool->free_workers_cond, &pool->free_workers_mutex);
|
||||
A_MUTEX_UNLOCK(&pool->free_workers_mutex);
|
||||
US_MUTEX_LOCK(&pool->free_workers_mutex);
|
||||
US_COND_WAIT_TRUE(pool->free_workers, &pool->free_workers_cond, &pool->free_workers_mutex);
|
||||
US_MUTEX_UNLOCK(&pool->free_workers_mutex);
|
||||
|
||||
if (pool->oldest_wr && !atomic_load(&pool->oldest_wr->has_job)) {
|
||||
ready_wr = pool->oldest_wr;
|
||||
@ -129,7 +129,7 @@ worker_s *workers_pool_wait(workers_pool_s *pool) {
|
||||
return ready_wr;
|
||||
}
|
||||
|
||||
void workers_pool_assign(workers_pool_s *pool, worker_s *ready_wr/*, void *job*/) {
|
||||
void us_workers_pool_assign(us_workers_pool_s *pool, us_worker_s *ready_wr/*, void *job*/) {
|
||||
if (pool->oldest_wr == NULL) {
|
||||
pool->oldest_wr = ready_wr;
|
||||
pool->latest_wr = pool->oldest_wr;
|
||||
@ -146,21 +146,21 @@ void workers_pool_assign(workers_pool_s *pool, worker_s *ready_wr/*, void *job*/
|
||||
}
|
||||
pool->latest_wr->next_wr = NULL;
|
||||
|
||||
A_MUTEX_LOCK(&ready_wr->has_job_mutex);
|
||||
US_MUTEX_LOCK(&ready_wr->has_job_mutex);
|
||||
//ready_wr->job = job;
|
||||
atomic_store(&ready_wr->has_job, true);
|
||||
A_MUTEX_UNLOCK(&ready_wr->has_job_mutex);
|
||||
A_COND_SIGNAL(&ready_wr->has_job_cond);
|
||||
US_MUTEX_UNLOCK(&ready_wr->has_job_mutex);
|
||||
US_COND_SIGNAL(&ready_wr->has_job_cond);
|
||||
|
||||
A_MUTEX_LOCK(&pool->free_workers_mutex);
|
||||
US_MUTEX_LOCK(&pool->free_workers_mutex);
|
||||
pool->free_workers -= 1;
|
||||
A_MUTEX_UNLOCK(&pool->free_workers_mutex);
|
||||
US_MUTEX_UNLOCK(&pool->free_workers_mutex);
|
||||
}
|
||||
|
||||
long double workers_pool_get_fluency_delay(workers_pool_s *pool, worker_s *ready_wr) {
|
||||
long double us_workers_pool_get_fluency_delay(us_workers_pool_s *pool, us_worker_s *ready_wr) {
|
||||
const long double approx_job_time = pool->approx_job_time * 0.9 + ready_wr->last_job_time * 0.1;
|
||||
|
||||
LOG_VERBOSE("Correcting pool's %s approx_job_time: %.3Lf -> %.3Lf (last_job_time=%.3Lf)",
|
||||
US_LOG_VERBOSE("Correcting pool's %s approx_job_time: %.3Lf -> %.3Lf (last_job_time=%.3Lf)",
|
||||
pool->name, pool->approx_job_time, approx_job_time, ready_wr->last_job_time);
|
||||
|
||||
pool->approx_job_time = approx_job_time;
|
||||
@ -176,35 +176,35 @@ long double workers_pool_get_fluency_delay(workers_pool_s *pool, worker_s *ready
|
||||
}
|
||||
|
||||
static void *_worker_thread(void *v_worker) {
|
||||
worker_s *wr = (worker_s *)v_worker;
|
||||
us_worker_s *wr = (us_worker_s *)v_worker;
|
||||
|
||||
A_THREAD_RENAME("%s", wr->name);
|
||||
LOG_DEBUG("Hello! I am a worker %s ^_^", wr->name);
|
||||
US_THREAD_RENAME("%s", wr->name);
|
||||
US_LOG_DEBUG("Hello! I am a worker %s ^_^", wr->name);
|
||||
|
||||
while (!atomic_load(&wr->pool->stop)) {
|
||||
LOG_DEBUG("Worker %s waiting for a new job ...", wr->name);
|
||||
US_LOG_DEBUG("Worker %s waiting for a new job ...", wr->name);
|
||||
|
||||
A_MUTEX_LOCK(&wr->has_job_mutex);
|
||||
A_COND_WAIT_TRUE(atomic_load(&wr->has_job), &wr->has_job_cond, &wr->has_job_mutex);
|
||||
A_MUTEX_UNLOCK(&wr->has_job_mutex);
|
||||
US_MUTEX_LOCK(&wr->has_job_mutex);
|
||||
US_COND_WAIT_TRUE(atomic_load(&wr->has_job), &wr->has_job_cond, &wr->has_job_mutex);
|
||||
US_MUTEX_UNLOCK(&wr->has_job_mutex);
|
||||
|
||||
if (!atomic_load(&wr->pool->stop)) {
|
||||
long double job_start_ts = get_now_monotonic();
|
||||
long double job_start_ts = us_get_now_monotonic();
|
||||
wr->job_failed = !wr->pool->run_job(wr);
|
||||
if (!wr->job_failed) {
|
||||
wr->job_start_ts = job_start_ts;
|
||||
wr->last_job_time = get_now_monotonic() - wr->job_start_ts;
|
||||
wr->last_job_time = us_get_now_monotonic() - wr->job_start_ts;
|
||||
}
|
||||
//wr->job = NULL;
|
||||
atomic_store(&wr->has_job, false);
|
||||
}
|
||||
|
||||
A_MUTEX_LOCK(&wr->pool->free_workers_mutex);
|
||||
US_MUTEX_LOCK(&wr->pool->free_workers_mutex);
|
||||
wr->pool->free_workers += 1;
|
||||
A_MUTEX_UNLOCK(&wr->pool->free_workers_mutex);
|
||||
A_COND_SIGNAL(&wr->pool->free_workers_cond);
|
||||
US_MUTEX_UNLOCK(&wr->pool->free_workers_mutex);
|
||||
US_COND_SIGNAL(&wr->pool->free_workers_cond);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Bye-bye (worker %s)", wr->name);
|
||||
US_LOG_DEBUG("Bye-bye (worker %s)", wr->name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
#include "../libs/logging.h"
|
||||
|
||||
|
||||
typedef struct worker_sx {
|
||||
typedef struct us_worker_sx {
|
||||
pthread_t tid;
|
||||
unsigned number;
|
||||
char *name;
|
||||
@ -49,27 +49,27 @@ typedef struct worker_sx {
|
||||
long double job_start_ts;
|
||||
pthread_cond_t has_job_cond;
|
||||
|
||||
struct worker_sx *prev_wr;
|
||||
struct worker_sx *next_wr;
|
||||
struct us_worker_sx *prev_wr;
|
||||
struct us_worker_sx *next_wr;
|
||||
|
||||
struct workers_pool_sx *pool;
|
||||
} worker_s;
|
||||
struct us_workers_pool_sx *pool;
|
||||
} us_worker_s;
|
||||
|
||||
typedef void *(*workers_pool_job_init_f)(void *arg);
|
||||
typedef void (*workers_pool_job_destroy_f)(void *job);
|
||||
typedef bool (*workers_pool_run_job_f)(worker_s *wr);
|
||||
typedef void *(*us_workers_pool_job_init_f)(void *arg);
|
||||
typedef void (*us_workers_pool_job_destroy_f)(void *job);
|
||||
typedef bool (*us_workers_pool_run_job_f)(us_worker_s *wr);
|
||||
|
||||
typedef struct workers_pool_sx {
|
||||
typedef struct us_workers_pool_sx {
|
||||
const char *name;
|
||||
long double desired_interval;
|
||||
|
||||
workers_pool_job_destroy_f job_destroy;
|
||||
workers_pool_run_job_f run_job;
|
||||
us_workers_pool_job_destroy_f job_destroy;
|
||||
us_workers_pool_run_job_f run_job;
|
||||
|
||||
unsigned n_workers;
|
||||
worker_s *workers;
|
||||
worker_s *oldest_wr;
|
||||
worker_s *latest_wr;
|
||||
us_worker_s *workers;
|
||||
us_worker_s *oldest_wr;
|
||||
us_worker_s *latest_wr;
|
||||
|
||||
long double approx_job_time;
|
||||
|
||||
@ -78,18 +78,18 @@ typedef struct workers_pool_sx {
|
||||
pthread_cond_t free_workers_cond;
|
||||
|
||||
atomic_bool stop;
|
||||
} workers_pool_s;
|
||||
} us_workers_pool_s;
|
||||
|
||||
|
||||
workers_pool_s *workers_pool_init(
|
||||
us_workers_pool_s *us_workers_pool_init(
|
||||
const char *name, const char *wr_prefix, unsigned n_workers, long double desired_interval,
|
||||
workers_pool_job_init_f job_init, void *job_init_arg,
|
||||
workers_pool_job_destroy_f job_destroy,
|
||||
workers_pool_run_job_f run_job);
|
||||
us_workers_pool_job_init_f job_init, void *job_init_arg,
|
||||
us_workers_pool_job_destroy_f job_destroy,
|
||||
us_workers_pool_run_job_f run_job);
|
||||
|
||||
void workers_pool_destroy(workers_pool_s *pool);
|
||||
void us_workers_pool_destroy(us_workers_pool_s *pool);
|
||||
|
||||
worker_s *workers_pool_wait(workers_pool_s *pool);
|
||||
void workers_pool_assign(workers_pool_s *pool, worker_s *ready_wr/*, void *job*/);
|
||||
us_worker_s *us_workers_pool_wait(us_workers_pool_s *pool);
|
||||
void us_workers_pool_assign(us_workers_pool_s *pool, us_worker_s *ready_wr/*, void *job*/);
|
||||
|
||||
long double workers_pool_get_fluency_delay(workers_pool_s *pool, worker_s *ready_wr);
|
||||
long double us_workers_pool_get_fluency_delay(us_workers_pool_s *pool, us_worker_s *ready_wr);
|
||||
|
||||
@ -41,7 +41,7 @@ def main() -> None:
|
||||
|
||||
html = html.strip()
|
||||
html = html.replace("\"", "\\\"")
|
||||
html = html.replace("%VERSION%", "\" VERSION \"")
|
||||
html = html.replace("%VERSION%", "\" US_VERSION \"")
|
||||
html = textwrap.indent(html, "\t", (lambda line: True))
|
||||
html = "\n".join(
|
||||
(f"{line} \\" if line.strip() else f"{line}\\")
|
||||
@ -49,7 +49,7 @@ def main() -> None:
|
||||
)
|
||||
|
||||
text = f"{common.C_PREPEND}\n#include \"{h_path}\"\n\n\n"
|
||||
text += f"const char *const HTML_{name}_PAGE = \" \\\n{html}\n\";\n"
|
||||
text += f"const char *const US_HTML_{name}_PAGE = \" \\\n{html}\n\";\n"
|
||||
|
||||
with open(c_path, "w") as c_file:
|
||||
c_file.write(text)
|
||||
|
||||
@ -82,10 +82,10 @@ def main() -> None:
|
||||
|
||||
text = f"{common.C_PREPEND}\n"
|
||||
text += f"#include \"{h_path}\"\n\n\n"
|
||||
text += f"const unsigned {name}_JPEG_WIDTH = {width};\n"
|
||||
text += f"const unsigned {name}_JPEG_HEIGHT = {height};\n\n"
|
||||
text += f"const size_t {name}_JPEG_DATA_SIZE = {len(jpeg_data)};\n"
|
||||
text += f"const uint8_t {name}_JPEG_DATA[] = {jpeg_data_text};\n"
|
||||
text += f"const unsigned US_{name}_JPEG_WIDTH = {width};\n"
|
||||
text += f"const unsigned US_{name}_JPEG_HEIGHT = {height};\n\n"
|
||||
text += f"const size_t US_{name}_JPEG_DATA_SIZE = {len(jpeg_data)};\n"
|
||||
text += f"const uint8_t US_{name}_JPEG_DATA[] = {jpeg_data_text};\n"
|
||||
|
||||
with open(c_path, "w") as c_file:
|
||||
c_file.write(text)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user