char-rtc-obs/whip-output.c

233 lines
6.1 KiB
C

#include "whip-output.h"
static void whip_output_close_unsafe(struct whip_output *output)
{
if (output && output->whip_output) {
char_rtc_output_close(output->whip_output);
char_rtc_output_free(output->whip_output);
output->whip_output = NULL;
}
}
static const char *whip_output_getname(void *type_data)
{
UNUSED_PARAMETER(type_data);
return obs_module_text("Output.Name");
}
static void *whip_output_create(obs_data_t *settings, obs_output_t *obs_output)
{
UNUSED_PARAMETER(settings);
struct whip_output *output = bzalloc(sizeof(struct whip_output));
pthread_mutex_init_value(&output->write_mutex);
output->output = obs_output;
output->whip_output = NULL;
// This needs to be recursive due to `obs_output_signal_stop` calling
// `whip_output_total_bytes_sent` (also guarded by mutex)
if (pthread_mutex_init_recursive(&output->write_mutex) != 0)
goto fail;
return output;
fail:
pthread_mutex_destroy(&output->write_mutex);
bfree(output);
return NULL;
}
static void whip_output_destroy(void *data)
{
struct whip_output *output = data;
pthread_mutex_lock(&output->write_mutex);
whip_output_close_unsafe(output);
pthread_mutex_unlock(&output->write_mutex);
pthread_mutex_destroy(&output->write_mutex);
bfree(output);
}
static int map_rtc_error_to_obs_error(RTCOutputError error)
{
switch (error) {
case RTCOutputError_ConnectFailed:
return OBS_OUTPUT_CONNECT_FAILED;
case RTCOutputError_NetworkError:
return OBS_OUTPUT_ERROR;
default:
blog(LOG_ERROR, "Invalid whip error code: %d", error);
return OBS_OUTPUT_ERROR;
}
}
static void whip_output_error_callback(void *data, RTCOutputError error)
{
struct whip_output *output = data;
pthread_mutex_lock(&output->write_mutex);
if (output->whip_output) {
whip_output_close_unsafe(output);
obs_output_signal_stop(output->output,
map_rtc_error_to_obs_error(error));
}
pthread_mutex_unlock(&output->write_mutex);
}
static bool whip_output_start(void *data)
{
struct whip_output *output = data;
obs_service_t *service;
obs_data_t *service_settings;
const char *url, *bearer_token;
service = obs_output_get_service(output->output);
if (!service)
return false;
if (!obs_output_can_begin_data_capture(output->output, 0))
return false;
if (!obs_output_initialize_encoders(output->output, 0))
return false;
output->whip_output = char_rtc_output_new();
if (!output->whip_output) {
blog(LOG_ERROR, "Unable to initialize whip output");
return false;
}
char_rtc_output_set_error_callback(
output->whip_output,
(RTCOutputErrorCallback)whip_output_error_callback, output);
service_settings = obs_service_get_settings(service);
if (!service_settings)
return false;
url = obs_service_get_url(service);
bearer_token = obs_data_get_string(service_settings, "bearer_token");
char_rtc_output_connect(output->whip_output, url, bearer_token);
obs_output_begin_data_capture(output->output, 0);
obs_data_release(service_settings);
return true;
}
static void whip_output_stop(void *data, uint64_t ts)
{
UNUSED_PARAMETER(ts);
struct whip_output *output = data;
pthread_mutex_lock(&output->write_mutex);
whip_output_close_unsafe(output);
pthread_mutex_unlock(&output->write_mutex);
obs_output_signal_stop(output->output, OBS_OUTPUT_SUCCESS);
}
static void whip_output_data(void *data, struct encoder_packet *packet)
{
struct whip_output *output = data;
int64_t duration = 0;
bool is_audio = false;
if (packet->type == OBS_ENCODER_VIDEO) {
duration = packet->dts_usec - output->video_timestamp;
output->video_timestamp = packet->dts_usec;
} else if (packet->type == OBS_ENCODER_AUDIO) {
is_audio = true;
duration = packet->dts_usec - output->audio_timestamp;
output->audio_timestamp = packet->dts_usec;
}
pthread_mutex_lock(&output->write_mutex);
if (output->whip_output) {
if (!char_rtc_output_write(output->whip_output, packet->data,
packet->size, duration, is_audio)) {
blog(LOG_ERROR,
"Unable to write packets to whip output");
}
}
pthread_mutex_unlock(&output->write_mutex);
}
static void whip_output_defaults(obs_data_t *defaults)
{
UNUSED_PARAMETER(defaults);
}
static obs_properties_t *whip_output_properties(void *unused)
{
UNUSED_PARAMETER(unused);
obs_properties_t *props = obs_properties_create();
return props;
}
static uint64_t whip_output_total_bytes_sent(void *data)
{
struct whip_output *output = data;
pthread_mutex_lock(&output->write_mutex);
uint64_t bytes_sent = 0;
if (output->whip_output)
bytes_sent = char_rtc_output_bytes_sent(output->whip_output);
pthread_mutex_unlock(&output->write_mutex);
return bytes_sent;
}
static int whip_output_dropped_frames(void *data)
{
struct whip_output *output = data;
pthread_mutex_lock(&output->write_mutex);
uint32_t dropped_frames = 0;
if (output->whip_output)
dropped_frames =
char_rtc_output_dropped_frames(output->whip_output);
pthread_mutex_unlock(&output->write_mutex);
return dropped_frames;
}
static int whip_output_connect_time_ms(void *data)
{
struct whip_output *output = data;
pthread_mutex_lock(&output->write_mutex);
uint32_t connect_time = 0;
if (output->whip_output)
connect_time =
char_rtc_output_connect_time_ms(output->whip_output);
pthread_mutex_unlock(&output->write_mutex);
return connect_time;
}
struct obs_output_info whip_output_info = {
.id = "whip_output",
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_SERVICE,
.encoded_video_codecs = "h264",
.encoded_audio_codecs = "opus",
.get_name = whip_output_getname,
.create = whip_output_create,
.destroy = whip_output_destroy,
.start = whip_output_start,
.stop = whip_output_stop,
.encoded_packet = whip_output_data,
.get_defaults = whip_output_defaults,
.get_properties = whip_output_properties,
.get_total_bytes = whip_output_total_bytes_sent,
.get_connect_time_ms = whip_output_connect_time_ms,
.get_dropped_frames = whip_output_dropped_frames,
};
void whip_register_output()
{
obs_register_output(&whip_output_info);
}