mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 16:26:30 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95fcc3c58e | ||
|
|
81500af1b3 | ||
|
|
86a2141361 | ||
|
|
22e5c8627b | ||
|
|
c8600e62c2 | ||
|
|
335f19f0e3 | ||
|
|
a24e0eeb86 | ||
|
|
3fcb8d3ee5 | ||
|
|
ca30656bf8 | ||
|
|
4f0abf7eec |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 5.27
|
||||
current_version = 5.29
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
@@ -38,7 +38,7 @@ If you're going to live-stream from your backyard webcam and need to control it,
|
||||
You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg9```/```libjpeg-turbo``` and ```libbsd``` (only for Linux).
|
||||
|
||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libgpiod-dev` for `WITH_GPIO=1` and `libsystemd-dev` for `WITH_SYSTEMD=1` and `libasound2-dev libspeex-dev libspeexdsp-dev libopus-dev` for `WITH_JANUS=1`.
|
||||
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
||||
* Alpine: `sudo apk add libevent-dev libbsd-dev libjpeg-turbo-dev musl-dev`. Build with `WITH_PTHREAD_NP=0`.
|
||||
|
||||
|
||||
@@ -102,6 +102,11 @@ static void *_common_thread(void *v_client, bool video) {
|
||||
packet.video = rtp->video;
|
||||
packet.buffer = (char *)rtp->datagram;
|
||||
packet.length = rtp->used;
|
||||
# if JANUS_PLUGIN_API_VERSION >= 100
|
||||
// The uStreamer Janus plugin places video in stream index 0 and audio
|
||||
// (if available) in stream index 1.
|
||||
packet.mindex = (rtp->video ? 0 : 1);
|
||||
# endif
|
||||
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
||||
// FIXME: Это очень эффективный способ уменьшить задержку, но WebRTC стек в хроме и фоксе
|
||||
// слишком корявый, чтобы обработать это, из-за чего на кейфреймах начинаются заикания.
|
||||
|
||||
@@ -154,9 +154,9 @@ static void *_video_sink_thread(UNUSED void *arg) {
|
||||
if (frame == NULL) {
|
||||
goto close_memsink;
|
||||
}
|
||||
// if (frame->key) {
|
||||
// atomic_store(&_g_key_required, false);
|
||||
// }
|
||||
if (frame->key) {
|
||||
atomic_store(&_g_key_required, false);
|
||||
}
|
||||
if (us_queue_put(_g_video_queue, frame, 0) != 0) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Video queue is full"); });
|
||||
us_frame_destroy(frame);
|
||||
@@ -427,12 +427,7 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
} else if (!strcmp(request_str, "watch")) {
|
||||
char *sdp;
|
||||
{
|
||||
// atomic_store(&_g_key_required, true);
|
||||
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
|
||||
if (video_sdp == NULL) {
|
||||
PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet");
|
||||
goto ok_wait;
|
||||
}
|
||||
char *const audio_sdp = (_g_rtpa ? us_rtpa_make_sdp(_g_rtpa) : us_strdup(""));
|
||||
US_ASPRINTF(sdp,
|
||||
"v=0" RN
|
||||
@@ -440,7 +435,16 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
"s=PiKVM uStreamer" RN
|
||||
"t=0 0" RN
|
||||
"%s%s",
|
||||
us_get_now_id() >> 1, audio_sdp, video_sdp
|
||||
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);
|
||||
@@ -450,6 +454,10 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
PUSH_STATUS("started", offer_jsep);
|
||||
json_decref(offer_jsep);
|
||||
|
||||
} else if (!strcmp(request_str, "key_required")) {
|
||||
US_JLOG_INFO("main", "Got keyframe request from a client");
|
||||
atomic_store(&_g_key_required, true);
|
||||
|
||||
} else {
|
||||
PUSH_ERROR(405, "Not implemented");
|
||||
}
|
||||
|
||||
@@ -36,35 +36,15 @@ us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay) {
|
||||
US_CALLOC(rtpv, 1);
|
||||
rtpv->rtp = us_rtp_init(96, true, zero_playout_delay);
|
||||
rtpv->callback = callback;
|
||||
rtpv->sps = us_frame_init();
|
||||
rtpv->pps = us_frame_init();
|
||||
US_MUTEX_INIT(rtpv->mutex);
|
||||
return rtpv;
|
||||
}
|
||||
|
||||
void us_rtpv_destroy(us_rtpv_s *rtpv) {
|
||||
US_MUTEX_DESTROY(rtpv->mutex);
|
||||
us_frame_destroy(rtpv->pps);
|
||||
us_frame_destroy(rtpv->sps);
|
||||
us_rtp_destroy(rtpv->rtp);
|
||||
free(rtpv);
|
||||
}
|
||||
|
||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
||||
US_MUTEX_LOCK(rtpv->mutex);
|
||||
|
||||
if (rtpv->sps->used == 0 || rtpv->pps->used == 0) {
|
||||
US_MUTEX_UNLOCK(rtpv->mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *sps = NULL;
|
||||
char *pps = NULL;
|
||||
us_base64_encode(rtpv->sps->data, rtpv->sps->used, &sps, NULL);
|
||||
us_base64_encode(rtpv->pps->data, rtpv->pps->used, &pps, NULL);
|
||||
|
||||
US_MUTEX_UNLOCK(rtpv->mutex);
|
||||
|
||||
# define PAYLOAD rtpv->rtp->payload
|
||||
// https://tools.ietf.org/html/rfc6184
|
||||
// https://github.com/meetecho/janus-gateway/issues/2443
|
||||
@@ -75,8 +55,6 @@ char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
||||
"a=rtpmap:%u H264/90000" RN
|
||||
"a=fmtp:%u profile-level-id=42E01F" RN
|
||||
"a=fmtp:%u packetization-mode=1" RN
|
||||
"a=fmtp:%u sprop-sps=%s" RN
|
||||
"a=fmtp:%u sprop-pps=%s" RN
|
||||
"a=rtcp-fb:%u nack" RN
|
||||
"a=rtcp-fb:%u nack pli" RN
|
||||
"a=rtcp-fb:%u goog-remb" RN
|
||||
@@ -84,17 +62,12 @@ char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
||||
"%s" // playout-delay
|
||||
"a=sendonly" RN,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
PAYLOAD, sps,
|
||||
PAYLOAD, pps,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
rtpv->rtp->ssrc,
|
||||
(rtpv->rtp->zero_playout_delay ? "a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN : "")
|
||||
);
|
||||
# undef PAYLOAD
|
||||
|
||||
free(sps);
|
||||
free(pps);
|
||||
return sdp;
|
||||
# undef PAYLOAD
|
||||
}
|
||||
|
||||
#define _PRE 3 // Annex B prefix length
|
||||
@@ -136,22 +109,11 @@ void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame) {
|
||||
}
|
||||
|
||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked) {
|
||||
# define DG rtpv->rtp->datagram
|
||||
|
||||
const unsigned ref_idc = (data[0] >> 5) & 3;
|
||||
const unsigned type = data[0] & 0x1F;
|
||||
|
||||
us_frame_s *ps = NULL;
|
||||
switch (type) {
|
||||
case 7: ps = rtpv->sps; break;
|
||||
case 8: ps = rtpv->pps; break;
|
||||
}
|
||||
if (ps != NULL) {
|
||||
US_MUTEX_LOCK(rtpv->mutex);
|
||||
us_frame_set_data(ps, data, size);
|
||||
US_MUTEX_UNLOCK(rtpv->mutex);
|
||||
}
|
||||
|
||||
# define DG rtpv->rtp->datagram
|
||||
|
||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
||||
us_rtp_write_header(rtpv->rtp, pts, marked);
|
||||
memcpy(DG + US_RTP_HEADER_SIZE, data, size);
|
||||
|
||||
@@ -31,12 +31,8 @@
|
||||
#include <sys/types.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
#include "uslibs/frame.h"
|
||||
#include "uslibs/base64.h"
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
@@ -44,9 +40,6 @@
|
||||
typedef struct {
|
||||
us_rtp_s *rtp;
|
||||
us_rtp_callback_f callback;
|
||||
us_frame_s *sps; // Actually not a frame, just a bytes storage
|
||||
us_frame_s *pps;
|
||||
pthread_mutex_t mutex;
|
||||
} us_rtpv_s;
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../../src/libs/base64.c
|
||||
@@ -1 +0,0 @@
|
||||
../../../src/libs/base64.h
|
||||
@@ -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 5.27" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 5.29" "January 2021"
|
||||
|
||||
.SH NAME
|
||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||
@@ -45,7 +45,7 @@ Limit the number of frames. Default: 0 (infinite).
|
||||
.BR \-i ", "\-\-interval\ \fIsec
|
||||
Delay between reading frames (float). Default: 0.
|
||||
.TP
|
||||
.BR \-k ", " \-\-key
|
||||
.BR \-k ", " \-\-key\-required
|
||||
Request keyframe from the sink. Default: disabled.
|
||||
|
||||
.SS "Logging options"
|
||||
|
||||
@@ -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 5.27" "November 2020"
|
||||
.TH USTREAMER 1 "version 5.29" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=5.27
|
||||
pkgver=5.29
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=5.27
|
||||
PKG_VERSION:=5.29
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ def _find_sources(suffix: str) -> list[str]:
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="5.27",
|
||||
version="5.29",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
|
||||
@@ -48,7 +48,7 @@ enum _OPT_VALUES {
|
||||
_O_OUTPUT_JSON = 'j',
|
||||
_O_COUNT = 'c',
|
||||
_O_INTERVAL = 'i',
|
||||
_O_KEY = 'k',
|
||||
_O_KEY_REQUIRED = 'k',
|
||||
|
||||
_O_HELP = 'h',
|
||||
_O_VERSION = 'v',
|
||||
@@ -68,7 +68,7 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"output-json", no_argument, NULL, _O_OUTPUT_JSON},
|
||||
{"count", required_argument, NULL, _O_COUNT},
|
||||
{"interval", required_argument, NULL, _O_INTERVAL},
|
||||
{"key", no_argument, NULL, _O_KEY},
|
||||
{"key-required", no_argument, NULL, _O_KEY_REQUIRED},
|
||||
|
||||
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
|
||||
{"perf", no_argument, NULL, _O_PERF},
|
||||
@@ -154,7 +154,7 @@ int main(int argc, char *argv[]) {
|
||||
case _O_OUTPUT_JSON: OPT_SET(output_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: OPT_SET(key_required, true);
|
||||
case _O_KEY_REQUIRED: OPT_SET(key_required, true);
|
||||
|
||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_g_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
|
||||
case _O_PERF: OPT_SET(us_g_log_level, US_LOG_LEVEL_PERF);
|
||||
@@ -332,7 +332,7 @@ static void _help(FILE *fp) {
|
||||
SAY(" -j|--output-json ──────── Format output as JSON. Required option --output. Default: disabled.\n");
|
||||
SAY(" -c|--count <N> ───────── Limit the number of frames. Default: 0 (infinite).\n");
|
||||
SAY(" -i|--interval <sec> ───── Delay between reading frames (float). Default: 0.\n");
|
||||
SAY(" -k|--key ──────────────── Request keyframe from the sink. Default: disabled.\n");
|
||||
SAY(" -k|--key-required ─────── Request keyframe from the sink. Default: disabled.\n");
|
||||
SAY("Logging options:");
|
||||
SAY("════════════════");
|
||||
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#pragma once
|
||||
|
||||
#define US_VERSION_MAJOR 5
|
||||
#define US_VERSION_MINOR 27
|
||||
#define US_VERSION_MINOR 29
|
||||
|
||||
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||
|
||||
Reference in New Issue
Block a user