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