Implement RTMP module (still needs drop code)

- Implement the RTMP output module.  This time around, we just use a
   simple FLV muxer, then just write to the stream with RTMP_Write.
   Easy and effective.

 - Fix the FLV muxer, the muxer now outputs proper FLV packets.

 - Output API:
   * When using encoders, automatically interleave encoded packets
     before sending it to the output.

   * Pair encoders and have them automatically wait for the other to
     start to ensure sync.

   * Change 'obs_output_signal_start_fail' to 'obs_output_signal_stop'
     because it was a bit confusing, and doing this makes a lot more
     sense for outputs that need to stop suddenly (disconnections/etc).

 - Encoder API:
   * Remove some unnecessary encoder functions from the actual API and
     make them internal.  Most of the encoder functions are handled
     automatically by outputs anyway, so there's no real need to expose
     them and end up inadvertently confusing plugin writers.

   * Have audio encoders wait for the video encoder to get a frame, then
     start at the exact data point that the first video frame starts to
     ensure the most accrate sync of video/audio possible.

   * Add a required 'frame_size' callback for audio encoders that
     returns the expected number of frames desired to encode with.  This
     way, the libobs encoder API can handle the circular buffering
     internally automatically for the encoder modules, so encoder
     writers don't have to do it themselves.

 - Fix a few bugs in the serializer interface.  It was passing the wrong
   variable for the data in a few cases.

 - If a source has video, make obs_source_update defer the actual update
   callback until the tick function is called to prevent threading
   issues.
master
jp9000 2014-04-07 22:00:10 -07:00
parent 7eec72245d
commit 92522d1886
33 changed files with 517 additions and 234 deletions

View File

@ -115,7 +115,6 @@
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;JANSSON_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
<DisableSpecificWarnings>4996</DisableSpecificWarnings>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -131,7 +130,6 @@
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;JANSSON_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
<DisableSpecificWarnings>4996</DisableSpecificWarnings>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -149,7 +147,6 @@
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;JANSSON_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
<DisableSpecificWarnings>4996</DisableSpecificWarnings>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -169,7 +166,6 @@
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;JANSSON_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
<DisableSpecificWarnings>4996</DisableSpecificWarnings>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>

View File

@ -103,9 +103,9 @@ static inline uint32_t get_audio_channels(enum speaker_layout speakers)
return 0;
}
static inline size_t get_audio_bytes_per_channel(enum audio_format type)
static inline size_t get_audio_bytes_per_channel(enum audio_format format)
{
switch (type) {
switch (format) {
case AUDIO_FORMAT_U8BIT:
case AUDIO_FORMAT_U8BIT_PLANAR:
return 1;
@ -127,9 +127,9 @@ static inline size_t get_audio_bytes_per_channel(enum audio_format type)
return 0;
}
static inline bool is_audio_planar(enum audio_format type)
static inline bool is_audio_planar(enum audio_format format)
{
switch (type) {
switch (format) {
case AUDIO_FORMAT_U8BIT:
case AUDIO_FORMAT_16BIT:
case AUDIO_FORMAT_32BIT:
@ -149,19 +149,19 @@ static inline bool is_audio_planar(enum audio_format type)
return false;
}
static inline size_t get_audio_planes(enum audio_format type,
static inline size_t get_audio_planes(enum audio_format format,
enum speaker_layout speakers)
{
return (is_audio_planar(type) ? get_audio_channels(speakers) : 1);
return (is_audio_planar(format) ? get_audio_channels(speakers) : 1);
}
static inline size_t get_audio_size(enum audio_format type,
static inline size_t get_audio_size(enum audio_format format,
enum speaker_layout speakers, uint32_t frames)
{
bool planar = is_audio_planar(type);
bool planar = is_audio_planar(format);
return (planar ? 1 : get_audio_channels(speakers)) *
get_audio_bytes_per_channel(type) *
get_audio_bytes_per_channel(format) *
frames;
}

View File

@ -18,7 +18,7 @@
#pragma once
/** Maximum number of source channels for output and per display */
#define MAX_CHANNELS 32
#define MAX_CHANNELS 64
#define MODULE_SUCCESS 0
#define MODULE_ERROR -1
@ -31,3 +31,4 @@
#define OBS_OUTPUT_CONNECT_FAILED -2
#define OBS_OUTPUT_INVALID_STREAM -3
#define OBS_OUTPUT_FAIL -4
#define OBS_OUTPUT_DISCONNECTED -5

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
@ -86,7 +86,7 @@ static struct obs_encoder *create_encoder(const char *id,
return encoder;
}
obs_encoder_t obs_encoder_create_video(const char *id, const char *name,
obs_encoder_t obs_video_encoder_create(const char *id, const char *name,
obs_data_t settings, video_t video)
{
const struct video_output_info *voi;
@ -99,7 +99,7 @@ obs_encoder_t obs_encoder_create_video(const char *id, const char *name,
voi->fps_den, voi->fps_num);
}
obs_encoder_t obs_encoder_create_audio(const char *id, const char *name,
obs_encoder_t obs_audio_encoder_create(const char *id, const char *name,
obs_data_t settings, audio_t audio)
{
const struct audio_output_info *aoi;
@ -118,11 +118,21 @@ static void receive_audio(void *param, struct audio_data *data);
static inline struct audio_convert_info *get_audio_info(
struct obs_encoder *encoder, struct audio_convert_info *info)
{
if (encoder->info.audio_info)
if (encoder->info.audio_info(encoder->data, info))
return info;
const struct audio_output_info *aoi;
aoi = audio_output_getinfo(encoder->media);
memset(info, 0, sizeof(struct audio_convert_info));
return false;
if (encoder->info.audio_info)
encoder->info.audio_info(encoder->data, info);
if (info->format == AUDIO_FORMAT_UNKNOWN)
info->format = aoi->format;
if (!info->samples_per_sec)
info->samples_per_sec = aoi->samples_per_sec;
if (info->speakers == SPEAKERS_UNKNOWN)
info->speakers = aoi->speakers;
return info;
}
static inline struct video_scale_info *get_video_info(
@ -141,10 +151,8 @@ static void add_connection(struct obs_encoder *encoder)
struct video_scale_info video_info = {0};
if (encoder->info.type == OBS_ENCODER_AUDIO) {
struct audio_convert_info *info = NULL;
info = get_audio_info(encoder, &audio_info);
audio_output_connect(encoder->media, info, receive_audio,
get_audio_info(encoder, &audio_info);
audio_output_connect(encoder->media, &audio_info, receive_audio,
encoder);
} else {
struct video_scale_info *info = NULL;
@ -169,6 +177,15 @@ static void remove_connection(struct obs_encoder *encoder)
encoder->active = false;
}
static inline void free_audio_buffers(struct obs_encoder *encoder)
{
for (size_t i = 0; i < MAX_AV_PLANES; i++) {
circlebuf_free(&encoder->audio_input_buffer[i]);
bfree(encoder->audio_output_buffer[i]);
encoder->audio_output_buffer[i] = NULL;
}
}
static void obs_encoder_actually_destroy(obs_encoder_t encoder)
{
if (encoder) {
@ -180,8 +197,11 @@ static void obs_encoder_actually_destroy(obs_encoder_t encoder)
da_free(encoder->outputs);
pthread_mutex_unlock(&encoder->outputs_mutex);
free_audio_buffers(encoder);
if (encoder->data)
encoder->info.destroy(encoder->data);
da_free(encoder->callbacks);
obs_data_release(encoder->settings);
pthread_mutex_destroy(&encoder->callbacks_mutex);
pthread_mutex_destroy(&encoder->outputs_mutex);
@ -280,6 +300,29 @@ obs_data_t obs_encoder_get_settings(obs_encoder_t encoder)
return encoder->settings;
}
static inline void reset_audio_buffers(struct obs_encoder *encoder)
{
free_audio_buffers(encoder);
for (size_t i = 0; i < encoder->planes; i++)
encoder->audio_output_buffer[i] =
bmalloc(encoder->framesize_bytes);
}
static void intitialize_audio_encoder(struct obs_encoder *encoder)
{
struct audio_convert_info info;
get_audio_info(encoder, &info);
encoder->samplerate = info.samples_per_sec;
encoder->planes = get_audio_planes(info.format, info.speakers);
encoder->blocksize = get_audio_size(info.format, info.speakers, 1);
encoder->framesize = encoder->info.frame_size(encoder->data);
encoder->framesize_bytes = encoder->blocksize * encoder->framesize;
reset_audio_buffers(encoder);
}
bool obs_encoder_initialize(obs_encoder_t encoder)
{
if (!encoder) return false;
@ -291,7 +334,16 @@ bool obs_encoder_initialize(obs_encoder_t encoder)
encoder->info.destroy(encoder->data);
encoder->data = encoder->info.create(encoder->settings, encoder);
return encoder->data != NULL;
if (!encoder->data)
return false;
encoder->paired_encoder = NULL;
encoder->start_ts = 0;
if (encoder->info.type == OBS_ENCODER_AUDIO)
intitialize_audio_encoder(encoder);
return true;
}
static inline size_t get_callback_idx(
@ -444,15 +496,16 @@ static void full_stop(struct obs_encoder *encoder)
}
static inline void do_encode(struct obs_encoder *encoder,
struct encoder_frame *frame, struct encoder_packet *packet)
struct encoder_frame *frame)
{
struct encoder_packet pkt = {0};
bool received = false;
bool success;
packet->timebase_num = encoder->timebase_num;
packet->timebase_den = encoder->timebase_den;
pkt.timebase_num = encoder->timebase_num;
pkt.timebase_den = encoder->timebase_den;
success = encoder->info.encode(encoder->data, frame, packet, &received);
success = encoder->info.encode(encoder->data, frame, &pkt, &received);
if (!success) {
full_stop(encoder);
blog(LOG_ERROR, "Error encoding with encoder '%s'",
@ -466,7 +519,7 @@ static inline void do_encode(struct obs_encoder *encoder,
for (size_t i = 0; i < encoder->callbacks.num; i++) {
struct encoder_callback *cb;
cb = encoder->callbacks.array+i;
send_packet(encoder, cb, packet);
send_packet(encoder, cb, &pkt);
}
pthread_mutex_unlock(&encoder->callbacks_mutex);
@ -476,7 +529,6 @@ static inline void do_encode(struct obs_encoder *encoder,
static void receive_video(void *param, struct video_data *frame)
{
struct obs_encoder *encoder = param;
struct encoder_packet packet = {0};
struct encoder_frame enc_frame;
memset(&enc_frame, 0, sizeof(struct encoder_frame));
@ -486,38 +538,90 @@ static void receive_video(void *param, struct video_data *frame)
enc_frame.linesize[i] = frame->linesize[i];
}
if (!encoder->start_ts)
encoder->start_ts = frame->timestamp;
enc_frame.frames = 1;
enc_frame.pts = encoder->cur_pts;
do_encode(encoder, &enc_frame, &packet);
do_encode(encoder, &enc_frame);
encoder->cur_pts += encoder->timebase_num;
}
static void receive_audio(void *param, struct audio_data *data)
static bool buffer_audio(struct obs_encoder *encoder, struct audio_data *data)
{
size_t samplerate = encoder->samplerate;
size_t size = data->frames * encoder->blocksize;
size_t offset_size = 0;
if (encoder->paired_encoder && !encoder->start_ts) {
uint64_t end_ts = data->timestamp;
uint64_t v_start_ts = encoder->paired_encoder->start_ts;
/* no video yet, so don't start audio */
if (!v_start_ts)
return false;
/* audio starting point still not synced with video starting
* point, so don't start audio */
end_ts += (uint64_t)data->frames * 1000000000ULL / samplerate;
if (end_ts <= v_start_ts)
return false;
/* ready to start audio, truncate if necessary */
if (data->timestamp < v_start_ts) {
uint64_t offset = v_start_ts - data->timestamp;
offset = (int)(offset * samplerate / 1000000000);
offset_size = (size_t)offset * encoder->blocksize;
}
encoder->start_ts = v_start_ts;
}
size -= offset_size;
/* push in to the circular buffer */
if (size)
for (size_t i = 0; i < encoder->planes; i++)
circlebuf_push_back(&encoder->audio_input_buffer[i],
data->data[i] + offset_size, size);
return true;
}
static void send_audio_data(struct obs_encoder *encoder)
{
struct obs_encoder *encoder = param;
struct encoder_packet packet = {0};
struct encoder_frame enc_frame;
size_t data_size;
memset(&enc_frame, 0, sizeof(struct encoder_frame));
data_size = audio_output_blocksize(encoder->media) * data->frames;
for (size_t i = 0; i < encoder->planes; i++) {
circlebuf_pop_front(&encoder->audio_input_buffer[i],
encoder->audio_output_buffer[i],
encoder->framesize_bytes);
for (size_t i = 0; i < MAX_AV_PLANES; i++) {
if (data->data[i]) {
enc_frame.data[i] = data->data[i];
enc_frame.linesize[i] = (uint32_t)data_size;
}
enc_frame.data[i] = encoder->audio_output_buffer[i];
enc_frame.linesize[i] = (uint32_t)encoder->framesize_bytes;
}
enc_frame.frames = data->frames;
enc_frame.frames = (uint32_t)encoder->framesize;
enc_frame.pts = encoder->cur_pts;
do_encode(encoder, &enc_frame, &packet);
do_encode(encoder, &enc_frame);
encoder->cur_pts += data->frames;
encoder->cur_pts += encoder->framesize;
}
static void receive_audio(void *param, struct audio_data *data)
{
struct obs_encoder *encoder = param;
if (!buffer_audio(encoder, data))
return;
while (encoder->audio_input_buffer[0].size >= encoder->framesize_bytes)
send_audio_data(encoder);
}
void obs_encoder_add_output(struct obs_encoder *encoder,

View File

@ -136,6 +136,9 @@ struct obs_encoder_info {
bool (*encode)(void *data, struct encoder_frame *frame,
struct encoder_packet *packet, bool *received_packet);
/** Audio encoder only: Returns the frame size for this encoder */
size_t (*frame_size)(void *data);
/* ----------------------------------------------------------------- */
/* Optional implementation */

View File

@ -19,6 +19,7 @@
#include "util/c99defs.h"
#include "util/darray.h"
#include "util/circlebuf.h"
#include "util/dstr.h"
#include "util/threading.h"
#include "callback/signal.h"
@ -187,6 +188,8 @@ struct obs_source {
signal_handler_t signals;
proc_handler_t procs;
bool defer_update;
/* ensures show/hide are only called once */
volatile long show_refs;
@ -279,6 +282,7 @@ struct obs_output {
int64_t first_video_ts;
int64_t video_offset;
int64_t audio_offset;
int interleaved_wait;
pthread_mutex_t interleaved_mutex;
DARRAY(struct il_packet) interleaved_packets;
@ -315,6 +319,12 @@ struct obs_encoder {
struct obs_encoder_info info;
obs_data_t settings;
uint32_t samplerate;
size_t planes;
size_t blocksize;
size_t framesize;
size_t framesize_bytes;
bool active;
uint32_t timebase_num;
@ -322,6 +332,17 @@ struct obs_encoder {
int64_t cur_pts;
struct circlebuf audio_input_buffer[MAX_AV_PLANES];
uint8_t *audio_output_buffer[MAX_AV_PLANES];
/* if a video encoder is paired with an audio encoder, make it start
* up at the specific timestamp. if this is the audio encoder,
* wait_for_video makes it wait until it's ready to sync up with
* video */
bool wait_for_video;
struct obs_encoder *paired_encoder;
uint64_t start_ts;
pthread_mutex_t outputs_mutex;
DARRAY(obs_output_t) outputs;
@ -334,6 +355,15 @@ struct obs_encoder {
DARRAY(struct encoder_callback) callbacks;
};
extern bool obs_encoder_initialize(obs_encoder_t encoder);
extern void obs_encoder_start(obs_encoder_t encoder,
void (*new_packet)(void *param, struct encoder_packet *packet),
void *param);
extern void obs_encoder_stop(obs_encoder_t encoder,
void (*new_packet)(void *param, struct encoder_packet *packet),
void *param);
extern void obs_encoder_add_output(struct obs_encoder *encoder,
struct obs_output *output);
extern void obs_encoder_remove_output(struct obs_encoder *encoder,

View File

@ -181,12 +181,16 @@ void obs_register_encoder_s(const struct obs_encoder_info *info, size_t size)
CHECK_REQUIRED_VAL(info, destroy, obs_register_encoder);
CHECK_REQUIRED_VAL(info, encode, obs_register_encoder);
if (info->type == OBS_ENCODER_AUDIO)
CHECK_REQUIRED_VAL(info, frame_size, obs_register_encoder);
REGISTER_OBS_DEF(size, obs_encoder_info, obs->encoder_types, info);
}
void obs_register_service_s(const struct obs_service_info *info, size_t size)
{
/* TODO */
UNUSED_PARAMETER(size);
UNUSED_PARAMETER(info);
}

View File

@ -18,6 +18,8 @@
#include "obs.h"
#include "obs-internal.h"
static inline void signal_stop(struct obs_output *output, int code);
static inline const struct obs_output_info *find_output(const char *id)
{
size_t i;
@ -139,8 +141,10 @@ bool obs_output_start(obs_output_t output)
void obs_output_stop(obs_output_t output)
{
if (output)
if (output) {
output->info.stop(output->data);
signal_stop(output, OBS_OUTPUT_SUCCESS);
}
}
bool obs_output_active(obs_output_t output)
@ -411,8 +415,7 @@ static void interleave_packets(void *data, struct encoder_packet *packet)
pthread_mutex_lock(&output->interleaved_mutex);
if (!prepare_interleaved_packet(output, &out, packet)) {
if (prepare_interleaved_packet(output, &out, packet)) {
for (idx = 0; idx < output->interleaved_packets.num; idx++) {
struct il_packet *cur_packet;
cur_packet = output->interleaved_packets.array + idx;
@ -425,8 +428,13 @@ static void interleave_packets(void *data, struct encoder_packet *packet)
/* when both video and audio have been received, we're ready
* to start sending out packets (one at a time) */
if (output->received_audio && output->received_video)
if (!output->interleaved_wait &&
output->received_audio &&
output->received_video)
send_interleaved(output);
if (output->interleaved_wait > 0)
output->interleaved_wait--;
}
pthread_mutex_unlock(&output->interleaved_mutex);
@ -439,8 +447,10 @@ static void hook_data_capture(struct obs_output *output, bool encoded,
void *param;
if (encoded) {
output->received_video = false;
output->received_video = false;
output->received_video = false;
output->received_video = false;
output->interleaved_wait = 5;
free_packets(output);
encoded_callback = (has_video && has_audio) ?
interleave_packets : output->info.encoded_packet;
@ -456,28 +466,26 @@ static void hook_data_capture(struct obs_output *output, bool encoded,
if (has_video)
video_output_connect(output->video,
get_video_conversion(output),
output->info.raw_video,
output->data);
output->info.raw_video, output->data);
if (has_audio)
audio_output_connect(output->audio,
get_audio_conversion(output),
output->info.raw_audio,
output->data);
output->info.raw_audio, output->data);
}
}
static inline void signal_start(struct obs_output *output, int code)
static inline void signal_start(struct obs_output *output)
{
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)
static inline void signal_stop(struct obs_output *output, int code)
{
struct calldata params = {0};
calldata_setint(&params, "errorcode", code);
calldata_setptr(&params, "output", output);
signal_handler_signal(output->signals, "stop", &params);
calldata_free(&params);
@ -508,6 +516,32 @@ bool obs_output_can_begin_data_capture(obs_output_t output, uint32_t flags)
return can_begin_data_capture(output, encoded, has_video, has_audio);
}
bool obs_output_initialize_encoders(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);
if (!encoded)
return false;
if (has_video && !obs_encoder_initialize(output->video_encoder))
return false;
if (has_audio && !obs_encoder_initialize(output->audio_encoder))
return false;
if (has_video && has_audio && !output->audio_encoder->active &&
!output->video_encoder->active) {
output->audio_encoder->wait_for_video = true;
output->video_encoder->paired_encoder = output->audio_encoder;
output->audio_encoder->paired_encoder = output->video_encoder;
}
return true;
}
bool obs_output_begin_data_capture(obs_output_t output, uint32_t flags)
{
bool encoded, has_video, has_audio;
@ -522,7 +556,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);
signal_start(output);
return true;
}
@ -551,19 +585,17 @@ void obs_output_end_data_capture(obs_output_t output)
} else {
if (has_video)
video_output_disconnect(output->video,
output->info.raw_video,
output->data);
output->info.raw_video, output->data);
if (has_audio)
audio_output_disconnect(output->audio,
output->info.raw_audio,
output->data);
output->info.raw_audio, output->data);
}
output->active = false;
signal_stop(output);
}
void obs_output_signal_start_fail(obs_output_t output, int code)
void obs_output_signal_stop(obs_output_t output, int code)
{
signal_start(output, code);
obs_output_end_data_capture(output);
signal_stop(output, code);
}

View File

@ -78,20 +78,26 @@ extern void obs_properties_apply_settings(obs_properties_t props,
EXPORT obs_property_t obs_properties_add_bool(obs_properties_t props,
const char *name, const char *description);
EXPORT obs_property_t obs_properties_add_int(obs_properties_t props,
const char *name, const char *description,
int min, int max, int step);
EXPORT obs_property_t obs_properties_add_float(obs_properties_t props,
const char *name, const char *description,
double min, double max, double step);
EXPORT obs_property_t obs_properties_add_text(obs_properties_t props,
const char *name, const char *description,
enum obs_text_type type);
EXPORT obs_property_t obs_properties_add_path(obs_properties_t props,
const char *name, const char *description);
EXPORT obs_property_t obs_properties_add_list(obs_properties_t props,
const char *name, const char *description,
enum obs_combo_type type, enum obs_combo_format format);
EXPORT obs_property_t obs_properties_add_color(obs_properties_t props,
const char *name, const char *description);

View File

@ -361,14 +361,24 @@ uint32_t obs_source_get_output_flags(obs_source_t source)
return source ? source->info.output_flags : 0;
}
static void obs_source_deferred_update(obs_source_t source)
{
source->info.update(source->data, source->settings);
source->defer_update = false;
}
void obs_source_update(obs_source_t source, obs_data_t settings)
{
if (!source) return;
obs_data_apply(source->settings, settings);
if (source->info.update)
source->info.update(source->data, source->settings);
if (source->info.update) {
if (source->info.output_flags & OBS_SOURCE_VIDEO)
source->defer_update = true;
else
source->info.update(source->data, source->settings);
}
}
static void activate_source(obs_source_t source)
@ -476,6 +486,9 @@ void obs_source_video_tick(obs_source_t source, float seconds)
{
if (!source) return;
if (source->defer_update)
obs_source_deferred_update(source);
/* reset the filter render texture information once every frame */
if (source->filter_texrender)
texrender_reset(source->filter_texrender);

View File

@ -500,6 +500,7 @@ void obs_shutdown(void)
da_free(obs->input_types);
da_free(obs->filter_types);
da_free(obs->encoder_types);
da_free(obs->transition_types);
da_free(obs->output_types);
da_free(obs->service_types);

View File

@ -751,6 +751,9 @@ EXPORT void obs_output_set_audio_conversion(obs_output_t output,
EXPORT bool obs_output_can_begin_data_capture(obs_output_t output,
uint32_t flags);
/** Initializes encoders (if any) */
EXPORT bool obs_output_initialize_encoders(obs_output_t output, uint32_t flags);
/**
* Begins data capture from media/encoders.
*
@ -768,8 +771,13 @@ 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);
/**
* Signals that the output has stopped itself.
*
* @param output Output context
* @param code Error code (or OBS_OUTPUT_SUCCESS if not an error)
*/
EXPORT void obs_output_signal_stop(obs_output_t output, int code);
/* ------------------------------------------------------------------------- */
@ -787,7 +795,7 @@ EXPORT const char *obs_encoder_getdisplayname(const char *id,
* @param video Video output context to encode data from
* @return The video encoder context, or NULL if failed or not found.
*/
EXPORT obs_encoder_t obs_encoder_create_video(const char *id, const char *name,
EXPORT obs_encoder_t obs_video_encoder_create(const char *id, const char *name,
obs_data_t settings, video_t video);
/**
@ -799,39 +807,12 @@ EXPORT obs_encoder_t obs_encoder_create_video(const char *id, const char *name,
* @param audio Audio output context to encode data from
* @return The video encoder context, or NULL if failed or not found.
*/
EXPORT obs_encoder_t obs_encoder_create_audio(const char *id, const char *name,
EXPORT obs_encoder_t obs_audio_encoder_create(const char *id, const char *name,
obs_data_t settings, audio_t audio);
/** Destroys an encoder context */
EXPORT void obs_encoder_destroy(obs_encoder_t encoder);
/**
* Initializes the encoder and returns whether settings are valid.
* Must be called before obs_encoder_start
*/
EXPORT bool obs_encoder_initialize(obs_encoder_t encoder);
/**
* Starts encoding. This function can be called more than once, and each
* callback will receive the same encoder data.
*
* @param encoder Encoder context
* @param new_packet Callback that receives encoded packets
* @param param Callback parameter
*/
EXPORT void obs_encoder_start(obs_encoder_t encoder,
void (*new_packet)(void *param, struct encoder_packet *packet),
void *param);
/**
* Stops encoding. You must use the same callback/parameter combination that
* was used for obs_encoder_start. Only when the last callback has been
* removed will all encoding stop.
*/
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);

View File

@ -33,6 +33,7 @@ static uint64_t array_output_get_pos(void *param)
void array_output_serializer_init(struct serializer *s,
struct array_output_data *data)
{
memset(s, 0, sizeof(struct serializer));
memset(data, 0, sizeof(struct array_output_data));
s->data = data;
s->write = array_output_write;

View File

@ -257,7 +257,7 @@ char **strlist_split(const char *str, char split_ch, bool include_empty)
da_push_back(list, &new_str);
}
cur_str = next_str;
cur_str = next_str+1;
next_str = strchr(cur_str, split_ch);
}

View File

@ -61,9 +61,9 @@ static inline size_t serialize(struct serializer *s, void *data, size_t len)
{
if (s) {
if (s->write)
return s->write(s, data, len);
return s->write(s->data, data, len);
else if (s->read)
return s->read(s, data, len);
return s->read(s->data, data, len);
}
return 0;
@ -73,14 +73,14 @@ static inline uint64_t serializer_seek(struct serializer *s, int64_t offset,
enum serialize_seek_type seek_type)
{
if (s && s->seek)
return s->seek(s, offset, seek_type);
return s->seek(s->data, offset, seek_type);
return 0;
}
static inline uint64_t serializer_get_pos(struct serializer *s)
{
if (s && s->get_pos)
return s->get_pos(s);
return s->get_pos(s->data);
return 0;
}

View File

@ -35,6 +35,8 @@
#include "ui_OBSBasic.h"
#include <sstream>
using namespace std;
Q_DECLARE_METATYPE(OBSScene);
@ -42,7 +44,9 @@ Q_DECLARE_METATYPE(OBSSceneItem);
OBSBasic::OBSBasic(QWidget *parent)
: OBSMainWindow (parent),
outputTest (NULL),
outputTest (nullptr),
aac (nullptr),
x264 (nullptr),
sceneChanging (false),
resizeTimer (0),
properties (nullptr),
@ -170,6 +174,8 @@ void OBSBasic::OBSInit()
* automatically later */
obs_load_module("test-input");
obs_load_module("obs-ffmpeg");
obs_load_module("obs-x264");
obs_load_module("obs-outputs");
#ifdef __APPLE__
obs_load_module("mac-capture");
#elif _WIN32
@ -781,33 +787,35 @@ void OBSBasic::on_actionSourceDown_triggered()
{
}
void OBSBasic::OutputStart(int errorcode)
void OBSBasic::OutputStop(int errorcode)
{
if (errorcode != OBS_OUTPUT_SUCCESS) {
obs_output_destroy(outputTest);
outputTest = NULL;
} else {
ui->streamButton->setText("Stop Streaming");
}
UNUSED_PARAMETER(errorcode);
ui->streamButton->setText("Start Streaming");
}
ui->streamButton->setEnabled(true);
void OBSBasic::OutputStart()
{
ui->streamButton->setText("Stop Streaming");
}
static void OBSOutputStart(void *data, calldata_t params)
{
int code = calldata_bool(params, "errorcode");
UNUSED_PARAMETER(params);
QMetaObject::invokeMethod(static_cast<OBSBasic*>(data), "OutputStart");
}
QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
"OutputStart", Q_ARG(int, code));
static void OBSOutputStop(void *data, calldata_t params)
{
int code = (int)calldata_int(params, "errorcode");
QMetaObject::invokeMethod(static_cast<OBSBasic*>(data), "OutputStop",
Q_ARG(int, code));
}
/* TODO: lots of temporary code */
void OBSBasic::on_streamButton_clicked()
{
if (outputTest) {
obs_output_destroy(outputTest);
outputTest = NULL;
ui->streamButton->setText("Start Streaming");
if (obs_output_active(outputTest)) {
obs_output_stop(outputTest);
} else {
const char *url = config_get_string(basicConfig, "OutputTemp",
"URL");
@ -821,6 +829,14 @@ void OBSBasic::on_streamButton_clicked()
if (!url)
return;
obs_output_destroy(outputTest);
obs_encoder_destroy(aac);
obs_encoder_destroy(x264);
outputTest = nullptr;
aac = nullptr;
x264 = nullptr;
#if 0
string fullURL = url;
if (key && *key)
fullURL = fullURL + "/" + key;
@ -832,15 +848,50 @@ void OBSBasic::on_streamButton_clicked()
outputTest = obs_output_create("ffmpeg_output", "test", data);
obs_data_release(data);
#else
obs_data_t aac_settings = obs_data_create();
obs_data_t x264_settings = obs_data_create();
obs_data_t output_settings = obs_data_create();
stringstream ss;
if (!outputTest)
ss << "filler=1:crf=0:bitrate=" << vBitrate;
obs_data_setint(aac_settings, "bitrate", aBitrate);
obs_data_setint(x264_settings, "bitrate", vBitrate);
obs_data_setint(x264_settings, "buffer_size", vBitrate);
obs_data_setint(x264_settings, "keyint_sec", 2);
obs_data_setstring(x264_settings, "x264opts", ss.str().c_str());
obs_data_setstring(output_settings, "path", url);
obs_data_setstring(output_settings, "key", key);
aac = obs_audio_encoder_create("ffmpeg_aac", "blabla1",
aac_settings, obs_audio());
x264 = obs_video_encoder_create("obs_x264", "blabla2",
x264_settings, obs_video());
outputTest = obs_output_create("rtmp_output", "test",
output_settings);
obs_output_set_video_encoder(outputTest, x264);
obs_output_set_audio_encoder(outputTest, aac);
obs_data_release(aac_settings);
obs_data_release(x264_settings);
obs_data_release(output_settings);
#endif
if (!outputTest) {
OutputStop(OBS_OUTPUT_FAIL);
return;
}
signal_handler_connect(obs_output_signalhandler(outputTest),
"start", OBSOutputStart, this);
signal_handler_connect(obs_output_signalhandler(outputTest),
"stop", OBSOutputStop, this);
obs_output_start(outputTest);
ui->streamButton->setEnabled(false);
}
}

View File

@ -35,6 +35,8 @@ class OBSBasic : public OBSMainWindow {
private:
std::unordered_map<obs_source_t, int> sourceSceneRefs;
obs_output_t outputTest;
obs_encoder_t aac;
obs_encoder_t x264;
bool sceneChanging;
int previewX, previewY;
@ -61,7 +63,8 @@ private:
void InsertSceneItem(obs_sceneitem_t item);
public slots:
void OutputStart(int errorcode);
void OutputStart();
void OutputStop(int errorcode);
private slots:
void AddSceneItem(OBSSceneItem item);

View File

@ -17,6 +17,7 @@
#include <util/base.h>
#include <util/circlebuf.h>
#include <util/darray.h>
#include <obs.h>
#include <libavformat/avformat.h>
@ -30,11 +31,12 @@ struct aac_encoder {
AVCodec *aac;
AVCodecContext *context;
struct circlebuf buffers[MAX_AV_PLANES];
uint8_t *samples[MAX_AV_PLANES];
AVFrame *aframe;
int total_samples;
DARRAY(uint8_t) packet_buffer;
size_t audio_planes;
size_t audio_size;
@ -63,15 +65,14 @@ static void aac_destroy(void *data)
{
struct aac_encoder *enc = data;
for (size_t i = 0; i < MAX_AV_PLANES; i++)
circlebuf_free(&enc->buffers[i]);
if (enc->samples[0])
av_freep(&enc->samples[0]);
if (enc->context)
avcodec_close(enc->context);
if (enc->aframe)
av_frame_free(&enc->aframe);
da_free(enc->packet_buffer);
bfree(enc);
}
@ -109,6 +110,18 @@ static bool initialize_codec(struct aac_encoder *enc)
return true;
}
static void init_sizes(struct aac_encoder *enc, audio_t audio)
{
const struct audio_output_info *aoi;
enum audio_format format;
aoi = audio_output_getinfo(audio);
format = convert_ffmpeg_sample_format(enc->context->sample_fmt);
enc->audio_planes = get_audio_planes(format, aoi->speakers);
enc->audio_size = get_audio_size(format, aoi->speakers, 1);
}
static void *aac_create(obs_data_t settings, obs_encoder_t encoder)
{
struct aac_encoder *enc;
@ -120,6 +133,8 @@ static void *aac_create(obs_data_t settings, obs_encoder_t encoder)
return NULL;
}
avcodec_register_all();
enc = bzalloc(sizeof(struct aac_encoder));
enc->encoder = encoder;
enc->aac = avcodec_find_encoder(AV_CODEC_ID_AAC);
@ -128,22 +143,19 @@ static void *aac_create(obs_data_t settings, obs_encoder_t encoder)
goto fail;
}
avcodec_register(enc->aac);
enc->context = avcodec_alloc_context3(enc->aac);
if (!enc->context) {
aac_warn("aac_create", "Failed to create codec context");
goto fail;
}
enc->context->bit_rate = bitrate;
enc->context->bit_rate = bitrate * 1000;
enc->context->channels = (int)audio_output_channels(audio);
enc->context->sample_rate = audio_output_samplerate(audio);
enc->context->sample_fmt = enc->aac->sample_fmts ?
enc->aac->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
enc->audio_planes = audio_output_planes(audio);
enc->audio_size = audio_output_blocksize(audio);
init_sizes(enc, audio);
/* enable experimental FFmpeg encoder if the only one available */
enc->context->strict_std_compliance = -2;
@ -192,9 +204,12 @@ static bool do_aac_encode(struct aac_encoder *enc,
if (!got_packet)
return true;
da_resize(enc->packet_buffer, 0);
da_push_back_array(enc->packet_buffer, avpacket.data, avpacket.size);
packet->pts = rescale_ts(avpacket.pts, enc->context, time_base);
packet->dts = rescale_ts(avpacket.dts, enc->context, time_base);
packet->data = bmemdup(avpacket.data, avpacket.size);
packet->data = enc->packet_buffer.array;
packet->size = avpacket.size;
packet->type = OBS_ENCODER_AUDIO;
packet->timebase_num = 1;
@ -207,19 +222,9 @@ static bool aac_encode(void *data, struct encoder_frame *frame,
struct encoder_packet *packet, bool *received_packet)
{
struct aac_encoder *enc = data;
size_t size = frame->frames * enc->audio_size;
for (size_t i = 0; i < enc->audio_planes; i++)
circlebuf_push_back(&enc->buffers[i], frame->data[i], size);
if (enc->buffers[0].size < (size_t)enc->frame_size_bytes) {
*received_packet = false;
return true;
}
for (size_t i = 0; i < enc->audio_planes; i++)
circlebuf_pop_front(&enc->buffers[i], enc->samples[i],
enc->frame_size_bytes);
memcpy(enc->samples[i], frame->data[i], enc->frame_size_bytes);
return do_aac_encode(enc, packet, received_packet);
}
@ -234,8 +239,7 @@ static obs_properties_t aac_properties(const char *locale)
obs_properties_t props = obs_properties_create(locale);
/* TODO: locale */
obs_properties_add_int(props, "bitrate", "Bitrate",
32, 320, 32);
obs_properties_add_int(props, "bitrate", "Bitrate", 32, 320, 32);
return props;
}
@ -257,6 +261,12 @@ static bool aac_audio_info(void *data, struct audio_convert_info *info)
return true;
}
static size_t aac_frame_size(void *data)
{
struct aac_encoder *enc =data;
return enc->frame_size;
}
struct obs_encoder_info aac_encoder_info = {
.id = "ffmpeg_aac",
.type = OBS_ENCODER_AUDIO,
@ -265,6 +275,7 @@ struct obs_encoder_info aac_encoder_info = {
.create = aac_create,
.destroy = aac_destroy,
.encode = aac_encode,
.frame_size = aac_frame_size,
.defaults = aac_defaults,
.properties = aac_properties,
.extra_data = aac_extra_data,

View File

@ -760,7 +760,7 @@ static void *start_thread(void *data)
struct ffmpeg_output *output = data;
if (!try_connect(output))
obs_output_signal_start_fail(output->output,
obs_output_signal_stop(output->output,
OBS_OUTPUT_CONNECT_FAILED);
output->connecting = false;

View File

@ -2,11 +2,13 @@
OBS_DECLARE_MODULE()
extern struct obs_output_info ffmpeg_output;
extern struct obs_output_info ffmpeg_output;
extern struct obs_encoder_info aac_encoder_info;
bool obs_module_load(uint32_t obs_version)
{
obs_register_output(&ffmpeg_output);
obs_register_encoder(&aac_encoder_info);
UNUSED_PARAMETER(obs_version);
return true;

View File

@ -21,6 +21,11 @@
#include "obs-output-ver.h"
#include "rtmp-helpers.h"
/* FIXME: this is currently hard-coded to h264 and aac! ..not that we'll
* use anything else for a long time. */
// #define DEBUG_TIMESTAMPS
#define VIDEO_HEADER_SIZE 5
#define MILLISECOND_DEN 1000
@ -44,28 +49,30 @@ static void build_flv_meta_data(obs_output_t context,
char *enc = buf;
char *end = enc+sizeof(buf);
enc_str(&enc, end, "onMetaData");
*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_val(&enc, end, "duration", 0.0);
enc_num_val(&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_num_val(&enc, end, "width", (double)video_output_width(video));
enc_num_val(&enc, end, "height", (double)video_output_height(video));
enc_str_val(&enc, end, "videocodecid", "avc1");
enc_num_val(&enc, end, "videodatarate", encoder_bitrate(vencoder));
enc_num_val(&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",
enc_str_val(&enc, end, "audiocodecid", "mp4a");
enc_num_val(&enc, end, "audiodatarate", encoder_bitrate(aencoder));
enc_num_val(&enc, end, "audiosamplerate",
(double)audio_output_samplerate(audio));
enc_num(&enc, end, "audiosamplesize", 16.0);
enc_num(&enc, end, "audiochannels",
enc_num_val(&enc, end, "audiosamplesize", 16.0);
enc_num_val(&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_bool_val(&enc, end, "stereo", audio_output_channels(audio) == 2);
enc_str_val(&enc, end, "encoder", MODULE_NAME);
*enc++ = 0;
*enc++ = 0;
@ -81,19 +88,29 @@ void flv_meta_data(obs_output_t context, uint8_t **output, size_t *size)
struct serializer s;
uint8_t *meta_data;
size_t meta_data_size;
uint32_t start_pos;
array_output_serializer_init(&s, &data);
build_flv_meta_data(context, &meta_data, &meta_data_size);
/* s_write(&s, "FLV", 3);
s_w8(&s, 1);
s_w8(&s, 5);
s_wb32(&s, 9);
s_wb32(&s, 0); */
start_pos = serializer_get_pos(&s);
s_w8(&s, RTMP_PACKET_TYPE_INFO);
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);
s_wb32(&s, (uint32_t)serializer_get_pos(&s) - start_pos + 4 - 1);
*output = data.bytes.array;
*size = data.bytes.num;
@ -109,15 +126,21 @@ static uint32_t get_ms_time(struct encoder_packet *packet, int64_t val)
static void flv_video(struct serializer *s, struct encoder_packet *packet,
bool is_header)
{
int64_t offset = packet->pts - packet->dts;
int64_t offset = packet->pts - packet->dts;
int32_t time_ms = get_ms_time(packet, packet->dts);
if (!packet->data || !packet->size)
return;
s_w8(s, RTMP_PACKET_TYPE_VIDEO);
#ifdef DEBUG_TIMESTAMPS
blog(LOG_DEBUG, "Video: %lu", time_ms);
#endif
s_wb24(s, (uint32_t)packet->size + 5);
s_wb32(s, get_ms_time(packet, packet->pts));
s_wb24(s, time_ms);
s_w8(s, (time_ms >> 24) & 0x7F);
s_wb24(s, 0);
/* these are the 5 extra bytes mentioned above */
@ -133,13 +156,20 @@ static void flv_video(struct serializer *s, struct encoder_packet *packet,
static void flv_audio(struct serializer *s, struct encoder_packet *packet,
bool is_header)
{
int32_t time_ms = get_ms_time(packet, packet->dts);
if (!packet->data || !packet->size)
return;
s_w8(s, RTMP_PACKET_TYPE_AUDIO);
#ifdef DEBUG_TIMESTAMPS
blog(LOG_DEBUG, "Audio: %lu", time_ms);
#endif
s_wb24(s, (uint32_t)packet->size + 2);
s_wb32(s, get_ms_time(packet, packet->pts));
s_wb24(s, time_ms);
s_w8(s, (time_ms >> 24) & 0x7F);
s_wb24(s, 0);
/* these are the two extra bytes mentioned above */

View File

@ -1,13 +1,30 @@
#include <obs-module.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#endif
OBS_DECLARE_MODULE()
extern struct obs_output_info rtmp_output_info;
bool obs_module_load(uint32_t libobs_ver)
{
#ifdef _WIN32
WSADATA wsad;
WSAStartup(MAKEWORD(2, 2), &wsad);
#endif
obs_register_output(&rtmp_output_info);
UNUSED_PARAMETER(libobs_ver);
return true;
}
#ifdef _WIN32
void obs_module_unload(void)
{
WSACleanup();
}
#endif

View File

@ -24,19 +24,21 @@ static inline AVal *flv_str(AVal *out, const char *str)
return out;
}
static inline void enc_num(char **enc, char *end, const char *name, double val)
static inline void enc_num_val(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)
static inline void enc_bool_val(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,
static inline void enc_str_val(char **enc, char *end, const char *name,
const char *val)
{
AVal s1, s2;
@ -44,3 +46,9 @@ static inline void enc_str(char **enc, char *end, const char *name,
flv_str(&s1, name),
flv_str(&s2, val));
}
static inline void enc_str(char **enc, char *end, const char *str)
{
AVal s;
*enc = AMF_EncodeString(*enc, end, flv_str(&s, str));
}

View File

@ -17,6 +17,7 @@
#include <obs.h>
#include <obs-avc.h>
#include <util/platform.h>
#include <util/circlebuf.h>
#include <util/dstr.h>
#include <util/threading.h>
@ -24,6 +25,8 @@
#include "librtmp/log.h"
#include "flv-mux.h"
//#define FILE_TEST
struct rtmp_stream {
obs_output_t output;
@ -42,6 +45,10 @@ struct rtmp_stream {
struct dstr path, key;
struct dstr username, password;
#ifdef FILE_TEST
FILE *test;
#endif
RTMP rtmp;
};
@ -68,15 +75,11 @@ static inline void free_packets(struct rtmp_stream *stream)
}
}
static void rtmp_stream_stop(void *data);
static void rtmp_stream_destroy(void *data)
{
struct rtmp_stream *stream = data;
if (stream) {
rtmp_stream_stop(stream);
free_packets(stream);
dstr_free(&stream->path);
dstr_free(&stream->key);
@ -86,6 +89,7 @@ static void rtmp_stream_destroy(void *data)
os_event_destroy(stream->stop_event);
os_sem_destroy(stream->send_sem);
pthread_mutex_destroy(&stream->packets_mutex);
circlebuf_free(&stream->packets);
bfree(stream);
}
}
@ -117,6 +121,10 @@ static void rtmp_stream_stop(void *data)
struct rtmp_stream *stream = data;
void *ret;
#ifdef FILE_TEST
fclose(stream->test);
#endif
os_event_signal(stream->stop_event);
if (stream->connecting)
@ -133,7 +141,7 @@ static void rtmp_stream_stop(void *data)
static inline void set_rtmp_str(AVal *val, const char *str)
{
bool valid = (!str || !*str);
bool valid = (str && *str);
val->av_val = valid ? (char*)str : NULL;
val->av_len = valid ? (int)strlen(str) : 0;
}
@ -166,10 +174,14 @@ static int send_packet(struct rtmp_stream *stream,
{
uint8_t *data;
size_t size;
int ret;
int ret = 0;
flv_packet_mux(packet, &data, &size, is_header);
#ifdef FILE_TEST
fwrite(data, 1, size, stream->test);
#else
ret = RTMP_Write(&stream->rtmp, (char*)data, (int)size);
#endif
bfree(data);
obs_free_encoder_packet(packet);
@ -211,8 +223,10 @@ static void *send_thread(void *data)
if (disconnected)
free_packets(stream);
if (os_event_try(stream->stop_event) == EAGAIN)
if (os_event_try(stream->stop_event) == EAGAIN) {
pthread_detach(stream->send_thread);
obs_output_signal_stop(stream->output, OBS_OUTPUT_DISCONNECTED);
}
stream->active = false;
return NULL;
@ -226,7 +240,11 @@ static void send_meta_data(struct rtmp_stream *stream)
size_t meta_data_size;
flv_meta_data(stream->output, &meta_data, &meta_data_size);
#ifdef FILE_TEST
fwrite(meta_data, 1, meta_data_size, stream->test);
#else
RTMP_Write(&stream->rtmp, (char*)meta_data, (int)meta_data_size);
#endif
bfree(meta_data);
}
@ -234,13 +252,15 @@ 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);
uint8_t *header;
struct encoder_packet packet = {
.type = OBS_ENCODER_AUDIO,
.type = OBS_ENCODER_AUDIO,
.timebase_den = 1
};
obs_encoder_get_extra_data(aencoder, &packet.data, &packet.size);
obs_encoder_get_extra_data(aencoder, &header, &packet.size);
packet.data = bmemdup(header, packet.size);
send_packet(stream, &packet, true);
}
@ -252,18 +272,21 @@ static void send_video_header(struct rtmp_stream *stream)
size_t size;
struct encoder_packet packet = {
.type = OBS_ENCODER_VIDEO,
.timebase_den = 1
.type = OBS_ENCODER_VIDEO,
.timebase_den = 1,
.keyframe = true
};
obs_encoder_get_extra_data(vencoder, &header, &size);
packet.size = obs_parse_avc_header(&packet.data, header, size);
send_packet(stream, &packet, true);
obs_free_encoder_packet(&packet);
}
static void send_headers(struct rtmp_stream *stream)
{
#ifdef FILE_TEST
stream->test = os_fopen("D:\\bla.flv", "wb");
#endif
send_meta_data(stream);
send_audio_header(stream);
send_video_header(stream);
@ -302,8 +325,10 @@ static int init_send(struct rtmp_stream *stream)
return OBS_OUTPUT_FAIL;
}
stream->active = true;
send_headers(stream);
obs_output_begin_data_capture(stream->output, 0);
return OBS_OUTPUT_SUCCESS;
}
@ -313,6 +338,8 @@ static int try_connect(struct rtmp_stream *stream)
stream->key.array))
return OBS_OUTPUT_BAD_PATH;
RTMP_EnableWrite(&stream->rtmp);
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;
@ -323,10 +350,12 @@ static int try_connect(struct rtmp_stream *stream)
stream->rtmp.m_bSendChunkSizeInfo = true;
stream->rtmp.m_bUseNagle = true;
#ifndef FILE_TEST
if (!RTMP_Connect(&stream->rtmp, NULL))
return OBS_OUTPUT_CONNECT_FAILED;
if (!RTMP_ConnectStream(&stream->rtmp, 0))
return OBS_OUTPUT_INVALID_STREAM;
#endif
return init_send(stream);
}
@ -337,7 +366,7 @@ static void *connect_thread(void *data)
int ret = try_connect(stream);
if (ret != OBS_OUTPUT_SUCCESS)
obs_output_signal_start_fail(stream->output, ret);
obs_output_signal_stop(stream->output, ret);
if (os_event_try(stream->stop_event) == EAGAIN)
pthread_detach(stream->connect_thread);
@ -353,6 +382,8 @@ static bool rtmp_stream_start(void *data)
if (!obs_output_can_begin_data_capture(stream->output, 0))
return false;
if (!obs_output_initialize_encoders(stream->output, 0))
return false;
settings = obs_output_get_settings(stream->output);
dstr_copy(&stream->path, obs_data_getstring(settings, "path"));
@ -362,7 +393,7 @@ static bool rtmp_stream_start(void *data)
obs_data_release(settings);
return pthread_create(&stream->connect_thread, NULL, connect_thread,
stream) != 0;
stream) == 0;
}
static void rtmp_stream_data(void *data, struct encoder_packet *packet)
@ -405,5 +436,5 @@ struct obs_output_info rtmp_output_info = {
.start = rtmp_stream_start,
.stop = rtmp_stream_stop,
.encoded_packet = rtmp_stream_data,
.properties = rtmp_stream_properties
.properties = rtmp_stream_properties
};

View File

@ -190,8 +190,6 @@ static inline void set_param(struct obs_x264 *obsx264, const char *param)
static inline void apply_x264_profile(struct obs_x264 *obsx264,
const char *profile)
{
if (!*profile) profile = NULL;
if (!obsx264->context && profile) {
int ret = x264_param_apply_profile(&obsx264->params, profile);
if (ret != 0)
@ -203,9 +201,6 @@ static inline void apply_x264_profile(struct obs_x264 *obsx264,
static bool reset_x264_params(struct obs_x264 *obsx264,
const char *preset, const char *tune)
{
if (!*preset) preset = NULL;
if (!*tune) tune = NULL;
return x264_param_default_preset(&obsx264->params, preset, tune) == 0;
}
@ -278,6 +273,8 @@ static bool update_settings(struct obs_x264 *obsx264, obs_data_t settings)
apply_x264_profile(obsx264, profile);
}
obsx264->params.b_repeat_headers = false;
strlist_free(paramlist);
bfree(preset);
bfree(profile);
@ -355,18 +352,6 @@ static void *obs_x264_create(obs_data_t settings, obs_encoder_t encoder)
return obsx264;
}
static inline int drop_priority(int priority)
{
switch (priority) {
case NAL_PRIORITY_DISPOSABLE: return NAL_PRIORITY_DISPOSABLE;
case NAL_PRIORITY_LOW: return NAL_PRIORITY_LOW;
case NAL_PRIORITY_HIGH: return NAL_PRIORITY_HIGHEST;
case NAL_PRIORITY_HIGHEST: return NAL_PRIORITY_HIGHEST;
}
return NAL_PRIORITY_HIGHEST;
}
static void parse_packet(struct obs_x264 *obsx264,
struct encoder_packet *packet, x264_nal_t *nals,
int nal_count, x264_picture_t *pic_out)
@ -386,9 +371,7 @@ static void parse_packet(struct obs_x264 *obsx264,
packet->type = OBS_ENCODER_VIDEO;
packet->pts = pic_out->i_pts;
packet->dts = pic_out->i_dts;
packet->keyframe = nals[0].i_type == NAL_SLICE_IDR;
packet->priority = nals[0].i_ref_idc;
packet->drop_priority = drop_priority(nals[0].i_ref_idc);
packet->keyframe = pic_out->b_keyframe != 0;
}
static inline void init_pic_data(struct obs_x264 *obsx264, x264_picture_t *pic,

View File

@ -93,6 +93,7 @@ static void AddTestItems(obs_scene_t scene, obs_source_t source)
-(void)windowWillClose:(NSNotification *)notification
{
(void)notification;
[NSApp stop:self];
}
@end
@ -183,7 +184,7 @@ static void test()
obs_shutdown();
blog(LOG_INFO, "Number of memory leaks: %llu", bnum_allocs());
blog(LOG_INFO, "Number of memory leaks: %lu", bnum_allocs());
}
/* --------------------------------------------------- */

View File

@ -102,7 +102,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOBSD3D11_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -122,7 +121,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOBSD3D11_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -144,7 +142,6 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBOBSD3D11_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -168,7 +165,6 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBOBSD3D11_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>

View File

@ -86,7 +86,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOBSOPENGL_EXPORTS;GLEW_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<ExceptionHandling>false</ExceptionHandling>
</ClCompile>
<Link>
@ -107,7 +106,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOBSOPENGL_EXPORTS;GLEW_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<ExceptionHandling>false</ExceptionHandling>
</ClCompile>
<Link>
@ -130,7 +128,6 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBOBSOPENGL_EXPORTS;GLEW_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<ExceptionHandling>false</ExceptionHandling>
</ClCompile>
<Link>
@ -155,7 +152,6 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBOBSOPENGL_EXPORTS;GLEW_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<ExceptionHandling>false</ExceptionHandling>
</ClCompile>
<Link>

View File

@ -204,7 +204,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOBS_EXPORTS;PTW32_STATIC_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<AdditionalIncludeDirectories>../../../libobs/util/vc</AdditionalIncludeDirectories>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
@ -226,7 +225,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOBS_EXPORTS;PTW32_STATIC_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<AdditionalIncludeDirectories>../../../libobs/util/vc</AdditionalIncludeDirectories>
</ClCompile>
<Link>
@ -249,7 +247,6 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBOBS_EXPORTS;PTW32_STATIC_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<AdditionalIncludeDirectories>../../../libobs/util/vc</AdditionalIncludeDirectories>
</ClCompile>
<Link>
@ -274,7 +271,6 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBOBS_EXPORTS;PTW32_STATIC_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<AdditionalIncludeDirectories>../../../libobs/util/vc</AdditionalIncludeDirectories>
</ClCompile>
<Link>

View File

@ -485,8 +485,8 @@
<AdditionalIncludeDirectories>../../../libobs;.\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtWidgets;..\..\..\obs;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<Optimization>Disabled</Optimization>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -505,8 +505,8 @@
<AdditionalIncludeDirectories>../../../libobs;.\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtWidgets;..\..\..\obs;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<Optimization>Disabled</Optimization>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -524,7 +524,6 @@
<PreprocessorDefinitions>UNICODE;WIN32;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs;.\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtWidgets;..\..\..\obs;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<DebugInformationFormat />
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
</ClCompile>
<Link>
@ -543,7 +542,6 @@
<PreprocessorDefinitions>UNICODE;WIN32;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs;.\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtWidgets;..\..\..\obs;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<DebugInformationFormat />
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
</ClCompile>
<Link>

View File

@ -75,7 +75,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>__CLEANUP_C;WIN32;_DEBUG;_WINDOWS;PTW32_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -93,7 +92,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>__CLEANUP_C;WIN32;_DEBUG;_WINDOWS;_LIB;PTW32_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -113,7 +111,6 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>__CLEANUP_C;WIN32;NDEBUG;_WINDOWS;_LIB;PTW32_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -135,7 +132,6 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>__CLEANUP_C;WIN32;NDEBUG;_WINDOWS;_LIB;PTW32_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>

View File

@ -92,7 +92,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;TESTINPUT_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -112,7 +111,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;TESTINPUT_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -134,7 +132,6 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;TESTINPUT_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -158,7 +155,6 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;TESTINPUT_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>

View File

@ -89,7 +89,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -109,7 +108,6 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;GLEW_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -131,7 +129,6 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -155,7 +152,6 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../../libobs</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>