mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-18 02:55:46 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88460b72e1 | ||
|
|
8c69c77481 | ||
|
|
5331ae14aa | ||
|
|
0127dcf018 | ||
|
|
aa58b1b002 | ||
|
|
a05eab71a8 | ||
|
|
e013356cf0 | ||
|
|
c730981827 | ||
|
|
3bb1ed3ef3 | ||
|
|
1cda22bfd2 | ||
|
|
29ee20e864 | ||
|
|
5e13f4cd58 | ||
|
|
39c1916f61 | ||
|
|
614e83771b | ||
|
|
15a9e28ac6 | ||
|
|
a1ae02de5d | ||
|
|
214708549d | ||
|
|
1be4521664 | ||
|
|
94752dde75 | ||
|
|
b933b7b407 | ||
|
|
61f44b5f97 | ||
|
|
8fef0408b6 | ||
|
|
62028be064 | ||
|
|
3b7592bb31 | ||
|
|
8adca998e9 | ||
|
|
6ac5a5f065 | ||
|
|
12cf4492bd | ||
|
|
a6f111f7cf |
@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 6.46
|
||||
current_version = 6.55
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
11
README.md
11
README.md
@ -35,13 +35,14 @@ If you're going to live-stream from your backyard webcam and need to control it,
|
||||
# Installation
|
||||
|
||||
## Building
|
||||
You need to download the µStreamer onto your system and build it from the sources.
|
||||
You need to download the µStreamer onto your system and build it from the sources, or use a package:
|
||||
|
||||
* AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer.
|
||||
* Fedora: https://src.fedoraproject.org/rpms/ustreamer.
|
||||
* Ubuntu: https://packages.ubuntu.com/jammy/ustreamer.
|
||||
* Arch Linux: https://aur.archlinux.org/packages/ustreamer
|
||||
* Fedora: https://src.fedoraproject.org/rpms/ustreamer
|
||||
* Ubuntu: https://packages.ubuntu.com/jammy/ustreamer
|
||||
* Debian: https://packages.debian.org/sid/ustreamer
|
||||
* FreeBSD port: https://www.freshports.org/multimedia/ustreamer.
|
||||
* OpenWRT: https://github.com/openwrt/packages/tree/master/multimedia/ustreamer
|
||||
* FreeBSD port: https://www.freshports.org/multimedia/ustreamer
|
||||
|
||||
### Preconditions
|
||||
You'll need ```make```, ```gcc```, ```pkg-config```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
|
||||
|
||||
@ -19,7 +19,7 @@ _BUILD = build
|
||||
|
||||
|
||||
# =====
|
||||
ifneq ($(shell sh -c 'uname 2>/dev/null || echo Unknown'),FreeBSD)
|
||||
ifeq ($(findstring bsd,$(shell $(CC) -dumpmachine)),)
|
||||
override _LDFLAGS += -latomic
|
||||
endif
|
||||
|
||||
|
||||
@ -107,11 +107,14 @@ void us_au_pcm_destroy(us_au_pcm_s *pcm) {
|
||||
|
||||
void us_au_pcm_mix(us_au_pcm_s *dest, us_au_pcm_s *src) {
|
||||
const uz size = src->frames * US_RTP_OPUS_CH * 2; // 2 for 16 bit
|
||||
|
||||
if (src->frames == 0) {
|
||||
return;
|
||||
|
||||
} else if (dest->frames == 0) {
|
||||
memcpy(dest->data, src->data, size);
|
||||
dest->frames = src->frames;
|
||||
|
||||
} else if (dest->frames == src->frames) {
|
||||
// https://stackoverflow.com/questions/12089662
|
||||
for (uz index = 0; index < size; ++index) {
|
||||
|
||||
@ -193,18 +193,24 @@ static void *_video_or_acap_thread(void *v_client, bool video) {
|
||||
};
|
||||
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
||||
|
||||
if (rtp.zero_playout_delay) {
|
||||
// https://github.com/pikvm/pikvm/issues/784
|
||||
packet.extensions.min_delay = 0;
|
||||
packet.extensions.max_delay = 0;
|
||||
} else {
|
||||
// Эти дефолты используются в Chrome/Safari/Firefox.
|
||||
// Работает всё одинаково, потому что у них общая кодовая база WebRTC.
|
||||
packet.extensions.min_delay = 0;
|
||||
packet.extensions.max_delay = 1000; // == 10s, i.e. 10ms granularity
|
||||
if (rtp.first_of_frame) {
|
||||
if (rtp.zero_playout_delay) {
|
||||
// https://github.com/pikvm/pikvm/issues/784
|
||||
packet.extensions.min_delay = 0;
|
||||
packet.extensions.max_delay = 0;
|
||||
} else {
|
||||
// Эти дефолты используются в Chrome/Safari/Firefox.
|
||||
// Работает всё одинаково, потому что у них общая кодовая база WebRTC.
|
||||
packet.extensions.min_delay = 0;
|
||||
packet.extensions.max_delay = 1000; // == 10s, i.e. 10ms granularity
|
||||
}
|
||||
}
|
||||
|
||||
if (rtp.video) {
|
||||
if (rtp.video && rtp.first_of_frame) {
|
||||
packet.extensions.abs_capture_ts = rtp.grab_ntp_ts;
|
||||
}
|
||||
|
||||
if (rtp.video && rtp.last_of_frame) {
|
||||
uint video_orient = atomic_load(&client->video_orient);
|
||||
if (video_orient != 0) {
|
||||
// The extension rotates the video clockwise, but want it counterclockwise.
|
||||
|
||||
@ -22,7 +22,6 @@
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
@ -56,6 +55,7 @@
|
||||
#include "rtp.h"
|
||||
#include "rtpv.h"
|
||||
#include "rtpa.h"
|
||||
#include "sdp.h"
|
||||
#include "memsinkfd.h"
|
||||
#include "config.h"
|
||||
|
||||
@ -279,7 +279,7 @@ static void *_acap_thread(void *arg) {
|
||||
if (_get_acap_hz(&hz) < 0 || acap->pcm_hz != hz) {
|
||||
goto close_acap;
|
||||
}
|
||||
uz size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
|
||||
uz size = US_RTP_TOTAL_SIZE - US_RTP_HEADER_SIZE;
|
||||
u8 data[size];
|
||||
u64 pts;
|
||||
const int result = us_acap_get_encoded(acap, data, &size, &pts);
|
||||
@ -641,31 +641,13 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
}
|
||||
|
||||
{
|
||||
char *sdp;
|
||||
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
|
||||
char *const audio_sdp = (with_acap ? us_rtpa_make_sdp(_g_rtpa, with_aplay) : us_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",
|
||||
us_get_now_id() >> 1,
|
||||
# if JANUS_PLUGIN_API_VERSION >= 100
|
||||
// Place video SDP before audio SDP so that the video and audio streams
|
||||
// have predictable indices, even if audio is not available.
|
||||
// See also client.c.
|
||||
video_sdp, audio_sdp
|
||||
# else
|
||||
// For versions of Janus prior to 1.x, place the audio SDP first.
|
||||
audio_sdp, video_sdp
|
||||
# endif
|
||||
);
|
||||
char *const sdp = us_sdp_create(
|
||||
_g_rtpv,
|
||||
(with_acap ? _g_rtpa : NULL),
|
||||
(with_acap && with_aplay));
|
||||
json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
|
||||
PUSH_STATUS("started", NULL, offer_jsep);
|
||||
json_decref(offer_jsep);
|
||||
free(audio_sdp);
|
||||
free(video_sdp);
|
||||
free(sdp);
|
||||
}
|
||||
|
||||
|
||||
@ -25,10 +25,17 @@
|
||||
#include "uslibs/types.h"
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
|
||||
#define US_RTP_DATAGRAM_SIZE 1200
|
||||
// Max RTP size for WebRTC is 1200 bytes:
|
||||
// - https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
|
||||
// But(!) Tailscale has 1200 MTU. So to fit it required to substract:
|
||||
// 1. possible RTP extensions (see sdp.c)
|
||||
// 2. additional SRTP fields (>= 10 bytes)
|
||||
// 3. UDP header (8 bytes)
|
||||
// 4. IPv6 header (40 bytes)
|
||||
// Finally it looks like 100 bytes for all above should be enough
|
||||
#define US_RTP_TOTAL_SIZE (1200 - 100)
|
||||
#define US_RTP_HEADER_SIZE 12
|
||||
#define US_RTP_PAYLOAD_SIZE (US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE)
|
||||
#define US_RTP_PAYLOAD_SIZE (US_RTP_TOTAL_SIZE - US_RTP_HEADER_SIZE)
|
||||
|
||||
#define US_RTP_H264_PAYLOAD 96
|
||||
#define US_RTP_OPUS_PAYLOAD 111
|
||||
@ -43,9 +50,13 @@ typedef struct {
|
||||
u32 ssrc;
|
||||
|
||||
u16 seq;
|
||||
u8 datagram[US_RTP_DATAGRAM_SIZE];
|
||||
u8 datagram[US_RTP_TOTAL_SIZE];
|
||||
uz used;
|
||||
|
||||
bool first_of_frame;
|
||||
bool last_of_frame;
|
||||
bool zero_playout_delay;
|
||||
u64 grab_ntp_ts;
|
||||
} us_rtp_s;
|
||||
|
||||
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
|
||||
|
||||
@ -23,11 +23,12 @@
|
||||
#include "rtpa.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
|
||||
us_rtpa_s *rtpa;
|
||||
@ -43,32 +44,8 @@ void us_rtpa_destroy(us_rtpa_s *rtpa) {
|
||||
free(rtpa);
|
||||
}
|
||||
|
||||
char *us_rtpa_make_sdp(us_rtpa_s *rtpa, bool mic) {
|
||||
const uint pl = rtpa->rtp->payload;
|
||||
char *sdp;
|
||||
US_ASPRINTF(sdp,
|
||||
"m=audio 1 RTP/SAVPF %u" RN
|
||||
"c=IN IP4 0.0.0.0" RN
|
||||
"a=rtpmap:%u OPUS/%u/%u" RN
|
||||
"a=fmtp:%u sprop-stereo=1" RN // useinbandfec=1
|
||||
"a=rtcp-fb:%u nack" RN
|
||||
"a=rtcp-fb:%u nack pli" RN
|
||||
"a=rtcp-fb:%u goog-remb" RN
|
||||
"a=mid:a" RN
|
||||
"a=msid:audio a" RN
|
||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||
"a=%s" RN,
|
||||
pl, pl,
|
||||
US_RTP_OPUS_HZ, US_RTP_OPUS_CH,
|
||||
pl, pl, pl, pl,
|
||||
rtpa->rtp->ssrc,
|
||||
(mic ? "sendrecv" : "sendonly")
|
||||
);
|
||||
return sdp;
|
||||
}
|
||||
|
||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts) {
|
||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_TOTAL_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;
|
||||
|
||||
@ -36,5 +36,4 @@ typedef struct {
|
||||
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback);
|
||||
void us_rtpa_destroy(us_rtpa_s *rtpa);
|
||||
|
||||
char *us_rtpa_make_sdp(us_rtpa_s *rtpa, bool mic);
|
||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const u8 *data, uz size, u32 pts);
|
||||
|
||||
@ -35,6 +35,8 @@
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/frame.h"
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool marked);
|
||||
|
||||
@ -55,33 +57,6 @@ void us_rtpv_destroy(us_rtpv_s *rtpv) {
|
||||
free(rtpv);
|
||||
}
|
||||
|
||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
||||
// https://tools.ietf.org/html/rfc6184
|
||||
// https://github.com/meetecho/janus-gateway/issues/2443
|
||||
const uint pl = rtpv->rtp->payload;
|
||||
char *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
|
||||
"a=fmtp:%u profile-level-id=42E01F" RN
|
||||
"a=fmtp:%u packetization-mode=1" RN
|
||||
"a=rtcp-fb:%u nack" RN
|
||||
"a=rtcp-fb:%u nack pli" RN
|
||||
"a=rtcp-fb:%u goog-remb" RN
|
||||
"a=mid:v" RN
|
||||
"a=msid:video v" RN
|
||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||
"a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
|
||||
"a=extmap:2 urn:3gpp:video-orientation" RN
|
||||
"a=sendonly" RN,
|
||||
pl, pl, pl, pl,
|
||||
pl, pl, pl,
|
||||
rtpv->rtp->ssrc
|
||||
);
|
||||
return sdp;
|
||||
}
|
||||
|
||||
#define _PRE 3 // Annex B prefix length
|
||||
|
||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay) {
|
||||
@ -90,7 +65,10 @@ void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_de
|
||||
|
||||
assert(frame->format == V4L2_PIX_FMT_H264);
|
||||
|
||||
rtpv->rtp->first_of_frame = true;
|
||||
rtpv->rtp->last_of_frame = false;
|
||||
rtpv->rtp->zero_playout_delay = zero_playout_delay;
|
||||
rtpv->rtp->grab_ntp_ts = us_get_now_ntp() - us_ld_to_ntp(us_get_now_monotonic() - frame->grab_begin_ts);
|
||||
|
||||
const u32 pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||
sz last_offset = -_PRE;
|
||||
@ -127,11 +105,30 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool
|
||||
const uint type = data[0] & 0x1F;
|
||||
u8 *dg = rtpv->rtp->datagram;
|
||||
|
||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
||||
// Set *_of_frame flags only for non-SPS/PPS packages
|
||||
/*
|
||||
# define CALL_FOR_SERVICE { \
|
||||
const bool m_fof = rtpv->rtp->first_of_frame; \
|
||||
const bool m_lof = rtpv->rtp->last_of_frame; \
|
||||
rtpv->rtp->first_of_frame = false; \
|
||||
rtpv->rtp->last_of_frame = false; \
|
||||
rtpv->callback(rtpv->rtp); \
|
||||
rtpv->rtp->first_of_frame = m_fof; \
|
||||
rtpv->rtp->last_of_frame = m_lof; \
|
||||
}
|
||||
*/
|
||||
|
||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_TOTAL_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);
|
||||
// if (type == 7 || type == 8) {
|
||||
// CALL_FOR_SERVICE;
|
||||
// } else {*/
|
||||
rtpv->rtp->last_of_frame = true;
|
||||
rtpv->callback(rtpv->rtp);
|
||||
rtpv->rtp->first_of_frame = false;
|
||||
// }
|
||||
return;
|
||||
}
|
||||
|
||||
@ -142,7 +139,7 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool
|
||||
|
||||
bool first = true;
|
||||
while (remaining > 0) {
|
||||
sz frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
|
||||
sz frag_size = US_RTP_TOTAL_SIZE - fu_overhead;
|
||||
const bool last = (remaining <= frag_size);
|
||||
if (last) {
|
||||
frag_size = remaining;
|
||||
@ -163,12 +160,20 @@ void _rtpv_process_nalu(us_rtpv_s *rtpv, const u8 *data, uz size, u32 pts, bool
|
||||
|
||||
memcpy(dg + fu_overhead, src, frag_size);
|
||||
rtpv->rtp->used = fu_overhead + frag_size;
|
||||
rtpv->callback(rtpv->rtp);
|
||||
// if (type == 7 || type == 8) {
|
||||
// CALL_FOR_SERVICE;
|
||||
// } else {
|
||||
rtpv->rtp->last_of_frame = last;
|
||||
rtpv->callback(rtpv->rtp);
|
||||
rtpv->rtp->first_of_frame = false;
|
||||
// }
|
||||
|
||||
src += frag_size;
|
||||
remaining -= frag_size;
|
||||
first = false;
|
||||
}
|
||||
|
||||
# undef CALL_FOR_SERVICE
|
||||
}
|
||||
|
||||
static sz _find_annexb(const u8 *data, uz size) {
|
||||
|
||||
@ -37,5 +37,4 @@ typedef struct {
|
||||
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback);
|
||||
void us_rtpv_destroy(us_rtpv_s *rtpv);
|
||||
|
||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv);
|
||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame, bool zero_playout_delay);
|
||||
|
||||
110
janus/src/sdp.c
Normal file
110
janus/src/sdp.c
Normal file
@ -0,0 +1,110 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "sdp.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <janus/plugins/plugin.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/tools.h"
|
||||
|
||||
#include "rtp.h"
|
||||
#include "rtpv.h"
|
||||
#include "rtpa.h"
|
||||
|
||||
|
||||
char *us_sdp_create(us_rtpv_s *rtpv, us_rtpa_s *rtpa, bool mic) {
|
||||
char *video_sdp;
|
||||
{
|
||||
// https://tools.ietf.org/html/rfc6184
|
||||
// https://github.com/meetecho/janus-gateway/issues/2443
|
||||
const uint pl = rtpv->rtp->payload;
|
||||
US_ASPRINTF(
|
||||
video_sdp,
|
||||
"m=video 1 RTP/SAVPF %u" RN
|
||||
"c=IN IP4 0.0.0.0" RN
|
||||
"a=rtpmap:%u H264/90000" RN
|
||||
"a=fmtp:%u profile-level-id=42E01F;packetization-mode=1" RN
|
||||
"a=rtcp-fb:%u nack" RN
|
||||
"a=rtcp-fb:%u nack pli" RN
|
||||
"a=rtcp-fb:%u goog-remb" RN
|
||||
"a=mid:v" RN
|
||||
"a=msid:video v" RN
|
||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||
"a=extmap:1/sendonly urn:3gpp:video-orientation" RN
|
||||
"a=extmap:2/sendonly http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
|
||||
"a=extmap:3/sendonly http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time" RN
|
||||
"a=sendonly" RN,
|
||||
pl, pl, pl, pl, pl, pl,
|
||||
rtpv->rtp->ssrc);
|
||||
}
|
||||
|
||||
char *audio_sdp;
|
||||
if (rtpa == NULL) {
|
||||
audio_sdp = us_strdup("");
|
||||
} else {
|
||||
const uint pl = rtpa->rtp->payload;
|
||||
US_ASPRINTF(
|
||||
audio_sdp,
|
||||
"m=audio 1 RTP/SAVPF %u" RN
|
||||
"c=IN IP4 0.0.0.0" RN
|
||||
"a=rtpmap:%u OPUS/%u/%u" RN
|
||||
"a=fmtp:%u sprop-stereo=1" RN // useinbandfec=1
|
||||
"a=rtcp-fb:%u nack" RN
|
||||
"a=rtcp-fb:%u nack pli" RN
|
||||
"a=rtcp-fb:%u goog-remb" RN
|
||||
"a=mid:a" RN
|
||||
"a=msid:audio a" RN
|
||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||
"a=%s" RN,
|
||||
pl, pl,
|
||||
US_RTP_OPUS_HZ, US_RTP_OPUS_CH,
|
||||
pl, pl, pl, pl,
|
||||
rtpa->rtp->ssrc,
|
||||
(mic ? "sendrecv" : "sendonly"));
|
||||
}
|
||||
|
||||
char *sdp;
|
||||
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",
|
||||
us_get_now_id() >> 1,
|
||||
# if JANUS_PLUGIN_API_VERSION >= 100
|
||||
// Place video SDP before audio SDP so that the video and audio streams
|
||||
// have predictable indices, even if audio is not available.
|
||||
// See also client.c.
|
||||
video_sdp, audio_sdp
|
||||
# else
|
||||
// For versions of Janus prior to 1.x, place the audio SDP first.
|
||||
audio_sdp, video_sdp
|
||||
# endif
|
||||
);
|
||||
|
||||
free(audio_sdp);
|
||||
free(video_sdp);
|
||||
return sdp;
|
||||
}
|
||||
31
janus/src/sdp.h
Normal file
31
janus/src/sdp.h
Normal file
@ -0,0 +1,31 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "uslibs/types.h"
|
||||
|
||||
#include "rtpv.h"
|
||||
#include "rtpa.h"
|
||||
|
||||
|
||||
char *us_sdp_create(us_rtpv_s *rtpv, us_rtpa_s *rtpa, bool mic);
|
||||
@ -33,7 +33,7 @@ max-line-length = 160
|
||||
|
||||
[BASIC]
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names = _, __, x, y, ws, make-html-h, make-ico-h
|
||||
good-names = _, __, i, x, y, ws, make-html-h, make-ico-h
|
||||
|
||||
# Regular expression matching correct method names
|
||||
method-rgx = [a-z_][a-z0-9_]{2,50}$
|
||||
|
||||
@ -15,8 +15,6 @@ commands = cppcheck \
|
||||
--quiet \
|
||||
--check-level=exhaustive \
|
||||
--enable=warning,portability,performance,style \
|
||||
--suppress=assignmentInAssert \
|
||||
--suppress=assertWithSideEffect \
|
||||
--suppress=variableScope \
|
||||
--inline-suppr \
|
||||
--library=python \
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer-dump.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER-DUMP 1 "version 6.46" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 6.55" "January 2021"
|
||||
|
||||
.SH NAME
|
||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER 1 "version 6.46" "November 2020"
|
||||
.TH USTREAMER 1 "version 6.55" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||
@ -23,7 +23,7 @@ For example, the recommended way of running µStreamer with TC358743-based captu
|
||||
.RS
|
||||
\fB\-\-format=uyvy \e\fR # Device input format
|
||||
.nf
|
||||
\fB\-\-encoder=m2m-image \e\fR # Hardware encoding with V4L2 M2M intraface
|
||||
\fB\-\-encoder=m2m-image \e\fR # Hardware encoding with V4L2 M2M interface
|
||||
.nf
|
||||
\fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder
|
||||
.nf
|
||||
@ -66,7 +66,7 @@ Available: MMAP, USERPTR; default: MMAP.
|
||||
Desired FPS. Default: maximum possible.
|
||||
.TP
|
||||
.BR \-z\ \fIN ", " \-\-min\-frame\-size\ \fIN
|
||||
Drop frames smaller then this limit. Useful if the device produces small\-sized garbage frames. Default: 128 bytes.
|
||||
Drop frames smaller than this limit. Useful if the device produces small\-sized garbage frames. Default: 128 bytes.
|
||||
.TP
|
||||
.BR \-T ", " \-\-allow\-truncated\-frames
|
||||
Allows to handle truncated frames. Useful if the device produces incorrect but still acceptable frames. Default: disabled.
|
||||
@ -78,7 +78,7 @@ Suppress repetitive signal source errors. Default: disabled.
|
||||
Enable DV-timings querying and events processing to automatic resolution change. Default: disabled.
|
||||
.TP
|
||||
.BR \-b\ \fIN ", " \-\-buffers\ \fIN
|
||||
The number of buffers to receive data from the device. Each buffer may processed using an independent thread.
|
||||
The number of buffers to receive data from the device. Each buffer may be processed using an independent thread.
|
||||
Default: 2 (the number of CPU cores (but not more than 4) + 1).
|
||||
.TP
|
||||
.BR \-w\ \fIN ", " \-\-workers\ \fIN
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=6.46
|
||||
pkgver=6.55
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
|
||||
@ -2,11 +2,14 @@
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
# This package is just an example. For OpenWRT it is recommended to use upstream package:
|
||||
# - https://github.com/openwrt/packages/tree/master/multimedia/ustreamer
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=6.46
|
||||
PKG_VERSION:=6.55
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ def main() -> None:
|
||||
flags = _find_flags()
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="6.46",
|
||||
version="6.55",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
|
||||
@ -1,3 +1,25 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
@ -46,7 +46,7 @@ _OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
||||
|
||||
|
||||
# =====
|
||||
ifneq ($(shell sh -c 'uname 2>/dev/null || echo Unknown'),FreeBSD)
|
||||
ifeq ($(findstring bsd,$(shell $(CC) -dumpmachine)),)
|
||||
override _USTR_LDFLAGS += -latomic
|
||||
override _DUMP_LDFLAGS += -latomic
|
||||
override _V4P_LDFLAGS += -latomic
|
||||
@ -78,7 +78,7 @@ endif
|
||||
|
||||
ifneq ($(MK_WITH_SETPROCTITLE),)
|
||||
override _CFLAGS += -DMK_WITH_SETPROCTITLE -DWITH_SETPROCTITLE
|
||||
ifeq ($(shell uname -s | tr A-Z a-z),linux)
|
||||
ifneq ($(findstring linux,$(shell $(CC) -dumpmachine)),)
|
||||
override _USTR_LDFLAGS += -lbsd
|
||||
endif
|
||||
endif
|
||||
|
||||
@ -24,56 +24,73 @@
|
||||
|
||||
|
||||
us_output_file_s *us_output_file_init(const char *path, bool json) {
|
||||
us_output_file_s *output;
|
||||
US_CALLOC(output, 1);
|
||||
us_output_file_s *out;
|
||||
US_CALLOC(out, 1);
|
||||
|
||||
if (!strcmp(path, "-")) {
|
||||
US_LOG_INFO("Using output: <stdout>");
|
||||
output->fp = stdout;
|
||||
out->fp = stdout;
|
||||
} else {
|
||||
US_LOG_INFO("Using output: %s", path);
|
||||
if ((output->fp = fopen(path, "wb")) == NULL) {
|
||||
if ((out->fp = fopen(path, "wb")) == NULL) {
|
||||
US_LOG_PERROR("Can't open output file");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
output->json = json;
|
||||
return output;
|
||||
out->json = json;
|
||||
return out;
|
||||
|
||||
error:
|
||||
us_output_file_destroy(output);
|
||||
us_output_file_destroy(out);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void us_output_file_write(void *v_output, const us_frame_s *frame) {
|
||||
us_output_file_s *output = v_output;
|
||||
if (output->json) {
|
||||
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, \"key\": %u, \"gop\": %u,"
|
||||
" \"grab_begin_ts\": %.3Lf, \"grab_end_ts\": %.3Lf,"
|
||||
" \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf,"
|
||||
void us_output_file_write(void *v_out, const us_frame_s *frame) {
|
||||
us_output_file_s *out = v_out;
|
||||
if (out->json) {
|
||||
us_base64_encode(frame->data, frame->used, &out->base64_data, &out->base64_allocated);
|
||||
fprintf(
|
||||
out->fp,
|
||||
"{\"size\": %zu,"
|
||||
" \"width\": %u,"
|
||||
" \"height\": %u,"
|
||||
" \"format\": %u,"
|
||||
" \"stride\": %u,"
|
||||
" \"online\": %u,"
|
||||
" \"key\": %u,"
|
||||
" \"gop\": %u,"
|
||||
" \"grab_begin_ts\": %.3Lf,"
|
||||
" \"grab_end_ts\": %.3Lf,"
|
||||
" \"encode_begin_ts\": %.3Lf,"
|
||||
" \"encode_end_ts\": %.3Lf,"
|
||||
" \"data\": \"%s\"}\n",
|
||||
frame->used, frame->width, frame->height,
|
||||
frame->format, frame->stride, frame->online, frame->key, frame->gop,
|
||||
frame->grab_begin_ts, frame->grab_end_ts,
|
||||
frame->encode_begin_ts, frame->encode_end_ts,
|
||||
output->base64_data);
|
||||
frame->used,
|
||||
frame->width,
|
||||
frame->height,
|
||||
frame->format,
|
||||
frame->stride,
|
||||
frame->online,
|
||||
frame->key,
|
||||
frame->gop,
|
||||
frame->grab_begin_ts,
|
||||
frame->grab_end_ts,
|
||||
frame->encode_begin_ts,
|
||||
frame->encode_end_ts,
|
||||
out->base64_data);
|
||||
} else {
|
||||
fwrite(frame->data, 1, frame->used, output->fp);
|
||||
fwrite(frame->data, 1, frame->used, out->fp);
|
||||
}
|
||||
fflush(output->fp);
|
||||
fflush(out->fp);
|
||||
}
|
||||
|
||||
void us_output_file_destroy(void *v_output) {
|
||||
us_output_file_s *output = v_output;
|
||||
US_DELETE(output->base64_data, free);
|
||||
if (output->fp && output->fp != stdout) {
|
||||
if (fclose(output->fp) < 0) {
|
||||
void us_output_file_destroy(void *v_out) {
|
||||
us_output_file_s *out = v_out;
|
||||
US_DELETE(out->base64_data, free);
|
||||
if (out->fp && out->fp != stdout) {
|
||||
if (fclose(out->fp) < 0) {
|
||||
US_LOG_PERROR("Can't close output file");
|
||||
}
|
||||
}
|
||||
free(output);
|
||||
free(out);
|
||||
}
|
||||
|
||||
@ -45,5 +45,5 @@ typedef struct {
|
||||
|
||||
|
||||
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);
|
||||
void us_output_file_write(void *v_out, const us_frame_s *frame);
|
||||
void us_output_file_destroy(void *v_out);
|
||||
|
||||
@ -28,7 +28,6 @@
|
||||
#include <float.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "../libs/const.h"
|
||||
#include "../libs/errors.h"
|
||||
@ -90,17 +89,19 @@ volatile bool _g_stop = false;
|
||||
|
||||
|
||||
typedef struct {
|
||||
void *v_output;
|
||||
void (*write)(void *v_output, const us_frame_s *frame);
|
||||
void (*destroy)(void *v_output);
|
||||
void *v_out;
|
||||
void (*write)(void *v_out, const us_frame_s *frame);
|
||||
void (*destroy)(void *v_out);
|
||||
} _output_context_s;
|
||||
|
||||
|
||||
static void _signal_handler(int signum);
|
||||
|
||||
static int _dump_sink(
|
||||
const char *sink_name, unsigned sink_timeout,
|
||||
long long count, long double interval,
|
||||
const char *sink_name,
|
||||
unsigned sink_timeout,
|
||||
long long count,
|
||||
long double interval,
|
||||
bool key_required,
|
||||
_output_context_s *ctx);
|
||||
|
||||
@ -113,8 +114,8 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
const char *sink_name = NULL;
|
||||
unsigned sink_timeout = 1;
|
||||
const char *output_path = NULL;
|
||||
bool output_json = false;
|
||||
const char *out_path = NULL;
|
||||
bool out_json = false;
|
||||
long long count = 0;
|
||||
long double interval = 0;
|
||||
bool key_required = false;
|
||||
@ -151,8 +152,8 @@ int main(int argc, char *argv[]) {
|
||||
switch (ch) {
|
||||
case _O_SINK: OPT_SET(sink_name, optarg);
|
||||
case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0);
|
||||
case _O_OUTPUT: OPT_SET(output_path, optarg);
|
||||
case _O_OUTPUT_JSON: OPT_SET(output_json, true);
|
||||
case _O_OUTPUT: OPT_SET(out_path, optarg);
|
||||
case _O_OUTPUT_JSON: OPT_SET(out_json, true);
|
||||
case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0);
|
||||
case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60);
|
||||
case _O_KEY_REQUIRED: OPT_SET(key_required, true);
|
||||
@ -183,8 +184,8 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
_output_context_s ctx = {0};
|
||||
|
||||
if (output_path && output_path[0] != '\0') {
|
||||
if ((ctx.v_output = (void*)us_output_file_init(output_path, output_json)) == NULL) {
|
||||
if (out_path && out_path[0] != '\0') {
|
||||
if ((ctx.v_out = (void*)us_output_file_init(out_path, out_json)) == NULL) {
|
||||
return 1;
|
||||
}
|
||||
ctx.write = us_output_file_write;
|
||||
@ -193,8 +194,8 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
us_install_signals_handler(_signal_handler, false);
|
||||
const int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, key_required, &ctx));
|
||||
if (ctx.v_output && ctx.destroy) {
|
||||
ctx.destroy(ctx.v_output);
|
||||
if (ctx.v_out && ctx.destroy) {
|
||||
ctx.destroy(ctx.v_out);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
@ -208,10 +209,13 @@ static void _signal_handler(int signum) {
|
||||
}
|
||||
|
||||
static int _dump_sink(
|
||||
const char *sink_name, unsigned sink_timeout,
|
||||
long long count, long double interval,
|
||||
const char *sink_name,
|
||||
unsigned sink_timeout,
|
||||
long long count,
|
||||
long double interval,
|
||||
bool key_required,
|
||||
_output_context_s *ctx) {
|
||||
_output_context_s *ctx
|
||||
) {
|
||||
|
||||
int retval = -1;
|
||||
|
||||
@ -259,8 +263,8 @@ static int _dump_sink(
|
||||
|
||||
us_fpsi_update(fpsi, true, NULL);
|
||||
|
||||
if (ctx->v_output != NULL) {
|
||||
ctx->write(ctx->v_output, frame);
|
||||
if (ctx->v_out != NULL) {
|
||||
ctx->write(ctx->v_out, frame);
|
||||
}
|
||||
|
||||
if (count >= 0) {
|
||||
|
||||
@ -22,16 +22,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
#define US_ARRAY_LEN(x_array) (sizeof(x_array) / sizeof((x_array)[0]))
|
||||
|
||||
#define US_ARRAY_ITERATE(x_array, x_start, x_item_ptr, ...) { \
|
||||
const int m_len = US_ARRAY_LEN(x_array); \
|
||||
assert(x_start <= m_len); \
|
||||
for (int m_index = x_start; m_index < m_len; ++m_index) { \
|
||||
__typeof__((x_array)[0]) *const x_item_ptr = &x_array[m_index]; \
|
||||
US_A(x_start <= m_len); \
|
||||
for (int m_i = x_start; m_i < m_len; ++m_i) { \
|
||||
__typeof__((x_array)[0]) *const x_item_ptr = &x_array[m_i]; \
|
||||
__VA_ARGS__ \
|
||||
} \
|
||||
}
|
||||
|
||||
@ -54,8 +54,8 @@ void us_base64_encode(const u8 *data, uz size, char **encoded, uz *allocated) {
|
||||
}
|
||||
}
|
||||
|
||||
for (uint data_index = 0, encoded_index = 0; data_index < size;) {
|
||||
# define OCTET(_name) uint _name = (data_index < size ? (u8)data[data_index++] : 0)
|
||||
for (uint data_i = 0, encoded_i = 0; data_i < size;) {
|
||||
# define OCTET(_name) uint _name = (data_i < size ? (u8)data[data_i++] : 0)
|
||||
OCTET(octet_a);
|
||||
OCTET(octet_b);
|
||||
OCTET(octet_c);
|
||||
@ -63,7 +63,7 @@ void us_base64_encode(const u8 *data, uz size, char **encoded, uz *allocated) {
|
||||
|
||||
const uint triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
||||
|
||||
# define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
|
||||
# define ENCODE(_offset) (*encoded)[encoded_i++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
|
||||
ENCODE(3);
|
||||
ENCODE(2);
|
||||
ENCODE(1);
|
||||
|
||||
@ -30,7 +30,6 @@
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/select.h>
|
||||
#include <sys/mman.h>
|
||||
@ -103,12 +102,21 @@ static int _capture_open_export_to_dma(us_capture_s *cap);
|
||||
static int _capture_apply_resolution(us_capture_s *cap, uint width, uint height, float hz);
|
||||
|
||||
static void _capture_apply_controls(const us_capture_s *cap);
|
||||
|
||||
static int _capture_query_control(
|
||||
const us_capture_s *cap, struct v4l2_queryctrl *query,
|
||||
const char *name, uint cid, bool quiet);
|
||||
const us_capture_s *cap,
|
||||
struct v4l2_queryctrl *query,
|
||||
const char *name,
|
||||
uint cid,
|
||||
bool quiet);
|
||||
|
||||
static void _capture_set_control(
|
||||
const us_capture_s *cap, const struct v4l2_queryctrl *query,
|
||||
const char *name, uint cid, int value, bool quiet);
|
||||
const us_capture_s *cap,
|
||||
const struct v4l2_queryctrl *query,
|
||||
const char *name,
|
||||
uint cid,
|
||||
int value,
|
||||
bool quiet);
|
||||
|
||||
static const char *_format_to_string_nullable(uint format);
|
||||
static const char *_format_to_string_supported(uint format);
|
||||
@ -306,15 +314,15 @@ void us_capture_close(us_capture_s *cap) {
|
||||
if (run->bufs != NULL) {
|
||||
say = true;
|
||||
_LOG_DEBUG("Releasing HW buffers ...");
|
||||
for (uint index = 0; index < run->n_bufs; ++index) {
|
||||
us_capture_hwbuf_s *hw = &run->bufs[index];
|
||||
for (uint i = 0; i < run->n_bufs; ++i) {
|
||||
us_capture_hwbuf_s *hw = &run->bufs[i];
|
||||
|
||||
US_CLOSE_FD(hw->dma_fd);
|
||||
|
||||
if (cap->io_method == V4L2_MEMORY_MMAP) {
|
||||
if (hw->raw.allocated > 0 && hw->raw.data != NULL) {
|
||||
if (munmap(hw->raw.data, hw->raw.allocated) < 0) {
|
||||
_LOG_PERROR("Can't unmap HW buffer=%u", index);
|
||||
_LOG_PERROR("Can't unmap HW buffer=%u", i);
|
||||
}
|
||||
}
|
||||
} else { // V4L2_MEMORY_USERPTR
|
||||
@ -451,7 +459,8 @@ int us_capture_hwbuf_grab(us_capture_s *cap, us_capture_hwbuf_s **hw) {
|
||||
(*hw)->raw.grab_end_ts = us_get_now_monotonic();
|
||||
|
||||
_LOG_DEBUG("Grabbed HW buffer=%u: bytesused=%u, grab_begin_ts=%.3Lf, grab_end_ts=%.3Lf, latency=%.3Lf, skipped=%u",
|
||||
buf.index, buf.bytesused,
|
||||
buf.index,
|
||||
buf.bytesused,
|
||||
(*hw)->raw.grab_begin_ts,
|
||||
(*hw)->raw.grab_end_ts,
|
||||
(*hw)->raw.grab_end_ts - (*hw)->raw.grab_begin_ts,
|
||||
@ -461,15 +470,15 @@ int us_capture_hwbuf_grab(us_capture_s *cap, us_capture_hwbuf_s **hw) {
|
||||
}
|
||||
|
||||
int us_capture_hwbuf_release(const us_capture_s *cap, us_capture_hwbuf_s *hw) {
|
||||
assert(atomic_load(&hw->refs) == 0);
|
||||
const uint index = hw->buf.index;
|
||||
_LOG_DEBUG("Releasing HW buffer=%u ...", index);
|
||||
US_A(atomic_load(&hw->refs) == 0);
|
||||
const uint i = hw->buf.index;
|
||||
_LOG_DEBUG("Releasing HW buffer=%u ...", i);
|
||||
if (us_xioctl(cap->run->fd, VIDIOC_QBUF, &hw->buf) < 0) {
|
||||
_LOG_PERROR("Can't release HW buffer=%u", index);
|
||||
_LOG_PERROR("Can't release HW buffer=%u", i);
|
||||
return -1;
|
||||
}
|
||||
hw->grabbed = false;
|
||||
_LOG_DEBUG("HW buffer=%u released", index);
|
||||
_LOG_DEBUG("HW buffer=%u released", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -546,7 +555,7 @@ static void _v4l2_buffer_copy(const struct v4l2_buffer *src, struct v4l2_buffer
|
||||
struct v4l2_plane *dest_planes = dest->m.planes;
|
||||
memcpy(dest, src, sizeof(struct v4l2_buffer));
|
||||
if (src->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
|
||||
assert(dest_planes);
|
||||
US_A(dest_planes);
|
||||
dest->m.planes = dest_planes;
|
||||
memcpy(dest->m.planes, src->m.planes, sizeof(struct v4l2_plane) * VIDEO_MAX_PLANES);
|
||||
}
|
||||
@ -850,31 +859,31 @@ static void _capture_open_hw_fps(us_capture_s *cap) { // cppcheck-suppress const
|
||||
return;
|
||||
}
|
||||
|
||||
# define SETFPS_TPF(x_next) setfps.parm.capture.timeperframe.x_next
|
||||
# define TPF(x_next) setfps.parm.capture.timeperframe.x_next
|
||||
|
||||
US_MEMSET_ZERO(setfps);
|
||||
setfps.type = run->capture_type;
|
||||
SETFPS_TPF(numerator) = 1;
|
||||
SETFPS_TPF(denominator) = -1; // Request maximum possible FPS
|
||||
TPF(numerator) = 1;
|
||||
TPF(denominator) = -1; // Request maximum possible FPS
|
||||
|
||||
if (us_xioctl(run->fd, VIDIOC_S_PARM, &setfps) < 0) {
|
||||
_LOG_PERROR("Can't set HW FPS");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SETFPS_TPF(numerator) != 1) {
|
||||
_LOG_ERROR("Invalid HW FPS numerator: %u != 1", SETFPS_TPF(numerator));
|
||||
if (TPF(numerator) != 1) {
|
||||
_LOG_ERROR("Invalid HW FPS numerator: %u != 1", TPF(numerator));
|
||||
return;
|
||||
}
|
||||
|
||||
if (SETFPS_TPF(denominator) == 0) { // Не знаю, бывает ли так, но пускай на всякий случай
|
||||
if (TPF(denominator) == 0) { // Не знаю, бывает ли так, но пускай на всякий случай
|
||||
_LOG_ERROR("Invalid HW FPS denominator: 0");
|
||||
return;
|
||||
}
|
||||
|
||||
_LOG_INFO("Using HW FPS: %u/%u", SETFPS_TPF(numerator), SETFPS_TPF(denominator));
|
||||
_LOG_INFO("Using HW FPS: %u/%u", TPF(numerator), TPF(denominator));
|
||||
|
||||
# undef SETFPS_TPF
|
||||
# undef TPF
|
||||
}
|
||||
|
||||
static void _capture_open_jpeg_quality(us_capture_s *cap) {
|
||||
@ -901,7 +910,7 @@ static int _capture_open_io_method(us_capture_s *cap) {
|
||||
switch (cap->io_method) {
|
||||
case V4L2_MEMORY_MMAP: return _capture_open_io_method_mmap(cap);
|
||||
case V4L2_MEMORY_USERPTR: return _capture_open_io_method_userptr(cap);
|
||||
default: assert(0 && "Unsupported IO method");
|
||||
default: US_RAISE("Unsupported IO method");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@ -962,7 +971,7 @@ static int _capture_open_io_method_mmap(us_capture_s *cap) {
|
||||
_LOG_PERROR("Can't map device buffer=%u", run->n_bufs);
|
||||
return -1;
|
||||
}
|
||||
assert(hw->raw.data != NULL);
|
||||
US_A(hw->raw.data != NULL);
|
||||
hw->raw.allocated = buf_size;
|
||||
|
||||
if (run->capture_mplane) {
|
||||
@ -1004,7 +1013,7 @@ static int _capture_open_io_method_userptr(us_capture_s *cap) {
|
||||
|
||||
for (run->n_bufs = 0; run->n_bufs < req.count; ++run->n_bufs) {
|
||||
us_capture_hwbuf_s *hw = &run->bufs[run->n_bufs];
|
||||
assert((hw->raw.data = aligned_alloc(page_size, buf_size)) != NULL);
|
||||
US_A((hw->raw.data = aligned_alloc(page_size, buf_size)) != NULL);
|
||||
memset(hw->raw.data, 0, buf_size);
|
||||
hw->raw.allocated = buf_size;
|
||||
if (run->capture_mplane) {
|
||||
@ -1017,12 +1026,12 @@ static int _capture_open_io_method_userptr(us_capture_s *cap) {
|
||||
static int _capture_open_queue_buffers(us_capture_s *cap) {
|
||||
us_capture_runtime_s *const run = cap->run;
|
||||
|
||||
for (uint index = 0; index < run->n_bufs; ++index) {
|
||||
for (uint i = 0; i < run->n_bufs; ++i) {
|
||||
struct v4l2_buffer buf = {0};
|
||||
struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0};
|
||||
buf.type = run->capture_type;
|
||||
buf.memory = cap->io_method;
|
||||
buf.index = index;
|
||||
buf.index = i;
|
||||
if (run->capture_mplane) {
|
||||
buf.m.planes = planes;
|
||||
buf.length = 1;
|
||||
@ -1031,11 +1040,11 @@ static int _capture_open_queue_buffers(us_capture_s *cap) {
|
||||
if (cap->io_method == V4L2_MEMORY_USERPTR) {
|
||||
// I am not sure, may be this is incorrect for mplane device,
|
||||
// but i don't have one which supports V4L2_MEMORY_USERPTR
|
||||
buf.m.userptr = (unsigned long)run->bufs[index].raw.data;
|
||||
buf.length = run->bufs[index].raw.allocated;
|
||||
buf.m.userptr = (unsigned long)run->bufs[i].raw.data;
|
||||
buf.length = run->bufs[i].raw.allocated;
|
||||
}
|
||||
|
||||
_LOG_DEBUG("Calling us_xioctl(VIDIOC_QBUF) for buffer=%u ...", index);
|
||||
_LOG_DEBUG("Calling us_xioctl(VIDIOC_QBUF) for buffer=%u ...", i);
|
||||
if (us_xioctl(run->fd, VIDIOC_QBUF, &buf) < 0) {
|
||||
_LOG_PERROR("Can't VIDIOC_QBUF");
|
||||
return -1;
|
||||
@ -1047,23 +1056,23 @@ static int _capture_open_queue_buffers(us_capture_s *cap) {
|
||||
static int _capture_open_export_to_dma(us_capture_s *cap) {
|
||||
us_capture_runtime_s *const run = cap->run;
|
||||
|
||||
for (uint index = 0; index < run->n_bufs; ++index) {
|
||||
for (uint i = 0; i < run->n_bufs; ++i) {
|
||||
struct v4l2_exportbuffer exp = {
|
||||
.type = run->capture_type,
|
||||
.index = index,
|
||||
.index = i,
|
||||
};
|
||||
_LOG_DEBUG("Exporting device buffer=%u to DMA ...", index);
|
||||
_LOG_DEBUG("Exporting device buffer=%u to DMA ...", i);
|
||||
if (us_xioctl(run->fd, VIDIOC_EXPBUF, &exp) < 0) {
|
||||
_LOG_PERROR("Can't export device buffer=%u to DMA", index);
|
||||
_LOG_PERROR("Can't export device buffer=%u to DMA", i);
|
||||
goto error;
|
||||
}
|
||||
run->bufs[index].dma_fd = exp.fd;
|
||||
run->bufs[i].dma_fd = exp.fd;
|
||||
}
|
||||
return 0;
|
||||
|
||||
error:
|
||||
for (uint index = 0; index < run->n_bufs; ++index) {
|
||||
US_CLOSE_FD(run->bufs[index].dma_fd);
|
||||
for (uint i = 0; i < run->n_bufs; ++i) {
|
||||
US_CLOSE_FD(run->bufs[i].dma_fd);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@ -1142,9 +1151,12 @@ static void _capture_apply_controls(const us_capture_s *cap) {
|
||||
}
|
||||
|
||||
static int _capture_query_control(
|
||||
const us_capture_s *cap, struct v4l2_queryctrl *query,
|
||||
const char *name, uint cid, bool quiet) {
|
||||
|
||||
const us_capture_s *cap,
|
||||
struct v4l2_queryctrl *query,
|
||||
const char *name,
|
||||
uint cid,
|
||||
bool quiet
|
||||
) {
|
||||
// cppcheck-suppress redundantPointerOp
|
||||
US_MEMSET_ZERO(*query);
|
||||
query->id = cid;
|
||||
@ -1159,9 +1171,13 @@ static int _capture_query_control(
|
||||
}
|
||||
|
||||
static void _capture_set_control(
|
||||
const us_capture_s *cap, const struct v4l2_queryctrl *query,
|
||||
const char *name, uint cid, int value, bool quiet) {
|
||||
|
||||
const us_capture_s *cap,
|
||||
const struct v4l2_queryctrl *query,
|
||||
const char *name,
|
||||
uint cid,
|
||||
int value,
|
||||
bool quiet
|
||||
) {
|
||||
if (value < query->minimum || value > query->maximum || value % query->step != 0) {
|
||||
if (!quiet) {
|
||||
_LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u",
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
|
||||
|
||||
#define US_VERSION_MAJOR 6
|
||||
#define US_VERSION_MINOR 46
|
||||
#define US_VERSION_MINOR 55
|
||||
|
||||
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||
|
||||
@ -61,11 +61,11 @@ static const char *_connector_type_to_string(u32 type);
|
||||
static float _get_refresh_rate(const drmModeModeInfo *mode);
|
||||
|
||||
|
||||
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("DRM: " x_msg, ##__VA_ARGS__)
|
||||
|
||||
|
||||
us_drm_s *us_drm_init(void) {
|
||||
@ -99,7 +99,7 @@ void us_drm_destroy(us_drm_s *drm) {
|
||||
int us_drm_open(us_drm_s *drm, const us_capture_s *cap) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd < 0);
|
||||
US_A(run->fd < 0);
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
@ -164,7 +164,12 @@ int us_drm_open(us_drm_s *drm, const us_capture_s *cap) {
|
||||
|
||||
run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id);
|
||||
_LOG_DEBUG("Setting up CRTC ...");
|
||||
if (drmModeSetCrtc(run->fd, run->crtc_id, run->bufs[0].id, 0, 0, &run->conn_id, 1, &run->mode) < 0) {
|
||||
if (drmModeSetCrtc(
|
||||
run->fd,
|
||||
run->crtc_id, run->bufs[0].id,
|
||||
0, 0, // X, Y
|
||||
&run->conn_id, 1, &run->mode
|
||||
) < 0) {
|
||||
_LOG_PERROR("Can't set CRTC");
|
||||
goto error;
|
||||
}
|
||||
@ -195,14 +200,15 @@ void us_drm_close(us_drm_s *drm) {
|
||||
if (run->exposing_dma_fd >= 0) {
|
||||
// Нужно подождать, пока dma_fd не освободится, прежде чем прерывать процесс.
|
||||
// Просто на всякий случай.
|
||||
assert(run->fd >= 0);
|
||||
US_A(run->fd >= 0);
|
||||
us_drm_wait_for_vsync(drm);
|
||||
run->exposing_dma_fd = -1;
|
||||
}
|
||||
|
||||
if (run->saved_crtc != NULL) {
|
||||
_LOG_DEBUG("Restoring CRTC ...");
|
||||
if (drmModeSetCrtc(run->fd,
|
||||
if (drmModeSetCrtc(
|
||||
run->fd,
|
||||
run->saved_crtc->crtc_id, run->saved_crtc->buffer_id,
|
||||
run->saved_crtc->x, run->saved_crtc->y,
|
||||
&run->conn_id, 1, &run->saved_crtc->mode
|
||||
@ -252,8 +258,8 @@ void us_drm_close(us_drm_s *drm) {
|
||||
int us_drm_ensure_no_signal(us_drm_s *drm) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd >= 0);
|
||||
assert(run->opened > 0);
|
||||
US_A(run->fd >= 0);
|
||||
US_A(run->opened > 0);
|
||||
|
||||
const ldf now_ts = us_get_now_monotonic();
|
||||
if (run->blank_at_ts == 0) {
|
||||
@ -278,7 +284,7 @@ int us_drm_ensure_no_signal(us_drm_s *drm) {
|
||||
}
|
||||
|
||||
int us_drm_dpms_power_off(us_drm_s *drm) {
|
||||
assert(drm->run->fd >= 0);
|
||||
US_A(drm->run->fd >= 0);
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
case US_ERROR_NO_DEVICE: return 0; // Unplugged, nice
|
||||
@ -294,7 +300,7 @@ int us_drm_dpms_power_off(us_drm_s *drm) {
|
||||
int us_drm_wait_for_vsync(us_drm_s *drm) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd >= 0);
|
||||
US_A(run->fd >= 0);
|
||||
run->blank_at_ts = 0;
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
@ -349,8 +355,8 @@ static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void
|
||||
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *cap) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd >= 0);
|
||||
assert(run->opened > 0);
|
||||
US_A(run->fd >= 0);
|
||||
US_A(run->opened > 0);
|
||||
run->blank_at_ts = 0;
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
@ -363,7 +369,7 @@ int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *ca
|
||||
# define DRAW_MSG(x_msg) us_frametext_draw(run->ft, (x_msg), run->mode.hdisplay, run->mode.vdisplay)
|
||||
switch (stub) {
|
||||
case US_DRM_STUB_BAD_RESOLUTION: {
|
||||
assert(cap != NULL);
|
||||
US_A(cap != NULL);
|
||||
char msg[1024];
|
||||
US_SNPRINTF(msg, 1023,
|
||||
"=== PiKVM ==="
|
||||
@ -414,8 +420,8 @@ int us_drm_expose_dma(us_drm_s *drm, const us_capture_hwbuf_s *hw) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
us_drm_buffer_s *const buf = &run->bufs[hw->buf.index];
|
||||
|
||||
assert(run->fd >= 0);
|
||||
assert(run->opened == 0);
|
||||
US_A(run->fd >= 0);
|
||||
US_A(run->opened == 0);
|
||||
run->blank_at_ts = 0;
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
@ -700,8 +706,8 @@ static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint
|
||||
if (best == NULL) {
|
||||
best = (conn->count_modes > 0 ? &conn->modes[0] : NULL);
|
||||
}
|
||||
assert(best == NULL || best->hdisplay > 0);
|
||||
assert(best == NULL || best->vdisplay > 0);
|
||||
US_A(best == NULL || best->hdisplay > 0);
|
||||
US_A(best == NULL || best->vdisplay > 0);
|
||||
return best;
|
||||
}
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ us_fpsi_s *us_fpsi_init(const char *name, bool with_meta) {
|
||||
US_CALLOC(fpsi, 1);
|
||||
fpsi->name = us_strdup(name);
|
||||
fpsi->with_meta = with_meta;
|
||||
atomic_init(&fpsi->state_sec_ts, 0);
|
||||
atomic_init(&fpsi->state_ts, 0);
|
||||
atomic_init(&fpsi->state, 0);
|
||||
return fpsi;
|
||||
}
|
||||
@ -56,25 +56,25 @@ void us_fpsi_frame_to_meta(const us_frame_s *frame, us_fpsi_meta_s *meta) {
|
||||
|
||||
void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta) {
|
||||
if (meta != NULL) {
|
||||
assert(fpsi->with_meta);
|
||||
US_A(fpsi->with_meta);
|
||||
} else {
|
||||
assert(!fpsi->with_meta);
|
||||
US_A(!fpsi->with_meta);
|
||||
}
|
||||
|
||||
const sll now_sec_ts = us_floor_ms(us_get_now_monotonic());
|
||||
if (atomic_load(&fpsi->state_sec_ts) != now_sec_ts) {
|
||||
const sll now_ts = us_floor_ms(us_get_now_monotonic());
|
||||
if (atomic_load(&fpsi->state_ts) != now_ts) {
|
||||
US_LOG_PERF_FPS("FPS: %s: %u", fpsi->name, fpsi->accum);
|
||||
|
||||
// Fast mutex-less store method
|
||||
ull state = (ull)fpsi->accum & 0xFFFF;
|
||||
if (fpsi->with_meta) {
|
||||
assert(meta != NULL);
|
||||
US_A(meta != NULL);
|
||||
state |= (ull)(meta->width & 0xFFFF) << 16;
|
||||
state |= (ull)(meta->height & 0xFFFF) << 32;
|
||||
state |= (ull)(meta->online ? 1 : 0) << 48;
|
||||
}
|
||||
atomic_store(&fpsi->state, state); // Сначала инфа
|
||||
atomic_store(&fpsi->state_sec_ts, now_sec_ts); // Потом время, это важно
|
||||
atomic_store(&fpsi->state_ts, now_ts); // Потом время, это важно
|
||||
fpsi->accum = 0;
|
||||
}
|
||||
if (bump) {
|
||||
@ -84,14 +84,14 @@ void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta) {
|
||||
|
||||
uint us_fpsi_get(us_fpsi_s *fpsi, us_fpsi_meta_s *meta) {
|
||||
if (meta != NULL) {
|
||||
assert(fpsi->with_meta);
|
||||
US_A(fpsi->with_meta);
|
||||
}
|
||||
|
||||
// Между чтением инфы и времени может быть гонка,
|
||||
// но это неважно. Если время свежее, до данные тоже
|
||||
// будут свежмими, обратный случай не так важен.
|
||||
const sll now_sec_ts = us_floor_ms(us_get_now_monotonic());
|
||||
const sll state_sec_ts = atomic_load(&fpsi->state_sec_ts); // Сначала время
|
||||
const sll now_ts = us_floor_ms(us_get_now_monotonic());
|
||||
const sll state_ts = atomic_load(&fpsi->state_ts); // Сначала время
|
||||
const ull state = atomic_load(&fpsi->state); // Потом инфа
|
||||
|
||||
uint current = state & 0xFFFF;
|
||||
@ -101,7 +101,7 @@ uint us_fpsi_get(us_fpsi_s *fpsi, us_fpsi_meta_s *meta) {
|
||||
meta->online = (state >> 48) & 1;
|
||||
}
|
||||
|
||||
if (state_sec_ts != now_sec_ts && (state_sec_ts + 1) != now_sec_ts) {
|
||||
if (state_ts != now_ts && (state_ts + 1) != now_ts) {
|
||||
// Только текущая или прошлая секунда
|
||||
current = 0;
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ typedef struct {
|
||||
char *name;
|
||||
bool with_meta;
|
||||
uint accum;
|
||||
atomic_llong state_sec_ts;
|
||||
atomic_llong state_ts;
|
||||
atomic_ullong state;
|
||||
} us_fpsi_s;
|
||||
|
||||
|
||||
@ -25,7 +25,6 @@
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
@ -107,7 +106,7 @@ uint us_frame_get_padding(const us_frame_s *frame) {
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0 && "Unknown format");
|
||||
US_RAISE("Unknown format");
|
||||
}
|
||||
if (bytes_per_pixel > 0 && frame->stride > frame->width) {
|
||||
return (frame->stride - frame->width * bytes_per_pixel);
|
||||
@ -120,7 +119,7 @@ bool us_is_jpeg(uint format) {
|
||||
}
|
||||
|
||||
const char *us_fourcc_to_string(uint format, char *buf, uz size) {
|
||||
assert(size >= 8);
|
||||
US_A(size >= 8);
|
||||
buf[0] = format & 0x7F;
|
||||
buf[1] = (format >> 8) & 0x7F;
|
||||
buf[2] = (format >> 16) & 0x7F;
|
||||
|
||||
@ -84,7 +84,7 @@ typedef struct {
|
||||
|
||||
|
||||
static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, uint format) {
|
||||
assert(src->used > 0);
|
||||
US_A(src->used > 0);
|
||||
US_FRAME_COPY_META(src, dest);
|
||||
dest->encode_begin_ts = us_get_now_monotonic();
|
||||
dest->format = format;
|
||||
@ -93,7 +93,7 @@ static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *de
|
||||
}
|
||||
|
||||
static inline void us_frame_encoding_end(us_frame_s *dest) {
|
||||
assert(dest->used > 0);
|
||||
US_A(dest->used > 0);
|
||||
dest->encode_end_ts = us_get_now_monotonic();
|
||||
}
|
||||
|
||||
|
||||
@ -34,9 +34,12 @@
|
||||
|
||||
|
||||
static void _frametext_draw_line(
|
||||
us_frametext_s *ft, const char *line,
|
||||
uint scale_x, uint scale_y,
|
||||
uint start_x, uint start_y);
|
||||
us_frametext_s *ft,
|
||||
const char *line,
|
||||
uint scale_x,
|
||||
uint scale_y,
|
||||
uint start_x,
|
||||
uint start_y);
|
||||
|
||||
|
||||
us_frametext_s *us_frametext_init(void) {
|
||||
@ -81,8 +84,8 @@ To access the nth pixel in a row, right-shift by n.
|
||||
*/
|
||||
|
||||
void us_frametext_draw(us_frametext_s *ft, const char *text, uint width, uint height) {
|
||||
assert(width > 0);
|
||||
assert(height > 0);
|
||||
US_A(width > 0);
|
||||
US_A(height > 0);
|
||||
|
||||
us_frame_s *const frame = ft->frame;
|
||||
|
||||
@ -156,10 +159,13 @@ empty:
|
||||
}
|
||||
|
||||
void _frametext_draw_line(
|
||||
us_frametext_s *ft, const char *line,
|
||||
uint scale_x, uint scale_y,
|
||||
uint start_x, uint start_y) {
|
||||
|
||||
us_frametext_s *ft,
|
||||
const char *line,
|
||||
uint scale_x,
|
||||
uint scale_y,
|
||||
uint start_x,
|
||||
uint start_y
|
||||
) {
|
||||
us_frame_s *const frame = ft->frame;
|
||||
|
||||
const size_t len = strlen(line);
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
#define US_LIST_DECLARE \
|
||||
@ -71,6 +71,6 @@
|
||||
|
||||
#define US_LIST_REMOVE_C(x_first, x_item, x_count) { \
|
||||
US_LIST_REMOVE(x_first, x_item); \
|
||||
assert((x_count) >= 1); \
|
||||
US_A((x_count) >= 1); \
|
||||
--(x_count); \
|
||||
}
|
||||
|
||||
@ -28,7 +28,6 @@
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
@ -75,7 +74,7 @@ extern pthread_mutex_t us_g_log_mutex;
|
||||
|
||||
#define US_SEP_INFO(x_ch) { \
|
||||
US_LOGGING_LOCK; \
|
||||
for (int m_count = 0; m_count < 80; ++m_count) { \
|
||||
for (int m_i = 0; m_i < 80; ++m_i) { \
|
||||
fputc((x_ch), stderr); \
|
||||
} \
|
||||
fputc('\n', stderr); \
|
||||
|
||||
@ -26,7 +26,6 @@
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
@ -110,7 +109,7 @@ bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
|
||||
// Если frame == NULL, то только проверяем наличие клиентов
|
||||
// или необходимость инициализировать память.
|
||||
|
||||
assert(sink->server);
|
||||
US_A(sink->server);
|
||||
|
||||
if (sink->mem->magic != US_MEMSINK_MAGIC || sink->mem->version != US_MEMSINK_VERSION) {
|
||||
// Если регион памяти не был инициализирован, то нужно что-то туда положить.
|
||||
@ -162,7 +161,7 @@ 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, bool *key_requested) {
|
||||
assert(sink->server);
|
||||
US_A(sink->server);
|
||||
|
||||
const ldf now = us_get_now_monotonic();
|
||||
|
||||
@ -210,7 +209,7 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key
|
||||
}
|
||||
|
||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_requested, bool key_required) {
|
||||
assert(!sink->server); // Client only
|
||||
US_A(!sink->server); // Client only
|
||||
|
||||
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
|
||||
if (errno == EWOULDBLOCK) {
|
||||
|
||||
@ -24,11 +24,11 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size) {
|
||||
@ -40,12 +40,12 @@ us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size) {
|
||||
if (mem == MAP_FAILED) {
|
||||
return NULL;
|
||||
}
|
||||
assert(mem != NULL);
|
||||
US_A(mem != NULL);
|
||||
return mem;
|
||||
}
|
||||
|
||||
int us_memsink_shared_unmap(us_memsink_shared_s *mem, uz data_size) {
|
||||
assert(mem != NULL);
|
||||
US_A(mem != NULL);
|
||||
return munmap(mem, sizeof(us_memsink_shared_s) + data_size);
|
||||
}
|
||||
|
||||
|
||||
@ -25,21 +25,21 @@
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <getopt.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
void us_build_short_options(const struct option opts[], char *short_opts, uz size) {
|
||||
memset(short_opts, 0, size);
|
||||
for (uint short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) {
|
||||
assert(short_index < size - 3);
|
||||
if (isalpha(opts[opt_index].val)) {
|
||||
short_opts[short_index] = opts[opt_index].val;
|
||||
++short_index;
|
||||
if (opts[opt_index].has_arg == required_argument) {
|
||||
short_opts[short_index] = ':';
|
||||
++short_index;
|
||||
for (uint short_i = 0, opt_i = 0; opts[opt_i].name != NULL; ++opt_i) {
|
||||
US_A(short_i < size - 3);
|
||||
if (isalpha(opts[opt_i].val)) {
|
||||
short_opts[short_i] = opts[opt_i].val;
|
||||
++short_i;
|
||||
if (opts[opt_i].has_arg == required_argument) {
|
||||
short_opts[short_i] = ':';
|
||||
++short_i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,13 +71,13 @@ INLINE int us_process_track_parent_death(void) {
|
||||
const pid_t parent = getppid();
|
||||
int signum = SIGTERM;
|
||||
# if defined(__linux__)
|
||||
const int retval = prctl(PR_SET_PDEATHSIG, signum);
|
||||
const int result = prctl(PR_SET_PDEATHSIG, signum);
|
||||
# elif defined(__FreeBSD__)
|
||||
const int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
|
||||
const int result = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
|
||||
# else
|
||||
# error WTF?
|
||||
# endif
|
||||
if (retval < 0) {
|
||||
if (result < 0) {
|
||||
US_LOG_PERROR("Can't set to receive SIGTERM on parent process death");
|
||||
return -1;
|
||||
}
|
||||
@ -103,15 +103,15 @@ INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefi
|
||||
US_REALLOC(cmdline, allocated);
|
||||
cmdline[0] = '\0';
|
||||
|
||||
for (int index = 0; index < argc; ++index) {
|
||||
uz arg_len = strlen(argv[index]);
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
uz arg_len = strlen(argv[i]);
|
||||
if (used + arg_len + 16 >= allocated) {
|
||||
allocated += arg_len + 2048;
|
||||
US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
|
||||
}
|
||||
|
||||
strcat(cmdline, " ");
|
||||
strcat(cmdline, argv[index]);
|
||||
strcat(cmdline, argv[i]);
|
||||
used = strlen(cmdline); // Не считаем вручную, так надежнее
|
||||
}
|
||||
|
||||
|
||||
@ -24,7 +24,6 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
@ -34,79 +33,79 @@
|
||||
|
||||
|
||||
us_queue_s *us_queue_init(uint capacity) {
|
||||
us_queue_s *queue;
|
||||
US_CALLOC(queue, 1);
|
||||
US_CALLOC(queue->items, capacity);
|
||||
queue->capacity = capacity;
|
||||
US_MUTEX_INIT(queue->mutex);
|
||||
us_queue_s *q;
|
||||
US_CALLOC(q, 1);
|
||||
US_CALLOC(q->items, capacity);
|
||||
q->capacity = capacity;
|
||||
US_MUTEX_INIT(q->mutex);
|
||||
|
||||
pthread_condattr_t attrs;
|
||||
assert(!pthread_condattr_init(&attrs));
|
||||
assert(!pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC));
|
||||
assert(!pthread_cond_init(&queue->full_cond, &attrs));
|
||||
assert(!pthread_cond_init(&queue->empty_cond, &attrs));
|
||||
assert(!pthread_condattr_destroy(&attrs));
|
||||
return queue;
|
||||
US_A(!pthread_condattr_init(&attrs));
|
||||
US_A(!pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC));
|
||||
US_A(!pthread_cond_init(&q->full_cond, &attrs));
|
||||
US_A(!pthread_cond_init(&q->empty_cond, &attrs));
|
||||
US_A(!pthread_condattr_destroy(&attrs));
|
||||
return q;
|
||||
}
|
||||
|
||||
void us_queue_destroy(us_queue_s *queue) {
|
||||
US_COND_DESTROY(queue->empty_cond);
|
||||
US_COND_DESTROY(queue->full_cond);
|
||||
US_MUTEX_DESTROY(queue->mutex);
|
||||
free(queue->items);
|
||||
free(queue);
|
||||
void us_queue_destroy(us_queue_s *q) {
|
||||
US_COND_DESTROY(q->empty_cond);
|
||||
US_COND_DESTROY(q->full_cond);
|
||||
US_MUTEX_DESTROY(q->mutex);
|
||||
free(q->items);
|
||||
free(q);
|
||||
}
|
||||
|
||||
#define _WAIT_OR_UNLOCK(x_var, x_cond) { \
|
||||
struct timespec m_ts; \
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \
|
||||
US_A(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \
|
||||
us_ld_to_timespec(us_timespec_to_ld(&m_ts) + timeout, &m_ts); \
|
||||
while (x_var) { \
|
||||
const int err = pthread_cond_timedwait(&(x_cond), &queue->mutex, &m_ts); \
|
||||
const int err = pthread_cond_timedwait(&(x_cond), &q->mutex, &m_ts); \
|
||||
if (err == ETIMEDOUT) { \
|
||||
US_MUTEX_UNLOCK(queue->mutex); \
|
||||
US_MUTEX_UNLOCK(q->mutex); \
|
||||
return -1; \
|
||||
} \
|
||||
assert(!err); \
|
||||
US_A(!err); \
|
||||
} \
|
||||
}
|
||||
|
||||
int us_queue_put(us_queue_s *queue, void *item, ldf timeout) {
|
||||
US_MUTEX_LOCK(queue->mutex);
|
||||
int us_queue_put(us_queue_s *q, void *item, ldf timeout) {
|
||||
US_MUTEX_LOCK(q->mutex);
|
||||
if (timeout == 0) {
|
||||
if (queue->size == queue->capacity) {
|
||||
US_MUTEX_UNLOCK(queue->mutex);
|
||||
if (q->size == q->capacity) {
|
||||
US_MUTEX_UNLOCK(q->mutex);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
_WAIT_OR_UNLOCK(queue->size == queue->capacity, queue->full_cond);
|
||||
_WAIT_OR_UNLOCK(q->size == q->capacity, q->full_cond);
|
||||
}
|
||||
queue->items[queue->in] = item;
|
||||
++queue->size;
|
||||
++queue->in;
|
||||
queue->in %= queue->capacity;
|
||||
US_MUTEX_UNLOCK(queue->mutex);
|
||||
US_COND_BROADCAST(queue->empty_cond);
|
||||
q->items[q->in] = item;
|
||||
++q->size;
|
||||
++q->in;
|
||||
q->in %= q->capacity;
|
||||
US_MUTEX_UNLOCK(q->mutex);
|
||||
US_COND_BROADCAST(q->empty_cond);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int us_queue_get(us_queue_s *queue, void **item, ldf 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;
|
||||
US_MUTEX_UNLOCK(queue->mutex);
|
||||
US_COND_BROADCAST(queue->full_cond);
|
||||
int us_queue_get(us_queue_s *q, void **item, ldf timeout) {
|
||||
US_MUTEX_LOCK(q->mutex);
|
||||
_WAIT_OR_UNLOCK(q->size == 0, q->empty_cond);
|
||||
*item = q->items[q->out];
|
||||
--q->size;
|
||||
++q->out;
|
||||
q->out %= q->capacity;
|
||||
US_MUTEX_UNLOCK(q->mutex);
|
||||
US_COND_BROADCAST(q->full_cond);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef _WAIT_OR_UNLOCK
|
||||
|
||||
bool us_queue_is_empty(us_queue_s *queue) {
|
||||
US_MUTEX_LOCK(queue->mutex);
|
||||
const uint size = queue->size;
|
||||
US_MUTEX_UNLOCK(queue->mutex);
|
||||
return (bool)(queue->capacity - size);
|
||||
bool us_queue_is_empty(us_queue_s *q) {
|
||||
US_MUTEX_LOCK(q->mutex);
|
||||
const uint size = q->size;
|
||||
US_MUTEX_UNLOCK(q->mutex);
|
||||
return (bool)(q->capacity - size);
|
||||
}
|
||||
|
||||
@ -43,22 +43,22 @@ typedef struct {
|
||||
} us_queue_s;
|
||||
|
||||
|
||||
#define US_QUEUE_DELETE_WITH_ITEMS(x_queue, x_free_item) { \
|
||||
if (x_queue) { \
|
||||
while (!us_queue_is_empty(x_queue)) { \
|
||||
#define US_QUEUE_DELETE_WITH_ITEMS(x_q, x_free_item) { \
|
||||
if (x_q) { \
|
||||
while (!us_queue_is_empty(x_q)) { \
|
||||
void *m_ptr; \
|
||||
if (!us_queue_get(x_queue, &m_ptr, 0)) { \
|
||||
if (!us_queue_get(x_q, &m_ptr, 0)) { \
|
||||
US_DELETE(m_ptr, x_free_item); \
|
||||
} \
|
||||
} \
|
||||
us_queue_destroy(x_queue); \
|
||||
us_queue_destroy(x_q); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
us_queue_s *us_queue_init(uint capacity);
|
||||
void us_queue_destroy(us_queue_s *queue);
|
||||
void us_queue_destroy(us_queue_s *q);
|
||||
|
||||
int us_queue_put(us_queue_s *queue, void *item, ldf timeout);
|
||||
int us_queue_get(us_queue_s *queue, void **item, ldf timeout);
|
||||
bool us_queue_is_empty(us_queue_s *queue);
|
||||
int us_queue_put(us_queue_s *q, void *item, ldf timeout);
|
||||
int us_queue_get(us_queue_s *q, void **item, ldf timeout);
|
||||
bool us_queue_is_empty(us_queue_s *q);
|
||||
|
||||
@ -20,8 +20,6 @@
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "ring.h"
|
||||
|
||||
#include "types.h"
|
||||
@ -29,8 +27,8 @@
|
||||
#include "queue.h"
|
||||
|
||||
|
||||
int _acquire(us_ring_s *ring, us_queue_s *queue, ldf timeout);
|
||||
void _release(us_ring_s *ring, us_queue_s *queue, uint index);
|
||||
int _acquire(us_ring_s *ring, us_queue_s *q, ldf timeout);
|
||||
void _release(us_ring_s *ring, us_queue_s *q, uint ri);
|
||||
|
||||
|
||||
us_ring_s *us_ring_init(uint capacity) {
|
||||
@ -41,9 +39,9 @@ us_ring_s *us_ring_init(uint capacity) {
|
||||
ring->capacity = capacity;
|
||||
ring->producer = us_queue_init(capacity);
|
||||
ring->consumer = us_queue_init(capacity);
|
||||
for (uint index = 0; index < capacity; ++index) {
|
||||
ring->places[index] = index; // XXX: Just to avoid casting between pointer and uint
|
||||
assert(!us_queue_put(ring->producer, (void*)(ring->places + index), 0));
|
||||
for (uint ri = 0; ri < capacity; ++ri) {
|
||||
ring->places[ri] = ri; // XXX: Just to avoid casting between pointer and uint
|
||||
US_A(!us_queue_put(ring->producer, (void*)(ring->places + ri), 0));
|
||||
}
|
||||
return ring;
|
||||
}
|
||||
@ -60,27 +58,27 @@ int us_ring_producer_acquire(us_ring_s *ring, ldf timeout) {
|
||||
return _acquire(ring, ring->producer, timeout);
|
||||
}
|
||||
|
||||
void us_ring_producer_release(us_ring_s *ring, uint index) {
|
||||
_release(ring, ring->consumer, index);
|
||||
void us_ring_producer_release(us_ring_s *ring, uint ri) {
|
||||
_release(ring, ring->consumer, ri);
|
||||
}
|
||||
|
||||
int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout) {
|
||||
return _acquire(ring, ring->consumer, timeout);
|
||||
}
|
||||
|
||||
void us_ring_consumer_release(us_ring_s *ring, uint index) {
|
||||
_release(ring, ring->producer, index);
|
||||
void us_ring_consumer_release(us_ring_s *ring, uint ri) {
|
||||
_release(ring, ring->producer, ri);
|
||||
}
|
||||
|
||||
int _acquire(us_ring_s *ring, us_queue_s *queue, ldf timeout) {
|
||||
int _acquire(us_ring_s *ring, us_queue_s *q, ldf timeout) {
|
||||
(void)ring;
|
||||
uint *place;
|
||||
if (us_queue_get(queue, (void**)&place, timeout) < 0) {
|
||||
if (us_queue_get(q, (void**)&place, timeout) < 0) {
|
||||
return -1;
|
||||
}
|
||||
return *place;
|
||||
}
|
||||
|
||||
void _release(us_ring_s *ring, us_queue_s *queue, uint index) {
|
||||
assert(!us_queue_put(queue, (void*)(ring->places + index), 0));
|
||||
void _release(us_ring_s *ring, us_queue_s *q, uint ri) {
|
||||
US_A(!us_queue_put(q, (void*)(ring->places + ri), 0));
|
||||
}
|
||||
|
||||
@ -38,15 +38,15 @@ typedef struct {
|
||||
|
||||
#define US_RING_INIT_WITH_ITEMS(x_ring, x_capacity, x_init_item) { \
|
||||
(x_ring) = us_ring_init(x_capacity); \
|
||||
for (uz m_index = 0; m_index < (x_ring)->capacity; ++m_index) { \
|
||||
(x_ring)->items[m_index] = x_init_item(); \
|
||||
for (uz m_ri = 0; m_ri < (x_ring)->capacity; ++m_ri) { \
|
||||
(x_ring)->items[m_ri] = x_init_item(); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define US_RING_DELETE_WITH_ITEMS(x_ring, x_destroy_item) { \
|
||||
if (x_ring) { \
|
||||
for (uz m_index = 0; m_index < (x_ring)->capacity; ++m_index) { \
|
||||
x_destroy_item((x_ring)->items[m_index]); \
|
||||
for (uz m_ri = 0; m_ri < (x_ring)->capacity; ++m_ri) { \
|
||||
x_destroy_item((x_ring)->items[m_ri]); \
|
||||
} \
|
||||
us_ring_destroy(x_ring); \
|
||||
} \
|
||||
@ -57,7 +57,7 @@ us_ring_s *us_ring_init(uint capacity);
|
||||
void us_ring_destroy(us_ring_s *ring);
|
||||
|
||||
int us_ring_producer_acquire(us_ring_s *ring, ldf timeout);
|
||||
void us_ring_producer_release(us_ring_s *ring, uint index);
|
||||
void us_ring_producer_release(us_ring_s *ring, uint ri);
|
||||
|
||||
int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout);
|
||||
void us_ring_consumer_release(us_ring_s *ring, uint index);
|
||||
void us_ring_consumer_release(us_ring_s *ring, uint ri);
|
||||
|
||||
@ -24,7 +24,6 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <assert.h>
|
||||
|
||||
#if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
|
||||
# define HAS_SIGABBREV_NP
|
||||
@ -58,25 +57,25 @@ char *us_signum_to_string(int signum) {
|
||||
void us_install_signals_handler(us_signal_handler_f handler, bool ignore_sigpipe) {
|
||||
struct sigaction sig_act = {0};
|
||||
|
||||
assert(!sigemptyset(&sig_act.sa_mask));
|
||||
US_A(!sigemptyset(&sig_act.sa_mask));
|
||||
sig_act.sa_handler = handler;
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGINT));
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
|
||||
US_A(!sigaddset(&sig_act.sa_mask, SIGINT));
|
||||
US_A(!sigaddset(&sig_act.sa_mask, SIGTERM));
|
||||
if (!ignore_sigpipe) {
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGPIPE));
|
||||
US_A(!sigaddset(&sig_act.sa_mask, SIGPIPE));
|
||||
}
|
||||
|
||||
US_LOG_DEBUG("Installing SIGINT handler ...");
|
||||
assert(!sigaction(SIGINT, &sig_act, NULL));
|
||||
US_A(!sigaction(SIGINT, &sig_act, NULL));
|
||||
|
||||
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
assert(!sigaction(SIGTERM, &sig_act, NULL));
|
||||
US_A(!sigaction(SIGTERM, &sig_act, NULL));
|
||||
|
||||
if (!ignore_sigpipe) {
|
||||
US_LOG_DEBUG("Installing SIGPIPE handler ...");
|
||||
assert(!sigaction(SIGPIPE, &sig_act, NULL));
|
||||
US_A(!sigaction(SIGPIPE, &sig_act, NULL));
|
||||
} else {
|
||||
US_LOG_DEBUG("Ignoring SIGPIPE ...");
|
||||
assert(signal(SIGPIPE, SIG_IGN) != SIG_ERR);
|
||||
US_A(signal(SIGPIPE, SIG_IGN) != SIG_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,7 +25,6 @@
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/syscall.h>
|
||||
|
||||
@ -47,8 +46,8 @@
|
||||
# define US_THREAD_NAME_SIZE ((uz)16)
|
||||
#endif
|
||||
|
||||
#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))
|
||||
#define US_THREAD_CREATE(x_tid, x_func, x_arg) US_A(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
|
||||
#define US_THREAD_JOIN(x_tid) US_A(!pthread_join((x_tid), NULL))
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
# define US_THREAD_RENAME(x_fmt, ...) { \
|
||||
@ -65,16 +64,16 @@
|
||||
us_thread_block_signals(); \
|
||||
}
|
||||
|
||||
#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 US_MUTEX_INIT(x_mutex) US_A(!pthread_mutex_init(&(x_mutex), NULL))
|
||||
#define US_MUTEX_DESTROY(x_mutex) US_A(!pthread_mutex_destroy(&(x_mutex)))
|
||||
#define US_MUTEX_LOCK(x_mutex) US_A(!pthread_mutex_lock(&(x_mutex)))
|
||||
#define US_MUTEX_UNLOCK(x_mutex) US_A(!pthread_mutex_unlock(&(x_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(x_cond) assert(!pthread_cond_signal(&(x_cond)))
|
||||
#define US_COND_BROADCAST(x_cond) assert(!pthread_cond_broadcast(&(x_cond)))
|
||||
#define US_COND_WAIT_FOR(x_var, x_cond, x_mutex) { while(!(x_var)) assert(!pthread_cond_wait(&(x_cond), &(x_mutex))); }
|
||||
#define US_COND_INIT(x_cond) US_A(!pthread_cond_init(&(x_cond), NULL))
|
||||
#define US_COND_DESTROY(x_cond) US_A(!pthread_cond_destroy(&(x_cond)))
|
||||
#define US_COND_SIGNAL(x_cond) US_A(!pthread_cond_signal(&(x_cond)))
|
||||
#define US_COND_BROADCAST(x_cond) US_A(!pthread_cond_broadcast(&(x_cond)))
|
||||
#define US_COND_WAIT_FOR(x_var, x_cond, x_mutex) { while(!(x_var)) US_A(!pthread_cond_wait(&(x_cond), &(x_mutex))); }
|
||||
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
@ -114,7 +113,7 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
|
||||
const pid_t tid = syscall(SYS_gettid);
|
||||
#elif defined(__FreeBSD__)
|
||||
long id;
|
||||
assert(!syscall(SYS_thr_self, &id));
|
||||
US_A(!syscall(SYS_thr_self, &id));
|
||||
const pid_t tid = id;
|
||||
#elif defined(__OpenBSD__)
|
||||
const pid_t tid = syscall(SYS_getthrid);
|
||||
@ -135,8 +134,8 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
|
||||
|
||||
INLINE void us_thread_block_signals(void) {
|
||||
sigset_t mask;
|
||||
assert(!sigemptyset(&mask));
|
||||
assert(!sigaddset(&mask, SIGINT));
|
||||
assert(!sigaddset(&mask, SIGTERM));
|
||||
assert(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
|
||||
US_A(!sigemptyset(&mask));
|
||||
US_A(!sigaddset(&mask, SIGINT));
|
||||
US_A(!sigaddset(&mask, SIGTERM));
|
||||
US_A(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
|
||||
}
|
||||
|
||||
@ -51,14 +51,17 @@
|
||||
|
||||
#define INLINE inline __attribute__((always_inline))
|
||||
|
||||
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
|
||||
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
|
||||
#define US_A(x_arg) { const bool m_ar = (x_arg); assert(m_ar && #x_arg); }
|
||||
#define US_RAISE(x_msg) assert(0 && x_msg)
|
||||
|
||||
#define US_CALLOC(x_dest, x_nmemb) US_A(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
|
||||
#define US_REALLOC(x_dest, x_nmemb) US_A(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
|
||||
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); x_dest = NULL; } }
|
||||
#define US_CLOSE_FD(x_dest) { if (x_dest >= 0) { close(x_dest); x_dest = -1; } }
|
||||
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
|
||||
|
||||
#define US_SNPRINTF(x_dest, x_size, x_fmt, ...) assert(snprintf((x_dest), (x_size), (x_fmt), ##__VA_ARGS__) > 0)
|
||||
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) > 0)
|
||||
#define US_SNPRINTF(x_dest, x_size, x_fmt, ...) US_A(snprintf((x_dest), (x_size), (x_fmt), ##__VA_ARGS__) > 0)
|
||||
#define US_ASPRINTF(x_dest, x_fmt, ...) US_A(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) > 0)
|
||||
|
||||
#define US_MIN(x_a, x_b) ({ \
|
||||
__typeof__(x_a) m_a = (x_a); \
|
||||
@ -85,7 +88,7 @@
|
||||
|
||||
INLINE char *us_strdup(const char *str) {
|
||||
char *const new = strdup(str);
|
||||
assert(new != NULL);
|
||||
US_A(new != NULL);
|
||||
return new;
|
||||
}
|
||||
|
||||
@ -115,7 +118,7 @@ INLINE u32 us_triple_u32(u32 x) {
|
||||
|
||||
INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
struct timespec ts;
|
||||
assert(!clock_gettime(clk_id, &ts));
|
||||
US_A(!clock_gettime(clk_id, &ts));
|
||||
*sec = ts.tv_sec;
|
||||
*msec = round(ts.tv_nsec / 1.0e6);
|
||||
|
||||
@ -134,7 +137,7 @@ INLINE ldf us_get_now_monotonic(void) {
|
||||
|
||||
INLINE u64 us_get_now_monotonic_u64(void) {
|
||||
struct timespec ts;
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &ts));
|
||||
US_A(!clock_gettime(CLOCK_MONOTONIC, &ts));
|
||||
return (u64)(ts.tv_nsec / 1000) + (u64)ts.tv_sec * 1000000;
|
||||
}
|
||||
|
||||
@ -150,12 +153,6 @@ INLINE ldf us_get_now_real(void) {
|
||||
return (ldf)sec + ((ldf)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE uint us_get_cores_available(void) {
|
||||
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
|
||||
return US_MAX(US_MIN(cores_sysconf, 4), 1);
|
||||
}
|
||||
|
||||
INLINE void us_ld_to_timespec(ldf ld, struct timespec *ts) {
|
||||
ts->tv_sec = (long)ld;
|
||||
ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L;
|
||||
@ -169,6 +166,28 @@ INLINE ldf us_timespec_to_ld(const struct timespec *ts) {
|
||||
return ts->tv_sec + ((ldf)ts->tv_nsec) / 1000000000;
|
||||
}
|
||||
|
||||
#define NTP_UNIX_TIME_DIFF 2208988800u // Difference between Unix time and NTP time in seconds (1970 - 1900)
|
||||
#define NTP_TICKS_IN_SECOND 4294967296u // Ticks per second in NTP time
|
||||
|
||||
INLINE u64 us_get_now_ntp(void) {
|
||||
struct timespec ts;
|
||||
US_A(!clock_gettime(CLOCK_REALTIME, &ts));
|
||||
return (((u64)ts.tv_sec + NTP_UNIX_TIME_DIFF) << 32) + ((u64)ts.tv_nsec / 1000 * NTP_TICKS_IN_SECOND) / 1000000;
|
||||
}
|
||||
|
||||
INLINE u64 us_ld_to_ntp(ldf ld) {
|
||||
return (ld > 0 ? ld * NTP_TICKS_IN_SECOND : 0);
|
||||
}
|
||||
|
||||
#undef NTP_TICKS_IN_SECOND
|
||||
#undef NTP_UNIX_TIME_DIFF
|
||||
|
||||
INLINE uint us_get_cores_available(void) {
|
||||
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
|
||||
return US_MAX(US_MIN(cores_sysconf, 4), 1);
|
||||
}
|
||||
|
||||
INLINE int us_flock_timedwait_monotonic(int fd, ldf timeout) {
|
||||
const ldf deadline_ts = us_get_now_monotonic() + timeout;
|
||||
int retval = -1;
|
||||
|
||||
@ -24,12 +24,12 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <setjmp.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "frame.h"
|
||||
|
||||
@ -45,7 +45,7 @@ static void _jpeg_error_handler(j_common_ptr jpeg);
|
||||
|
||||
|
||||
int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
|
||||
assert(us_is_jpeg(src->format));
|
||||
US_A(us_is_jpeg(src->format));
|
||||
|
||||
volatile int retval = 0;
|
||||
|
||||
@ -77,7 +77,7 @@ int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
|
||||
|
||||
if (decode) {
|
||||
JSAMPARRAY scanlines;
|
||||
scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr) &jpeg, JPOOL_IMAGE, dest->stride, 1);
|
||||
scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr)&jpeg, JPOOL_IMAGE, dest->stride, 1);
|
||||
|
||||
us_frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2);
|
||||
while (jpeg.output_scanline < jpeg.output_height) {
|
||||
|
||||
@ -24,7 +24,6 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
@ -81,8 +80,8 @@ us_encoder_s *us_encoder_init(void) {
|
||||
void us_encoder_destroy(us_encoder_s *enc) {
|
||||
us_encoder_runtime_s *const run = enc->run;
|
||||
if (run->m2ms != NULL) {
|
||||
for (uint index = 0; index < run->n_m2ms; ++index) {
|
||||
US_DELETE(run->m2ms[index], us_m2m_encoder_destroy);
|
||||
for (uint i = 0; i < run->n_m2ms; ++i) {
|
||||
US_DELETE(run->m2ms[i], us_m2m_encoder_destroy);
|
||||
}
|
||||
free(run->m2ms);
|
||||
}
|
||||
@ -113,7 +112,7 @@ void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
|
||||
us_encoder_runtime_s *const run = enc->run;
|
||||
us_capture_runtime_s *const cr = cap->run;
|
||||
|
||||
assert(run->pool == NULL);
|
||||
US_A(run->pool == NULL);
|
||||
|
||||
us_encoder_type_e type = enc->type;
|
||||
uint quality = cap->jpeg_quality;
|
||||
@ -135,10 +134,13 @@ void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
|
||||
}
|
||||
|
||||
} 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"));
|
||||
US_LOG_DEBUG("Preparing M2M-%s encoder ...",
|
||||
(type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"));
|
||||
|
||||
if (run->m2ms == NULL) {
|
||||
US_CALLOC(run->m2ms, n_workers);
|
||||
}
|
||||
|
||||
for (; run->n_m2ms < n_workers; ++run->n_m2ms) {
|
||||
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||
char name[32];
|
||||
@ -163,14 +165,17 @@ void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
|
||||
US_MUTEX_UNLOCK(run->mutex);
|
||||
|
||||
enc->run->pool = us_workers_pool_init(
|
||||
"JPEG", "jw", n_workers,
|
||||
_worker_job_init, (void*)enc,
|
||||
"JPEG",
|
||||
"jw",
|
||||
n_workers,
|
||||
_worker_job_init,
|
||||
(void*)enc,
|
||||
_worker_job_destroy,
|
||||
_worker_run_job);
|
||||
}
|
||||
|
||||
void us_encoder_close(us_encoder_s *enc) {
|
||||
assert(enc->run->pool != NULL);
|
||||
US_A(enc->run->pool != NULL);
|
||||
US_DELETE(enc->run->pool, us_workers_pool_destroy);
|
||||
}
|
||||
|
||||
@ -201,36 +206,35 @@ static bool _worker_run_job(us_worker_s *wr) {
|
||||
us_encoder_runtime_s *const run = job->enc->run;
|
||||
const us_frame_s *const src = &job->hw->raw;
|
||||
us_frame_s *const dest = job->dest;
|
||||
const uint i = job->hw->buf.index;
|
||||
|
||||
if (run->type == US_ENCODER_TYPE_CPU) {
|
||||
US_LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
US_LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u", wr->name, i);
|
||||
us_cpu_encoder_compress(src, dest, run->quality);
|
||||
|
||||
} else if (run->type == US_ENCODER_TYPE_HW) {
|
||||
US_LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
US_LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u", wr->name, i);
|
||||
us_hw_encoder_compress(src, dest);
|
||||
|
||||
} else if (run->type == US_ENCODER_TYPE_M2M_VIDEO || run->type == US_ENCODER_TYPE_M2M_IMAGE) {
|
||||
US_LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u",
|
||||
(run->type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"), wr->name, job->hw->buf.index);
|
||||
(run->type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"),
|
||||
wr->name, i);
|
||||
if (us_m2m_encoder_compress(run->m2ms[wr->number], src, dest, false) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
} else {
|
||||
assert(0 && "Unknown encoder type");
|
||||
US_RAISE("Unknown encoder type");
|
||||
}
|
||||
|
||||
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,
|
||||
job->hw->buf.index);
|
||||
wr->name, i);
|
||||
return true;
|
||||
|
||||
error:
|
||||
US_LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index);
|
||||
US_LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, i);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -27,6 +27,18 @@
|
||||
|
||||
#include "encoder.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../../../libs/types.h"
|
||||
#include "../../../libs/tools.h"
|
||||
#include "../../../libs/frame.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_destination_mgr mgr; // Default manager
|
||||
@ -127,7 +139,8 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, uint quali
|
||||
_jpeg_write_scanlines_bgr24(&jpeg, src);
|
||||
# endif
|
||||
break;
|
||||
default: assert(0 && "Unsupported input format for CPU encoder"); return;
|
||||
default:
|
||||
US_RAISE("Unsupported input format for CPU encoder");
|
||||
}
|
||||
|
||||
jpeg_finish_compress(&jpeg);
|
||||
@ -138,7 +151,7 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, uint quali
|
||||
|
||||
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)(
|
||||
US_A((jpeg->dest = (struct jpeg_destination_mgr*)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(_jpeg_dest_manager_s)
|
||||
)) != NULL);
|
||||
}
|
||||
@ -179,8 +192,7 @@ static void _jpeg_write_scanlines_yuv(struct jpeg_compress_struct *jpeg, const u
|
||||
u = data[0];
|
||||
v = data[2];
|
||||
} else {
|
||||
assert(0 && "Unsupported pixel format");
|
||||
return; // Makes linter happy
|
||||
US_RAISE("Unsupported pixel format");
|
||||
}
|
||||
|
||||
ptr[0] = y;
|
||||
@ -234,8 +246,7 @@ static void _jpeg_write_scanlines_yuv_planar(struct jpeg_compress_struct *jpeg,
|
||||
v = chroma1_data[chroma_position];
|
||||
break;
|
||||
default:
|
||||
assert(0 && "Unsupported pixel format");
|
||||
return; // Makes linter happy
|
||||
US_RAISE("Unsupported pixel format");
|
||||
}
|
||||
|
||||
ptr[0] = y;
|
||||
@ -358,7 +369,7 @@ static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s*)jpeg->dest;
|
||||
|
||||
// Allocate the output buffer - it will be released when done with image
|
||||
assert((dest->buf = (JOCTET*)(*jpeg->mem->alloc_small)(
|
||||
US_A((dest->buf = (JOCTET*)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET)
|
||||
)) != NULL);
|
||||
|
||||
|
||||
@ -22,16 +22,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../../../libs/tools.h"
|
||||
#include "../../../libs/types.h"
|
||||
#include "../../../libs/frame.h"
|
||||
|
||||
|
||||
|
||||
@ -27,13 +27,23 @@
|
||||
|
||||
#include "encoder.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../../../libs/types.h"
|
||||
#include "../../../libs/tools.h"
|
||||
#include "../../../libs/frame.h"
|
||||
|
||||
#include "huffman.h"
|
||||
|
||||
|
||||
void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest);
|
||||
static bool _is_huffman(const uint8_t *data);
|
||||
static bool _is_huffman(const u8 *data);
|
||||
|
||||
|
||||
void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest) {
|
||||
assert(us_is_jpeg(src->format));
|
||||
US_A(us_is_jpeg(src->format));
|
||||
_copy_plus_huffman(src, dest);
|
||||
}
|
||||
|
||||
@ -41,8 +51,8 @@ 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;
|
||||
const uint8_t *const src_end = src->data + src->used;
|
||||
const u8 *src_ptr = src->data;
|
||||
const u8 *const src_end = src->data + src->used;
|
||||
|
||||
while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) {
|
||||
src_ptr += 1;
|
||||
@ -52,7 +62,7 @@ void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t paste = src_ptr - src->data;
|
||||
const uz paste = src_ptr - src->data;
|
||||
|
||||
us_frame_set_data(dest, src->data, paste);
|
||||
us_frame_append_data(dest, US_HUFFMAN_TABLE, sizeof(US_HUFFMAN_TABLE));
|
||||
@ -65,14 +75,14 @@ void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest) {
|
||||
us_frame_encoding_end(dest);
|
||||
}
|
||||
|
||||
static bool _is_huffman(const uint8_t *data) {
|
||||
unsigned count = 0;
|
||||
static bool _is_huffman(const u8 *data) {
|
||||
uint count = 0;
|
||||
|
||||
while ((((uint16_t)data[0] << 8) | data[1]) != 0xFFDA) {
|
||||
while ((((u16)data[0] << 8) | data[1]) != 0xFFDA) {
|
||||
if (count++ > 2048) {
|
||||
return false;
|
||||
}
|
||||
if ((((uint16_t)data[0] << 8) | data[1]) == 0xFFC4) {
|
||||
if ((((u16)data[0] << 8) | data[1]) == 0xFFC4) {
|
||||
return true;
|
||||
}
|
||||
data += 1;
|
||||
|
||||
@ -22,16 +22,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../../../libs/frame.h"
|
||||
|
||||
#include "huffman.h"
|
||||
|
||||
|
||||
void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest);
|
||||
|
||||
@ -27,10 +27,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "../../../libs/types.h"
|
||||
|
||||
|
||||
static const uint8_t US_HUFFMAN_TABLE[] = {
|
||||
static const u8 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,
|
||||
|
||||
@ -32,7 +32,7 @@ us_gpio_s us_g_gpio = {
|
||||
.role = x_role, \
|
||||
.consumer = NULL, \
|
||||
.line = NULL, \
|
||||
.state = false \
|
||||
.on = false \
|
||||
}
|
||||
.prog_running = MAKE_OUTPUT("prog-running"),
|
||||
.stream_online = MAKE_OUTPUT("stream-online"),
|
||||
@ -46,13 +46,13 @@ us_gpio_s us_g_gpio = {
|
||||
};
|
||||
|
||||
|
||||
static void _gpio_output_init(us_gpio_output_s *output, struct gpiod_chip *chip);
|
||||
static void _gpio_output_destroy(us_gpio_output_s *output);
|
||||
static void _gpio_output_init(us_gpio_output_s *out, struct gpiod_chip *chip);
|
||||
static void _gpio_output_destroy(us_gpio_output_s *out);
|
||||
|
||||
|
||||
void us_gpio_init(void) {
|
||||
# ifndef HAVE_GPIOD2
|
||||
assert(us_g_gpio.chip == NULL);
|
||||
US_A(us_g_gpio.chip == NULL);
|
||||
# endif
|
||||
if (
|
||||
us_g_gpio.prog_running.pin >= 0
|
||||
@ -92,23 +92,23 @@ void us_gpio_destroy(void) {
|
||||
}
|
||||
}
|
||||
|
||||
int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
|
||||
int us_gpio_inner_set(us_gpio_output_s *out, bool on) {
|
||||
int retval = 0;
|
||||
|
||||
# ifndef HAVE_GPIOD2
|
||||
assert(us_g_gpio.chip != NULL);
|
||||
US_A(us_g_gpio.chip != NULL);
|
||||
# endif
|
||||
assert(output->line != NULL);
|
||||
assert(output->state != state); // Must be checked in macro for the performance
|
||||
US_A(out->line != NULL);
|
||||
US_A(out->on != on); // Must be checked in macro for the performance
|
||||
US_MUTEX_LOCK(us_g_gpio.mutex);
|
||||
|
||||
# ifdef HAVE_GPIOD2
|
||||
if (gpiod_line_request_set_value(output->line, output->pin, state) < 0) {
|
||||
if (gpiod_line_request_set_value(out->line, out->pin, on) < 0) {
|
||||
# else
|
||||
if (gpiod_line_set_value(output->line, (int)state) < 0) {
|
||||
if (gpiod_line_set_value(out->line, (int)on) < 0) {
|
||||
# endif
|
||||
US_LOG_PERROR("GPIO: Can't write value %d to line %s", state, output->consumer); \
|
||||
_gpio_output_destroy(output);
|
||||
US_LOG_PERROR("GPIO: Can't write value %d to line %s", on, out->consumer); \
|
||||
_gpio_output_destroy(out);
|
||||
retval = -1;
|
||||
}
|
||||
|
||||
@ -116,65 +116,65 @@ int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void _gpio_output_init(us_gpio_output_s *output, struct gpiod_chip *chip) {
|
||||
assert(output->line == NULL);
|
||||
static void _gpio_output_init(us_gpio_output_s *out, struct gpiod_chip *chip) {
|
||||
US_A(out->line == NULL);
|
||||
|
||||
US_ASPRINTF(output->consumer, "%s::%s", us_g_gpio.consumer_prefix, output->role);
|
||||
US_ASPRINTF(out->consumer, "%s::%s", us_g_gpio.consumer_prefix, out->role);
|
||||
|
||||
if (output->pin >= 0) {
|
||||
if (out->pin >= 0) {
|
||||
# ifdef HAVE_GPIOD2
|
||||
struct gpiod_line_settings *line_settings;
|
||||
assert(line_settings = gpiod_line_settings_new());
|
||||
assert(!gpiod_line_settings_set_direction(line_settings, GPIOD_LINE_DIRECTION_OUTPUT));
|
||||
assert(!gpiod_line_settings_set_output_value(line_settings, false));
|
||||
US_A(line_settings = gpiod_line_settings_new());
|
||||
US_A(!gpiod_line_settings_set_direction(line_settings, GPIOD_LINE_DIRECTION_OUTPUT));
|
||||
US_A(!gpiod_line_settings_set_output_value(line_settings, false));
|
||||
|
||||
struct gpiod_line_config *line_config;
|
||||
assert(line_config = gpiod_line_config_new());
|
||||
const unsigned offset = output->pin;
|
||||
assert(!gpiod_line_config_add_line_settings(line_config, &offset, 1, line_settings));
|
||||
US_A(line_config = gpiod_line_config_new());
|
||||
const unsigned offset = out->pin;
|
||||
US_A(!gpiod_line_config_add_line_settings(line_config, &offset, 1, line_settings));
|
||||
|
||||
struct gpiod_request_config *request_config;
|
||||
assert(request_config = gpiod_request_config_new());
|
||||
gpiod_request_config_set_consumer(request_config, output->consumer);
|
||||
struct gpiod_request_config *req_config;
|
||||
US_A(req_config = gpiod_request_config_new());
|
||||
gpiod_request_config_set_consumer(req_config, out->consumer);
|
||||
|
||||
if ((output->line = gpiod_chip_request_lines(chip, request_config, line_config)) == NULL) {
|
||||
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
|
||||
if ((out->line = gpiod_chip_request_lines(chip, req_config, line_config)) == NULL) {
|
||||
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", out->pin, out->consumer);
|
||||
}
|
||||
|
||||
gpiod_request_config_free(request_config);
|
||||
gpiod_request_config_free(req_config);
|
||||
gpiod_line_config_free(line_config);
|
||||
gpiod_line_settings_free(line_settings);
|
||||
|
||||
if (output->line == NULL) {
|
||||
_gpio_output_destroy(output);
|
||||
if (out->line == NULL) {
|
||||
_gpio_output_destroy(out);
|
||||
}
|
||||
|
||||
# else
|
||||
|
||||
if ((output->line = gpiod_chip_get_line(chip, output->pin)) != NULL) {
|
||||
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) {
|
||||
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
|
||||
_gpio_output_destroy(output);
|
||||
if ((out->line = gpiod_chip_get_line(chip, out->pin)) != NULL) {
|
||||
if (gpiod_line_request_output(out->line, out->consumer, 0) < 0) {
|
||||
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", out->pin, out->consumer);
|
||||
_gpio_output_destroy(out);
|
||||
}
|
||||
} else {
|
||||
US_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", out->pin, out->consumer);
|
||||
}
|
||||
# endif
|
||||
}
|
||||
}
|
||||
|
||||
static void _gpio_output_destroy(us_gpio_output_s *output) {
|
||||
if (output->line != NULL) {
|
||||
static void _gpio_output_destroy(us_gpio_output_s *out) {
|
||||
if (out->line != NULL) {
|
||||
# ifdef HAVE_GPIOD2
|
||||
gpiod_line_request_release(output->line);
|
||||
gpiod_line_request_release(out->line);
|
||||
# else
|
||||
gpiod_line_release(output->line);
|
||||
gpiod_line_release(out->line);
|
||||
# endif
|
||||
output->line = NULL;
|
||||
out->line = NULL;
|
||||
}
|
||||
if (output->consumer != NULL) {
|
||||
free(output->consumer);
|
||||
output->consumer = NULL;
|
||||
if (out->consumer != NULL) {
|
||||
free(out->consumer);
|
||||
out->consumer = NULL;
|
||||
}
|
||||
output->state = false;
|
||||
out->on = false;
|
||||
}
|
||||
|
||||
@ -25,7 +25,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <gpiod.h>
|
||||
@ -44,7 +43,7 @@ typedef struct {
|
||||
# else
|
||||
struct gpiod_line *line;
|
||||
# endif
|
||||
bool state;
|
||||
bool on;
|
||||
} us_gpio_output_s;
|
||||
|
||||
typedef struct {
|
||||
@ -69,27 +68,27 @@ extern us_gpio_s us_g_gpio;
|
||||
|
||||
void us_gpio_init(void);
|
||||
void us_gpio_destroy(void);
|
||||
int us_gpio_inner_set(us_gpio_output_s *output, bool state);
|
||||
int us_gpio_inner_set(us_gpio_output_s *out, bool on);
|
||||
|
||||
|
||||
#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; \
|
||||
#define SET_ON(x_out, x_on) { \
|
||||
if (x_out.line && x_out.on != x_on) { \
|
||||
if (!us_gpio_inner_set(&x_out, x_on)) { \
|
||||
x_out.on = x_on; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
INLINE void us_gpio_set_prog_running(bool state) {
|
||||
SET_STATE(us_g_gpio.prog_running, state);
|
||||
INLINE void us_gpio_set_prog_running(bool on) {
|
||||
SET_ON(us_g_gpio.prog_running, on);
|
||||
}
|
||||
|
||||
INLINE void us_gpio_set_stream_online(bool state) {
|
||||
SET_STATE(us_g_gpio.stream_online, state);
|
||||
INLINE void us_gpio_set_stream_online(bool on) {
|
||||
SET_ON(us_g_gpio.stream_online, on);
|
||||
}
|
||||
|
||||
INLINE void us_gpio_set_has_http_clients(bool state) {
|
||||
SET_STATE(us_g_gpio.has_http_clients, state);
|
||||
INLINE void us_gpio_set_has_http_clients(bool on) {
|
||||
SET_ON(us_g_gpio.has_http_clients, on);
|
||||
}
|
||||
|
||||
#undef SET_STATE
|
||||
#undef SET_ON
|
||||
|
||||
@ -67,6 +67,6 @@ const char *us_guess_mime_type(const char *path) {
|
||||
}
|
||||
});
|
||||
|
||||
misc:
|
||||
return "application/misc";
|
||||
misc:
|
||||
return "application/misc";
|
||||
}
|
||||
|
||||
@ -31,7 +31,6 @@
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@ -75,17 +74,17 @@
|
||||
#endif
|
||||
|
||||
|
||||
static int _http_preprocess_request(struct evhttp_request *request, us_server_s *server);
|
||||
static int _http_preprocess_request(struct evhttp_request *req, us_server_s *server);
|
||||
|
||||
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server);
|
||||
static int _http_check_run_compat_action(struct evhttp_request *req, void *v_server);
|
||||
|
||||
static void _http_callback_root(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_favicon(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_static(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_state(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_root(struct evhttp_request *req, void *v_server);
|
||||
static void _http_callback_favicon(struct evhttp_request *req, void *v_server);
|
||||
static void _http_callback_static(struct evhttp_request *req, void *v_server);
|
||||
static void _http_callback_state(struct evhttp_request *req, void *v_server);
|
||||
static void _http_callback_snapshot(struct evhttp_request *req, void *v_server);
|
||||
|
||||
static void _http_callback_stream(struct evhttp_request *request, void *v_server);
|
||||
static void _http_callback_stream(struct evhttp_request *req, void *v_server);
|
||||
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_ctx);
|
||||
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx);
|
||||
|
||||
@ -96,18 +95,18 @@ static void _http_send_snapshot(us_server_s *server);
|
||||
static bool _expose_frame(us_server_s *server, const us_frame_s *frame);
|
||||
|
||||
|
||||
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
|
||||
#define _A_EVBUFFER_NEW(x_buf) assert((x_buf = evbuffer_new()) != NULL)
|
||||
#define _A_EVBUFFER_ADD(x_buf, x_data, x_size) assert(!evbuffer_add(x_buf, x_data, x_size))
|
||||
#define _A_EVBUFFER_ADD_PRINTF(x_buf, x_fmt, ...) assert(evbuffer_add_printf(x_buf, x_fmt, ##__VA_ARGS__) >= 0)
|
||||
#define _A_EVBUFFER_NEW(x_buf) US_A((x_buf = evbuffer_new()) != NULL)
|
||||
#define _A_EVBUFFER_ADD(x_buf, x_data, x_size) US_A(!evbuffer_add(x_buf, x_data, x_size))
|
||||
#define _A_EVBUFFER_ADD_PRINTF(x_buf, x_fmt, ...) US_A(evbuffer_add_printf(x_buf, x_fmt, ##__VA_ARGS__) >= 0)
|
||||
|
||||
#define _A_ADD_HEADER(x_request, x_key, x_value) \
|
||||
assert(!evhttp_add_header(evhttp_request_get_output_headers(x_request), x_key, x_value))
|
||||
#define _A_ADD_HEADER(x_req, x_key, x_value) \
|
||||
US_A(!evhttp_add_header(evhttp_request_get_output_headers(x_req), x_key, x_value))
|
||||
|
||||
|
||||
us_server_s *us_server_init(us_stream_s *stream) {
|
||||
@ -135,9 +134,9 @@ us_server_s *us_server_init(us_stream_s *stream) {
|
||||
server->stream = stream;
|
||||
server->run = run;
|
||||
|
||||
assert(!evthread_use_pthreads());
|
||||
assert((run->base = event_base_new()) != NULL);
|
||||
assert((run->http = evhttp_new(run->base)) != NULL);
|
||||
US_A(!evthread_use_pthreads());
|
||||
US_A((run->base = event_base_new()) != NULL);
|
||||
US_A((run->http = evhttp_new(run->base)) != NULL);
|
||||
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD|EVHTTP_REQ_OPTIONS);
|
||||
return server;
|
||||
}
|
||||
@ -188,17 +187,17 @@ int us_server_listen(us_server_s *server) {
|
||||
_LOG_INFO("Enabling the 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, "/favicon.ico", _http_callback_favicon, (void*)server));
|
||||
US_A(!evhttp_set_cb(run->http, "/", _http_callback_root, (void*)server));
|
||||
US_A(!evhttp_set_cb(run->http, "/favicon.ico", _http_callback_favicon, (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));
|
||||
US_A(!evhttp_set_cb(run->http, "/state", _http_callback_state, (void*)server));
|
||||
US_A(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void*)server));
|
||||
US_A(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void*)server));
|
||||
}
|
||||
|
||||
us_frame_copy(stream->run->blank->jpeg, ex->frame);
|
||||
|
||||
assert((run->refresher = event_new(run->base, -1, 0, _http_refresher, server)) != NULL);
|
||||
US_A((run->refresher = event_new(run->base, -1, 0, _http_refresher, server)) != NULL);
|
||||
stream->run->http->jpeg_refresher = run->refresher;
|
||||
|
||||
evhttp_set_timeout(run->http, server->timeout);
|
||||
@ -260,66 +259,66 @@ void us_server_loop_break(us_server_s *server) {
|
||||
event_base_loopbreak(server->run->base);
|
||||
}
|
||||
|
||||
static int _http_preprocess_request(struct evhttp_request *request, us_server_s *server) {
|
||||
static int _http_preprocess_request(struct evhttp_request *req, us_server_s *server) {
|
||||
const us_server_runtime_s *const run = server->run;
|
||||
|
||||
atomic_store(&server->stream->run->http->last_request_ts, us_get_now_monotonic());
|
||||
atomic_store(&server->stream->run->http->last_req_ts, us_get_now_monotonic());
|
||||
|
||||
if (server->allow_origin[0] != '\0') {
|
||||
const char *const cors_headers = us_evhttp_get_header(request, "Access-Control-Request-Headers");
|
||||
const char *const cors_method = us_evhttp_get_header(request, "Access-Control-Request-Method");
|
||||
const char *const cors_headers = us_evhttp_get_header(req, "Access-Control-Request-Headers");
|
||||
const char *const cors_method = us_evhttp_get_header(req, "Access-Control-Request-Method");
|
||||
|
||||
_A_ADD_HEADER(request, "Access-Control-Allow-Origin", server->allow_origin);
|
||||
_A_ADD_HEADER(request, "Access-Control-Allow-Credentials", "true");
|
||||
_A_ADD_HEADER(req, "Access-Control-Allow-Origin", server->allow_origin);
|
||||
_A_ADD_HEADER(req, "Access-Control-Allow-Credentials", "true");
|
||||
if (cors_headers != NULL) {
|
||||
_A_ADD_HEADER(request, "Access-Control-Allow-Headers", cors_headers);
|
||||
_A_ADD_HEADER(req, "Access-Control-Allow-Headers", cors_headers);
|
||||
}
|
||||
if (cors_method != NULL) {
|
||||
_A_ADD_HEADER(request, "Access-Control-Allow-Methods", cors_method);
|
||||
_A_ADD_HEADER(req, "Access-Control-Allow-Methods", cors_method);
|
||||
}
|
||||
}
|
||||
|
||||
if (evhttp_request_get_command(request) == EVHTTP_REQ_OPTIONS) {
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", NULL);
|
||||
if (evhttp_request_get_command(req) == EVHTTP_REQ_OPTIONS) {
|
||||
evhttp_send_reply(req, HTTP_OK, "OK", NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (run->auth_token != NULL) {
|
||||
const char *const token = us_evhttp_get_header(request, "Authorization");
|
||||
const char *const token = us_evhttp_get_header(req, "Authorization");
|
||||
if (token == NULL || strcmp(token, run->auth_token) != 0) {
|
||||
_A_ADD_HEADER(request, "WWW-Authenticate", "Basic realm=\"Restricted area\"");
|
||||
evhttp_send_reply(request, 401, "Unauthorized", NULL);
|
||||
_A_ADD_HEADER(req, "WWW-Authenticate", "Basic realm=\"Restricted area\"");
|
||||
evhttp_send_reply(req, 401, "Unauthorized", NULL);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) {
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", NULL);
|
||||
if (evhttp_request_get_command(req) == EVHTTP_REQ_HEAD) {
|
||||
evhttp_send_reply(req, HTTP_OK, "OK", NULL);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define PREPROCESS_REQUEST { \
|
||||
if (_http_preprocess_request(request, server) < 0) { \
|
||||
if (_http_preprocess_request(req, server) < 0) { \
|
||||
return; \
|
||||
} \
|
||||
}
|
||||
|
||||
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server) {
|
||||
static int _http_check_run_compat_action(struct evhttp_request *req, void *v_server) {
|
||||
// MJPG-Streamer compatibility layer
|
||||
|
||||
int retval = -1;
|
||||
|
||||
struct evkeyvalq params;
|
||||
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
||||
evhttp_parse_query(evhttp_request_get_uri(req), ¶ms);
|
||||
const char *const action = evhttp_find_header(¶ms, "action");
|
||||
|
||||
if (action && !strcmp(action, "snapshot")) {
|
||||
_http_callback_snapshot(request, v_server);
|
||||
_http_callback_snapshot(req, v_server);
|
||||
retval = 0;
|
||||
} else if (action && !strcmp(action, "stream")) {
|
||||
_http_callback_stream(request, v_server);
|
||||
_http_callback_stream(req, v_server);
|
||||
retval = 0;
|
||||
}
|
||||
|
||||
@ -328,12 +327,12 @@ static int _http_check_run_compat_action(struct evhttp_request *request, void *v
|
||||
}
|
||||
|
||||
#define COMPAT_REQUEST { \
|
||||
if (_http_check_run_compat_action(request, v_server) == 0) { \
|
||||
if (_http_check_run_compat_action(req, v_server) == 0) { \
|
||||
return; \
|
||||
} \
|
||||
}
|
||||
|
||||
static void _http_callback_root(struct evhttp_request *request, void *v_server) {
|
||||
static void _http_callback_root(struct evhttp_request *req, void *v_server) {
|
||||
us_server_s *const server = v_server;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
@ -342,13 +341,13 @@ static void _http_callback_root(struct evhttp_request *request, void *v_server)
|
||||
struct evbuffer *buf;
|
||||
_A_EVBUFFER_NEW(buf);
|
||||
_A_EVBUFFER_ADD_PRINTF(buf, "%s", US_HTML_INDEX_PAGE);
|
||||
_A_ADD_HEADER(request, "Content-Type", "text/html");
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
_A_ADD_HEADER(req, "Content-Type", "text/html");
|
||||
evhttp_send_reply(req, HTTP_OK, "OK", buf);
|
||||
|
||||
evbuffer_free(buf);
|
||||
}
|
||||
|
||||
static void _http_callback_favicon(struct evhttp_request *request, void *v_server) {
|
||||
static void _http_callback_favicon(struct evhttp_request *req, void *v_server) {
|
||||
us_server_s *const server = v_server;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
@ -356,13 +355,13 @@ static void _http_callback_favicon(struct evhttp_request *request, void *v_serve
|
||||
struct evbuffer *buf;
|
||||
_A_EVBUFFER_NEW(buf);
|
||||
_A_EVBUFFER_ADD(buf, (const void*)US_FAVICON_ICO_DATA, US_FAVICON_ICO_DATA_SIZE);
|
||||
_A_ADD_HEADER(request, "Content-Type", "image/x-icon");
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
_A_ADD_HEADER(req, "Content-Type", "image/x-icon");
|
||||
evhttp_send_reply(req, HTTP_OK, "OK", buf);
|
||||
|
||||
evbuffer_free(buf);
|
||||
}
|
||||
|
||||
static void _http_callback_static(struct evhttp_request *request, void *v_server) {
|
||||
static void _http_callback_static(struct evhttp_request *req, void *v_server) {
|
||||
us_server_s *const server = v_server;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
@ -376,7 +375,7 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
||||
|
||||
{
|
||||
const char *uri_path;
|
||||
if ((uri = evhttp_uri_parse(evhttp_request_get_uri(request))) == NULL) {
|
||||
if ((uri = evhttp_uri_parse(evhttp_request_get_uri(req))) == NULL) {
|
||||
goto bad_request;
|
||||
}
|
||||
if ((uri_path = (char*)evhttp_uri_get_path(uri)) == NULL) {
|
||||
@ -413,17 +412,17 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
||||
// and will close it when finished transferring data
|
||||
fd = -1;
|
||||
|
||||
_A_ADD_HEADER(request, "Content-Type", us_guess_mime_type(static_path));
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
_A_ADD_HEADER(req, "Content-Type", us_guess_mime_type(static_path));
|
||||
evhttp_send_reply(req, HTTP_OK, "OK", buf);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
bad_request:
|
||||
evhttp_send_error(request, HTTP_BADREQUEST, NULL);
|
||||
evhttp_send_error(req, HTTP_BADREQUEST, NULL);
|
||||
goto cleanup;
|
||||
|
||||
not_found:
|
||||
evhttp_send_error(request, HTTP_NOTFOUND, NULL);
|
||||
evhttp_send_error(req, HTTP_NOTFOUND, NULL);
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
@ -436,7 +435,7 @@ cleanup:
|
||||
|
||||
#undef COMPAT_REQUEST
|
||||
|
||||
static void _http_callback_state(struct evhttp_request *request, void *v_server) {
|
||||
static void _http_callback_state(struct evhttp_request *req, void *v_server) {
|
||||
us_server_s *const server = v_server;
|
||||
us_server_runtime_s *const run = server->run;
|
||||
us_server_exposed_s *const ex = run->exposed;
|
||||
@ -451,60 +450,61 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
struct evbuffer *buf;
|
||||
_A_EVBUFFER_NEW(buf);
|
||||
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
_A_EVBUFFER_ADD_PRINTF(
|
||||
buf,
|
||||
"{\"ok\": true, \"result\": {"
|
||||
" \"instance_id\": \"%s\","
|
||||
" \"encoder\": {\"type\": \"%s\", \"quality\": %u},",
|
||||
server->instance_id,
|
||||
us_encoder_type_to_string(enc_type),
|
||||
enc_quality
|
||||
);
|
||||
enc_quality);
|
||||
|
||||
# ifdef WITH_V4P
|
||||
if (stream->drm != NULL) {
|
||||
us_fpsi_meta_s meta;
|
||||
const uint fps = us_fpsi_get(stream->run->http->drm_fpsi, &meta);
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
_A_EVBUFFER_ADD_PRINTF(
|
||||
buf,
|
||||
" \"drm\": {\"live\": %s, \"fps\": %u},",
|
||||
us_bool_to_string(meta.online),
|
||||
fps
|
||||
);
|
||||
fps);
|
||||
}
|
||||
# endif
|
||||
|
||||
if (stream->h264_sink != NULL) {
|
||||
us_fpsi_meta_s meta;
|
||||
const uint fps = us_fpsi_get(stream->run->http->h264_fpsi, &meta);
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
_A_EVBUFFER_ADD_PRINTF(
|
||||
buf,
|
||||
" \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s, \"fps\": %u},",
|
||||
stream->h264_bitrate,
|
||||
stream->h264_gop,
|
||||
us_bool_to_string(meta.online),
|
||||
fps
|
||||
);
|
||||
fps);
|
||||
}
|
||||
|
||||
if (stream->jpeg_sink != NULL || stream->h264_sink != NULL) {
|
||||
_A_EVBUFFER_ADD_PRINTF(buf, " \"sinks\": {");
|
||||
if (stream->jpeg_sink != NULL) {
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
_A_EVBUFFER_ADD_PRINTF(
|
||||
buf,
|
||||
"\"jpeg\": {\"has_clients\": %s}",
|
||||
us_bool_to_string(atomic_load(&stream->jpeg_sink->has_clients))
|
||||
);
|
||||
us_bool_to_string(atomic_load(&stream->jpeg_sink->has_clients)));
|
||||
}
|
||||
if (stream->h264_sink != NULL) {
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
_A_EVBUFFER_ADD_PRINTF(
|
||||
buf,
|
||||
"%s\"h264\": {\"has_clients\": %s}",
|
||||
(stream->jpeg_sink ? ", " : ""),
|
||||
us_bool_to_string(atomic_load(&stream->h264_sink->has_clients))
|
||||
);
|
||||
us_bool_to_string(atomic_load(&stream->h264_sink->has_clients)));
|
||||
}
|
||||
_A_EVBUFFER_ADD_PRINTF(buf, "},");
|
||||
}
|
||||
|
||||
us_fpsi_meta_s captured_meta;
|
||||
const uint captured_fps = us_fpsi_get(stream->run->http->captured_fpsi, &captured_meta);
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
_A_EVBUFFER_ADD_PRINTF(
|
||||
buf,
|
||||
" \"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
|
||||
" \"online\": %s, \"desired_fps\": %u, \"captured_fps\": %u},"
|
||||
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
|
||||
@ -514,11 +514,11 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
stream->desired_fps,
|
||||
captured_fps,
|
||||
us_fpsi_get(ex->queued_fpsi, NULL),
|
||||
run->stream_clients_count
|
||||
);
|
||||
run->stream_clients_count);
|
||||
|
||||
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
_A_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,
|
||||
@ -528,18 +528,17 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
us_bool_to_string(client->dual_final_frames),
|
||||
us_bool_to_string(client->zero_data),
|
||||
(client->key != NULL ? client->key : "0"),
|
||||
(client->next ? ", " : "")
|
||||
);
|
||||
(client->next ? ", " : ""));
|
||||
});
|
||||
|
||||
_A_EVBUFFER_ADD_PRINTF(buf, "}}}}");
|
||||
|
||||
_A_ADD_HEADER(request, "Content-Type", "application/json");
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
_A_ADD_HEADER(req, "Content-Type", "application/json");
|
||||
evhttp_send_reply(req, HTTP_OK, "OK", buf);
|
||||
evbuffer_free(buf);
|
||||
}
|
||||
|
||||
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server) {
|
||||
static void _http_callback_snapshot(struct evhttp_request *req, void *v_server) {
|
||||
us_server_s *const server = v_server;
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
@ -547,14 +546,14 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
||||
us_snapshot_client_s *client;
|
||||
US_CALLOC(client, 1);
|
||||
client->server = server;
|
||||
client->request = request;
|
||||
client->request_ts = us_get_now_monotonic();
|
||||
client->req = req;
|
||||
client->req_ts = us_get_now_monotonic();
|
||||
|
||||
atomic_fetch_add(&server->stream->run->http->snapshot_requested, 1);
|
||||
US_LIST_APPEND(server->run->snapshot_clients, client);
|
||||
}
|
||||
|
||||
static void _http_callback_stream(struct evhttp_request *request, void *v_server) {
|
||||
static void _http_callback_stream(struct evhttp_request *req, void *v_server) {
|
||||
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2814
|
||||
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2789
|
||||
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L362
|
||||
@ -566,17 +565,17 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
|
||||
PREPROCESS_REQUEST;
|
||||
|
||||
struct evhttp_connection *const conn = evhttp_request_get_connection(request);
|
||||
struct evhttp_connection *const conn = evhttp_request_get_connection(req);
|
||||
if (conn != NULL) {
|
||||
us_stream_client_s *client;
|
||||
US_CALLOC(client, 1);
|
||||
client->server = server;
|
||||
client->request = request;
|
||||
client->req = req;
|
||||
client->need_initial = true;
|
||||
client->need_first_frame = true;
|
||||
|
||||
struct evkeyvalq params;
|
||||
evhttp_parse_query(evhttp_request_get_uri(request), ¶ms);
|
||||
evhttp_parse_query(evhttp_request_get_uri(req), ¶ms);
|
||||
# define PARSE_PARAM(x_type, x_name) client->x_name = us_evkeyvalq_get_##x_type(¶ms, #x_name)
|
||||
PARSE_PARAM(string, key);
|
||||
PARSE_PARAM(true, extra_headers);
|
||||
@ -586,7 +585,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
# undef PARSE_PARAM
|
||||
evhttp_clear_headers(¶ms);
|
||||
|
||||
client->hostport = us_evhttp_get_hostport(request);
|
||||
client->hostport = us_evhttp_get_hostport(req);
|
||||
client->id = us_get_now_id();
|
||||
|
||||
{
|
||||
@ -612,7 +611,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
if (server->tcp_nodelay && run->ext_fd >= 0) {
|
||||
_LOG_DEBUG("Setting up TCP_NODELAY to the client %s ...", client->hostport);
|
||||
const evutil_socket_t fd = bufferevent_getfd(buf_event);
|
||||
assert(fd >= 0);
|
||||
US_A(fd >= 0);
|
||||
int on = 1;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on)) != 0) {
|
||||
_LOG_PERROR("Can't set TCP_NODELAY to the client %s", client->hostport);
|
||||
@ -621,7 +620,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void*)client);
|
||||
bufferevent_enable(buf_event, EV_READ);
|
||||
} else {
|
||||
evhttp_request_free(request);
|
||||
evhttp_request_free(req);
|
||||
}
|
||||
}
|
||||
|
||||
@ -659,21 +658,24 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
# define BOUNDARY "boundarydonotcross"
|
||||
|
||||
# define ADD_ADVANCE_HEADERS \
|
||||
_A_EVBUFFER_ADD_PRINTF(buf, \
|
||||
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, us_get_now_real())
|
||||
_A_EVBUFFER_ADD_PRINTF( \
|
||||
buf, \
|
||||
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, \
|
||||
us_get_now_real())
|
||||
|
||||
if (client->need_initial) {
|
||||
_A_EVBUFFER_ADD_PRINTF(buf, "HTTP/1.0 200 OK" RN);
|
||||
|
||||
if (client->server->allow_origin[0] != '\0') {
|
||||
const char *const cors_headers = us_evhttp_get_header(client->request, "Access-Control-Request-Headers");
|
||||
const char *const cors_method = us_evhttp_get_header(client->request, "Access-Control-Request-Method");
|
||||
const char *const cors_headers = us_evhttp_get_header(client->req, "Access-Control-Request-Headers");
|
||||
const char *const cors_method = us_evhttp_get_header(client->req, "Access-Control-Request-Method");
|
||||
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
_A_EVBUFFER_ADD_PRINTF(
|
||||
buf,
|
||||
"Access-Control-Allow-Origin: %s" RN
|
||||
"Access-Control-Allow-Credentials: true" RN,
|
||||
client->server->allow_origin
|
||||
);
|
||||
client->server->allow_origin);
|
||||
|
||||
if (cors_headers != NULL) {
|
||||
_A_EVBUFFER_ADD_PRINTF(buf, "Access-Control-Allow-Headers: %s" RN, cors_headers);
|
||||
}
|
||||
@ -682,7 +684,8 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
}
|
||||
}
|
||||
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
_A_EVBUFFER_ADD_PRINTF(
|
||||
buf,
|
||||
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
||||
"Pragma: no-cache" RN
|
||||
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
|
||||
@ -693,30 +696,31 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
(server->instance_id[0] == '\0' ? "" : "_"),
|
||||
server->instance_id,
|
||||
(client->key != NULL ? client->key : "0"),
|
||||
client->id
|
||||
);
|
||||
client->id);
|
||||
|
||||
if (client->advance_headers) {
|
||||
ADD_ADVANCE_HEADERS;
|
||||
}
|
||||
|
||||
assert(!bufferevent_write_buffer(buf_event, buf));
|
||||
US_A(!bufferevent_write_buffer(buf_event, buf));
|
||||
client->need_initial = false;
|
||||
}
|
||||
|
||||
if (!client->advance_headers) {
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
_A_EVBUFFER_ADD_PRINTF(
|
||||
buf,
|
||||
"Content-Type: image/jpeg" RN
|
||||
"Content-Length: %zu" RN
|
||||
"X-Timestamp: %.06Lf" RN
|
||||
"%s",
|
||||
(!client->zero_data ? ex->frame->used : 0),
|
||||
us_get_now_real(),
|
||||
(client->extra_headers ? "" : RN)
|
||||
);
|
||||
(client->extra_headers ? "" : RN));
|
||||
|
||||
const ldf now_ts = us_get_now_monotonic();
|
||||
if (client->extra_headers) {
|
||||
_A_EVBUFFER_ADD_PRINTF(buf,
|
||||
_A_EVBUFFER_ADD_PRINTF(
|
||||
buf,
|
||||
"X-UStreamer-Online: %s" RN
|
||||
"X-UStreamer-Dropped: %u" RN
|
||||
"X-UStreamer-Width: %u" RN
|
||||
@ -745,8 +749,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
ex->expose_cmp_ts,
|
||||
ex->expose_end_ts,
|
||||
now_ts,
|
||||
now_ts - ex->frame->grab_begin_ts
|
||||
);
|
||||
now_ts - ex->frame->grab_begin_ts);
|
||||
}
|
||||
}
|
||||
|
||||
@ -759,7 +762,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
ADD_ADVANCE_HEADERS;
|
||||
}
|
||||
|
||||
assert(!bufferevent_write_buffer(buf_event, buf));
|
||||
US_A(!bufferevent_write_buffer(buf_event, buf));
|
||||
evbuffer_free(buf);
|
||||
|
||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void*)client);
|
||||
@ -791,7 +794,7 @@ static void _http_callback_stream_error(struct bufferevent *buf_event, short wha
|
||||
run->stream_clients_count, client->hostport, client->id, reason);
|
||||
free(reason);
|
||||
|
||||
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
|
||||
struct evhttp_connection *conn = evhttp_request_get_connection(client->req);
|
||||
US_DELETE(conn, evhttp_connection_free);
|
||||
|
||||
us_fpsi_destroy(client->fpsi);
|
||||
@ -808,7 +811,7 @@ static void _http_send_stream(us_server_s *server, bool stream_updated, bool fra
|
||||
bool has_clients = true;
|
||||
|
||||
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
|
||||
struct evhttp_connection *const conn = evhttp_request_get_connection(client->request);
|
||||
struct evhttp_connection *const conn = evhttp_request_get_connection(client->req);
|
||||
if (conn != NULL) {
|
||||
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
|
||||
// WebKit отрисовывает последний фрейм в серии с некоторой задержкой,
|
||||
@ -852,22 +855,22 @@ static void _http_send_snapshot(us_server_s *server) {
|
||||
|
||||
# define ADD_TIME_HEADER(x_key, x_value) { \
|
||||
US_SNPRINTF(header_buf, 255, "%.06Lf", x_value); \
|
||||
_A_ADD_HEADER(request, x_key, header_buf); \
|
||||
_A_ADD_HEADER(req, x_key, header_buf); \
|
||||
}
|
||||
|
||||
# define ADD_UNSIGNED_HEADER(x_key, x_value) { \
|
||||
US_SNPRINTF(header_buf, 255, "%u", x_value); \
|
||||
_A_ADD_HEADER(request, x_key, header_buf); \
|
||||
_A_ADD_HEADER(req, x_key, header_buf); \
|
||||
}
|
||||
|
||||
us_fpsi_meta_s captured_meta;
|
||||
us_fpsi_get(server->stream->run->http->captured_fpsi, &captured_meta);
|
||||
|
||||
US_LIST_ITERATE(server->run->snapshot_clients, client, { // cppcheck-suppress constStatement
|
||||
struct evhttp_request *request = client->request;
|
||||
struct evhttp_request *req = client->req;
|
||||
|
||||
const bool has_fresh_snapshot = (atomic_load(&server->stream->run->http->snapshot_requested) == 0);
|
||||
const bool timed_out = (client->request_ts + US_MAX((uint)1, server->stream->error_delay * 3) < us_get_now_monotonic());
|
||||
const bool timed_out = (client->req_ts + US_MAX((uint)1, server->stream->error_delay * 3) < us_get_now_monotonic());
|
||||
|
||||
if (has_fresh_snapshot || timed_out) {
|
||||
us_frame_s *frame = ex->frame;
|
||||
@ -883,15 +886,15 @@ static void _http_send_snapshot(us_server_s *server) {
|
||||
_A_EVBUFFER_NEW(buf);
|
||||
_A_EVBUFFER_ADD(buf, (const void*)frame->data, frame->used);
|
||||
|
||||
_A_ADD_HEADER(request, "Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
|
||||
_A_ADD_HEADER(request, "Pragma", "no-cache");
|
||||
_A_ADD_HEADER(request, "Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
|
||||
_A_ADD_HEADER(req, "Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
|
||||
_A_ADD_HEADER(req, "Pragma", "no-cache");
|
||||
_A_ADD_HEADER(req, "Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
|
||||
|
||||
char header_buf[256];
|
||||
|
||||
ADD_TIME_HEADER("X-Timestamp", us_get_now_real());
|
||||
|
||||
_A_ADD_HEADER(request, "X-UStreamer-Online", us_bool_to_string(frame->online));
|
||||
_A_ADD_HEADER(req, "X-UStreamer-Online", us_bool_to_string(frame->online));
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Width", frame->width);
|
||||
ADD_UNSIGNED_HEADER("X-UStreamer-Height", frame->height);
|
||||
ADD_TIME_HEADER("X-UStreamer-Grab-Begin-Timestamp", frame->grab_begin_ts);
|
||||
@ -900,9 +903,9 @@ static void _http_send_snapshot(us_server_s *server) {
|
||||
ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", frame->encode_end_ts);
|
||||
ADD_TIME_HEADER("X-UStreamer-Send-Timestamp", us_get_now_monotonic());
|
||||
|
||||
_A_ADD_HEADER(request, "Content-Type", "image/jpeg");
|
||||
_A_ADD_HEADER(req, "Content-Type", "image/jpeg");
|
||||
|
||||
evhttp_send_reply(request, HTTP_OK, "OK", buf);
|
||||
evhttp_send_reply(req, HTTP_OK, "OK", buf);
|
||||
evbuffer_free(buf);
|
||||
|
||||
US_LIST_REMOVE(server->run->snapshot_clients, client);
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
|
||||
typedef struct {
|
||||
struct us_server_sx *server;
|
||||
struct evhttp_request *request;
|
||||
struct evhttp_request *req;
|
||||
|
||||
char *key;
|
||||
bool extra_headers;
|
||||
@ -59,8 +59,8 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
struct us_server_sx *server;
|
||||
struct evhttp_request *request;
|
||||
ldf request_ts;
|
||||
struct evhttp_request *req;
|
||||
ldf req_ts;
|
||||
|
||||
US_LIST_DECLARE;
|
||||
} us_snapshot_client_s;
|
||||
|
||||
@ -26,7 +26,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
@ -36,17 +35,17 @@
|
||||
#include "path.h"
|
||||
|
||||
|
||||
char *us_find_static_file_path(const char *root_path, const char *request_path) {
|
||||
char *us_find_static_file_path(const char *root_path, const char *req_path) {
|
||||
char *path = NULL;
|
||||
|
||||
char *const simplified_path = us_simplify_request_path(request_path);
|
||||
char *const simplified_path = us_simplify_request_path(req_path);
|
||||
if (simplified_path[0] == '\0') {
|
||||
US_LOG_VERBOSE("HTTP: Invalid request path %s to static", request_path);
|
||||
US_LOG_VERBOSE("HTTP: Invalid request path %s to static", req_path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
US_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
|
||||
assert(sprintf(path, "%s/%s", root_path, simplified_path) > 0);
|
||||
US_A(sprintf(path, "%s/%s", root_path, simplified_path) > 0);
|
||||
|
||||
struct stat st;
|
||||
# define LOAD_STAT { \
|
||||
|
||||
@ -23,4 +23,4 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
char *us_find_static_file_path(const char *root_path, const char *request_path);
|
||||
char *us_find_static_file_path(const char *root_path, const char *req_path);
|
||||
|
||||
@ -23,7 +23,6 @@
|
||||
#include "systemd.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <event2/http.h>
|
||||
#include <event2/util.h>
|
||||
@ -47,7 +46,7 @@ evutil_socket_t us_evhttp_bind_systemd(struct evhttp *http) {
|
||||
}
|
||||
fd = SD_LISTEN_FDS_START;
|
||||
|
||||
assert(!evutil_make_socket_nonblocking(fd));
|
||||
US_A(!evutil_make_socket_nonblocking(fd));
|
||||
|
||||
if (evhttp_accept_socket(http, fd) < 0) {
|
||||
US_LOG_PERROR("HTTP: Can't evhttp_accept_socket() systemd socket");
|
||||
|
||||
@ -25,7 +25,6 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
@ -54,8 +53,8 @@ evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
const evutil_socket_t fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
assert(fd >= 0);
|
||||
assert(!evutil_make_socket_nonblocking(fd));
|
||||
US_A(fd >= 0);
|
||||
US_A(!evutil_make_socket_nonblocking(fd));
|
||||
|
||||
if (rm && unlink(path) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
@ -82,27 +81,27 @@ evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool
|
||||
return fd;
|
||||
}
|
||||
|
||||
const char *us_evhttp_get_header(struct evhttp_request *request, const char *key) {
|
||||
return evhttp_find_header(evhttp_request_get_input_headers(request), key);
|
||||
const char *us_evhttp_get_header(struct evhttp_request *req, const char *key) {
|
||||
return evhttp_find_header(evhttp_request_get_input_headers(req), key);
|
||||
}
|
||||
|
||||
char *us_evhttp_get_hostport(struct evhttp_request *request) {
|
||||
char *us_evhttp_get_hostport(struct evhttp_request *req) {
|
||||
char *addr = NULL;
|
||||
unsigned short port = 0;
|
||||
struct evhttp_connection *conn = evhttp_request_get_connection(request);
|
||||
struct evhttp_connection *conn = evhttp_request_get_connection(req);
|
||||
if (conn != NULL) {
|
||||
char *peer;
|
||||
evhttp_connection_get_peer(conn, &peer, &port);
|
||||
addr = us_strdup(peer);
|
||||
}
|
||||
|
||||
const char *xff = us_evhttp_get_header(request, "X-Forwarded-For");
|
||||
const char *xff = us_evhttp_get_header(req, "X-Forwarded-For");
|
||||
if (xff != NULL) {
|
||||
US_DELETE(addr, free);
|
||||
assert((addr = strndup(xff, 1024)) != NULL);
|
||||
for (uint index = 0; addr[index]; ++index) {
|
||||
if (addr[index] == ',') {
|
||||
addr[index] = '\0';
|
||||
US_A((addr = strndup(xff, 1024)) != NULL);
|
||||
for (uint i = 0; addr[i]; ++i) {
|
||||
if (addr[i] == ',') {
|
||||
addr[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,8 +32,8 @@
|
||||
|
||||
evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode);
|
||||
|
||||
const char *us_evhttp_get_header(struct evhttp_request *request, const char *key);
|
||||
char *us_evhttp_get_hostport(struct evhttp_request *request);
|
||||
const char *us_evhttp_get_header(struct evhttp_request *req, const char *key);
|
||||
char *us_evhttp_get_hostport(struct evhttp_request *req);
|
||||
|
||||
bool us_evkeyvalq_get_true(struct evkeyvalq *params, const char *key);
|
||||
char *us_evkeyvalq_get_string(struct evkeyvalq *params, const char *key);
|
||||
|
||||
@ -28,7 +28,6 @@
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
@ -42,25 +41,39 @@
|
||||
|
||||
|
||||
static us_m2m_encoder_s *_m2m_encoder_init(
|
||||
const char *name, const char *path, uint output_format,
|
||||
uint bitrate, uint gop, uint quality, bool allow_dma, bool boost);
|
||||
const char *name,
|
||||
const char *path,
|
||||
uint out_format,
|
||||
uint bitrate,
|
||||
uint gop,
|
||||
uint quality,
|
||||
bool allow_dma,
|
||||
bool boost);
|
||||
|
||||
static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame);
|
||||
|
||||
static int _m2m_encoder_init_buffers(
|
||||
us_m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
us_m2m_buffer_s **bufs_ptr, uint *n_bufs_ptr, bool dma);
|
||||
us_m2m_encoder_s *enc,
|
||||
const char *name,
|
||||
enum v4l2_buf_type type,
|
||||
us_m2m_buffer_s **bufs_ptr,
|
||||
uint *n_bufs_ptr,
|
||||
bool dma);
|
||||
|
||||
static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc);
|
||||
|
||||
static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *src, us_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 _LOG_ERROR(x_msg, ...) US_LOG_ERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
|
||||
|
||||
us_m2m_encoder_s *us_m2m_h264_encoder_init(const char *name, const char *path, uint bitrate, uint gop, bool boost) {
|
||||
@ -75,7 +88,7 @@ us_m2m_encoder_s *us_m2m_mjpeg_encoder_init(const char *name, const char *path,
|
||||
double bitrate = log10(quality) * (b_max - b_min) / 2 + b_min;
|
||||
bitrate = step * round(bitrate / step);
|
||||
bitrate *= 1000; // From Kbps
|
||||
assert(bitrate > 0);
|
||||
US_A(bitrate > 0);
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_MJPEG, bitrate, 0, 0, true, false);
|
||||
}
|
||||
|
||||
@ -95,8 +108,8 @@ void us_m2m_encoder_destroy(us_m2m_encoder_s *enc) {
|
||||
int us_m2m_encoder_compress(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key) {
|
||||
us_m2m_encoder_runtime_s *const run = enc->run;
|
||||
|
||||
uint dest_format = enc->output_format;
|
||||
switch (enc->output_format) {
|
||||
uint dest_format = enc->out_format;
|
||||
switch (enc->out_format) {
|
||||
case V4L2_PIX_FMT_JPEG:
|
||||
force_key = false;
|
||||
// fall through
|
||||
@ -138,9 +151,15 @@ int us_m2m_encoder_compress(us_m2m_encoder_s *enc, const us_frame_s *src, us_fra
|
||||
}
|
||||
|
||||
static us_m2m_encoder_s *_m2m_encoder_init(
|
||||
const char *name, const char *path, uint output_format,
|
||||
uint bitrate, uint gop, uint quality, bool allow_dma, bool boost) {
|
||||
|
||||
const char *name,
|
||||
const char *path,
|
||||
uint out_format,
|
||||
uint bitrate,
|
||||
uint gop,
|
||||
uint quality,
|
||||
bool allow_dma,
|
||||
bool boost
|
||||
) {
|
||||
US_LOG_INFO("%s: Initializing encoder ...", name);
|
||||
|
||||
us_m2m_encoder_runtime_s *run;
|
||||
@ -152,11 +171,11 @@ static us_m2m_encoder_s *_m2m_encoder_init(
|
||||
US_CALLOC(enc, 1);
|
||||
enc->name = us_strdup(name);
|
||||
if (path == NULL) {
|
||||
enc->path = us_strdup(output_format == V4L2_PIX_FMT_JPEG ? "/dev/video31" : "/dev/video11");
|
||||
enc->path = us_strdup(out_format == V4L2_PIX_FMT_JPEG ? "/dev/video31" : "/dev/video11");
|
||||
} else {
|
||||
enc->path = us_strdup(path);
|
||||
}
|
||||
enc->output_format = output_format;
|
||||
enc->out_format = out_format;
|
||||
enc->bitrate = bitrate;
|
||||
enc->gop = gop;
|
||||
enc->quality = quality;
|
||||
@ -180,7 +199,7 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
if (
|
||||
run->p_width == frame->width
|
||||
&& run->p_height == frame->height
|
||||
&& run->p_input_format == frame->format
|
||||
&& run->p_in_format == frame->format
|
||||
&& run->p_stride == frame->stride
|
||||
&& run->p_dma == dma
|
||||
) {
|
||||
@ -189,18 +208,18 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
|
||||
_LOG_INFO("Configuring encoder: DMA=%d ...", dma);
|
||||
|
||||
_LOG_DEBUG("Encoder changes: width=%u->%u, height=%u->%u, input_format=%u->%u, stride=%u->%u, dma=%u->%u",
|
||||
run->p_width, frame->width,
|
||||
run->p_height, frame->height,
|
||||
run->p_input_format, frame->format,
|
||||
run->p_stride, frame->stride,
|
||||
run->p_dma, dma);
|
||||
_LOG_DEBUG("Encoder changes: width=%u->%u, height=%u->%u, in_format=%u->%u, stride=%u->%u, dma=%u->%u",
|
||||
run->p_width, frame->width,
|
||||
run->p_height, frame->height,
|
||||
run->p_in_format, frame->format,
|
||||
run->p_stride, frame->stride,
|
||||
run->p_dma, dma);
|
||||
|
||||
_m2m_encoder_cleanup(enc);
|
||||
|
||||
run->p_width = frame->width;
|
||||
run->p_height = frame->height;
|
||||
run->p_input_format = frame->format;
|
||||
run->p_in_format = frame->format;
|
||||
run->p_stride = frame->stride;
|
||||
run->p_dma = dma;
|
||||
|
||||
@ -218,7 +237,7 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
_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) {
|
||||
if (enc->out_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);
|
||||
@ -234,9 +253,9 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER, 1);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_MIN_QP, 16);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_MAX_QP, 32);
|
||||
} else if (enc->output_format == V4L2_PIX_FMT_MJPEG) {
|
||||
} else if (enc->out_format == V4L2_PIX_FMT_MJPEG) {
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate);
|
||||
} else if (enc->output_format == V4L2_PIX_FMT_JPEG) {
|
||||
} else if (enc->out_format == V4L2_PIX_FMT_JPEG) {
|
||||
SET_OPTION(V4L2_CID_JPEG_COMPRESSION_QUALITY, enc->quality);
|
||||
}
|
||||
# undef SET_OPTION
|
||||
@ -246,7 +265,7 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
fmt.fmt.pix_mp.width = run->p_width;
|
||||
fmt.fmt.pix_mp.height = run->p_height;
|
||||
fmt.fmt.pix_mp.pixelformat = run->p_input_format;
|
||||
fmt.fmt.pix_mp.pixelformat = run->p_in_format;
|
||||
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
|
||||
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_JPEG; // FIXME: Wrong colors
|
||||
fmt.fmt.pix_mp.num_planes = 1;
|
||||
@ -260,22 +279,22 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
fmt.fmt.pix_mp.width = run->p_width;
|
||||
fmt.fmt.pix_mp.height = run->p_height;
|
||||
fmt.fmt.pix_mp.pixelformat = enc->output_format;
|
||||
fmt.fmt.pix_mp.pixelformat = enc->out_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;
|
||||
if (enc->output_format == V4L2_PIX_FMT_H264) {
|
||||
if (enc->out_format == V4L2_PIX_FMT_H264) {
|
||||
// https://github.com/pikvm/ustreamer/issues/169
|
||||
// https://github.com/raspberrypi/linux/pull/5232
|
||||
fmt.fmt.pix_mp.plane_fmt[0].sizeimage = (1024 + 512) << 10; // 1.5Mb
|
||||
}
|
||||
_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) {
|
||||
if (fmt.fmt.pix_mp.pixelformat != enc->out_format) {
|
||||
char fourcc_str[8];
|
||||
_LOG_ERROR("The OUTPUT format can't be configured as %s",
|
||||
us_fourcc_to_string(enc->output_format, fourcc_str, 8));
|
||||
us_fourcc_to_string(enc->out_format, fourcc_str, 8));
|
||||
_LOG_ERROR("In case of Raspberry Pi, try to append 'start_x=1' to /boot/config.txt");
|
||||
goto error;
|
||||
}
|
||||
@ -283,7 +302,7 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
|
||||
if (
|
||||
(run->p_width * run->p_height <= 1280 * 720)
|
||||
|| ((enc->output_format == V4L2_PIX_FMT_H264) && enc->boost)
|
||||
|| ((enc->out_format == V4L2_PIX_FMT_H264) && enc->boost)
|
||||
) {
|
||||
// H264 требует каких-то лимитов. Больше 30 не поддерживается, а при 0
|
||||
// через какое-то время начинает производить некорректные фреймы.
|
||||
@ -306,14 +325,22 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
}
|
||||
|
||||
if (_m2m_encoder_init_buffers(
|
||||
enc, (dma ? "INPUT-DMA" : "INPUT"), V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
|
||||
&run->input_bufs, &run->n_input_bufs, dma
|
||||
enc,
|
||||
(dma ? "INPUT-DMA" : "INPUT"),
|
||||
V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
|
||||
&run->in_bufs,
|
||||
&run->n_in_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
|
||||
enc,
|
||||
"OUTPUT",
|
||||
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
|
||||
&run->out_bufs,
|
||||
&run->n_out_bufs,
|
||||
false
|
||||
) < 0) {
|
||||
goto error;
|
||||
}
|
||||
@ -338,9 +365,13 @@ error:
|
||||
}
|
||||
|
||||
static int _m2m_encoder_init_buffers(
|
||||
us_m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
us_m2m_buffer_s **bufs_ptr, uint *n_bufs_ptr, bool dma) {
|
||||
|
||||
us_m2m_encoder_s *enc,
|
||||
const char *name,
|
||||
enum v4l2_buf_type type,
|
||||
us_m2m_buffer_s **bufs_ptr,
|
||||
uint *n_bufs_ptr,
|
||||
bool dma
|
||||
) {
|
||||
us_m2m_encoder_runtime_s *const run = enc->run;
|
||||
|
||||
_LOG_DEBUG("Initializing %s buffers ...", name);
|
||||
@ -385,7 +416,7 @@ static int _m2m_encoder_init_buffers(
|
||||
_LOG_PERROR("Can't map %s buffer=%u", name, *n_bufs_ptr);
|
||||
goto error;
|
||||
}
|
||||
assert((*bufs_ptr)[*n_bufs_ptr].data != NULL);
|
||||
US_A((*bufs_ptr)[*n_bufs_ptr].data != NULL);
|
||||
(*bufs_ptr)[*n_bufs_ptr].allocated = plane.length;
|
||||
|
||||
_LOG_DEBUG("Queuing %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
@ -420,11 +451,11 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
|
||||
# define DELETE_BUFFERS(x_name, x_target) { \
|
||||
if (run->x_target##_bufs != NULL) { \
|
||||
say = true; \
|
||||
for (uint m_index = 0; m_index < run->n_##x_target##_bufs; ++m_index) { \
|
||||
us_m2m_buffer_s *m_buf = &run->x_target##_bufs[m_index]; \
|
||||
for (uint m_i = 0; m_i < run->n_##x_target##_bufs; ++m_i) { \
|
||||
us_m2m_buffer_s *m_buf = &run->x_target##_bufs[m_i]; \
|
||||
if (m_buf->allocated > 0 && m_buf->data != NULL) { \
|
||||
if (munmap(m_buf->data, m_buf->allocated) < 0) { \
|
||||
_LOG_PERROR("Can't unmap %s buffer=%u", #x_name, m_index); \
|
||||
_LOG_PERROR("Can't unmap %s buffer=%u", #x_name, m_i); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
@ -432,8 +463,8 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
|
||||
} \
|
||||
run->n_##x_target##_bufs = 0; \
|
||||
}
|
||||
DELETE_BUFFERS("OUTPUT", output);
|
||||
DELETE_BUFFERS("INPUT", input);
|
||||
DELETE_BUFFERS("OUTPUT", out);
|
||||
DELETE_BUFFERS("INPUT", in);
|
||||
# undef DELETE_BUFFERS
|
||||
|
||||
if (run->fd >= 0) {
|
||||
@ -452,10 +483,15 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
|
||||
}
|
||||
}
|
||||
|
||||
static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *src, us_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
|
||||
) {
|
||||
us_m2m_encoder_runtime_s *const run = enc->run;
|
||||
|
||||
assert(run->ready);
|
||||
US_A(run->ready);
|
||||
|
||||
if (force_key) {
|
||||
struct v4l2_control ctl = {0};
|
||||
@ -465,28 +501,28 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
|
||||
_E_XIOCTL(VIDIOC_S_CTRL, &ctl, "Can't force keyframe");
|
||||
}
|
||||
|
||||
struct v4l2_buffer input_buf = {0};
|
||||
struct v4l2_plane input_plane = {0};
|
||||
input_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
input_buf.length = 1;
|
||||
input_buf.m.planes = &input_plane;
|
||||
struct v4l2_buffer in_buf = {0};
|
||||
struct v4l2_plane in_plane = {0};
|
||||
in_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
in_buf.length = 1;
|
||||
in_buf.m.planes = &in_plane;
|
||||
|
||||
if (run->p_dma) {
|
||||
input_buf.index = 0;
|
||||
input_buf.memory = V4L2_MEMORY_DMABUF;
|
||||
input_buf.field = V4L2_FIELD_NONE;
|
||||
input_plane.m.fd = src->dma_fd;
|
||||
_LOG_DEBUG("Using INPUT-DMA buffer=%u", input_buf.index);
|
||||
in_buf.index = 0;
|
||||
in_buf.memory = V4L2_MEMORY_DMABUF;
|
||||
in_buf.field = V4L2_FIELD_NONE;
|
||||
in_plane.m.fd = src->dma_fd;
|
||||
_LOG_DEBUG("Using INPUT-DMA buffer=%u", in_buf.index);
|
||||
} else {
|
||||
input_buf.memory = V4L2_MEMORY_MMAP;
|
||||
in_buf.memory = V4L2_MEMORY_MMAP;
|
||||
_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_XIOCTL(VIDIOC_DQBUF, &in_buf, "Can't grab INPUT buffer");
|
||||
if (in_buf.index >= run->n_in_bufs) {
|
||||
_LOG_ERROR("V4L2 error: grabbed invalid INPUT: buffer=%u, n_bufs=%u",
|
||||
input_buf.index, run->n_input_bufs);
|
||||
in_buf.index, run->n_in_bufs);
|
||||
goto error;
|
||||
}
|
||||
_LOG_DEBUG("Grabbed INPUT buffer=%u", input_buf.index);
|
||||
_LOG_DEBUG("Grabbed INPUT buffer=%u", in_buf.index);
|
||||
}
|
||||
|
||||
const u64 now_ts = us_get_now_monotonic_u64();
|
||||
@ -495,21 +531,21 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
|
||||
.tv_usec = now_ts % 1000000,
|
||||
};
|
||||
|
||||
input_buf.timestamp.tv_sec = ts.tv_sec;
|
||||
input_buf.timestamp.tv_usec = ts.tv_usec;
|
||||
input_plane.bytesused = src->used;
|
||||
input_plane.length = src->used;
|
||||
in_buf.timestamp.tv_sec = ts.tv_sec;
|
||||
in_buf.timestamp.tv_usec = ts.tv_usec;
|
||||
in_plane.bytesused = src->used;
|
||||
in_plane.length = src->used;
|
||||
if (!run->p_dma) {
|
||||
memcpy(run->input_bufs[input_buf.index].data, src->data, src->used);
|
||||
memcpy(run->in_bufs[in_buf.index].data, src->data, src->used);
|
||||
}
|
||||
|
||||
const char *input_name = (run->p_dma ? "INPUT-DMA" : "INPUT");
|
||||
const char *in_name = (run->p_dma ? "INPUT-DMA" : "INPUT");
|
||||
|
||||
_LOG_DEBUG("Sending%s %s buffer ...", (!run->p_dma ? " (releasing)" : ""), input_name);
|
||||
_E_XIOCTL(VIDIOC_QBUF, &input_buf, "Can't send %s buffer", input_name);
|
||||
_LOG_DEBUG("Sending%s %s buffer ...", (!run->p_dma ? " (releasing)" : ""), in_name);
|
||||
_E_XIOCTL(VIDIOC_QBUF, &in_buf, "Can't send %s buffer", in_name);
|
||||
|
||||
// Для не-DMA отправка буфера по факту являтся освобождением этого буфера
|
||||
bool input_released = !run->p_dma;
|
||||
bool in_released = !run->p_dma;
|
||||
|
||||
// https://github.com/pikvm/ustreamer/issues/253
|
||||
// За секунду точно должно закодироваться.
|
||||
@ -529,37 +565,37 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
|
||||
}
|
||||
|
||||
if (enc_poll.revents & POLLIN) {
|
||||
if (!input_released) {
|
||||
_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;
|
||||
if (!in_released) {
|
||||
_LOG_DEBUG("Releasing %s buffer=%u ...", in_name, in_buf.index);
|
||||
_E_XIOCTL(VIDIOC_DQBUF, &in_buf, "Can't release %s buffer=%u",
|
||||
in_name, in_buf.index);
|
||||
in_released = true;
|
||||
}
|
||||
|
||||
struct v4l2_buffer output_buf = {0};
|
||||
struct v4l2_plane output_plane = {0};
|
||||
output_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
output_buf.memory = V4L2_MEMORY_MMAP;
|
||||
output_buf.length = 1;
|
||||
output_buf.m.planes = &output_plane;
|
||||
struct v4l2_buffer out_buf = {0};
|
||||
struct v4l2_plane out_plane = {0};
|
||||
out_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
out_buf.memory = V4L2_MEMORY_MMAP;
|
||||
out_buf.length = 1;
|
||||
out_buf.m.planes = &out_plane;
|
||||
_LOG_DEBUG("Fetching OUTPUT buffer ...");
|
||||
_E_XIOCTL(VIDIOC_DQBUF, &output_buf, "Can't fetch OUTPUT buffer");
|
||||
_E_XIOCTL(VIDIOC_DQBUF, &out_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) {
|
||||
if (ts.tv_sec != out_buf.timestamp.tv_sec || ts.tv_usec != out_buf.timestamp.tv_usec) {
|
||||
// Енкодер первый раз может выдать буфер с мусором и нулевым таймстампом,
|
||||
// так что нужно убедиться, что мы читаем выходной буфер, соответствующий
|
||||
// входному (с тем же таймстампом).
|
||||
_LOG_DEBUG("Need to retry OUTPUT buffer due timestamp mismatch");
|
||||
} else {
|
||||
us_frame_set_data(dest, run->output_bufs[output_buf.index].data, output_plane.bytesused);
|
||||
dest->key = output_buf.flags & V4L2_BUF_FLAG_KEYFRAME;
|
||||
us_frame_set_data(dest, run->out_bufs[out_buf.index].data, out_plane.bytesused);
|
||||
dest->key = out_buf.flags & V4L2_BUF_FLAG_KEYFRAME;
|
||||
dest->gop = enc->gop;
|
||||
done = true;
|
||||
}
|
||||
|
||||
_LOG_DEBUG("Releasing OUTPUT buffer=%u ...", output_buf.index);
|
||||
_E_XIOCTL(VIDIOC_QBUF, &output_buf, "Can't release OUTPUT buffer=%u", output_buf.index);
|
||||
_LOG_DEBUG("Releasing OUTPUT buffer=%u ...", out_buf.index);
|
||||
_E_XIOCTL(VIDIOC_QBUF, &out_buf, "Can't release OUTPUT buffer=%u", out_buf.index);
|
||||
|
||||
if (done) {
|
||||
break;
|
||||
|
||||
@ -34,14 +34,14 @@ typedef struct {
|
||||
typedef struct {
|
||||
int fd;
|
||||
uint fps_limit;
|
||||
us_m2m_buffer_s *input_bufs;
|
||||
uint n_input_bufs;
|
||||
us_m2m_buffer_s *output_bufs;
|
||||
uint n_output_bufs;
|
||||
us_m2m_buffer_s *in_bufs;
|
||||
uint n_in_bufs;
|
||||
us_m2m_buffer_s *out_bufs;
|
||||
uint n_out_bufs;
|
||||
|
||||
uint p_width;
|
||||
uint p_height;
|
||||
uint p_input_format;
|
||||
uint p_in_format;
|
||||
uint p_stride;
|
||||
bool p_dma;
|
||||
|
||||
@ -53,7 +53,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
char *name;
|
||||
char *path;
|
||||
uint output_format;
|
||||
uint out_format;
|
||||
uint bitrate;
|
||||
uint gop;
|
||||
uint quality;
|
||||
|
||||
@ -46,10 +46,10 @@ static us_server_s *_g_server = NULL;
|
||||
|
||||
static void _block_thread_signals(void) {
|
||||
sigset_t mask;
|
||||
assert(!sigemptyset(&mask));
|
||||
assert(!sigaddset(&mask, SIGINT));
|
||||
assert(!sigaddset(&mask, SIGTERM));
|
||||
assert(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
|
||||
US_A(!sigemptyset(&mask));
|
||||
US_A(!sigaddset(&mask, SIGINT));
|
||||
US_A(!sigaddset(&mask, SIGTERM));
|
||||
US_A(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
|
||||
}
|
||||
|
||||
static void *_stream_loop_thread(void *arg) {
|
||||
@ -77,19 +77,19 @@ static void _signal_handler(int signum) {
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
assert(argc >= 0);
|
||||
US_A(argc >= 0);
|
||||
int exit_code = 0;
|
||||
|
||||
US_LOGGING_INIT;
|
||||
US_THREAD_RENAME("main");
|
||||
|
||||
us_options_s *options = us_options_init(argc, argv);
|
||||
us_options_s *opts = us_options_init(argc, argv);
|
||||
us_capture_s *cap = us_capture_init();
|
||||
us_encoder_s *enc = us_encoder_init();
|
||||
_g_stream = us_stream_init(cap, enc);
|
||||
_g_server = us_server_init(_g_stream);
|
||||
|
||||
if ((exit_code = options_parse(options, cap, enc, _g_stream, _g_server)) == 0) {
|
||||
if ((exit_code = us_options_parse(opts, cap, enc, _g_stream, _g_server)) == 0) {
|
||||
us_stream_update_blank(_g_stream, cap);
|
||||
# ifdef WITH_GPIO
|
||||
us_gpio_init();
|
||||
@ -120,7 +120,7 @@ int main(int argc, char *argv[]) {
|
||||
us_stream_destroy(_g_stream);
|
||||
us_encoder_destroy(enc);
|
||||
us_capture_destroy(cap);
|
||||
us_options_destroy(options);
|
||||
us_options_destroy(opts);
|
||||
|
||||
if (exit_code == 0) {
|
||||
US_LOG_INFO("Bye-bye");
|
||||
|
||||
@ -22,6 +22,35 @@
|
||||
|
||||
#include "options.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/const.h"
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/process.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/memsink.h"
|
||||
#include "../libs/options.h"
|
||||
#include "../libs/capture.h"
|
||||
#ifdef WITH_V4P
|
||||
# include "../libs/drm/drm.h"
|
||||
#endif
|
||||
|
||||
#include "encoder.h"
|
||||
#include "stream.h"
|
||||
#include "http/server.h"
|
||||
#ifdef WITH_GPIO
|
||||
# include "gpio/gpio.h"
|
||||
#endif
|
||||
|
||||
|
||||
enum _US_OPT_VALUES {
|
||||
_O_DEVICE = 'd',
|
||||
@ -252,53 +281,66 @@ static const struct option _LONG_OPTS[] = {
|
||||
};
|
||||
|
||||
|
||||
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited);
|
||||
static int _parse_resolution(const char *str, uint *width, uint *height, bool limited);
|
||||
static int _check_instance_id(const char *str);
|
||||
|
||||
static void _features(void);
|
||||
static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server);
|
||||
|
||||
static void _help(
|
||||
FILE *fp,
|
||||
const us_capture_s *cap,
|
||||
const us_encoder_s *enc,
|
||||
const us_stream_s *stream,
|
||||
const us_server_s *server);
|
||||
|
||||
|
||||
us_options_s *us_options_init(unsigned argc, char *argv[]) {
|
||||
us_options_s *options;
|
||||
US_CALLOC(options, 1);
|
||||
options->argc = argc;
|
||||
options->argv = argv;
|
||||
us_options_s *us_options_init(uint argc, char *argv[]) {
|
||||
us_options_s *opts;
|
||||
US_CALLOC(opts, 1);
|
||||
opts->argc = argc;
|
||||
opts->argv = argv;
|
||||
|
||||
US_CALLOC(options->argv_copy, argc);
|
||||
for (unsigned index = 0; index < argc; ++index) {
|
||||
options->argv_copy[index] = us_strdup(argv[index]);
|
||||
US_CALLOC(opts->argv_copy, argc + 1);
|
||||
for (uint i = 0; i < argc; ++i) {
|
||||
opts->argv_copy[i] = us_strdup(argv[i]);
|
||||
}
|
||||
return options;
|
||||
opts->argv_copy[argc] = NULL;
|
||||
return opts;
|
||||
}
|
||||
|
||||
void us_options_destroy(us_options_s *options) {
|
||||
US_DELETE(options->jpeg_sink, us_memsink_destroy);
|
||||
US_DELETE(options->raw_sink, us_memsink_destroy);
|
||||
US_DELETE(options->h264_sink, us_memsink_destroy);
|
||||
void us_options_destroy(us_options_s *opts) {
|
||||
US_DELETE(opts->jpeg_sink, us_memsink_destroy);
|
||||
US_DELETE(opts->raw_sink, us_memsink_destroy);
|
||||
US_DELETE(opts->h264_sink, us_memsink_destroy);
|
||||
# ifdef WITH_V4P
|
||||
US_DELETE(options->drm, us_drm_destroy);
|
||||
US_DELETE(opts->drm, us_drm_destroy);
|
||||
# endif
|
||||
|
||||
for (unsigned index = 0; index < options->argc; ++index) {
|
||||
free(options->argv_copy[index]);
|
||||
for (uint i = 0; i < opts->argc; ++i) {
|
||||
free(opts->argv_copy[i]);
|
||||
}
|
||||
free(options->argv_copy);
|
||||
free(opts->argv_copy);
|
||||
|
||||
free(options);
|
||||
free(opts);
|
||||
}
|
||||
|
||||
|
||||
int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, us_stream_s *stream, us_server_s *server) {
|
||||
int us_options_parse(
|
||||
us_options_s *opts,
|
||||
us_capture_s *cap,
|
||||
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(x_name, x_dest, x_min, x_max, x_base) { \
|
||||
errno = 0; char *m_end = NULL; const long long m_tmp = strtoll(optarg, &m_end, x_base); \
|
||||
errno = 0; char *m_end = NULL; const sll 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); \
|
||||
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", x_name, optarg, (sll)x_min, (sll)x_max); \
|
||||
return -1; \
|
||||
} \
|
||||
x_dest = m_tmp; \
|
||||
@ -317,7 +359,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
|
||||
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"); \
|
||||
default: US_RAISE("Unknown error"); \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
@ -362,8 +404,8 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
|
||||
const 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;
|
||||
uint x_prefix##_client_ttl = 10; \
|
||||
uint x_prefix##_timeout = 1;
|
||||
ADD_SINK(jpeg_sink);
|
||||
ADD_SINK(raw_sink);
|
||||
ADD_SINK(h264_sink);
|
||||
@ -376,7 +418,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
|
||||
char 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;) {
|
||||
for (int ch; (ch = getopt_long(opts->argc, opts->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0;) {
|
||||
switch (ch) {
|
||||
case _O_DEVICE: OPT_SET(cap->path, optarg);
|
||||
case _O_INPUT: OPT_NUMBER("--input", cap->input, 0, 128, 0);
|
||||
@ -475,8 +517,8 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
|
||||
|
||||
# ifdef WITH_V4P
|
||||
case _O_V4P:
|
||||
options->drm = us_drm_init();
|
||||
stream->drm = options->drm;
|
||||
opts->drm = us_drm_init();
|
||||
stream->drm = opts->drm;
|
||||
break;
|
||||
# endif
|
||||
|
||||
@ -522,7 +564,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
|
||||
|
||||
# define ADD_SINK(x_label, x_prefix) { \
|
||||
if (x_prefix##_name && x_prefix##_name[0] != '\0') { \
|
||||
options->x_prefix = us_memsink_init_opened( \
|
||||
opts->x_prefix = us_memsink_init_opened( \
|
||||
x_label, \
|
||||
x_prefix##_name, \
|
||||
true, \
|
||||
@ -532,7 +574,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
|
||||
x_prefix##_timeout \
|
||||
); \
|
||||
} \
|
||||
stream->x_prefix = options->x_prefix; \
|
||||
stream->x_prefix = opts->x_prefix; \
|
||||
}
|
||||
ADD_SINK("JPEG", jpeg_sink);
|
||||
ADD_SINK("RAW", raw_sink);
|
||||
@ -541,7 +583,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
|
||||
|
||||
# ifdef WITH_SETPROCTITLE
|
||||
if (process_name_prefix != NULL) {
|
||||
us_process_set_name_prefix(options->argc, options->argv, process_name_prefix);
|
||||
us_process_set_name_prefix(opts->argc, opts->argv, process_name_prefix);
|
||||
}
|
||||
# endif
|
||||
|
||||
@ -555,9 +597,9 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited) {
|
||||
unsigned tmp_width;
|
||||
unsigned tmp_height;
|
||||
static int _parse_resolution(const char *str, uint *width, uint *height, bool limited) {
|
||||
uint tmp_width;
|
||||
uint tmp_height;
|
||||
if (sscanf(str, "%ux%u", &tmp_width, &tmp_height) != 2) {
|
||||
return -1;
|
||||
}
|
||||
@ -636,7 +678,13 @@ static void _features(void) {
|
||||
# endif
|
||||
}
|
||||
|
||||
static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server) {
|
||||
static void _help(
|
||||
FILE *fp,
|
||||
const us_capture_s *cap,
|
||||
const us_encoder_s *enc,
|
||||
const us_stream_s *stream,
|
||||
const us_server_s *server
|
||||
) {
|
||||
# define SAY(x_msg, ...) fprintf(fp, x_msg "\n", ##__VA_ARGS__)
|
||||
SAY("\nuStreamer - Lightweight and fast MJPEG-HTTP streamer");
|
||||
SAY("═══════════════════════════════════════════════════");
|
||||
@ -657,7 +705,7 @@ static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, co
|
||||
SAY(" Changing of this parameter may increase the performance. Or not.");
|
||||
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(" -z|--min-frame-size <N> ───────────── Drop frames smaller than this limit. Useful if the device");
|
||||
SAY(" produces small-sized garbage frames. Default: %zu bytes.\n", cap->min_frame_size);
|
||||
SAY(" -T|--allow-truncated-frames ───────── Allows to handle truncated frames. Useful if the device");
|
||||
SAY(" produces incorrect but still acceptable frames. Default: disabled.\n");
|
||||
@ -665,7 +713,7 @@ static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, co
|
||||
SAY(" -t|--dv-timings ───────────────────── Enable DV-timings querying and events processing");
|
||||
SAY(" to automatic resolution change. Default: disabled.\n");
|
||||
SAY(" -b|--buffers <N> ──────────────────── The number of buffers to receive data from the device.");
|
||||
SAY(" Each buffer may processed using an independent thread.");
|
||||
SAY(" Each buffer may be processed using an independent thread.");
|
||||
SAY(" Default: %u (the number of CPU cores (but not more than 4) + 1).\n", cap->n_bufs);
|
||||
SAY(" -w|--workers <N> ──────────────────── The number of worker threads but not more than buffers.");
|
||||
SAY(" Default: %u (the number of CPU cores (but not more than 4)).\n", enc->n_workers);
|
||||
|
||||
@ -22,23 +22,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "../libs/const.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/process.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/memsink.h"
|
||||
#include "../libs/options.h"
|
||||
#include "../libs/capture.h"
|
||||
#ifdef WITH_V4P
|
||||
# include "../libs/drm/drm.h"
|
||||
@ -47,13 +32,10 @@
|
||||
#include "encoder.h"
|
||||
#include "stream.h"
|
||||
#include "http/server.h"
|
||||
#ifdef WITH_GPIO
|
||||
# include "gpio/gpio.h"
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned argc;
|
||||
uint argc;
|
||||
char **argv;
|
||||
char **argv_copy;
|
||||
us_memsink_s *jpeg_sink;
|
||||
@ -65,7 +47,12 @@ typedef struct {
|
||||
} us_options_s;
|
||||
|
||||
|
||||
us_options_s *us_options_init(unsigned argc, char *argv[]);
|
||||
us_options_s *us_options_init(uint argc, char *argv[]);
|
||||
void us_options_destroy(us_options_s *options);
|
||||
|
||||
int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, us_stream_s *stream, us_server_s *server);
|
||||
int us_options_parse(
|
||||
us_options_s *options,
|
||||
us_capture_s *cap,
|
||||
us_encoder_s *enc,
|
||||
us_stream_s *stream,
|
||||
us_server_s *server);
|
||||
|
||||
@ -27,7 +27,6 @@
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <pthread.h>
|
||||
@ -62,14 +61,14 @@
|
||||
typedef struct {
|
||||
pthread_t tid;
|
||||
us_capture_s *cap;
|
||||
us_queue_s *queue;
|
||||
us_queue_s *q;
|
||||
pthread_mutex_t *mutex;
|
||||
atomic_bool *stop;
|
||||
} _releaser_context_s;
|
||||
|
||||
typedef struct {
|
||||
pthread_t tid;
|
||||
us_queue_s *queue;
|
||||
us_queue_s *q;
|
||||
us_stream_s *stream;
|
||||
atomic_bool *stop;
|
||||
} _worker_context_s;
|
||||
@ -83,7 +82,7 @@ static void *_h264_thread(void *v_ctx);
|
||||
static void *_drm_thread(void *v_ctx);
|
||||
#endif
|
||||
|
||||
static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *queue);
|
||||
static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *q);
|
||||
|
||||
static bool _stream_has_jpeg_clients_cached(us_stream_s *stream);
|
||||
static bool _stream_has_any_clients_cached(us_stream_s *stream);
|
||||
@ -108,7 +107,7 @@ us_stream_s *us_stream_init(us_capture_s *cap, us_encoder_s *enc) {
|
||||
US_RING_INIT_WITH_ITEMS(http->jpeg_ring, 4, us_frame_init);
|
||||
atomic_init(&http->has_clients, false);
|
||||
atomic_init(&http->snapshot_requested, 0);
|
||||
atomic_init(&http->last_request_ts, 0);
|
||||
atomic_init(&http->last_req_ts, 0);
|
||||
http->captured_fpsi = us_fpsi_init("STREAM-CAPTURED", true);
|
||||
|
||||
us_stream_runtime_s *run;
|
||||
@ -154,7 +153,7 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
us_stream_runtime_s *const run = stream->run;
|
||||
us_capture_s *const cap = stream->cap;
|
||||
|
||||
atomic_store(&run->http->last_request_ts, us_get_now_monotonic());
|
||||
atomic_store(&run->http->last_req_ts, us_get_now_monotonic());
|
||||
|
||||
if (stream->h264_sink != NULL) {
|
||||
run->h264_enc = us_m2m_h264_encoder_init(
|
||||
@ -177,10 +176,10 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
const uint n_releasers = cap->run->n_bufs;
|
||||
_releaser_context_s *releasers;
|
||||
US_CALLOC(releasers, n_releasers);
|
||||
for (uint index = 0; index < n_releasers; ++index) {
|
||||
_releaser_context_s *ctx = &releasers[index];
|
||||
for (uint i = 0; i < n_releasers; ++i) {
|
||||
_releaser_context_s *ctx = &releasers[i];
|
||||
ctx->cap = cap;
|
||||
ctx->queue = us_queue_init(1);
|
||||
ctx->q = us_queue_init(1);
|
||||
ctx->mutex = &release_mutex;
|
||||
ctx->stop = &threads_stop;
|
||||
US_THREAD_CREATE(ctx->tid, _releaser_thread, ctx);
|
||||
@ -190,7 +189,7 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
_worker_context_s *x_ctx = NULL; \
|
||||
if (x_cond) { \
|
||||
US_CALLOC(x_ctx, 1); \
|
||||
x_ctx->queue = us_queue_init(x_capacity); \
|
||||
x_ctx->q = us_queue_init(x_capacity); \
|
||||
x_ctx->stream = stream; \
|
||||
x_ctx->stop = &threads_stop; \
|
||||
US_THREAD_CREATE(x_ctx->tid, (x_thread), x_ctx); \
|
||||
@ -199,7 +198,7 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
CREATE_WORKER((stream->raw_sink != NULL), raw_ctx, _raw_thread, 2);
|
||||
CREATE_WORKER((stream->h264_sink != NULL), h264_ctx, _h264_thread, cap->run->n_bufs);
|
||||
# ifdef WITH_V4P
|
||||
CREATE_WORKER((stream->drm != NULL), drm_ctx, _drm_thread, cap->run->n_bufs); // cppcheck-suppress assertWithSideEffect
|
||||
CREATE_WORKER((stream->drm != NULL), drm_ctx, _drm_thread, cap->run->n_bufs);
|
||||
# endif
|
||||
# undef CREATE_WORKER
|
||||
|
||||
@ -222,7 +221,7 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
|
||||
# define QUEUE_HW(x_ctx) if (x_ctx != NULL) { \
|
||||
us_capture_hwbuf_incref(hw); \
|
||||
us_queue_put(x_ctx->queue, hw, 0); \
|
||||
us_queue_put(x_ctx->q, hw, 0); \
|
||||
}
|
||||
QUEUE_HW(jpeg_ctx);
|
||||
QUEUE_HW(raw_ctx);
|
||||
@ -231,7 +230,7 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
QUEUE_HW(drm_ctx);
|
||||
# endif
|
||||
# undef QUEUE_HW
|
||||
us_queue_put(releasers[hw->buf.index].queue, hw, 0); // Plan to release
|
||||
us_queue_put(releasers[hw->buf.index].q, hw, 0); // Plan to release
|
||||
|
||||
// Мы не обновляем здесь состояние синков, потому что это происходит внутри обслуживающих их потоков
|
||||
_stream_check_suicide(stream);
|
||||
@ -249,7 +248,7 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
|
||||
# define DELETE_WORKER(x_ctx) if (x_ctx != NULL) { \
|
||||
US_THREAD_JOIN(x_ctx->tid); \
|
||||
us_queue_destroy(x_ctx->queue); \
|
||||
us_queue_destroy(x_ctx->q); \
|
||||
free(x_ctx); \
|
||||
}
|
||||
# ifdef WITH_V4P
|
||||
@ -260,9 +259,9 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
DELETE_WORKER(jpeg_ctx);
|
||||
# undef DELETE_WORKER
|
||||
|
||||
for (uint index = 0; index < n_releasers; ++index) {
|
||||
US_THREAD_JOIN(releasers[index].tid);
|
||||
us_queue_destroy(releasers[index].queue);
|
||||
for (uint i = 0; i < n_releasers; ++i) {
|
||||
US_THREAD_JOIN(releasers[i].tid);
|
||||
us_queue_destroy(releasers[i].q);
|
||||
}
|
||||
free(releasers);
|
||||
US_MUTEX_DESTROY(release_mutex);
|
||||
@ -292,7 +291,7 @@ static void *_releaser_thread(void *v_ctx) {
|
||||
|
||||
while (!atomic_load(ctx->stop)) {
|
||||
us_capture_hwbuf_s *hw;
|
||||
if (us_queue_get(ctx->queue, (void**)&hw, 0.1) < 0) {
|
||||
if (us_queue_get(ctx->q, (void**)&hw, 0.1) < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -348,7 +347,7 @@ static void *_jpeg_thread(void *v_ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue);
|
||||
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->q);
|
||||
if (hw == NULL) {
|
||||
continue;
|
||||
}
|
||||
@ -399,7 +398,7 @@ static void *_raw_thread(void *v_ctx) {
|
||||
_worker_context_s *ctx = v_ctx;
|
||||
|
||||
while (!atomic_load(ctx->stop)) {
|
||||
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue);
|
||||
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->q);
|
||||
if (hw == NULL) {
|
||||
continue;
|
||||
}
|
||||
@ -423,7 +422,7 @@ static void *_h264_thread(void *v_ctx) {
|
||||
uint step = 1;
|
||||
|
||||
while (!atomic_load(ctx->stop)) {
|
||||
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue);
|
||||
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->q);
|
||||
if (hw == NULL) {
|
||||
continue;
|
||||
}
|
||||
@ -472,7 +471,7 @@ static void *_drm_thread(void *v_ctx) {
|
||||
# define SLOWDOWN { \
|
||||
const ldf m_next_ts = us_get_now_monotonic() + 1; \
|
||||
while (!atomic_load(ctx->stop) && us_get_now_monotonic() < m_next_ts) { \
|
||||
us_capture_hwbuf_s *m_pass_hw = _get_latest_hw(ctx->queue); \
|
||||
us_capture_hwbuf_s *m_pass_hw = _get_latest_hw(ctx->q); \
|
||||
if (m_pass_hw != NULL) { \
|
||||
us_capture_hwbuf_decref(m_pass_hw); \
|
||||
} \
|
||||
@ -485,7 +484,7 @@ static void *_drm_thread(void *v_ctx) {
|
||||
CHECK(us_drm_wait_for_vsync(stream->drm));
|
||||
US_DELETE(prev_hw, us_capture_hwbuf_decref);
|
||||
|
||||
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue);
|
||||
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->q);
|
||||
if (hw == NULL) {
|
||||
continue;
|
||||
}
|
||||
@ -521,14 +520,14 @@ static void *_drm_thread(void *v_ctx) {
|
||||
}
|
||||
#endif
|
||||
|
||||
static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *queue) {
|
||||
static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *q) {
|
||||
us_capture_hwbuf_s *hw;
|
||||
if (us_queue_get(queue, (void**)&hw, 0.1) < 0) {
|
||||
if (us_queue_get(q, (void**)&hw, 0.1) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
while (!us_queue_is_empty(queue)) { // Берем только самый свежий кадр
|
||||
while (!us_queue_is_empty(q)) { // Берем только самый свежий кадр
|
||||
us_capture_hwbuf_decref(hw);
|
||||
assert(!us_queue_get(queue, (void**)&hw, 0));
|
||||
US_A(!us_queue_get(q, (void**)&hw, 0));
|
||||
}
|
||||
return hw;
|
||||
}
|
||||
@ -647,11 +646,11 @@ static int _stream_init_loop(us_stream_s *stream) {
|
||||
US_LOG_INFO("Device error, exiting ...");
|
||||
us_process_suicide();
|
||||
}
|
||||
for (uint count = 0; count < stream->error_delay * 10; ++count) {
|
||||
for (uint i = 0; i < stream->error_delay * 10; ++i) {
|
||||
if (atomic_load(&run->stop)) {
|
||||
break;
|
||||
}
|
||||
if (count % 10 == 0) {
|
||||
if (i % 10 == 0) {
|
||||
// Каждую секунду повторяем blank
|
||||
uint width = stream->cap->run->width;
|
||||
uint height = stream->cap->run->height;
|
||||
@ -716,12 +715,14 @@ close:
|
||||
|
||||
static void _stream_expose_jpeg(us_stream_s *stream, const us_frame_s *frame) {
|
||||
us_stream_runtime_s *const run = stream->run;
|
||||
|
||||
int ri;
|
||||
while ((ri = us_ring_producer_acquire(run->http->jpeg_ring, 0)) < 0) {
|
||||
if (atomic_load(&run->stop)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
us_frame_s *const dest = run->http->jpeg_ring->items[ri];
|
||||
us_frame_copy(frame, dest);
|
||||
us_ring_producer_release(run->http->jpeg_ring, ri);
|
||||
@ -741,6 +742,7 @@ static void _stream_encode_expose_h264(us_stream_s *stream, const us_frame_s *fr
|
||||
if (stream->h264_sink == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
us_stream_runtime_s *run = stream->run;
|
||||
|
||||
us_fpsi_meta_s meta = {.online = false};
|
||||
@ -767,16 +769,16 @@ static void _stream_check_suicide(us_stream_s *stream) {
|
||||
if (stream->exit_on_no_clients == 0) {
|
||||
return;
|
||||
}
|
||||
us_stream_runtime_s *const run = stream->run;
|
||||
|
||||
atomic_ullong *last_req_ts = &stream->run->http->last_req_ts;
|
||||
const ldf now_ts = us_get_now_monotonic();
|
||||
const ull http_last_request_ts = atomic_load(&run->http->last_request_ts); // Seconds
|
||||
|
||||
if (_stream_has_any_clients_cached(stream)) {
|
||||
atomic_store(&run->http->last_request_ts, now_ts);
|
||||
} else if (http_last_request_ts + stream->exit_on_no_clients < now_ts) {
|
||||
atomic_store(last_req_ts, now_ts);
|
||||
} else if (atomic_load(last_req_ts) + stream->exit_on_no_clients < now_ts) {
|
||||
US_LOG_INFO("No requests or HTTP/sink clients found in last %u seconds, exiting ...",
|
||||
stream->exit_on_no_clients);
|
||||
atomic_store(last_req_ts, now_ts); // Prevent a signal spam
|
||||
us_process_suicide();
|
||||
atomic_store(&run->http->last_request_ts, now_ts);
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ typedef struct {
|
||||
us_ring_s *jpeg_ring;
|
||||
atomic_bool has_clients;
|
||||
atomic_uint snapshot_requested;
|
||||
atomic_ullong last_request_ts; // Seconds
|
||||
atomic_ullong last_req_ts; // Seconds
|
||||
us_fpsi_s *captured_fpsi;
|
||||
} us_stream_http_s;
|
||||
|
||||
|
||||
@ -37,11 +37,14 @@ static void *_worker_thread(void *v_worker);
|
||||
|
||||
|
||||
us_workers_pool_s *us_workers_pool_init(
|
||||
const char *name, const char *wr_prefix, uint n_workers,
|
||||
us_workers_pool_job_init_f job_init, void *job_init_arg,
|
||||
const char *name,
|
||||
const char *wr_prefix,
|
||||
uint n_workers,
|
||||
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) {
|
||||
|
||||
us_workers_pool_run_job_f run_job
|
||||
) {
|
||||
US_LOG_INFO("Creating pool %s with %u workers ...", name, n_workers);
|
||||
|
||||
us_workers_pool_s *pool;
|
||||
@ -57,12 +60,12 @@ us_workers_pool_s *us_workers_pool_init(
|
||||
US_MUTEX_INIT(pool->free_workers_mutex);
|
||||
US_COND_INIT(pool->free_workers_cond);
|
||||
|
||||
for (uint index = 0; index < pool->n_workers; ++index) {
|
||||
for (uint i = 0; i < pool->n_workers; ++i) {
|
||||
us_worker_s *wr;
|
||||
US_CALLOC(wr, 1);
|
||||
|
||||
wr->number = index;
|
||||
US_ASPRINTF(wr->name, "%s-%u", wr_prefix, index);
|
||||
wr->number = i;
|
||||
US_ASPRINTF(wr->name, "%s-%u", wr_prefix, i);
|
||||
|
||||
US_MUTEX_INIT(wr->has_job_mutex);
|
||||
atomic_init(&wr->has_job, false);
|
||||
@ -112,11 +115,14 @@ us_worker_s *us_workers_pool_wait(us_workers_pool_s *pool) {
|
||||
|
||||
us_worker_s *found = NULL;
|
||||
US_LIST_ITERATE(pool->workers, wr, { // cppcheck-suppress constStatement
|
||||
if (!atomic_load(&wr->has_job) && (found == NULL || found->job_start_ts <= wr->job_start_ts)) {
|
||||
if (
|
||||
!atomic_load(&wr->has_job)
|
||||
&& (found == NULL || found->job_start_ts <= wr->job_start_ts)
|
||||
) {
|
||||
found = wr;
|
||||
}
|
||||
});
|
||||
assert(found != NULL);
|
||||
US_A(found != NULL);
|
||||
US_LIST_REMOVE(pool->workers, found);
|
||||
US_LIST_APPEND(pool->workers, found); // Перемещаем в конец списка
|
||||
|
||||
|
||||
@ -75,8 +75,11 @@ typedef struct us_workers_pool_sx {
|
||||
|
||||
|
||||
us_workers_pool_s *us_workers_pool_init(
|
||||
const char *name, const char *wr_prefix, uint n_workers,
|
||||
us_workers_pool_job_init_f job_init, void *job_init_arg,
|
||||
const char *name,
|
||||
const char *wr_prefix,
|
||||
uint n_workers,
|
||||
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);
|
||||
|
||||
|
||||
@ -28,7 +28,6 @@
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
@ -245,11 +244,11 @@ static void _main_loop(void) {
|
||||
static void *_follower_thread(void *v_unix_follow) { // cppcheck-suppress constParameterCallback
|
||||
US_THREAD_SETTLE("follower");
|
||||
const char *path = v_unix_follow;
|
||||
assert(path != NULL);
|
||||
US_A(path != NULL);
|
||||
|
||||
while (!atomic_load(&_g_stop)) {
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
assert(fd >= 0);
|
||||
US_A(fd >= 0);
|
||||
|
||||
struct sockaddr_un addr = {0};
|
||||
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
|
||||
|
||||
@ -41,9 +41,9 @@ def main() -> None:
|
||||
data_text = "{\n\t" + ",\n\t".join(
|
||||
", ".join(
|
||||
f"0x{ch:02X}"
|
||||
for ch in data[index:index + 20]
|
||||
for ch in data[i:i + 20]
|
||||
)
|
||||
for index in range(0, len(data), 20)
|
||||
for i in range(0, len(data), 20)
|
||||
) + ",\n}"
|
||||
|
||||
text = f"{common.C_PREPEND}\n"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user