diff --git a/CMakeLists.txt b/CMakeLists.txt index 80a8d4ed8..ad705cb9e 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -244,7 +244,7 @@ if(ENABLE_QT) # Check for system Qt on Linux, fallback to bundled Qt if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if (NOT YUZU_USE_BUNDLED_QT) - find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets DBus) + find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets DBus Multimedia) endif() if (NOT Qt5_FOUND OR YUZU_USE_BUNDLED_QT) # Check for dependencies, then enable bundled Qt download @@ -351,9 +351,9 @@ if(ENABLE_QT) set(YUZU_QT_NO_CMAKE_SYSTEM_PATH "NO_CMAKE_SYSTEM_PATH") endif() if ((${CMAKE_SYSTEM_NAME} STREQUAL "Linux") AND YUZU_USE_BUNDLED_QT) - find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets DBus ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH}) + find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets DBus Multimedia ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH}) else() - find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH}) + find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets Multimedia ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH}) endif() if (YUZU_USE_QT_WEB_ENGINE) find_package(Qt5 COMPONENTS WebEngineCore WebEngineWidgets) @@ -460,7 +460,7 @@ if (CONAN_REQUIRED_LIBS) if(ENABLE_QT) list(APPEND CMAKE_MODULE_PATH "${CONAN_QT_ROOT_RELEASE}") list(APPEND CMAKE_PREFIX_PATH "${CONAN_QT_ROOT_RELEASE}") - find_package(Qt5 5.15 REQUIRED COMPONENTS Widgets) + find_package(Qt5 5.15 REQUIRED COMPONENTS Widgets Multimedia) if (YUZU_USE_QT_WEB_ENGINE) find_package(Qt5 REQUIRED COMPONENTS WebEngineCore WebEngineWidgets) endif() diff --git a/CMakeModules/CopyYuzuQt5Deps.cmake b/CMakeModules/CopyYuzuQt5Deps.cmake index dd97f5b2b..54f121512 100755 --- a/CMakeModules/CopyYuzuQt5Deps.cmake +++ b/CMakeModules/CopyYuzuQt5Deps.cmake @@ -10,11 +10,13 @@ function(copy_yuzu_Qt5_deps target_dir) set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/") set(Qt5_PLATFORMTHEMES_DIR "${Qt5_DIR}/../../../plugins/platformthemes/") set(Qt5_PLATFORMINPUTCONTEXTS_DIR "${Qt5_DIR}/../../../plugins/platforminputcontexts/") + set(Qt5_MEDIASERVICE_DIR "${Qt5_DIR}/../../../plugins/mediaservice/") set(Qt5_XCBGLINTEGRATIONS_DIR "${Qt5_DIR}/../../../plugins/xcbglintegrations/") set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/") set(Qt5_IMAGEFORMATS_DIR "${Qt5_DIR}/../../../plugins/imageformats/") set(Qt5_RESOURCES_DIR "${Qt5_DIR}/../../../resources/") set(PLATFORMS ${DLL_DEST}plugins/platforms/) + set(MEDIASERVICE ${DLL_DEST}mediaservice/) set(STYLES ${DLL_DEST}plugins/styles/) set(IMAGEFORMATS ${DLL_DEST}plugins/imageformats/) if (MSVC) @@ -25,6 +27,8 @@ function(copy_yuzu_Qt5_deps target_dir) Qt5Core$<$:d>.* Qt5Gui$<$:d>.* Qt5Widgets$<$:d>.* + Qt5Multimedia$<$:d>.* + Qt5Network$<$:d>.* ) if (YUZU_USE_QT_WEB_ENGINE) @@ -56,7 +60,11 @@ function(copy_yuzu_Qt5_deps target_dir) windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS} qjpeg$<$:d>.* qgif$<$:d>.* - ) + ) + windows_copy_files(yuzu ${Qt5_MEDIASERVICE_DIR} ${MEDIASERVICE} + dsengine$<$:d>.* + wmfengine$<$:d>.* + ) else() set(Qt5_DLLS "${Qt5_DLL_DIR}libQt5Core.so.5" diff --git a/README.md b/README.md index 7a67689ea..d251f0eaa 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ yuzu emulator early access ============= -This is the source code for early-access 2839. +This is the source code for early-access 2840. ## Legal Notice diff --git a/externals/cubeb/.github/workflows/build.yml b/externals/cubeb/.github/workflows/build.yml index e10744f49..a5f533b8b 100755 --- a/externals/cubeb/.github/workflows/build.yml +++ b/externals/cubeb/.github/workflows/build.yml @@ -9,11 +9,11 @@ jobs: BUILD_TYPE: ${{ matrix.type }} strategy: matrix: - os: [ubuntu-20.04, windows-2019, macos-11] + os: [ubuntu-20.04, windows-2019, macos-10.15] type: [Release, Debug] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v2 with: submodules: true @@ -46,13 +46,13 @@ jobs: matrix: type: [Release, Debug] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v2 with: submodules: true - name: Configure CMake shell: bash - run: cmake -S . -B build -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-28 + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-26 - name: Build shell: bash @@ -61,7 +61,7 @@ jobs: check_format: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v2 with: submodules: true diff --git a/externals/cubeb/CMakeLists.txt b/externals/cubeb/CMakeLists.txt index 9cfae641b..6cd808e5a 100755 --- a/externals/cubeb/CMakeLists.txt +++ b/externals/cubeb/CMakeLists.txt @@ -139,7 +139,7 @@ if(NOT BUNDLE_SPEEX) endif() if(NOT TARGET speex) - add_library(speex OBJECT subprojects/speex/resample.c) + add_library(speex STATIC subprojects/speex/resample.c) set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE) target_include_directories(speex INTERFACE subprojects) target_compile_definitions(speex PUBLIC @@ -259,7 +259,7 @@ if(USE_WASAPI) target_sources(cubeb PRIVATE src/cubeb_wasapi.cpp) target_compile_definitions(cubeb PRIVATE USE_WASAPI) - target_link_libraries(cubeb PRIVATE avrt ole32 ksuser) + target_link_libraries(cubeb PRIVATE avrt ole32) endif() check_include_files("windows.h;mmsystem.h" USE_WINMM) @@ -284,22 +284,9 @@ if(HAVE_SYS_SOUNDCARD_H) try_compile(USE_OSS "${PROJECT_BINARY_DIR}/compile_tests" ${PROJECT_SOURCE_DIR}/cmake/compile_tests/oss_is_v4.c) if(USE_OSS) - # strlcpy is not available on BSD systems that use glibc, - # like Debian kfreebsd, so try using libbsd if available - include(CheckSymbolExists) - check_symbol_exists(strlcpy string.h HAVE_STRLCPY) - if(NOT HAVE_STRLCPY) - pkg_check_modules(libbsd-overlay IMPORTED_TARGET libbsd-overlay) - if(libbsd-overlay_FOUND) - target_link_libraries(cubeb PRIVATE PkgConfig::libbsd-overlay) - set(HAVE_STRLCPY true) - endif() - endif() - if (HAVE_STRLCPY) - target_sources(cubeb PRIVATE - src/cubeb_oss.c) - target_compile_definitions(cubeb PRIVATE USE_OSS) - endif() + target_sources(cubeb PRIVATE + src/cubeb_oss.c) + target_compile_definitions(cubeb PRIVATE USE_OSS) endif() endif() @@ -380,7 +367,7 @@ if(BUILD_TESTS) add_executable(test_${NAME} test/test_${NAME}.cpp) target_include_directories(test_${NAME} PRIVATE ${gtest_SOURCE_DIR}/include src) target_link_libraries(test_${NAME} PRIVATE cubeb gtest_main) - add_test(${NAME} test_${NAME} --gtest_death_test_style=threadsafe) + add_test(${NAME} test_${NAME}) add_sanitizers(test_${NAME}) install(TARGETS test_${NAME}) endmacro(cubeb_add_test) diff --git a/externals/cubeb/INSTALL.md b/externals/cubeb/INSTALL.md index 4e941b9f8..f8492243a 100755 --- a/externals/cubeb/INSTALL.md +++ b/externals/cubeb/INSTALL.md @@ -2,12 +2,12 @@ You must have CMake v3.1 or later installed. -1. `git clone --recursive https://github.com/mozilla/cubeb.git` +1. `git clone --recursive https://github.com/kinetiknz/cubeb.git` 2. `mkdir cubeb-build` 3. `cd cubeb-build` -4. `cmake ../cubeb` -5. `cmake --build .` -6. `ctest` +3. `cmake ../cubeb` +4. `cmake --build .` +5. `ctest` # Windows build notes @@ -41,6 +41,6 @@ To build with MinGW-w64, install the following items: - Download and install MinGW-w64 with Win32 threads. - Download and install CMake. - Run MinGW-w64 Terminal from the Start Menu. -- Follow the build steps at the top of this file, but at step 4 run: - `cmake -G "MinGW Makefiles" ../cubeb` +- Follow the build steps at the top of this file, but at step 3 run: + `cmake -G "MinGW Makefiles" ..` - Continue the build steps at the top of this file. diff --git a/externals/cubeb/README.md b/externals/cubeb/README.md index e4e165882..92df4f22c 100755 --- a/externals/cubeb/README.md +++ b/externals/cubeb/README.md @@ -2,6 +2,6 @@ See INSTALL.md for build instructions. -See [Backend Support](https://github.com/mozilla/cubeb/wiki/Backend-Support) in the wiki for the support level of each backend. +See [Backend Support](https://github.com/kinetiknz/cubeb/wiki/Backend-Support) in the wiki for the support level of each backend. Licensed under an ISC-style license. See LICENSE for details. diff --git a/externals/cubeb/src/cubeb_aaudio.cpp b/externals/cubeb/src/cubeb_aaudio.cpp index 6076f1d21..b12c5966f 100755 --- a/externals/cubeb/src/cubeb_aaudio.cpp +++ b/externals/cubeb/src/cubeb_aaudio.cpp @@ -935,8 +935,7 @@ aaudio_stream_init_impl(cubeb_stream * stm, cubeb_devid input_device, stm->resampler = cubeb_resampler_create( stm, input_stream_params ? &in_params : NULL, output_stream_params ? &out_params : NULL, target_sample_rate, - stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT, - CUBEB_RESAMPLER_RECLOCK_NONE); + stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT); if (!stm->resampler) { LOG("Failed to create resampler"); diff --git a/externals/cubeb/src/cubeb_audiounit.cpp b/externals/cubeb/src/cubeb_audiounit.cpp index 37036a3f2..02cd13469 100755 --- a/externals/cubeb/src/cubeb_audiounit.cpp +++ b/externals/cubeb/src/cubeb_audiounit.cpp @@ -541,13 +541,6 @@ audiounit_input_callback(void * user_ptr, AudioUnitRenderActionFlags * flags, long outframes = cubeb_resampler_fill(stm->resampler.get(), stm->input_linear_buffer->data(), &total_input_frames, NULL, 0); - if (outframes < 0) { - stm->shutdown = true; - OSStatus r = AudioOutputUnitStop(stm->input_unit); - assert(r == 0); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return noErr; - } stm->draining = outframes < total_input_frames; // Reset input buffer @@ -1449,13 +1442,6 @@ audiounit_destroy(cubeb * ctx) audiounit_active_streams(ctx)); } - // Destroying a cubeb context with device collection callbacks registered - // is misuse of the API, assert then attempt to clean up. - assert(!ctx->input_collection_changed_callback && - !ctx->input_collection_changed_user_ptr && - !ctx->output_collection_changed_callback && - !ctx->output_collection_changed_user_ptr); - /* Unregister the callback if necessary. */ if (ctx->input_collection_changed_callback) { audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_INPUT); @@ -2714,8 +2700,7 @@ audiounit_setup_stream(cubeb_stream * stm) stm->resampler.reset(cubeb_resampler_create( stm, has_input(stm) ? &input_unconverted_params : NULL, has_output(stm) ? &stm->output_stream_params : NULL, target_sample_rate, - stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP, - CUBEB_RESAMPLER_RECLOCK_NONE)); + stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP)); if (!stm->resampler) { LOG("(%p) Could not create resampler.", stm); return CUBEB_ERROR; diff --git a/externals/cubeb/src/cubeb_jack.cpp b/externals/cubeb/src/cubeb_jack.cpp index 3b3056c64..e6d948f1a 100755 --- a/externals/cubeb/src/cubeb_jack.cpp +++ b/externals/cubeb/src/cubeb_jack.cpp @@ -925,18 +925,15 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, if (stm->devs == DUPLEX) { stm->resampler = cubeb_resampler_create( stm, &stm->in_params, &stm->out_params, stream_actual_rate, - stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP, - CUBEB_RESAMPLER_RECLOCK_NONE); + stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP); } else if (stm->devs == IN_ONLY) { stm->resampler = cubeb_resampler_create( stm, &stm->in_params, nullptr, stream_actual_rate, stm->data_callback, - stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP, - CUBEB_RESAMPLER_RECLOCK_NONE); + stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP); } else if (stm->devs == OUT_ONLY) { stm->resampler = cubeb_resampler_create( stm, nullptr, &stm->out_params, stream_actual_rate, stm->data_callback, - stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP, - CUBEB_RESAMPLER_RECLOCK_NONE); + stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP); } if (!stm->resampler) { diff --git a/externals/cubeb/src/cubeb_log.cpp b/externals/cubeb/src/cubeb_log.cpp index bd34af166..ff72e0e87 100755 --- a/externals/cubeb/src/cubeb_log.cpp +++ b/externals/cubeb/src/cubeb_log.cpp @@ -123,7 +123,7 @@ cubeb_async_log(char const * fmt, ...) } void -cubeb_async_log_reset_threads(void) +cubeb_async_log_reset_threads() { if (!g_cubeb_log_callback) { return; diff --git a/externals/cubeb/src/cubeb_log.h b/externals/cubeb/src/cubeb_log.h index 4380da439..aee31805e 100755 --- a/externals/cubeb/src/cubeb_log.h +++ b/externals/cubeb/src/cubeb_log.h @@ -35,7 +35,7 @@ extern cubeb_log_callback g_cubeb_log_callback PRINTF_FORMAT(1, 2); void cubeb_async_log(const char * fmt, ...); void -cubeb_async_log_reset_threads(void); +cubeb_async_log_reset_threads(); #ifdef __cplusplus } diff --git a/externals/cubeb/src/cubeb_opensl.c b/externals/cubeb/src/cubeb_opensl.c index e5969984b..78096d5dd 100755 --- a/externals/cubeb/src/cubeb_opensl.c +++ b/externals/cubeb/src/cubeb_opensl.c @@ -1479,8 +1479,7 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, stm->resampler = cubeb_resampler_create( stm, input_stream_params ? &input_params : NULL, output_stream_params ? &output_params : NULL, target_sample_rate, - data_callback, user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT, - CUBEB_RESAMPLER_RECLOCK_NONE); + data_callback, user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT); if (!stm->resampler) { LOG("Failed to create resampler"); opensl_stream_destroy(stm); diff --git a/externals/cubeb/src/cubeb_oss.c b/externals/cubeb/src/cubeb_oss.c index 88f8582a0..c85aec554 100755 --- a/externals/cubeb/src/cubeb_oss.c +++ b/externals/cubeb/src/cubeb_oss.c @@ -96,8 +96,9 @@ struct oss_stream { oss_devnode_t name; int fd; void * buf; + unsigned int nfr; /* Number of frames allocated */ + unsigned int nfrags; unsigned int bufframes; - unsigned int maxframes; struct stream_info { int channels; @@ -823,9 +824,9 @@ retry: pfds[0].fd = s->play.fd; pfds[1].fd = -1; goto retry; - } else if (tnfr > (long)s->play.maxframes) { + } else if (tnfr > (long)s->play.bufframes) { /* too many frames available - limit */ - tnfr = (long)s->play.maxframes; + tnfr = (long)s->play.bufframes; } if (nfr > tnfr) { nfr = tnfr; @@ -841,9 +842,9 @@ retry: pfds[0].fd = -1; pfds[1].fd = s->record.fd; goto retry; - } else if (tnfr > (long)s->record.maxframes) { + } else if (tnfr > (long)s->record.bufframes) { /* too many frames available - limit */ - tnfr = (long)s->record.maxframes; + tnfr = (long)s->record.bufframes; } if (nfr > tnfr) { nfr = tnfr; @@ -1060,8 +1061,6 @@ oss_stream_init(cubeb * context, cubeb_stream ** stream, } if (input_stream_params != NULL) { unsigned int nb_channels; - uint32_t minframes; - if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { LOG("Loopback not supported"); ret = CUBEB_ERROR_NOT_SUPPORTED; @@ -1090,17 +1089,18 @@ oss_stream_init(cubeb * context, cubeb_stream ** stream, (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE); s->record.frame_size = s->record.info.channels * (s->record.info.precision / 8); - s->record.bufframes = latency_frames; - - oss_get_min_latency(context, *input_stream_params, &minframes); - if (s->record.bufframes < minframes) { - s->record.bufframes = minframes; + s->record.nfrags = OSS_NFRAGS; + s->record.nfr = latency_frames / OSS_NFRAGS; + s->record.bufframes = s->record.nfrags * s->record.nfr; + uint32_t minnfr; + oss_get_min_latency(context, *input_stream_params, &minnfr); + if (s->record.nfr < minnfr) { + s->record.nfr = minnfr; + s->record.nfrags = latency_frames / minnfr; } } if (output_stream_params != NULL) { unsigned int nb_channels; - uint32_t minframes; - if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { LOG("Loopback not supported"); ret = CUBEB_ERROR_NOT_SUPPORTED; @@ -1128,16 +1128,19 @@ oss_stream_init(cubeb * context, cubeb_stream ** stream, } s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE); s->play.frame_size = s->play.info.channels * (s->play.info.precision / 8); - s->play.bufframes = latency_frames; - - oss_get_min_latency(context, *output_stream_params, &minframes); - if (s->play.bufframes < minframes) { - s->play.bufframes = minframes; + s->play.nfrags = OSS_NFRAGS; + s->play.nfr = latency_frames / OSS_NFRAGS; + uint32_t minnfr; + oss_get_min_latency(context, *output_stream_params, &minnfr); + if (s->play.nfr < minnfr) { + s->play.nfr = minnfr; + s->play.nfrags = latency_frames / minnfr; } + s->play.bufframes = s->play.nfrags * s->play.nfr; } if (s->play.fd != -1) { int frag = oss_get_frag_params( - oss_calc_frag_shift(s->play.bufframes, s->play.frame_size)); + oss_calc_frag_shift(s->play.nfr, s->play.frame_size)); if (ioctl(s->play.fd, SNDCTL_DSP_SETFRAGMENT, &frag)) LOG("Failed to set play fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x", frag); @@ -1145,28 +1148,19 @@ oss_stream_init(cubeb * context, cubeb_stream ** stream, if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi)) LOG("Failed to get play fd's buffer info."); else { - s->play.bufframes = (bi.fragsize * bi.fragstotal) / s->play.frame_size; + s->play.nfr = bi.fragsize / s->play.frame_size; + s->play.nfrags = bi.fragments; + s->play.bufframes = s->play.nfr * s->play.nfrags; } - int lw; - /* - * Force 32 ms service intervals at most, or when recording is - * active, use the recording service intervals as a reference. - */ - s->play.maxframes = (32 * output_stream_params->rate) / 1000; - if (s->record.fd != -1 || s->play.maxframes >= s->play.bufframes) { - lw = s->play.frame_size; /* Feed data when possible. */ - s->play.maxframes = s->play.bufframes; - } else { - lw = (s->play.bufframes - s->play.maxframes) * s->play.frame_size; - } + int lw = s->play.frame_size; if (ioctl(s->play.fd, SNDCTL_DSP_LOW_WATER, &lw)) LOG("Audio device \"%s\" (play) could not set trigger threshold", s->play.name); } if (s->record.fd != -1) { int frag = oss_get_frag_params( - oss_calc_frag_shift(s->record.bufframes, s->record.frame_size)); + oss_calc_frag_shift(s->record.nfr, s->record.frame_size)); if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag)) LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x", frag); @@ -1174,11 +1168,11 @@ oss_stream_init(cubeb * context, cubeb_stream ** stream, if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi)) LOG("Failed to get record fd's buffer info."); else { - s->record.bufframes = - (bi.fragsize * bi.fragstotal) / s->record.frame_size; + s->record.nfr = bi.fragsize / s->record.frame_size; + s->record.nfrags = bi.fragments; + s->record.bufframes = s->record.nfr * s->record.nfrags; } - s->record.maxframes = s->record.bufframes; int lw = s->record.frame_size; if (ioctl(s->record.fd, SNDCTL_DSP_LOW_WATER, &lw)) LOG("Audio device \"%s\" (record) could not set trigger threshold", diff --git a/externals/cubeb/src/cubeb_pulse.c b/externals/cubeb/src/cubeb_pulse.c index 13f679164..438a695e1 100755 --- a/externals/cubeb/src/cubeb_pulse.c +++ b/externals/cubeb/src/cubeb_pulse.c @@ -280,7 +280,6 @@ trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, if (got < 0) { WRAP(pa_stream_cancel_write)(s); stm->shutdown = 1; - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); return; } // If more iterations move offset of read buffer @@ -393,9 +392,6 @@ stream_read_callback(pa_stream * s, size_t nbytes, void * u) if (got < 0 || (size_t)got != read_frames) { WRAP(pa_stream_cancel_write)(s); stm->shutdown = 1; - if (got < 0) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - } break; } } @@ -787,10 +783,6 @@ pulse_context_destroy(cubeb * ctx) static void pulse_destroy(cubeb * ctx) { - assert(!ctx->input_collection_changed_callback && - !ctx->input_collection_changed_user_ptr && - !ctx->output_collection_changed_callback && - !ctx->output_collection_changed_user_ptr); free(ctx->context_name); if (ctx->context) { pulse_context_destroy(ctx); diff --git a/externals/cubeb/src/cubeb_resampler.cpp b/externals/cubeb/src/cubeb_resampler.cpp index c31944b82..d61b1daf2 100755 --- a/externals/cubeb/src/cubeb_resampler.cpp +++ b/externals/cubeb/src/cubeb_resampler.cpp @@ -323,8 +323,7 @@ cubeb_resampler_create(cubeb_stream * stream, cubeb_stream_params * input_params, cubeb_stream_params * output_params, unsigned int target_rate, cubeb_data_callback callback, - void * user_ptr, cubeb_resampler_quality quality, - cubeb_resampler_reclock reclock) + void * user_ptr, cubeb_resampler_quality quality) { cubeb_sample_format format; @@ -338,13 +337,13 @@ cubeb_resampler_create(cubeb_stream * stream, switch (format) { case CUBEB_SAMPLE_S16NE: - return cubeb_resampler_create_internal( - stream, input_params, output_params, target_rate, callback, user_ptr, - quality, reclock); + return cubeb_resampler_create_internal(stream, input_params, + output_params, target_rate, + callback, user_ptr, quality); case CUBEB_SAMPLE_FLOAT32NE: - return cubeb_resampler_create_internal( - stream, input_params, output_params, target_rate, callback, user_ptr, - quality, reclock); + return cubeb_resampler_create_internal(stream, input_params, + output_params, target_rate, + callback, user_ptr, quality); default: assert(false); return nullptr; diff --git a/externals/cubeb/src/cubeb_resampler.h b/externals/cubeb/src/cubeb_resampler.h index 711a3771d..e9b95324e 100755 --- a/externals/cubeb/src/cubeb_resampler.h +++ b/externals/cubeb/src/cubeb_resampler.h @@ -21,11 +21,6 @@ typedef enum { CUBEB_RESAMPLER_QUALITY_DESKTOP } cubeb_resampler_quality; -typedef enum { - CUBEB_RESAMPLER_RECLOCK_NONE, - CUBEB_RESAMPLER_RECLOCK_INPUT -} cubeb_resampler_reclock; - /** * Create a resampler to adapt the requested sample rate into something that * is accepted by the audio backend. @@ -49,8 +44,7 @@ cubeb_resampler_create(cubeb_stream * stream, cubeb_stream_params * input_params, cubeb_stream_params * output_params, unsigned int target_rate, cubeb_data_callback callback, - void * user_ptr, cubeb_resampler_quality quality, - cubeb_resampler_reclock reclock); + void * user_ptr, cubeb_resampler_quality quality); /** * Fill the buffer with frames acquired using the data callback. Resampling will diff --git a/externals/cubeb/src/cubeb_resampler_internal.h b/externals/cubeb/src/cubeb_resampler_internal.h index b271afc15..2eabb7c4c 100755 --- a/externals/cubeb/src/cubeb_resampler_internal.h +++ b/externals/cubeb/src/cubeb_resampler_internal.h @@ -496,8 +496,7 @@ cubeb_resampler_create_internal(cubeb_stream * stream, cubeb_stream_params * output_params, unsigned int target_rate, cubeb_data_callback callback, void * user_ptr, - cubeb_resampler_quality quality, - cubeb_resampler_reclock reclock) + cubeb_resampler_quality quality) { std::unique_ptr> input_resampler = nullptr; std::unique_ptr> output_resampler = nullptr; diff --git a/externals/cubeb/src/cubeb_ringbuffer.h b/externals/cubeb/src/cubeb_ringbuffer.h index f02035156..28381849f 100755 --- a/externals/cubeb/src/cubeb_ringbuffer.h +++ b/externals/cubeb/src/cubeb_ringbuffer.h @@ -110,8 +110,8 @@ public: assert_correct_thread(producer_id); #endif + int rd_idx = read_index_.load(std::memory_order_relaxed); int wr_idx = write_index_.load(std::memory_order_relaxed); - int rd_idx = read_index_.load(std::memory_order_acquire); if (full_internal(rd_idx, wr_idx)) { return 0; @@ -154,8 +154,8 @@ public: assert_correct_thread(consumer_id); #endif - int rd_idx = read_index_.load(std::memory_order_relaxed); int wr_idx = write_index_.load(std::memory_order_acquire); + int rd_idx = read_index_.load(std::memory_order_relaxed); if (empty_internal(rd_idx, wr_idx)) { return 0; @@ -172,7 +172,7 @@ public: } read_index_.store(increment_index(rd_idx, to_read), - std::memory_order_release); + std::memory_order_relaxed); return to_read; } @@ -190,7 +190,7 @@ public: #endif return available_read_internal( read_index_.load(std::memory_order_relaxed), - write_index_.load(std::memory_order_acquire)); + write_index_.load(std::memory_order_relaxed)); } /** * Get the number of available elements for consuming. @@ -205,7 +205,7 @@ public: assert_correct_thread(producer_id); #endif return available_write_internal( - read_index_.load(std::memory_order_acquire), + read_index_.load(std::memory_order_relaxed), write_index_.load(std::memory_order_relaxed)); } /** diff --git a/externals/cubeb/src/cubeb_utils_win.h b/externals/cubeb/src/cubeb_utils_win.h index 48e7b1b6d..4c47f454c 100755 --- a/externals/cubeb/src/cubeb_utils_win.h +++ b/externals/cubeb/src/cubeb_utils_win.h @@ -11,23 +11,24 @@ #include "cubeb-internal.h" #include -/* This wraps an SRWLock to track the owner in debug mode, adapted from +/* This wraps a critical section to track the owner in debug mode, adapted from NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx */ class owned_critical_section { public: owned_critical_section() - : srwlock(SRWLOCK_INIT) #ifndef NDEBUG - , - owner(0) + : owner(0) #endif { + InitializeCriticalSection(&critical_section); } + ~owned_critical_section() { DeleteCriticalSection(&critical_section); } + void lock() { - AcquireSRWLockExclusive(&srwlock); + EnterCriticalSection(&critical_section); #ifndef NDEBUG XASSERT(owner != GetCurrentThreadId() && "recursive locking"); owner = GetCurrentThreadId(); @@ -40,7 +41,7 @@ public: /* GetCurrentThreadId cannot return 0: it is not a the valid thread id */ owner = 0; #endif - ReleaseSRWLockExclusive(&srwlock); + LeaveCriticalSection(&critical_section); } /* This is guaranteed to have the good behaviour if it succeeds. The behaviour @@ -54,12 +55,12 @@ public: } private: - SRWLOCK srwlock; + CRITICAL_SECTION critical_section; #ifndef NDEBUG DWORD owner; #endif - // Disallow copy and assignment because SRWLock cannot be copied. + // Disallow copy and assignment because CRICICAL_SECTION cannot be copied. owned_critical_section(const owned_critical_section &); owned_critical_section & operator=(const owned_critical_section &); }; diff --git a/externals/cubeb/src/cubeb_wasapi.cpp b/externals/cubeb/src/cubeb_wasapi.cpp index 039ad76c0..9cc8a262e 100755 --- a/externals/cubeb/src/cubeb_wasapi.cpp +++ b/externals/cubeb/src/cubeb_wasapi.cpp @@ -183,46 +183,6 @@ private: extern cubeb_ops const wasapi_ops; -static com_heap_ptr -wasapi_get_default_device_id(EDataFlow flow, ERole role, - IMMDeviceEnumerator * enumerator); - -struct wasapi_default_devices { - wasapi_default_devices(IMMDeviceEnumerator * enumerator) - : render_console_id( - wasapi_get_default_device_id(eRender, eConsole, enumerator)), - render_comms_id( - wasapi_get_default_device_id(eRender, eCommunications, enumerator)), - capture_console_id( - wasapi_get_default_device_id(eCapture, eConsole, enumerator)), - capture_comms_id( - wasapi_get_default_device_id(eCapture, eCommunications, enumerator)) - { - } - - bool is_default(EDataFlow flow, ERole role, wchar_t const * id) - { - wchar_t const * default_id = nullptr; - if (flow == eRender && role == eConsole) { - default_id = this->render_console_id.get(); - } else if (flow == eRender && role == eCommunications) { - default_id = this->render_comms_id.get(); - } else if (flow == eCapture && role == eConsole) { - default_id = this->capture_console_id.get(); - } else if (flow == eCapture && role == eCommunications) { - default_id = this->capture_comms_id.get(); - } - - return default_id && wcscmp(id, default_id) == 0; - } - -private: - com_heap_ptr render_console_id; - com_heap_ptr render_comms_id; - com_heap_ptr capture_console_id; - com_heap_ptr capture_comms_id; -}; - int wasapi_stream_stop(cubeb_stream * stm); int @@ -235,14 +195,12 @@ ERole pref_to_role(cubeb_stream_prefs param); int wasapi_create_device(cubeb * ctx, cubeb_device_info & ret, - IMMDeviceEnumerator * enumerator, IMMDevice * dev, - wasapi_default_devices * defaults); + IMMDeviceEnumerator * enumerator, IMMDevice * dev); void wasapi_destroy_device(cubeb_device_info * device_info); static int -wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type, - cubeb_device_collection * out, - DWORD state_mask); +wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection * out); static int wasapi_device_collection_destroy(cubeb * ctx, cubeb_device_collection * collection); @@ -258,7 +216,6 @@ class monitor_device_notifications; struct cubeb { cubeb_ops const * ops = &wasapi_ops; - owned_critical_section lock; cubeb_strings * device_ids; /* Device enumerator to get notifications when the device collection change. */ @@ -410,9 +367,12 @@ struct cubeb_stream { float volume = 1.0; /* True if the stream is draining. */ bool draining = false; - /* If the render thread fails to stop, this is set to true and ownership of - * the stm is "leaked" to the render thread for later cleanup. */ - std::atomic emergency_bailout{false}; + /* True when we've destroyed the stream. This pointer is leaked on stream + * destruction if we could not join the thread. */ + std::atomic *> emergency_bailout{nullptr}; + /* Synchronizes render thread start to ensure safe access to + * emergency_bailout. */ + HANDLE thread_ready_event = 0; /* This needs an active audio input stream to be known, and is updated in the * first audio input callback. */ std::atomic input_latency_hns{LATENCY_NOT_AVAILABLE_YET}; @@ -751,34 +711,11 @@ private: namespace { -long -wasapi_data_callback(cubeb_stream * stm, void * user_ptr, - void const * input_buffer, void * output_buffer, - long nframes) -{ - if (stm->emergency_bailout) { - return CUBEB_ERROR; - } - return stm->data_callback(stm, user_ptr, input_buffer, output_buffer, - nframes); -} - -void -wasapi_state_callback(cubeb_stream * stm, void * user_ptr, cubeb_state state) -{ - if (stm->emergency_bailout) { - return; - } - return stm->state_callback(stm, user_ptr, state); -} - char const * intern_device_id(cubeb * ctx, wchar_t const * id) { XASSERT(id); - auto_lock lock(ctx->lock); - char const * tmp = wstr_to_utf8(id); if (!tmp) { return nullptr; @@ -892,11 +829,8 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count, long out_frames = cubeb_resampler_fill(stm->resampler.get(), input_buffer, &input_frames_count, dest, output_frames_needed); - if (out_frames < 0) { - ALOGV("Callback refill error: %d", out_frames); - wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return out_frames; - } + /* TODO: Report out_frames < 0 as an error via the API. */ + XASSERT(out_frames >= 0); float volume = 1.0; { @@ -968,16 +902,16 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count, return out_frames; } -bool +int trigger_async_reconfigure(cubeb_stream * stm) { XASSERT(stm && stm->reconfigure_event); - LOG("Try reconfiguring the stream"); BOOL ok = SetEvent(stm->reconfigure_event); if (!ok) { LOG("SetEvent on reconfigure_event failed: %lx", GetLastError()); + return CUBEB_ERROR; } - return static_cast(ok); + return CUBEB_OK; } /* This helper grabs all the frames available from a capture client, put them in @@ -1006,16 +940,8 @@ get_input_buffer(cubeb_stream * stm) if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { // Application can recover from this error. More info // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx - LOG("Input device invalidated error"); - // No need to reset device if user asks to use particular device, or - // switching is disabled. - if (stm->input_device_id || - (stm->input_stream_params.prefs & - CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) || - !trigger_async_reconfigure(stm)) { - wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return false; - } + LOG("Device invalidated error, reset default device"); + trigger_async_reconfigure(stm); return true; } @@ -1120,16 +1046,8 @@ get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count) if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { // Application can recover from this error. More info // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx - LOG("Output device invalidated error"); - // No need to reset device if user asks to use particular device, or - // switching is disabled. - if (stm->output_device_id || - (stm->output_stream_params.prefs & - CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) || - !trigger_async_reconfigure(stm)) { - wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return false; - } + LOG("Device invalidated error, reset default device"); + trigger_async_reconfigure(stm); return true; } @@ -1143,7 +1061,7 @@ get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count) if (stm->draining) { if (padding_out == 0) { LOG("Draining finished."); - wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); return false; } LOG("Draining."); @@ -1212,7 +1130,6 @@ refill_callback_duplex(cubeb_stream * stm) static_cast(stm->total_output_frames) - stm->total_input_frames, static_cast(stm->total_output_frames) / stm->total_input_frames); - long got; if (stm->has_dummy_output) { ALOGV( "Duplex callback (dummy output): input frames: %Iu, output frames: %Iu", @@ -1220,15 +1137,13 @@ refill_callback_duplex(cubeb_stream * stm) // We don't want to expose the dummy output to the callback so don't pass // the output buffer (it will be released later with silence in it) - got = - refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0); - + refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0); } else { ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu", input_frames, output_frames); - got = refill(stm, stm->linear_input_buffer->data(), input_frames, - output_buffer, output_frames); + refill(stm, stm->linear_input_buffer->data(), input_frames, output_buffer, + output_frames); } stm->linear_input_buffer->clear(); @@ -1244,9 +1159,6 @@ refill_callback_duplex(cubeb_stream * stm) LOG("failed to release buffer: %lx", hr); return false; } - if (got < 0) { - return false; - } return true; } @@ -1273,9 +1185,8 @@ refill_callback_input(cubeb_stream * stm) long read = refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0); - if (read < 0) { - return false; - } + + XASSERT(read >= 0); stm->linear_input_buffer->clear(); @@ -1305,9 +1216,8 @@ refill_callback_output(cubeb_stream * stm) ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames, got); - if (got < 0) { - return false; - } + + XASSERT(got >= 0); XASSERT(size_t(got) == output_frames || stm->draining); hr = stm->render_client->ReleaseBuffer(got, 0); @@ -1319,25 +1229,17 @@ refill_callback_output(cubeb_stream * stm) return size_t(got) == output_frames || stm->draining; } -void -wasapi_stream_destroy(cubeb_stream * stm); - -static void -handle_emergency_bailout(cubeb_stream * stm) -{ - if (stm->emergency_bailout) { - CloseHandle(stm->thread); - stm->thread = NULL; - CloseHandle(stm->shutdown_event); - stm->shutdown_event = 0; - wasapi_stream_destroy(stm); - _endthreadex(0); - } -} - static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) { cubeb_stream * stm = static_cast(stream); + std::atomic * emergency_bailout = stm->emergency_bailout; + + // Signal wasapi_stream_start that we've copied emergency_bailout. + BOOL ok = SetEvent(stm->thread_ready_event); + if (!ok) { + LOG("thread_ready SetEvent failed: %lx", GetLastError()); + return 0; + } bool is_playing = true; HANDLE wait_array[4] = {stm->shutdown_event, stm->reconfigure_event, @@ -1370,10 +1272,20 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) unsigned timeout_count = 0; const unsigned timeout_limit = 3; while (is_playing) { - handle_emergency_bailout(stm); + // We want to check the emergency bailout variable before a + // and after the WaitForMultipleObject, because the handles + // WaitForMultipleObjects is going to wait on might have been closed + // already. + if (*emergency_bailout) { + delete emergency_bailout; + return 0; + } DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array), wait_array, FALSE, 1000); - handle_emergency_bailout(stm); + if (*emergency_bailout) { + delete emergency_bailout; + return 0; + } if (waitResult != WAIT_TIMEOUT) { timeout_count = 0; } @@ -1383,7 +1295,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) /* We don't check if the drain is actually finished here, we just want to shutdown. */ if (stm->draining) { - wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); } continue; } @@ -1445,8 +1357,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) case WAIT_OBJECT_0 + 3: { /* input available */ HRESULT rv = get_input_buffer(stm); if (FAILED(rv)) { - is_playing = false; - continue; + return rv; } if (!has_output(stm)) { @@ -1465,29 +1376,18 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) break; default: LOG("case %lu not handled in render loop.", waitResult); - XASSERT(false); + abort(); } } - // Stop audio clients since this thread will no longer service - // the events. - if (stm->output_client) { - stm->output_client->Stop(); - } - if (stm->input_client) { - stm->input_client->Stop(); + if (FAILED(hr)) { + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); } if (mmcss_handle) { AvRevertMmThreadCharacteristics(mmcss_handle); } - handle_emergency_bailout(stm); - - if (FAILED(hr)) { - wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - } - return 0; } @@ -1497,7 +1397,7 @@ wasapi_destroy(cubeb * context); HRESULT register_notification_client(cubeb_stream * stm) { - XASSERT(stm->device_enumerator && !stm->notification_client); + XASSERT(stm->device_enumerator); stm->notification_client.reset(new wasapi_endpoint_notification_client( stm->reconfigure_event, stm->role)); @@ -1515,7 +1415,7 @@ register_notification_client(cubeb_stream * stm) HRESULT unregister_notification_client(cubeb_stream * stm) { - XASSERT(stm->device_enumerator && stm->notification_client); + XASSERT(stm->device_enumerator); HRESULT hr = stm->device_enumerator->UnregisterEndpointNotificationCallback( stm->notification_client.get()); @@ -1554,9 +1454,6 @@ get_endpoint(com_ptr & device, LPCWSTR devid) HRESULT register_collection_notification_client(cubeb * context) { - context->lock.assert_current_thread_owns(); - XASSERT(!context->device_collection_enumerator && - !context->collection_notification_client); HRESULT hr = CoCreateInstance( __uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(context->device_collection_enumerator.receive())); @@ -1583,9 +1480,6 @@ register_collection_notification_client(cubeb * context) HRESULT unregister_collection_notification_client(cubeb * context) { - context->lock.assert_current_thread_owns(); - XASSERT(context->device_collection_enumerator && - context->collection_notification_client); HRESULT hr = context->device_collection_enumerator ->UnregisterEndpointNotificationCallback( context->collection_notification_client.get()); @@ -1714,7 +1608,6 @@ wasapi_init(cubeb ** context, char const * context_name) cubeb * ctx = new cubeb(); ctx->ops = &wasapi_ops; - auto_lock lock(ctx->lock); if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) { delete ctx; return CUBEB_ERROR; @@ -1736,67 +1629,59 @@ wasapi_init(cubeb ** context, char const * context_name) } namespace { -enum ShutdownPhase { OnStop, OnDestroy }; - bool -stop_and_join_render_thread(cubeb_stream * stm, ShutdownPhase phase) +stop_and_join_render_thread(cubeb_stream * stm) { - // Only safe to transfer `stm` ownership to the render thread when - // the stream is being destroyed by the caller. - bool bailout = phase == OnDestroy; - - LOG("%p: Stop and join render thread: %p (%d), phase=%d", stm, stm->thread, - stm->emergency_bailout.load(), static_cast(phase)); + bool rv = true; + LOG("Stop and join render thread."); if (!stm->thread) { + LOG("No thread present."); return true; } - XASSERT(!stm->emergency_bailout); + // If we've already leaked the thread, just return, + // there is not much we can do. + if (!stm->emergency_bailout.load()) { + return false; + } BOOL ok = SetEvent(stm->shutdown_event); if (!ok) { - LOG("stop_and_join_render_thread: SetEvent failed: %lx", GetLastError()); - stm->emergency_bailout = bailout; - return false; + LOG("Destroy SetEvent failed: %lx", GetLastError()); } /* Wait five seconds for the rendering thread to return. It's supposed to - * check its event loop very often, five seconds is rather conservative. - * Note: 5*1s loop to work around timer sleep issues on pre-Windows 8. */ - DWORD r; - for (int i = 0; i < 5; ++i) { - r = WaitForSingleObject(stm->thread, 1000); - if (r == WAIT_OBJECT_0) { - break; - } - } + * check its event loop very often, five seconds is rather conservative. */ + DWORD r = WaitForSingleObject(stm->thread, 5000); if (r != WAIT_OBJECT_0) { - LOG("stop_and_join_render_thread: WaitForSingleObject on thread failed: " - "%lx, %lx", - r, GetLastError()); - stm->emergency_bailout = bailout; - return false; + /* Something weird happened, leak the thread and continue the shutdown + * process. */ + *(stm->emergency_bailout) = true; + // We give the ownership to the rendering thread. + stm->emergency_bailout = nullptr; + LOG("Destroy WaitForSingleObject on thread failed: %lx, %lx", r, + GetLastError()); + rv = false; } - // Only attempt to close and null out the thread and event if the - // WaitForSingleObject above succeeded. - LOG("stop_and_join_render_thread: Closing thread."); - CloseHandle(stm->thread); - stm->thread = NULL; + // Only attempts to close and null out the thread and event if the + // WaitForSingleObject above succeeded, so that calling this function again + // attemps to clean up the thread and event each time. + if (rv) { + LOG("Closing thread."); + CloseHandle(stm->thread); + stm->thread = NULL; - CloseHandle(stm->shutdown_event); - stm->shutdown_event = 0; + CloseHandle(stm->shutdown_event); + stm->shutdown_event = 0; + } - return true; + return rv; } void wasapi_destroy(cubeb * context) { - auto_lock lock(context->lock); - XASSERT(!context->device_collection_enumerator && - !context->collection_notification_client); - if (context->device_ids) { cubeb_strings_destroy(context->device_ids); } @@ -1925,6 +1810,9 @@ wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) return CUBEB_OK; } +void +wasapi_stream_destroy(cubeb_stream * stm); + static void waveformatex_update_derived_properties(WAVEFORMATEX * format) { @@ -1999,7 +1887,7 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction, } } -static int +static bool initialize_iaudioclient2(com_ptr & audio_client) { com_ptr audio_client2; @@ -2022,8 +1910,8 @@ initialize_iaudioclient2(com_ptr & audio_client) return CUBEB_OK; } -#if 0 -bool +// Not static to suppress a warning. +/* static */ bool initialize_iaudioclient3(com_ptr & audio_client, cubeb_stream * stm, const com_heap_ptr & mix_format, @@ -2133,7 +2021,6 @@ initialize_iaudioclient3(com_ptr & audio_client, LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr); return false; } -#endif #define DIRECTION_NAME (direction == eCapture ? "capture" : "render") @@ -2148,8 +2035,6 @@ setup_wasapi_stream_one_side(cubeb_stream * stm, cubeb_stream_params * mix_params, com_ptr & device) { - XASSERT(direction == eCapture || direction == eRender); - HRESULT hr; bool is_loopback = stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK; if (is_loopback && direction != eCapture) { @@ -2158,10 +2043,6 @@ setup_wasapi_stream_one_side(cubeb_stream * stm, } stm->stream_reset_lock.assert_current_thread_owns(); - // If user doesn't specify a particular device, we can choose another one when - // the given devid is unavailable. - bool allow_fallback = - direction == eCapture ? !stm->input_device_id : !stm->output_device_id; bool try_again = false; // This loops until we find a device that works, or we've exhausted all // possibilities. @@ -2211,7 +2092,7 @@ setup_wasapi_stream_one_side(cubeb_stream * stm, DIRECTION_NAME, hr); // A particular device can't be activated because it has been // unplugged, try fall back to the default audio device. - if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED && allow_fallback) { + if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED) { LOG("Trying again with the default %s audio device.", DIRECTION_NAME); devid = nullptr; device = nullptr; @@ -2288,41 +2169,40 @@ setup_wasapi_stream_one_side(cubeb_stream * stm, flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; } + // Sanity check the latency, it may be that the device doesn't support it. + REFERENCE_TIME minimum_period; + REFERENCE_TIME default_period; + hr = audio_client->GetDevicePeriod(&default_period, &minimum_period); + if (FAILED(hr)) { + LOG("Could not get device period: %lx", hr); + return CUBEB_ERROR; + } + REFERENCE_TIME latency_hns = frames_to_hns(stream_params->rate, stm->latency); + stm->input_bluetooth_handsfree = false; - // Adjust input latency and check if input is using bluetooth handsfree - // protocol. - if (direction == eCapture) { - stm->input_bluetooth_handsfree = false; - - wasapi_default_devices default_devices(stm->device_enumerator.get()); - cubeb_device_info device_info; - if (wasapi_create_device(stm->context, device_info, - stm->device_enumerator.get(), device.get(), - &default_devices) == CUBEB_OK) { - if (device_info.latency_hi == 0) { - LOG("Input: could not query latency_hi to guess safe latency"); - wasapi_destroy_device(&device_info); - return CUBEB_ERROR; - } - // This multiplicator has been found empirically. - uint32_t latency_frames = device_info.latency_hi * 8; - LOG("Input: latency increased to %u frames from a default of %u", - latency_frames, device_info.latency_hi); - latency_hns = frames_to_hns(device_info.default_rate, latency_frames); - - const char * HANDSFREE_TAG = "BTHHFENUM"; - size_t len = sizeof(HANDSFREE_TAG); + cubeb_device_info device_info; + if (wasapi_create_device(stm->context, device_info, + stm->device_enumerator.get(), + device.get()) == CUBEB_OK) { + const char * HANDSFREE_TAG = "BTHHFENUM"; + size_t len = sizeof(HANDSFREE_TAG); + if (direction == eCapture) { + uint32_t default_period_frames = + hns_to_frames(device_info.default_rate, default_period); if (strlen(device_info.group_id) >= len && strncmp(device_info.group_id, HANDSFREE_TAG, len) == 0) { - LOG("Input device is using bluetooth handsfree protocol"); stm->input_bluetooth_handsfree = true; } - - wasapi_destroy_device(&device_info); - } else { - LOG("Could not get cubeb_device_info. Skip customizing input settings"); + // This multiplicator has been found empirically. + uint32_t latency_frames = default_period_frames * 8; + LOG("Input: latency increased to %u frames from a default of %u", + latency_frames, default_period_frames); + latency_hns = frames_to_hns(device_info.default_rate, latency_frames); } + wasapi_destroy_device(&device_info); + } else { + LOG("Could not get cubeb_device_info."); } if (stream_params->prefs & CUBEB_STREAM_PREF_RAW) { @@ -2378,10 +2258,8 @@ setup_wasapi_stream_one_side(cubeb_stream * stm, #undef DIRECTION_NAME -// Returns a non-null cubeb_devid if we find a matched device, or nullptr -// otherwise. -cubeb_devid -wasapi_find_bt_handsfree_output_device(cubeb_stream * stm) +void +wasapi_find_matching_output_device(cubeb_stream * stm) { HRESULT hr; cubeb_device_info * input_device = nullptr; @@ -2390,29 +2268,27 @@ wasapi_find_bt_handsfree_output_device(cubeb_stream * stm) // Only try to match to an output device if the input device is a bluetooth // device that is using the handsfree protocol if (!stm->input_bluetooth_handsfree) { - return nullptr; + return; } wchar_t * tmp = nullptr; hr = stm->input_device->GetId(&tmp); if (FAILED(hr)) { - LOG("Couldn't get input device id in " - "wasapi_find_bt_handsfree_output_device"); - return nullptr; + LOG("Couldn't get input device id in wasapi_find_matching_output_device"); + return; } com_heap_ptr device_id(tmp); - cubeb_devid input_device_id = reinterpret_cast( - intern_device_id(stm->context, device_id.get())); + cubeb_devid input_device_id = intern_device_id(stm->context, device_id.get()); if (!input_device_id) { - return nullptr; + return; } - int rv = wasapi_enumerate_devices_internal( + int rv = wasapi_enumerate_devices( stm->context, (cubeb_device_type)(CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT), - &collection, DEVICE_STATE_ACTIVE); + &collection); if (rv != CUBEB_OK) { - return nullptr; + return; } // Find the input device, and then find the output device with the same group @@ -2424,36 +2300,19 @@ wasapi_find_bt_handsfree_output_device(cubeb_stream * stm) } } - cubeb_devid matched_output = nullptr; - - if (input_device) { - for (uint32_t i = 0; i < collection.count; i++) { - cubeb_device_info & dev = collection.device[i]; - if (dev.type == CUBEB_DEVICE_TYPE_OUTPUT && dev.group_id && - !strcmp(dev.group_id, input_device->group_id) && - dev.default_rate == input_device->default_rate) { - LOG("Found matching device for %s: %s", input_device->friendly_name, - dev.friendly_name); - matched_output = dev.devid; - break; - } + for (uint32_t i = 0; i < collection.count; i++) { + cubeb_device_info & dev = collection.device[i]; + if (dev.type == CUBEB_DEVICE_TYPE_OUTPUT && dev.group_id && input_device && + !strcmp(dev.group_id, input_device->group_id) && + dev.default_rate == input_device->default_rate) { + LOG("Found matching device for %s: %s", input_device->friendly_name, + dev.friendly_name); + stm->output_device_id = + utf8_to_wstr(reinterpret_cast(dev.devid)); } } wasapi_device_collection_destroy(stm->context, &collection); - return matched_output; -} - -std::unique_ptr -copy_wide_string(const wchar_t * src) -{ - XASSERT(src); - size_t len = wcslen(src); - std::unique_ptr copy(new wchar_t[len + 1]); - if (wcsncpy_s(copy.get(), len + 1, src, len) != 0) { - return nullptr; - } - return copy; } int @@ -2466,17 +2325,6 @@ setup_wasapi_stream(cubeb_stream * stm) XASSERT((!stm->output_client || !stm->input_client) && "WASAPI stream already setup, close it first."); - std::unique_ptr selected_output_device_id; - if (stm->output_device_id) { - if (std::unique_ptr tmp = - move(copy_wide_string(stm->output_device_id.get()))) { - selected_output_device_id = move(tmp); - } else { - LOG("Failed to copy output device identifier."); - return CUBEB_ERROR; - } - } - if (has_input(stm)) { LOG("(%p) Setup capture: device=%p", stm, stm->input_device_id.get()); rv = setup_wasapi_stream_one_side( @@ -2508,12 +2356,8 @@ setup_wasapi_stream(cubeb_stream * stm) // device, and the default device is the same bluetooth device, pick the // right output device, running at the same rate and with the same protocol // as the input. - if (!selected_output_device_id) { - cubeb_devid matched = wasapi_find_bt_handsfree_output_device(stm); - if (matched) { - selected_output_device_id = - move(utf8_to_wstr(reinterpret_cast(matched))); - } + if (!stm->output_device_id) { + wasapi_find_matching_output_device(stm); } } @@ -2527,24 +2371,23 @@ setup_wasapi_stream(cubeb_stream * stm) stm->output_stream_params.channels = stm->input_stream_params.channels; stm->output_stream_params.layout = stm->input_stream_params.layout; if (stm->input_device_id) { - if (std::unique_ptr tmp = - move(copy_wide_string(stm->input_device_id.get()))) { - XASSERT(!selected_output_device_id); - selected_output_device_id = move(tmp); - } else { - LOG("Failed to copy device identifier while copying input stream " - "configuration to output stream configuration to drive loopback."); + size_t len = wcslen(stm->input_device_id.get()); + std::unique_ptr tmp(new wchar_t[len + 1]); + if (wcsncpy_s(tmp.get(), len + 1, stm->input_device_id.get(), len) != 0) { + LOG("Failed to copy device identifier while copying input stream" + " configuration to output stream configuration to drive loopback."); return CUBEB_ERROR; } + stm->output_device_id = move(tmp); } stm->has_dummy_output = true; } if (has_output(stm)) { - LOG("(%p) Setup render: device=%p", stm, selected_output_device_id.get()); + LOG("(%p) Setup render: device=%p", stm, stm->output_device_id.get()); rv = setup_wasapi_stream_one_side( - stm, &stm->output_stream_params, selected_output_device_id.get(), - eRender, __uuidof(IAudioRenderClient), stm->output_client, + stm, &stm->output_stream_params, stm->output_device_id.get(), eRender, + __uuidof(IAudioRenderClient), stm->output_client, &stm->output_buffer_frame_count, stm->refill_event, stm->render_client, &stm->output_mix_params, stm->output_device); if (rv != CUBEB_OK) { @@ -2605,11 +2448,10 @@ setup_wasapi_stream(cubeb_stream * stm) stm->resampler.reset(cubeb_resampler_create( stm, has_input(stm) ? &input_params : nullptr, - has_output(stm) && !stm->has_dummy_output ? &output_params : nullptr, - target_sample_rate, wasapi_data_callback, stm->user_ptr, + has_output(stm) ? &output_params : nullptr, target_sample_rate, + stm->data_callback, stm->user_ptr, stm->voice ? CUBEB_RESAMPLER_QUALITY_VOIP - : CUBEB_RESAMPLER_QUALITY_DESKTOP, - CUBEB_RESAMPLER_RECLOCK_NONE)); + : CUBEB_RESAMPLER_QUALITY_DESKTOP)); if (!stm->resampler) { LOG("Could not get a resampler"); return CUBEB_ERROR; @@ -2774,15 +2616,12 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, return rv; } - // Follow the system default devices when not specifying devices explicitly - // and CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING is not set. - if ((!input_device && input_stream_params && - !(input_stream_params->prefs & - CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING)) || - (!output_device && output_stream_params && - !(output_stream_params->prefs & - CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING))) { - LOG("Follow the system default input or/and output devices"); + if (!((input_stream_params ? (input_stream_params->prefs & + CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) + : 0) || + (output_stream_params ? (output_stream_params->prefs & + CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) + : 0))) { HRESULT hr = register_notification_client(stm.get()); if (FAILED(hr)) { /* this is not fatal, we can still play audio, but we won't be able @@ -2839,25 +2678,33 @@ wasapi_stream_destroy(cubeb_stream * stm) XASSERT(stm); LOG("Stream destroy (%p)", stm); - if (!stop_and_join_render_thread(stm, OnDestroy)) { - // Emergency bailout: render thread becomes responsible for calling - // wasapi_stream_destroy. - return; + // Only free stm->emergency_bailout if we could join the thread. + // If we could not join the thread, stm->emergency_bailout is true + // and is still alive until the thread wakes up and exits cleanly. + if (stop_and_join_render_thread(stm)) { + delete stm->emergency_bailout.load(); + stm->emergency_bailout = nullptr; } if (stm->notification_client) { unregister_notification_client(stm); } + CloseHandle(stm->reconfigure_event); + CloseHandle(stm->refill_event); + CloseHandle(stm->input_available_event); + + // The variables intialized in wasapi_stream_init, + // must be destroyed in wasapi_stream_destroy. + stm->linear_input_buffer.reset(); + + stm->device_enumerator = nullptr; + { auto_lock lock(stm->stream_reset_lock); close_wasapi_stream(stm); } - CloseHandle(stm->reconfigure_event); - CloseHandle(stm->refill_event); - CloseHandle(stm->input_available_event); - delete stm; } @@ -2912,6 +2759,8 @@ wasapi_stream_start(cubeb_stream * stm) XASSERT(stm && !stm->thread && !stm->shutdown_event); XASSERT(stm->output_client || stm->input_client); + stm->emergency_bailout = new std::atomic(false); + if (stm->output_client) { int rv = stream_start_one_side(stm, OUTPUT); if (rv != CUBEB_OK) { @@ -2932,18 +2781,30 @@ wasapi_stream_start(cubeb_stream * stm) return CUBEB_ERROR; } + stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL); + if (!stm->thread_ready_event) { + LOG("Can't create the thread_ready event, error: %lx", GetLastError()); + return CUBEB_ERROR; + } + cubeb_async_log_reset_threads(); stm->thread = (HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); if (stm->thread == NULL) { LOG("could not create WASAPI render thread."); - CloseHandle(stm->shutdown_event); - stm->shutdown_event = 0; return CUBEB_ERROR; } - wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); + // Wait for wasapi_stream_render_loop to signal that emergency_bailout has + // been read, avoiding a bailout situation where we could free `stm` + // before wasapi_stream_render_loop had a chance to run. + HRESULT hr = WaitForSingleObject(stm->thread_ready_event, INFINITE); + XASSERT(hr == WAIT_OBJECT_0); + CloseHandle(stm->thread_ready_event); + stm->thread_ready_event = 0; + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); return CUBEB_OK; } @@ -2973,12 +2834,15 @@ wasapi_stream_stop(cubeb_stream * stm) } } - wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); } - if (!stop_and_join_render_thread(stm, OnStop)) { + if (stop_and_join_render_thread(stm)) { + delete stm->emergency_bailout.load(); + stm->emergency_bailout = nullptr; + } else { // If we could not join the thread, put the stream in error. - wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); return CUBEB_ERROR; } @@ -3147,30 +3011,31 @@ static com_ptr wasapi_get_device_node( return ret; } -static com_heap_ptr -wasapi_get_default_device_id(EDataFlow flow, ERole role, - IMMDeviceEnumerator * enumerator) +static BOOL +wasapi_is_default_device(EDataFlow flow, ERole role, LPCWSTR device_id, + IMMDeviceEnumerator * enumerator) { + BOOL ret = FALSE; com_ptr dev; + HRESULT hr; - HRESULT hr = enumerator->GetDefaultAudioEndpoint(flow, role, dev.receive()); + hr = enumerator->GetDefaultAudioEndpoint(flow, role, dev.receive()); if (SUCCEEDED(hr)) { wchar_t * tmp = nullptr; if (SUCCEEDED(dev->GetId(&tmp))) { - com_heap_ptr devid(tmp); - return devid; + com_heap_ptr defdevid(tmp); + ret = (wcscmp(defdevid.get(), device_id) == 0); } } - return nullptr; + return ret; } /* `ret` must be deallocated with `wasapi_destroy_device`, iff the return value * of this function is `CUBEB_OK`. */ int wasapi_create_device(cubeb * ctx, cubeb_device_info & ret, - IMMDeviceEnumerator * enumerator, IMMDevice * dev, - wasapi_default_devices * defaults) + IMMDeviceEnumerator * enumerator, IMMDevice * dev) { com_ptr endpoint; com_ptr devnode; @@ -3181,8 +3046,6 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret, REFERENCE_TIME def_period, min_period; HRESULT hr; - XASSERT(enumerator && dev && defaults); - // zero-out to be able to safely delete the pointers to friendly_name and // group_id at all time in this function. PodZero(&ret, 1); @@ -3270,14 +3133,19 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret, } ret.preferred = CUBEB_DEVICE_PREF_NONE; - if (defaults->is_default(flow, eConsole, device_id.get())) { + if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) { ret.preferred = - (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA | - CUBEB_DEVICE_PREF_NOTIFICATION); - } else if (defaults->is_default(flow, eCommunications, device_id.get())) { + (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA); + } + if (wasapi_is_default_device(flow, eCommunications, device_id.get(), + enumerator)) { ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_VOICE); } + if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) { + ret.preferred = + (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_NOTIFICATION); + } if (flow == eRender) { ret.type = CUBEB_DEVICE_TYPE_OUTPUT; @@ -3344,9 +3212,8 @@ wasapi_destroy_device(cubeb_device_info * device) } static int -wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type, - cubeb_device_collection * out, - DWORD state_mask) +wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection * out) { com_ptr enumerator; com_ptr collection; @@ -3362,19 +3229,17 @@ wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type, return CUBEB_ERROR; } - wasapi_default_devices default_devices(enumerator.get()); - - if (type == CUBEB_DEVICE_TYPE_OUTPUT) { + if (type == CUBEB_DEVICE_TYPE_OUTPUT) flow = eRender; - } else if (type == CUBEB_DEVICE_TYPE_INPUT) { + else if (type == CUBEB_DEVICE_TYPE_INPUT) flow = eCapture; - } else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) { + else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) flow = eAll; - } else { + else return CUBEB_ERROR; - } - hr = enumerator->EnumAudioEndpoints(flow, state_mask, collection.receive()); + hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL, + collection.receive()); if (FAILED(hr)) { LOG("Could not enumerate audio endpoints: %lx", hr); return CUBEB_ERROR; @@ -3399,7 +3264,7 @@ wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type, continue; } if (wasapi_create_device(context, devices[out->count], enumerator.get(), - dev.get(), &default_devices) == CUBEB_OK) { + dev.get()) == CUBEB_OK) { out->count += 1; } } @@ -3408,15 +3273,6 @@ wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type, return CUBEB_OK; } -static int -wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection * out) -{ - return wasapi_enumerate_devices_internal( - context, type, out, - DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_UNPLUGGED); -} - static int wasapi_device_collection_destroy(cubeb * /*ctx*/, cubeb_device_collection * collection) @@ -3438,7 +3294,6 @@ wasapi_register_device_collection_changed( cubeb_device_collection_changed_callback collection_changed_callback, void * user_ptr) { - auto_lock lock(context->lock); if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) { return CUBEB_ERROR_INVALID_PARAMETER; } diff --git a/externals/cubeb/src/cubeb_winmm.c b/externals/cubeb/src/cubeb_winmm.c index 44aec86d2..169b74385 100755 --- a/externals/cubeb/src/cubeb_winmm.c +++ b/externals/cubeb/src/cubeb_winmm.c @@ -73,6 +73,17 @@ #define CUBEB_STREAM_MAX 32 #define NBUFS 4 +const GUID KSDATAFORMAT_SUBTYPE_PCM = { + 0x00000001, + 0x0000, + 0x0010, + {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; +const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { + 0x00000003, + 0x0000, + 0x0010, + {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; + struct cubeb_stream_item { SLIST_ENTRY head; cubeb_stream * stream; diff --git a/externals/cubeb/test/test_callback_ret.cpp b/externals/cubeb/test/test_callback_ret.cpp index 7ce33d061..517d847df 100755 --- a/externals/cubeb/test/test_callback_ret.cpp +++ b/externals/cubeb/test/test_callback_ret.cpp @@ -34,7 +34,6 @@ enum test_direction { struct user_state_callback_ret { std::atomic cb_count{ 0 }; std::atomic expected_cb_count{ 0 }; - std::atomic error_state{ 0 }; }; // Data callback that always returns 0 @@ -99,30 +98,10 @@ long data_cb_ret_nframes(cubeb_stream * stream, void * user, const void * inputb return nframes; } -// Data callback that always returns CUBEB_ERROR -long -data_cb_ret_error(cubeb_stream * stream, void * user, const void * inputbuffer, - void * outputbuffer, long nframes) -{ - user_state_callback_ret * u = (user_state_callback_ret *)user; - // If this is the first time the callback has been called set our expected - // callback count - if (u->cb_count == 0) { - u->expected_cb_count = 1; - } - u->cb_count++; - if (nframes < 1) { - // This shouldn't happen - EXPECT_TRUE(false) << "nframes should not be 0 in data callback!"; - } - return CUBEB_ERROR; -} - -void state_cb_ret(cubeb_stream * stream, void * user, cubeb_state state) +void state_cb_ret(cubeb_stream * stream, void * /*user*/, cubeb_state state) { if (stream == NULL) return; - user_state_callback_ret * u = (user_state_callback_ret *)user; switch (state) { case CUBEB_STATE_STARTED: @@ -131,13 +110,11 @@ void state_cb_ret(cubeb_stream * stream, void * user, cubeb_state state) fprintf(stderr, "stream stopped\n"); break; case CUBEB_STATE_DRAINED: fprintf(stderr, "stream drained\n"); break; - case CUBEB_STATE_ERROR: - fprintf(stderr, "stream error\n"); - u->error_state.fetch_add(1); - break; default: fprintf(stderr, "unknown stream state %d\n", state); } + + return; } void run_test_callback(test_direction direction, @@ -206,10 +183,6 @@ void run_test_callback(test_direction direction, ASSERT_EQ(user_state.expected_cb_count, user_state.cb_count) << "Callback called unexpected number of times for " << test_desc << "!"; - // TODO: On some test configurations, the data_callback is never called. - if (data_cb == data_cb_ret_error && user_state.cb_count != 0) { - ASSERT_EQ(user_state.error_state, 1) << "Callback expected error state"; - } } TEST(cubeb, test_input_callback) @@ -217,8 +190,6 @@ TEST(cubeb, test_input_callback) run_test_callback(INPUT_ONLY, data_cb_ret_zero, "input only, return 0"); run_test_callback(INPUT_ONLY, data_cb_ret_nframes_minus_one, "input only, return nframes - 1"); run_test_callback(INPUT_ONLY, data_cb_ret_nframes, "input only, return nframes"); - run_test_callback(INPUT_ONLY, data_cb_ret_error, - "input only, return CUBEB_ERROR"); } TEST(cubeb, test_output_callback) @@ -226,8 +197,6 @@ TEST(cubeb, test_output_callback) run_test_callback(OUTPUT_ONLY, data_cb_ret_zero, "output only, return 0"); run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes_minus_one, "output only, return nframes - 1"); run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes, "output only, return nframes"); - run_test_callback(OUTPUT_ONLY, data_cb_ret_error, - "output only, return CUBEB_ERROR"); } TEST(cubeb, test_duplex_callback) @@ -235,5 +204,4 @@ TEST(cubeb, test_duplex_callback) run_test_callback(DUPLEX, data_cb_ret_zero, "duplex, return 0"); run_test_callback(DUPLEX, data_cb_ret_nframes_minus_one, "duplex, return nframes - 1"); run_test_callback(DUPLEX, data_cb_ret_nframes, "duplex, return nframes"); - run_test_callback(DUPLEX, data_cb_ret_error, "duplex, return CUBEB_ERROR"); } diff --git a/externals/cubeb/test/test_duplex.cpp b/externals/cubeb/test/test_duplex.cpp index ff0b397e8..3620bf066 100755 --- a/externals/cubeb/test/test_duplex.cpp +++ b/externals/cubeb/test/test_duplex.cpp @@ -137,20 +137,26 @@ void device_collection_changed_callback(cubeb * context, void * user) " called when opening a stream"; } -void -duplex_collection_change_impl(cubeb * ctx) +TEST(cubeb, duplex_collection_change) { - cubeb_stream * stream; + cubeb *ctx; + cubeb_stream *stream; cubeb_stream_params input_params; cubeb_stream_params output_params; int r; uint32_t latency_frames = 0; - r = cubeb_register_device_collection_changed( - ctx, static_cast(CUBEB_DEVICE_TYPE_INPUT), - device_collection_changed_callback, nullptr); + r = common_init(&ctx, "Cubeb duplex example with collection change"); + ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; + + r = cubeb_register_device_collection_changed(ctx, + static_cast(CUBEB_DEVICE_TYPE_INPUT), + device_collection_changed_callback, + nullptr); ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; + std::unique_ptr + cleanup_cubeb_at_exit(ctx, cubeb_destroy); /* typical user-case: mono input, stereo output, low latency. */ input_params.format = STREAM_FORMAT; @@ -167,43 +173,13 @@ duplex_collection_change_impl(cubeb * ctx) r = cubeb_get_min_latency(ctx, &output_params, &latency_frames); ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency"; - r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL, - &output_params, latency_frames, data_cb_duplex, - state_cb_duplex, nullptr); + r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", + NULL, &input_params, NULL, &output_params, + latency_frames, data_cb_duplex, state_cb_duplex, nullptr); ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; cubeb_stream_destroy(stream); } -TEST(cubeb, duplex_collection_change) -{ - cubeb * ctx; - int r; - - r = common_init(&ctx, "Cubeb duplex example with collection change"); - ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; - std::unique_ptr cleanup_cubeb_at_exit( - ctx, cubeb_destroy); - - duplex_collection_change_impl(ctx); - r = cubeb_register_device_collection_changed( - ctx, static_cast(CUBEB_DEVICE_TYPE_INPUT), nullptr, - nullptr); - ASSERT_EQ(r, CUBEB_OK); -} - -TEST(cubeb, duplex_collection_change_no_unregister) -{ - cubeb * ctx; - int r; - - r = common_init(&ctx, "Cubeb duplex example with collection change"); - ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; - std::unique_ptr cleanup_cubeb_at_exit( - ctx, [](cubeb * p) noexcept { EXPECT_DEATH(cubeb_destroy(p), ""); }); - - duplex_collection_change_impl(ctx); -} - long data_cb_input(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes) { if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) { diff --git a/externals/cubeb/test/test_resampler.cpp b/externals/cubeb/test/test_resampler.cpp index b4e148533..8ac878fc3 100755 --- a/externals/cubeb/test/test_resampler.cpp +++ b/externals/cubeb/test/test_resampler.cpp @@ -338,8 +338,7 @@ void test_resampler_duplex(uint32_t input_channels, uint32_t output_channels, cubeb_resampler * resampler = cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate, - data_cb_resampler, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP, - CUBEB_RESAMPLER_RECLOCK_NONE); + data_cb_resampler, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP); long latency = cubeb_resampler_latency(resampler); @@ -485,8 +484,8 @@ TEST(cubeb, resampler_output_only_noop) cubeb_resampler * resampler = cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate, test_output_only_noop_data_cb, nullptr, - CUBEB_RESAMPLER_QUALITY_VOIP, - CUBEB_RESAMPLER_RECLOCK_NONE); + CUBEB_RESAMPLER_QUALITY_VOIP); + const long out_frames = 128; float out_buffer[out_frames]; long got; @@ -524,8 +523,7 @@ TEST(cubeb, resampler_drain) cubeb_resampler * resampler = cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate, test_drain_data_cb, &cb_count, - CUBEB_RESAMPLER_QUALITY_VOIP, - CUBEB_RESAMPLER_RECLOCK_NONE); + CUBEB_RESAMPLER_QUALITY_VOIP); const long out_frames = 128; float out_buffer[out_frames]; @@ -574,8 +572,7 @@ TEST(cubeb, resampler_passthrough_output_only) cubeb_resampler * resampler = cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate, cb_passthrough_resampler_output, nullptr, - CUBEB_RESAMPLER_QUALITY_VOIP, - CUBEB_RESAMPLER_RECLOCK_NONE); + CUBEB_RESAMPLER_QUALITY_VOIP); float output_buffer[output_channels * 256]; @@ -619,8 +616,7 @@ TEST(cubeb, resampler_passthrough_input_only) cubeb_resampler * resampler = cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, nullptr, target_rate, cb_passthrough_resampler_input, nullptr, - CUBEB_RESAMPLER_QUALITY_VOIP, - CUBEB_RESAMPLER_RECLOCK_NONE); + CUBEB_RESAMPLER_QUALITY_VOIP); float input_buffer[input_channels * 256]; @@ -741,8 +737,7 @@ TEST(cubeb, resampler_passthrough_duplex_callback_reordering) cubeb_resampler * resampler = cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate, cb_passthrough_resampler_duplex, &c, - CUBEB_RESAMPLER_QUALITY_VOIP, - CUBEB_RESAMPLER_RECLOCK_NONE); + CUBEB_RESAMPLER_QUALITY_VOIP); const long BUF_BASE_SIZE = 256; float input_buffer_prebuffer[input_channels * BUF_BASE_SIZE * 2]; @@ -825,7 +820,7 @@ TEST(cubeb, resampler_drift_drop_data) cubeb_resampler * resampler = cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate, cb_passthrough_resampler_duplex, &c, - CUBEB_RESAMPLER_QUALITY_VOIP, CUBEB_RESAMPLER_RECLOCK_NONE); + CUBEB_RESAMPLER_QUALITY_VOIP); const long BUF_BASE_SIZE = 256; diff --git a/externals/cubeb/tools/cubeb-test.cpp b/externals/cubeb/tools/cubeb-test.cpp index ab17123ae..863e9ee16 100755 --- a/externals/cubeb/tools/cubeb-test.cpp +++ b/externals/cubeb/tools/cubeb-test.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #ifdef _WIN32 #include // Used by CoInitialize() #endif @@ -36,32 +35,6 @@ static const char* state_to_string(cubeb_state state) { } } -static const char* device_type_to_string(cubeb_device_type type) { - switch (type) { - case CUBEB_DEVICE_TYPE_INPUT: - return "input"; - case CUBEB_DEVICE_TYPE_OUTPUT: - return "output"; - case CUBEB_DEVICE_TYPE_UNKNOWN: - return "unknown"; - default: - assert(false); - } -} - -static const char* device_state_to_string(cubeb_device_state state) { - switch (state) { - case CUBEB_DEVICE_STATE_DISABLED: - return "disabled"; - case CUBEB_DEVICE_STATE_UNPLUGGED: - return "unplugged"; - case CUBEB_DEVICE_STATE_ENABLED: - return "enabled"; - default: - assert(false); - } -} - void print_log(const char* msg, ...) { va_list args; va_start(args, msg); @@ -75,7 +48,6 @@ public: ~cubeb_client() {} bool init(char const * backend_name = nullptr); - cubeb_devid select_device(cubeb_device_type type) const; bool init_stream(); bool start_stream(); bool stop_stream(); @@ -98,10 +70,7 @@ public: bool unregister_device_collection_changed(cubeb_device_type devtype) const; cubeb_stream_params output_params = {}; - cubeb_devid output_device = nullptr; - cubeb_stream_params input_params = {}; - cubeb_devid input_device = nullptr; void force_drain() { _force_drain = true; } @@ -112,6 +81,8 @@ private: cubeb* context = nullptr; cubeb_stream* stream = nullptr; + cubeb_devid output_device = nullptr; + cubeb_devid input_device = nullptr; /* Accessed only from client and audio thread. */ std::atomic _rate = {0}; @@ -521,56 +492,6 @@ bool choose_action(cubeb_client& cl, operation_data * op, int c) { return true; // Loop up } -cubeb_devid cubeb_client::select_device(cubeb_device_type type) const -{ - assert(type == CUBEB_DEVICE_TYPE_INPUT || type == CUBEB_DEVICE_TYPE_OUTPUT); - - cubeb_device_collection collection; - if (cubeb_enumerate_devices(context, type, &collection) == - CUBEB_ERROR_NOT_SUPPORTED) { - fprintf(stderr, - "Not support %s device selection. Force to use default device\n", - device_type_to_string(type)); - return nullptr; - } - - assert(collection.count); - fprintf(stderr, "Found %zu %s devices. Choose one:\n", collection.count, - device_type_to_string(type)); - - std::vector devices; - devices.emplace_back(nullptr); - fprintf(stderr, "# 0\n\tname: system default device\n"); - for (size_t i = 0; i < collection.count; i++) { - assert(collection.device[i].type == type); - fprintf(stderr, - "# %zu %s\n" - "\tname: %s\n" - "\tdevice id: %s\n" - "\tmax channels: %u\n" - "\tstate: %s\n", - devices.size(), - collection.device[i].preferred ? " (PREFERRED)" : "", - collection.device[i].friendly_name, collection.device[i].device_id, - collection.device[i].max_channels, - device_state_to_string(collection.device[i].state)); - devices.emplace_back(collection.device[i].devid); - } - - cubeb_device_collection_destroy(context, &collection); - - size_t number; - std::cout << "Enter device number: "; - std::cin >> number; - while (!std::cin || number >= devices.size()) { - std::cin.clear(); - std::cin.ignore(100, '\n'); - std::cout << "Error: Please enter a valid numeric input. Enter again: "; - std::cin >> number; - } - return devices[number]; -} - int main(int argc, char* argv[]) { #ifdef _WIN32 CoInitialize(nullptr); @@ -602,7 +523,7 @@ int main(int argc, char* argv[]) { bool res = false; cubeb_client cl; - cl.activate_log(CUBEB_LOG_NORMAL); + cl.activate_log(CUBEB_LOG_DISABLED); fprintf(stderr, "Log level is DISABLED\n"); cl.init(/* default backend */); @@ -619,12 +540,10 @@ int main(int argc, char* argv[]) { } } else { if (op.pm == PLAYBACK || op.pm == DUPLEX || op.pm == LATENCY_TESTING) { - cl.output_device = cl.select_device(CUBEB_DEVICE_TYPE_OUTPUT); cl.output_params = {CUBEB_SAMPLE_FLOAT32NE, op.rate, DEFAULT_OUTPUT_CHANNELS, CUBEB_LAYOUT_STEREO, CUBEB_STREAM_PREF_NONE}; } if (op.pm == RECORD || op.pm == DUPLEX || op.pm == LATENCY_TESTING) { - cl.input_device = cl.select_device(CUBEB_DEVICE_TYPE_INPUT); cl.input_params = {CUBEB_SAMPLE_FLOAT32NE, op.rate, DEFAULT_INPUT_CHANNELS, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE}; } if (op.pm == LATENCY_TESTING) { diff --git a/src/common/input.h b/src/common/input.h index bb42aaacc..995c35d9d 100755 --- a/src/common/input.h +++ b/src/common/input.h @@ -28,7 +28,7 @@ enum class InputType { Color, Vibration, Nfc, - Ir, + IrSensor, }; // Internal battery charge level @@ -53,6 +53,15 @@ enum class PollingMode { IR, }; +enum class CameraFormat { + Size320x240, + Size160x120, + Size80x60, + Size40x30, + Size20x15, + None, +}; + // Vibration reply from the controller enum class VibrationError { None, @@ -68,6 +77,13 @@ enum class PollingError { Unknown, }; +// Ir camera reply from the controller +enum class CameraError { + None, + NotSupported, + Unknown, +}; + // Hint for amplification curve to be used enum class VibrationAmplificationType { Linear, @@ -176,6 +192,12 @@ struct LedStatus { bool led_4{}; }; +// Raw data fom camera +struct CameraStatus { + CameraFormat format{CameraFormat::None}; + std::vector data{}; +}; + // List of buttons to be passed to Qt that can be translated enum class ButtonNames { Undefined, @@ -233,6 +255,7 @@ struct CallbackStatus { BodyColorStatus color_status{}; BatteryStatus battery_status{}; VibrationStatus vibration_status{}; + CameraStatus camera_status{}; }; // Triggered once every input change @@ -281,6 +304,10 @@ public: virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) { return PollingError::NotSupported; } + + virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) { + return CameraError::NotSupported; + } }; /// An abstract class template for a factory that can create input devices. diff --git a/src/common/settings.h b/src/common/settings.h index 2014ad8e8..1079cf8cb 100755 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -106,7 +106,7 @@ struct ResolutionScalingInfo { * configurations. Specifying a default value and label is required. A minimum and maximum range can * be specified for sanitization. */ -template +template class Setting { protected: Setting() = default; @@ -126,8 +126,8 @@ public: * @param default_val Intial value of the setting, and default value of the setting * @param name Label for the setting */ - explicit Setting(const Type& default_val, const std::string& name) - : value{default_val}, default_value{default_val}, ranged{false}, label{name} {} + explicit Setting(const Type& default_val, const std::string& name) requires(!ranged) + : value{default_val}, default_value{default_val}, label{name} {} virtual ~Setting() = default; /** @@ -139,9 +139,9 @@ public: * @param name Label for the setting */ explicit Setting(const Type& default_val, const Type& min_val, const Type& max_val, - const std::string& name) - : value{default_val}, default_value{default_val}, maximum{max_val}, minimum{min_val}, - ranged{true}, label{name} {} + const std::string& name) requires(ranged) + : value{default_val}, + default_value{default_val}, maximum{max_val}, minimum{min_val}, label{name} {} /** * Returns a reference to the setting's value. @@ -158,7 +158,7 @@ public: * @param val The desired value */ virtual void SetValue(const Type& val) { - Type temp{(ranged) ? std::clamp(val, minimum, maximum) : val}; + Type temp{ranged ? std::clamp(val, minimum, maximum) : val}; std::swap(value, temp); } @@ -188,7 +188,7 @@ public: * @returns A reference to the setting */ virtual const Type& operator=(const Type& val) { - Type temp{(ranged) ? std::clamp(val, minimum, maximum) : val}; + Type temp{ranged ? std::clamp(val, minimum, maximum) : val}; std::swap(value, temp); return value; } @@ -207,7 +207,6 @@ protected: const Type default_value{}; ///< The default value const Type maximum{}; ///< Maximum allowed value of the setting const Type minimum{}; ///< Minimum allowed value of the setting - const bool ranged; ///< The setting has sanitization ranges const std::string label{}; ///< The setting's label }; @@ -219,8 +218,8 @@ protected: * * By default, the global setting is used. */ -template -class SwitchableSetting : virtual public Setting { +template +class SwitchableSetting : virtual public Setting { public: /** * Sets a default value, label, and setting value. @@ -228,7 +227,7 @@ public: * @param default_val Intial value of the setting, and default value of the setting * @param name Label for the setting */ - explicit SwitchableSetting(const Type& default_val, const std::string& name) + explicit SwitchableSetting(const Type& default_val, const std::string& name) requires(!ranged) : Setting{default_val, name} {} virtual ~SwitchableSetting() = default; @@ -241,8 +240,8 @@ public: * @param name Label for the setting */ explicit SwitchableSetting(const Type& default_val, const Type& min_val, const Type& max_val, - const std::string& name) - : Setting{default_val, min_val, max_val, name} {} + const std::string& name) requires(ranged) + : Setting{default_val, min_val, max_val, name} {} /** * Tells this setting to represent either the global or custom setting when other member @@ -290,7 +289,7 @@ public: * @param val The new value */ void SetValue(const Type& val) override { - Type temp{(this->ranged) ? std::clamp(val, this->minimum, this->maximum) : val}; + Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val}; if (use_global) { std::swap(this->value, temp); } else { @@ -306,7 +305,7 @@ public: * @returns A reference to the current setting value */ const Type& operator=(const Type& val) override { - Type temp{(this->ranged) ? std::clamp(val, this->minimum, this->maximum) : val}; + Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val}; if (use_global) { std::swap(this->value, temp); return this->value; @@ -375,7 +374,7 @@ struct Values { Setting audio_output_device_id{"auto", "output_device"}; Setting audio_input_device_id{"auto", "input_device"}; Setting audio_muted{false, "audio_muted"}; - SwitchableSetting volume{100, 0, 100, "volume"}; + SwitchableSetting volume{100, 0, 100, "volume"}; Setting dump_audio_commands{false, "dump_audio_commands"}; // Core @@ -383,8 +382,8 @@ struct Values { SwitchableSetting use_extended_memory_layout{false, "use_extended_memory_layout"}; // Cpu - SwitchableSetting cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto, - CPUAccuracy::Paranoid, "cpu_accuracy"}; + SwitchableSetting cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto, + CPUAccuracy::Paranoid, "cpu_accuracy"}; // TODO: remove cpu_accuracy_first_time, migration setting added 8 July 2021 Setting cpu_accuracy_first_time{true, "cpu_accuracy_first_time"}; Setting cpu_debug_mode{false, "cpu_debug_mode"}; @@ -411,7 +410,7 @@ struct Values { true, "cpuopt_unsafe_ignore_global_monitor"}; // Renderer - SwitchableSetting renderer_backend{ + SwitchableSetting renderer_backend{ RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Vulkan, "backend"}; Setting renderer_debug{false, "debug"}; Setting renderer_shader_feedback{false, "shader_feedback"}; @@ -425,26 +424,26 @@ struct Values { SwitchableSetting anti_aliasing{AntiAliasing::None, "anti_aliasing"}; // *nix platforms may have issues with the borderless windowed fullscreen mode. // Default to exclusive fullscreen on these platforms for now. - SwitchableSetting fullscreen_mode{ + SwitchableSetting fullscreen_mode{ #ifdef _WIN32 FullscreenMode::Borderless, #else FullscreenMode::Exclusive, #endif FullscreenMode::Borderless, FullscreenMode::Exclusive, "fullscreen_mode"}; - SwitchableSetting aspect_ratio{0, 0, 3, "aspect_ratio"}; - SwitchableSetting max_anisotropy{0, 0, 5, "max_anisotropy"}; + SwitchableSetting aspect_ratio{0, 0, 3, "aspect_ratio"}; + SwitchableSetting max_anisotropy{0, 0, 5, "max_anisotropy"}; SwitchableSetting use_speed_limit{true, "use_speed_limit"}; - SwitchableSetting speed_limit{100, 0, 9999, "speed_limit"}; + SwitchableSetting speed_limit{100, 0, 9999, "speed_limit"}; SwitchableSetting use_disk_shader_cache{true, "use_disk_shader_cache"}; - SwitchableSetting gpu_accuracy{GPUAccuracy::High, GPUAccuracy::Normal, - GPUAccuracy::Extreme, "gpu_accuracy"}; + SwitchableSetting gpu_accuracy{GPUAccuracy::High, GPUAccuracy::Normal, + GPUAccuracy::Extreme, "gpu_accuracy"}; SwitchableSetting use_asynchronous_gpu_emulation{true, "use_asynchronous_gpu_emulation"}; SwitchableSetting nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"}; SwitchableSetting accelerate_astc{true, "accelerate_astc"}; SwitchableSetting use_vsync{true, "use_vsync"}; - SwitchableSetting shader_backend{ShaderBackend::GLASM, ShaderBackend::GLSL, - ShaderBackend::SPIRV, "shader_backend"}; + SwitchableSetting shader_backend{ShaderBackend::GLASM, ShaderBackend::GLSL, + ShaderBackend::SPIRV, "shader_backend"}; SwitchableSetting use_asynchronous_shaders{false, "use_asynchronous_shaders"}; SwitchableSetting use_fast_gpu_time{true, "use_fast_gpu_time"}; @@ -460,10 +459,10 @@ struct Values { s64 custom_rtc_differential; Setting current_user{0, "current_user"}; - SwitchableSetting language_index{1, 0, 17, "language_index"}; - SwitchableSetting region_index{1, 0, 6, "region_index"}; - SwitchableSetting time_zone_index{0, 0, 45, "time_zone_index"}; - SwitchableSetting sound_index{1, 0, 2, "sound_index"}; + SwitchableSetting language_index{1, 0, 17, "language_index"}; + SwitchableSetting region_index{1, 0, 6, "region_index"}; + SwitchableSetting time_zone_index{0, 0, 45, "time_zone_index"}; + SwitchableSetting sound_index{1, 0, 2, "sound_index"}; // Controls InputSetting> players; @@ -485,7 +484,7 @@ struct Values { Setting tas_loop{false, "tas_loop"}; Setting mouse_panning{false, "mouse_panning"}; - Setting mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"}; + Setting mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"}; Setting mouse_enabled{false, "mouse_enabled"}; Setting emulate_analog_keyboard{false, "emulate_analog_keyboard"}; @@ -504,6 +503,9 @@ struct Values { Setting enable_ring_controller{true, "enable_ring_controller"}; RingconRaw ringcon_analogs; + Setting enable_ir_sensor{false, "enable_ir_sensor"}; + Setting ir_sensor_device{"auto", "ir_sensor_device"}; + // Data Storage Setting use_virtual_sd{true, "use_virtual_sd"}; Setting gamecard_inserted{false, "gamecard_inserted"}; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index cf329c850..66c65a885 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -156,6 +156,7 @@ add_library(core STATIC hid/input_converter.h hid/input_interpreter.cpp hid/input_interpreter.h + hid/irs_types.h hid/motion_input.cpp hid/motion_input.h hle/api_version.h @@ -475,6 +476,20 @@ add_library(core STATIC hle/service/hid/hidbus/starlink.h hle/service/hid/hidbus/stubbed.cpp hle/service/hid/hidbus/stubbed.h + hle/service/hid/irsensor/clustering_processor.cpp + hle/service/hid/irsensor/clustering_processor.h + hle/service/hid/irsensor/image_transfer_processor.cpp + hle/service/hid/irsensor/image_transfer_processor.h + hle/service/hid/irsensor/ir_led_processor.cpp + hle/service/hid/irsensor/ir_led_processor.h + hle/service/hid/irsensor/moment_processor.cpp + hle/service/hid/irsensor/moment_processor.h + hle/service/hid/irsensor/pointing_processor.cpp + hle/service/hid/irsensor/pointing_processor.h + hle/service/hid/irsensor/processor_base.cpp + hle/service/hid/irsensor/processor_base.h + hle/service/hid/irsensor/tera_plugin_processor.cpp + hle/service/hid/irsensor/tera_plugin_processor.h hle/service/jit/jit_context.cpp hle/service/jit/jit_context.h hle/service/jit/jit.cpp diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index bd2384515..8c3895937 100755 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -126,10 +126,14 @@ void EmulatedController::LoadDevices() { battery_params[LeftIndex].Set("battery", true); battery_params[RightIndex].Set("battery", true); + camera_params = Common::ParamPackage{"engine:camera,camera:1"}; + output_params[LeftIndex] = left_joycon; output_params[RightIndex] = right_joycon; + output_params[2] = camera_params; output_params[LeftIndex].Set("output", true); output_params[RightIndex].Set("output", true); + output_params[2].Set("output", true); LoadTASParams(); @@ -146,6 +150,7 @@ void EmulatedController::LoadDevices() { Common::Input::CreateDevice); std::transform(battery_params.begin(), battery_params.end(), battery_devices.begin(), Common::Input::CreateDevice); + camera_devices = Common::Input::CreateDevice(camera_params); std::transform(output_params.begin(), output_params.end(), output_devices.begin(), Common::Input::CreateDevice); @@ -267,6 +272,14 @@ void EmulatedController::ReloadInput() { motion_devices[index]->ForceUpdate(); } + if (camera_devices) { + camera_devices->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); }, + }); + camera_devices->ForceUpdate(); + } + // Use a common UUID for TAS static constexpr Common::UUID TAS_UUID = Common::UUID{ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; @@ -851,6 +864,25 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac TriggerOnChange(ControllerTriggerType::Battery, true); } +void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) { + std::unique_lock lock{mutex}; + controller.camera_values = TransformToCamera(callback); + + if (is_configuring) { + lock.unlock(); + TriggerOnChange(ControllerTriggerType::IrSensor, false); + return; + } + + controller.camera_state.sample++; + controller.camera_state.format = + static_cast(controller.camera_values.format); + controller.camera_state.data = controller.camera_values.data; + + lock.unlock(); + TriggerOnChange(ControllerTriggerType::IrSensor, true); +} + bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { if (device_index >= output_devices.size()) { return false; @@ -928,6 +960,23 @@ bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) return output_device->SetPollingMode(polling_mode) == Common::Input::PollingError::None; } +bool EmulatedController::SetCameraFormat( + Core::IrSensor::ImageTransferProcessorFormat camera_format) { + LOG_INFO(Service_HID, "Set camera format {}", camera_format); + + auto& right_output_device = output_devices[static_cast(DeviceIndex::Right)]; + auto& camera_output_device = output_devices[2]; + + if (right_output_device->SetCameraFormat(static_cast( + camera_format)) == Common::Input::CameraError::None) { + return true; + } + + // Fallback to Qt camera if native device doesn't have support + return camera_output_device->SetCameraFormat(static_cast( + camera_format)) == Common::Input::CameraError::None; +} + void EmulatedController::SetLedPattern() { for (auto& device : output_devices) { if (!device) { @@ -1163,6 +1212,11 @@ BatteryValues EmulatedController::GetBatteryValues() const { return controller.battery_values; } +CameraValues EmulatedController::GetCameraValues() const { + std::scoped_lock lock{mutex}; + return controller.camera_values; +} + HomeButtonState EmulatedController::GetHomeButtons() const { std::scoped_lock lock{mutex}; if (is_configuring) { @@ -1251,6 +1305,11 @@ BatteryLevelState EmulatedController::GetBattery() const { return controller.battery_state; } +const CameraState& EmulatedController::GetCamera() const { + std::scoped_lock lock{mutex}; + return controller.camera_state; +} + void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) { std::scoped_lock lock{callback_mutex}; for (const auto& poller_pair : callback_list) { diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index 3f02ed3c0..823c1700c 100755 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h @@ -15,10 +15,12 @@ #include "common/settings.h" #include "common/vector_math.h" #include "core/hid/hid_types.h" +#include "core/hid/irs_types.h" #include "core/hid/motion_input.h" namespace Core::HID { const std::size_t max_emulated_controllers = 2; +const std::size_t output_devices = 3; struct ControllerMotionInfo { Common::Input::MotionStatus raw_status{}; MotionInput emulated{}; @@ -34,15 +36,16 @@ using TriggerDevices = std::array, Settings::NativeTrigger::NumTriggers>; using BatteryDevices = std::array, max_emulated_controllers>; -using OutputDevices = - std::array, max_emulated_controllers>; +using CameraDevices = std::unique_ptr; +using OutputDevices = std::array, output_devices>; using ButtonParams = std::array; using StickParams = std::array; using ControllerMotionParams = std::array; using TriggerParams = std::array; using BatteryParams = std::array; -using OutputParams = std::array; +using CameraParams = Common::ParamPackage; +using OutputParams = std::array; using ButtonValues = std::array; using SticksValues = std::array; @@ -51,6 +54,7 @@ using TriggerValues = using ControllerMotionValues = std::array; using ColorValues = std::array; using BatteryValues = std::array; +using CameraValues = Common::Input::CameraStatus; using VibrationValues = std::array; struct AnalogSticks { @@ -70,6 +74,12 @@ struct BatteryLevelState { NpadPowerInfo right{}; }; +struct CameraState { + Core::IrSensor::ImageTransferProcessorFormat format{}; + std::vector data{}; + std::size_t sample{}; +}; + struct ControllerMotion { Common::Vec3f accel{}; Common::Vec3f gyro{}; @@ -96,6 +106,7 @@ struct ControllerStatus { ColorValues color_values{}; BatteryValues battery_values{}; VibrationValues vibration_values{}; + CameraValues camera_values{}; // Data for HID serices HomeButtonState home_button_state{}; @@ -107,6 +118,7 @@ struct ControllerStatus { NpadGcTriggerState gc_trigger_state{}; ControllerColors colors_state{}; BatteryLevelState battery_state{}; + CameraState camera_state{}; }; enum class ControllerTriggerType { @@ -117,6 +129,7 @@ enum class ControllerTriggerType { Color, Battery, Vibration, + IrSensor, Connected, Disconnected, Type, @@ -269,6 +282,9 @@ public: /// Returns the latest battery status from the controller with parameters BatteryValues GetBatteryValues() const; + /// Returns the latest camera status from the controller with parameters + CameraValues GetCameraValues() const; + /// Returns the latest status of button input for the hid::HomeButton service HomeButtonState GetHomeButtons() const; @@ -296,6 +312,9 @@ public: /// Returns the latest battery status from the controller BatteryLevelState GetBattery() const; + /// Returns the latest camera status from the controller + const CameraState& GetCamera() const; + /** * Sends a specific vibration to the output device * @return true if vibration had no errors @@ -315,6 +334,13 @@ public: */ bool SetPollingMode(Common::Input::PollingMode polling_mode); + /** + * Sets the desired camera format to be polled from a controller + * @param camera_format size of each frame + * @return true if SetCameraFormat was successfull + */ + bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format); + /// Returns the led pattern corresponding to this emulated controller LedPattern GetLedPattern() const; @@ -392,6 +418,12 @@ private: */ void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index); + /** + * Updates the camera status of the controller + * @param callback A CallbackStatus containing the camera status + */ + void SetCamera(const Common::Input::CallbackStatus& callback); + /** * Triggers a callback that something has changed on the controller status * @param type Input type of the event to trigger @@ -417,6 +449,7 @@ private: ControllerMotionParams motion_params; TriggerParams trigger_params; BatteryParams battery_params; + CameraParams camera_params; OutputParams output_params; ButtonDevices button_devices; @@ -424,6 +457,7 @@ private: ControllerMotionDevices motion_devices; TriggerDevices trigger_devices; BatteryDevices battery_devices; + CameraDevices camera_devices; OutputDevices output_devices; // TAS related variables diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index 18d9f042d..68d143a01 100755 --- a/src/core/hid/input_converter.cpp +++ b/src/core/hid/input_converter.cpp @@ -270,6 +270,20 @@ Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatu return status; } +Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback) { + Common::Input::CameraStatus camera{}; + switch (callback.type) { + case Common::Input::InputType::IrSensor: + camera = callback.camera_status; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to camera not implemented", callback.type); + break; + } + + return camera; +} + void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) { const auto& properties = analog.properties; float& raw_value = analog.raw_value; diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h index 2be36889f..143c50cc0 100755 --- a/src/core/hid/input_converter.h +++ b/src/core/hid/input_converter.h @@ -76,6 +76,14 @@ Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackSta */ Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback); +/** + * Converts raw input data into a valid camera status. + * + * @param callback Supported callbacks: Camera. + * @return A valid CameraObject object. + */ +Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback); + /** * Converts raw analog data into a valid analog value * @param analog An analog object containing raw data and properties diff --git a/src/core/hid/irs_types.h b/src/core/hid/irs_types.h new file mode 100755 index 000000000..88c5b016d --- /dev/null +++ b/src/core/hid/irs_types.h @@ -0,0 +1,301 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/hid/hid_types.h" + +namespace Core::IrSensor { + +// This is nn::irsensor::CameraAmbientNoiseLevel +enum class CameraAmbientNoiseLevel : u32 { + Low, + Medium, + High, + Unkown3, // This level can't be reached +}; + +// This is nn::irsensor::CameraLightTarget +enum class CameraLightTarget : u32 { + AllLeds, + BrightLeds, + DimLeds, + None, +}; + +// This is nn::irsensor::PackedCameraLightTarget +enum class PackedCameraLightTarget : u8 { + AllLeds, + BrightLeds, + DimLeds, + None, +}; + +// This is nn::irsensor::AdaptiveClusteringMode +enum class AdaptiveClusteringMode : u32 { + StaticFov, + DynamicFov, +}; + +// This is nn::irsensor::AdaptiveClusteringTargetDistance +enum class AdaptiveClusteringTargetDistance : u32 { + Near, + Middle, + Far, +}; + +// This is nn::irsensor::ImageTransferProcessorFormat +enum class ImageTransferProcessorFormat : u32 { + Size320x240, + Size160x120, + Size80x60, + Size40x30, + Size20x15, +}; + +// This is nn::irsensor::PackedImageTransferProcessorFormat +enum class PackedImageTransferProcessorFormat : u8 { + Size320x240, + Size160x120, + Size80x60, + Size40x30, + Size20x15, +}; + +// This is nn::irsensor::IrCameraStatus +enum class IrCameraStatus : u32 { + Available, + Unsupported, + Unconnected, +}; + +// This is nn::irsensor::IrCameraInternalStatus +enum class IrCameraInternalStatus : u32 { + Stopped, + FirmwareUpdateNeeded, + Unkown2, + Unkown3, + Unkown4, + FirmwareVersionRequested, + FirmwareVersionIsInvalid, + Ready, + Setting, +}; + +// This is nn::irsensor::detail::StatusManager::IrSensorMode +enum class IrSensorMode : u64 { + None, + MomentProcessor, + ClusteringProcessor, + ImageTransferProcessor, + PointingProcessorMarker, + TeraPluginProcessor, + IrLedProcessor, +}; + +// This is nn::irsensor::ImageProcessorStatus +enum ImageProcessorStatus : u32 { + Stopped, + Running, +}; + +// This is nn::irsensor::HandAnalysisMode +enum class HandAnalysisMode : u32 { + None, + Silhouette, + Image, + SilhoueteAndImage, + SilhuetteOnly, +}; + +// This is nn::irsensor::IrSensorFunctionLevel +enum class IrSensorFunctionLevel : u8 { + unknown0, + unknown1, + unknown2, + unknown3, + unknown4, +}; + +// This is nn::irsensor::MomentProcessorPreprocess +enum class MomentProcessorPreprocess : u32 { + Unkown0, + Unkown1, +}; + +// This is nn::irsensor::PackedMomentProcessorPreprocess +enum class PackedMomentProcessorPreprocess : u8 { + Unkown0, + Unkown1, +}; + +// This is nn::irsensor::PointingStatus +enum class PointingStatus : u32 { + Unkown0, + Unkown1, +}; + +struct IrsRect { + s16 x; + s16 y; + s16 width; + s16 height; +}; + +struct IrsCentroid { + f32 x; + f32 y; +}; + +struct CameraConfig { + u64 exposure_time; + CameraLightTarget light_target; + u32 gain; + bool is_negative_used; + INSERT_PADDING_BYTES(7); +}; +static_assert(sizeof(CameraConfig) == 0x18, "CameraConfig is an invalid size"); + +struct PackedCameraConfig { + u64 exposure_time; + PackedCameraLightTarget light_target; + u8 gain; + bool is_negative_used; + INSERT_PADDING_BYTES(5); +}; +static_assert(sizeof(PackedCameraConfig) == 0x10, "PackedCameraConfig is an invalid size"); + +// This is nn::irsensor::IrCameraHandle +struct IrCameraHandle { + u8 npad_id{}; + Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None}; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(IrCameraHandle) == 4, "IrCameraHandle is an invalid size"); + +// This is nn::irsensor::PackedMcuVersion +struct PackedMcuVersion { + u16 major; + u16 minor; +}; +static_assert(sizeof(PackedMcuVersion) == 4, "PackedMcuVersion is an invalid size"); + +// This is nn::irsensor::PackedMomentProcessorConfig +struct PackedMomentProcessorConfig { + PackedCameraConfig camera_config; + IrsRect window_of_interest; + PackedMcuVersion required_mcu_version; + PackedMomentProcessorPreprocess preprocess; + u8 preprocess_intensity_threshold; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(PackedMomentProcessorConfig) == 0x20, + "PackedMomentProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedClusteringProcessorConfig +struct PackedClusteringProcessorConfig { + PackedCameraConfig camera_config; + IrsRect window_of_interest; + PackedMcuVersion required_mcu_version; + u32 pixel_count_min; + u32 pixel_count_max; + u8 object_intensity_min; + bool is_external_light_filter_enabled; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(PackedClusteringProcessorConfig) == 0x28, + "PackedClusteringProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedImageTransferProcessorConfig +struct PackedImageTransferProcessorConfig { + PackedCameraConfig camera_config; + PackedMcuVersion required_mcu_version; + PackedImageTransferProcessorFormat format; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(PackedImageTransferProcessorConfig) == 0x18, + "PackedImageTransferProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedTeraPluginProcessorConfig +struct PackedTeraPluginProcessorConfig { + PackedMcuVersion required_mcu_version; + u8 mode; + u8 unknown_1; + u8 unknown_2; + u8 unknown_3; +}; +static_assert(sizeof(PackedTeraPluginProcessorConfig) == 0x8, + "PackedTeraPluginProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedPointingProcessorConfig +struct PackedPointingProcessorConfig { + IrsRect window_of_interest; + PackedMcuVersion required_mcu_version; +}; +static_assert(sizeof(PackedPointingProcessorConfig) == 0xC, + "PackedPointingProcessorConfig is an invalid size"); + +// This is nn::irsensor::PackedFunctionLevel +struct PackedFunctionLevel { + IrSensorFunctionLevel function_level; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(PackedFunctionLevel) == 0x4, "PackedFunctionLevel is an invalid size"); + +// This is nn::irsensor::PackedImageTransferProcessorExConfig +struct PackedImageTransferProcessorExConfig { + PackedCameraConfig camera_config; + PackedMcuVersion required_mcu_version; + PackedImageTransferProcessorFormat origin_format; + PackedImageTransferProcessorFormat trimming_format; + u16 trimming_start_x; + u16 trimming_start_y; + bool is_external_light_filter_enabled; + INSERT_PADDING_BYTES(5); +}; +static_assert(sizeof(PackedImageTransferProcessorExConfig) == 0x20, + "PackedImageTransferProcessorExConfig is an invalid size"); + +// This is nn::irsensor::PackedIrLedProcessorConfig +struct PackedIrLedProcessorConfig { + PackedMcuVersion required_mcu_version; + u8 light_target; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(PackedIrLedProcessorConfig) == 0x8, + "PackedIrLedProcessorConfig is an invalid size"); + +// This is nn::irsensor::HandAnalysisConfig +struct HandAnalysisConfig { + HandAnalysisMode mode; +}; +static_assert(sizeof(HandAnalysisConfig) == 0x4, "HandAnalysisConfig is an invalid size"); + +// This is nn::irsensor::detail::ProcessorState contents are different for each processor +struct ProcessorState { + std::array processor_raw_data{}; +}; +static_assert(sizeof(ProcessorState) == 0xE20, "ProcessorState is an invalid size"); + +// This is nn::irsensor::detail::DeviceFormat +struct DeviceFormat { + Core::IrSensor::IrCameraStatus camera_status{Core::IrSensor::IrCameraStatus::Unconnected}; + Core::IrSensor::IrCameraInternalStatus camera_internal_status{ + Core::IrSensor::IrCameraInternalStatus::Ready}; + Core::IrSensor::IrSensorMode mode{Core::IrSensor::IrSensorMode::None}; + ProcessorState state{}; +}; +static_assert(sizeof(DeviceFormat) == 0xE30, "DeviceFormat is an invalid size"); + +// This is nn::irsensor::ImageTransferProcessorState +struct ImageTransferProcessorState { + u64 sampling_number; + Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level; + INSERT_PADDING_BYTES(4); +}; +static_assert(sizeof(ImageTransferProcessorState) == 0x10, + "ImageTransferProcessorState is an invalid size"); + +} // namespace Core::IrSensor diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h index 46282f42e..4613a4e60 100755 --- a/src/core/hle/service/hid/errors.h +++ b/src/core/hle/service/hid/errors.h @@ -19,3 +19,10 @@ constexpr Result InvalidNpadId{ErrorModule::HID, 709}; constexpr Result NpadNotConnected{ErrorModule::HID, 710}; } // namespace Service::HID + +namespace Service::IRS { + +constexpr Result InvalidProcessorState{ErrorModule::Irsensor, 78}; +constexpr Result InvalidIrCameraHandle{ErrorModule::Irsensor, 204}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 89bb12442..5ecbddf94 100755 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -2345,8 +2345,8 @@ void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system std::make_shared(system)->InstallAsService(service_manager); std::make_shared(system)->InstallAsService(service_manager); - std::make_shared(system)->InstallAsService(service_manager); - std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); std::make_shared(system)->InstallAsService(service_manager); } diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index d2a91d913..d5107e41f 100755 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -1,16 +1,28 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include + #include "core/core.h" #include "core/core_timing.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_shared_memory.h" #include "core/hle/kernel/k_transfer_memory.h" #include "core/hle/kernel/kernel.h" #include "core/hle/service/hid/errors.h" #include "core/hle/service/hid/irs.h" +#include "core/hle/service/hid/irsensor/clustering_processor.h" +#include "core/hle/service/hid/irsensor/image_transfer_processor.h" +#include "core/hle/service/hid/irsensor/ir_led_processor.h" +#include "core/hle/service/hid/irsensor/moment_processor.h" +#include "core/hle/service/hid/irsensor/pointing_processor.h" +#include "core/hle/service/hid/irsensor/tera_plugin_processor.h" +#include "core/memory.h" -namespace Service::HID { +namespace Service::IRS { IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} { // clang-format off @@ -36,14 +48,19 @@ IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} { }; // clang-format on + u8* raw_shared_memory = system.Kernel().GetIrsSharedMem().GetPointer(); RegisterHandlers(functions); + shared_memory = std::construct_at(reinterpret_cast(raw_shared_memory)); + + npad_device = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); } +IRS::~IRS() = default; void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop()}; - LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", + LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -54,7 +71,7 @@ void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop()}; - LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", + LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -75,7 +92,7 @@ void IRS::GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx) { void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -88,17 +105,23 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + auto result = IsIrCameraHandleValid(parameters.camera_handle); + if (result.IsSuccess()) { + // TODO: Stop Image processor + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - PackedMomentProcessorConfig processor_config; + Core::IrSensor::PackedMomentProcessorConfig processor_config; }; static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size."); @@ -109,19 +132,28 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + const auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessor(parameters.camera_handle, device); + auto& image_transfer_processor = GetProcessor(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - PackedClusteringProcessorConfig processor_config; + Core::IrSensor::PackedClusteringProcessorConfig processor_config; }; - static_assert(sizeof(Parameters) == 0x40, "Parameters has incorrect size."); + static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size."); const auto parameters{rp.PopRaw()}; @@ -130,17 +162,27 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessor(parameters.camera_handle, device); + auto& image_transfer_processor = + GetProcessor(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - PackedImageTransferProcessorConfig processor_config; + Core::IrSensor::PackedImageTransferProcessorConfig processor_config; u32 transfer_memory_size; }; static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size."); @@ -151,20 +193,42 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) { auto t_mem = system.CurrentProcess()->GetHandleTable().GetObject(t_mem_handle); - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, transfer_memory_size={}, " - "applet_resource_user_id={}", - parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, - parameters.transfer_memory_size, parameters.applet_resource_user_id); + if (t_mem.IsNull()) { + LOG_ERROR(Service_IRS, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + ASSERT_MSG(t_mem->GetSize() == parameters.transfer_memory_size, "t_mem has incorrect size"); + + u8* transfer_memory = system.Memory().GetPointer(t_mem->GetSourceAddress()); + + LOG_INFO(Service_IRS, + "called, npad_type={}, npad_id={}, transfer_memory_size={}, transfer_memory_size={}, " + "applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.transfer_memory_size, t_mem->GetSize(), parameters.applet_resource_user_id); + + const auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessorWithCoreContext(parameters.camera_handle, device); + auto& image_transfer_processor = + GetProcessor(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + image_transfer_processor.SetTransferMemoryPointer(transfer_memory); + } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -172,32 +236,68 @@ void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw()}; - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", - parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, - parameters.applet_resource_user_id); + LOG_DEBUG(Service_IRS, "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.applet_resource_user_id); - IPC::ResponseBuilder rb{ctx, 5}; + const auto result = IsIrCameraHandleValid(parameters.camera_handle); + if (result.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + const auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + + if (device.mode != Core::IrSensor::IrSensorMode::ImageTransferProcessor) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(InvalidProcessorState); + return; + } + + std::vector data{}; + const auto& image_transfer_processor = + GetProcessor(parameters.camera_handle); + const auto& state = image_transfer_processor.GetState(data); + + ctx.WriteBuffer(data); + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(ResultSuccess); - rb.PushRaw(system.CoreTiming().GetCPUTicks()); - rb.PushRaw(0); + rb.PushRaw(state); } void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto camera_handle{rp.PopRaw()}; - const auto processor_config{rp.PopRaw()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + Core::IrSensor::IrCameraHandle camera_handle; + Core::IrSensor::PackedTeraPluginProcessorConfig processor_config; + INSERT_PADDING_WORDS_NOINIT(1); + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, mode={}, mcu_version={}.{}, " - "applet_resource_user_id={}", - camera_handle.npad_type, camera_handle.npad_id, processor_config.mode, - processor_config.required_mcu_version.major, - processor_config.required_mcu_version.minor, applet_resource_user_id); + const auto parameters{rp.PopRaw()}; + + LOG_WARNING( + Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, mode={}, mcu_version={}.{}, " + "applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.processor_config.mode, parameters.processor_config.required_mcu_version.major, + parameters.processor_config.required_mcu_version.minor, parameters.applet_resource_user_id); + + const auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessor(parameters.camera_handle, device); + auto& image_transfer_processor = + GetProcessor(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) { @@ -207,17 +307,17 @@ void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) { if (npad_id > Core::HID::NpadIdType::Player8 && npad_id != Core::HID::NpadIdType::Invalid && npad_id != Core::HID::NpadIdType::Handheld) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(InvalidNpadId); + rb.Push(Service::HID::InvalidNpadId); return; } - IrCameraHandle camera_handle{ + Core::IrSensor::IrCameraHandle camera_handle{ .npad_id = static_cast(NpadIdTypeToIndex(npad_id)), .npad_type = Core::HID::NpadStyleIndex::None, }; - LOG_WARNING(Service_IRS, "(STUBBED) called, npad_id={}, camera_npad_id={}, camera_npad_type={}", - npad_id, camera_handle.npad_id, camera_handle.npad_type); + LOG_INFO(Service_IRS, "called, npad_id={}, camera_npad_id={}, camera_npad_type={}", npad_id, + camera_handle.npad_id, camera_handle.npad_type); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); @@ -226,8 +326,8 @@ void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) { void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto camera_handle{rp.PopRaw()}; - const auto processor_config{rp.PopRaw()}; + const auto camera_handle{rp.PopRaw()}; + const auto processor_config{rp.PopRaw()}; const auto applet_resource_user_id{rp.Pop()}; LOG_WARNING( @@ -236,14 +336,23 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) { camera_handle.npad_type, camera_handle.npad_id, processor_config.required_mcu_version.major, processor_config.required_mcu_version.minor, applet_resource_user_id); + auto result = IsIrCameraHandleValid(camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle); + MakeProcessor(camera_handle, device); + auto& image_transfer_processor = GetProcessor(camera_handle); + image_transfer_processor.SetConfig(processor_config); + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -256,14 +365,20 @@ void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + auto result = IsIrCameraHandleValid(parameters.camera_handle); + if (result.IsSuccess()) { + // TODO: Suspend image processor + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto camera_handle{rp.PopRaw()}; - const auto mcu_version{rp.PopRaw()}; + const auto camera_handle{rp.PopRaw()}; + const auto mcu_version{rp.PopRaw()}; const auto applet_resource_user_id{rp.Pop()}; LOG_WARNING( @@ -272,37 +387,45 @@ void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) { camera_handle.npad_type, camera_handle.npad_id, applet_resource_user_id, mcu_version.major, mcu_version.minor); + auto result = IsIrCameraHandleValid(camera_handle); + if (result.IsSuccess()) { + // TODO: Check firmware version + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::SetFunctionLevel(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - struct Parameters { - IrCameraHandle camera_handle; - PackedFunctionLevel function_level; - u64 applet_resource_user_id; - }; - static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); + const auto camera_handle{rp.PopRaw()}; + const auto function_level{rp.PopRaw()}; + const auto applet_resource_user_id{rp.Pop()}; - const auto parameters{rp.PopRaw()}; + LOG_WARNING( + Service_IRS, + "(STUBBED) called, npad_type={}, npad_id={}, function_level={}, applet_resource_user_id={}", + camera_handle.npad_type, camera_handle.npad_id, function_level.function_level, + applet_resource_user_id); - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", - parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, - parameters.applet_resource_user_id); + auto result = IsIrCameraHandleValid(camera_handle); + if (result.IsSuccess()) { + // TODO: Set Function level + result = ResultSuccess; + } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - PackedImageTransferProcessorExConfig processor_config; + Core::IrSensor::PackedImageTransferProcessorExConfig processor_config; u64 transfer_memory_size; }; static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size."); @@ -313,20 +436,33 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) { auto t_mem = system.CurrentProcess()->GetHandleTable().GetObject(t_mem_handle); - LOG_WARNING(Service_IRS, - "(STUBBED) called, npad_type={}, npad_id={}, transfer_memory_size={}, " - "applet_resource_user_id={}", - parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, - parameters.transfer_memory_size, parameters.applet_resource_user_id); + u8* transfer_memory = system.Memory().GetPointer(t_mem->GetSourceAddress()); + + LOG_INFO(Service_IRS, + "called, npad_type={}, npad_id={}, transfer_memory_size={}, " + "applet_resource_user_id={}", + parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, + parameters.transfer_memory_size, parameters.applet_resource_user_id); + + auto result = IsIrCameraHandleValid(parameters.camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle); + MakeProcessorWithCoreContext(parameters.camera_handle, device); + auto& image_transfer_processor = + GetProcessor(parameters.camera_handle); + image_transfer_processor.SetConfig(parameters.processor_config); + image_transfer_processor.SetTransferMemoryPointer(transfer_memory); + } IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto camera_handle{rp.PopRaw()}; - const auto processor_config{rp.PopRaw()}; + const auto camera_handle{rp.PopRaw()}; + const auto processor_config{rp.PopRaw()}; const auto applet_resource_user_id{rp.Pop()}; LOG_WARNING(Service_IRS, @@ -336,14 +472,23 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) { processor_config.required_mcu_version.major, processor_config.required_mcu_version.minor, applet_resource_user_id); + auto result = IsIrCameraHandleValid(camera_handle); + + if (result.IsSuccess()) { + auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle); + MakeProcessor(camera_handle, device); + auto& image_transfer_processor = GetProcessor(camera_handle); + image_transfer_processor.SetConfig(processor_config); + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - IrCameraHandle camera_handle; + Core::IrSensor::IrCameraHandle camera_handle; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -356,14 +501,20 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) { parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, parameters.applet_resource_user_id); + auto result = IsIrCameraHandleValid(parameters.camera_handle); + if (result.IsSuccess()) { + // TODO: Stop image processor async + result = ResultSuccess; + } + IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - PackedFunctionLevel function_level; + Core::IrSensor::PackedFunctionLevel function_level; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -378,7 +529,22 @@ void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) { rb.Push(ResultSuccess); } -IRS::~IRS() = default; +Result IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const { + if (camera_handle.npad_id > + static_cast(NpadIdTypeToIndex(Core::HID::NpadIdType::Handheld))) { + return InvalidIrCameraHandle; + } + if (camera_handle.npad_type != Core::HID::NpadStyleIndex::None) { + return InvalidIrCameraHandle; + } + return ResultSuccess; +} + +Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry( + const Core::IrSensor::IrCameraHandle& camera_handle) { + ASSERT_MSG(sizeof(StatusManager::device) > camera_handle.npad_id, "invalid npad_id"); + return shared_memory->device[camera_handle.npad_id]; +} IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} { // clang-format off @@ -395,4 +561,4 @@ IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} { IRS_SYS::~IRS_SYS() = default; -} // namespace Service::HID +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h index 361dc2213..2e6115c73 100755 --- a/src/core/hle/service/hid/irs.h +++ b/src/core/hle/service/hid/irs.h @@ -4,13 +4,19 @@ #pragma once #include "core/hid/hid_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" #include "core/hle/service/service.h" namespace Core { class System; } -namespace Service::HID { +namespace Core::HID { +class EmulatedController; +} // namespace Core::HID + +namespace Service::IRS { class IRS final : public ServiceFramework { public: @@ -18,234 +24,19 @@ public: ~IRS() override; private: - // This is nn::irsensor::IrCameraStatus - enum IrCameraStatus : u32 { - Available, - Unsupported, - Unconnected, + // This is nn::irsensor::detail::AruidFormat + struct AruidFormat { + u64 sensor_aruid; + u64 sensor_aruid_status; }; + static_assert(sizeof(AruidFormat) == 0x10, "AruidFormat is an invalid size"); - // This is nn::irsensor::IrCameraInternalStatus - enum IrCameraInternalStatus : u32 { - Stopped, - FirmwareUpdateNeeded, - Unkown2, - Unkown3, - Unkown4, - FirmwareVersionRequested, - FirmwareVersionIsInvalid, - Ready, - Setting, + // This is nn::irsensor::detail::StatusManager + struct StatusManager { + std::array device; + std::array aruid; }; - - // This is nn::irsensor::detail::StatusManager::IrSensorMode - enum IrSensorMode : u64 { - None, - MomentProcessor, - ClusteringProcessor, - ImageTransferProcessor, - PointingProcessorMarker, - TeraPluginProcessor, - IrLedProcessor, - }; - - // This is nn::irsensor::ImageProcessorStatus - enum ImageProcessorStatus : u8 { - stopped, - running, - }; - - // This is nn::irsensor::ImageTransferProcessorFormat - enum ImageTransferProcessorFormat : u8 { - Size320x240, - Size160x120, - Size80x60, - Size40x30, - Size20x15, - }; - - // This is nn::irsensor::AdaptiveClusteringMode - enum AdaptiveClusteringMode : u8 { - StaticFov, - DynamicFov, - }; - - // This is nn::irsensor::AdaptiveClusteringTargetDistance - enum AdaptiveClusteringTargetDistance : u8 { - Near, - Middle, - Far, - }; - - // This is nn::irsensor::IrsHandAnalysisMode - enum IrsHandAnalysisMode : u8 { - Silhouette, - Image, - SilhoueteAndImage, - SilhuetteOnly, - }; - - // This is nn::irsensor::IrSensorFunctionLevel - enum IrSensorFunctionLevel : u8 { - unknown0, - unknown1, - unknown2, - unknown3, - unknown4, - }; - - // This is nn::irsensor::IrCameraHandle - struct IrCameraHandle { - u8 npad_id{}; - Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None}; - INSERT_PADDING_BYTES(2); - }; - static_assert(sizeof(IrCameraHandle) == 4, "IrCameraHandle is an invalid size"); - - struct IrsRect { - s16 x; - s16 y; - s16 width; - s16 height; - }; - - // This is nn::irsensor::PackedMcuVersion - struct PackedMcuVersion { - u16 major; - u16 minor; - }; - static_assert(sizeof(PackedMcuVersion) == 4, "PackedMcuVersion is an invalid size"); - - // This is nn::irsensor::MomentProcessorConfig - struct MomentProcessorConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(7); - IrsRect window_of_interest; - u8 preprocess; - u8 preprocess_intensity_threshold; - INSERT_PADDING_BYTES(5); - }; - static_assert(sizeof(MomentProcessorConfig) == 0x28, - "MomentProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedMomentProcessorConfig - struct PackedMomentProcessorConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(5); - IrsRect window_of_interest; - PackedMcuVersion required_mcu_version; - u8 preprocess; - u8 preprocess_intensity_threshold; - INSERT_PADDING_BYTES(2); - }; - static_assert(sizeof(PackedMomentProcessorConfig) == 0x20, - "PackedMomentProcessorConfig is an invalid size"); - - // This is nn::irsensor::ClusteringProcessorConfig - struct ClusteringProcessorConfig { - u64 exposire_time; - u32 light_target; - u32 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(7); - IrsRect window_of_interest; - u32 pixel_count_min; - u32 pixel_count_max; - u32 object_intensity_min; - u8 is_external_light_filter_enabled; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(ClusteringProcessorConfig) == 0x30, - "ClusteringProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedClusteringProcessorConfig - struct PackedClusteringProcessorConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(5); - IrsRect window_of_interest; - PackedMcuVersion required_mcu_version; - u32 pixel_count_min; - u32 pixel_count_max; - u32 object_intensity_min; - u8 is_external_light_filter_enabled; - INSERT_PADDING_BYTES(2); - }; - static_assert(sizeof(PackedClusteringProcessorConfig) == 0x30, - "PackedClusteringProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedImageTransferProcessorConfig - struct PackedImageTransferProcessorConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(5); - PackedMcuVersion required_mcu_version; - u8 format; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedImageTransferProcessorConfig) == 0x18, - "PackedImageTransferProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedTeraPluginProcessorConfig - struct PackedTeraPluginProcessorConfig { - PackedMcuVersion required_mcu_version; - u8 mode; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedTeraPluginProcessorConfig) == 0x8, - "PackedTeraPluginProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedPointingProcessorConfig - struct PackedPointingProcessorConfig { - IrsRect window_of_interest; - PackedMcuVersion required_mcu_version; - }; - static_assert(sizeof(PackedPointingProcessorConfig) == 0xC, - "PackedPointingProcessorConfig is an invalid size"); - - // This is nn::irsensor::PackedFunctionLevel - struct PackedFunctionLevel { - IrSensorFunctionLevel function_level; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedFunctionLevel) == 0x4, "PackedFunctionLevel is an invalid size"); - - // This is nn::irsensor::PackedImageTransferProcessorExConfig - struct PackedImageTransferProcessorExConfig { - u64 exposire_time; - u8 light_target; - u8 gain; - u8 is_negative_used; - INSERT_PADDING_BYTES(5); - PackedMcuVersion required_mcu_version; - ImageTransferProcessorFormat origin_format; - ImageTransferProcessorFormat trimming_format; - u16 trimming_start_x; - u16 trimming_start_y; - u8 is_external_light_filter_enabled; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedImageTransferProcessorExConfig) == 0x20, - "PackedImageTransferProcessorExConfig is an invalid size"); - - // This is nn::irsensor::PackedIrLedProcessorConfig - struct PackedIrLedProcessorConfig { - PackedMcuVersion required_mcu_version; - u8 light_target; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(PackedIrLedProcessorConfig) == 0x8, - "PackedIrLedProcessorConfig is an invalid size"); + static_assert(sizeof(StatusManager) == 0x8000, "StatusManager is an invalid size"); void ActivateIrsensor(Kernel::HLERequestContext& ctx); void DeactivateIrsensor(Kernel::HLERequestContext& ctx); @@ -265,6 +56,56 @@ private: void RunIrLedProcessor(Kernel::HLERequestContext& ctx); void StopImageProcessorAsync(Kernel::HLERequestContext& ctx); void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx); + + Result IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const; + Core::IrSensor::DeviceFormat& GetIrCameraSharedMemoryDeviceEntry( + const Core::IrSensor::IrCameraHandle& camera_handle); + + template + void MakeProcessor(const Core::IrSensor::IrCameraHandle& handle, + Core::IrSensor::DeviceFormat& device_state) { + const auto index = static_cast(handle.npad_id); + if (index > sizeof(processors)) { + LOG_CRITICAL(Service_IRS, "Invalid index {}", index); + return; + } + processors[index] = std::make_unique(device_state); + } + + template + void MakeProcessorWithCoreContext(const Core::IrSensor::IrCameraHandle& handle, + Core::IrSensor::DeviceFormat& device_state) { + const auto index = static_cast(handle.npad_id); + if (index > sizeof(processors)) { + LOG_CRITICAL(Service_IRS, "Invalid index {}", index); + return; + } + processors[index] = std::make_unique(system.HIDCore(), device_state, index); + } + + template + T& GetProcessor(const Core::IrSensor::IrCameraHandle& handle) { + const auto index = static_cast(handle.npad_id); + if (index > sizeof(processors)) { + LOG_CRITICAL(Service_IRS, "Invalid index {}", index); + return static_cast(*processors[0]); + } + return static_cast(*processors[index]); + } + + template + const T& GetProcessor(const Core::IrSensor::IrCameraHandle& handle) const { + const auto index = static_cast(handle.npad_id); + if (index > sizeof(processors)) { + LOG_CRITICAL(Service_IRS, "Invalid index {}", index); + return static_cast(*processors[0]); + } + return static_cast(*processors[index]); + } + + Core::HID::EmulatedController* npad_device = nullptr; + StatusManager* shared_memory = nullptr; + std::array, 9> processors{}; }; class IRS_SYS final : public ServiceFramework { @@ -273,4 +114,4 @@ public: ~IRS_SYS() override; }; -} // namespace Service::HID +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.cpp b/src/core/hle/service/hid/irsensor/clustering_processor.cpp new file mode 100755 index 000000000..6479af212 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/clustering_processor.cpp @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/clustering_processor.h" + +namespace Service::IRS { +ClusteringProcessor::ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::ClusteringProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +ClusteringProcessor::~ClusteringProcessor() = default; + +void ClusteringProcessor::StartProcessor() {} + +void ClusteringProcessor::SuspendProcessor() {} + +void ClusteringProcessor::StopProcessor() {} + +void ClusteringProcessor::SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config) { + current_config.camera_config.exposure_time = config.camera_config.exposure_time; + current_config.camera_config.gain = config.camera_config.gain; + current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; + current_config.camera_config.light_target = + static_cast(config.camera_config.light_target); + current_config.pixel_count_min = config.pixel_count_min; + current_config.pixel_count_max = config.pixel_count_max; + current_config.is_external_light_filter_enabled = config.is_external_light_filter_enabled; + current_config.object_intensity_min = config.object_intensity_min; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.h b/src/core/hle/service/hid/irsensor/clustering_processor.h new file mode 100755 index 000000000..6e2ba8846 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/clustering_processor.h @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class ClusteringProcessor final : public ProcessorBase { +public: + explicit ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format); + ~ClusteringProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config); + +private: + // This is nn::irsensor::ClusteringProcessorConfig + struct ClusteringProcessorConfig { + Core::IrSensor::CameraConfig camera_config; + Core::IrSensor::IrsRect window_of_interest; + u32 pixel_count_min; + u32 pixel_count_max; + u32 object_intensity_min; + bool is_external_light_filter_enabled; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(ClusteringProcessorConfig) == 0x30, + "ClusteringProcessorConfig is an invalid size"); + + // This is nn::irsensor::AdaptiveClusteringProcessorConfig + struct AdaptiveClusteringProcessorConfig { + Core::IrSensor::AdaptiveClusteringMode mode; + Core::IrSensor::AdaptiveClusteringTargetDistance target_distance; + }; + static_assert(sizeof(AdaptiveClusteringProcessorConfig) == 0x8, + "AdaptiveClusteringProcessorConfig is an invalid size"); + + // This is nn::irsensor::ClusteringData + struct ClusteringData { + f32 average_intensity; + Core::IrSensor::IrsCentroid centroid; + u32 pixel_count; + Core::IrSensor::IrsRect bound; + }; + static_assert(sizeof(ClusteringData) == 0x18, "ClusteringData is an invalid size"); + + // This is nn::irsensor::ClusteringProcessorState + struct ClusteringProcessorState { + s64 sampling_number; + u64 timestamp; + u8 object_count; + INSERT_PADDING_BYTES(3); + Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level; + std::array data; + }; + static_assert(sizeof(ClusteringProcessorState) == 0x198, + "ClusteringProcessorState is an invalid size"); + + ClusteringProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp new file mode 100755 index 000000000..98f0c579d --- /dev/null +++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hle/service/hid/irsensor/image_transfer_processor.h" + +namespace Service::IRS { +ImageTransferProcessor::ImageTransferProcessor(Core::HID::HIDCore& hid_core_, + Core::IrSensor::DeviceFormat& device_format, + std::size_t npad_index) + : device{device_format} { + npad_device = hid_core_.GetEmulatedControllerByIndex(npad_index); + + Core::HID::ControllerUpdateCallback engine_callback{ + .on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); }, + .is_npad_service = true, + }; + callback_key = npad_device->SetCallback(engine_callback); + + device.mode = Core::IrSensor::IrSensorMode::ImageTransferProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +ImageTransferProcessor::~ImageTransferProcessor() { + npad_device->DeleteCallback(callback_key); +}; + +void ImageTransferProcessor::StartProcessor() { + is_active = true; + device.camera_status = Core::IrSensor::IrCameraStatus::Available; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready; + processor_state.sampling_number = 0; + processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low; +} + +void ImageTransferProcessor::SuspendProcessor() {} + +void ImageTransferProcessor::StopProcessor() {} + +void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) { + if (type != Core::HID::ControllerTriggerType::IrSensor) { + return; + } + if (!is_transfer_memory_set) { + return; + } + + const auto camera_data = npad_device->GetCamera(); + + // This indicates how much ambient light is precent + processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low; + processor_state.sampling_number = camera_data.sample; + + if (camera_data.format != current_config.origin_format) { + LOG_WARNING(Service_IRS, "Wrong Input format {} expected {}", camera_data.format, + current_config.origin_format); + memset(transfer_memory, 0, GetDataSize(current_config.trimming_format)); + return; + } + + if (current_config.origin_format > current_config.trimming_format) { + LOG_WARNING(Service_IRS, "Origin format {} is smaller than trimming format {}", + current_config.origin_format, current_config.trimming_format); + memset(transfer_memory, 0, GetDataSize(current_config.trimming_format)); + return; + } + + std::vector window_data{}; + const auto origin_width = GetDataWidth(current_config.origin_format); + const auto origin_height = GetDataHeight(current_config.origin_format); + const auto trimming_width = GetDataWidth(current_config.trimming_format); + const auto trimming_height = GetDataHeight(current_config.trimming_format); + window_data.resize(GetDataSize(current_config.trimming_format)); + + if (trimming_width + current_config.trimming_start_x > origin_width || + trimming_height + current_config.trimming_start_y > origin_height) { + LOG_WARNING(Service_IRS, + "Trimming area ({}, {}, {}, {}) is outside of origin area ({}, {})", + current_config.trimming_start_x, current_config.trimming_start_y, + trimming_width, trimming_height, origin_width, origin_height); + memset(transfer_memory, 0, GetDataSize(current_config.trimming_format)); + return; + } + + for (std::size_t y = 0; y < trimming_height; y++) { + for (std::size_t x = 0; x < trimming_width; x++) { + const std::size_t window_index = (y * trimming_width) + x; + const std::size_t origin_index = + ((y + current_config.trimming_start_y) * origin_width) + x + + current_config.trimming_start_x; + window_data[window_index] = camera_data.data[origin_index]; + } + } + + memcpy(transfer_memory, window_data.data(), GetDataSize(current_config.trimming_format)); + + if (!IsProcessorActive()) { + StartProcessor(); + } +} + +void ImageTransferProcessor::SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config) { + current_config.camera_config.exposure_time = config.camera_config.exposure_time; + current_config.camera_config.gain = config.camera_config.gain; + current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; + current_config.camera_config.light_target = + static_cast(config.camera_config.light_target); + current_config.origin_format = + static_cast(config.format); + current_config.trimming_format = + static_cast(config.format); + current_config.trimming_start_x = 0; + current_config.trimming_start_y = 0; + + npad_device->SetCameraFormat(current_config.origin_format); +} + +void ImageTransferProcessor::SetConfig( + Core::IrSensor::PackedImageTransferProcessorExConfig config) { + current_config.camera_config.exposure_time = config.camera_config.exposure_time; + current_config.camera_config.gain = config.camera_config.gain; + current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; + current_config.camera_config.light_target = + static_cast(config.camera_config.light_target); + current_config.origin_format = + static_cast(config.origin_format); + current_config.trimming_format = + static_cast(config.trimming_format); + current_config.trimming_start_x = config.trimming_start_x; + current_config.trimming_start_y = config.trimming_start_y; + + npad_device->SetCameraFormat(current_config.origin_format); +} + +void ImageTransferProcessor::SetTransferMemoryPointer(u8* t_mem) { + is_transfer_memory_set = true; + transfer_memory = t_mem; +} + +Core::IrSensor::ImageTransferProcessorState ImageTransferProcessor::GetState( + std::vector& data) const { + const auto size = GetDataSize(current_config.trimming_format); + data.resize(size); + memcpy(data.data(), transfer_memory, size); + return processor_state; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.h b/src/core/hle/service/hid/irsensor/image_transfer_processor.h new file mode 100755 index 000000000..393df492d --- /dev/null +++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Core::HID { +class EmulatedController; +} // namespace Core::HID + +namespace Service::IRS { +class ImageTransferProcessor final : public ProcessorBase { +public: + explicit ImageTransferProcessor(Core::HID::HIDCore& hid_core_, + Core::IrSensor::DeviceFormat& device_format, + std::size_t npad_index); + ~ImageTransferProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config); + void SetConfig(Core::IrSensor::PackedImageTransferProcessorExConfig config); + + // Transfer memory where the image data will be stored + void SetTransferMemoryPointer(u8* t_mem); + + Core::IrSensor::ImageTransferProcessorState GetState(std::vector& data) const; + +private: + // This is nn::irsensor::ImageTransferProcessorConfig + struct ImageTransferProcessorConfig { + Core::IrSensor::CameraConfig camera_config; + Core::IrSensor::ImageTransferProcessorFormat format; + }; + static_assert(sizeof(ImageTransferProcessorConfig) == 0x20, + "ImageTransferProcessorConfig is an invalid size"); + + // This is nn::irsensor::ImageTransferProcessorExConfig + struct ImageTransferProcessorExConfig { + Core::IrSensor::CameraConfig camera_config; + Core::IrSensor::ImageTransferProcessorFormat origin_format; + Core::IrSensor::ImageTransferProcessorFormat trimming_format; + u16 trimming_start_x; + u16 trimming_start_y; + bool is_external_light_filter_enabled; + INSERT_PADDING_BYTES(3); + }; + static_assert(sizeof(ImageTransferProcessorExConfig) == 0x28, + "ImageTransferProcessorExConfig is an invalid size"); + + void OnControllerUpdate(Core::HID::ControllerTriggerType type); + + ImageTransferProcessorExConfig current_config{}; + Core::IrSensor::ImageTransferProcessorState processor_state{}; + Core::IrSensor::DeviceFormat& device; + Core::HID::EmulatedController* npad_device; + int callback_key{}; + + u8* transfer_memory = nullptr; + bool is_transfer_memory_set = false; +}; +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.cpp b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp new file mode 100755 index 000000000..8e6dd99e4 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/ir_led_processor.h" + +namespace Service::IRS { +IrLedProcessor::IrLedProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::IrLedProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +IrLedProcessor::~IrLedProcessor() = default; + +void IrLedProcessor::StartProcessor() {} + +void IrLedProcessor::SuspendProcessor() {} + +void IrLedProcessor::StopProcessor() {} + +void IrLedProcessor::SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config) { + current_config.light_target = + static_cast(config.light_target); +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.h b/src/core/hle/service/hid/irsensor/ir_led_processor.h new file mode 100755 index 000000000..c3d8693c9 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/ir_led_processor.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class IrLedProcessor final : public ProcessorBase { +public: + explicit IrLedProcessor(Core::IrSensor::DeviceFormat& device_format); + ~IrLedProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config); + +private: + // This is nn::irsensor::IrLedProcessorConfig + struct IrLedProcessorConfig { + Core::IrSensor::CameraLightTarget light_target; + }; + static_assert(sizeof(IrLedProcessorConfig) == 0x4, "IrLedProcessorConfig is an invalid size"); + + struct IrLedProcessorState { + s64 sampling_number; + u64 timestamp; + std::array data; + }; + static_assert(sizeof(IrLedProcessorState) == 0x18, "IrLedProcessorState is an invalid size"); + + IrLedProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/moment_processor.cpp b/src/core/hle/service/hid/irsensor/moment_processor.cpp new file mode 100755 index 000000000..dbaca420a --- /dev/null +++ b/src/core/hle/service/hid/irsensor/moment_processor.cpp @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/moment_processor.h" + +namespace Service::IRS { +MomentProcessor::MomentProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::MomentProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +MomentProcessor::~MomentProcessor() = default; + +void MomentProcessor::StartProcessor() {} + +void MomentProcessor::SuspendProcessor() {} + +void MomentProcessor::StopProcessor() {} + +void MomentProcessor::SetConfig(Core::IrSensor::PackedMomentProcessorConfig config) { + current_config.camera_config.exposure_time = config.camera_config.exposure_time; + current_config.camera_config.gain = config.camera_config.gain; + current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; + current_config.camera_config.light_target = + static_cast(config.camera_config.light_target); + current_config.window_of_interest = config.window_of_interest; + current_config.preprocess = + static_cast(config.preprocess); + current_config.preprocess_intensity_threshold = config.preprocess_intensity_threshold; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/moment_processor.h b/src/core/hle/service/hid/irsensor/moment_processor.h new file mode 100755 index 000000000..d4bd22e0f --- /dev/null +++ b/src/core/hle/service/hid/irsensor/moment_processor.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class MomentProcessor final : public ProcessorBase { +public: + explicit MomentProcessor(Core::IrSensor::DeviceFormat& device_format); + ~MomentProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedMomentProcessorConfig config); + +private: + // This is nn::irsensor::MomentProcessorConfig + struct MomentProcessorConfig { + Core::IrSensor::CameraConfig camera_config; + Core::IrSensor::IrsRect window_of_interest; + Core::IrSensor::MomentProcessorPreprocess preprocess; + u32 preprocess_intensity_threshold; + }; + static_assert(sizeof(MomentProcessorConfig) == 0x28, + "MomentProcessorConfig is an invalid size"); + + // This is nn::irsensor::MomentStatistic + struct MomentStatistic { + f32 average_intensity; + Core::IrSensor::IrsCentroid centroid; + }; + static_assert(sizeof(MomentStatistic) == 0xC, "MomentStatistic is an invalid size"); + + // This is nn::irsensor::MomentProcessorState + struct MomentProcessorState { + s64 sampling_number; + u64 timestamp; + Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level; + INSERT_PADDING_BYTES(4); + std::array stadistic; + }; + static_assert(sizeof(MomentProcessorState) == 0x258, "MomentProcessorState is an invalid size"); + + MomentProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.cpp b/src/core/hle/service/hid/irsensor/pointing_processor.cpp new file mode 100755 index 000000000..929f177fc --- /dev/null +++ b/src/core/hle/service/hid/irsensor/pointing_processor.cpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/pointing_processor.h" + +namespace Service::IRS { +PointingProcessor::PointingProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::PointingProcessorMarker; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +PointingProcessor::~PointingProcessor() = default; + +void PointingProcessor::StartProcessor() {} + +void PointingProcessor::SuspendProcessor() {} + +void PointingProcessor::StopProcessor() {} + +void PointingProcessor::SetConfig(Core::IrSensor::PackedPointingProcessorConfig config) { + current_config.window_of_interest = config.window_of_interest; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.h b/src/core/hle/service/hid/irsensor/pointing_processor.h new file mode 100755 index 000000000..cf4930794 --- /dev/null +++ b/src/core/hle/service/hid/irsensor/pointing_processor.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class PointingProcessor final : public ProcessorBase { +public: + explicit PointingProcessor(Core::IrSensor::DeviceFormat& device_format); + ~PointingProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedPointingProcessorConfig config); + +private: + // This is nn::irsensor::PointingProcessorConfig + struct PointingProcessorConfig { + Core::IrSensor::IrsRect window_of_interest; + }; + static_assert(sizeof(PointingProcessorConfig) == 0x8, + "PointingProcessorConfig is an invalid size"); + + struct PointingProcessorMarkerData { + u8 pointing_status; + INSERT_PADDING_BYTES(3); + u32 unknown; + float unkown_float1; + float position_x; + float position_y; + float unkown_float2; + Core::IrSensor::IrsRect window_of_interest; + }; + static_assert(sizeof(PointingProcessorMarkerData) == 0x20, + "PointingProcessorMarkerData is an invalid size"); + + struct PointingProcessorMarkerState { + s64 sampling_number; + u64 timestamp; + std::array data; + }; + static_assert(sizeof(PointingProcessorMarkerState) == 0x70, + "PointingProcessorMarkerState is an invalid size"); + + PointingProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/processor_base.cpp b/src/core/hle/service/hid/irsensor/processor_base.cpp new file mode 100755 index 000000000..4d43ca17a --- /dev/null +++ b/src/core/hle/service/hid/irsensor/processor_base.cpp @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { + +ProcessorBase::ProcessorBase() {} +ProcessorBase::~ProcessorBase() = default; + +bool ProcessorBase::IsProcessorActive() const { + return is_active; +} + +std::size_t ProcessorBase::GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const { + switch (format) { + case Core::IrSensor::ImageTransferProcessorFormat::Size320x240: + return 320 * 240; + case Core::IrSensor::ImageTransferProcessorFormat::Size160x120: + return 160 * 120; + case Core::IrSensor::ImageTransferProcessorFormat::Size80x60: + return 80 * 60; + case Core::IrSensor::ImageTransferProcessorFormat::Size40x30: + return 40 * 30; + case Core::IrSensor::ImageTransferProcessorFormat::Size20x15: + return 20 * 15; + default: + return 0; + } +} + +std::size_t ProcessorBase::GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const { + switch (format) { + case Core::IrSensor::ImageTransferProcessorFormat::Size320x240: + return 320; + case Core::IrSensor::ImageTransferProcessorFormat::Size160x120: + return 160; + case Core::IrSensor::ImageTransferProcessorFormat::Size80x60: + return 80; + case Core::IrSensor::ImageTransferProcessorFormat::Size40x30: + return 40; + case Core::IrSensor::ImageTransferProcessorFormat::Size20x15: + return 20; + default: + return 0; + } +} + +std::size_t ProcessorBase::GetDataHeight( + Core::IrSensor::ImageTransferProcessorFormat format) const { + switch (format) { + case Core::IrSensor::ImageTransferProcessorFormat::Size320x240: + return 240; + case Core::IrSensor::ImageTransferProcessorFormat::Size160x120: + return 120; + case Core::IrSensor::ImageTransferProcessorFormat::Size80x60: + return 60; + case Core::IrSensor::ImageTransferProcessorFormat::Size40x30: + return 30; + case Core::IrSensor::ImageTransferProcessorFormat::Size20x15: + return 15; + default: + return 0; + } +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/processor_base.h b/src/core/hle/service/hid/irsensor/processor_base.h new file mode 100755 index 000000000..bc0d2977b --- /dev/null +++ b/src/core/hle/service/hid/irsensor/processor_base.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hid/irs_types.h" + +namespace Service::IRS { +class ProcessorBase { +public: + explicit ProcessorBase(); + virtual ~ProcessorBase(); + + virtual void StartProcessor() = 0; + virtual void SuspendProcessor() = 0; + virtual void StopProcessor() = 0; + + bool IsProcessorActive() const; + +protected: + /// Returns the number of bytes the image uses + std::size_t GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const; + + /// Returns the width of the image + std::size_t GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const; + + /// Returns the height of the image + std::size_t GetDataHeight(Core::IrSensor::ImageTransferProcessorFormat format) const; + + bool is_active{false}; +}; +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp new file mode 100755 index 000000000..e691c840a --- /dev/null +++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/hid/irsensor/tera_plugin_processor.h" + +namespace Service::IRS { +TeraPluginProcessor::TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format) + : device(device_format) { + device.mode = Core::IrSensor::IrSensorMode::TeraPluginProcessor; + device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; + device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; +} + +TeraPluginProcessor::~TeraPluginProcessor() = default; + +void TeraPluginProcessor::StartProcessor() {} + +void TeraPluginProcessor::SuspendProcessor() {} + +void TeraPluginProcessor::StopProcessor() {} + +void TeraPluginProcessor::SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config) { + current_config.mode = config.mode; + current_config.unknown_1 = config.unknown_1; + current_config.unknown_2 = config.unknown_2; + current_config.unknown_3 = config.unknown_3; +} + +} // namespace Service::IRS diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.h b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h new file mode 100755 index 000000000..bbea7ed0b --- /dev/null +++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "core/hid/irs_types.h" +#include "core/hle/service/hid/irsensor/processor_base.h" + +namespace Service::IRS { +class TeraPluginProcessor final : public ProcessorBase { +public: + explicit TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format); + ~TeraPluginProcessor() override; + + // Called when the processor is initialized + void StartProcessor() override; + + // Called when the processor is suspended + void SuspendProcessor() override; + + // Called when the processor is stopped + void StopProcessor() override; + + // Sets config parameters of the camera + void SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config); + +private: + // This is nn::irsensor::TeraPluginProcessorConfig + struct TeraPluginProcessorConfig { + u8 mode; + u8 unknown_1; + u8 unknown_2; + u8 unknown_3; + }; + static_assert(sizeof(TeraPluginProcessorConfig) == 0x4, + "TeraPluginProcessorConfig is an invalid size"); + + struct TeraPluginProcessorState { + s64 sampling_number; + u64 timestamp; + Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level; + std::array data; + }; + static_assert(sizeof(TeraPluginProcessorState) == 0x140, + "TeraPluginProcessorState is an invalid size"); + + TeraPluginProcessorConfig current_config{}; + Core::IrSensor::DeviceFormat& device; +}; + +} // namespace Service::IRS diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 48e799cf5..90dd629c6 100755 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -1,4 +1,6 @@ add_library(input_common STATIC + drivers/camera.cpp + drivers/camera.h drivers/gc_adapter.cpp drivers/gc_adapter.h drivers/keyboard.cpp diff --git a/src/input_common/drivers/camera.cpp b/src/input_common/drivers/camera.cpp new file mode 100755 index 000000000..dceea67e0 --- /dev/null +++ b/src/input_common/drivers/camera.cpp @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "common/param_package.h" +#include "input_common/drivers/camera.h" + +namespace InputCommon { +constexpr PadIdentifier identifier = { + .guid = Common::UUID{}, + .port = 0, + .pad = 0, +}; + +Camera::Camera(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + PreSetController(identifier); +} + +void Camera::SetCameraData(std::size_t width, std::size_t height, std::vector data) { + const std::size_t desired_width = getImageWidth(); + const std::size_t desired_height = getImageHeight(); + status.data.resize(desired_width * desired_height); + + // Resize image to desired format + for (std::size_t y = 0; y < desired_height; y++) { + for (std::size_t x = 0; x < desired_width; x++) { + const std::size_t pixel_index = y * desired_width + x; + const std::size_t old_x = width * x / desired_width; + const std::size_t old_y = height * y / desired_height; + const std::size_t data_pixel_index = old_y * width + old_x; + status.data[pixel_index] = static_cast(data[data_pixel_index] & 0xFF); + } + } + + SetCamera(identifier, status); +} + +std::size_t Camera::getImageWidth() const { + switch (status.format) { + case Common::Input::CameraFormat::Size320x240: + return 320; + case Common::Input::CameraFormat::Size160x120: + return 160; + case Common::Input::CameraFormat::Size80x60: + return 80; + case Common::Input::CameraFormat::Size40x30: + return 40; + case Common::Input::CameraFormat::Size20x15: + return 20; + case Common::Input::CameraFormat::None: + default: + return 0; + } +} + +std::size_t Camera::getImageHeight() const { + switch (status.format) { + case Common::Input::CameraFormat::Size320x240: + return 240; + case Common::Input::CameraFormat::Size160x120: + return 120; + case Common::Input::CameraFormat::Size80x60: + return 60; + case Common::Input::CameraFormat::Size40x30: + return 30; + case Common::Input::CameraFormat::Size20x15: + return 15; + case Common::Input::CameraFormat::None: + default: + return 0; + } +} + +Common::Input::CameraError Camera::SetCameraFormat( + [[maybe_unused]] const PadIdentifier& identifier_, + const Common::Input::CameraFormat camera_format) { + status.format = camera_format; + return Common::Input::CameraError::None; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/camera.h b/src/input_common/drivers/camera.h new file mode 100755 index 000000000..b8a7c75e5 --- /dev/null +++ b/src/input_common/drivers/camera.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "input_common/input_engine.h" + +namespace InputCommon { + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class Camera final : public InputEngine { +public: + explicit Camera(std::string input_engine_); + + void SetCameraData(std::size_t width, std::size_t height, std::vector data); + + std::size_t getImageWidth() const; + std::size_t getImageHeight() const; + + Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_, + Common::Input::CameraFormat camera_format) override; + + Common::Input::CameraStatus status{}; +}; + +} // namespace InputCommon diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp index 12214d146..6ede0e4b0 100755 --- a/src/input_common/input_engine.cpp +++ b/src/input_common/input_engine.cpp @@ -90,6 +90,18 @@ void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const B TriggerOnMotionChange(identifier, motion, value); } +void InputEngine::SetCamera(const PadIdentifier& identifier, + const Common::Input::CameraStatus& value) { + { + std::scoped_lock lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.camera = value; + } + } + TriggerOnCameraChange(identifier, value); +} + bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const { std::scoped_lock lock{mutex}; const auto controller_iter = controller_list.find(identifier); @@ -165,6 +177,18 @@ BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) return controller.motions.at(motion); } +Common::Input::CameraStatus InputEngine::GetCamera(const PadIdentifier& identifier) const { + std::scoped_lock lock{mutex}; + const auto controller_iter = controller_list.find(identifier); + if (controller_iter == controller_list.cend()) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(), + identifier.pad, identifier.port); + return {}; + } + const ControllerData& controller = controller_iter->second; + return controller.camera; +} + void InputEngine::ResetButtonState() { for (const auto& controller : controller_list) { for (const auto& button : controller.second.buttons) { @@ -317,6 +341,20 @@ void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int mot }); } +void InputEngine::TriggerOnCameraChange(const PadIdentifier& identifier, + [[maybe_unused]] const Common::Input::CameraStatus& value) { + std::scoped_lock lock{mutex_callback}; + for (const auto& poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Camera, 0)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } +} + bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier, const PadIdentifier& identifier, EngineInputType type, int index) const { diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h index 13295bd49..f6b3c4610 100755 --- a/src/input_common/input_engine.h +++ b/src/input_common/input_engine.h @@ -36,11 +36,12 @@ struct BasicMotion { // Types of input that are stored in the engine enum class EngineInputType { None, - Button, - HatButton, Analog, - Motion, Battery, + Button, + Camera, + HatButton, + Motion, }; namespace std { @@ -115,10 +116,17 @@ public: // Sets polling mode to a controller virtual Common::Input::PollingError SetPollingMode( [[maybe_unused]] const PadIdentifier& identifier, - [[maybe_unused]] const Common::Input::PollingMode vibration) { + [[maybe_unused]] const Common::Input::PollingMode polling_mode) { return Common::Input::PollingError::NotSupported; } + // Sets camera format to a controller + virtual Common::Input::CameraError SetCameraFormat( + [[maybe_unused]] const PadIdentifier& identifier, + [[maybe_unused]] Common::Input::CameraFormat camera_format) { + return Common::Input::CameraError::NotSupported; + } + // Returns the engine name [[nodiscard]] const std::string& GetEngineName() const; @@ -174,6 +182,7 @@ public: f32 GetAxis(const PadIdentifier& identifier, int axis) const; Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const; BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const; + Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const; int SetCallback(InputIdentifier input_identifier); void SetMappingCallback(MappingCallback callback); @@ -185,6 +194,7 @@ protected: void SetAxis(const PadIdentifier& identifier, int axis, f32 value); void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value); void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value); + void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value); virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const { return "Unknown"; @@ -197,6 +207,7 @@ private: std::unordered_map axes; std::unordered_map motions; Common::Input::BatteryLevel battery{}; + Common::Input::CameraStatus camera{}; }; void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value); @@ -205,6 +216,8 @@ private: void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value); void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, const BasicMotion& value); + void TriggerOnCameraChange(const PadIdentifier& identifier, + const Common::Input::CameraStatus& value); bool IsInputIdentifierEqual(const InputIdentifier& input_identifier, const PadIdentifier& identifier, EngineInputType type, diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index 49ccb4422..133422d5c 100755 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp @@ -664,6 +664,47 @@ private: InputEngine* input_engine; }; +class InputFromCamera final : public Common::Input::InputDevice { +public: + explicit InputFromCamera(PadIdentifier identifier_, InputEngine* input_engine_) + : identifier(identifier_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Camera, + .index = 0, + .callback = engine_callback, + }; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromCamera() override { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::CameraStatus GetStatus() const { + return input_engine->GetCamera(identifier); + } + + void ForceUpdate() override { + OnChange(); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::IrSensor, + .camera_status = GetStatus(), + }; + + TriggerOnChange(status); + } + +private: + const PadIdentifier identifier; + int callback_key; + InputEngine* input_engine; +}; + class OutputFromIdentifier final : public Common::Input::OutputDevice { public: explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_) @@ -682,6 +723,10 @@ public: return input_engine->SetPollingMode(identifier, polling_mode); } + Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override { + return input_engine->SetCameraFormat(identifier, camera_format); + } + private: const PadIdentifier identifier; InputEngine* input_engine; @@ -920,6 +965,18 @@ std::unique_ptr InputFactory::CreateMotionDevice( properties_y, properties_z, input_engine.get()); } +std::unique_ptr InputFactory::CreateCameraDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast(params.Get("port", 0)), + .pad = static_cast(params.Get("pad", 0)), + }; + + input_engine->PreSetController(identifier); + return std::make_unique(identifier, input_engine.get()); +} + InputFactory::InputFactory(std::shared_ptr input_engine_) : input_engine(std::move(input_engine_)) {} @@ -928,6 +985,9 @@ std::unique_ptr InputFactory::Create( if (params.Has("battery")) { return CreateBatteryDevice(params); } + if (params.Has("camera")) { + return CreateCameraDevice(params); + } if (params.Has("button") && params.Has("axis")) { return CreateTriggerDevice(params); } diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h index 6ebe0dbf5..4410a8415 100755 --- a/src/input_common/input_poller.h +++ b/src/input_common/input_poller.h @@ -211,6 +211,17 @@ private: */ std::unique_ptr CreateMotionDevice(Common::ParamPackage params); + /** + * Creates a camera device from the parameters given. + * @param params contains parameters for creating the device: + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr CreateCameraDevice( + const Common::ParamPackage& params); + std::shared_ptr input_engine; }; } // namespace InputCommon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 21834fb6b..ca1cb9542 100755 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -5,6 +5,7 @@ #include #include "common/input.h" #include "common/param_package.h" +#include "input_common/drivers/camera.h" #include "input_common/drivers/gc_adapter.h" #include "input_common/drivers/keyboard.h" #include "input_common/drivers/mouse.h" @@ -78,6 +79,15 @@ struct InputSubsystem::Impl { Common::Input::RegisterFactory(tas_input->GetEngineName(), tas_output_factory); + camera = std::make_shared("camera"); + camera->SetMappingCallback(mapping_callback); + camera_input_factory = std::make_shared(camera); + camera_output_factory = std::make_shared(camera); + Common::Input::RegisterFactory(camera->GetEngineName(), + camera_input_factory); + Common::Input::RegisterFactory(camera->GetEngineName(), + camera_output_factory); + #ifdef HAVE_SDL2 sdl = std::make_shared("sdl"); sdl->SetMappingCallback(mapping_callback); @@ -317,6 +327,7 @@ struct InputSubsystem::Impl { std::shared_ptr touch_screen; std::shared_ptr tas_input; std::shared_ptr udp_client; + std::shared_ptr camera; std::shared_ptr keyboard_factory; std::shared_ptr mouse_factory; @@ -324,12 +335,14 @@ struct InputSubsystem::Impl { std::shared_ptr touch_screen_factory; std::shared_ptr udp_client_input_factory; std::shared_ptr tas_input_factory; + std::shared_ptr camera_input_factory; std::shared_ptr keyboard_output_factory; std::shared_ptr mouse_output_factory; std::shared_ptr gcadapter_output_factory; std::shared_ptr udp_client_output_factory; std::shared_ptr tas_output_factory; + std::shared_ptr camera_output_factory; #ifdef HAVE_SDL2 std::shared_ptr sdl; @@ -382,6 +395,14 @@ const TasInput::Tas* InputSubsystem::GetTas() const { return impl->tas_input.get(); } +Camera* InputSubsystem::GetCamera() { + return impl->camera.get(); +} + +const Camera* InputSubsystem::GetCamera() const { + return impl->camera.get(); +} + std::vector InputSubsystem::GetInputDevices() const { return impl->GetInputDevices(); } diff --git a/src/input_common/main.h b/src/input_common/main.h index 147c310c4..b756bb5c6 100755 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -30,6 +30,7 @@ enum Values : int; } namespace InputCommon { +class Camera; class Keyboard; class Mouse; class TouchScreen; @@ -92,9 +93,15 @@ public: /// Retrieves the underlying tas input device. [[nodiscard]] TasInput::Tas* GetTas(); - /// Retrieves the underlying tas input device. + /// Retrieves the underlying tas input device. [[nodiscard]] const TasInput::Tas* GetTas() const; + /// Retrieves the underlying camera input device. + [[nodiscard]] Camera* GetCamera(); + + /// Retrieves the underlying camera input device. + [[nodiscard]] const Camera* GetCamera() const; + /** * Returns all available input devices that this Factory can create a new device with. * Each returned ParamPackage should have a `display` field used for display, a `engine` field diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 534e55355..57e0e7025 100755 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -41,6 +41,9 @@ add_executable(yuzu configuration/configure_audio.cpp configuration/configure_audio.h configuration/configure_audio.ui + configuration/configure_camera.cpp + configuration/configure_camera.h + configuration/configure_camera.ui configuration/configure_cpu.cpp configuration/configure_cpu.h configuration/configure_cpu.ui @@ -254,7 +257,7 @@ endif() create_target_directory_groups(yuzu) target_link_libraries(yuzu PRIVATE common core input_common video_core) -target_link_libraries(yuzu PRIVATE Boost::boost glad Qt::Widgets) +target_link_libraries(yuzu PRIVATE Boost::boost glad Qt::Widgets Qt::Multimedia) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include) diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 01acda22b..774085809 100755 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -5,6 +5,8 @@ #include #include +#include +#include #include #include #include @@ -31,6 +33,7 @@ #include "core/core.h" #include "core/cpu_manager.h" #include "core/frontend/framebuffer_layout.h" +#include "input_common/drivers/camera.h" #include "input_common/drivers/keyboard.h" #include "input_common/drivers/mouse.h" #include "input_common/drivers/tas_input.h" @@ -801,6 +804,74 @@ void GRenderWindow::TouchEndEvent() { input_subsystem->GetTouchScreen()->ReleaseAllTouch(); } +void GRenderWindow::InitializeCamera() { + if (!Settings::values.enable_ir_sensor) { + return; + } + + bool camera_found = false; + const QList cameras = QCameraInfo::availableCameras(); + for (const QCameraInfo& cameraInfo : cameras) { + if (Settings::values.ir_sensor_device.GetValue() == cameraInfo.deviceName().toStdString() || + Settings::values.ir_sensor_device.GetValue() == "Auto") { + camera = std::make_unique(cameraInfo); + camera_found = true; + break; + } + } + + if (!camera_found) { + return; + } + + camera_capture = std::make_unique(camera.get()); + connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this, + &GRenderWindow::OnCameraCapture); + camera->unload(); + camera->setCaptureMode(QCamera::CaptureViewfinder); + camera->load(); + + camera_timer = std::make_unique(); + connect(camera_timer.get(), &QTimer::timeout, [this] { RequestCameraCapture(); }); + // This timer should be dependent of camera resolution 5ms for every 100 pixels + camera_timer->start(100); +} + +void GRenderWindow::FinalizeCamera() { + if (camera_timer) { + camera_timer->stop(); + } + if (camera) { + camera->unload(); + } +} + +void GRenderWindow::RequestCameraCapture() { + if (!Settings::values.enable_ir_sensor) { + return; + } + + // Idealy one should only call capture but Qt refuses to take a second capture without + // stopping the camera + camera->stop(); + camera->start(); + + camera_capture->capture(); +} + +void GRenderWindow::OnCameraCapture(int requestId, const QImage& img) { + constexpr std::size_t camera_width = 320; + constexpr std::size_t camera_height = 240; + const auto converted = + img.scaled(camera_width, camera_height, Qt::AspectRatioMode::IgnoreAspectRatio, + Qt::TransformationMode::SmoothTransformation) + .mirrored(false, true); + std::vector camera_data{}; + camera_data.resize(camera_width * camera_height); + std::memcpy(camera_data.data(), converted.bits(), camera_width * camera_height * sizeof(u32)); + input_subsystem->GetCamera()->SetCameraData(camera_width, camera_height, camera_data); +} + bool GRenderWindow::event(QEvent* event) { if (event->type() == QEvent::TouchBegin) { TouchBeginEvent(static_cast(event)); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 81fe52c0e..346201768 100755 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -20,6 +20,8 @@ class GRenderWindow; class GMainWindow; +class QCamera; +class QCameraImageCapture; class QKeyEvent; namespace Core { @@ -164,6 +166,9 @@ public: void mouseReleaseEvent(QMouseEvent* event) override; void wheelEvent(QWheelEvent* event) override; + void InitializeCamera(); + void FinalizeCamera(); + bool event(QEvent* event) override; void focusOutEvent(QFocusEvent* event) override; @@ -207,6 +212,9 @@ private: void TouchUpdateEvent(const QTouchEvent* event); void TouchEndEvent(); + void RequestCameraCapture(); + void OnCameraCapture(int requestId, const QImage& img); + void OnMinimalClientAreaChangeRequest(std::pair minimal_size) override; bool InitializeOpenGL(); @@ -232,6 +240,10 @@ private: bool first_frame = false; InputCommon::TasInput::TasState last_tas_state; + std::unique_ptr camera; + std::unique_ptr camera_capture; + std::unique_ptr camera_timer; + Core::System& system; protected: diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index b6c4de20c..c841843f0 100755 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -143,8 +143,8 @@ void Config::ReadBasicSetting(Settings::Setting& setting) { } } -template -void Config::ReadBasicSetting(Settings::Setting& setting) { +template +void Config::ReadBasicSetting(Settings::Setting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const Type default_value = setting.GetDefault(); if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) { @@ -164,16 +164,16 @@ void Config::WriteBasicSetting(const Settings::Setting& setting) { qt_config->setValue(name, QString::fromStdString(value)); } -template -void Config::WriteBasicSetting(const Settings::Setting& setting) { +template +void Config::WriteBasicSetting(const Settings::Setting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const Type value = setting.GetValue(); qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); qt_config->setValue(name, value); } -template -void Config::WriteGlobalSetting(const Settings::SwitchableSetting& setting) { +template +void Config::WriteGlobalSetting(const Settings::SwitchableSetting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const Type& value = setting.GetValue(global); if (!global) { @@ -368,6 +368,11 @@ void Config::ReadHidbusValues() { } } +void Config::ReadIrCameraValues() { + ReadBasicSetting(Settings::values.enable_ir_sensor); + ReadBasicSetting(Settings::values.ir_sensor_device); +} + void Config::ReadAudioValues() { qt_config->beginGroup(QStringLiteral("Audio")); @@ -393,6 +398,7 @@ void Config::ReadControlValues() { ReadTouchscreenValues(); ReadMotionTouchValues(); ReadHidbusValues(); + ReadIrCameraValues(); #ifdef _WIN32 ReadBasicSetting(Settings::values.enable_raw_input); @@ -998,6 +1004,11 @@ void Config::SaveHidbusValues() { QString::fromStdString(default_param)); } +void Config::SaveIrCameraValues() { + WriteBasicSetting(Settings::values.enable_ir_sensor); + WriteBasicSetting(Settings::values.ir_sensor_device); +} + void Config::SaveValues() { if (global) { SaveControlValues(); @@ -1040,6 +1051,7 @@ void Config::SaveControlValues() { SaveTouchscreenValues(); SaveMotionTouchValues(); SaveHidbusValues(); + SaveIrCameraValues(); WriteGlobalSetting(Settings::values.use_docked_mode); WriteGlobalSetting(Settings::values.vibration_enabled); @@ -1413,8 +1425,8 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) return result; } -template -void Config::ReadGlobalSetting(Settings::SwitchableSetting& setting) { +template +void Config::ReadGlobalSetting(Settings::SwitchableSetting& setting) { QString name = QString::fromStdString(setting.GetLabel()); const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); setting.SetGlobal(use_global); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 9ca878d23..a71eabe8e 100755 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -68,6 +68,7 @@ private: void ReadTouchscreenValues(); void ReadMotionTouchValues(); void ReadHidbusValues(); + void ReadIrCameraValues(); // Read functions bases off the respective config section names. void ReadAudioValues(); @@ -96,6 +97,7 @@ private: void SaveTouchscreenValues(); void SaveMotionTouchValues(); void SaveHidbusValues(); + void SaveIrCameraValues(); // Save functions based off the respective config section names. void SaveAudioValues(); @@ -159,8 +161,8 @@ private: * * @param The setting */ - template - void ReadGlobalSetting(Settings::SwitchableSetting& setting); + template + void ReadGlobalSetting(Settings::SwitchableSetting& setting); /** * Sets a value to the qt_config using the setting's label and default value. If the config is a @@ -168,8 +170,8 @@ private: * * @param The setting */ - template - void WriteGlobalSetting(const Settings::SwitchableSetting& setting); + template + void WriteGlobalSetting(const Settings::SwitchableSetting& setting); /** * Reads a value from the qt_config using the setting's label and default value and applies the @@ -177,15 +179,15 @@ private: * * @param The setting */ - template - void ReadBasicSetting(Settings::Setting& setting); + template + void ReadBasicSetting(Settings::Setting& setting); /** Sets a value from the setting in the qt_config using the setting's label and default value. * * @param The setting */ - template - void WriteBasicSetting(const Settings::Setting& setting); + template + void WriteBasicSetting(const Settings::Setting& setting); ConfigType type; std::unique_ptr qt_config; diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h index 77802a367..56800b6ff 100755 --- a/src/yuzu/configuration/configuration_shared.h +++ b/src/yuzu/configuration/configuration_shared.h @@ -27,8 +27,9 @@ enum class CheckState { // ApplyPerGameSetting, given a Settings::Setting and a Qt UI element, properly applies a Setting void ApplyPerGameSetting(Settings::SwitchableSetting* setting, const QCheckBox* checkbox, const CheckState& tracker); -template -void ApplyPerGameSetting(Settings::SwitchableSetting* setting, const QComboBox* combobox) { +template +void ApplyPerGameSetting(Settings::SwitchableSetting* setting, + const QComboBox* combobox) { if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) { setting->SetValue(static_cast(combobox->currentIndex())); } else if (!Settings::IsConfiguringGlobal()) { @@ -45,8 +46,9 @@ void ApplyPerGameSetting(Settings::SwitchableSetting* setting, const QComb // Sets a Qt UI element given a Settings::Setting void SetPerGameSetting(QCheckBox* checkbox, const Settings::SwitchableSetting* setting); -template -void SetPerGameSetting(QComboBox* combobox, const Settings::SwitchableSetting* setting) { +template +void SetPerGameSetting(QComboBox* combobox, + const Settings::SwitchableSetting* setting) { combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX : static_cast(setting->GetValue()) + ConfigurationShared::USE_GLOBAL_OFFSET); diff --git a/src/yuzu/configuration/configure_camera.cpp b/src/yuzu/configuration/configure_camera.cpp new file mode 100755 index 000000000..8917f622a --- /dev/null +++ b/src/yuzu/configuration/configure_camera.cpp @@ -0,0 +1,126 @@ +// Text : Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include + +#include "input_common/drivers/camera.h" +#include "input_common/main.h" +#include "ui_configure_camera.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_camera.h" + +ConfigureCamera::ConfigureCamera(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_) + : QDialog(parent), input_subsystem{input_subsystem_}, + ui(std::make_unique()) { + ui->setupUi(this); + + connect(ui->restore_defaults_button, &QPushButton::clicked, this, + &ConfigureCamera::RestoreDefaults); + connect(ui->preview_button, &QPushButton::clicked, this, &ConfigureCamera::PreviewCamera); + + auto blank_image = QImage(320, 240, QImage::Format::Format_RGB32); + blank_image.fill(Qt::black); + DisplayCapturedFrame(0, blank_image); + + LoadConfiguration(); + resize(0, 0); +} + +ConfigureCamera::~ConfigureCamera() = default; + +void ConfigureCamera::PreviewCamera() { + const auto index = ui->ir_sensor_combo_box->currentIndex(); + bool camera_found = false; + const QList cameras = QCameraInfo::availableCameras(); + for (const QCameraInfo& cameraInfo : cameras) { + if (input_devices[index] == cameraInfo.deviceName().toStdString() || + input_devices[index] == "Auto") { + LOG_INFO(Frontend, "Selected Camera {} {}", cameraInfo.description().toStdString(), + cameraInfo.deviceName().toStdString()); + camera = std::make_unique(cameraInfo); + camera_found = true; + break; + } + } + + // Clear previous frame + auto blank_image = QImage(320, 240, QImage::Format::Format_RGB32); + blank_image.fill(Qt::black); + DisplayCapturedFrame(0, blank_image); + + if (!camera_found) { + return; + } + + camera_capture = std::make_unique(camera.get()); + connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this, + &ConfigureCamera::DisplayCapturedFrame); + camera->unload(); + camera->setCaptureMode(QCamera::CaptureViewfinder); + camera->load(); + + camera_timer = std::make_unique(); + connect(camera_timer.get(), &QTimer::timeout, [this] { + camera->stop(); + camera->start(); + + camera_capture->capture(); + }); + + camera_timer->start(250); +} + +void ConfigureCamera::DisplayCapturedFrame(int requestId, const QImage& img) { + LOG_INFO(Frontend, "ImageCaptured {} {}", img.width(), img.height()); + const auto converted = img.scaled(320, 240, Qt::AspectRatioMode::IgnoreAspectRatio, + Qt::TransformationMode::SmoothTransformation); + ui->preview_box->setPixmap(QPixmap::fromImage(converted)); +} + +void ConfigureCamera::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureCamera::RetranslateUI() { + ui->retranslateUi(this); +} + +void ConfigureCamera::ApplyConfiguration() { + const auto index = ui->ir_sensor_combo_box->currentIndex(); + Settings::values.ir_sensor_device.SetValue(input_devices[index]); +} + +void ConfigureCamera::LoadConfiguration() { + input_devices.clear(); + ui->ir_sensor_combo_box->clear(); + input_devices.push_back("Auto"); + ui->ir_sensor_combo_box->addItem(tr("Auto")); + const auto cameras = QCameraInfo::availableCameras(); + for (const QCameraInfo& cameraInfo : cameras) { + input_devices.push_back(cameraInfo.deviceName().toStdString()); + ui->ir_sensor_combo_box->addItem(cameraInfo.description()); + } + + const auto current_device = Settings::values.ir_sensor_device.GetValue(); + + const auto devices_it = std::find_if( + input_devices.begin(), input_devices.end(), + [current_device](const std::string& device) { return device == current_device; }); + const int device_index = + devices_it != input_devices.end() + ? static_cast(std::distance(input_devices.begin(), devices_it)) + : 0; + ui->ir_sensor_combo_box->setCurrentIndex(device_index); +} + +void ConfigureCamera::RestoreDefaults() { + ui->ir_sensor_combo_box->setCurrentIndex(0); +} diff --git a/src/yuzu/configuration/configure_camera.h b/src/yuzu/configuration/configure_camera.h new file mode 100755 index 000000000..af7551c03 --- /dev/null +++ b/src/yuzu/configuration/configure_camera.h @@ -0,0 +1,52 @@ +// Text : Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +class QTimer; +class QCamera; +class QCameraImageCapture; + +namespace InputCommon { +class InputSubsystem; +} // namespace InputCommon + +namespace Ui { +class ConfigureCamera; +} + +class ConfigureCamera : public QDialog { + Q_OBJECT + +public: + explicit ConfigureCamera(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_); + ~ConfigureCamera() override; + + void ApplyConfiguration(); + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + /// Load configuration settings. + void LoadConfiguration(); + + /// Restore all buttons to their default values. + void RestoreDefaults(); + + void DisplayCapturedFrame(int requestId, const QImage& img); + + /// Loads and signals the current selected camera to display a frame + void PreviewCamera(); + + InputCommon::InputSubsystem* input_subsystem; + + std::unique_ptr camera; + std::unique_ptr camera_capture; + std::unique_ptr camera_timer; + std::vector input_devices; + std::unique_ptr ui; +}; diff --git a/src/yuzu/configuration/configure_camera.ui b/src/yuzu/configuration/configure_camera.ui new file mode 100755 index 000000000..976a9b1ec --- /dev/null +++ b/src/yuzu/configuration/configure_camera.ui @@ -0,0 +1,170 @@ + + + ConfigureCamera + + + + 0 + 0 + 298 + 339 + + + + Configure Infrared Camera + + + + + + + 280 + 0 + + + + Select where the image of the emulated camera comes from. It may be a virtual camera or a real camera. + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Camera Image Source: + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Input device: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Preview + + + + + + + 320 + 240 + + + + Resolution: 320*240 + + + + + + + Click to preview + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Restore Defaults + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + ConfigureCamera + accept() + + + buttonBox + rejected() + ConfigureCamera + reject() + + + diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 73d7ba24b..f1b061b13 100755 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -15,6 +15,7 @@ #include "ui_configure_input.h" #include "ui_configure_input_advanced.h" #include "ui_configure_input_player.h" +#include "yuzu/configuration/configure_camera.h" #include "yuzu/configuration/configure_debug_controller.h" #include "yuzu/configuration/configure_input.h" #include "yuzu/configuration/configure_input_advanced.h" @@ -163,6 +164,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, [this, input_subsystem, &hid_core] { CallConfigureDialog(*this, input_subsystem, hid_core); }); + connect(advanced, &ConfigureInputAdvanced::CallCameraDialog, + [this, input_subsystem, &hid_core] { + CallConfigureDialog(*this, input_subsystem); + }); connect(ui->vibrationButton, &QPushButton::clicked, [this, &hid_core] { CallConfigureDialog(*this, hid_core); }); diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index f14bdc831..10f841b98 100755 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -89,6 +89,7 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent) [this] { CallMotionTouchConfigDialog(); }); connect(ui->ring_controller_configure, &QPushButton::clicked, this, [this] { CallRingControllerDialog(); }); + connect(ui->camera_configure, &QPushButton::clicked, this, [this] { CallCameraDialog(); }); #ifndef _WIN32 ui->enable_raw_input->setVisible(false); @@ -136,6 +137,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() { Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked(); Settings::values.controller_navigation = ui->controller_navigation->isChecked(); Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked(); + Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked(); } void ConfigureInputAdvanced::LoadConfiguration() { @@ -169,6 +171,7 @@ void ConfigureInputAdvanced::LoadConfiguration() { ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue()); ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue()); ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue()); + ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue()); UpdateUIEnabled(); } diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h index 644e56dd8..fc1230284 100755 --- a/src/yuzu/configuration/configure_input_advanced.h +++ b/src/yuzu/configuration/configure_input_advanced.h @@ -29,6 +29,7 @@ signals: void CallTouchscreenConfigDialog(); void CallMotionTouchConfigDialog(); void CallRingControllerDialog(); + void CallCameraDialog(); private: void changeEvent(QEvent* event) override; diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index 14403cb10..fac8cf827 100755 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -2617,6 +2617,20 @@ + + + + Infrared Camera + + + + + + + Configure + + + diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 9ee090f45..8da1d44ad 100755 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1540,6 +1540,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t mouse_hide_timer.start(); } + render_window->InitializeCamera(); + std::string title_name; std::string title_version; const auto res = system->GetGameName(title_name); @@ -1621,6 +1623,7 @@ void GMainWindow::ShutdownGame() { tas_label->clear(); input_subsystem->GetTas()->Stop(); OnTasStateChanged(); + render_window->FinalizeCamera(); // Enable all controllers system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All}); @@ -2860,6 +2863,12 @@ void GMainWindow::OnConfigure() { mouse_hide_timer.start(); } + // Restart camera config + if (emulation_running) { + render_window->FinalizeCamera(); + render_window->InitializeCamera(); + } + if (!UISettings::values.has_broken_vulkan) { renderer_status_button->setEnabled(!emulation_running); } diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 9a93896ad..ad7f9d239 100755 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -99,8 +99,8 @@ void Config::ReadSetting(const std::string& group, Settings::Setting& sett setting = sdl2_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault()); } -template -void Config::ReadSetting(const std::string& group, Settings::Setting& setting) { +template +void Config::ReadSetting(const std::string& group, Settings::Setting& setting) { setting = static_cast(sdl2_config->GetInteger(group, setting.GetLabel(), static_cast(setting.GetDefault()))); } diff --git a/src/yuzu_cmd/config.h b/src/yuzu_cmd/config.h index ccf77d668..32c03075f 100755 --- a/src/yuzu_cmd/config.h +++ b/src/yuzu_cmd/config.h @@ -33,6 +33,6 @@ private: * @param group The name of the INI group * @param setting The yuzu setting to modify */ - template - void ReadSetting(const std::string& group, Settings::Setting& setting); + template + void ReadSetting(const std::string& group, Settings::Setting& setting); };