Add packet interleaving and improve encoder API
- Add interleaving of video/audio packets for outputs that are encoded and expect both video and audio data, sorting the packets and sending them to the output when both video and audio is received. - Combine create and initialize callbacks for the encoder API callback interface.
This commit is contained in:
@@ -51,14 +51,6 @@ static bool init_encoder(struct obs_encoder *encoder, const char *name,
|
||||
if (encoder->info.defaults)
|
||||
encoder->info.defaults(encoder->settings);
|
||||
|
||||
encoder->data = encoder->info.create(encoder->settings, encoder);
|
||||
|
||||
if (!encoder->data) {
|
||||
pthread_mutex_destroy(&encoder->callbacks_mutex);
|
||||
obs_data_release(encoder->settings);
|
||||
return false;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&obs->data.encoders_mutex);
|
||||
da_push_back(obs->data.encoders, &encoder);
|
||||
pthread_mutex_unlock(&obs->data.encoders_mutex);
|
||||
@@ -188,7 +180,8 @@ static void obs_encoder_actually_destroy(obs_encoder_t encoder)
|
||||
da_free(encoder->outputs);
|
||||
pthread_mutex_unlock(&encoder->outputs_mutex);
|
||||
|
||||
encoder->info.destroy(encoder->data);
|
||||
if (encoder->data)
|
||||
encoder->info.destroy(encoder->data);
|
||||
obs_data_release(encoder->settings);
|
||||
pthread_mutex_destroy(&encoder->callbacks_mutex);
|
||||
pthread_mutex_destroy(&encoder->outputs_mutex);
|
||||
@@ -265,15 +258,16 @@ void obs_encoder_update(obs_encoder_t encoder, obs_data_t settings)
|
||||
if (!encoder) return;
|
||||
|
||||
obs_data_apply(encoder->settings, settings);
|
||||
if (encoder->info.update)
|
||||
if (encoder->info.update && encoder->data)
|
||||
encoder->info.update(encoder->data, encoder->settings);
|
||||
}
|
||||
|
||||
bool obs_encoder_get_extra_data(obs_encoder_t encoder, uint8_t **extra_data,
|
||||
size_t *size)
|
||||
{
|
||||
if (encoder && encoder->info.extra_data)
|
||||
return encoder->info.extra_data(encoder, extra_data, size);
|
||||
if (encoder && encoder->info.extra_data && encoder->data)
|
||||
return encoder->info.extra_data(encoder->data, extra_data,
|
||||
size);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -293,9 +287,11 @@ bool obs_encoder_initialize(obs_encoder_t encoder)
|
||||
if (encoder->active)
|
||||
return true;
|
||||
|
||||
encoder->initialized = encoder->info.initialize(encoder,
|
||||
encoder->settings);
|
||||
return encoder->initialized;
|
||||
if (encoder->data)
|
||||
encoder->info.destroy(encoder->data);
|
||||
|
||||
encoder->data = encoder->info.create(encoder->settings, encoder);
|
||||
return encoder->data != NULL;
|
||||
}
|
||||
|
||||
static inline size_t get_callback_idx(
|
||||
@@ -321,7 +317,7 @@ void obs_encoder_start(obs_encoder_t encoder,
|
||||
bool success = true;
|
||||
bool first = false;
|
||||
|
||||
if (!encoder || !new_packet || !encoder->initialized) return;
|
||||
if (!encoder || !new_packet || !encoder->data) return;
|
||||
|
||||
pthread_mutex_lock(&encoder->callbacks_mutex);
|
||||
|
||||
|
@@ -109,7 +109,8 @@ struct obs_encoder_info {
|
||||
*
|
||||
* @param settings Settings for the encoder
|
||||
* @param encoder OBS encoder context
|
||||
* @return Data associated with this encoder context
|
||||
* @return Data associated with this encoder context, or
|
||||
* NULL if initialization failed.
|
||||
*/
|
||||
void *(*create)(obs_data_t settings, obs_encoder_t encoder);
|
||||
|
||||
@@ -120,16 +121,6 @@ struct obs_encoder_info {
|
||||
*/
|
||||
void (*destroy)(void *data);
|
||||
|
||||
/**
|
||||
* Initializes the encoder with the specified settings
|
||||
*
|
||||
* @param data Data associated with this encoder context
|
||||
* @param settings Settings for the encoder
|
||||
* @return true if the encoder settings are valid and the
|
||||
* encoder is ready to be used, false otherwise
|
||||
*/
|
||||
bool (*initialize)(void *data, obs_data_t settings);
|
||||
|
||||
/**
|
||||
* Encodes frame(s), and outputs encoded packets as they become
|
||||
* available.
|
||||
|
@@ -259,6 +259,12 @@ extern void obs_source_video_tick(obs_source_t source, float seconds);
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* outputs */
|
||||
|
||||
struct il_packet {
|
||||
int64_t input_ts_us;
|
||||
int64_t output_ts_us;
|
||||
struct encoder_packet packet;
|
||||
};
|
||||
|
||||
struct obs_output {
|
||||
char *name;
|
||||
void *data;
|
||||
@@ -268,8 +274,13 @@ struct obs_output {
|
||||
signal_handler_t signals;
|
||||
proc_handler_t procs;
|
||||
|
||||
bool received_video;
|
||||
bool received_audio;
|
||||
int64_t first_video_ts;
|
||||
int64_t video_offset;
|
||||
int64_t audio_offset;
|
||||
pthread_mutex_t interleaved_mutex;
|
||||
DARRAY(struct encoder_packet) interleaved_packets;
|
||||
DARRAY(struct il_packet) interleaved_packets;
|
||||
|
||||
bool active;
|
||||
video_t video;
|
||||
@@ -304,7 +315,6 @@ struct obs_encoder {
|
||||
struct obs_encoder_info info;
|
||||
obs_data_t settings;
|
||||
|
||||
bool initialized;
|
||||
bool active;
|
||||
|
||||
uint32_t timebase_num;
|
||||
|
@@ -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
|
||||
@@ -222,7 +222,6 @@ void obs_register_encoder(const struct obs_encoder_info *info)
|
||||
CHECK_REQUIRED_VAL(info, getname, obs_register_encoder);
|
||||
CHECK_REQUIRED_VAL(info, create, obs_register_encoder);
|
||||
CHECK_REQUIRED_VAL(info, destroy, obs_register_encoder);
|
||||
CHECK_REQUIRED_VAL(info, initialize, obs_register_encoder);
|
||||
CHECK_REQUIRED_VAL(info, encode, obs_register_encoder);
|
||||
|
||||
REGISTER_OBS_DEF(cur_encoder_info_size, obs_encoder_info,
|
||||
|
@@ -93,10 +93,15 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void free_il_packet(struct il_packet *data)
|
||||
{
|
||||
obs_free_encoder_packet(&data->packet);
|
||||
}
|
||||
|
||||
static inline void free_packets(struct obs_output *output)
|
||||
{
|
||||
for (size_t i = 0; i < output->interleaved_packets.num; i++)
|
||||
obs_free_encoder_packet(output->interleaved_packets.array+i);
|
||||
free_il_packet(output->interleaved_packets.array+i);
|
||||
da_free(output->interleaved_packets);
|
||||
}
|
||||
|
||||
@@ -342,9 +347,89 @@ static inline struct audio_convert_info *get_audio_conversion(
|
||||
return output->audio_conversion_set ? &output->audio_conversion : NULL;
|
||||
}
|
||||
|
||||
#define MICROSECOND_DEN 1000000
|
||||
|
||||
static inline int64_t convert_packet_dts(struct encoder_packet *packet)
|
||||
{
|
||||
return packet->dts * MICROSECOND_DEN / packet->timebase_den;
|
||||
}
|
||||
|
||||
static bool prepare_interleaved_packet(struct obs_output *output,
|
||||
struct il_packet *out, struct encoder_packet *packet)
|
||||
{
|
||||
int64_t offset;
|
||||
|
||||
out->input_ts_us = convert_packet_dts(packet);
|
||||
|
||||
/* audio and video need to start at timestamp 0, and the encoders
|
||||
* may not currently be at 0 when we get data. so, we store the
|
||||
* current dts as offset and subtract that value from the dts/pts
|
||||
* of the output packet. */
|
||||
if (packet->type == OBS_ENCODER_VIDEO) {
|
||||
if (!output->received_video) {
|
||||
output->first_video_ts = out->input_ts_us;
|
||||
output->video_offset = packet->dts;
|
||||
output->received_video = true;
|
||||
}
|
||||
|
||||
offset = output->video_offset;
|
||||
} else{
|
||||
/* don't accept audio that's before the first video timestamp */
|
||||
if (!output->received_video ||
|
||||
out->input_ts_us < output->first_video_ts)
|
||||
return false;
|
||||
|
||||
if (!output->received_audio) {
|
||||
output->audio_offset = packet->dts;
|
||||
output->received_audio = true;
|
||||
}
|
||||
|
||||
offset = output->audio_offset;
|
||||
}
|
||||
|
||||
obs_duplicate_encoder_packet(&out->packet, packet);
|
||||
out->packet.dts -= offset;
|
||||
out->packet.pts -= offset;
|
||||
out->output_ts_us = convert_packet_dts(&out->packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void send_interleaved(struct obs_output *output)
|
||||
{
|
||||
struct il_packet out = output->interleaved_packets.array[0];
|
||||
da_erase(output->interleaved_packets, 0);
|
||||
|
||||
output->info.encoded_packet(output->data, &out.packet);
|
||||
free_il_packet(&out);
|
||||
}
|
||||
|
||||
static void interleave_packets(void *data, struct encoder_packet *packet)
|
||||
{
|
||||
|
||||
struct obs_output *output = data;
|
||||
struct il_packet out;
|
||||
size_t idx;
|
||||
|
||||
pthread_mutex_lock(&output->interleaved_mutex);
|
||||
|
||||
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;
|
||||
|
||||
if (out.output_ts_us < cur_packet->output_ts_us)
|
||||
break;
|
||||
}
|
||||
|
||||
da_insert(output->interleaved_packets, idx, &out);
|
||||
|
||||
/* 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)
|
||||
send_interleaved(output);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&output->interleaved_mutex);
|
||||
}
|
||||
|
||||
static void hook_data_capture(struct obs_output *output, bool encoded,
|
||||
@@ -354,6 +439,9 @@ static void hook_data_capture(struct obs_output *output, bool encoded,
|
||||
void *param;
|
||||
|
||||
if (encoded) {
|
||||
output->received_video = false;
|
||||
output->received_video = false;
|
||||
|
||||
encoded_callback = (has_video && has_audio) ?
|
||||
interleave_packets : output->info.encoded_packet;
|
||||
param = (has_video && has_audio) ? output : output->data;
|
||||
|
Reference in New Issue
Block a user