From 0cf9e0cfdd0ff749b6f37b0339a0abaca89f5b18 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Tue, 1 Apr 2014 11:55:18 -0700 Subject: [PATCH] Add preliminary FLV/RTMP output (incomplete) - obs-outputs module: Add preliminary code to send out data, and add an FLV muxer. This time we don't really need to build the packets ourselves, we can just use the FLV muxer and send it directly to RTMP_Write and it should automatically parse the entire stream for us without us having to do much manual code at all. We'll see how it goes. - libobs: Add AVC NAL packet parsing code - libobs/media-io: Add quick helper functions for audio/video to get the width/height/fps/samplerate/etc rather than having to query the info structures each time. - libobs (obs-output.c): Change 'connect' signal to 'start' and 'stop' signals. 'start' now specifies an error code rather than whether it simply failed, that way the client can actually know *why* a failure occurred. Added those error codes to obs-defs.h. - libobs: Add a few functions to duplicate/free encoder packets --- libobs/CMakeLists.txt | 4 + libobs/media-io/audio-io.c | 5 + libobs/media-io/audio-io.h | 2 + libobs/media-io/video-io.c | 18 ++ libobs/media-io/video-io.h | 5 + libobs/obs-avc.c | 218 ++++++++++++++ libobs/obs-avc.h | 29 ++ libobs/obs-defs.h | 7 +- libobs/obs-encoder.c | 18 ++ libobs/obs-encoder.h | 3 + libobs/obs-output.c | 63 +++- libobs/obs-output.h | 1 + libobs/obs-service.h | 8 +- libobs/obs.h | 16 + obs/window-basic-main.cpp | 2 +- plugins/CMakeLists.txt | 2 +- plugins/obs-ffmpeg/obs-ffmpeg-output.c | 18 +- plugins/obs-outputs/CMakeLists.txt | 44 ++- plugins/obs-outputs/flv-mux.c | 168 +++++++++++ plugins/obs-outputs/flv-mux.h | 24 ++ plugins/obs-outputs/obs-output-ver.h | 3 + plugins/obs-outputs/obs-outputs.c | 1 - plugins/obs-outputs/rtmp-helpers.h | 46 +++ plugins/obs-outputs/rtmp-stream.c | 279 +++++++++++++++++- vs/2013/OBS.sln | 29 ++ vs/2013/libobs/libobs.vcxproj | 4 + vs/2013/libobs/libobs.vcxproj.filters | 12 + vs/2013/obs-outputs/obs-outputs.vcxproj | 44 ++- .../obs-outputs/obs-outputs.vcxproj.filters | 73 ++++- 29 files changed, 1083 insertions(+), 63 deletions(-) create mode 100644 libobs/obs-avc.c create mode 100644 libobs/obs-avc.h create mode 100644 plugins/obs-outputs/flv-mux.c create mode 100644 plugins/obs-outputs/flv-mux.h create mode 100644 plugins/obs-outputs/obs-output-ver.h create mode 100644 plugins/obs-outputs/rtmp-helpers.h diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index 004801eb8..f20467c58 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -122,6 +122,7 @@ set(libobs_mediaio_HEADERS media-io/video-scaler.h) set(libobs_util_SOURCES + util/array-serializer.c util/base.c util/platform.c util/cf-lexer.c @@ -133,6 +134,7 @@ set(libobs_util_SOURCES util/text-lookup.c util/cf-parser.c) set(libobs_util_HEADERS + util/array-serializer.h util/utf8.h util/base.h util/text-lookup.h @@ -154,6 +156,7 @@ set(libobs_util_HEADERS set(libobs_libobs_SOURCES ${libobs_PLATFORM_SOURCES} + obs-avc.c obs-encoder.c obs-source.c obs-output.c @@ -167,6 +170,7 @@ set(libobs_libobs_SOURCES obs-video.c) set(libobs_libobs_HEADERS obs-defs.h + obs-avc.h obs-encoder.h obs-service.h obs-internal.h diff --git a/libobs/media-io/audio-io.c b/libobs/media-io/audio-io.c index 30be6cd64..eb2c209af 100644 --- a/libobs/media-io/audio-io.c +++ b/libobs/media-io/audio-io.c @@ -704,6 +704,11 @@ size_t audio_output_channels(audio_t audio) return audio ? audio->channels : 0; } +uint32_t audio_output_samplerate(audio_t audio) +{ + return audio ? audio->info.samples_per_sec : 0; +} + /* TODO: Optimization of volume multiplication functions */ static inline void mul_vol_u8bit(void *array, float volume, size_t total_num) diff --git a/libobs/media-io/audio-io.h b/libobs/media-io/audio-io.h index 41be4fb00..876868c5c 100644 --- a/libobs/media-io/audio-io.h +++ b/libobs/media-io/audio-io.h @@ -185,12 +185,14 @@ EXPORT bool audio_output_active(audio_t audio); EXPORT size_t audio_output_blocksize(audio_t audio); EXPORT size_t audio_output_planes(audio_t audio); EXPORT size_t audio_output_channels(audio_t audio); +EXPORT uint32_t audio_output_samplerate(audio_t audio); EXPORT const struct audio_output_info *audio_output_getinfo(audio_t audio); EXPORT audio_line_t audio_output_createline(audio_t audio, const char *name); EXPORT void audio_line_destroy(audio_line_t line); EXPORT void audio_line_output(audio_line_t line, const struct audio_data *data); + #ifdef __cplusplus } #endif diff --git a/libobs/media-io/video-io.c b/libobs/media-io/video-io.c index da226d6ee..5af5ad359 100644 --- a/libobs/media-io/video-io.c +++ b/libobs/media-io/video-io.c @@ -370,3 +370,21 @@ void video_output_stop(video_t video) os_event_signal(video->update_event); } } + +uint32_t video_output_width(video_t video) +{ + return video ? video->info.width : 0; +} + +uint32_t video_output_height(video_t video) +{ + return video ? video->info.height : 0; +} + +double video_output_framerate(video_t video) +{ + if (!video) + return 0.0; + + return (double)video->info.fps_num / (double)video->info.fps_den; +} diff --git a/libobs/media-io/video-io.h b/libobs/media-io/video-io.h index ab57fb7bb..8357b3ff8 100644 --- a/libobs/media-io/video-io.h +++ b/libobs/media-io/video-io.h @@ -133,6 +133,11 @@ EXPORT uint64_t video_getframetime(video_t video); EXPORT uint64_t video_gettime(video_t video); EXPORT void video_output_stop(video_t video); +EXPORT uint32_t video_output_width(video_t video); +EXPORT uint32_t video_output_height(video_t video); +EXPORT double video_output_framerate(video_t video); + + #ifdef __cplusplus } #endif diff --git a/libobs/obs-avc.c b/libobs/obs-avc.c new file mode 100644 index 000000000..9c8d15673 --- /dev/null +++ b/libobs/obs-avc.c @@ -0,0 +1,218 @@ +/****************************************************************************** + Copyright (C) 2014 by Hugh Bailey + + 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 2 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 . +******************************************************************************/ + +#include "obs.h" +#include "util/array-serializer.h" + +enum { + NAL_UNKNOWN = 0, + NAL_SLICE = 1, + NAL_SLICE_DPA = 2, + NAL_SLICE_DPB = 3, + NAL_SLICE_DPC = 4, + NAL_SLICE_IDR = 5, + NAL_SEI = 6, + NAL_SPS = 7, + NAL_PPS = 8, + NAL_AUD = 9, + NAL_FILLER = 12, +}; + +enum { + NAL_PRIORITY_DISPOSABLE = 0, + NAL_PRIORITY_LOW = 1, + NAL_PRIORITY_HIGH = 2, + NAL_PRIORITY_HIGHEST = 3, +}; + +/* NOTE: I noticed that FFmpeg does some unusual special handling of certain + * scenarios that I was unaware of, so instead of just searching for {0, 0, 1} + * we'll just use the code from FFmpeg - http://www.ffmpeg.org/ */ +static const uint8_t *ff_avc_find_startcode_internal(const uint8_t *p, + const uint8_t *end) +{ + const uint8_t *a = p + 4 - ((intptr_t)p & 3); + + for (end -= 3; p < a && p < end; p++) { + if (p[0] == 0 && p[1] == 0 && p[2] == 1) + return p; + } + + for (end -= 3; p < end; p += 4) { + uint32_t x = *(const uint32_t*)p; + + if ((x - 0x01010101) & (~x) & 0x80808080) { + if (p[1] == 0) { + if (p[0] == 0 && p[2] == 1) + return p; + if (p[2] == 0 && p[3] == 1) + return p+1; + } + + if (p[3] == 0) { + if (p[2] == 0 && p[4] == 1) + return p+2; + if (p[4] == 0 && p[5] == 1) + return p+3; + } + } + } + + for (end += 3; p < end; p++) { + if (p[0] == 0 && p[1] == 0 && p[2] == 1) + return p; + } + + return end + 3; +} + +const uint8_t *obs_avc_find_startcode(const uint8_t *p, const uint8_t *end) +{ + const uint8_t *out= ff_avc_find_startcode_internal(p, end); + if (p < out && out < end && !out[-1]) out--; + return out; +} + +static inline int get_drop_priority(int priority) +{ + switch (priority) { + case NAL_PRIORITY_DISPOSABLE: return NAL_PRIORITY_DISPOSABLE; + case NAL_PRIORITY_LOW: return NAL_PRIORITY_LOW; + } + + return NAL_PRIORITY_HIGHEST; +} + +static void serialize_avc_data(struct serializer *s, const uint8_t *data, + size_t size, bool *is_keyframe, int *priority) +{ + const uint8_t *nal_start, *nal_end; + const uint8_t *end = data+size; + int type; + + nal_start = obs_avc_find_startcode(data, end); + while (true) { + while (nal_start < end && !*(nal_start++)); + + if (nal_start == end) + break; + + type = nal_start[0] & 0x1F; + + if (type == NAL_SLICE_IDR || type == NAL_SLICE) { + if (is_keyframe) + *is_keyframe = (type == NAL_SLICE_IDR); + if (priority) + *priority = nal_start[0] >> 5; + } + + nal_end = obs_avc_find_startcode(nal_start, end); + s_wb32(s, (uint32_t)(nal_end - nal_start)); + s_write(s, nal_start, nal_end - nal_start); + nal_start = nal_end; + } +} + +void obs_create_avc_packet(struct encoder_packet *avc_packet, + struct encoder_packet *src) +{ + struct array_output_data output; + struct serializer s; + + array_output_serializer_init(&s, &output); + *avc_packet = *src; + + serialize_avc_data(&s, src->data, src->size, &avc_packet->keyframe, + &avc_packet->priority); + + avc_packet->data = output.bytes.array; + avc_packet->size = output.bytes.num; + avc_packet->drop_priority = get_drop_priority(avc_packet->priority); +} + +static inline bool has_start_code(const uint8_t *data) +{ + if (data[0] != 0 || data[1] != 0) + return false; + + return data[2] == 1 || (data[2] == 0 && data[3] == 1); +} + +static void get_sps_pps(const uint8_t *data, size_t size, + const uint8_t **sps, size_t *sps_size, + const uint8_t **pps, size_t *pps_size) +{ + const uint8_t *nal_start, *nal_end; + const uint8_t *end = data+size; + int type; + + nal_start = obs_avc_find_startcode(data, end); + while (true) { + while (nal_start < end && !*(nal_start++)); + + if (nal_start == end) + break; + + nal_end = obs_avc_find_startcode(nal_start, end); + + type = nal_start[0] & 0x1F; + if (type == NAL_SPS) { + *sps = nal_start; + *sps_size = nal_end - nal_start; + } else if (type == NAL_PPS) { + *pps = nal_start; + *pps_size = nal_end - nal_start; + } + + nal_start = nal_end; + } +} + +size_t obs_create_avc_header(uint8_t **header, const uint8_t *data, size_t size) +{ + struct array_output_data output; + struct serializer s; + const uint8_t *sps = NULL, *pps = NULL; + size_t sps_size = 0, pps_size = 0; + + array_output_serializer_init(&s, &output); + + if (size <= 6) return 0; + + if (!has_start_code(data)) { + *header = bmemdup(data, size); + return size; + } + + get_sps_pps(data, size, &sps, &sps_size, &pps, &pps_size); + if (!sps || !pps || sps_size < 4) + return 0; + + s_w8(&s, 0x01); + s_write(&s, sps+1, 3); + s_w8(&s, 0xff); + s_w8(&s, 0xe1); + + s_wb16(&s, (uint16_t)sps_size); + s_write(&s, sps, sps_size); + s_w8(&s, 0x01); + s_wb16(&s, (uint16_t)pps_size); + s_write(&s, pps, pps_size); + + *header = output.bytes.array; + return output.bytes.num; +} diff --git a/libobs/obs-avc.h b/libobs/obs-avc.h new file mode 100644 index 000000000..4d444a366 --- /dev/null +++ b/libobs/obs-avc.h @@ -0,0 +1,29 @@ +/****************************************************************************** + Copyright (C) 2014 by Hugh Bailey + + 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 2 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 . +******************************************************************************/ + +#pragma once + +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, + size_t size); diff --git a/libobs/obs-defs.h b/libobs/obs-defs.h index 542660376..65e5a4327 100644 --- a/libobs/obs-defs.h +++ b/libobs/obs-defs.h @@ -1,5 +1,5 @@ /****************************************************************************** - Copyright (C) 2013 by Hugh Bailey + Copyright (C) 2013-2014 by Hugh Bailey 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 @@ -25,3 +25,8 @@ #define MODULE_FILE_NOT_FOUND -2 #define MODULE_FUNCTION_NOT_FOUND -3 #define MODULE_INCOMPATIBLE_VER -4 + +#define OBS_OUTPUT_SUCCESS 0 +#define OBS_OUTPUT_BAD_PATH -1 +#define OBS_OUTPUT_CONNECT_FAILED -2 +#define OBS_OUTPUT_INVALID_STREAM -3 diff --git a/libobs/obs-encoder.c b/libobs/obs-encoder.c index 55d529577..bbc02e459 100644 --- a/libobs/obs-encoder.c +++ b/libobs/obs-encoder.c @@ -358,6 +358,11 @@ void obs_encoder_stop(obs_encoder_t encoder, } } +const char *obs_encoder_get_codec(obs_encoder_t encoder) +{ + return encoder ? encoder->info.codec : NULL; +} + video_t obs_encoder_video(obs_encoder_t encoder) { return (encoder && encoder->info.type == OBS_ENCODER_VIDEO) ? @@ -526,3 +531,16 @@ void obs_encoder_remove_output(struct obs_encoder *encoder, da_erase_item(encoder->outputs, &output); pthread_mutex_unlock(&encoder->outputs_mutex); } + +void obs_duplicate_encoder_packet(struct encoder_packet *dst, + const struct encoder_packet *src) +{ + *dst = *src; + dst->data = bmemdup(src->data, src->size); +} + +void obs_free_encoder_packet(struct encoder_packet *packet) +{ + bfree(packet->data); + memset(packet, 0, sizeof(struct encoder_packet)); +} diff --git a/libobs/obs-encoder.h b/libobs/obs-encoder.h index c9ded3c56..f96cc72ac 100644 --- a/libobs/obs-encoder.h +++ b/libobs/obs-encoder.h @@ -36,6 +36,9 @@ struct encoder_packet { enum obs_encoder_type type; /**< Encoder type */ + /* ---------------------------------------------------------------- */ + /* Internal video variables (will be parsed automatically) */ + bool keyframe; /**< Is a keyframe */ /** diff --git a/libobs/obs-output.c b/libobs/obs-output.c index f74116b01..4354b7657 100644 --- a/libobs/obs-output.c +++ b/libobs/obs-output.c @@ -28,6 +28,26 @@ static inline const struct obs_output_info *find_output(const char *id) return NULL; } +static const char *output_signals[] = { + "void start(ptr output, int errorcode)", + "void stop(ptr output)", + NULL +}; + +static bool init_output_handlers(struct obs_output *output) +{ + output->signals = signal_handler_create(); + if (!output->signals) + return false; + + output->procs = proc_handler_create(); + if (!output->procs) + return false; + + signal_handler_add_array(output->signals, output_signals); + return true; +} + obs_output_t obs_output_create(const char *id, const char *name, obs_data_t settings) { @@ -41,12 +61,7 @@ obs_output_t obs_output_create(const char *id, const char *name, output = bzalloc(sizeof(struct obs_output)); - output->signals = signal_handler_create(); - if (!output->signals) - goto fail; - - output->procs = proc_handler_create(); - if (!output->procs) + if (!init_output_handlers(output)) goto fail; output->info = *info; @@ -327,6 +342,23 @@ static void hook_data_capture(struct obs_output *output, bool encoded, } } +static inline void signal_start(struct obs_output *output, int code) +{ + struct calldata params = {0}; + calldata_setint(¶ms, "code", code); + calldata_setptr(¶ms, "output", output); + signal_handler_signal(output->signals, "start", ¶ms); + calldata_free(¶ms); +} + +static inline void signal_stop(struct obs_output *output) +{ + struct calldata params = {0}; + calldata_setptr(¶ms, "output", output); + signal_handler_signal(output->signals, "stop", ¶ms); + calldata_free(¶ms); +} + static inline void convert_flags(struct obs_output *output, uint32_t flags, bool *encoded, bool *has_video, bool *has_audio) { @@ -340,6 +372,18 @@ static inline void convert_flags(struct obs_output *output, uint32_t flags, *has_audio = (flags & OBS_OUTPUT_AUDIO) != 0; } +bool obs_output_can_begin_data_capture(obs_output_t output, uint32_t flags) +{ + bool encoded, has_video, has_audio; + + if (!output) return false; + if (output->active) return false; + + convert_flags(output, flags, &encoded, &has_video, &has_audio); + + return can_begin_data_capture(output, encoded, has_video, has_audio); +} + bool obs_output_begin_data_capture(obs_output_t output, uint32_t flags) { bool encoded, has_video, has_audio; @@ -354,6 +398,7 @@ bool obs_output_begin_data_capture(obs_output_t output, uint32_t flags) hook_data_capture(output, encoded, has_video, has_audio); output->active = true; + signal_start(output, OBS_OUTPUT_SUCCESS); return true; } @@ -387,4 +432,10 @@ void obs_output_end_data_capture(obs_output_t output) } output->active = false; + signal_stop(output); +} + +void obs_output_signal_start_fail(obs_output_t output, int code) +{ + signal_start(output, code); } diff --git a/libobs/obs-output.h b/libobs/obs-output.h index 53c9e99ba..0e2ea328a 100644 --- a/libobs/obs-output.h +++ b/libobs/obs-output.h @@ -21,6 +21,7 @@ #define OBS_OUTPUT_AUDIO (1<<1) #define OBS_OUTPUT_AV (OBS_OUTPUT_VIDEO | OBS_OUTPUT_AUDIO) #define OBS_OUTPUT_ENCODED (1<<2) +#define OBS_OUTPUT_SERVICE (1<<3) struct encoder_packet; diff --git a/libobs/obs-service.h b/libobs/obs-service.h index 36baf0afb..2eec5f645 100644 --- a/libobs/obs-service.h +++ b/libobs/obs-service.h @@ -22,15 +22,15 @@ struct obs_service_info { char *id; const char *(*getname)(const char *locale); -#if 0 - void *(*create)(obs_data_t settings, struct service_data *service); + void *(*create)(obs_data_t settings, obs_service_t service); void (*destroy)(void *data); /* optional */ void (*update)(void *data, obs_data_t settings); /* get stream url/key */ - /* get (viewers/etc) */ + const char *(*get_url)(void *data); + const char *(*get_key)(void *data); + /* send (current game/title/activate commercial/etc) */ -#endif }; diff --git a/libobs/obs.h b/libobs/obs.h index 559315c6c..2a1e5fe4e 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -747,6 +747,10 @@ EXPORT void obs_output_set_video_conversion(obs_output_t output, EXPORT void obs_output_set_audio_conversion(obs_output_t output, const struct audio_convert_info *conversion); +/** Returns whether data capture can begin with the specified flags */ +EXPORT bool obs_output_can_begin_data_capture(obs_output_t output, + uint32_t flags); + /** * Begins data capture from media/encoders. * @@ -764,6 +768,9 @@ EXPORT bool obs_output_begin_data_capture(obs_output_t output, uint32_t flags); /** Ends data capture from media/encoders */ EXPORT void obs_output_end_data_capture(obs_output_t output); +/** Signals that start failed */ +EXPORT void obs_output_signal_start_fail(obs_output_t output, int code); + /* ------------------------------------------------------------------------- */ /* Encoders */ @@ -825,6 +832,9 @@ EXPORT void obs_encoder_stop(obs_encoder_t encoder, void (*new_packet)(void *param, struct encoder_packet *packet), void *param); +/** Returns the codec of the encoder */ +EXPORT const char *obs_encoder_get_codec(obs_encoder_t encoder); + /** Gets the default settings for an encoder type */ EXPORT obs_data_t obs_encoder_defaults(const char *id); @@ -864,6 +874,12 @@ EXPORT video_t obs_encoder_video(obs_encoder_t encoder); */ EXPORT audio_t obs_encoder_audio(obs_encoder_t encoder); +/** Duplicates an encoder packet */ +EXPORT void obs_duplicate_encoder_packet(struct encoder_packet *dst, + const struct encoder_packet *src); + +EXPORT void obs_free_encoder_packet(struct encoder_packet *packet); + /* ------------------------------------------------------------------------- */ /* Stream Services */ diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index e50d58b93..0a8be5ddb 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -836,7 +836,7 @@ void OBSBasic::on_streamButton_clicked() return; signal_handler_connect(obs_output_signalhandler(outputTest), - "connect", OBSOutputConnect, this); + "start", OBSOutputConnect, this); obs_output_start(outputTest); ui->streamButton->setEnabled(false); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 93df39bbd..b6942e58e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -13,4 +13,4 @@ endif() add_subdirectory(obs-x264) add_subdirectory(obs-ffmpeg) -# add_subdirectory(obs-outputs) +add_subdirectory(obs-outputs) diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-output.c b/plugins/obs-ffmpeg/obs-ffmpeg-output.c index cd5d0e1ba..b1871a49a 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-output.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-output.c @@ -448,9 +448,6 @@ static void *ffmpeg_output_create(obs_data_t settings, obs_output_t output) if (os_sem_init(&data->write_sem, 0) != 0) goto fail; - signal_handler_add(obs_output_signalhandler(output), - "void connect(ptr output, bool success)"); - av_log_set_callback(ffmpeg_log_callback); UNUSED_PARAMETER(settings); @@ -769,6 +766,9 @@ static bool try_connect(struct ffmpeg_output *output) output->active = true; + if (!obs_output_can_begin_data_capture(output->output, 0)) + return false; + ret = pthread_create(&output->write_thread, NULL, write_thread, output); if (ret != 0) { blog(LOG_WARNING, "ffmpeg_output_start: failed to create write " @@ -787,18 +787,12 @@ static bool try_connect(struct ffmpeg_output *output) static void *start_thread(void *data) { struct ffmpeg_output *output = data; - struct calldata params = {0}; - bool success = try_connect(output); + if (!try_connect(output)) + obs_output_signal_start_fail(output->output, + OBS_OUTPUT_CONNECT_FAILED); output->connecting = false; - - calldata_setbool(¶ms, "success", success); - calldata_setptr(¶ms, "output", output->output); - signal_handler_signal(obs_output_signalhandler(output->output), - "connect", ¶ms); - calldata_free(¶ms); - return NULL; } diff --git a/plugins/obs-outputs/CMakeLists.txt b/plugins/obs-outputs/CMakeLists.txt index 23dcf9dce..83c167090 100644 --- a/plugins/obs-outputs/CMakeLists.txt +++ b/plugins/obs-outputs/CMakeLists.txt @@ -1,18 +1,50 @@ project(obs-outputs) -find_package(Libx264 REQUIRED) -include_directories(${Libx264_INCLUDE_DIR}) +if(WIN32) + set(obs-outputs_PLATFORM_DEPS + ws2_32.lib) +endif() +set(obs-outputs_librtmp_HEADERS + librtmp/amf.h + librtmp/bytes.h + librtmp/cencode.h + librtmp/dh.h + librtmp/dhgroups.h + librtmp/handshake.h + librtmp/http.h + librtmp/log.h + librtmp/md5.h + librtmp/rtmp.h + librtmp/rtmp_sys.h) +set(obs-outputs_librtmp_SOURCES + librtmp/amf.c + librtmp/cencode.c + librtmp/hashswf.c + librtmp/log.c + librtmp/md5.c + librtmp/parseurl.c + librtmp/rtmp.c) + +set(obs-outputs_HEADERS + obs-output-ver.h + rtmp-helpers.h + flv-mux.h + librtmp) set(obs-outputs_SOURCES obs-outputs.c - rtmp-stream.c) + rtmp-stream.c + flv-mux.c) add_library(obs-outputs MODULE - ${obs-outputs_SOURCES}) + ${obs-outputs_SOURCES} + ${obs-outputs_HEADER} + ${obs-outputs_librtmp_SOURCES} + ${obs-outputs_librtmp_HEADERS}) target_link_libraries(obs-outputs libobs - ${Libx264_LIBRARIES}) + ${libobs_PLATFORM_DEPS}) install_obs_plugin(obs-outputs) -obs_fixup_install_target(obs-outputs PATH ${Libx264_LIBRARIES}) +obs_fixup_install_target(obs-outputs PATH) diff --git a/plugins/obs-outputs/flv-mux.c b/plugins/obs-outputs/flv-mux.c new file mode 100644 index 000000000..dd9fa5db3 --- /dev/null +++ b/plugins/obs-outputs/flv-mux.c @@ -0,0 +1,168 @@ +/****************************************************************************** + Copyright (C) 2014 by Hugh Bailey + + 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 2 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 . +******************************************************************************/ + +#include +#include +#include "flv-mux.h" +#include "obs-output-ver.h" +#include "rtmp-helpers.h" + +#define VIDEO_HEADER_SIZE 5 +#define MILLISECOND_DEN 1000 + +static inline double encoder_bitrate(obs_encoder_t encoder) +{ + obs_data_t settings = obs_encoder_get_settings(encoder); + double bitrate = obs_data_getdouble(settings, "bitrate"); + + obs_data_release(settings); + return bitrate; +} + +static void build_flv_meta_data(obs_output_t context, + uint8_t **output, size_t *size) +{ + obs_encoder_t vencoder = obs_output_get_video_encoder(context); + obs_encoder_t aencoder = obs_output_get_audio_encoder(context); + video_t video = obs_encoder_video(vencoder); + audio_t audio = obs_encoder_audio(aencoder); + char buf[4096]; + char *enc = buf; + char *end = enc+sizeof(buf); + + *enc++ = AMF_ECMA_ARRAY; + enc = AMF_EncodeInt32(enc, end, 14); + + enc_num(&enc, end, "duration", 0.0); + enc_num(&enc, end, "fileSize", 0.0); + + enc_num(&enc, end, "width", (double)video_output_width(video)); + enc_num(&enc, end, "height", (double)video_output_height(video)); + enc_str(&enc, end, "videocodecid", "avc1"); + enc_num(&enc, end, "videodatarate", encoder_bitrate(vencoder)); + enc_num(&enc, end, "framerate", video_output_framerate(video)); + + enc_str(&enc, end, "audiocodecid", "mp4a"); + enc_num(&enc, end, "audiodatarate", encoder_bitrate(aencoder)); + enc_num(&enc, end, "audiosamplerate", + (double)audio_output_samplerate(audio)); + enc_num(&enc, end, "audiosamplesize", 16.0); + enc_num(&enc, end, "audiochannels", + (double)audio_output_channels(audio)); + + enc_bool(&enc, end, "stereo", audio_output_channels(audio) == 2); + enc_str(&enc, end, "encoder", MODULE_NAME); + + *enc++ = 0; + *enc++ = 0; + *enc++ = AMF_OBJECT_END; + + *size = enc-buf; + *output = bmemdup(buf, *size); +} + +void flv_meta_data(obs_output_t context, uint8_t **output, size_t *size) +{ + struct array_output_data data; + struct serializer s; + uint8_t *meta_data; + size_t meta_data_size; + + build_flv_meta_data(context, &meta_data, &meta_data_size); + + array_output_serializer_init(&s, &data); + s_w8(&s, RTMP_PACKET_TYPE_VIDEO); + + s_wb24(&s, (uint32_t)meta_data_size); + s_wb32(&s, 0); + s_wb24(&s, 0); + s_write(&s, meta_data, meta_data_size); + + s_wb32(&s, (uint32_t)serializer_get_pos(&s) + 4 - 1); + + *output = data.bytes.array; + *size = data.bytes.num; + + bfree(meta_data); +} + +static uint32_t get_ms_time(struct encoder_packet *packet, int64_t val) +{ + return (uint32_t)(val * MILLISECOND_DEN / packet->timebase_den); +} + +static void flv_video(struct serializer *s, struct encoder_packet *packet, + bool is_header) +{ + int64_t offset = packet->pts - packet->dts; + + if (!packet->data || !packet->size) + return; + + s_w8(s, RTMP_PACKET_TYPE_VIDEO); + + s_wb24(s, (uint32_t)packet->size + 5); + s_wb32(s, get_ms_time(packet, packet->pts)); + s_wb24(s, 0); + + /* these are the 5 extra bytes mentioned above */ + s_w8(s, packet->keyframe ? 0x17 : 0x27); + s_w8(s, is_header ? 0 : 1); + s_wb24(s, get_ms_time(packet, offset)); + s_write(s, packet->data, packet->size); + + /* write tag size (starting byte doesnt count) */ + s_wb32(s, (uint32_t)serializer_get_pos(s) + 4 - 1); +} + +static void flv_audio(struct serializer *s, struct encoder_packet *packet, + bool is_header) +{ + if (!packet->data || !packet->size) + return; + + s_w8(s, RTMP_PACKET_TYPE_AUDIO); + + s_wb24(s, (uint32_t)packet->size + 2); + s_wb32(s, get_ms_time(packet, packet->pts)); + s_wb24(s, 0); + + /* these are the two extra bytes mentioned above */ + s_w8(s, 0xaf); + s_w8(s, is_header ? 0 : 1); + s_write(s, packet->data, packet->size); + + /* write tag size (starting byte doesnt count) */ + s_wb32(s, (uint32_t)serializer_get_pos(s) + 4 - 1); +} + +void flv_packet_mux(struct encoder_packet *packet, + uint8_t **output, size_t *size, bool is_header) +{ + struct array_output_data data; + struct serializer s; + + array_output_serializer_init(&s, &data); + + if (packet->type == OBS_ENCODER_VIDEO) + flv_video(&s, packet, is_header); + else + flv_audio(&s, packet, is_header); + + *output = data.bytes.array; + *size = data.bytes.num; +} diff --git a/plugins/obs-outputs/flv-mux.h b/plugins/obs-outputs/flv-mux.h new file mode 100644 index 000000000..ce4f9262b --- /dev/null +++ b/plugins/obs-outputs/flv-mux.h @@ -0,0 +1,24 @@ +/****************************************************************************** + Copyright (C) 2014 by Hugh Bailey + + 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 2 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 . +******************************************************************************/ + +#pragma once + +#include + +extern void flv_meta_data(obs_output_t context, uint8_t **output, size_t *size); +extern void flv_packet_mux(struct encoder_packet *packet, + uint8_t **output, size_t *size, bool is_header); diff --git a/plugins/obs-outputs/obs-output-ver.h b/plugins/obs-outputs/obs-output-ver.h new file mode 100644 index 000000000..8b46a52f6 --- /dev/null +++ b/plugins/obs-outputs/obs-output-ver.h @@ -0,0 +1,3 @@ +#pragma once + +#define MODULE_NAME "Open Broadcast Studio obs-output plugin 0.0.1" diff --git a/plugins/obs-outputs/obs-outputs.c b/plugins/obs-outputs/obs-outputs.c index bbd183d27..d65f99f09 100644 --- a/plugins/obs-outputs/obs-outputs.c +++ b/plugins/obs-outputs/obs-outputs.c @@ -2,7 +2,6 @@ OBS_DECLARE_MODULE() - bool obs_module_load(uint32_t libobs_ver) { UNUSED_PARAMETER(libobs_ver); diff --git a/plugins/obs-outputs/rtmp-helpers.h b/plugins/obs-outputs/rtmp-helpers.h new file mode 100644 index 000000000..4861e9e91 --- /dev/null +++ b/plugins/obs-outputs/rtmp-helpers.h @@ -0,0 +1,46 @@ +/****************************************************************************** + Copyright (C) 2014 by Hugh Bailey + + 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 2 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 . +******************************************************************************/ + +#include "librtmp/rtmp.h" + +static inline AVal *flv_str(AVal *out, const char *str) +{ + out->av_val = (char*)str; + out->av_len = (int)strlen(str); + return out; +} + +static inline void enc_num(char **enc, char *end, const char *name, double val) +{ + AVal s; + *enc = AMF_EncodeNamedNumber(*enc, end, flv_str(&s, name), val); +} + +static inline void enc_bool(char **enc, char *end, const char *name, bool val) +{ + AVal s; + *enc = AMF_EncodeNamedBoolean(*enc, end, flv_str(&s, name), val); +} + +static inline void enc_str(char **enc, char *end, const char *name, + const char *val) +{ + AVal s1, s2; + *enc = AMF_EncodeNamedString(*enc, end, + flv_str(&s1, name), + flv_str(&s2, val)); +} diff --git a/plugins/obs-outputs/rtmp-stream.c b/plugins/obs-outputs/rtmp-stream.c index ad7481d3d..4fe4b085f 100644 --- a/plugins/obs-outputs/rtmp-stream.c +++ b/plugins/obs-outputs/rtmp-stream.c @@ -1,5 +1,5 @@ /****************************************************************************** - Copyright (C) 2013 by Hugh Bailey + Copyright (C) 2014 by Hugh Bailey 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 @@ -16,44 +16,297 @@ ******************************************************************************/ #include +#include +#include +#include +#include +#include "librtmp/rtmp.h" +#include "librtmp/log.h" +#include "flv-mux.h" struct rtmp_stream { - obs_output_t output; - obs_encoder_t video_encoder; - obs_encoder_t audio_encoder; - obs_service_t service; + obs_output_t output; - bool active; + pthread_mutex_t packets_mutex; + DARRAY(struct encoder_packet) packets; + + bool connecting; + bool active; + pthread_t connect_thread; + pthread_t send_thread; + + os_sem_t send_sem; + os_event_t stop_event; + + struct dstr path, key; + struct dstr username, password; + + RTMP rtmp; }; static const char *rtmp_stream_getname(const char *locale) { /* TODO: locale stuff */ + UNUSED_PARAMETER(locale); return "RTMP Stream"; } +static void log_rtmp(int level, const char *format, va_list args) +{ + blogva(LOG_DEBUG, format, args); + + UNUSED_PARAMETER(level); +} + +static void rtmp_stream_destroy(void *data) +{ + struct rtmp_stream *stream = data; + + if (stream) { + dstr_free(&stream->path); + dstr_free(&stream->key); + dstr_free(&stream->username); + dstr_free(&stream->password); + RTMP_Close(&stream->rtmp); + os_event_destroy(stream->stop_event); + os_sem_destroy(stream->send_sem); + pthread_mutex_destroy(&stream->packets_mutex); + bfree(stream); + } +} + static void *rtmp_stream_create(obs_data_t settings, obs_output_t output) { - struct rtmp_stream *stream = bmalloc(sizeof(struct rtmp_stream)); - memset(stream, 0, sizeof(struct rtmp_stream)); + struct rtmp_stream *stream = bzalloc(sizeof(struct rtmp_stream)); + stream->output = output; + pthread_mutex_init_value(&stream->packets_mutex); + + RTMP_Init(&stream->rtmp); + RTMP_LogSetCallback(log_rtmp); + + if (pthread_mutex_init(&stream->packets_mutex, NULL) != 0) + goto fail; + if (os_event_init(&stream->stop_event, OS_EVENT_TYPE_MANUAL) != 0) + goto fail; + + UNUSED_PARAMETER(settings); + return stream; + +fail: + rtmp_stream_destroy(stream); + return NULL; } -static void rtmp_stream_destroy(struct rtmp_stream *stream) +static void rtmp_stream_stop(void *data) { + UNUSED_PARAMETER(data); } -static void rtmp_stream_update(struct rtmp_stream *stream, obs_data_t settings) +static void rtmp_stream_update(void *data, obs_data_t settings) { + UNUSED_PARAMETER(data); + UNUSED_PARAMETER(settings); } -static bool rtmp_stream_start(struct rtmp_stream *stream) +static inline void set_rtmp_str(AVal *val, const char *str) { + bool valid = (!str || !*str); + val->av_val = valid ? (char*)str : NULL; + val->av_len = valid ? (int)strlen(str) : 0; } -static void rtmp_stream_stop(struct rtmp_stream *stream) +static inline void set_rtmp_dstr(AVal *val, struct dstr *str) { + bool valid = !dstr_isempty(str); + val->av_val = valid ? str->array : NULL; + val->av_len = valid ? (int)str->len : 0; } -static bool rtmp_stream_active(struct rtmp_stream *stream) +static inline bool get_next_packet(struct rtmp_stream *stream, + struct encoder_packet *packet) { + bool new_packet = false; + + pthread_mutex_lock(&stream->packets_mutex); + if (stream->packets.num) { + *packet = stream->packets.array[0]; + 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, + struct encoder_packet *packet, bool is_header) +{ + uint8_t *data; + size_t size; + + flv_packet_mux(packet, &data, &size, is_header); + RTMP_Write(&stream->rtmp, (char*)data, (int)size); + bfree(data); +} + +static void *send_thread(void *data) +{ + struct rtmp_stream *stream = data; + + 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); + } + + return NULL; +} + +#define MIN_SENDBUF_SIZE 65535 + +static void send_meta_data(struct rtmp_stream *stream) +{ + 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); + bfree(meta_data); +} + +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); + + struct encoder_packet packet = { + .type = OBS_ENCODER_AUDIO, + .timebase_den = 1 + }; + + obs_encoder_get_extra_data(aencoder, &packet.data, &packet.size); + send_packet(stream, &packet, true); +} + +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; + + struct encoder_packet packet = { + .type = OBS_ENCODER_VIDEO, + .timebase_den = 1 + }; + + obs_encoder_get_extra_data(vencoder, &header, &size); + packet.size = obs_create_avc_header(&packet.data, header, size); + send_packet(stream, &packet, true); + obs_free_encoder_packet(&packet); +} + +static void send_headers(struct rtmp_stream *stream) +{ + send_meta_data(stream); + send_audio_header(stream); + send_video_header(stream); +} + +static inline bool reset_semaphore(struct rtmp_stream *stream) +{ + os_sem_destroy(stream->send_sem); + return os_sem_init(&stream->send_sem, 0) == 0; +} + +#ifdef _WIN32 +#define socklen_t int +#endif + +static void init_send(struct rtmp_stream *stream) +{ + int cur_sendbuf_size = MIN_SENDBUF_SIZE; + socklen_t size = sizeof(int); + socklen_t ret; + + getsockopt(stream->rtmp.m_sb.sb_socket, SOL_SOCKET, SO_SNDBUF, + (char*)&cur_sendbuf_size, &size); + + if (cur_sendbuf_size < MIN_SENDBUF_SIZE) { + cur_sendbuf_size = 65535; + setsockopt(stream->rtmp.m_sb.sb_socket, SOL_SOCKET, SO_SNDBUF, + (const char*)&cur_sendbuf_size, size); + } + + reset_semaphore(stream); + + ret = pthread_create(&stream->send_thread, NULL, send_thread, stream); + if (ret != 0) + bcrash("Failed to create send thread"); + + send_headers(stream); + obs_output_begin_data_capture(stream->output, 0); +} + +static int try_connect(struct rtmp_stream *stream) +{ + if (!RTMP_SetupURL2(&stream->rtmp, stream->path.array, + stream->key.array)) + return OBS_OUTPUT_BAD_PATH; + + set_rtmp_dstr(&stream->rtmp.Link.pubUser, &stream->username); + set_rtmp_dstr(&stream->rtmp.Link.pubPasswd, &stream->password); + stream->rtmp.Link.swfUrl = stream->rtmp.Link.tcUrl; + set_rtmp_str(&stream->rtmp.Link.flashVer, + "FMLE/3.0 (compatible; FMSc/1.0)"); + + stream->rtmp.m_outChunkSize = 4096; + stream->rtmp.m_bSendChunkSizeInfo = true; + stream->rtmp.m_bUseNagle = true; + + if (!RTMP_Connect(&stream->rtmp, NULL)) + return OBS_OUTPUT_CONNECT_FAILED; + if (!RTMP_ConnectStream(&stream->rtmp, 0)) + return OBS_OUTPUT_INVALID_STREAM; + + init_send(stream); + return OBS_OUTPUT_SUCCESS; +} + +static void *connect_thread(void *data) +{ + UNUSED_PARAMETER(data); + return NULL; +} + +static bool rtmp_stream_start(void *data) +{ + struct rtmp_stream *stream = data; + obs_data_t settings; + + if (!obs_output_can_begin_data_capture(stream->output, 0)) + return false; + + settings = obs_output_get_settings(stream->output); + dstr_copy(&stream->path, obs_data_getstring(settings, "path")); + dstr_copy(&stream->key, obs_data_getstring(settings, "key")); + dstr_copy(&stream->username, obs_data_getstring(settings, "username")); + dstr_copy(&stream->password, obs_data_getstring(settings, "password")); + obs_data_release(settings); + + return pthread_create(&stream->connect_thread, NULL, connect_thread, + stream) != 0; +} + +static bool rtmp_stream_active(void *data) +{ + UNUSED_PARAMETER(data); + return false; } diff --git a/vs/2013/OBS.sln b/vs/2013/OBS.sln index f5ab29186..406087fb1 100644 --- a/vs/2013/OBS.sln +++ b/vs/2013/OBS.sln @@ -38,12 +38,29 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "obs-ffmpeg", "obs-ffmpeg\ob EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "obs-studio", "obs-studio\obs-studio.vcxproj", "{B12702AD-ABFB-343A-A199-8E24837244A3}" + ProjectSection(ProjectDependencies) = postProject + {6F1AC2AE-6424-401A-AF9F-A771E6BEE026} = {6F1AC2AE-6424-401A-AF9F-A771E6BEE026} + EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "win-wasapi", "win-wasapi\win-wasapi.vcxproj", "{A3D24C9D-669D-4DDF-91BA-152D7DDD7C04}" + ProjectSection(ProjectDependencies) = postProject + {6F1AC2AE-6424-401A-AF9F-A771E6BEE026} = {6F1AC2AE-6424-401A-AF9F-A771E6BEE026} + EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "win-capture", "win-capture\win-capture.vcxproj", "{AB83E5F0-D76E-45AB-A4C9-4711B1BE6916}" + ProjectSection(ProjectDependencies) = postProject + {6F1AC2AE-6424-401A-AF9F-A771E6BEE026} = {6F1AC2AE-6424-401A-AF9F-A771E6BEE026} + EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "obs-x264", "obs-x264\obs-x264.vcxproj", "{1798CAF2-531A-4E0E-A722-29F4B1B5D49F}" + ProjectSection(ProjectDependencies) = postProject + {6F1AC2AE-6424-401A-AF9F-A771E6BEE026} = {6F1AC2AE-6424-401A-AF9F-A771E6BEE026} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "obs-outputs", "obs-outputs\obs-outputs.vcxproj", "{CDC3B5C9-2BEA-4509-A2A8-6477506010ED}" + ProjectSection(ProjectDependencies) = postProject + {6F1AC2AE-6424-401A-AF9F-A771E6BEE026} = {6F1AC2AE-6424-401A-AF9F-A771E6BEE026} + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -199,6 +216,18 @@ Global {1798CAF2-531A-4E0E-A722-29F4B1B5D49F}.Release|Win32.Build.0 = Release|Win32 {1798CAF2-531A-4E0E-A722-29F4B1B5D49F}.Release|x64.ActiveCfg = Release|x64 {1798CAF2-531A-4E0E-A722-29F4B1B5D49F}.Release|x64.Build.0 = Release|x64 + {CDC3B5C9-2BEA-4509-A2A8-6477506010ED}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {CDC3B5C9-2BEA-4509-A2A8-6477506010ED}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {CDC3B5C9-2BEA-4509-A2A8-6477506010ED}.Debug|Win32.ActiveCfg = Debug|Win32 + {CDC3B5C9-2BEA-4509-A2A8-6477506010ED}.Debug|Win32.Build.0 = Debug|Win32 + {CDC3B5C9-2BEA-4509-A2A8-6477506010ED}.Debug|x64.ActiveCfg = Debug|x64 + {CDC3B5C9-2BEA-4509-A2A8-6477506010ED}.Debug|x64.Build.0 = Debug|x64 + {CDC3B5C9-2BEA-4509-A2A8-6477506010ED}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {CDC3B5C9-2BEA-4509-A2A8-6477506010ED}.Release|Mixed Platforms.Build.0 = Release|Win32 + {CDC3B5C9-2BEA-4509-A2A8-6477506010ED}.Release|Win32.ActiveCfg = Release|Win32 + {CDC3B5C9-2BEA-4509-A2A8-6477506010ED}.Release|Win32.Build.0 = Release|Win32 + {CDC3B5C9-2BEA-4509-A2A8-6477506010ED}.Release|x64.ActiveCfg = Release|x64 + {CDC3B5C9-2BEA-4509-A2A8-6477506010ED}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/vs/2013/libobs/libobs.vcxproj b/vs/2013/libobs/libobs.vcxproj index a47857f93..726c3050d 100644 --- a/vs/2013/libobs/libobs.vcxproj +++ b/vs/2013/libobs/libobs.vcxproj @@ -46,6 +46,7 @@ + @@ -57,6 +58,7 @@ + @@ -99,6 +101,7 @@ + @@ -111,6 +114,7 @@ + diff --git a/vs/2013/libobs/libobs.vcxproj.filters b/vs/2013/libobs/libobs.vcxproj.filters index bac15421b..661dcb423 100644 --- a/vs/2013/libobs/libobs.vcxproj.filters +++ b/vs/2013/libobs/libobs.vcxproj.filters @@ -216,6 +216,12 @@ callback\Header Files + + util\Header Files + + + libobs\Header Files + @@ -368,5 +374,11 @@ callback\Source Files + + util\Source Files + + + libobs\Source Files + \ No newline at end of file diff --git a/vs/2013/obs-outputs/obs-outputs.vcxproj b/vs/2013/obs-outputs/obs-outputs.vcxproj index 381497994..5e9b94290 100644 --- a/vs/2013/obs-outputs/obs-outputs.vcxproj +++ b/vs/2013/obs-outputs/obs-outputs.vcxproj @@ -90,11 +90,11 @@ Windows true - libobs.lib;%(AdditionalDependencies) + libobs.lib;ws2_32.lib;%(AdditionalDependencies) $(OutDir);%(AdditionalLibraryDirectories) - copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/plugins/32bit/$(TargetName)$(TargetExt)" + copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/32bit/$(TargetName)$(TargetExt)" @@ -109,11 +109,11 @@ Windows true - libobs.lib;%(AdditionalDependencies) + libobs.lib;ws2_32.lib;%(AdditionalDependencies) $(OutDir);%(AdditionalLibraryDirectories) - copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/plugins/64bit/$(TargetName)$(TargetExt)" + copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/64bit/$(TargetName)$(TargetExt)" @@ -132,11 +132,11 @@ true true true - libobs.lib;%(AdditionalDependencies) + libobs.lib;ws2_32.lib;%(AdditionalDependencies) $(OutDir);%(AdditionalLibraryDirectories) - copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/plugins/32bit/$(TargetName)$(TargetExt)" + copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/32bit/$(TargetName)$(TargetExt)" @@ -155,22 +155,40 @@ true true true - libobs.lib;%(AdditionalDependencies) + libobs.lib;ws2_32.lib;%(AdditionalDependencies) $(OutDir);%(AdditionalLibraryDirectories) - copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/plugins/64bit/$(TargetName)$(TargetExt)" + copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/64bit/$(TargetName)$(TargetExt)" - - - + + + + + + + + + + + + + + + + + + + + + + - - + diff --git a/vs/2013/obs-outputs/obs-outputs.vcxproj.filters b/vs/2013/obs-outputs/obs-outputs.vcxproj.filters index 5c59bfa57..151200dc3 100644 --- a/vs/2013/obs-outputs/obs-outputs.vcxproj.filters +++ b/vs/2013/obs-outputs/obs-outputs.vcxproj.filters @@ -13,27 +13,90 @@ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + {6b714a6d-2272-49ba-b4cd-99cf60857bae} + + + {57e42abc-f21b-4bd3-a032-9cab6303dd1c} + + + {59c34a42-3966-49f7-9758-2914dfec75e1} + - + Header Files - + Header Files - + Header Files + + librtmp\Header Files + + + librtmp\Header Files + + + librtmp\Header Files + + + librtmp\Header Files + + + librtmp\Header Files + + + librtmp\Header Files + + + librtmp\Header Files + + + librtmp\Header Files + + + librtmp\Header Files + + + librtmp\Header Files + + + librtmp\Header Files + Source Files - + Source Files - + Source Files + + librtmp\Source Files + + + librtmp\Source Files + + + librtmp\Source Files + + + librtmp\Source Files + + + librtmp\Source Files + + + librtmp\Source Files + + + librtmp\Source Files + \ No newline at end of file