Skip to content

Commit

Permalink
Merge pull request #885 from exeldro/ptz
Browse files Browse the repository at this point in the history
Add ptz to ndi source
  • Loading branch information
paulpv committed Nov 8, 2023
2 parents 02b08ed + afaea9f commit de5dcaa
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 55 deletions.
4 changes: 4 additions & 0 deletions data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ NDIPlugin.SourceProps.Latency="Latency Mode"
NDIPlugin.SourceProps.Latency.Normal="Normal (safe)"
NDIPlugin.SourceProps.Latency.Low="Low (experimental)"
NDIPlugin.SourceProps.Audio="Enable audio"
NDIPlugin.SourceProps.PTZ="Pan Tilt Zoom"
NDIPlugin.SourceProps.Pan="Pan"
NDIPlugin.SourceProps.Tilt="Tilt"
NDIPlugin.SourceProps.Zoom="Zoom"
NDIPlugin.BWMode.Highest="Highest"
NDIPlugin.BWMode.Lowest="Lowest"
NDIPlugin.BWMode.AudioOnly="Audio Only"
Expand Down
177 changes: 122 additions & 55 deletions src/obs-ndi-source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define PROP_YUV_COLORSPACE "yuv_colorspace"
#define PROP_LATENCY "latency"
#define PROP_AUDIO "ndi_audio"
#define PROP_PTZ "ndi_ptz"
#define PROP_PAN "ndi_pan"
#define PROP_TILT "ndi_tilt"
#define PROP_ZOOM "ndi_zoom"

#define PROP_BW_HIGHEST 0
#define PROP_BW_LOWEST 1
Expand Down Expand Up @@ -72,6 +76,9 @@ struct ndi_source {
bool alpha_filter_enabled;
bool audio_enabled;
os_performance_token_t *perf_token;
char *ndi_name;
int bandwidth;
bool hwAccelEnabled;
};

static obs_source_t *find_filter_by_id(obs_source_t *context, const char *id)
Expand Down Expand Up @@ -178,7 +185,6 @@ obs_properties_t *ndi_source_getproperties(void *data)
UNUSED_PARAMETER(data);

obs_properties_t *props = obs_properties_create();
obs_properties_set_flags(props, OBS_PROPERTIES_DEFER_UPDATE);

obs_property_t *source_list = obs_properties_add_list(
props, PROP_SOURCE,
Expand Down Expand Up @@ -293,6 +299,22 @@ obs_properties_t *ndi_source_getproperties(void *data)
obs_properties_add_bool(props, PROP_AUDIO,
obs_module_text("NDIPlugin.SourceProps.Audio"));

obs_properties_t *group = obs_properties_create();

obs_properties_add_float_slider(
group, PROP_PAN, obs_module_text("NDIPlugin.SourceProps.Pan"),
-1.0, 1.0, 0.001);
obs_properties_add_float_slider(
group, PROP_TILT, obs_module_text("NDIPlugin.SourceProps.Tilt"),
-1.0, 1.0, 0.001);
obs_properties_add_float_slider(
group, PROP_ZOOM, obs_module_text("NDIPlugin.SourceProps.Zoom"),
0.0, 1.0, 0.001);

obs_properties_add_group(props, PROP_PTZ,
obs_module_text("NDIPlugin.SourceProps.PTZ"),
OBS_GROUP_CHECKABLE, group);

obs_properties_add_button(props, "ndi_website", "NDI.NewTek.com",
[](obs_properties_t *pps,
obs_property_t *prop, void *private_data) {
Expand Down Expand Up @@ -326,6 +348,17 @@ void ndi_source_getdefaults(obs_data_t *settings)
obs_data_set_default_bool(settings, PROP_AUDIO, true);
}

void ndi_source_ptz(struct ndi_source *s, obs_data_t *settings)
{
if (!obs_data_get_bool(settings, PROP_PTZ))
return;
float pan = (float)obs_data_get_double(settings, PROP_PAN);
float tilt = (float)obs_data_get_double(settings, PROP_TILT);
ndiLib->recv_ptz_pan_tilt(s->ndi_receiver, pan, tilt);
float zoom = (float)obs_data_get_double(settings, PROP_ZOOM);
ndiLib->recv_ptz_zoom(s->ndi_receiver, zoom);
}

void *ndi_source_poll_audio_video(void *data)
{
auto s = (struct ndi_source *)data;
Expand All @@ -337,6 +370,7 @@ void *ndi_source_poll_audio_video(void *data)
obs_source_audio obs_audio_frame = {};
NDIlib_video_frame_v2_t video_frame;
obs_source_frame obs_video_frame = {};
bool connected = false;

if (s->perf_token) {
os_end_high_performance(s->perf_token);
Expand All @@ -346,10 +380,19 @@ void *ndi_source_poll_audio_video(void *data)
NDIlib_frame_type_e frame_received = NDIlib_frame_type_none;
while (s->running) {
if (ndiLib->recv_get_no_connections(s->ndi_receiver) == 0) {
if (connected)
connected = false;
std::this_thread::sleep_for(
std::chrono::milliseconds(100));
continue;
}
if (!connected) {
connected = true;
obs_data_t *settings =
obs_source_get_settings(s->source);
ndi_source_ptz(s, settings);
obs_data_release(settings);
}

frame_received =
ndiLib->recv_capture_v3(s->ndi_receiver, &video_frame,
Expand Down Expand Up @@ -475,14 +518,85 @@ void ndi_source_update(void *data, obs_data_t *settings)
{
auto s = (struct ndi_source *)data;

if (s->running) {
const char *ndi_name = obs_data_get_string(settings, PROP_SOURCE);
long long bandwidth = obs_data_get_int(settings, PROP_BANDWIDTH);
bool hwAccelEnabled = obs_data_get_bool(settings, PROP_HW_ACCEL);
if (!s->ndi_name || strcmp(ndi_name, s->ndi_name) != 0 ||
s->bandwidth != bandwidth) {
s->bandwidth = bandwidth;
bfree(s->ndi_name);
s->ndi_name = bstrdup(ndi_name);
if (s->running) {
s->running = false;
pthread_join(s->av_thread, NULL);
}
s->running = false;
pthread_join(s->av_thread, NULL);
ndiLib->recv_destroy(s->ndi_receiver);

NDIlib_recv_create_v3_t recv_desc;
recv_desc.source_to_connect_to.p_ndi_name = ndi_name;
recv_desc.allow_video_fields = true;
recv_desc.color_format = NDIlib_recv_color_format_UYVY_BGRA;

switch (obs_data_get_int(settings, PROP_BANDWIDTH)) {
case PROP_BW_HIGHEST:
default:
recv_desc.bandwidth = NDIlib_recv_bandwidth_highest;
break;
case PROP_BW_LOWEST:
recv_desc.bandwidth = NDIlib_recv_bandwidth_lowest;
break;
case PROP_BW_AUDIO_ONLY:
recv_desc.bandwidth = NDIlib_recv_bandwidth_audio_only;
obs_source_output_video(s->source, blank_video_frame());
break;
}

s->ndi_receiver = ndiLib->recv_create_v3(&recv_desc);
if (s->ndi_receiver) {
if (hwAccelEnabled) {
NDIlib_metadata_frame_t hwAccelMetadata;
hwAccelMetadata.p_data =
(char *)"<ndi_hwaccel enabled=\"true\"/>";
ndiLib->recv_send_metadata(s->ndi_receiver,
&hwAccelMetadata);
}
s->hwAccelEnabled = hwAccelEnabled;

s->running = true;
pthread_create(&s->av_thread, nullptr,
ndi_source_poll_audio_video, data);

blog(LOG_INFO, "started A/V threads for source '%s'",
recv_desc.source_to_connect_to.p_ndi_name);

// Update tally status
Config *conf = Config::Current();
s->tally.on_preview = conf->TallyPreviewEnabled &&
obs_source_showing(s->source);
s->tally.on_program = conf->TallyProgramEnabled &&
obs_source_active(s->source);
ndiLib->recv_set_tally(s->ndi_receiver, &s->tally);
} else {
blog(LOG_ERROR,
"can't create a receiver for NDI source '%s'",
ndi_name);
}
}
s->running = false;
ndiLib->recv_destroy(s->ndi_receiver);
if (s->ndi_receiver) {
if (s->hwAccelEnabled != hwAccelEnabled) {
NDIlib_metadata_frame_t hwAccelMetadata;
hwAccelMetadata.p_data =
hwAccelEnabled
? (char *)"<ndi_hwaccel enabled=\"true\"/>"
: (char *)"<ndi_hwaccel enabled=\"false\"/>";
ndiLib->recv_send_metadata(s->ndi_receiver,
&hwAccelMetadata);

bool hwAccelEnabled = obs_data_get_bool(settings, PROP_HW_ACCEL);
s->hwAccelEnabled = hwAccelEnabled;
}
ndi_source_ptz(s, settings);
}

s->alpha_filter_enabled = obs_data_get_bool(settings, PROP_FIX_ALPHA);
// Don't persist this value in settings
Expand All @@ -503,26 +617,6 @@ void ndi_source_update(void *data, obs_data_t *settings)
}
}

NDIlib_recv_create_v3_t recv_desc;
recv_desc.source_to_connect_to.p_ndi_name =
obs_data_get_string(settings, PROP_SOURCE);
recv_desc.allow_video_fields = true;
recv_desc.color_format = NDIlib_recv_color_format_UYVY_BGRA;

switch (obs_data_get_int(settings, PROP_BANDWIDTH)) {
case PROP_BW_HIGHEST:
default:
recv_desc.bandwidth = NDIlib_recv_bandwidth_highest;
break;
case PROP_BW_LOWEST:
recv_desc.bandwidth = NDIlib_recv_bandwidth_lowest;
break;
case PROP_BW_AUDIO_ONLY:
recv_desc.bandwidth = NDIlib_recv_bandwidth_audio_only;
obs_source_output_video(s->source, blank_video_frame());
break;
}

s->sync_mode = (int)obs_data_get_int(settings, PROP_SYNC);
// if sync mode is set to the unsupported "Internal" mode, set it
// to "Source Timing" mode and apply that change to the settings data
Expand All @@ -542,35 +636,7 @@ void ndi_source_update(void *data, obs_data_t *settings)
obs_source_set_async_unbuffered(s->source, is_unbuffered);

s->audio_enabled = obs_data_get_bool(settings, PROP_AUDIO);

s->ndi_receiver = ndiLib->recv_create_v3(&recv_desc);
if (s->ndi_receiver) {
if (hwAccelEnabled) {
NDIlib_metadata_frame_t hwAccelMetadata;
hwAccelMetadata.p_data =
(char *)"<ndi_hwaccel enabled=\"true\"/>";
ndiLib->recv_send_metadata(s->ndi_receiver,
&hwAccelMetadata);
}

s->running = true;
pthread_create(&s->av_thread, nullptr,
ndi_source_poll_audio_video, data);

blog(LOG_INFO, "started A/V threads for source '%s'",
recv_desc.source_to_connect_to.p_ndi_name);

// Update tally status
Config *conf = Config::Current();
s->tally.on_preview = conf->TallyPreviewEnabled &&
obs_source_showing(s->source);
s->tally.on_program = conf->TallyProgramEnabled &&
obs_source_active(s->source);
ndiLib->recv_set_tally(s->ndi_receiver, &s->tally);
} else {
blog(LOG_ERROR, "can't create a receiver for NDI source '%s'",
recv_desc.source_to_connect_to.p_ndi_name);
}
obs_source_set_audio_active(s->source, s->audio_enabled);
}

void ndi_source_shown(void *data)
Expand Down Expand Up @@ -629,6 +695,7 @@ void ndi_source_destroy(void *data)
s->running = false;
pthread_join(s->av_thread, NULL);
ndiLib->recv_destroy(s->ndi_receiver);
bfree(s->ndi_name);
bfree(s);
}

Expand Down

0 comments on commit de5dcaa

Please sign in to comment.