提交 aa4d1d0c 编写于 作者: J jp9000

obs-outputs: Add dynamic bitrate to RTMP output

The dynamic bitrate operates based upon estimating the current bitrate
output, and then adjusting the bitrate on the fly as necessary when
congestion is detected as a replacement for dropping frames.

This may still need adjustment, as it is difficult to accurately emulate
real-world frame drop scenarios.  This does not currently drop frames at
all, and because of that, very high congestion may cause additional
stream delay to viewers (because data will be buffered), but from
limited testing, most congestion will not cause that and it can safely
recover pretty quickly without adding significant delay.
上级 806ab5a0
......@@ -17,6 +17,24 @@
#include "rtmp-stream.h"
#ifndef SEC_TO_NSEC
#define SEC_TO_NSEC 1000000000ULL
#endif
#ifndef MSEC_TO_USEC
#define MSEC_TO_USEC 1000ULL
#endif
#ifndef MSEC_TO_NSEC
#define MSEC_TO_NSEC 1000000ULL
#endif
/* dynamic bitrate coefficients */
#define DBR_INC_TIMER (30ULL * SEC_TO_NSEC)
#define DBR_TRIGGER_USEC (200ULL * MSEC_TO_USEC)
#define MIN_ESTIMATE_DURATION_MS 1000
#define MAX_ESTIMATE_DURATION_MS 2000
static const char *rtmp_stream_getname(void *unused)
{
UNUSED_PARAMETER(unused);
......@@ -107,6 +125,8 @@ static void rtmp_stream_destroy(void *data)
#ifdef TEST_FRAMEDROPS
circlebuf_free(&stream->droptest_info);
#endif
circlebuf_free(&stream->dbr_frames);
pthread_mutex_destroy(&stream->dbr_mutex);
os_event_destroy(stream->buffer_space_available_event);
os_event_destroy(stream->buffer_has_data_event);
......@@ -139,6 +159,11 @@ static void *rtmp_stream_create(obs_data_t *settings, obs_output_t *output)
goto fail;
}
if (pthread_mutex_init(&stream->dbr_mutex, NULL) != 0) {
warn("Failed to initialize dbr mutex");
goto fail;
}
if (os_event_init(&stream->buffer_space_available_event,
OS_EVENT_TYPE_AUTO) != 0) {
warn("Failed to initialize write buffer event");
......@@ -495,6 +520,39 @@ static void set_output_error(struct rtmp_stream *stream)
obs_output_set_last_error(stream->output, msg);
}
static void dbr_add_frame(struct rtmp_stream *stream, struct dbr_frame *back)
{
struct dbr_frame front;
uint64_t dur;
circlebuf_push_back(&stream->dbr_frames, back, sizeof(*back));
circlebuf_peek_front(&stream->dbr_frames, &front, sizeof(front));
stream->dbr_data_size += back->size;
dur = (back->send_end - front.send_beg) / 1000000;
if (dur >= MAX_ESTIMATE_DURATION_MS) {
stream->dbr_data_size -= front.size;
circlebuf_pop_front(&stream->dbr_frames, NULL, sizeof(front));
}
stream->dbr_est_bitrate =
(dur >= MIN_ESTIMATE_DURATION_MS)
? (long)(stream->dbr_data_size * 1000 / dur)
: 0;
stream->dbr_est_bitrate *= 8;
stream->dbr_est_bitrate /= 1000;
if (stream->dbr_est_bitrate) {
stream->dbr_est_bitrate -= stream->audio_bitrate;
if (stream->dbr_est_bitrate < 50)
stream->dbr_est_bitrate = 50;
}
}
static void dbr_set_bitrate(struct rtmp_stream *stream);
static void *send_thread(void *data)
{
struct rtmp_stream *stream = data;
......@@ -503,6 +561,7 @@ static void *send_thread(void *data)
while (os_sem_wait(stream->send_sem) == 0) {
struct encoder_packet packet;
struct dbr_frame dbr_frame;
if (stopping(stream) && stream->stop_ts == 0) {
break;
......@@ -525,10 +584,23 @@ static void *send_thread(void *data)
}
}
if (stream->dbr_enabled) {
dbr_frame.send_beg = os_gettime_ns();
dbr_frame.size = packet.size;
}
if (send_packet(stream, &packet, false, packet.track_idx) < 0) {
os_atomic_set_bool(&stream->disconnected, true);
break;
}
if (stream->dbr_enabled) {
dbr_frame.send_end = os_gettime_ns();
pthread_mutex_lock(&stream->dbr_mutex);
dbr_add_frame(stream, &dbr_frame);
pthread_mutex_unlock(&stream->dbr_mutex);
}
}
bool encode_error = os_atomic_load_bool(&stream->encode_error);
......@@ -565,6 +637,15 @@ static void *send_thread(void *data)
os_event_reset(stream->stop_event);
os_atomic_set_bool(&stream->active, false);
stream->sent_headers = false;
/* reset bitrate on stop */
if (stream->dbr_enabled) {
if (stream->dbr_cur_bitrate != stream->dbr_orig_bitrate) {
stream->dbr_cur_bitrate = stream->dbr_orig_bitrate;
dbr_set_bitrate(stream);
}
}
return NULL;
}
......@@ -923,6 +1004,7 @@ static bool init_connect(struct rtmp_stream *stream)
const char *bind_ip;
int64_t drop_p;
int64_t drop_b;
uint32_t caps;
if (stopping(stream)) {
pthread_join(stream->send_thread, NULL);
......@@ -953,6 +1035,37 @@ static bool init_connect(struct rtmp_stream *stream)
stream->max_shutdown_time_sec =
(int)obs_data_get_int(settings, OPT_MAX_SHUTDOWN_TIME_SEC);
obs_encoder_t *venc = obs_output_get_video_encoder(stream->output);
obs_encoder_t *aenc = obs_output_get_audio_encoder(stream->output, 0);
obs_data_t *vsettings = obs_encoder_get_settings(venc);
obs_data_t *asettings = obs_encoder_get_settings(aenc);
circlebuf_free(&stream->dbr_frames);
stream->audio_bitrate = (long)obs_data_get_int(asettings, "bitrate");
stream->dbr_data_size = 0;
stream->dbr_orig_bitrate = (long)obs_data_get_int(vsettings, "bitrate");
stream->dbr_cur_bitrate = stream->dbr_orig_bitrate;
stream->dbr_est_bitrate = 0;
stream->dbr_inc_bitrate = stream->dbr_orig_bitrate / 10;
stream->dbr_inc_timeout = 0;
stream->dbr_enabled = obs_data_get_bool(settings, OPT_DYN_BITRATE);
caps = obs_encoder_get_caps(venc);
if ((caps & OBS_ENCODER_CAP_DYN_BITRATE) == 0) {
stream->dbr_enabled = false;
}
if (obs_output_get_delay(stream->output) != 0) {
stream->dbr_enabled = false;
}
if (stream->dbr_enabled) {
info("Dynamic bitrate enabled. Dropped frames begone!");
}
obs_data_release(vsettings);
obs_data_release(asettings);
if (drop_p < (drop_b + 200))
drop_p = drop_b + 200;
......@@ -1087,6 +1200,96 @@ static bool find_first_video_packet(struct rtmp_stream *stream,
return false;
}
static bool dbr_bitrate_lowered(struct rtmp_stream *stream)
{
long prev_bitrate = stream->dbr_prev_bitrate;
long est_bitrate = 0;
long new_bitrate;
if (stream->dbr_est_bitrate &&
stream->dbr_est_bitrate < stream->dbr_cur_bitrate) {
stream->dbr_data_size = 0;
circlebuf_pop_front(&stream->dbr_frames, NULL,
stream->dbr_frames.size);
est_bitrate = stream->dbr_est_bitrate / 100 * 100;
if (est_bitrate < 50) {
est_bitrate = 50;
}
}
#if 0
if (prev_bitrate && est_bitrate) {
if (prev_bitrate < est_bitrate) {
blog(LOG_INFO, "going back to prev bitrate: "
"prev_bitrate (%d) < est_bitrate (%d)",
prev_bitrate,
est_bitrate);
new_bitrate = prev_bitrate;
} else {
new_bitrate = est_bitrate;
}
new_bitrate = est_bitrate;
} else if (prev_bitrate) {
new_bitrate = prev_bitrate;
info("going back to prev bitrate");
} else if (est_bitrate) {
new_bitrate = est_bitrate;
} else {
return false;
}
#else
if (est_bitrate) {
new_bitrate = est_bitrate;
} else if (prev_bitrate) {
new_bitrate = prev_bitrate;
info("going back to prev bitrate");
} else {
return false;
}
if (new_bitrate == stream->dbr_cur_bitrate) {
return false;
}
#endif
stream->dbr_prev_bitrate = 0;
stream->dbr_cur_bitrate = new_bitrate;
stream->dbr_inc_timeout = os_gettime_ns() + DBR_INC_TIMER;
info("bitrate decreased to: %ld", stream->dbr_cur_bitrate);
return true;
}
static void dbr_set_bitrate(struct rtmp_stream *stream)
{
obs_encoder_t *vencoder = obs_output_get_video_encoder(stream->output);
obs_data_t *settings = obs_encoder_get_settings(vencoder);
obs_data_set_int(settings, "bitrate", stream->dbr_cur_bitrate);
obs_encoder_update(vencoder, settings);
obs_data_release(settings);
}
static void dbr_inc_bitrate(struct rtmp_stream *stream)
{
stream->dbr_prev_bitrate = stream->dbr_cur_bitrate;
stream->dbr_cur_bitrate += stream->dbr_inc_bitrate;
if (stream->dbr_cur_bitrate >= stream->dbr_orig_bitrate) {
stream->dbr_cur_bitrate = stream->dbr_orig_bitrate;
info("bitrate increased to: %ld, done",
stream->dbr_cur_bitrate);
} else if (stream->dbr_cur_bitrate < stream->dbr_orig_bitrate) {
stream->dbr_inc_timeout = os_gettime_ns() + DBR_INC_TIMER;
info("bitrate increased to: %ld, waiting",
stream->dbr_cur_bitrate);
}
}
static void check_to_drop_frames(struct rtmp_stream *stream, bool pframes)
{
struct encoder_packet first;
......@@ -1098,6 +1301,18 @@ static void check_to_drop_frames(struct rtmp_stream *stream, bool pframes)
int64_t drop_threshold = pframes ? stream->pframe_drop_threshold_usec
: stream->drop_threshold_usec;
if (!pframes && stream->dbr_enabled) {
if (stream->dbr_inc_timeout) {
uint64_t t = os_gettime_ns();
if (t >= stream->dbr_inc_timeout) {
stream->dbr_inc_timeout = 0;
dbr_inc_bitrate(stream);
dbr_set_bitrate(stream);
}
}
}
if (num_packets < 5) {
if (!pframes)
stream->congestion = 0.0f;
......@@ -1116,6 +1331,31 @@ static void check_to_drop_frames(struct rtmp_stream *stream, bool pframes)
(float)buffer_duration_usec / (float)drop_threshold;
}
/* alternatively, drop only pframes:
* (!pframes && stream->dbr_enabled)
* but let's test without dropping frames
* at all first */
if (stream->dbr_enabled) {
bool bitrate_changed = false;
if (pframes) {
return;
}
if (buffer_duration_usec >= DBR_TRIGGER_USEC) {
pthread_mutex_lock(&stream->dbr_mutex);
bitrate_changed = dbr_bitrate_lowered(stream);
pthread_mutex_unlock(&stream->dbr_mutex);
}
if (bitrate_changed) {
debug("buffer_duration_msec: %" PRId64,
buffer_duration_usec / 1000);
dbr_set_bitrate(stream);
}
return;
}
if (buffer_duration_usec > drop_threshold) {
debug("buffer_duration_usec: %" PRId64, buffer_duration_usec);
drop_frames(stream, name, priority, pframes);
......
......@@ -24,6 +24,7 @@
#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__)
#define OPT_DYN_BITRATE "dyn_bitrate"
#define OPT_DROP_THRESHOLD "drop_threshold_ms"
#define OPT_PFRAME_DROP_THRESHOLD "pframe_drop_threshold_ms"
#define OPT_MAX_SHUTDOWN_TIME_SEC "max_shutdown_time_sec"
......@@ -45,6 +46,12 @@ struct droptest_info {
};
#endif
struct dbr_frame {
uint64_t send_beg;
uint64_t send_end;
size_t size;
};
struct rtmp_stream {
obs_output_t *output;
......@@ -93,6 +100,18 @@ struct rtmp_stream {
size_t droptest_size;
#endif
pthread_mutex_t dbr_mutex;
struct circlebuf dbr_frames;
size_t dbr_data_size;
uint64_t dbr_inc_timeout;
long audio_bitrate;
long dbr_est_bitrate;
long dbr_orig_bitrate;
long dbr_prev_bitrate;
long dbr_cur_bitrate;
long dbr_inc_bitrate;
bool dbr_enabled;
RTMP rtmp;
bool new_socket_loop;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册