obs-studio/plugins/obs-ffmpeg/obs-ffmpeg.c

205 lines
4.5 KiB
C
Raw Normal View History

Revamp API and start using doxygen The API used to be designed in such a way to where it would expect exports for each individual source/output/encoder/etc. You would export functions for each and it would automatically load those functions based on a specific naming scheme from the module. The idea behind this was that I wanted to limit the usage of structures in the API so only functions could be used. It was an interesting idea in theory, but this idea turned out to be flawed in a number of ways: 1.) Requiring exports to create sources/outputs/encoders/etc meant that you could not create them by any other means, which meant that things like faruton's .net plugin would become difficult. 2.) Export function declarations could not be checked, therefore if you created a function with the wrong parameters and parameter types, the compiler wouldn't know how to check for that. 3.) Required overly complex load functions in libobs just to handle it. It makes much more sense to just have a load function that you call manually. Complexity is the bane of all good programs. 4.) It required that you have functions of specific names, which looked and felt somewhat unsightly. So, to fix these issues, I replaced it with a more commonly used API scheme, seen commonly in places like kernels and typical C libraries with abstraction. You simply create a structure that contains the callback definitions, and you pass it to a function to register that definition (such as obs_register_source), which you call in the obs_module_load of the module. It will also automatically check the structure size and ensure that it only loads the required values if the structure happened to add new values in an API change. The "main" source file for each module must include obs-module.h, and must use OBS_DECLARE_MODULE() within that source file. Also, started writing some doxygen documentation in to the main library headers. Will add more detailed documentation as I go.
2014-02-12 07:04:50 -08:00
#include <obs-module.h>
#include <util/darray.h>
2016-04-18 10:19:49 -07:00
#include <util/platform.h>
#include <libavutil/log.h>
2016-04-18 10:19:49 -07:00
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <pthread.h>
Revamp API and start using doxygen The API used to be designed in such a way to where it would expect exports for each individual source/output/encoder/etc. You would export functions for each and it would automatically load those functions based on a specific naming scheme from the module. The idea behind this was that I wanted to limit the usage of structures in the API so only functions could be used. It was an interesting idea in theory, but this idea turned out to be flawed in a number of ways: 1.) Requiring exports to create sources/outputs/encoders/etc meant that you could not create them by any other means, which meant that things like faruton's .net plugin would become difficult. 2.) Export function declarations could not be checked, therefore if you created a function with the wrong parameters and parameter types, the compiler wouldn't know how to check for that. 3.) Required overly complex load functions in libobs just to handle it. It makes much more sense to just have a load function that you call manually. Complexity is the bane of all good programs. 4.) It required that you have functions of specific names, which looked and felt somewhat unsightly. So, to fix these issues, I replaced it with a more commonly used API scheme, seen commonly in places like kernels and typical C libraries with abstraction. You simply create a structure that contains the callback definitions, and you pass it to a function to register that definition (such as obs_register_source), which you call in the obs_module_load of the module. It will also automatically check the structure size and ensure that it only loads the required values if the structure happened to add new values in an API change. The "main" source file for each module must include obs-module.h, and must use OBS_DECLARE_MODULE() within that source file. Also, started writing some doxygen documentation in to the main library headers. Will add more detailed documentation as I go.
2014-02-12 07:04:50 -08:00
OBS_DECLARE_MODULE()
2014-07-09 22:12:57 -07:00
OBS_MODULE_USE_DEFAULT_LOCALE("obs-ffmpeg", "en-US")
2018-09-11 01:51:38 -07:00
MODULE_EXPORT const char *obs_module_description(void)
{
return "FFmpeg based sources/outputs/encoders";
}
2015-03-04 10:45:50 -08:00
extern struct obs_source_info ffmpeg_source;
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.
2014-04-07 22:00:10 -07:00
extern struct obs_output_info ffmpeg_output;
extern struct obs_output_info ffmpeg_muxer;
extern struct obs_output_info replay_buffer;
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.
2014-04-07 22:00:10 -07:00
extern struct obs_encoder_info aac_encoder_info;
2017-07-31 15:55:02 -07:00
extern struct obs_encoder_info opus_encoder_info;
2016-04-18 10:19:49 -07:00
extern struct obs_encoder_info nvenc_encoder_info;
static DARRAY(struct log_context {
void *context;
char str[4096];
int print_prefix;
} *) active_log_contexts;
static DARRAY(struct log_context *) cached_log_contexts;
pthread_mutex_t log_contexts_mutex = PTHREAD_MUTEX_INITIALIZER;
static struct log_context *create_or_fetch_log_context(void *context)
{
pthread_mutex_lock(&log_contexts_mutex);
for (size_t i = 0; i < active_log_contexts.num; i++) {
if (context == active_log_contexts.array[i]->context) {
pthread_mutex_unlock(&log_contexts_mutex);
return active_log_contexts.array[i];
}
}
struct log_context *new_log_context = NULL;
size_t cnt = cached_log_contexts.num;
if (!!cnt) {
new_log_context = cached_log_contexts.array[cnt - 1];
da_pop_back(cached_log_contexts);
}
if (!new_log_context)
new_log_context = bzalloc(sizeof(struct log_context));
new_log_context->context = context;
new_log_context->str[0] = '\0';
new_log_context->print_prefix = 1;
da_push_back(active_log_contexts, &new_log_context);
pthread_mutex_unlock(&log_contexts_mutex);
return new_log_context;
}
static void destroy_log_context(struct log_context *log_context)
{
pthread_mutex_lock(&log_contexts_mutex);
da_erase_item(active_log_contexts, &log_context);
da_push_back(cached_log_contexts, &log_context);
pthread_mutex_unlock(&log_contexts_mutex);
}
static void ffmpeg_log_callback(void* context, int level, const char* format,
va_list args)
{
if (format == NULL)
return;
struct log_context *log_context = create_or_fetch_log_context(context);
char *str = log_context->str;
av_log_format_line(context, level, format, args, str + strlen(str),
(int)(sizeof(log_context->str) - strlen(str)),
&log_context->print_prefix);
int obs_level;
switch (level) {
case AV_LOG_PANIC:
case AV_LOG_FATAL:
obs_level = LOG_ERROR;
break;
case AV_LOG_ERROR:
case AV_LOG_WARNING:
obs_level = LOG_WARNING;
break;
case AV_LOG_INFO:
case AV_LOG_VERBOSE:
obs_level = LOG_INFO;
break;
case AV_LOG_DEBUG:
default:
obs_level = LOG_DEBUG;
}
if (!log_context->print_prefix)
return;
char *str_end = str + strlen(str) - 1;
while(str < str_end) {
if (*str_end != '\n')
break;
*str_end-- = '\0';
}
if (str_end <= str)
goto cleanup;
blog(obs_level, "[ffmpeg] %s", str);
cleanup:
destroy_log_context(log_context);
}
2015-03-04 10:45:50 -08:00
#ifndef __APPLE__
static const char *nvenc_check_name = "nvenc_check";
2016-04-18 10:19:49 -07:00
static bool nvenc_supported(void)
{
av_register_all();
profile_start(nvenc_check_name);
2016-04-18 10:19:49 -07:00
AVCodec *nvenc = avcodec_find_encoder_by_name("nvenc_h264");
void *lib = NULL;
bool success = false;
2016-04-18 10:19:49 -07:00
if (!nvenc) {
goto cleanup;
}
2016-04-18 10:19:49 -07:00
#if defined(_WIN32)
if (sizeof(void*) == 8) {
lib = os_dlopen("nvEncodeAPI64.dll");
} else {
lib = os_dlopen("nvEncodeAPI.dll");
}
#else
lib = os_dlopen("libnvidia-encode.so.1");
#endif
/* ------------------------------------------- */
success = !!lib;
cleanup:
if (lib)
os_dlclose(lib);
profile_end(nvenc_check_name);
return success;
2016-04-18 10:19:49 -07:00
}
#endif
bool obs_module_load(void)
{
da_init(active_log_contexts);
da_init(cached_log_contexts);
//av_log_set_callback(ffmpeg_log_callback);
2015-03-04 10:45:50 -08:00
obs_register_source(&ffmpeg_source);
Revamp API and start using doxygen The API used to be designed in such a way to where it would expect exports for each individual source/output/encoder/etc. You would export functions for each and it would automatically load those functions based on a specific naming scheme from the module. The idea behind this was that I wanted to limit the usage of structures in the API so only functions could be used. It was an interesting idea in theory, but this idea turned out to be flawed in a number of ways: 1.) Requiring exports to create sources/outputs/encoders/etc meant that you could not create them by any other means, which meant that things like faruton's .net plugin would become difficult. 2.) Export function declarations could not be checked, therefore if you created a function with the wrong parameters and parameter types, the compiler wouldn't know how to check for that. 3.) Required overly complex load functions in libobs just to handle it. It makes much more sense to just have a load function that you call manually. Complexity is the bane of all good programs. 4.) It required that you have functions of specific names, which looked and felt somewhat unsightly. So, to fix these issues, I replaced it with a more commonly used API scheme, seen commonly in places like kernels and typical C libraries with abstraction. You simply create a structure that contains the callback definitions, and you pass it to a function to register that definition (such as obs_register_source), which you call in the obs_module_load of the module. It will also automatically check the structure size and ensure that it only loads the required values if the structure happened to add new values in an API change. The "main" source file for each module must include obs-module.h, and must use OBS_DECLARE_MODULE() within that source file. Also, started writing some doxygen documentation in to the main library headers. Will add more detailed documentation as I go.
2014-02-12 07:04:50 -08:00
obs_register_output(&ffmpeg_output);
obs_register_output(&ffmpeg_muxer);
obs_register_output(&replay_buffer);
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.
2014-04-07 22:00:10 -07:00
obs_register_encoder(&aac_encoder_info);
2017-07-31 15:55:02 -07:00
obs_register_encoder(&opus_encoder_info);
#ifndef __APPLE__
2016-04-18 10:19:49 -07:00
if (nvenc_supported()) {
blog(LOG_INFO, "NVENC supported");
obs_register_encoder(&nvenc_encoder_info);
}
#endif
return true;
}
void obs_module_unload(void)
{
av_log_set_callback(av_log_default_callback);
#ifdef _WIN32
pthread_mutex_destroy(&log_contexts_mutex);
#endif
for (size_t i = 0; i < active_log_contexts.num; i++) {
bfree(active_log_contexts.array[i]);
}
for (size_t i = 0; i < cached_log_contexts.num; i++) {
bfree(cached_log_contexts.array[i]);
}
da_free(active_log_contexts);
da_free(cached_log_contexts);
}