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
master
jp9000 2014-04-01 11:55:18 -07:00
parent ed6fc7b122
commit 0cf9e0cfdd
29 changed files with 1083 additions and 63 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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;
}

View File

@ -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

218
libobs/obs-avc.c Normal file
View File

@ -0,0 +1,218 @@
/******************************************************************************
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#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;
}

29
libobs/obs-avc.h Normal file
View File

@ -0,0 +1,29 @@
/******************************************************************************
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#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);

View File

@ -1,5 +1,5 @@
/******************************************************************************
Copyright (C) 2013 by Hugh Bailey <obs.jim@gmail.com>
Copyright (C) 2013-2014 by Hugh Bailey <obs.jim@gmail.com>
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

View File

@ -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));
}

View File

@ -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 */
/**

View File

@ -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(&params, "code", code);
calldata_setptr(&params, "output", output);
signal_handler_signal(output->signals, "start", &params);
calldata_free(&params);
}
static inline void signal_stop(struct obs_output *output)
{
struct calldata params = {0};
calldata_setptr(&params, "output", output);
signal_handler_signal(output->signals, "stop", &params);
calldata_free(&params);
}
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);
}

View File

@ -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;

View File

@ -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
};

View File

@ -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 */

View File

@ -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);

View File

@ -13,4 +13,4 @@ endif()
add_subdirectory(obs-x264)
add_subdirectory(obs-ffmpeg)
# add_subdirectory(obs-outputs)
add_subdirectory(obs-outputs)

View File

@ -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(&params, "success", success);
calldata_setptr(&params, "output", output->output);
signal_handler_signal(obs_output_signalhandler(output->output),
"connect", &params);
calldata_free(&params);
return NULL;
}

View File

@ -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)

View File

@ -0,0 +1,168 @@
/******************************************************************************
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#include <obs.h>
#include <util/array-serializer.h>
#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;
}

View File

@ -0,0 +1,24 @@
/******************************************************************************
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <obs.h>
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);

View File

@ -0,0 +1,3 @@
#pragma once
#define MODULE_NAME "Open Broadcast Studio obs-output plugin 0.0.1"

View File

@ -2,7 +2,6 @@
OBS_DECLARE_MODULE()
bool obs_module_load(uint32_t libobs_ver)
{
UNUSED_PARAMETER(libobs_ver);

View File

@ -0,0 +1,46 @@
/******************************************************************************
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#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));
}

View File

@ -1,5 +1,5 @@
/******************************************************************************
Copyright (C) 2013 by Hugh Bailey <obs.jim@gmail.com>
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
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 <obs.h>
#include <obs-avc.h>
#include <util/darray.h>
#include <util/dstr.h>
#include <util/threading.h>
#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;
}

View File

@ -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

View File

@ -46,6 +46,7 @@
<ClInclude Include="..\..\..\libobs\media-io\video-frame.h" />
<ClInclude Include="..\..\..\libobs\media-io\video-io.h" />
<ClInclude Include="..\..\..\libobs\media-io\video-scaler.h" />
<ClInclude Include="..\..\..\libobs\obs-avc.h" />
<ClInclude Include="..\..\..\libobs\obs-data.h" />
<ClInclude Include="..\..\..\libobs\obs-defs.h" />
<ClInclude Include="..\..\..\libobs\obs-encoder.h" />
@ -57,6 +58,7 @@
<ClInclude Include="..\..\..\libobs\obs-service.h" />
<ClInclude Include="..\..\..\libobs\obs-source.h" />
<ClInclude Include="..\..\..\libobs\obs.h" />
<ClInclude Include="..\..\..\libobs\util\array-serializer.h" />
<ClInclude Include="..\..\..\libobs\util\base.h" />
<ClInclude Include="..\..\..\libobs\util\bmem.h" />
<ClInclude Include="..\..\..\libobs\util\c99defs.h" />
@ -99,6 +101,7 @@
<ClCompile Include="..\..\..\libobs\media-io\video-frame.c" />
<ClCompile Include="..\..\..\libobs\media-io\video-io.c" />
<ClCompile Include="..\..\..\libobs\media-io\video-scaler-ffmpeg.c" />
<ClCompile Include="..\..\..\libobs\obs-avc.c" />
<ClCompile Include="..\..\..\libobs\obs-data.c" />
<ClCompile Include="..\..\..\libobs\obs-display.c" />
<ClCompile Include="..\..\..\libobs\obs-encoder.c" />
@ -111,6 +114,7 @@
<ClCompile Include="..\..\..\libobs\obs-view.c" />
<ClCompile Include="..\..\..\libobs\obs-windows.c" />
<ClCompile Include="..\..\..\libobs\obs.c" />
<ClCompile Include="..\..\..\libobs\util\array-serializer.c" />
<ClCompile Include="..\..\..\libobs\util\base.c" />
<ClCompile Include="..\..\..\libobs\util\bmem.c" />
<ClCompile Include="..\..\..\libobs\util\cf-lexer.c" />

View File

@ -216,6 +216,12 @@
<ClInclude Include="..\..\..\libobs\callback\decl.h">
<Filter>callback\Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\libobs\util\array-serializer.h">
<Filter>util\Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\libobs\obs-avc.h">
<Filter>libobs\Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\libobs\obs-output.c">
@ -368,5 +374,11 @@
<ClCompile Include="..\..\..\libobs\callback\decl.c">
<Filter>callback\Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\libobs\util\array-serializer.c">
<Filter>util\Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\libobs\obs-avc.c">
<Filter>libobs\Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -90,11 +90,11 @@
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>libobs.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/plugins/32bit/$(TargetName)$(TargetExt)"</Command>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/32bit/$(TargetName)$(TargetExt)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@ -109,11 +109,11 @@
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>libobs.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/plugins/64bit/$(TargetName)$(TargetExt)"</Command>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/64bit/$(TargetName)$(TargetExt)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@ -132,11 +132,11 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>libobs.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/plugins/32bit/$(TargetName)$(TargetExt)"</Command>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/32bit/$(TargetName)$(TargetExt)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@ -155,22 +155,40 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>libobs.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/plugins/64bit/$(TargetName)$(TargetExt)"</Command>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/64bit/$(TargetName)$(TargetExt)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\..\..\plugins\obs-outputs\obs-outputs.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\obs-stream.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\obs-x264.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\flv-mux.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\amf.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\bytes.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\cencode.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\dh.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\dhgroups.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\handshake.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\http.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\log.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\md5.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\rtmp.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\rtmp_sys.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\obs-output-ver.h" />
<ClInclude Include="..\..\..\plugins\obs-outputs\rtmp-helpers.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\plugins\obs-outputs\flv-mux.c" />
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\amf.c" />
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\cencode.c" />
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\hashswf.c" />
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\log.c" />
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\md5.c" />
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\parseurl.c" />
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\rtmp.c" />
<ClCompile Include="..\..\..\plugins\obs-outputs\obs-outputs.c" />
<ClCompile Include="..\..\..\plugins\obs-outputs\obs-stream.c" />
<ClCompile Include="..\..\..\plugins\obs-outputs\obs-x264.c" />
<ClCompile Include="..\..\..\plugins\obs-outputs\rtmp-stream.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@ -13,27 +13,90 @@
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="librtmp">
<UniqueIdentifier>{6b714a6d-2272-49ba-b4cd-99cf60857bae}</UniqueIdentifier>
</Filter>
<Filter Include="librtmp\Source Files">
<UniqueIdentifier>{57e42abc-f21b-4bd3-a032-9cab6303dd1c}</UniqueIdentifier>
</Filter>
<Filter Include="librtmp\Header Files">
<UniqueIdentifier>{59c34a42-3966-49f7-9758-2914dfec75e1}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\plugins\obs-outputs\obs-outputs.h">
<ClInclude Include="..\..\..\plugins\obs-outputs\flv-mux.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\plugins\obs-outputs\obs-x264.h">
<ClInclude Include="..\..\..\plugins\obs-outputs\obs-output-ver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\plugins\obs-outputs\obs-stream.h">
<ClInclude Include="..\..\..\plugins\obs-outputs\rtmp-helpers.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\dhgroups.h">
<Filter>librtmp\Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\handshake.h">
<Filter>librtmp\Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\http.h">
<Filter>librtmp\Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\log.h">
<Filter>librtmp\Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\md5.h">
<Filter>librtmp\Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\rtmp.h">
<Filter>librtmp\Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\rtmp_sys.h">
<Filter>librtmp\Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\amf.h">
<Filter>librtmp\Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\bytes.h">
<Filter>librtmp\Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\cencode.h">
<Filter>librtmp\Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\plugins\obs-outputs\librtmp\dh.h">
<Filter>librtmp\Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\plugins\obs-outputs\obs-outputs.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\plugins\obs-outputs\obs-stream.c">
<ClCompile Include="..\..\..\plugins\obs-outputs\flv-mux.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\plugins\obs-outputs\obs-x264.c">
<ClCompile Include="..\..\..\plugins\obs-outputs\rtmp-stream.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\md5.c">
<Filter>librtmp\Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\parseurl.c">
<Filter>librtmp\Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\rtmp.c">
<Filter>librtmp\Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\amf.c">
<Filter>librtmp\Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\cencode.c">
<Filter>librtmp\Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\hashswf.c">
<Filter>librtmp\Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\plugins\obs-outputs\librtmp\log.c">
<Filter>librtmp\Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>