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>
|
2016-04-18 10:19:49 -07:00
|
|
|
#include <util/platform.h>
|
2018-04-08 12:05:22 -07:00
|
|
|
#include <libavutil/avutil.h>
|
2016-04-18 10:19:49 -07:00
|
|
|
#include <libavcodec/avcodec.h>
|
2018-01-18 01:34:15 -08:00
|
|
|
#include <libavformat/avformat.h>
|
2019-07-09 08:44:04 -07:00
|
|
|
|
|
|
|
#include "obs-ffmpeg-config.h"
|
2018-01-17 09:22:13 -08:00
|
|
|
|
2019-02-05 00:33:48 -08:00
|
|
|
#ifdef _WIN32
|
|
|
|
#include <dxgi.h>
|
|
|
|
#include <util/dstr.h>
|
|
|
|
#include <util/windows/win-version.h>
|
|
|
|
#endif
|
|
|
|
|
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";
|
|
|
|
}
|
2014-01-19 02:16:41 -08:00
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
extern struct obs_source_info ffmpeg_source;
|
|
|
|
extern struct obs_output_info ffmpeg_output;
|
|
|
|
extern struct obs_output_info ffmpeg_muxer;
|
2020-03-24 19:27:07 -07:00
|
|
|
extern struct obs_output_info ffmpeg_mpegts_muxer;
|
2019-06-22 22:13:45 -07:00
|
|
|
extern struct obs_output_info replay_buffer;
|
2020-10-13 13:50:47 -07:00
|
|
|
extern struct obs_output_info ffmpeg_hls_muxer;
|
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;
|
2014-01-19 02:16:41 -08:00
|
|
|
|
2018-04-08 12:05:22 -07:00
|
|
|
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(55, 27, 100)
|
|
|
|
#define LIBAVUTIL_VAAPI_AVAILABLE
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef LIBAVUTIL_VAAPI_AVAILABLE
|
|
|
|
extern struct obs_encoder_info vaapi_encoder_info;
|
|
|
|
#endif
|
|
|
|
|
2018-01-17 09:22:13 -08:00
|
|
|
#ifndef __APPLE__
|
|
|
|
|
2018-01-17 04:20:34 -08:00
|
|
|
static const char *nvenc_check_name = "nvenc_check";
|
|
|
|
|
2019-02-05 00:33:48 -08:00
|
|
|
#ifdef _WIN32
|
2019-10-16 03:19:09 -07:00
|
|
|
static const int blacklisted_adapters[] = {
|
|
|
|
0x1298, // GK208M [GeForce GT 720M]
|
|
|
|
0x1140, // GF117M [GeForce 610M/710M/810M/820M / GT 620M/625M/630M/720M]
|
|
|
|
0x1293, // GK208M [GeForce GT 730M]
|
|
|
|
0x1290, // GK208M [GeForce GT 730M]
|
|
|
|
0x0fe1, // GK107M [GeForce GT 730M]
|
|
|
|
0x0fdf, // GK107M [GeForce GT 740M]
|
|
|
|
0x1294, // GK208M [GeForce GT 740M]
|
|
|
|
0x1292, // GK208M [GeForce GT 740M]
|
|
|
|
0x0fe2, // GK107M [GeForce GT 745M]
|
|
|
|
0x0fe3, // GK107M [GeForce GT 745M]
|
|
|
|
0x1140, // GF117M [GeForce 610M/710M/810M/820M / GT 620M/625M/630M/720M]
|
|
|
|
0x0fed, // GK107M [GeForce 820M]
|
|
|
|
0x1340, // GM108M [GeForce 830M]
|
|
|
|
0x1393, // GM107M [GeForce 840M]
|
|
|
|
0x1341, // GM108M [GeForce 840M]
|
|
|
|
0x1398, // GM107M [GeForce 845M]
|
|
|
|
0x1390, // GM107M [GeForce 845M]
|
|
|
|
0x1344, // GM108M [GeForce 845M]
|
|
|
|
0x1299, // GK208BM [GeForce 920M]
|
|
|
|
0x134f, // GM108M [GeForce 920MX]
|
|
|
|
0x134e, // GM108M [GeForce 930MX]
|
|
|
|
0x1349, // GM108M [GeForce 930M]
|
|
|
|
0x1346, // GM108M [GeForce 930M]
|
|
|
|
0x179c, // GM107 [GeForce 940MX]
|
|
|
|
0x139c, // GM107M [GeForce 940M]
|
|
|
|
0x1347, // GM108M [GeForce 940M]
|
|
|
|
0x134d, // GM108M [GeForce 940MX]
|
|
|
|
0x134b, // GM108M [GeForce 940MX]
|
|
|
|
0x1399, // GM107M [GeForce 945M]
|
|
|
|
0x1348, // GM108M [GeForce 945M / 945A]
|
|
|
|
0x1d01, // GP108 [GeForce GT 1030]
|
|
|
|
0x0fc5, // GK107 [GeForce GT 1030]
|
|
|
|
0x174e, // GM108M [GeForce MX110]
|
|
|
|
0x174d, // GM108M [GeForce MX130]
|
|
|
|
0x1d10, // GP108M [GeForce MX150]
|
|
|
|
0x1d12, // GP108M [GeForce MX150]
|
|
|
|
0x1d11, // GP108M [GeForce MX230]
|
|
|
|
0x1d13, // GP108M [GeForce MX250]
|
|
|
|
0x1d52, // GP108BM [GeForce MX250]
|
2020-10-28 23:51:09 -07:00
|
|
|
0x1c94, // GP107 [GeForce MX350]
|
2019-10-16 03:19:09 -07:00
|
|
|
0x137b, // GM108GLM [Quadro M520 Mobile]
|
|
|
|
0x1d33, // GP108GLM [Quadro P500 Mobile]
|
|
|
|
0x137a, // GM108GLM [Quadro K620M / Quadro M500M]
|
2019-02-05 00:33:48 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
static const size_t num_blacklisted =
|
|
|
|
sizeof(blacklisted_adapters) / sizeof(blacklisted_adapters[0]);
|
|
|
|
|
2019-10-16 03:19:09 -07:00
|
|
|
static bool is_blacklisted(const int device_id)
|
2019-02-05 00:33:48 -08:00
|
|
|
{
|
|
|
|
for (size_t i = 0; i < num_blacklisted; i++) {
|
2019-10-16 03:19:09 -07:00
|
|
|
const int blacklisted_adapter = blacklisted_adapters[i];
|
|
|
|
if (device_id == blacklisted_adapter) {
|
2019-02-05 00:33:48 -08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-06-22 22:13:45 -07:00
|
|
|
typedef HRESULT(WINAPI *create_dxgi_proc)(const IID *, IDXGIFactory1 **);
|
2019-02-05 00:33:48 -08:00
|
|
|
|
|
|
|
static bool nvenc_device_available(void)
|
|
|
|
{
|
|
|
|
static HMODULE dxgi = NULL;
|
|
|
|
static create_dxgi_proc create = NULL;
|
|
|
|
IDXGIFactory1 *factory;
|
|
|
|
IDXGIAdapter1 *adapter;
|
|
|
|
bool available = false;
|
|
|
|
HRESULT hr;
|
|
|
|
UINT i = 0;
|
|
|
|
|
|
|
|
if (!dxgi) {
|
|
|
|
dxgi = GetModuleHandleW(L"dxgi");
|
|
|
|
if (!dxgi) {
|
|
|
|
dxgi = LoadLibraryW(L"dxgi");
|
|
|
|
if (!dxgi) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!create) {
|
|
|
|
create = (create_dxgi_proc)GetProcAddress(dxgi,
|
2019-06-22 22:13:45 -07:00
|
|
|
"CreateDXGIFactory1");
|
2019-02-05 00:33:48 -08:00
|
|
|
if (!create) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hr = create(&IID_IDXGIFactory1, &factory);
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (factory->lpVtbl->EnumAdapters1(factory, i++, &adapter) == S_OK) {
|
|
|
|
DXGI_ADAPTER_DESC desc;
|
|
|
|
|
|
|
|
hr = adapter->lpVtbl->GetDesc(adapter, &desc);
|
|
|
|
adapter->lpVtbl->Release(adapter);
|
|
|
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-10-16 03:19:09 -07:00
|
|
|
// 0x10de = NVIDIA Corporation
|
|
|
|
if (desc.VendorId == 0x10de && !is_blacklisted(desc.DeviceId)) {
|
2019-02-05 00:33:48 -08:00
|
|
|
available = true;
|
|
|
|
goto finish;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
finish:
|
|
|
|
factory->lpVtbl->Release(factory);
|
|
|
|
return available;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-02-05 18:20:16 -08:00
|
|
|
#ifdef _WIN32
|
|
|
|
extern bool load_nvenc_lib(void);
|
|
|
|
#endif
|
|
|
|
|
2016-04-18 10:19:49 -07:00
|
|
|
static bool nvenc_supported(void)
|
|
|
|
{
|
2019-07-28 18:31:43 -07:00
|
|
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
|
2018-01-18 01:34:15 -08:00
|
|
|
av_register_all();
|
2019-07-28 18:31:43 -07:00
|
|
|
#endif
|
2018-01-18 01:34:15 -08:00
|
|
|
|
2018-01-17 04:20:34 -08:00
|
|
|
profile_start(nvenc_check_name);
|
2018-01-18 01:34:15 -08:00
|
|
|
|
2016-04-18 10:19:49 -07:00
|
|
|
AVCodec *nvenc = avcodec_find_encoder_by_name("nvenc_h264");
|
|
|
|
void *lib = NULL;
|
2018-01-17 04:20:34 -08:00
|
|
|
bool success = false;
|
2016-04-18 10:19:49 -07:00
|
|
|
|
2018-01-17 04:20:34 -08:00
|
|
|
if (!nvenc) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2016-04-18 10:19:49 -07:00
|
|
|
|
|
|
|
#if defined(_WIN32)
|
2019-02-05 00:33:48 -08:00
|
|
|
if (!nvenc_device_available()) {
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2019-02-05 18:20:16 -08:00
|
|
|
if (load_nvenc_lib()) {
|
|
|
|
success = true;
|
|
|
|
goto finish;
|
2016-04-18 10:19:49 -07:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
lib = os_dlopen("libnvidia-encode.so.1");
|
|
|
|
#endif
|
2018-01-17 04:20:34 -08:00
|
|
|
|
|
|
|
/* ------------------------------------------- */
|
|
|
|
|
2018-01-20 05:31:58 -08:00
|
|
|
success = !!lib;
|
2018-01-17 04:20:34 -08:00
|
|
|
|
|
|
|
cleanup:
|
2018-01-20 05:31:58 -08:00
|
|
|
if (lib)
|
|
|
|
os_dlclose(lib);
|
2019-02-20 14:36:10 -08:00
|
|
|
#if defined(_WIN32)
|
2019-02-05 18:20:16 -08:00
|
|
|
finish:
|
2019-02-20 14:36:10 -08:00
|
|
|
#endif
|
2018-01-17 04:20:34 -08:00
|
|
|
profile_end(nvenc_check_name);
|
|
|
|
return success;
|
2016-04-18 10:19:49 -07:00
|
|
|
}
|
|
|
|
|
2018-01-17 09:22:13 -08:00
|
|
|
#endif
|
|
|
|
|
2018-04-08 12:05:22 -07:00
|
|
|
#ifdef LIBAVUTIL_VAAPI_AVAILABLE
|
|
|
|
static bool vaapi_supported(void)
|
|
|
|
{
|
|
|
|
AVCodec *vaenc = avcodec_find_encoder_by_name("h264_vaapi");
|
|
|
|
return !!vaenc;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-02-05 18:20:16 -08:00
|
|
|
#ifdef _WIN32
|
|
|
|
extern void jim_nvenc_load(void);
|
|
|
|
extern void jim_nvenc_unload(void);
|
|
|
|
#endif
|
|
|
|
|
2019-07-09 08:44:04 -07:00
|
|
|
#if ENABLE_FFMPEG_LOGGING
|
|
|
|
extern void obs_ffmpeg_load_logging(void);
|
|
|
|
extern void obs_ffmpeg_unload_logging(void);
|
|
|
|
#endif
|
|
|
|
|
2014-07-27 12:42:43 -07:00
|
|
|
bool obs_module_load(void)
|
2014-02-07 02:03:54 -08:00
|
|
|
{
|
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);
|
2015-05-28 23:11:37 -07:00
|
|
|
obs_register_output(&ffmpeg_muxer);
|
2020-03-24 19:27:07 -07:00
|
|
|
obs_register_output(&ffmpeg_mpegts_muxer);
|
2020-10-13 13:50:47 -07:00
|
|
|
obs_register_output(&ffmpeg_hls_muxer);
|
2016-12-07 02:58:27 -08:00
|
|
|
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);
|
2018-01-17 09:22:13 -08:00
|
|
|
#ifndef __APPLE__
|
2016-04-18 10:19:49 -07:00
|
|
|
if (nvenc_supported()) {
|
|
|
|
blog(LOG_INFO, "NVENC supported");
|
2019-02-05 18:20:16 -08:00
|
|
|
#ifdef _WIN32
|
|
|
|
if (get_win_ver_int() > 0x0601) {
|
|
|
|
jim_nvenc_load();
|
2020-03-26 16:16:59 -07:00
|
|
|
} else {
|
|
|
|
// if on Win 7, new nvenc isn't available so there's
|
|
|
|
// no nvenc encoder for the user to select, expose
|
|
|
|
// the old encoder directly
|
|
|
|
nvenc_encoder_info.caps &= ~OBS_ENCODER_CAP_INTERNAL;
|
2019-02-05 18:20:16 -08:00
|
|
|
}
|
|
|
|
#endif
|
2016-04-18 10:19:49 -07:00
|
|
|
obs_register_encoder(&nvenc_encoder_info);
|
|
|
|
}
|
2018-04-08 12:05:22 -07:00
|
|
|
#if !defined(_WIN32) && defined(LIBAVUTIL_VAAPI_AVAILABLE)
|
|
|
|
if (vaapi_supported()) {
|
|
|
|
blog(LOG_INFO, "FFMPEG VAAPI supported");
|
|
|
|
obs_register_encoder(&vaapi_encoder_info);
|
|
|
|
}
|
|
|
|
#endif
|
2019-07-09 08:44:04 -07:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#if ENABLE_FFMPEG_LOGGING
|
|
|
|
obs_ffmpeg_load_logging();
|
2018-01-17 09:22:13 -08:00
|
|
|
#endif
|
2014-02-07 02:03:54 -08:00
|
|
|
return true;
|
2014-01-19 02:16:41 -08:00
|
|
|
}
|
2015-08-13 12:55:21 -07:00
|
|
|
|
|
|
|
void obs_module_unload(void)
|
|
|
|
{
|
2019-07-09 08:44:04 -07:00
|
|
|
#if ENABLE_FFMPEG_LOGGING
|
|
|
|
obs_ffmpeg_unload_logging();
|
2015-08-13 12:55:21 -07:00
|
|
|
#endif
|
|
|
|
|
2019-02-05 18:20:16 -08:00
|
|
|
#ifdef _WIN32
|
|
|
|
jim_nvenc_unload();
|
|
|
|
#endif
|
2015-10-12 13:41:18 -07:00
|
|
|
}
|