New upstream version 26.1.0+dfsg1
This commit is contained in:
parent
040dcc3fc2
commit
013818c4af
594 changed files with 19576 additions and 4478 deletions
332
plugins/obs-ffmpeg/obs-ffmpeg-hls-mux.c
Normal file
332
plugins/obs-ffmpeg/obs-ffmpeg-hls-mux.c
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
#include "obs-ffmpeg-mux.h"
|
||||
|
||||
#define do_log(level, format, ...) \
|
||||
blog(level, "[ffmpeg hls muxer: '%s'] " format, \
|
||||
obs_output_get_name(stream->output), ##__VA_ARGS__)
|
||||
|
||||
#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
|
||||
#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
|
||||
|
||||
const char *ffmpeg_hls_mux_getname(void *type)
|
||||
{
|
||||
UNUSED_PARAMETER(type);
|
||||
return obs_module_text("FFmpegHlsMuxer");
|
||||
}
|
||||
|
||||
int hls_stream_dropped_frames(void *data)
|
||||
{
|
||||
struct ffmpeg_muxer *stream = data;
|
||||
return stream->dropped_frames;
|
||||
}
|
||||
|
||||
void ffmpeg_hls_mux_destroy(void *data)
|
||||
{
|
||||
struct ffmpeg_muxer *stream = data;
|
||||
|
||||
if (stream) {
|
||||
deactivate(stream, 0);
|
||||
|
||||
pthread_mutex_destroy(&stream->write_mutex);
|
||||
os_sem_destroy(stream->write_sem);
|
||||
os_event_destroy(stream->stop_event);
|
||||
|
||||
da_free(stream->mux_packets);
|
||||
circlebuf_free(&stream->packets);
|
||||
|
||||
os_process_pipe_destroy(stream->pipe);
|
||||
dstr_free(&stream->path);
|
||||
dstr_free(&stream->printable_path);
|
||||
dstr_free(&stream->stream_key);
|
||||
dstr_free(&stream->muxer_settings);
|
||||
bfree(data);
|
||||
}
|
||||
}
|
||||
|
||||
void *ffmpeg_hls_mux_create(obs_data_t *settings, obs_output_t *output)
|
||||
{
|
||||
struct ffmpeg_muxer *stream = bzalloc(sizeof(*stream));
|
||||
pthread_mutex_init_value(&stream->write_mutex);
|
||||
stream->output = output;
|
||||
|
||||
/* init mutex, semaphore and event */
|
||||
if (pthread_mutex_init(&stream->write_mutex, NULL) != 0)
|
||||
goto fail;
|
||||
if (os_event_init(&stream->stop_event, OS_EVENT_TYPE_AUTO) != 0)
|
||||
goto fail;
|
||||
if (os_sem_init(&stream->write_sem, 0) != 0)
|
||||
goto fail;
|
||||
|
||||
UNUSED_PARAMETER(settings);
|
||||
return stream;
|
||||
|
||||
fail:
|
||||
ffmpeg_hls_mux_destroy(stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool process_packet(struct ffmpeg_muxer *stream)
|
||||
{
|
||||
struct encoder_packet packet;
|
||||
bool has_packet = false;
|
||||
bool ret = true;
|
||||
|
||||
pthread_mutex_lock(&stream->write_mutex);
|
||||
|
||||
if (stream->packets.size) {
|
||||
circlebuf_pop_front(&stream->packets, &packet, sizeof(packet));
|
||||
has_packet = true;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&stream->write_mutex);
|
||||
|
||||
if (has_packet) {
|
||||
ret = write_packet(stream, &packet);
|
||||
obs_encoder_packet_release(&packet);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void *write_thread(void *data)
|
||||
{
|
||||
struct ffmpeg_muxer *stream = data;
|
||||
|
||||
while (os_sem_wait(stream->write_sem) == 0) {
|
||||
if (os_event_try(stream->stop_event) == 0)
|
||||
return NULL;
|
||||
|
||||
if (!process_packet(stream))
|
||||
break;
|
||||
}
|
||||
|
||||
obs_output_signal_stop(stream->output, OBS_OUTPUT_ERROR);
|
||||
deactivate(stream, 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ffmpeg_hls_mux_start(void *data)
|
||||
{
|
||||
struct ffmpeg_muxer *stream = data;
|
||||
obs_service_t *service;
|
||||
const char *path_str;
|
||||
const char *stream_key;
|
||||
struct dstr path = {0};
|
||||
obs_encoder_t *vencoder;
|
||||
obs_data_t *settings;
|
||||
int keyint_sec;
|
||||
|
||||
if (!obs_output_can_begin_data_capture(stream->output, 0))
|
||||
return false;
|
||||
if (!obs_output_initialize_encoders(stream->output, 0))
|
||||
return false;
|
||||
|
||||
service = obs_output_get_service(stream->output);
|
||||
if (!service)
|
||||
return false;
|
||||
path_str = obs_service_get_url(service);
|
||||
stream_key = obs_service_get_key(service);
|
||||
dstr_copy(&stream->stream_key, stream_key);
|
||||
dstr_copy(&path, path_str);
|
||||
dstr_replace(&path, "{stream_key}", stream_key);
|
||||
dstr_copy(&stream->muxer_settings,
|
||||
"method=PUT http_persistent=1 ignore_io_errors=1 ");
|
||||
dstr_catf(&stream->muxer_settings, "http_user_agent=libobs/%s",
|
||||
OBS_VERSION);
|
||||
|
||||
vencoder = obs_output_get_video_encoder(stream->output);
|
||||
settings = obs_encoder_get_settings(vencoder);
|
||||
keyint_sec = (int)obs_data_get_int(settings, "keyint_sec");
|
||||
if (keyint_sec) {
|
||||
dstr_catf(&stream->muxer_settings, " hls_time=%d", keyint_sec);
|
||||
stream->keyint_sec = keyint_sec;
|
||||
}
|
||||
|
||||
obs_data_release(settings);
|
||||
|
||||
start_pipe(stream, path.array);
|
||||
dstr_free(&path);
|
||||
|
||||
if (!stream->pipe) {
|
||||
obs_output_set_last_error(
|
||||
stream->output, obs_module_text("HelperProcessFailed"));
|
||||
warn("Failed to create process pipe");
|
||||
return false;
|
||||
}
|
||||
stream->mux_thread_joinable = pthread_create(&stream->mux_thread, NULL,
|
||||
write_thread, stream) == 0;
|
||||
if (!stream->mux_thread_joinable)
|
||||
return false;
|
||||
|
||||
/* write headers and start capture */
|
||||
os_atomic_set_bool(&stream->active, true);
|
||||
os_atomic_set_bool(&stream->capturing, true);
|
||||
stream->is_hls = true;
|
||||
stream->total_bytes = 0;
|
||||
stream->dropped_frames = 0;
|
||||
stream->min_priority = 0;
|
||||
|
||||
obs_output_begin_data_capture(stream->output, 0);
|
||||
|
||||
dstr_copy(&stream->printable_path, path_str);
|
||||
info("Writing to path '%s'...", stream->printable_path.array);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool write_packet_to_buf(struct ffmpeg_muxer *stream,
|
||||
struct encoder_packet *packet)
|
||||
{
|
||||
circlebuf_push_back(&stream->packets, packet,
|
||||
sizeof(struct encoder_packet));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void drop_frames(struct ffmpeg_muxer *stream, int highest_priority)
|
||||
{
|
||||
struct circlebuf new_buf = {0};
|
||||
int num_frames_dropped = 0;
|
||||
|
||||
circlebuf_reserve(&new_buf, sizeof(struct encoder_packet) * 8);
|
||||
|
||||
while (stream->packets.size) {
|
||||
struct encoder_packet packet;
|
||||
circlebuf_pop_front(&stream->packets, &packet, sizeof(packet));
|
||||
|
||||
/* do not drop audio data or video keyframes */
|
||||
if (packet.type == OBS_ENCODER_AUDIO ||
|
||||
packet.drop_priority >= highest_priority) {
|
||||
circlebuf_push_back(&new_buf, &packet, sizeof(packet));
|
||||
} else {
|
||||
num_frames_dropped++;
|
||||
obs_encoder_packet_release(&packet);
|
||||
}
|
||||
}
|
||||
|
||||
circlebuf_free(&stream->packets);
|
||||
stream->packets = new_buf;
|
||||
|
||||
if (stream->min_priority < highest_priority)
|
||||
stream->min_priority = highest_priority;
|
||||
|
||||
stream->dropped_frames += num_frames_dropped;
|
||||
}
|
||||
|
||||
static bool find_first_video_packet(struct ffmpeg_muxer *stream,
|
||||
struct encoder_packet *first)
|
||||
{
|
||||
size_t count = stream->packets.size / sizeof(*first);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
struct encoder_packet *cur =
|
||||
circlebuf_data(&stream->packets, i * sizeof(*first));
|
||||
if (cur->type == OBS_ENCODER_VIDEO && !cur->keyframe) {
|
||||
*first = *cur;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void check_to_drop_frames(struct ffmpeg_muxer *stream, bool pframes)
|
||||
{
|
||||
struct encoder_packet first;
|
||||
int64_t buffer_duration_usec;
|
||||
int priority = pframes ? OBS_NAL_PRIORITY_HIGHEST
|
||||
: OBS_NAL_PRIORITY_HIGH;
|
||||
int keyint_sec = stream->keyint_sec;
|
||||
int64_t drop_threshold_sec = keyint_sec ? 2 * keyint_sec : 10;
|
||||
|
||||
if (!find_first_video_packet(stream, &first))
|
||||
return;
|
||||
|
||||
buffer_duration_usec = stream->last_dts_usec - first.dts_usec;
|
||||
|
||||
if (buffer_duration_usec > drop_threshold_sec * 1000000)
|
||||
drop_frames(stream, priority);
|
||||
}
|
||||
|
||||
static bool add_video_packet(struct ffmpeg_muxer *stream,
|
||||
struct encoder_packet *packet)
|
||||
{
|
||||
check_to_drop_frames(stream, false);
|
||||
check_to_drop_frames(stream, true);
|
||||
|
||||
/* if currently dropping frames, drop packets until it reaches the
|
||||
* desired priority */
|
||||
if (packet->drop_priority < stream->min_priority) {
|
||||
stream->dropped_frames++;
|
||||
return false;
|
||||
} else {
|
||||
stream->min_priority = 0;
|
||||
}
|
||||
|
||||
stream->last_dts_usec = packet->dts_usec;
|
||||
return write_packet_to_buf(stream, packet);
|
||||
}
|
||||
|
||||
void ffmpeg_hls_mux_data(void *data, struct encoder_packet *packet)
|
||||
{
|
||||
struct ffmpeg_muxer *stream = data;
|
||||
struct encoder_packet new_packet;
|
||||
struct encoder_packet tmp_packet;
|
||||
bool added_packet = false;
|
||||
|
||||
if (!active(stream))
|
||||
return;
|
||||
|
||||
/* encoder failure */
|
||||
if (!packet) {
|
||||
deactivate(stream, OBS_OUTPUT_ENCODE_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stream->sent_headers) {
|
||||
if (!send_headers(stream))
|
||||
return;
|
||||
stream->sent_headers = true;
|
||||
}
|
||||
|
||||
if (stopping(stream)) {
|
||||
if (packet->sys_dts_usec >= stream->stop_ts) {
|
||||
deactivate(stream, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (packet->type == OBS_ENCODER_VIDEO) {
|
||||
obs_parse_avc_packet(&tmp_packet, packet);
|
||||
packet->drop_priority = tmp_packet.priority;
|
||||
obs_encoder_packet_release(&tmp_packet);
|
||||
}
|
||||
obs_encoder_packet_ref(&new_packet, packet);
|
||||
|
||||
pthread_mutex_lock(&stream->write_mutex);
|
||||
|
||||
if (active(stream)) {
|
||||
added_packet =
|
||||
(packet->type == OBS_ENCODER_VIDEO)
|
||||
? add_video_packet(stream, &new_packet)
|
||||
: write_packet_to_buf(stream, &new_packet);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&stream->write_mutex);
|
||||
|
||||
if (added_packet)
|
||||
os_sem_post(stream->write_sem);
|
||||
else
|
||||
obs_encoder_packet_release(&new_packet);
|
||||
}
|
||||
|
||||
struct obs_output_info ffmpeg_hls_muxer = {
|
||||
.id = "ffmpeg_hls_muxer",
|
||||
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK |
|
||||
OBS_OUTPUT_SERVICE,
|
||||
.encoded_video_codecs = "h264",
|
||||
.encoded_audio_codecs = "aac",
|
||||
.get_name = ffmpeg_hls_mux_getname,
|
||||
.create = ffmpeg_hls_mux_create,
|
||||
.destroy = ffmpeg_hls_mux_destroy,
|
||||
.start = ffmpeg_hls_mux_start,
|
||||
.stop = ffmpeg_mux_stop,
|
||||
.encoded_packet = ffmpeg_hls_mux_data,
|
||||
.get_total_bytes = ffmpeg_mux_total_bytes,
|
||||
.get_dropped_frames = hls_stream_dropped_frames,
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue