diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index 75fa8331c..0e1b00a4e 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -328,14 +328,18 @@ bool OBSBasic::InitOutputs() bool OBSBasic::InitEncoders() { - aac = obs_audio_encoder_create("ffmpeg_aac", "aac", nullptr); - if (!aac) - return false; - x264 = obs_video_encoder_create("obs_x264", "h264", nullptr); if (!x264) return false; + aac = obs_audio_encoder_create("libfdk_aac", "aac", nullptr); + + if(!aac) + aac = obs_audio_encoder_create("ffmpeg_aac", "aac", nullptr); + + if (!aac) + return false; + return true; } @@ -463,6 +467,7 @@ void OBSBasic::OBSInit() * automatically later */ obs_load_module("test-input"); obs_load_module("obs-ffmpeg"); + obs_load_module("obs-libfdk"); obs_load_module("obs-x264"); obs_load_module("obs-outputs"); obs_load_module("rtmp-services"); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 56b474076..d5a9e4c4c 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -14,6 +14,7 @@ elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") endif() add_subdirectory(obs-x264) +add_subdirectory(obs-libfdk) add_subdirectory(obs-ffmpeg) add_subdirectory(obs-outputs) add_subdirectory(rtmp-services) diff --git a/plugins/obs-libfdk/CMakeLists.txt b/plugins/obs-libfdk/CMakeLists.txt new file mode 100644 index 000000000..4b63548e8 --- /dev/null +++ b/plugins/obs-libfdk/CMakeLists.txt @@ -0,0 +1,21 @@ +project(obs-libfdk) + +find_package(Libfdk QUIET) +if(NOT LIBFDK_FOUND) + message(STATUS "Libfdk not found - obs-libfdk plugin disabled") + return() +endif() + +include_directories(${LIBFDK_INCLUDE_DIRS}) +add_definitions(${LIBFDK_DEFINITIONS}) + +set(obs-libfdk_SOURCES + obs-libfdk.c) + +add_library(obs-libfdk MODULE + ${obs-libfdk_SOURCES}) +target_link_libraries(obs-libfdk + libobs + ${LIBFDK_LIBRARIES}) + +install_obs_plugin(obs-libfdk) diff --git a/plugins/obs-libfdk/obs-libfdk.c b/plugins/obs-libfdk/obs-libfdk.c new file mode 100644 index 000000000..77369abc6 --- /dev/null +++ b/plugins/obs-libfdk/obs-libfdk.c @@ -0,0 +1,311 @@ +#include + +#ifdef DEBUG +# ifndef _DEBUG +# define _DEBUG +# endif +# undef DEBUG +#endif + +#include + + +static const char *libfdk_get_error(AACENC_ERROR err) +{ + switch(err) { + case AACENC_OK: + return "No error"; + case AACENC_INVALID_HANDLE: + return "Invalid handle"; + case AACENC_MEMORY_ERROR: + return "Memory allocation error"; + case AACENC_UNSUPPORTED_PARAMETER: + return "Unsupported parameter"; + case AACENC_INVALID_CONFIG: + return "Invalid config"; + case AACENC_INIT_ERROR: + return "Initialization error"; + case AACENC_INIT_AAC_ERROR: + return "AAC library initialization error"; + case AACENC_INIT_SBR_ERROR: + return "SBR library initialization error"; + case AACENC_INIT_TP_ERROR: + return "Transport library initialization error"; + case AACENC_INIT_META_ERROR: + return "Metadata library initialization error"; + case AACENC_ENCODE_ERROR: + return "Encoding error"; + case AACENC_ENCODE_EOF: + return "End of file"; + default: + return "Unknown error"; + } +} + + +typedef struct libfdk_encoder { + obs_encoder_t encoder; + + int channels, sample_rate; + + HANDLE_AACENCODER fdkhandle; + AACENC_InfoStruct info; + + uint64_t total_samples; + + int frame_size_bytes; + + uint8_t *packet_buffer; + int packet_buffer_size; +} libfdk_encoder_t; + +static const char *libfdk_getname(const char *locale) +{ + UNUSED_PARAMETER(locale); + return "libfdk aac encoder"; +} + +static obs_properties_t libfdk_properties(const char *locale) +{ + obs_properties_t props = obs_properties_create(locale); + + obs_properties_add_int(props, "bitrate", "Bitrate", 32, 256, 32); + obs_properties_add_bool(props, "afterburner", "Enable AAC Afterburner"); + + return props; +} + +static void libfdk_defaults(obs_data_t settings) +{ + obs_data_set_default_int(settings, "bitrate", 128); + obs_data_set_default_bool(settings, "afterburner", true); +} + +#define CHECK_LIBFDK(r) \ + if((err = (r)) != AACENC_OK) { \ + blog(LOG_ERROR, #r " failed: %s", libfdk_get_error(err)); \ + goto fail; \ + } + +static void *libfdk_create(obs_data_t settings, obs_encoder_t encoder) +{ + bool hasFdkHandle = false; + libfdk_encoder_t *enc = 0; + int bitrate = (int)obs_data_getint(settings, "bitrate") * 1000; + int afterburner = obs_data_getbool(settings, "afterburner") ? 1 : 0; + audio_t audio = obs_encoder_audio(encoder); + int mode = 0; + AACENC_ERROR err; + + if (!bitrate) { + blog(LOG_ERROR, "Invalid bitrate"); + return NULL; + } + + enc = bzalloc(sizeof(libfdk_encoder_t)); + enc->encoder = encoder; + + enc->channels = (int)audio_output_channels(audio); + enc->sample_rate = audio_output_samplerate(audio); + + switch(enc->channels) { + case 1: + mode = MODE_1; + break; + case 2: + mode = MODE_2; + break; + case 3: + mode = MODE_1_2; + break; + case 4: + mode = MODE_1_2_1; + break; + case 5: + mode = MODE_1_2_2; + break; + case 6: + mode = MODE_1_2_2_1; + break; + default: + blog(LOG_ERROR, "Invalid channel count"); + goto fail; + } + + CHECK_LIBFDK(aacEncOpen(&enc->fdkhandle, 0, enc->channels)); + hasFdkHandle = true; + + CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_AOT, + 2)); // MPEG-4 AAC-LC + CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_SAMPLERATE, + enc->sample_rate)); + CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_CHANNELMODE, mode)); + CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_CHANNELORDER, 1)); + CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_BITRATEMODE, 0)); + CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_BITRATE, bitrate)); + CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_TRANSMUX, 0)); + CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_AFTERBURNER, + afterburner)); + + CHECK_LIBFDK(aacEncEncode(enc->fdkhandle, NULL, NULL, NULL, NULL)); + + CHECK_LIBFDK(aacEncInfo(enc->fdkhandle, &enc->info)); + + enc->frame_size_bytes = enc->info.frameLength * 2 * enc->channels; + + enc->packet_buffer_size = enc->channels * 768; + if(enc->packet_buffer_size < 8192) + enc->packet_buffer_size = 8192; + + enc->packet_buffer = bmalloc(enc->packet_buffer_size); + + blog(LOG_INFO, "libfdk_aac encoder created"); + + return enc; + +fail: + + if(hasFdkHandle) + aacEncClose(&enc->fdkhandle); + + if(enc->packet_buffer) + bfree(enc->packet_buffer); + + if(enc) + bfree(enc); + + blog(LOG_WARNING, "libfdk_aac encoder creation failed"); + + return 0; +} + +static void libfdk_destroy(void *data) +{ + libfdk_encoder_t *enc = data; + + aacEncClose(&enc->fdkhandle); + + bfree(enc->packet_buffer); + bfree(enc); + + blog(LOG_INFO, "libfdk_aac encoder destroyed"); +} + +static bool libfdk_encode(void *data, struct encoder_frame *frame, + struct encoder_packet *packet, bool *received_packet) +{ + libfdk_encoder_t *enc = data; + + AACENC_BufDesc in_buf = { 0 }; + AACENC_BufDesc out_buf = { 0 }; + AACENC_InArgs in_args = { 0 }; + AACENC_OutArgs out_args = { 0 }; + int in_identifier = IN_AUDIO_DATA; + int in_size, in_elem_size; + int out_identifier = OUT_BITSTREAM_DATA; + int out_size, out_elem_size; + void *in_ptr; + void *out_ptr; + AACENC_ERROR err; + + + in_ptr = frame->data[0]; + in_size = enc->frame_size_bytes; + in_elem_size = 2; + + in_args.numInSamples = enc->info.frameLength * enc->channels; + in_buf.numBufs = 1; + in_buf.bufs = &in_ptr; + in_buf.bufferIdentifiers = &in_identifier; + in_buf.bufSizes = &in_size; + in_buf.bufElSizes = &in_elem_size; + + out_ptr = enc->packet_buffer; + out_size = enc->packet_buffer_size; + out_elem_size = 1; + + out_buf.numBufs = 1; + out_buf.bufs = &out_ptr; + out_buf.bufferIdentifiers = &out_identifier; + out_buf.bufSizes = &out_size; + out_buf.bufElSizes = &out_elem_size; + + if((err = aacEncEncode(enc->fdkhandle, &in_buf, &out_buf, &in_args, + &out_args)) != AACENC_OK) { + blog(LOG_ERROR, "Failed to encode frame: %s", libfdk_get_error(err)); + return false; + } + + enc->total_samples += enc->info.frameLength; + + if(out_args.numOutBytes == 0) { + *received_packet = false; + return true; + } + + *received_packet = true; + + packet->pts = enc->total_samples - + enc->info.encoderDelay; // TODO: Just a guess, find out if that's actualy right + packet->dts = enc->total_samples - enc->info.encoderDelay; + packet->data = enc->packet_buffer; + packet->size = out_args.numOutBytes; + packet->type = OBS_ENCODER_AUDIO; + packet->timebase_num = 1; + packet->timebase_den = enc->sample_rate; + + return true; +} + +static bool libfdk_extra_data(void *data, uint8_t **extra_data, size_t *size) +{ + libfdk_encoder_t *enc = data; + + *size = enc->info.confSize; + *extra_data = enc->info.confBuf; + + return true; +} + +static bool libfdk_audio_info(void *data, struct audio_convert_info *info) +{ + UNUSED_PARAMETER(data); + + memset(info, 0, sizeof(struct audio_convert_info)); + info->format = AUDIO_FORMAT_16BIT; + + return true; +} + +static size_t libfdk_frame_size(void *data) +{ + libfdk_encoder_t *enc = data; + + return enc->info.frameLength; +} + +struct obs_encoder_info obs_libfdk_encoder = { + .id = "libfdk_aac", + .type = OBS_ENCODER_AUDIO, + .codec = "AAC", + .getname = libfdk_getname, + .create = libfdk_create, + .destroy = libfdk_destroy, + .encode = libfdk_encode, + .frame_size = libfdk_frame_size, + .defaults = libfdk_defaults, + .properties = libfdk_properties, + .extra_data = libfdk_extra_data, + .audio_info = libfdk_audio_info +}; + +bool obs_module_load(uint32_t libobs_ver) +{ + UNUSED_PARAMETER(libobs_ver); + + obs_register_encoder(&obs_libfdk_encoder); + + return true; +} + +OBS_DECLARE_MODULE()