From 4a652ec82d9eebdd2cf9acc52c251e86fece9278 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Wed, 2 Apr 2014 00:42:12 -0700 Subject: [PATCH] obs-output module: Fill out more functions - Add start/stop code to obs-output module - Use a circular buffer for the buffered encoder packets instead of a dynamic array - Add pthreads.lib as a dependency to obs-output module on windows in visual studio project files - Fix an windows export bug for avc parsing functions on windows. Also, rename those functions to be more consistent with each other. - Make outputs use a single function for encoded data rather than multiple functions - Add the ability to make 'text' properties be passworded --- libobs/obs-avc.c | 7 +- libobs/obs-avc.h | 20 ++- libobs/obs-defs.h | 1 + libobs/obs-module.c | 8 +- libobs/obs-output.c | 8 +- libobs/obs-output.h | 3 +- libobs/obs-properties.c | 19 ++- libobs/obs-properties.h | 8 +- obs/properties-view.cpp | 10 +- plugins/obs-outputs/flv-mux.c | 5 +- plugins/obs-outputs/obs-outputs.c | 4 + plugins/obs-outputs/rtmp-stream.c | 192 ++++++++++++++++++------ plugins/obs-x264/obs-x264.c | 3 +- vs/2013/obs-outputs/obs-outputs.vcxproj | 8 +- 14 files changed, 214 insertions(+), 82 deletions(-) diff --git a/libobs/obs-avc.c b/libobs/obs-avc.c index 9c8d15673..4d9e0ecd4 100644 --- a/libobs/obs-avc.c +++ b/libobs/obs-avc.c @@ -16,6 +16,7 @@ ******************************************************************************/ #include "obs.h" +#include "obs-avc.h" #include "util/array-serializer.h" enum { @@ -127,8 +128,8 @@ static void serialize_avc_data(struct serializer *s, const uint8_t *data, } } -void obs_create_avc_packet(struct encoder_packet *avc_packet, - struct encoder_packet *src) +void obs_parse_avc_packet(struct encoder_packet *avc_packet, + const struct encoder_packet *src) { struct array_output_data output; struct serializer s; @@ -182,7 +183,7 @@ static void get_sps_pps(const uint8_t *data, size_t size, } } -size_t obs_create_avc_header(uint8_t **header, const uint8_t *data, size_t size) +size_t obs_parse_avc_header(uint8_t **header, const uint8_t *data, size_t size) { struct array_output_data output; struct serializer s; diff --git a/libobs/obs-avc.h b/libobs/obs-avc.h index 4d444a366..86ce991c2 100644 --- a/libobs/obs-avc.h +++ b/libobs/obs-avc.h @@ -17,13 +17,23 @@ #pragma once +#include "util/c99defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + struct encoder_packet; /* Helpers for parsing AVC NAL units. */ -const uint8_t *obs_avc_find_startcode(const uint8_t *p, const uint8_t *end); - -EXPORT void obs_create_avc_packet(struct encoder_packet *avc_packet, - struct encoder_packet *src); -EXPORT size_t obs_create_avc_header(uint8_t **header, const uint8_t *data, +EXPORT const uint8_t *obs_avc_find_startcode(const uint8_t *p, + const uint8_t *end); +EXPORT void obs_parse_avc_packet(struct encoder_packet *avc_packet, + const struct encoder_packet *src); +EXPORT size_t obs_parse_avc_header(uint8_t **header, const uint8_t *data, size_t size); + +#ifdef __cplusplus +} +#endif diff --git a/libobs/obs-defs.h b/libobs/obs-defs.h index 65e5a4327..f664e1ac2 100644 --- a/libobs/obs-defs.h +++ b/libobs/obs-defs.h @@ -30,3 +30,4 @@ #define OBS_OUTPUT_BAD_PATH -1 #define OBS_OUTPUT_CONNECT_FAILED -2 #define OBS_OUTPUT_INVALID_STREAM -3 +#define OBS_OUTPUT_FAIL -4 diff --git a/libobs/obs-module.c b/libobs/obs-module.c index a843cb692..32db9220a 100644 --- a/libobs/obs-module.c +++ b/libobs/obs-module.c @@ -200,13 +200,7 @@ void obs_register_output(const struct obs_output_info *info) CHECK_REQUIRED_VAL(info, stop, obs_register_output); if (info->flags & OBS_OUTPUT_ENCODED) { - if (info->flags & OBS_OUTPUT_VIDEO) - CHECK_REQUIRED_VAL(info, encoded_video, - obs_register_output); - - if (info->flags & OBS_OUTPUT_AUDIO) - CHECK_REQUIRED_VAL(info, encoded_audio, - obs_register_output); + CHECK_REQUIRED_VAL(info, encoded_data, obs_register_output); } else { if (info->flags & OBS_OUTPUT_VIDEO) CHECK_REQUIRED_VAL(info, raw_video, diff --git a/libobs/obs-output.c b/libobs/obs-output.c index 4354b7657..76630e6b6 100644 --- a/libobs/obs-output.c +++ b/libobs/obs-output.c @@ -322,11 +322,11 @@ static void hook_data_capture(struct obs_output *output, bool encoded, if (encoded) { if (has_video) obs_encoder_start(output->video_encoder, - output->info.encoded_video, + output->info.encoded_data, output->data); if (has_audio) obs_encoder_start(output->audio_encoder, - output->info.encoded_audio, + output->info.encoded_data, output->data); } else { if (has_video) @@ -414,11 +414,11 @@ void obs_output_end_data_capture(obs_output_t output) if (encoded) { if (has_video) obs_encoder_stop(output->video_encoder, - output->info.encoded_video, + output->info.encoded_data, output->data); if (has_audio) obs_encoder_stop(output->audio_encoder, - output->info.encoded_audio, + output->info.encoded_data, output->data); } else { if (has_video) diff --git a/libobs/obs-output.h b/libobs/obs-output.h index 0e2ea328a..eda95cc1c 100644 --- a/libobs/obs-output.h +++ b/libobs/obs-output.h @@ -42,8 +42,7 @@ struct obs_output_info { void (*raw_video)(void *data, struct video_data *frame); void (*raw_audio)(void *data, struct audio_data *frames); - void (*encoded_video)(void *data, struct encoder_packet *packet); - void (*encoded_audio)(void *data, struct encoder_packet *packet); + void (*encoded_data)(void *data, struct encoder_packet *packet); /* optional */ void (*update)(void *data, obs_data_t settings); diff --git a/libobs/obs-properties.c b/libobs/obs-properties.c index fc5c7b7dc..ceda4358e 100644 --- a/libobs/obs-properties.c +++ b/libobs/obs-properties.c @@ -36,6 +36,10 @@ struct list_item { char *value; }; +struct text_data { + enum obs_text_type type; +}; + struct list_data { DARRAY(struct list_item) items; enum obs_combo_type type; @@ -136,7 +140,7 @@ static inline size_t get_property_size(enum obs_property_type type) case OBS_PROPERTY_BOOL: return 0; case OBS_PROPERTY_INT: return sizeof(struct int_data); case OBS_PROPERTY_FLOAT: return sizeof(struct float_data); - case OBS_PROPERTY_TEXT: return 0; + case OBS_PROPERTY_TEXT: return sizeof(struct text_data); case OBS_PROPERTY_PATH: return 0; case OBS_PROPERTY_LIST: return sizeof(struct list_data); case OBS_PROPERTY_COLOR: return 0; @@ -224,10 +228,13 @@ void obs_properties_add_float(obs_properties_t props, const char *name, } void obs_properties_add_text(obs_properties_t props, const char *name, - const char *desc) + const char *desc, enum obs_text_type type) { if (!props || has_prop(props, name)) return; - new_prop(props, name, desc, OBS_PROPERTY_TEXT); + + struct obs_property *p = new_prop(props, name, desc, OBS_PROPERTY_TEXT); + struct text_data *data = get_property_data(p); + data->type = type; } void obs_properties_add_path(obs_properties_t props, const char *name, @@ -356,6 +363,12 @@ double obs_property_float_step(obs_property_t p) return data ? data->step : 0; } +enum obs_text_type obs_proprety_text_type(obs_property_t p) +{ + struct text_data *data = get_type_data(p, OBS_PROPERTY_TEXT); + return data ? data->type : OBS_TEXT_DEFAULT; +} + enum obs_combo_type obs_property_list_type(obs_property_t p) { struct list_data *data = get_list_data(p); diff --git a/libobs/obs-properties.h b/libobs/obs-properties.h index 3a0506ad7..0fa63517b 100644 --- a/libobs/obs-properties.h +++ b/libobs/obs-properties.h @@ -47,6 +47,11 @@ enum obs_combo_type { OBS_COMBO_TYPE_LIST, }; +enum obs_text_type { + OBS_TEXT_DEFAULT, + OBS_TEXT_PASSWORD, +}; + struct obs_properties; struct obs_property; typedef struct obs_properties *obs_properties_t; @@ -71,7 +76,7 @@ EXPORT void obs_properties_add_int(obs_properties_t props, const char *name, EXPORT void obs_properties_add_float(obs_properties_t props, const char *name, const char *description, double min, double max, double step); EXPORT void obs_properties_add_text(obs_properties_t props, const char *name, - const char *description); + const char *description, enum obs_text_type type); EXPORT void obs_properties_add_path(obs_properties_t props, const char *name, const char *description); EXPORT obs_property_t obs_properties_add_list(obs_properties_t props, @@ -97,6 +102,7 @@ EXPORT int obs_property_int_step(obs_property_t p); EXPORT double obs_property_float_min(obs_property_t p); EXPORT double obs_property_float_max(obs_property_t p); EXPORT double obs_property_float_step(obs_property_t p); +EXPORT enum obs_text_type obs_proprety_text_type(obs_property_t p); EXPORT enum obs_combo_type obs_property_list_type(obs_property_t p); EXPORT enum obs_combo_format obs_property_list_format(obs_property_t p); diff --git a/obs/properties-view.cpp b/obs/properties-view.cpp index 5dd0ab977..38efb3afa 100644 --- a/obs/properties-view.cpp +++ b/obs/properties-view.cpp @@ -63,9 +63,13 @@ QWidget *OBSPropertiesView::AddCheckbox(obs_property_t prop) QWidget *OBSPropertiesView::AddText(obs_property_t prop) { - const char *name = obs_property_name(prop); - const char *val = obs_data_getstring(settings, name); - QLineEdit *edit = new QLineEdit(); + const char *name = obs_property_name(prop); + const char *val = obs_data_getstring(settings, name); + obs_text_type type = obs_proprety_text_type(prop); + QLineEdit *edit = new QLineEdit(); + + if (type == OBS_TEXT_PASSWORD) + edit->setEchoMode(QLineEdit::Password); edit->setText(QT_UTF8(val)); diff --git a/plugins/obs-outputs/flv-mux.c b/plugins/obs-outputs/flv-mux.c index dd9fa5db3..76df55a2f 100644 --- a/plugins/obs-outputs/flv-mux.c +++ b/plugins/obs-outputs/flv-mux.c @@ -82,10 +82,11 @@ void flv_meta_data(obs_output_t context, uint8_t **output, size_t *size) uint8_t *meta_data; size_t meta_data_size; + array_output_serializer_init(&s, &data); + build_flv_meta_data(context, &meta_data, &meta_data_size); - array_output_serializer_init(&s, &data); - s_w8(&s, RTMP_PACKET_TYPE_VIDEO); + s_w8(&s, RTMP_PACKET_TYPE_INFO); s_wb24(&s, (uint32_t)meta_data_size); s_wb32(&s, 0); diff --git a/plugins/obs-outputs/obs-outputs.c b/plugins/obs-outputs/obs-outputs.c index d65f99f09..8ee3db037 100644 --- a/plugins/obs-outputs/obs-outputs.c +++ b/plugins/obs-outputs/obs-outputs.c @@ -2,8 +2,12 @@ OBS_DECLARE_MODULE() +extern struct obs_output_info rtmp_output_info; + bool obs_module_load(uint32_t libobs_ver) { + obs_register_output(&rtmp_output_info); + UNUSED_PARAMETER(libobs_ver); return true; } diff --git a/plugins/obs-outputs/rtmp-stream.c b/plugins/obs-outputs/rtmp-stream.c index 4fe4b085f..9711737b8 100644 --- a/plugins/obs-outputs/rtmp-stream.c +++ b/plugins/obs-outputs/rtmp-stream.c @@ -17,7 +17,7 @@ #include #include -#include +#include #include #include #include "librtmp/rtmp.h" @@ -25,23 +25,24 @@ #include "flv-mux.h" struct rtmp_stream { - obs_output_t output; + obs_output_t output; - pthread_mutex_t packets_mutex; - DARRAY(struct encoder_packet) packets; + pthread_mutex_t packets_mutex; + struct circlebuf packets; - bool connecting; - bool active; - pthread_t connect_thread; - pthread_t send_thread; + bool connecting; + pthread_t connect_thread; - os_sem_t send_sem; - os_event_t stop_event; + bool active; + pthread_t send_thread; - struct dstr path, key; - struct dstr username, password; + os_sem_t send_sem; + os_event_t stop_event; - RTMP rtmp; + struct dstr path, key; + struct dstr username, password; + + RTMP rtmp; }; static const char *rtmp_stream_getname(const char *locale) @@ -58,11 +59,25 @@ static void log_rtmp(int level, const char *format, va_list args) UNUSED_PARAMETER(level); } +static inline void free_packets(struct rtmp_stream *stream) +{ + while (stream->packets.size) { + struct encoder_packet packet; + circlebuf_pop_front(&stream->packets, &packet, sizeof(packet)); + obs_free_encoder_packet(&packet); + } +} + +static void rtmp_stream_stop(void *data); + static void rtmp_stream_destroy(void *data) { struct rtmp_stream *stream = data; if (stream) { + rtmp_stream_stop(stream); + + free_packets(stream); dstr_free(&stream->path); dstr_free(&stream->key); dstr_free(&stream->username); @@ -99,13 +114,21 @@ fail: static void rtmp_stream_stop(void *data) { - UNUSED_PARAMETER(data); -} + struct rtmp_stream *stream = data; + void *ret; -static void rtmp_stream_update(void *data, obs_data_t settings) -{ - UNUSED_PARAMETER(data); - UNUSED_PARAMETER(settings); + os_event_signal(stream->stop_event); + + if (stream->connecting) + pthread_join(stream->connect_thread, &ret); + + if (stream->active) { + obs_output_end_data_capture(stream->output); + os_sem_post(stream->send_sem); + pthread_join(stream->send_thread, &ret); + } + + os_event_reset(stream->stop_event); } static inline void set_rtmp_str(AVal *val, const char *str) @@ -128,44 +151,70 @@ static inline bool get_next_packet(struct rtmp_stream *stream, bool new_packet = false; pthread_mutex_lock(&stream->packets_mutex); - if (stream->packets.num) { - *packet = stream->packets.array[0]; + if (stream->packets.size) { + circlebuf_pop_front(&stream->packets, packet, + sizeof(struct encoder_packet)); new_packet = true; - da_erase(stream->packets, 0); } pthread_mutex_unlock(&stream->packets_mutex); return new_packet; } -static void send_packet(struct rtmp_stream *stream, +static int send_packet(struct rtmp_stream *stream, struct encoder_packet *packet, bool is_header) { uint8_t *data; size_t size; + int ret; flv_packet_mux(packet, &data, &size, is_header); - RTMP_Write(&stream->rtmp, (char*)data, (int)size); + ret = RTMP_Write(&stream->rtmp, (char*)data, (int)size); bfree(data); + + obs_free_encoder_packet(packet); + return ret; +} + +static bool send_remaining_packets(struct rtmp_stream *stream) +{ + struct encoder_packet packet; + + while (get_next_packet(stream, &packet)) + if (send_packet(stream, &packet, false) < 0) + return false; + + return true; } static void *send_thread(void *data) { struct rtmp_stream *stream = data; + bool disconnected = false; while (os_sem_wait(stream->send_sem) == 0) { struct encoder_packet packet; if (os_event_try(stream->stop_event) != EAGAIN) break; - if (!get_next_packet(stream, &packet)) continue; - - send_packet(stream, &packet, false); - obs_free_encoder_packet(&packet); + if (send_packet(stream, &packet, false) < 0) { + disconnected = true; + break; + } } + if (!disconnected && !send_remaining_packets(stream)) + disconnected = true; + + if (disconnected) + free_packets(stream); + + if (os_event_try(stream->stop_event) == EAGAIN) + pthread_detach(stream->send_thread); + + stream->active = false; return NULL; } @@ -173,8 +222,8 @@ static void *send_thread(void *data) static void send_meta_data(struct rtmp_stream *stream) { - uint8_t *meta_data; - size_t meta_data_size; + uint8_t *meta_data; + size_t meta_data_size; flv_meta_data(stream->output, &meta_data, &meta_data_size); RTMP_Write(&stream->rtmp, (char*)meta_data, (int)meta_data_size); @@ -183,8 +232,8 @@ static void send_meta_data(struct rtmp_stream *stream) static void send_audio_header(struct rtmp_stream *stream) { - obs_output_t context = stream->output; - obs_encoder_t aencoder = obs_output_get_audio_encoder(context); + obs_output_t context = stream->output; + obs_encoder_t aencoder = obs_output_get_audio_encoder(context); struct encoder_packet packet = { .type = OBS_ENCODER_AUDIO, @@ -197,10 +246,10 @@ static void send_audio_header(struct rtmp_stream *stream) static void send_video_header(struct rtmp_stream *stream) { - obs_output_t context = stream->output; - obs_encoder_t vencoder = obs_output_get_video_encoder(context); - uint8_t *header; - size_t size; + obs_output_t context = stream->output; + obs_encoder_t vencoder = obs_output_get_video_encoder(context); + uint8_t *header; + size_t size; struct encoder_packet packet = { .type = OBS_ENCODER_VIDEO, @@ -208,7 +257,7 @@ static void send_video_header(struct rtmp_stream *stream) }; obs_encoder_get_extra_data(vencoder, &header, &size); - packet.size = obs_create_avc_header(&packet.data, header, size); + packet.size = obs_parse_avc_header(&packet.data, header, size); send_packet(stream, &packet, true); obs_free_encoder_packet(&packet); } @@ -230,11 +279,11 @@ static inline bool reset_semaphore(struct rtmp_stream *stream) #define socklen_t int #endif -static void init_send(struct rtmp_stream *stream) +static int init_send(struct rtmp_stream *stream) { int cur_sendbuf_size = MIN_SENDBUF_SIZE; socklen_t size = sizeof(int); - socklen_t ret; + int ret; getsockopt(stream->rtmp.m_sb.sb_socket, SOL_SOCKET, SO_SNDBUF, (char*)&cur_sendbuf_size, &size); @@ -248,11 +297,14 @@ static void init_send(struct rtmp_stream *stream) reset_semaphore(stream); ret = pthread_create(&stream->send_thread, NULL, send_thread, stream); - if (ret != 0) - bcrash("Failed to create send thread"); + if (ret != 0) { + RTMP_Close(&stream->rtmp); + return OBS_OUTPUT_FAIL; + } send_headers(stream); obs_output_begin_data_capture(stream->output, 0); + return OBS_OUTPUT_SUCCESS; } static int try_connect(struct rtmp_stream *stream) @@ -276,13 +328,21 @@ static int try_connect(struct rtmp_stream *stream) if (!RTMP_ConnectStream(&stream->rtmp, 0)) return OBS_OUTPUT_INVALID_STREAM; - init_send(stream); - return OBS_OUTPUT_SUCCESS; + return init_send(stream); } static void *connect_thread(void *data) { - UNUSED_PARAMETER(data); + struct rtmp_stream *stream = data; + int ret = try_connect(stream); + + if (ret != OBS_OUTPUT_SUCCESS) + obs_output_signal_start_fail(stream->output, ret); + + if (os_event_try(stream->stop_event) == EAGAIN) + pthread_detach(stream->connect_thread); + + stream->connecting = false; return NULL; } @@ -305,8 +365,46 @@ static bool rtmp_stream_start(void *data) stream) != 0; } -static bool rtmp_stream_active(void *data) +static void rtmp_stream_data(void *data, struct encoder_packet *packet) { - UNUSED_PARAMETER(data); - return false; + struct rtmp_stream *stream = data; + struct encoder_packet new_packet; + + if (packet->type == OBS_ENCODER_AUDIO) + obs_duplicate_encoder_packet(&new_packet, packet); + else if (packet->type == OBS_ENCODER_VIDEO) + obs_parse_avc_packet(&new_packet, packet); + + pthread_mutex_lock(&stream->packets_mutex); + circlebuf_push_back(&stream->packets, &new_packet, sizeof(new_packet)); + pthread_mutex_unlock(&stream->packets_mutex); + os_sem_post(stream->send_sem); } + +static obs_properties_t rtmp_stream_properties(const char *locale) +{ + obs_properties_t props = obs_properties_create(); + + /* TODO: locale */ + obs_properties_add_text(props, "path", "Stream URL", OBS_TEXT_DEFAULT); + obs_properties_add_text(props, "key", "Stream Key", OBS_TEXT_PASSWORD); + obs_properties_add_text(props, "username", "User Name", + OBS_TEXT_DEFAULT); + obs_properties_add_text(props, "password", "Password", + OBS_TEXT_PASSWORD); + + UNUSED_PARAMETER(locale); + return props; +} + +struct obs_output_info rtmp_output_info = { + .id = "rtmp_output", + .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_SERVICE, + .getname = rtmp_stream_getname, + .create = rtmp_stream_create, + .destroy = rtmp_stream_destroy, + .start = rtmp_stream_start, + .stop = rtmp_stream_stop, + .encoded_data = rtmp_stream_data, + .properties = rtmp_stream_properties +}; diff --git a/plugins/obs-x264/obs-x264.c b/plugins/obs-x264/obs-x264.c index 01e5f8986..96ee26206 100644 --- a/plugins/obs-x264/obs-x264.c +++ b/plugins/obs-x264/obs-x264.c @@ -122,7 +122,8 @@ static obs_properties_t obs_x264_props(const char *locale) add_strings(list, x264_tune_names); obs_properties_add_text(props, "x264opts", - "x264 encoder options (separated by ':')"); + "x264 encoder options (separated by ':')", + OBS_TEXT_DEFAULT); return props; } diff --git a/vs/2013/obs-outputs/obs-outputs.vcxproj b/vs/2013/obs-outputs/obs-outputs.vcxproj index 5e9b94290..5f5396ec9 100644 --- a/vs/2013/obs-outputs/obs-outputs.vcxproj +++ b/vs/2013/obs-outputs/obs-outputs.vcxproj @@ -90,7 +90,7 @@ Windows true - libobs.lib;ws2_32.lib;%(AdditionalDependencies) + pthreads.lib;libobs.lib;ws2_32.lib;%(AdditionalDependencies) $(OutDir);%(AdditionalLibraryDirectories) @@ -109,7 +109,7 @@ Windows true - libobs.lib;ws2_32.lib;%(AdditionalDependencies) + pthreads.lib;libobs.lib;ws2_32.lib;%(AdditionalDependencies) $(OutDir);%(AdditionalLibraryDirectories) @@ -132,7 +132,7 @@ true true true - libobs.lib;ws2_32.lib;%(AdditionalDependencies) + pthreads.lib;libobs.lib;ws2_32.lib;%(AdditionalDependencies) $(OutDir);%(AdditionalLibraryDirectories) @@ -155,7 +155,7 @@ true true true - libobs.lib;ws2_32.lib;%(AdditionalDependencies) + pthreads.lib;libobs.lib;ws2_32.lib;%(AdditionalDependencies) $(OutDir);%(AdditionalLibraryDirectories)