diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..b37b5f28b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# Data science would like to know about all schema changes Engineering plan to make to SQL databases. This automates the process and is intended to be used by Data Science as a FYI of an incoming change as opposed to a review. +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners +* @vald-green/code-owners-harald +*.sql @vald-green/code-owners-harald @vald-green/code-owners-data-science diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 90bc80691..5591cbf3a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,6 +4,8 @@ --> ## Fixes # +Related DevOps Ticket: AB# + ### Description of the changes: - - diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e49b00e4..6117172b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,22 @@ ## Change Log +### v1.4.1 + +* Added NEON for ARM64 +* Failed conversion of MJPEG to BGRA is now a warning - not an error. + ### v1.4.0 -### v1.3.0 +* Added ARM64 Suport. +* On Windows Opencv-4.1.1 is now being used and tested. +* CPP; Adding record.hpp, updated playback.hpp +* Fixed small error in transformation functions +* Updated K4aRecorder allow: + * Setting manual exposure based on exposure time. + * Record BGRA32 format. +* Added transformation API's to CSharp + +### v1.3.0 * On Windows VS dependencies are now dynamically linked and require redistributables for [VS2015](https://www.microsoft.com/en-us/download/details.aspx?id=48145) or newer. diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d06adf70..4f002b5d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,12 +128,15 @@ endif() # executable for shared objects. This is done on Linux to emulate the default # behavior of the Windows loader, which searches for DLLs in the path of the # executable. +# +# We only set RPATH for build since our libs and executables are put in the +# same folder. if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") - set(CMAKE_BUILD_WITH_INSTALL_RPATH YES) - set(CMAKE_INSTALL_RPATH "\$ORIGIN") + set(CMAKE_BUILD_RPATH "\$ORIGIN") endif() -set(TARGET_ARCH ${CMAKE_SYSTEM_PROCESSOR}) +include(DetermineTargetArch) +determine_target_arch(TARGET_ARCH) # CMake doesn't set the target processor correctly for MSVC if ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") @@ -147,16 +150,14 @@ if ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") # Check what architecture we are building for. This assumes all 64-bit architectures are amd64, which will break # if we decide to support arm. - if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") - set(TARGET_ARCH "amd64") + if ("${TARGET_ARCH}" STREQUAL "x86_64") configure_file(k4a.props.in ${CMAKE_CURRENT_SOURCE_DIR}/src/csharp/k4a.x64.props) configure_file(StubGenerator.xml.in ${CMAKE_CURRENT_SOURCE_DIR}/src/csharp/StubGenerator.x64.xml) - elseif("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") - set(TARGET_ARCH "x86") + elseif("${TARGET_ARCH}" STREQUAL "i686") configure_file(k4a.props.in ${CMAKE_CURRENT_SOURCE_DIR}/src/csharp/k4a.x86.props) configure_file(StubGenerator.xml.in ${CMAKE_CURRENT_SOURCE_DIR}/src/csharp/StubGenerator.x86.xml) else() - message(FATAL_ERROR "Unknown architecture with size of void* = ${CMAKE_SIZEOF_VOID_P}") + message(FATAL_ERROR "Unknown architecture for MSVC: ${TARGET_ARCH}") endif() endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 85de5ad77..9c3b9e0a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,12 +24,69 @@ When opening a new issue be sure to document: ### Requesting new features -Please review the list of open Issues to see if one is already open. Please review all categories, Bugs and -Enhancements. Also check for Closed Issues before opening a new one. +Please review the list of open Issues to see if one is already open. Please review all categories, Bugs and enhancements. Also check for Closed Issues before opening a new one. +If you have a small enhancement that is well defined, please create a new feature request on GitHub. + +If you have a larger idea for a new feature please share it with us on [Microsoft Azure Feedback Forums](https://feedback.azure.com/forums/920053) where the rest of the community can up vote it too. We will review the submitted ideas very frequently, usually twice weekly. + +Below is our list of possible status states we may assign to the request: + +| Microsoft Azure Feedback Forums status| Process steps definition +|---------------------------------------|---------------------------------------| +| No status | New issue we have not looked at yet | +| Need Feedback | Idea doesn't have enough details or needs more community support | +| Under Review | Proposal for the new feature is created and in review | +| Planned | Approved to get the work started | +| Started | Work started | +| Completed | Feature is released | +| Declined | We have decided not to make this change | +| Moved | Issue which has been moved to GitHub | +| Archived | Feature not possible to implement on current HW. | +| Triaged | Request has been seen and is under internal discussion | + +#### No status + +This is a new request that we have not yet seen. Within a week we should have reviewed the request and assigned it an initial state. + +#### Needs Feedback + +The Azure Kinect team uses this state to ask for more information about this issue. We may need more information from the issue filer as we don't completely understand the request. We might also use this status because we are waiting for more community feedback on the proposal; either in the form of spec feedback or up voting. + +#### Under Review + +The team is actively reviewing the proposal and determine what the next actions should be. We may ask for more information, iterate on the proposal, or move to planned state while we wait for resources. We will also need to carefully consider not only the work to implement the request but the work needed to be invested our build and test infrastructure to ensure quality of the code remains high. + +#### Planned + +The requested issue has been planned but not yet started. It may stay in this state indefinitely if we don't have resources to complete the request. + +#### Started + +Work on the feature, new tests, and possible infrastructure changes have begun. + +#### Completed + +The requested issue has been checked into develop branch and we are done. The feature however, may not yet be part of a release. + +#### Declined + +We have consider the request and have decided not to implement it due to various reasons. For example, the idea cannot be implemented with the current hardware. + +#### Moved + +Issue has been moved to GitHub for tracking. + +#### Archived + +We will archive the idea if the current hardware can't support the request. + +#### Triaged + +We use the this status to indicate that request has been seen and is under internal discussion. ## Finding issues you can help with -Looking for something to work on? Issues marked [``Good First Issue``](https://github.com/Microsoft/Azure-Kinect-Sensor-SDK/labels/good%20first%20issue) +Looking for something to work on? Issues marked [``Good First Issue``](https://github.com/Microsoft/Azure-Kinect-Sensor-SDK/labels/good%20first%20issue) are a good place to start. You can also check the [``Help Wanted``](https://github.com/Microsoft/Azure-Kinect-Sensor-SDK/labels/help%20wanted) tag to diff --git a/README.md b/README.md index 58cb8a780..b2687fb56 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # Azure Kinect SDK (K4A) -Welcome to the Azure Kinect DK Sensor SDK! While devices and the Body Tracking SDK will be available this -summer, we wanted to share the Sensor SDK now. We hope you can use this interim period to get familiar with -our SDK, ask questions, and provide feedback. See [Azure.com/Kinect](https://Azure.com/kinect) for device +Welcome to the Azure Kinect Sensor SDK! We hope you can use it to build many great applications and participate in the project. Don't be shy to ask questions, and provide feedback. See [Azure.com/Kinect](https://Azure.com/kinect) for device info and available documentation. [![Build Status @@ -83,6 +81,8 @@ email to ensure we received your original message. Further information, includin [MSRC PGP](https://technet.microsoft.com/en-us/security/dn606155) key, can be found in the [Security TechCenter](https://technet.microsoft.com/en-us/security/default). -## License +## License and Microsoft Support for Azure Kinect Sensor SDK -[MIT License](LICENSE) \ No newline at end of file +[MIT License](LICENSE) + +[Microsoft Support for Azure Kinect Sensor SDK](microsoft-support.md) \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5dcbe07eb..d750b442c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,17 +3,27 @@ name: K4A-SDK-$(SourceBranchName)-$(Date:yyyyMMdd)-$(Rev:rrr) +schedules: +- cron: "*/30 08-13 * * *" + displayName: 'Daily Builds starting at 8:00AM UTC (12:00AM PST) every 30 min, last build 13:30AM (5:30AM PST)' + branches: + include: + - master + - develop + always: true + + variables: - name: 'skipComponentGovernaceDetection' value: true - name: 'linux_firmware_version' - value: '1.6.102075014' + value: '1.6.110079014' - name: 'windows_firmware_version' - value: '1.6.102075014' + value: '1.6.110079014' - name: 'NuGetPackageVersion' - value: '1.3.0' + value: '1.4.1-alpha.0' - name: 'OpenCVPath' - value: 'C:\OpenCV\Build\x64\vc14\' + value: 'C:\OpenCV\Build\x64\vc15\' trigger: batch: false @@ -369,9 +379,6 @@ jobs: - job: LinuxK4ABuildTest displayName: Linux - pool: - vmImage: 'ubuntu-16.04' - container: mcr.microsoft.com/akbuilder-linux:v3 strategy: maxParallel: 100 matrix: @@ -379,18 +386,40 @@ jobs: CMakeLinuxTargetTriple: 'x86_64-linux-clang' CMakeConfiguration: 'debug' UsesOpenCV: 'TRUE' + container: 'mcr.microsoft.com/akbuilder-linux:v5-amd64' x64-gnu_debug_ninja: CMakeLinuxTargetTriple: 'x86_64-linux-gnu' CMakeConfiguration: 'debug' UsesOpenCV: 'TRUE' + container: 'mcr.microsoft.com/akbuilder-linux:v5-amd64' x64-clang_rel_ninja: CMakeLinuxTargetTriple: 'x86_64-linux-clang' CMakeConfiguration: 'relwithdebinfo' UsesOpenCV: 'TRUE' + container: 'mcr.microsoft.com/akbuilder-linux:v5-amd64' x64-gnu_rel_ninja: CMakeLinuxTargetTriple: 'x86_64-linux-gnu' CMakeConfiguration: 'relwithdebinfo' UsesOpenCV: 'TRUE' + container: 'mcr.microsoft.com/akbuilder-linux:v5-amd64' + # arm64-clang_debug_ninja: + # CMakeLinuxTargetTriple: 'arm64-linux-clang' + # CMakeConfiguration: 'debug' + # UsesOpenCV: 'FALSE' + arm64-gnu_debug_ninja: + CMakeLinuxTargetTriple: 'arm64-linux-gnu' + CMakeConfiguration: 'debug' + UsesOpenCV: 'FALSE' + container: 'mcr.microsoft.com/akbuilder-linux:v5-arm64' + # arm64-clang_rel_ninja: + # CMakeLinuxTargetTriple: 'arm64-linux-clang' + # CMakeConfiguration: 'relwithdebinfo' + # UsesOpenCV: 'FALSE' + arm64-gnu_rel_ninja: + CMakeLinuxTargetTriple: 'arm64-linux-gnu' + CMakeConfiguration: 'relwithdebinfo' + UsesOpenCV: 'FALSE' + container: 'mcr.microsoft.com/akbuilder-linux:v5-arm64' # 32-bit builds are currently broken # i386-unknown-linux-clang_debug_ninja: @@ -405,6 +434,9 @@ jobs: # i386-unknown-linux-gnu_relwithdebinfo_ninja: # CMakeLinuxTargetTriple: 'i386-linux-gnu' # CMakeConfiguration: 'relwithdebinfo' + pool: + vmImage: 'ubuntu-18.04' + container: $[ variables['container'] ] steps: - checkout: self @@ -470,7 +502,7 @@ jobs: ArtifactName: '$(CMakeLinuxTargetTriple)-$(CMakeConfiguration)' parallel: true parallelCount: 8 - condition: and(succeeded(), contains(variables['CMakeLinuxTargetTriple'], 'clang')) + condition: and(succeeded(), contains(variables['CMakeLinuxTargetTriple'], 'gnu')) - job: DocumentationBuild displayName: Documentation Build @@ -657,7 +689,7 @@ jobs: inputs: sourceFolder: "$(System.ArtifactsDirectory)/depthengineplugin/windows/amd64/release/" contents: "depthengine*.dll" - targetFolder: "$(System.ArtifactsDirectory)/amd64-windows-msvc-RelWithDebInfo/bin" + targetFolder: "$(System.ArtifactsDirectory)/amd64-windows-msvc-relwithdebinfo/bin" flattenFolders: true - task: CopyFiles@2 @@ -665,33 +697,33 @@ jobs: inputs: sourceFolder: "$(System.ArtifactsDirectory)/depthengineplugin/windows/amd64/release/" contents: "depthengine*.dll" - targetFolder: "$(System.ArtifactsDirectory)/amd64-windows-msvc-RelWithDebInfo/bin/Release/Microsoft.AzureKinect.FunctionalTests/netcoreapp2.1/" + targetFolder: "$(System.ArtifactsDirectory)/amd64-windows-msvc-relwithdebinfo/bin/Release/Microsoft.AzureKinect.FunctionalTests/netcoreapp2.1/" flattenFolders: true - - script: '.\amd64-windows-msvc-RelWithDebInfo\bin\AzureKinectFirmwareTool.exe -r' + - script: '.\amd64-windows-msvc-relwithdebinfo\bin\AzureKinectFirmwareTool.exe -r' workingDirectory: '$(System.ArtifactsDirectory)' displayName: 'Reset K4A Device' - - script: '.\amd64-windows-msvc-RelWithDebInfo\bin\AzureKinectFirmwareTool.exe -u firmware/AzureKinectDK_Fw_$(firmware_version).bin' + - script: '.\amd64-windows-msvc-relwithdebinfo\bin\AzureKinectFirmwareTool.exe -u firmware/AzureKinectDK_Fw_$(firmware_version).bin' workingDirectory: '$(System.ArtifactsDirectory)' displayName: 'Update Device' - - script: '.\amd64-windows-msvc-RelWithDebInfo\bin\enumerate_devices.exe' + - script: '.\amd64-windows-msvc-relwithdebinfo\bin\enumerate_devices.exe' workingDirectory: '$(System.ArtifactsDirectory)' displayName: 'Check Device Health' - - script: 'python $(Build.SourcesDirectory)/scripts/RunTestList.py --list bin/functional_test_list.txt --bin bin/ --output=xml --gtestargs "--gtest_filter=-*ONBOARDING*"' - workingDirectory: '$(System.ArtifactsDirectory)/amd64-windows-msvc-RelWithDebInfo' + - script: 'python $(Build.SourcesDirectory)/scripts/RunTestList.py --list bin/functional_test_list.txt --bin bin/ --output=xml --gtest_filter=-*ONBOARDING*' + workingDirectory: '$(System.ArtifactsDirectory)/amd64-windows-msvc-relwithdebinfo' displayName: 'Run Functional Tests' timeoutInMinutes: 15 - - script: 'python $(Build.SourcesDirectory)/scripts/RunTestList.py --list bin/functional_custom_test_list.txt --bin bin/ --output=xml --gtestargs "--gtest_filter=-*ONBOARDING*"' - workingDirectory: '$(System.ArtifactsDirectory)/amd64-windows-msvc-RelWithDebInfo' + - script: 'python $(Build.SourcesDirectory)/scripts/RunTestList.py --list bin/functional_custom_test_list.txt --bin bin/ --output=xml --gtest_filter=-*ONBOARDING*' + workingDirectory: '$(System.ArtifactsDirectory)/amd64-windows-msvc-relwithdebinfo' displayName: 'Run Custom Functional Tests' timeoutInMinutes: 15 - - script: 'python $(Build.SourcesDirectory)/scripts/RunTestList.py --list bin/functional_test_list.txt --bin bin/ --output=xml --gtestargs "--gtest_filter=*ONBOARDING*"' - workingDirectory: '$(System.ArtifactsDirectory)/amd64-windows-msvc-RelWithDebInfo' + - script: 'python $(Build.SourcesDirectory)/scripts/RunTestList.py --list bin/functional_test_list.txt --bin bin/ --output=xml --gtest_filter=*ONBOARDING*' + workingDirectory: '$(System.ArtifactsDirectory)/amd64-windows-msvc-relwithdebinfo' displayName: 'Run Functional Tests - Onboarding' timeoutInMinutes: 15 continueOnError: true @@ -714,10 +746,19 @@ jobs: dependsOn: LinuxK4ABuildTest variables: firmware_version: "$(linux_firmware_version)" + strategy: + maxParallel: 100 + matrix: + x86_64: + CMakeLinuxTargetTriple: 'x86_64-linux-gnu' + EdenArch: 'x64' + arm64: + CMakeLinuxTargetTriple: 'arm64-linux-gnu' + EdenArch: 'arm64' pool: name: Analog-FwConnected demands: - - Eden + - EdenArch -equals $(EdenArch) - Agent.OS -equals Linux steps: @@ -730,7 +771,7 @@ jobs: - task: DownloadBuildArtifacts@0 displayName: 'Download Build Artifacts' inputs: - artifactName: 'x86_64-linux-clang-RelWithDebInfo' + artifactName: '$(CMakeLinuxTargetTriple)-relwithdebinfo' parallelizationLimit: 8 - task: NuGetToolInstaller@0 @@ -738,9 +779,8 @@ jobs: versionSpec: '>=5.1.0' # The account on the NUC does not currently give this user permission to install, so we have done so manually. - # This captures the dependency NuGet has in this environment. We locally ran 'apt-get install mono-complete' - # We also need opencv, so install that too - #- script: 'apt-get install mono libopencv-dev' + # Mono has a bug, so we need a newer version. Version 6.8 is working well for both AMD64 and ARM64. + #- script: 'apt-get install mono-complete python git-lfs openssh-server ufw libopencv-dev' # workingDirectory: '$(System.ArtifactsDirectory)' # displayName: "Ensure Mono is installed" @@ -755,19 +795,19 @@ jobs: displayName: "Temp List files" - task: CopyFiles@2 - displayName: "Copy DepthEngine into amd64/debug artifacts folder" + displayName: "Copy DepthEngine into $(EdenArch)/debug artifacts folder" inputs: - sourceFolder: "$(System.ArtifactsDirectory)/NugetOutputDir/Microsoft.Azure.Kinect.Sensor.$(NuGetPackageVersion)/linux/lib/native/x64/release/" + sourceFolder: "$(System.ArtifactsDirectory)/NugetOutputDir/Microsoft.Azure.Kinect.Sensor.$(NuGetPackageVersion)/linux/lib/native/$(EdenArch)/release/" contents: "libdepthengine*" - targetFolder: "$(System.ArtifactsDirectory)/depthengineplugin/linux/x86_64/debug" + targetFolder: "$(System.ArtifactsDirectory)/depthengineplugin/linux/$(EdenArch)/debug" flattenFolders: true - task: CopyFiles@2 - displayName: "Copy DepthEngine into amd64/release artifacts folder" + displayName: "Copy DepthEngine into $(EdenArch)/release artifacts folder" inputs: - sourceFolder: "$(System.ArtifactsDirectory)/NugetOutputDir/Microsoft.Azure.Kinect.Sensor.$(NuGetPackageVersion)/linux/lib/native/x64/release/" + sourceFolder: "$(System.ArtifactsDirectory)/NugetOutputDir/Microsoft.Azure.Kinect.Sensor.$(NuGetPackageVersion)/linux/lib/native/$(EdenArch)/release/" contents: "libdepthengine*" - targetFolder: "$(System.ArtifactsDirectory)/depthengineplugin/linux/x86_64/release" + targetFolder: "$(System.ArtifactsDirectory)/depthengineplugin/linux/$(EdenArch)/release" flattenFolders: true - task: CopyFiles@2 @@ -788,52 +828,65 @@ jobs: - task: CopyFiles@2 displayName: "Copy DepthEnginePlugin into Build Artifacts" inputs: - sourceFolder: "$(System.ArtifactsDirectory)/depthengineplugin/linux/x86_64/release/" + sourceFolder: "$(System.ArtifactsDirectory)/depthengineplugin/linux/$(EdenArch)/release/" contents: "libdepthengine.so*" - targetFolder: "$(System.ArtifactsDirectory)/x86_64-linux-clang-relwithdebinfo/bin/" + targetFolder: "$(System.ArtifactsDirectory)/$(CMakeLinuxTargetTriple)-relwithdebinfo/bin/" flattenFolders: true - - script: 'chmod +x ./x86_64-linux-clang-relwithdebinfo/bin/*' + - script: 'chmod +x ./$(CMakeLinuxTargetTriple)-relwithdebinfo/bin/*' workingDirectory: '$(System.ArtifactsDirectory)' displayName: 'Add execution property to binary files' - # Set the DISPLAY variable since DepthEngine needs to open a display window (even there is no visual display). - - script: 'echo "##vso[task.setvariable variable=DISPLAY]:0"' - workingDirectory: '$(System.ArtifactsDirectory)' - displayName: 'set DISPLAY variable' - - - script: 'env && which xauth && glxinfo | grep "OpenGL"' + # Set and verify the DISPLAY variable since DepthEngine needs to open a display window (even there is no visual display). + - script: | + echo + echo Checking OpenGL version on DISPLAY=:0 + echo "##vso[task.setvariable variable=DISPLAY]:0" + export DISPLAY=:0 + env | grep DISPLAY + which xauth + glxinfo | grep "OpenGL" + if [ $? -ne 0 ] ; then + echo + echo Checking OpenGL version on DISPLAY=:1 + echo "##vso[task.setvariable variable=DISPLAY]:1" + export DISPLAY=:1 + env | grep DISPLAY + which xauth + glxinfo | grep "OpenGL" + fi workingDirectory: '$(System.ArtifactsDirectory)' displayName: 'Check openGL version' + timeoutInMinutes: 5 - - script: './x86_64-linux-clang-relwithdebinfo/bin/AzureKinectFirmwareTool -r' + - script: './$(CMakeLinuxTargetTriple)-relwithdebinfo/bin/AzureKinectFirmwareTool -r' workingDirectory: '$(System.ArtifactsDirectory)' displayName: 'Reset K4A Device' env: K4A_LOG_LEVEL: 'I' - - script: './x86_64-linux-clang-relwithdebinfo/bin/AzureKinectFirmwareTool -u firmware/AzureKinectDK_Fw_$(firmware_version).bin' + - script: './$(CMakeLinuxTargetTriple)-relwithdebinfo/bin/AzureKinectFirmwareTool -u firmware/AzureKinectDK_Fw_$(firmware_version).bin' workingDirectory: '$(System.ArtifactsDirectory)' displayName: 'Update Device' env: K4A_LOG_LEVEL: 'I' - - script: './x86_64-linux-clang-relwithdebinfo/bin/enumerate_devices' + - script: './$(CMakeLinuxTargetTriple)-relwithdebinfo/bin/enumerate_devices' workingDirectory: '$(System.ArtifactsDirectory)' displayName: 'Check Device Health' - - script: 'python $(Build.SourcesDirectory)/scripts/RunTestList.py --list bin/functional_test_list.txt --bin bin/ --output=xml --gtestargs "--gtest_filter=-*ONBOARDING*"' - workingDirectory: '$(System.ArtifactsDirectory)/x86_64-linux-clang-relwithdebinfo' + - script: 'python $(Build.SourcesDirectory)/scripts/RunTestList.py --list bin/functional_test_list.txt --bin bin/ --output=xml --gtest_filter=-*ONBOARDING*' + workingDirectory: '$(System.ArtifactsDirectory)/$(CMakeLinuxTargetTriple)-relwithdebinfo' displayName: 'Run Functional Tests' timeoutInMinutes: 15 - - script: 'python $(Build.SourcesDirectory)/scripts/RunTestList.py --list bin/functional_custom_test_list.txt --bin bin/ --output=xml --gtestargs "--gtest_filter=-*ONBOARDING*"' - workingDirectory: '$(System.ArtifactsDirectory)/x86_64-linux-clang-relwithdebinfo' + - script: 'python $(Build.SourcesDirectory)/scripts/RunTestList.py --list bin/functional_custom_test_list.txt --bin bin/ --output=xml --gtest_filter=-*ONBOARDING*' + workingDirectory: '$(System.ArtifactsDirectory)/$(CMakeLinuxTargetTriple)-relwithdebinfo' displayName: 'Run Custom Functional Tests' timeoutInMinutes: 15 - - script: 'python $(Build.SourcesDirectory)/scripts/RunTestList.py --list bin/functional_test_list.txt --bin bin/ --output=xml --gtestargs "--gtest_filter=*ONBOARDING*"' - workingDirectory: '$(System.ArtifactsDirectory)/x86_64-linux-clang-relwithdebinfo' + - script: 'python $(Build.SourcesDirectory)/scripts/RunTestList.py --list bin/functional_test_list.txt --bin bin/ --output=xml --gtest_filter=*ONBOARDING*' + workingDirectory: '$(System.ArtifactsDirectory)/$(CMakeLinuxTargetTriple)-relwithdebinfo' displayName: 'Run Functional Tests - Onboarding' timeoutInMinutes: 15 continueOnError: true diff --git a/cmake/DetermineTargetArch.cmake b/cmake/DetermineTargetArch.cmake new file mode 100644 index 000000000..7eaf56c3d --- /dev/null +++ b/cmake/DetermineTargetArch.cmake @@ -0,0 +1,53 @@ +function(determine_target_arch OUTPUT_VARIABLE) + if("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") + if("${MSVC_C_ARCHITECTURE_ID}" STREQUAL "X86") + set(ARCH "i686") + elseif("${MSVC_C_ARCHITECTURE_ID}" STREQUAL "x64") + set(ARCH "x86_64") + elseif("${MSVC_C_ARCHITECTURE_ID}" STREQUAL "ARM") + set(ARCH "arm") + elseif("${MSVC_C_ARCHITECTURE_ID}" STREQUAL "ARM64") + set(ARCH "arm64") + else() + message(FATAL_ERROR "Unrecognized architecture ${MSVC_C_ARCHITECTURE_ID} from ${CMAKE_C_COMPILER}") + endif() + elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + if("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + set(CMD_ARGS "-target" "${CMAKE_C_COMPILER_TARGET}" "-dumpmachine") + else() + set(CMD_ARGS "-dumpmachine") + endif() + execute_process( + COMMAND + ${CMAKE_C_COMPILER} ${CMD_ARGS} + RESULT_VARIABLE + RESULT + OUTPUT_VARIABLE + ARCH + ERROR_QUIET) + if(RESULT) + message(FATAL_ERROR "Failed to determine target architecture triplet: ${RESULT}") + endif() + string(REGEX MATCH "([^-]+).*" ARCH_MATCH ${ARCH}) + if(NOT CMAKE_MATCH_1 OR NOT ARCH_MATCH) + message(FATAL_ERROR "Failed to match the target architecture triplet: ${ARCH}") + endif() + set(ARCH ${CMAKE_MATCH_1}) + + if("${ARCH}" STREQUAL "x86_64") + # Do nothing + elseif("${ARCH}" STREQUAL "aarch64") + set(ARCH "arm64") + elseif("${ARCH}" STREQUAL "i686") + # Do nothing + elseif("${ARCH}" STREQUAL "i386") + # Do nothing + else() + message(FATAL_ERROR "Unrecognized architecture ${ARCH} from ${CMAKE_C_COMPILER}") + endif() + else() + message(FATAL_ERROR "Unrecognized Compiler ${CMAKE_C_COMPILER_ID}") + endif() + message(STATUS "Target architecture - ${ARCH}") + set(${OUTPUT_VARIABLE} ${ARCH} PARENT_SCOPE) +endfunction() diff --git a/cmake/toolchains/arm64-linux-clang.cmake b/cmake/toolchains/arm64-linux-clang.cmake new file mode 100644 index 000000000..6a9f2b463 --- /dev/null +++ b/cmake/toolchains/arm64-linux-clang.cmake @@ -0,0 +1,22 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# the name of the target OS and arch +SET(CMAKE_SYSTEM_NAME Linux) +SET(CMAKE_SYSTEM_PROCESSOR arm64) +SET(triple aarch64-linux-gnu) + +# which compilers to use +SET(CMAKE_C_COMPILER "clang-6.0") +SET(CMAKE_C_COMPILER_TARGET ${triple}) +SET(CMAKE_CXX_COMPILER "clang++-6.0") +SET(CMAKE_CXX_COMPILER_TARGET ${triple}) + +# Tell pkgconfig to use aarch64 +SET(ENV{PKG_CONFIG_PATH} "/usr/lib/aarch64-linux-gnu/pkgconfig") + +# Tell CMake to use qemu to emulate +# Note: This should be automatically done by Ubuntu using binfmt_misc, but that +# seems to be broken on WSL (https://github.com/microsoft/WSL/issues/2620) so +# explicitly setting the emulator for now. +SET(CMAKE_CROSSCOMPILING_EMULATOR qemu-aarch64-static) diff --git a/cmake/toolchains/arm64-linux-gnu.cmake b/cmake/toolchains/arm64-linux-gnu.cmake new file mode 100644 index 000000000..73d076241 --- /dev/null +++ b/cmake/toolchains/arm64-linux-gnu.cmake @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# the name of the target OS and arch +SET(CMAKE_SYSTEM_NAME Linux) +SET(CMAKE_SYSTEM_PROCESSOR arm64) + +# which compilers to use +SET(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) +SET(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) + +# Tell pkgconfig to use arm64 +SET(ENV{PKG_CONFIG_PATH} "/usr/lib/aarch64-linux-gnu/pkgconfig") + +# Tell CMake to use qemu to emulate +# Note: This should be automatically done by Ubuntu using binfmt_misc, but that +# seems to be broken on WSL (https://github.com/microsoft/WSL/issues/2620) so +# explicitly setting the emulator for now. +SET(CMAKE_CROSSCOMPILING_EMULATOR qemu-aarch64-static) diff --git a/cmake/toolchains/x86_64-linux-clang.cmake b/cmake/toolchains/x86_64-linux-clang.cmake index 78cc3931f..2a810e192 100644 --- a/cmake/toolchains/x86_64-linux-clang.cmake +++ b/cmake/toolchains/x86_64-linux-clang.cmake @@ -4,12 +4,13 @@ # the name of the target OS and arch SET(CMAKE_SYSTEM_NAME Linux) SET(CMAKE_SYSTEM_PROCESSOR x86_64) +SET(triple x86_64-linux-gnu) # which compilers to use SET(CMAKE_C_COMPILER "clang-6.0") -SET(CMAKE_C_FLAGS -m64) +SET(CMAKE_C_COMPILER_TARGET ${triple}) SET(CMAKE_CXX_COMPILER "clang++-6.0") -SET(CMAKE_CXX_FLAGS -m64) +SET(CMAKE_CXX_COMPILER_TARGET ${triple}) # save flags to cache SET(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} CACHE STRING "C Flags" FORCE) diff --git a/docs/building.md b/docs/building.md index 2104751c6..aabd9e0b1 100644 --- a/docs/building.md +++ b/docs/building.md @@ -81,12 +81,12 @@ Windows PC is setup properly.* The list of libraries and tools that must be installed is maintained in a Dockerfile used by our CI system. Extract and run the install list from the -[Dockerfile](../scripts/Dockerfile) to ensure your machine has required dependencies. +[Dockerfile](../scripts/docker/Dockerfile) to ensure your machine has required dependencies. The depth engine is needed as well. The depth engine (DE) is a closed source binary shipped with the Linux Debian package. As an example, run `apt install libk4a1.3` to install the Azure Kinect 1.3 and get the depth engine. See -[using the depth engine](docs/usage.md#debian-package) for information about +[using the depth engine](usage.md#debian-package) for information about versioning and adding the Microsoft's Package Repository to your machine. **NOTE** *This step is not need for building, but is required running the SDK* @@ -104,10 +104,15 @@ need for building, but is required running the SDK* 2. Run CMake from that directory. The preferred build is ninja. All other generators are untested. - + + Release Build: ``` cmake .. -GNinja ``` + Debug Build: + ``` + cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug + ``` 3. Run the build (ninja). @@ -120,10 +125,6 @@ need for building, but is required running the SDK* Visual Studio 2017 supports opening CMake based projects directly. Use File / Open / CMake ... to open the root CMakeLists.txt in the project. -To cross compile for Linux on Windows you can run a pre-configured -[docker container](../docker/DOCKER.md) with the tools needed for Visual -Studio. - ### C# Wrapper The C# Wrapper is not included in the primary CMake build systems, it is built using the standard diff --git a/docs/releasing.md b/docs/releasing.md index 0d8457f91..01bab10b0 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -89,59 +89,62 @@ used to create a released build. Checkout the commit that matches the release la The following table contains all artifacts that are released in installers and packages. The file paths listed in the table are only representative, within any given package they will be different to match the standards and conventions for those packages. Each unique released artifact is listed in this table only once, although they will potentially appear duplicate times in packages or installers. File | MSI/Installer | NuGet | Runtime Debian Package | Development Debian Package | Tooling Debian Package --------------------------------------------------------------- | ------------------ | ------------------ | ------------------ | ------------------ | ----------------- -LICENSE.txt \* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: -REDIST.txt \* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: -ThirdPartyNotices.txt \* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: -version.txt \* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: -build/msbuild/native/Microsoft.Azure.Kinect.Sensor.targets \* | :one: | :white_check_mark: | | | -build/msbuild/netstandard2.0/Microsoft.Azure.Kinect.Sensor.targets \* | :two: | :two: | | | -build/cmake/x64/k4aConfig.cmake | :one: | :one: | | :white_check_mark: | -build/cmake/x64/k4aConfigVersion.cmake | :one: | :one: | | :white_check_mark: | -build/cmake/x64/k4aTargets-relwithdebinfo.cmake | :one: | :one: | | :white_check_mark: | -build/cmake/x64/k4aTargets.cmake | :one: | :one: | | :white_check_mark: | -build/cmake/x64/k4arecordConfig.cmake | :one: | :one: | | :white_check_mark: | -build/cmake/x64/k4arecordConfigVersion.cmake | :one: | :one: | | :white_check_mark: | -build/cmake/x64/k4arecordTargets-relwithdebinfo.cmake | :one: | :one: | | :white_check_mark: | -build/cmake/x64/k4arecordTargets.cmake | :one: | :one: | | :white_check_mark: | -include/k4a/k4a.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -include/k4a/k4a.hpp | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -include/k4a/k4a_export.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -include/k4a/k4atypes.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -include/k4a/k4aversion.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -include/k4arecord/k4arecord_export.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -include/k4arecord/playback.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -include/k4arecord/record.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -include/k4arecord/types.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -linux-ubuntu/x64/release/libdepthengine.so \* | | | :white_check_mark: | | -linux-ubuntu/x64/release/libdepthengine.so.2.0 \* | | | :white_check_mark: | | -linux-ubuntu/x64/release/libk4a.so (symlink) | | | | :white_check_mark: | -linux-ubuntu/x64/release/libk4a.so.1.x (symlink) | | | :white_check_mark: | | -linux-ubuntu/x64/release/libk4a.so.1.x.x | | | :white_check_mark: | | -linux-ubuntu/x64/release/libk4arecord.so (symlink) | | | | :white_check_mark: | -linux-ubuntu/x64/release/libk4arecord.so.1.x (symlink) | | | :white_check_mark: | | -linux-ubuntu/x64/release/libk4arecord.so.1.x.x | | | :white_check_mark: | | -linux-ubuntu/tools/x64/release/AzureKinectFirmwareTool | | | | | :white_check_mark: -linux-ubuntu/tools/x64/release/k4arecorder | | | | | :white_check_mark: -linux-ubuntu/tools/x64/release/k4aviewer | | | | | :white_check_mark: -netstandard2.0/AnyCpu/release/Microsoft.AzureKinect.deps.json | :two: | :two: | | | -netstandard2.0/AnyCpu/release/Microsoft.AzureKinect.dll | :two: | :two: | | | -netstandard2.0/AnyCpu/release/Microsoft.AzureKinect.pdb | :two: | :two: | | | -netstandard2.0/AnyCpu/release/Microsoft.AzureKinect.xml | :two: | :two: | | | -windows-desktop/amd64/release/depthengine_2_0.dll \* | :white_check_mark: | :white_check_mark: | | | -windows-desktop/amd64/release/k4a.dll | :white_check_mark: | :white_check_mark: | | | -windows-desktop/amd64/release/k4a.lib | :white_check_mark: | :white_check_mark: | | | -windows-desktop/amd64/release/k4a.pdb | :white_check_mark: | :white_check_mark: | | | -windows-desktop/amd64/release/k4arecord.dll | :white_check_mark: | :white_check_mark: | | | -windows-desktop/amd64/release/k4arecord.lib | :white_check_mark: | :white_check_mark: | | | -windows-desktop/amd64/release/k4arecord.pdb | :white_check_mark: | :white_check_mark: | | | -windows-desktop/tools/amd64/release/AzureKinectFirmwareTool.exe | :white_check_mark: | | | | -windows-desktop/tools/amd64/release/AzureKinectFirmwareTool.pdb | :white_check_mark: | | | | -windows-desktop/tools/amd64/release/k4arecorder.exe | :white_check_mark: | | | | -windows-desktop/tools/amd64/release/k4arecorder.pdb | :white_check_mark: | | | | -windows-desktop/tools/amd64/release/k4aviewer.exe | :white_check_mark: | | | | -windows-desktop/tools/amd64/release/k4aviewer.pdb | :white_check_mark: | | | | +----------------------------------------------------------------------------- | ------------------ | ------------------ | ------------------ | ------------------ | ----------------- +LICENSE.txt \* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: +REDIST.txt \* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: +ThirdPartyNotices.txt \* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: +version.txt \* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: +build/msbuild/native/Microsoft.Azure.Kinect.Sensor.targets \* | :one: | :white_check_mark: | | | +build/msbuild/netstandard2.0/Microsoft.Azure.Kinect.Sensor.targets \* | :white_check_mark: | :white_check_mark: | | | +build/cmake/x64/k4aConfig.cmake | :one: | :one: | | :white_check_mark: | +build/cmake/x64/k4aConfigVersion.cmake | :one: | :one: | | :white_check_mark: | +build/cmake/x64/k4aTargets-relwithdebinfo.cmake | :one: | :one: | | :white_check_mark: | +build/cmake/x64/k4aTargets.cmake | :one: | :one: | | :white_check_mark: | +build/cmake/x64/k4arecordConfig.cmake | :one: | :one: | | :white_check_mark: | +build/cmake/x64/k4arecordConfigVersion.cmake | :one: | :one: | | :white_check_mark: | +build/cmake/x64/k4arecordTargets-relwithdebinfo.cmake | :one: | :one: | | :white_check_mark: | +build/cmake/x64/k4arecordTargets.cmake | :one: | :one: | | :white_check_mark: | +include/k4a/k4a.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +include/k4a/k4a.hpp | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +include/k4a/k4a_export.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +include/k4a/k4atypes.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +include/k4a/k4aversion.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +include/k4arecord/k4arecord_export.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +include/k4arecord/playback.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +include/k4arecord/record.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +include/k4arecord/types.h | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +linux-ubuntu/x64/release/libdepthengine.so \* | | | :white_check_mark: | | +linux-ubuntu/x64/release/libdepthengine.so.2.0 \* | | | :white_check_mark: | | +linux-ubuntu/x64/release/libk4a.so (symlink) | | | | :white_check_mark: | +linux-ubuntu/x64/release/libk4a.so.1.x (symlink) | | | :white_check_mark: | | +linux-ubuntu/x64/release/libk4a.so.1.x.x | | | :white_check_mark: | | +linux-ubuntu/x64/release/libk4arecord.so (symlink) | | | | :white_check_mark: | +linux-ubuntu/x64/release/libk4arecord.so.1.x (symlink) | | | :white_check_mark: | | +linux-ubuntu/x64/release/libk4arecord.so.1.x.x | | | :white_check_mark: | | +linux-ubuntu/tools/x64/release/AzureKinectFirmwareTool | | | | | :white_check_mark: +linux-ubuntu/tools/x64/release/k4arecorder | | | | | :white_check_mark: +linux-ubuntu/tools/x64/release/k4aviewer | | | | | :white_check_mark: +netstandard2.0/AnyCpu/release/Microsoft.Azure.Kinect.Sensor.deps.json | :white_check_mark: | :white_check_mark: | | | +netstandard2.0/AnyCpu/release/Microsoft.Azure.Kinect.Sensor.dll | :white_check_mark: | :white_check_mark: | | | +netstandard2.0/AnyCpu/release/Microsoft.Azure.Kinect.Sensor.pdb | :white_check_mark: | :white_check_mark: | | | +netstandard2.0/AnyCpu/release/Microsoft.Azure.Kinect.Sensor.xml | :white_check_mark: | :white_check_mark: | | | +netstandard2.0/AnyCpu/release/Microsoft.Azure.Kinect.Sensor.Record.deps.json | :white_check_mark: | :white_check_mark: | | | +netstandard2.0/AnyCpu/release/Microsoft.Azure.Kinect.Sensor.Record.dll | :white_check_mark: | :white_check_mark: | | | +netstandard2.0/AnyCpu/release/Microsoft.Azure.Kinect.Sensor.Record.pdb | :white_check_mark: | :white_check_mark: | | | +netstandard2.0/AnyCpu/release/Microsoft.Azure.Kinect.Sensor.Record.xml | :white_check_mark: | :white_check_mark: | | | +windows-desktop/amd64/release/depthengine_2_0.dll \* | :white_check_mark: | :white_check_mark: | | | +windows-desktop/amd64/release/k4a.dll | :white_check_mark: | :white_check_mark: | | | +windows-desktop/amd64/release/k4a.lib | :white_check_mark: | :white_check_mark: | | | +windows-desktop/amd64/release/k4a.pdb | :white_check_mark: | :white_check_mark: | | | +windows-desktop/amd64/release/k4arecord.dll | :white_check_mark: | :white_check_mark: | | | +windows-desktop/amd64/release/k4arecord.lib | :white_check_mark: | :white_check_mark: | | | +windows-desktop/amd64/release/k4arecord.pdb | :white_check_mark: | :white_check_mark: | | | +windows-desktop/tools/amd64/release/AzureKinectFirmwareTool.exe | :white_check_mark: | | | | +windows-desktop/tools/amd64/release/AzureKinectFirmwareTool.pdb | :white_check_mark: | | | | +windows-desktop/tools/amd64/release/k4arecorder.exe | :white_check_mark: | | | | +windows-desktop/tools/amd64/release/k4arecorder.pdb | :white_check_mark: | | | | +windows-desktop/tools/amd64/release/k4aviewer.exe | :white_check_mark: | | | | +windows-desktop/tools/amd64/release/k4aviewer.pdb | :white_check_mark: | | | | * \* These files are generated/included at packaging time from Microsoft Internal sources. * :one: Include CMake and MS Build files in the MSI (issue [#370](https://github.com/microsoft/Azure-Kinect-Sensor-SDK/issues/370)) -* :two: .NET support planned for a future release (issue [#136](https://github.com/microsoft/Azure-Kinect-Sensor-SDK/issues/136)) diff --git a/docs/usage.md b/docs/usage.md index 75597f55c..e415ef38e 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -19,6 +19,7 @@ The latest stable binaries are available for download as MSIs. Tag | MSI | Firmware -------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------- + [v1.4.0](https://github.com/microsoft/Azure-Kinect-Sensor-SDK/releases/tag/v1.4.0) | [Azure Kinect SDK 1.4.0.exe](https://download.microsoft.com/download/4/5/a/45aa3917-45bf-4f24-b934-5cff74df73e1/Azure%20Kinect%20SDK%201.4.0.exe) | [1.6.108079014](https://download.microsoft.com/download/4/5/a/45aa3917-45bf-4f24-b934-5cff74df73e1/Firmware/AzureKinectDK_Fw_1.6.108079014.bin) [v1.3.0](https://github.com/microsoft/Azure-Kinect-Sensor-SDK/releases/tag/v1.3.0) | [Azure Kinect SDK 1.3.0.exe](https://download.microsoft.com/download/e/6/6/e66482b2-b6c1-4e34-bfee-95294163fc40/Azure%20Kinect%20SDK%201.3.0.exe) | [1.6.102075014](https://download.microsoft.com/download/1/9/8/198048e8-63f2-45c6-8f96-1fd541d1b4bc/AzureKinectDK_Fw_1.6.102075014.bin) [v1.2.0](https://github.com/microsoft/Azure-Kinect-Sensor-SDK/releases/tag/v1.2.0) | [Azure Kinect SDK 1.2.0.msi](http://download.microsoft.com/download/1/9/8/198048e8-63f2-45c6-8f96-1fd541d1b4bc/Azure%20Kinect%20SDK%201.2.0.msi) | [1.6.102075014](https://download.microsoft.com/download/1/9/8/198048e8-63f2-45c6-8f96-1fd541d1b4bc/AzureKinectDK_Fw_1.6.102075014.bin) [v1.1.1](https://github.com/Microsoft/Azure-Kinect-Sensor-SDK/releases/tag/v1.1.1) | [Azure Kinect SDK 1.1.1.msi](http://download.microsoft.com/download/4/9/0/490A8EB2-FFCA-4BAD-B0AD-0581CCE438FC/Azure%20Kinect%20SDK%201.1.1.msi) | [1.6.987014](https://download.microsoft.com/download/4/9/0/490A8EB2-FFCA-4BAD-B0AD-0581CCE438FC/AzureKinectDK_Fw_1.6.987014.bin) @@ -41,9 +42,12 @@ need for a different debian distribution, please file an [enhancement request](https://aka.ms/azurekinectfeedback). Our packages are hosted in [Microsoft's Package -Repository](https://packages.microsoft.com). Please follow [these +Repository](https://packages.microsoft.com). +* **AMD64** users, please follow [these instructions](https://docs.microsoft.com/en-us/windows-server/administration/linux-package-repository-for-microsoft-software) to configure Microsoft's Package Repository on your machine. +* **ARM64** users, please use the same instructions, but use https://packages.microsoft.com/ubuntu/18.04/multiarch/prod for the repository path instead of the default ~~https://packages.microsoft.com/ubuntu/18.04/prod~~. + Once you have configured Microsoft's Package Repository you should have access to the following packages: @@ -53,7 +57,7 @@ to the following packages: * k4a-tools (Tools package) Please note that "\" and "\" refer to the major and minor -portion of the version of the SDK you would like to target. For example, at the writing of these instructions the following packages are available +portion of the version of the SDK you would like to target. For example, at the writing of these instructions the following packages are available. * libk4a1.3 * libk4a1.3-dev diff --git a/examples/green_screen/CMakeLists.txt b/examples/green_screen/CMakeLists.txt index 423ca68a4..90fc70e94 100644 --- a/examples/green_screen/CMakeLists.txt +++ b/examples/green_screen/CMakeLists.txt @@ -1,16 +1,4 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. add_executable(green_screen main.cpp) -target_include_directories( green_screen PRIVATE ${OpenCV_INCLUDE_DIRS} ) - - -# OpenCV_LIBS, by default, is picking up the debug version of opencv on Windows even in release mode, which was causing a dependency on non-redistributable Visual Studio dlls. -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") - target_link_libraries(green_screen PRIVATE k4a::k4a ${OpenCV_DIR}/Opencv_world320d.lib) - else() - target_link_libraries(green_screen PRIVATE k4a::k4a ${OpenCV_DIR}/Opencv_world320.lib) - endif() -else() - target_link_libraries(green_screen PRIVATE k4a::k4a ${OpenCV_LIBS}) -endif() +target_link_libraries(green_screen PRIVATE k4a::k4a ${OpenCV_LIBS}) diff --git a/examples/green_screen/MultiDeviceCapturer.h b/examples/green_screen/MultiDeviceCapturer.h index 46ab33255..9dfeef56f 100644 --- a/examples/green_screen/MultiDeviceCapturer.h +++ b/examples/green_screen/MultiDeviceCapturer.h @@ -8,9 +8,7 @@ #include // This is the maximum difference between when we expected an image's timestamp to be and when it actually occurred. -// TODO waiting on a firmware update to be returned to 50 -constexpr std::chrono::microseconds MAX_ALLOWABLE_TIME_OFFSET_ERROR_FOR_IMAGE_TIMESTAMP(33000); -// constexpr std::chrono::microseconds MAX_ALLOWABLE_TIME_OFFSET_ERROR_FOR_IMAGE_TIMESTAMP(50); +constexpr std::chrono::microseconds MAX_ALLOWABLE_TIME_OFFSET_ERROR_FOR_IMAGE_TIMESTAMP(100); constexpr int64_t WAIT_FOR_SYNCHRONIZED_CAPTURE_TIMEOUT = 60000; diff --git a/examples/green_screen/README.md b/examples/green_screen/README.md index 94e4f0f1e..9fad82245 100644 --- a/examples/green_screen/README.md +++ b/examples/green_screen/README.md @@ -1,27 +1,22 @@ # Green Screen Example -The goal of the green screen example is to demonstrate best practices for using multiple Azure Kinect devices, with an +The goal of the green screen example is to demonstrate best practices for using multiple Azure Kinect DK devices, with an emphasis on synchronization and calibration (the 'green screen' code is only a small portion of the logic). In particular, the green screen application showcases a physical limitation of the hardware and how it can be mostly addressed using another device. -## What does this application *do*? +## What does this application *do* -The green screen example displays the scene as observed by one of the cameras (the 'main' camera). Using the camera's -depth, it will paint over anything beyond its depth threshold with a still image of the background (as seen when the app -was started), allowing people and things to "appear" suddenly when they walk within the depth threshold specified by the -user. It will fill in missing details with the 'backup' camera, if possible, resulting in a better green screen than the -main camera could achieve alone. +The green screen example displays the scene as observed by one of the cameras (the 'main' camera). Using the camera's depth, it will paint over anything beyond its depth threshold with a still image of the background (as seen when the app was started), allowing people and things to "appear" suddenly when they walk within the depth threshold specified by the +user. It will fill in missing details with the 'backup' camera, if possible, resulting in a better green screen than the main camera could achieve alone. -## Why use two cameras? Isn't one good enough? +## Why would more than 1 camera be used -First of all, you can use one camera if you like. The first option to the `green_screen` command is the number of -devices you'd like to use (1 or 2). +First of all, you can use one camera if you like. The first option to the `green_screen` command is the number of devices you'd like to use (1 or 2). -It's true that one camera can get you most of the way to a good solution in this case. However, if you only use one -camera (either by using the single-camera mode, or just covering the backup camera), and something in the scene is -closer to the camera than something else in the scene (for example, if you hold out an arm), you should see a "shadow" -of on the further-away object near the edge of the obstructing object. +It's true that one camera can get you most of the way to a good solution in this case. However, if you only use one camera (either by using the single-camera mode, or just covering the backup camera), and something in the scene is closer to the camera than something else in the scene (for example, if you hold out an arm), you should see a "shadow" of on the further-away object near the edge of the obstructing object. + + ![Shadow](./shadow.png) Why? The answer comes back to the physical construction of the Azure Kinect. The color camera and the depth camera are physically offset. Therefore, it's possible for the color camera to be able to see something that the depth camera @@ -31,6 +26,8 @@ transformed into the color camera space, segments of the transformed image that in using the secondary depth camera, which can (hopefully) see those parts of an object that are occluded from the main depth camera. + ![No shadow](./noshadow.png) + ## Installation instructions This example requires OpenCV to be installed to build. To ensure it will be built, ensure that OpenCV is found by adding @@ -45,46 +42,33 @@ is installed as recommended then it will automatically be used by the build. #### Windows -Our recommended way of getting OpenCV on Windows is by installing pre-built libraries. There's even a PowerShell script -that'll do much of the work for you in `scripts/install-opencv.ps1`. This will place OpenCV in your `C:\` folder. CMake -will copy the OpenCV binaries to the the `${CMAKE_RUNTIME_OUTPUT_DIRECTORY}` folder so that all built binaries will run -as expected. A user may choose to add the OpenCV binaries location to thier `%PATH%` to avoid this dependency. +Our recommended way of getting OpenCV on Windows is by installing pre-built libraries. There's even a PowerShell script that'll do much of the work for you in `scripts/install-opencv.ps1`. This will place OpenCV in your `C:\` folder. CMake will copy the OpenCV binaries to the the `${CMAKE_RUNTIME_OUTPUT_DIRECTORY}` folder so that all built binaries will run as expected. A user may choose to add the OpenCV binaries location to thier `%PATH%` to avoid this dependency. ## A Note on Calibration (only relevant for 2-camera version) -This program relies on transforming the backup camera's depth image into the color camera's space. This transformation -requires knowing the transformation between the two cameras. To find that transformation, we must calibrate the cameras. -This program relies on OpenCV's chessboard calibration functions. As a result, you should have a chessboard pattern to -use while calibrating the cameras. If you don't have one, print one out. You can find a 9x6 one -[here](https://docs.opencv.org/2.4/_downloads/pattern.png) (note that the 9x6 comes from the interior corners, not the -number of squares). The example requires the calibration board to be in view for both devices' color cameras for many -frames, so make sure it's visible to both cameras. +This program relies on transforming the backup camera's depth image into the color camera's space. This transformation requires knowing the transformation between the two cameras. To find that transformation, we must calibrate the cameras. +This program relies on OpenCV's chessboard calibration functions. As a result, you should have a chessboard pattern to use while calibrating the cameras. If you don't have one, print one out. You can find a 9x6 one +[here](https://docs.opencv.org/2.4/_downloads/pattern.png) (note that the 9x6 comes from the interior corners, not the number of squares). The example requires the calibration board to be in view for both devices' color cameras for many frames, so make sure it's visible to both cameras. -Checked into this folder is chessboard.png. It is 10 x 7. To OpenCV it is 9 x 6 and 22 mm if printed in landscape, at 100%, and with normal margins. +Checked into this folder is chessboard.png. It is 10 x 7. To OpenCV it is 9 x 6 and 22 mm if printed in landscape, at 100%, and with normal margins. on diferent platforms the printing experience can be different. When printing from the Mac, choose "print entire image" option. -Also, DO NOT move the cameras during or after calibration! Changing that translation will cause the backup camera to -provide inaccurate information. +Also, DO NOT move the cameras during or after calibration! Changing that translation will cause the backup camera to provide inaccurate information. ## Running the program -Run the `green_screen` executable in the `bin` folder with no arguments for usage details and defaults, and then fill in -any customizations you might need. +Run the `green_screen` executable in the `bin` folder with no arguments for usage details and defaults, and then fill in any customizations you might need. -The first option is the number of cameras you need. The next three options are for the calibration (if you use our recommended -board, you want those to be 9, 6, and (length of an individual calibration square side, in millimeters). If you're only -using a single camera, those numbers don't matter, although for validation they should be nonzero. 1 1 1 should be fine. +The first option is the number of cameras you need. The next three options are for the calibration (if you use our recommended board, you want those to be 9, 6, and (length of an individual calibration square side, in millimeters). If you're only using a single camera, those numbers don't matter, although for validation they should be nonzero. 1 1 1 should be fine. -The rest of the options aren't required. Run the example with no argument for details. In particular, you can change the -cutoff threshold distance. +The rest of the options aren't required. Run the example with no argument for details. In particular, you can change the cutoff threshold distance. ## The Green Screen -Once calibration has completed, a new screen should pop up with a steady video stream. If it's working right, the -example will take a picture of what it saw when it first started and display it as a background. Anything within the -threshold distance will be shown as it moves in real time, but anything too far away will be replaced with the -background. +Once calibration has completed, a new screen should pop up with a steady video stream. If it's working right, the example will take a picture of what it saw when it first started and display it as a background. Anything within the threshold distance will be shown as it moves in real time, but anything too far away will be replaced with the background. + + ![Green screen example](./hands.PNG) -# Potential reasons for failure +## Potential reasons for failure - If you're having weird runtime errors, sometimes disconnecting and reconnecting the cameras can help. @@ -99,5 +83,4 @@ background. - If you're on Linux and you're getting strange libusb or libuvc errors when you try to use more than one camera, see [here](https://github.com/microsoft/Azure-Kinect-Sensor-SDK/issues/485). -- If you're on Windows and you're getting a popup about not being able to find an OpenCV dll, you may need to re-check - your PATH to make sure it has the directory with that dll in it. +- If you're on Windows and you're getting a popup about not being able to find an OpenCV dll, you may need to re-check your PATH to make sure it has the directory with that dll in it. diff --git a/examples/green_screen/hands.PNG b/examples/green_screen/hands.PNG new file mode 100644 index 000000000..4fc57b78c Binary files /dev/null and b/examples/green_screen/hands.PNG differ diff --git a/examples/green_screen/noshadow.png b/examples/green_screen/noshadow.png new file mode 100644 index 000000000..4fe10043b Binary files /dev/null and b/examples/green_screen/noshadow.png differ diff --git a/examples/green_screen/shadow.png b/examples/green_screen/shadow.png new file mode 100644 index 000000000..85d6a2168 Binary files /dev/null and b/examples/green_screen/shadow.png differ diff --git a/examples/opencv_compatibility/CMakeLists.txt b/examples/opencv_compatibility/CMakeLists.txt index ef72e991d..47e8de9a9 100644 --- a/examples/opencv_compatibility/CMakeLists.txt +++ b/examples/opencv_compatibility/CMakeLists.txt @@ -2,15 +2,4 @@ # Licensed under the MIT License. add_executable(opencv_example main.cpp) -target_include_directories( opencv_example PRIVATE ${OpenCV_INCLUDE_DIRS} ) - -# OpenCV_LIBS, by default, is picking up the debug version of opencv on Windows even in release mode, which was causing a dependency on non-redistributable Visual Studio dlls. -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") - target_link_libraries(opencv_example PRIVATE k4a::k4a ${OpenCV_DIR}/Opencv_world320d.lib) - else() - target_link_libraries(opencv_example PRIVATE k4a::k4a ${OpenCV_DIR}/Opencv_world320.lib) - endif() -else() - target_link_libraries(opencv_example PRIVATE k4a::k4a ${OpenCV_LIBS}) -endif() +target_link_libraries(opencv_example PRIVATE k4a::k4a ${OpenCV_LIBS}) diff --git a/examples/opencv_compatibility/README.md b/examples/opencv_compatibility/README.md index 22d569068..b42a65122 100644 --- a/examples/opencv_compatibility/README.md +++ b/examples/opencv_compatibility/README.md @@ -8,7 +8,7 @@ camera into pixel-coordinates of the depth camera. We then show how the same ope OpenCV function projectPoints(). If the user has OpenCV installed, the OpenCV-specific code can be enabled by uncommenting the HAVE_OPENCV pound define. The -OpenCV code has been tested using OpenCV 4.0.1. +OpenCV code has been tested using OpenCV 4.1.1. ## Usage Info diff --git a/examples/transformation/main.cpp b/examples/transformation/main.cpp index 1c775a076..9211c8f01 100644 --- a/examples/transformation/main.cpp +++ b/examples/transformation/main.cpp @@ -120,7 +120,7 @@ static int capture(std::string output_dir, uint8_t deviceId = K4A_DEVICE_DEFAULT { int returnCode = 1; k4a_device_t device = NULL; - const int32_t TIMEOUT_IN_MS = 1000; + const int32_t TIMEOUT_IN_MS = 10000; k4a_transformation_t transformation = NULL; k4a_transformation_t transformation_color_downscaled = NULL; k4a_capture_t capture = NULL; diff --git a/examples/undistort/main.cpp b/examples/undistort/main.cpp index 15af9e755..f2d5eb66e 100644 --- a/examples/undistort/main.cpp +++ b/examples/undistort/main.cpp @@ -128,7 +128,7 @@ static pinhole_t create_pinhole_from_xy_range(const k4a_calibration_t *calibrati } float x_min = 0, x_max = 0, y_min = 0, y_max = 0; - compute_xy_range(calibration, K4A_CALIBRATION_TYPE_DEPTH, width, height, x_min, x_max, y_min, y_max); + compute_xy_range(calibration, camera, width, height, x_min, x_max, y_min, y_max); pinhole_t pinhole; diff --git a/extern/azure_c_shared/CMakeLists.txt b/extern/azure_c_shared/CMakeLists.txt index 7fa2a9ee9..c82cecbfd 100644 --- a/extern/azure_c_shared/CMakeLists.txt +++ b/extern/azure_c_shared/CMakeLists.txt @@ -1,7 +1,8 @@ if (NOT TARGET aziotsharedutil) - option(use_http "" OFF) - option(use_wsio "" OFF) - option(use_cppunittest "" OFF) + SET(use_http OFF CACHE BOOL "" FORCE) + SET(use_wsio OFF CACHE BOOL "" FORCE) + SET(use_cppunittest OFF CACHE BOOL "" FORCE) + SET(skip_samples ON CACHE BOOL "" FORCE) # By default, CMake turns on /W3 for all MSVC builds. # azure_c_shared_utility's CMakeLists.txt will then append /W4 to the @@ -13,6 +14,9 @@ if (NOT TARGET aziotsharedutil) add_subdirectory(src EXCLUDE_FROM_ALL) + target_include_directories(aziotsharedutil PUBLIC $) + target_include_directories(aziotsharedutil PUBLIC $) + else() message(STATUS "aziotsharedutil is already a target. Skipping adding it twice") endif() diff --git a/extern/azure_c_shared/src b/extern/azure_c_shared/src index bcf6393b1..5a70f2770 160000 --- a/extern/azure_c_shared/src +++ b/extern/azure_c_shared/src @@ -1 +1 @@ -Subproject commit bcf6393b1ce3cecf0fcdf8988621fd6e4d414df3 +Subproject commit 5a70f27709a2d4869b7db6794887481db28260d0 diff --git a/include/k4a/k4a.h b/include/k4a/k4a.h index 5f673dad2..e7a99494b 100644 --- a/include/k4a/k4a.h +++ b/include/k4a/k4a.h @@ -2135,9 +2135,9 @@ K4A_EXPORT k4a_result_t k4a_transformation_depth_image_to_color_camera(k4a_trans * or k4a_image_create_from_buffer(). * * \remarks - * Using linear interpolation could create new values to \p transformed_custom_image which do no exist in \p - * custom_image. Setting \p use_linear_interpolation to false will prevent this from happenning but will result in less - * smooth image. + * Using ::K4A_TRANSFORMATION_INTERPOLATION_TYPE_LINEAR for \p interpolation_type could create new values to \p + * transformed_custom_image which do no exist in \p custom_image. Using ::K4A_TRANSFORMATION_INTERPOLATION_TYPE_NEAREST + * will prevent this from happenning but will result in a less smooth image. * * \returns * ::K4A_RESULT_SUCCEEDED if \p transformed_depth_image and \p transformed_custom_image were successfully written and diff --git a/include/k4a/k4a.hpp b/include/k4a/k4a.hpp index 6d9dea331..c86299882 100644 --- a/include/k4a/k4a.hpp +++ b/include/k4a/k4a.hpp @@ -109,7 +109,7 @@ class image reset(); } - /** Sets image to a shallow copy of other + /** Sets image to a shallow copy of the other image */ image &operator=(const image &other) noexcept { @@ -176,7 +176,14 @@ class image /** Returns true if the image is valid, false otherwise */ - operator bool() const noexcept + explicit operator bool() const noexcept + { + return is_valid(); + } + + /** Returns true if the image is valid, false otherwise + */ + bool is_valid() const noexcept { return m_handle != nullptr; } @@ -436,7 +443,7 @@ class capture reset(); } - /** Sets capture to a shallow copy of other + /** Sets capture to a shallow copy of the other image */ capture &operator=(const capture &other) noexcept { @@ -503,7 +510,14 @@ class capture /** Returns true if the capture is valid, false otherwise */ - operator bool() const noexcept + explicit operator bool() const noexcept + { + return is_valid(); + } + + /** Returns true if the capture is valid, false otherwise + */ + bool is_valid() const noexcept { return m_handle != nullptr; } @@ -1111,7 +1125,14 @@ class device /** Returns true if the device is valid, false otherwise */ - operator bool() const noexcept + explicit operator bool() const noexcept + { + return is_valid(); + } + + /** Returns true if the device is valid, false otherwise + */ + bool is_valid() const noexcept { return m_handle != nullptr; } @@ -1318,7 +1339,7 @@ class device { std::vector calibration; size_t buffer = 0; - k4a_buffer_result_t result = k4a_device_get_raw_calibration(m_handle, &calibration[0], &buffer); + k4a_buffer_result_t result = k4a_device_get_raw_calibration(m_handle, nullptr, &buffer); if (result == K4A_BUFFER_RESULT_TOO_SMALL && buffer > 1) { diff --git a/include/k4ainternal/common.h b/include/k4ainternal/common.h index 27e92cc96..6da3c6296 100644 --- a/include/k4ainternal/common.h +++ b/include/k4ainternal/common.h @@ -36,6 +36,10 @@ typedef struct _guid_t #define MAX_SERIAL_NUMBER_LENGTH \ (13 * 2) // Current schema is for 12 digits plus NULL, the extra size is in case that grows in the future. +#define HZ_TO_PERIOD_MS(Hz) (1000 / Hz) +#define HZ_TO_PERIOD_US(Hz) (1000000 / Hz) +#define HZ_TO_PERIOD_NS(Hz) (1000000000 / Hz) + inline static uint32_t k4a_convert_fps_to_uint(k4a_fps_t fps) { uint32_t fps_int; @@ -140,6 +144,52 @@ inline static bool k4a_convert_depth_mode_to_width_height(k4a_depth_mode_t mode, return true; } +inline static bool k4a_is_version_greater_or_equal(k4a_version_t *fw_version_l, k4a_version_t *fw_version_r) +{ + typedef enum + { + FW_OK, + FW_TOO_LOW, + FW_UNKNOWN + } fw_check_state_t; + + fw_check_state_t fw = FW_UNKNOWN; + + // Check major version + if (fw_version_l->major > fw_version_r->major) + { + fw = FW_OK; + } + else if (fw_version_l->major < fw_version_r->major) + { + fw = FW_TOO_LOW; + } + + // Check minor version + if (fw == FW_UNKNOWN) + { + if (fw_version_l->minor > fw_version_r->minor) + { + fw = FW_OK; + } + else if (fw_version_l->minor < fw_version_r->minor) + { + fw = FW_TOO_LOW; + } + } + + // Check iteration version + if (fw == FW_UNKNOWN) + { + fw = FW_TOO_LOW; + if (fw_version_l->iteration >= fw_version_r->iteration) + { + fw = FW_OK; + } + } + + return (fw == FW_OK); +} #ifdef __cplusplus } #endif diff --git a/include/k4ainternal/matroska_common.h b/include/k4ainternal/matroska_common.h index 6c8314130..0bc26f6f9 100644 --- a/include/k4ainternal/matroska_common.h +++ b/include/k4ainternal/matroska_common.h @@ -81,6 +81,11 @@ constexpr uint64_t operator"" _s(unsigned long long x) #define CLUSTER_WRITE_DELAY_NS 2_s #endif +#ifndef CLUSTER_WRITE_QUEUE_WARNING_NS +// If a cluster is in the qeuue too long, warn about disk write speed. +#define CLUSTER_WRITE_QUEUE_WARNING_NS CLUSTER_WRITE_DELAY_NS + 2_s +#endif + #ifndef CUE_ENTRY_GAP_NS #define CUE_ENTRY_GAP_NS 1_s #endif diff --git a/include/k4arecord/playback.h b/include/k4arecord/playback.h index 6502ea8fa..b1f2d14e4 100644 --- a/include/k4arecord/playback.h +++ b/include/k4arecord/playback.h @@ -818,7 +818,7 @@ K4ARECORD_EXPORT void k4a_playback_data_block_release(k4a_playback_data_block_t * * \remarks * The first call to k4a_playback_get_next_imu_sample() after k4a_playback_seek_timestamp() will return the first imu - * sample with a timestamp greter than or equal to the seek time. + * sample with a timestamp greater than or equal to the seek time. * * \remarks * The first call to k4a_playback_get_previous_imu_sample() after k4a_playback_seek_timestamp() will return the first diff --git a/include/k4arecord/playback.hpp b/include/k4arecord/playback.hpp index dc21174c6..7ec749a59 100644 --- a/include/k4arecord/playback.hpp +++ b/include/k4arecord/playback.hpp @@ -18,7 +18,107 @@ namespace k4a { -/** \class playback playback.hpp +/** \class data_block playback.hpp + * Wrapper for \ref k4a_playback_data_block_t + * + * \sa k4a_playback_data_block_t + */ +class data_block +{ +public: + /** Creates a data_block from a k4a_playback_data_block_t + * Takes ownership of the handle, you should not call k4a_playback_data_block_release on the handle after + * giving it to the data_block; the data_block will take care of that. + */ + data_block(k4a_playback_data_block_t handle = nullptr) noexcept : m_handle(handle) {} + + // No Copies allowed + data_block(const data_block &) = delete; + data_block &operator=(const data_block &) = delete; + + /** Moves another data_block into a new data_block + */ + data_block(data_block &&other) noexcept + { + m_handle = other.m_handle; + other.m_handle = nullptr; + } + + ~data_block() + { + reset(); + } + + /** Moves another data_block into this data_block; other is set to invalid + */ + data_block &operator=(data_block &&other) noexcept + { + if (this != &other) + { + reset(); + m_handle = other.m_handle; + other.m_handle = nullptr; + } + return *this; + } + + /** Returns true if the data_block is valid, false otherwise + */ + explicit operator bool() const noexcept + { + return is_valid(); + } + + /** Returns true if the data_block is valid, false otherwise + */ + bool is_valid() const noexcept + { + return m_handle != nullptr; + } + + /** Releases the underlying k4a_playback_data_block_t; the data_block is set to invalid. + */ + void reset() noexcept + { + if (m_handle) + { + k4a_playback_data_block_release(m_handle); + m_handle = nullptr; + } + } + + /** Get the time stamp in micro seconds for the given data_block + * + * \sa k4a_playback_data_block_get_device_timestamp_usec + */ + std::chrono::microseconds get_device_timestamp_usec() const noexcept + { + return std::chrono::microseconds(k4a_playback_data_block_get_device_timestamp_usec(m_handle)); + } + + /** Get the size of the data_block buffer. + * + * \sa k4a_playback_data_block_get_buffer_size + */ + size_t get_buffer_size() const noexcept + { + return k4a_playback_data_block_get_buffer_size(m_handle); + } + + /** Get the data_block buffer. + * + * \sa k4a_playback_data_block_get_buffer + */ + const uint8_t *get_buffer() const noexcept + { + return k4a_playback_data_block_get_buffer(m_handle); + } + +private: + k4a_playback_data_block_t m_handle; +}; + +/** \class playback playback.hpp * Wrapper for \ref k4a_playback_t * * Wraps a handle for a playback object @@ -67,7 +167,14 @@ class playback /** Returns true if the k4a::playback is valid, false otherwise */ - operator bool() const noexcept + explicit operator bool() const noexcept + { + return is_valid(); + } + + /** Returns true if the k4a::playback is valid, false otherwise + */ + bool is_valid() const noexcept { return m_handle != nullptr; } @@ -94,7 +201,7 @@ class playback { std::vector calibration; size_t buffer = 0; - k4a_buffer_result_t result = k4a_playback_get_raw_calibration(m_handle, &calibration[0], &buffer); + k4a_buffer_result_t result = k4a_playback_get_raw_calibration(m_handle, nullptr, &buffer); if (result == K4A_BUFFER_RESULT_TOO_SMALL && buffer > 1) { @@ -145,8 +252,8 @@ class playback return config; } - /** Get the next capture in the recording - * Returns true if a capture was available, false if there are none left + /** Get the next capture in the recording. + * Returns true if a capture was available, false if there are none left. * Throws error on failure. * * \sa k4a_playback_get_next_capture @@ -169,8 +276,8 @@ class playback throw error("Failed to get next capture!"); } - /** Get the next capture in the recording - * Returns true if a capture was available, false if there are none left + /** Get the previous capture in the recording. + * Returns true if a capture was available, false if there are none left. * Throws error on failure. * * \sa k4a_playback_get_previous_capture @@ -190,7 +297,7 @@ class playback return false; } - throw error("Failed to get next capture!"); + throw error("Failed to get previous capture!"); } /** Reads the value of a tag from the recording @@ -202,7 +309,7 @@ class playback { std::string tag; size_t buffer = 0; - k4a_buffer_result_t result = k4a_playback_get_tag(m_handle, name, &tag[0], &buffer); + k4a_buffer_result_t result = k4a_playback_get_tag(m_handle, name, nullptr, &buffer); if (result == K4A_BUFFER_RESULT_TOO_SMALL && buffer > 0) { @@ -227,8 +334,8 @@ class playback return true; } - /** Get the next IMU sample in the recording - * Returns true if a sample was available, false if there are none left + /** Get the next IMU sample in the recording. + * Returns true if a sample was available, false if there are none left. * Throws error on failure. * * \sa k4a_playback_get_next_imu_sample @@ -249,8 +356,8 @@ class playback throw error("Failed to get next IMU sample!"); } - /** Get the previous IMU sample in the recording - * Returns true if a sample was available, false if there are none left + /** Get the previous IMU sample in the recording. + * Returns true if a sample was available, false if there are none left. * Throws error on failure. * * \sa k4a_playback_get_previous_imu_sample @@ -295,8 +402,8 @@ class playback return std::chrono::microseconds(k4a_playback_get_recording_length_usec(m_handle)); } - /** Set the image format that color captures will be converted to. By default the conversion format will be the same - * as the image format stored in the recording file, and no conversion will occur. + /** Set the image format that color captures will be converted to. By default the conversion format will be the + * same as the image format stored in the recording file, and no conversion will occur. * * Throws error on failure. * @@ -312,6 +419,77 @@ class playback } } + /** Get the next data block in the recording. + * Returns true if a block was available, false if there are none left. + * Throws error on failure. + * + * \sa k4a_playback_get_next_data_block + */ + bool get_next_data_block(const char *track, data_block *block) + { + k4a_playback_data_block_t block_handle; + k4a_stream_result_t result = k4a_playback_get_next_data_block(m_handle, track, &block_handle); + + if (K4A_STREAM_RESULT_SUCCEEDED == result) + { + *block = data_block(block_handle); + return true; + } + else if (K4A_STREAM_RESULT_EOF == result) + { + return false; + } + + throw error("Failed to get next data block!"); + } + + /** Get the previous data block from the recording. + * Returns true if a block was available, false if there are none left. + * Throws error on failure. + * + * \sa k4a_playback_get_previous_data_block + */ + bool get_previous_data_block(const char *track, data_block *block) + { + k4a_playback_data_block_t block_handle; + k4a_stream_result_t result = k4a_playback_get_previous_data_block(m_handle, track, &block_handle); + + if (K4A_STREAM_RESULT_SUCCEEDED == result) + { + *block = data_block(block_handle); + return true; + } + else if (K4A_STREAM_RESULT_EOF == result) + { + return false; + } + + throw error("Failed to get previous data block!"); + } + + /** Get the attachment block from the recording. + * Returns true if the attachment was available, false if it was not found. + * Throws error on failure. + * + * \sa k4a_playback_get_attachment + */ + bool get_attachment(const char *attachment, std::vector *data) + { + size_t data_size = 0; + k4a_buffer_result_t result = k4a_playback_get_attachment(m_handle, attachment, nullptr, &data_size); + if (result == K4A_BUFFER_RESULT_TOO_SMALL) + { + data->resize(data_size); + result = k4a_playback_get_attachment(m_handle, attachment, &(*data)[0], &data_size); + if (result != K4A_BUFFER_RESULT_SUCCEEDED) + { + throw error("Failed to read attachment!"); + } + return true; + } + return false; + } + /** Opens a K4A recording for playback. * Throws error on failure. * diff --git a/include/k4arecord/record.hpp b/include/k4arecord/record.hpp index c822ca8eb..62a6507b1 100644 --- a/include/k4arecord/record.hpp +++ b/include/k4arecord/record.hpp @@ -62,7 +62,14 @@ class record /** Returns true if the k4a::record is valid, false otherwise */ - operator bool() const noexcept + explicit operator bool() const noexcept + { + return is_valid(); + } + + /** Returns true if the k4a::record is valid, false otherwise + */ + bool is_valid() const noexcept { return m_handle != nullptr; } @@ -73,7 +80,7 @@ class record */ void close() noexcept { - if (m_handle) + if (is_valid()) { k4a_record_close(m_handle); m_handle = nullptr; @@ -90,7 +97,7 @@ class record { k4a_result_t result = k4a_record_flush(m_handle); - if (K4A_RESULT_SUCCEEDED != result) + if (K4A_FAILED(result)) { throw error("Failed to flush!"); } @@ -106,7 +113,7 @@ class record { k4a_result_t result = k4a_record_add_tag(m_handle, name, value); - if (K4A_RESULT_SUCCEEDED != result) + if (K4A_FAILED(result)) { throw error("Failed to add tag!"); } @@ -121,7 +128,7 @@ class record { k4a_result_t result = k4a_record_add_imu_track(m_handle); - if (K4A_RESULT_SUCCEEDED != result) + if (K4A_FAILED(result)) { throw error("Failed to add imu_track!"); } @@ -136,7 +143,7 @@ class record { k4a_result_t result = k4a_record_add_attachment(m_handle, attachment_name, buffer, buffer_size); - if (K4A_RESULT_SUCCEEDED != result) + if (K4A_FAILED(result)) { throw error("Failed to add attachment!"); } @@ -160,7 +167,7 @@ class record codec_context_size, track_settings); - if (K4A_RESULT_SUCCEEDED != result) + if (K4A_FAILED(result)) { throw error("Failed to add custom video track!"); } @@ -184,7 +191,7 @@ class record codec_context_size, track_settings); - if (K4A_RESULT_SUCCEEDED != result) + if (K4A_FAILED(result)) { throw error("Failed to add custom subtitle track!"); } @@ -199,7 +206,7 @@ class record { k4a_result_t result = k4a_record_write_header(m_handle); - if (K4A_RESULT_SUCCEEDED != result) + if (K4A_FAILED(result)) { throw error("Failed to write header!"); } @@ -210,11 +217,11 @@ class record * * \sa k4a_record_write_capture */ - void write_capture(capture &capture) + void write_capture(const capture &capture) { k4a_result_t result = k4a_record_write_capture(m_handle, capture.handle()); - if (K4A_RESULT_SUCCEEDED != result) + if (K4A_FAILED(result)) { throw error("Failed to write capture!"); } @@ -225,11 +232,11 @@ class record * * \sa k4a_record_write_imu_sample */ - void write_imu_sample(k4a_imu_sample_t &imu_sample) + void write_imu_sample(const k4a_imu_sample_t &imu_sample) { k4a_result_t result = k4a_record_write_imu_sample(m_handle, imu_sample); - if (K4A_RESULT_SUCCEEDED != result) + if (K4A_FAILED(result)) { throw error("Failed to write imu sample!"); } @@ -241,17 +248,18 @@ class record * \sa k4a_record_write_custom_track_data */ void write_custom_track_data(const char *track_name, - uint64_t device_timestamp_usec, + const std::chrono::microseconds device_timestamp_usec, uint8_t *custom_data, size_t custom_data_size) { k4a_result_t result = k4a_record_write_custom_track_data(m_handle, track_name, - device_timestamp_usec, + internal::clamp_cast( + device_timestamp_usec.count()), custom_data, custom_data_size); - if (K4A_RESULT_SUCCEEDED != result) + if (K4A_FAILED(result)) { throw error("Failed to write custom track data!"); } @@ -262,12 +270,12 @@ class record * * \sa k4a_record_create */ - static record create(const char *path, device &device, k4a_device_configuration_t &device_configuration) + static record create(const char *path, const device &device, const k4a_device_configuration_t &device_configuration) { k4a_record_t handle = nullptr; k4a_result_t result = k4a_record_create(path, device.handle(), device_configuration, &handle); - if (K4A_RESULT_SUCCEEDED != result) + if (K4A_FAILED(result)) { throw error("Failed to create recorder!"); } diff --git a/microsoft-support.md b/microsoft-support.md new file mode 100644 index 000000000..95a56914d --- /dev/null +++ b/microsoft-support.md @@ -0,0 +1,36 @@ +# Microsoft Support for Azure Kinect DK Sensor SDK + +Every Microsoft product has a lifecycle, including Azure Kinect Sensor SDK. The lifecycle begins when a product is released and ends when it's no longer supported. Knowing key dates in this lifecycle helps you make informed decisions about when to upgrade or make other changes to your software. This product is governed by the [Microsoft Modern Lifecycle](https://support.microsoft.com/help/30881/modern-lifecycle-policy). + +This document describes the support lifecycle for: Azure Kinect Sensor SDK. + +## Release Types + +Microsoft produces **Long Term Support (LTS)** and **Current** releases, which are defined as: + +* **LTS** releases are designed for long-term support. They included features and components that have been stabilized, requiring few updates over a longer support release lifetime. These releases are a good choice for hosting applications that you do not intend to update. +* **Current** releases include new features that may undergo future change based on feedback. These releases are a good choice for applications in active development, giving you access to the latest features and improvements. You need to upgrade to later Sensor SDK releases more often to stay in support. + +Both types of releases receive critical fixes throughout their lifecycle, for security, reliability, or to add support for new operating system versions. You must stay up-to-date with the latest patches to qualify for support. + +## Release Support Policies + +Azure Kinect Sensor SDK releases are supported according to the following policies. + +### Long Term Support (LTS) releases + +LTS releases are supported for three years after the initial release. + +### Current releases + +Current releases are supported for three months after a subsequent Current or LTS release. + +### Maintenance releases + +Maintenance releases are in the last stage of the lifecycle . During Maintenance, a release will receive security updates. The length of Maintenance time is 3 months for Current and 1 year for LTS. + +### End of support + +End of support refers to the date when Microsoft no longer provides fixes, updates, or online technical assistance. As this date nears, make sure you have the latest available update\* installed. Without Microsoft support, you will no longer receive security updates that can help protect your machine from harmful viruses, spyware, and other malicious software that can steal your personal information. + +\* Updates are cumulative, with each update built upon all of the updates that preceded it. A device needs to install the latest update to remain supported. Updates may include new features, fixes (security and/or non-security), or a combination of both. Not all features in an update will work on all devices. Update availability may vary, for example by country, region, network connectivity, or hardware capabilities (including, for example, free disk space). diff --git a/proposals/ARM-support.md b/proposals/ARM-support.md new file mode 100644 index 000000000..ebe983c8b --- /dev/null +++ b/proposals/ARM-support.md @@ -0,0 +1,56 @@ + +# Support for ARM + +## Proposal State + +* [x] Proposed 10/31/2019 +* [x] Prototype: Skipped +* [x] Implementation Started: 1/1/2020 +* [x] Feature Complete: 3/23/2020 + +## Summary + +Support the Sensor and Body Tracking SDKs on ARM based boards. + +## Feature Scenario + +Support for ARM enables our customers to build more stand along solutions utilizing mini- PC platforms. This unlocks more scenarios around robotics, manufacturing and healthcare where an Azure Kinect DK should be more mobile. The feature is also the most asked feature on customer voice. + +## Supported Hardware + +We are considering to use 2 kinds of Jetson boards for the ARM support since most of our customers prefer these boards: + +- Jetson Nano + - GPU: Custom 128 CUDA Core GPU + - CPU: Quad-Core ARM Cortex-A57 @ 1.43 GHz + - 4GB DDR4 RAM +- Jetson TX2 (to run Body Tracking SDK) + - GPU: Custom 256 CUDA Core GPU (Pascal architecture) + - CPU: Dual-Core NVIDIA Denver 2 ARMv8 64-bit processor + Quad-Core ARM Cortex-A57 @ 1.43 GHz + - 4GB / 8GB DDR4 RAM + +## Implementation + +There are several large adjustments need to be implemented: + +- Changes to Sensor SDK, Depth Engine, Body Tracking SDK and packaging repo's +- Changes to the build process for Sensor SDK, Depth Engine and Body Tracking SDK +- Changes to the test system for Sensor SDK, Depth Engine and Body Tracking SDK +- Changes to the release process for both Linux and Windows. +- Evaluate and make changes if needed to the depth engine code that uses SSE instructions. + +We will target releasing ARM binaries for Windows and Linux. + +## Packaging + +### Sensor SDK + +- Microsoft installer (MSI) +- Nuget package (for Windows) +- Deb package (For Ubuntu 18.04) + +### Body Tracking SDK + +- Microsoft installer (MSI) +- Nuget package (For Windows) +- Deb package (For Ubuntu 18.04) diff --git a/proposals/README.md b/proposals/README.md new file mode 100644 index 000000000..156c34399 --- /dev/null +++ b/proposals/README.md @@ -0,0 +1,6 @@ +# Azure Kinect Sensor SDK Proposals + +New features proposals are living documents describing the current thinking about a given feature. Proposals are the way for Azure Kinect team to communicate to the public upcoming new feature work and get the feedback. +At this time, all proposals will be created by Microsoft Azure Kinect team for the most desired features. +If you have a great idea for a proposal, please submit it on [User Voice](https://feedback.azure.com/forums/920053). See more details about contributing to this project or new ideas in our [contribution guide](https://github.com/microsoft/Azure-Kinect-Sensor-SDK/blob/develop/CONTRIBUTING.md). +We would love your feedback on every proposal and therefore we will create a GitHub issue where you can provide it. The proposal will be available for review for 2 weeks before we will start the work. diff --git a/scripts/99-k4a.rules b/scripts/99-k4a.rules index 75c93d576..570390a73 100644 --- a/scripts/99-k4a.rules +++ b/scripts/99-k4a.rules @@ -1,8 +1,8 @@ -# Bus 002 Device 116: ID 045e:097a Microsoft Corp. -# Bus 001 Device 015: ID 045e:097b Microsoft Corp. -# Bus 002 Device 118: ID 045e:097c Microsoft Corp. -# Bus 002 Device 117: ID 045e:097d Microsoft Corp. -# Bus 001 Device 016: ID 045e:097e Microsoft Corp. +# Bus 002 Device 116: ID 045e:097a Microsoft Corp. - Generic Superspeed USB Hub +# Bus 001 Device 015: ID 045e:097b Microsoft Corp. - Generic USB Hub +# Bus 002 Device 118: ID 045e:097c Microsoft Corp. - Azure Kinect Depth Camera +# Bus 002 Device 117: ID 045e:097d Microsoft Corp. - Azure Kinect 4K Camera +# Bus 001 Device 016: ID 045e:097e Microsoft Corp. - Azure Kinect Microphone Array BUS!="usb", ACTION!="add", SUBSYSTEM!=="usb_device", GOTO="k4a_logic_rules_end" diff --git a/scripts/Dockerfile b/scripts/Dockerfile deleted file mode 100644 index b77b9c5bb..000000000 --- a/scripts/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -# Use the official ubuntu:18.04 image as the parent image -FROM ubuntu:18.04 - -# Tell Ubuntu non-interactive install -ARG DEBIAN_FRONTEND=noninteractive - -# Set the working directory to /app -WORKDIR /app - -# Run apt-get steps -RUN apt-get update && apt-get install -y \ - pkg-config \ - ninja-build \ - doxygen \ - clang \ - gcc-multilib \ - g++-multilib \ - python3 \ - git-lfs \ - nasm \ - cmake \ - libgl1-mesa-dev \ - libsoundio-dev \ - libvulkan-dev \ - libx11-dev \ - libxcursor-dev \ - libxinerama-dev \ - libxrandr-dev \ - libusb-1.0-0-dev \ - libssl-dev \ - libudev-dev \ - mesa-common-dev \ - uuid-dev \ - libopencv-dev diff --git a/scripts/docker/Dockerfile b/scripts/docker/Dockerfile new file mode 100644 index 000000000..a0006c0ba --- /dev/null +++ b/scripts/docker/Dockerfile @@ -0,0 +1,20 @@ +ARG UBUNTU_VERSION=18.04 +# Use the official ubuntu:18.04 image as the parent image +FROM ubuntu:${UBUNTU_VERSION} + +# Set the working directory to /app +WORKDIR /app + +ARG ARCH=amd64 + +ADD setup-ubuntu.sh /app +ADD sources.list /app + +# Tell Ubuntu non-interactive install +ARG DEBIAN_FRONTEND=noninteractive + + +RUN ./setup-ubuntu.sh ${ARCH} + +RUN apt-get install -y ca-certificates +RUN update-ca-certificates \ No newline at end of file diff --git a/scripts/docker/setup-ubuntu.sh b/scripts/docker/setup-ubuntu.sh new file mode 100644 index 000000000..9bf440bfc --- /dev/null +++ b/scripts/docker/setup-ubuntu.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +# Usage: +# ./setup-ubuntu.sh [arm64 | amd64] + +# Warning! This will override your sources.list file!! + +arch=amd64 + +# Copy off old sources.list file +cp /etc/apt/sources.list /etc/apt/sources.list.old +echo "Backed up /etc/apt/sources.list to /etc/apt/sources.list.old" + +# Copy over the new file +cp sources.list /etc/apt/sources.list +echo "Overwrote /etc/apt/sources.list with sources.list" + +apt-get update + +apt-get install wget -y + +# Add Public microsoft repo keys to the image +wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb +dpkg -i packages-microsoft-prod.deb + +if [ "$1" = "arm64" ]; then + arch="arm64" +fi + +echo "Setting up for building $arch binaries" + +dpkg --add-architecture amd64 +dpkg --add-architecture arm64 + +apt-get update + +packages=(\ + gcc-aarch64-linux-gnu \ + g++-aarch64-linux-gnu \ + file \ + dpkg-dev \ + qemu \ + binfmt-support \ + qemu-user-static \ + pkg-config \ + ninja-build \ + doxygen \ + clang \ + python3 \ + gcc \ + g++ \ + git \ + git-lfs \ + nasm \ + cmake \ + powershell \ + libgl1-mesa-dev:$arch \ + libsoundio-dev:$arch \ + libjpeg-dev:$arch \ + libvulkan-dev:$arch \ + libx11-dev:$arch \ + libxcursor-dev:$arch \ + libxinerama-dev:$arch \ + libxrandr-dev:$arch \ + libusb-1.0-0-dev:$arch \ + libssl-dev:$arch \ + libudev-dev:$arch \ + mesa-common-dev:$arch \ + uuid-dev:$arch ) + +if [ "$arch" = "amd64" ]; then + packages+=(libopencv-dev:$arch) +fi + +apt-get install -y --no-install-recommends ${packages[@]} \ No newline at end of file diff --git a/scripts/docker/sources.list b/scripts/docker/sources.list new file mode 100644 index 000000000..28fe9d19d --- /dev/null +++ b/scripts/docker/sources.list @@ -0,0 +1,26 @@ + +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic main restricted +deb [arch=arm64] http://ports.ubuntu.com/ bionic main restricted + +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted +deb [arch=arm64] http://ports.ubuntu.com/ bionic-updates main restricted + +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic universe +deb [arch=arm64] http://ports.ubuntu.com/ bionic universe +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-updates universe +deb [arch=arm64] http://ports.ubuntu.com/ bionic-updates universe + +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic multiverse +deb [arch=arm64] http://ports.ubuntu.com/ bionic multiverse +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-updates multiverse +deb [arch=arm64] http://ports.ubuntu.com/ bionic-updates multiverse + +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted universe multiverse +deb [arch=arm64] http://ports.ubuntu.com/ bionic-backports main restricted universe multiverse + +deb [arch=amd64] http://security.ubuntu.com/ubuntu/ bionic-security main restricted +deb [arch=arm64] http://ports.ubuntu.com/ bionic-security main restricted +deb [arch=amd64] http://security.ubuntu.com/ubuntu/ bionic-security universe +deb [arch=arm64] http://ports.ubuntu.com/ bionic-security universe +deb [arch=amd64] http://security.ubuntu.com/ubuntu/ bionic-security multiverse +deb [arch=arm64] http://ports.ubuntu.com/ bionic-security multiverse \ No newline at end of file diff --git a/scripts/install-opencv.ps1 b/scripts/install-opencv.ps1 index d3b68b9b4..27095c769 100644 --- a/scripts/install-opencv.ps1 +++ b/scripts/install-opencv.ps1 @@ -23,17 +23,52 @@ function Download-ToTemp $tempDir = [System.IO.Path]::GetTempPath() $path = Join-Path -Path $tempDir -ChildPath $filename - Write-Host -NoNewline "Downloading $url to $path..." + Write-Host "Downloading $url to $path..." Invoke-WebRequest -Uri $url -OutFile $path -UserAgent "NativeClient" -MaximumRetryCount 5 -RetryIntervalSec 60 - Write-Host "Done" + Write-Host "Downloading Done" return $path } -# Download OpenCV -$url = "https://sourceforge.net/projects/opencvlibrary/files/opencv-win/3.2.0/opencv-3.2.0-vc14.exe/download" -$filename = "opencv-3.2.0-vc14.exe" -$opencv_exe = Download-ToTemp -url $url -filename $filename +# Total timeout is 20 minutes +$delay_in_seconds = 15 +$max_retry_attempts = 80 +$url = "https://sourceforge.net/projects/opencvlibrary/files/4.1.1/opencv-4.1.1-vc14_vc15.exe/download" +$filename = "opencv-4.1.1-vc14_vc15.exe" + +$retry = 1; +Do +{ + Write-Host + Write-Host + Write-Host "Attempting to download OpenCV, try #$retry" + + $opencv_exe = "error_Download-ToTemp_did_not_return_a_file" # default value incase of exception + try + { + # Download OpenCV + $opencv_exe = Download-ToTemp -url $url -filename $filename + } + catch + { + Write-Host + Write-Host "An exception was thrown: $($PSItem.ToString())" + } + + Write-Host "Processing downloaded file: $opencv_exe" + + $retry+=1 + if (-not(Test-Path $opencv_exe)) + { + if ($retry -gt $max_retry_attempts) + { + Write-Host "ERROR: Retries exhausted!" + exit 1 + } + Write-Host "Retry in $delay_in_seconds seconds ..." + Start-Sleep -s $delay_in_seconds + } +}While (-not (Test-Path $opencv_exe)) Start-Process -Wait $opencv_exe -ArgumentList -o"C:\",-y Write-Host "OpenCV installed." diff --git a/src/calibration/calibration.c b/src/calibration/calibration.c index f046ef201..bf5f34a49 100644 --- a/src/calibration/calibration.c +++ b/src/calibration/calibration.c @@ -7,6 +7,7 @@ // Dependent libraries #include #include +#include //cJSON.h need this set correctly. // System dependencies #include @@ -636,7 +637,28 @@ k4a_result_t calibration_create_from_raw(char *raw_calibration, k4a_result_t result = K4A_RESULT_SUCCEEDED; - if (depth_calibration != NULL) +#ifdef _WIN32 + int previous_thread_locale = -1; + if (K4A_SUCCEEDED(result)) + { + previous_thread_locale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); + result = K4A_RESULT_FROM_BOOL(previous_thread_locale == _ENABLE_PER_THREAD_LOCALE || + previous_thread_locale == _DISABLE_PER_THREAD_LOCALE); + } + + if (K4A_SUCCEEDED(result)) + { + result = K4A_RESULT_FROM_BOOL(setlocale(LC_ALL, "C") != NULL); + } + +#else // NOT _WIN32 + + locale_t thread_locale = newlocale(LC_ALL_MASK, "C", (locale_t)0); + locale_t previous_locale = uselocale(thread_locale); + +#endif + + if (K4A_SUCCEEDED(result) && depth_calibration != NULL) { result = get_camera_calibration(raw_calibration, depth_calibration, "CALIBRATION_CameraLocationD0"); } @@ -658,6 +680,29 @@ k4a_result_t calibration_create_from_raw(char *raw_calibration, "CALIBRATION_InertialSensorType_Accelerometer"); } +#ifdef _WIN32 + if (previous_thread_locale == _ENABLE_PER_THREAD_LOCALE || previous_thread_locale == _DISABLE_PER_THREAD_LOCALE) + { + if (K4A_FAILED(K4A_RESULT_FROM_BOOL(_configthreadlocale(previous_thread_locale) != -1))) + { + // Only set result to failed, don't let this call succeed and clear a failure that might have happened + // already. + result = K4A_RESULT_FAILED; + } + } +#else // NOT _WIN32 + if ((previous_locale != NULL) && (K4A_FAILED(K4A_RESULT_FROM_BOOL(uselocale(previous_locale) != NULL)))) + { + // Only set result to failed, don't let this call succeed and clear a failure that might have happened + // already. + result = K4A_RESULT_FAILED; + } + if (thread_locale) + { + freelocale(thread_locale); + } +#endif + return result; } diff --git a/src/capturesync/capturesync.c b/src/capturesync/capturesync.c index 26ca11954..0c46b9139 100644 --- a/src/capturesync/capturesync.c +++ b/src/capturesync/capturesync.c @@ -59,8 +59,6 @@ K4A_DECLARE_CONTEXT(capturesync_t, capturesync_context_t); #define DEPTH_CAPTURE (false) #define COLOR_CAPTURE (true) -#define MICRO_SECONDS(seconds) (seconds * 1000000) - /** * This function is responsible for updating the information in either capturesync_context_t->depth_ir or in * capturesync_context_t->color. capturesync_context_t holds the capture, image, and ts for the sample we are currenly @@ -488,40 +486,31 @@ k4a_result_t capturesync_start(capturesync_t capturesync_handle, const k4a_devic RETURN_VALUE_IF_HANDLE_INVALID(K4A_RESULT_FAILED, capturesync_t, capturesync_handle); RETURN_VALUE_IF_ARG(K4A_RESULT_FAILED, config == NULL); capturesync_context_t *sync = capturesync_t_get_context(capturesync_handle); - k4a_result_t result = K4A_RESULT_SUCCEEDED; // Reset frames to drop sync->waiting_for_clean_depth_ts = true; sync->synchronized_images_only = config->synchronized_images_only; - uint32_t camera_fps = k4a_convert_fps_to_uint(config->camera_fps); + sync->fps_period = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(config->camera_fps)); + sync->fps_1_quarter_period = sync->fps_period / 4; + sync->depth_delay_off_color_usec = config->depth_delay_off_color_usec; + sync->sync_captures = true; + sync->depth_captures_dropped = 0; - result = K4A_RESULT_FROM_BOOL(camera_fps > 0); - if (K4A_SUCCEEDED(result)) + if (config->color_resolution == K4A_COLOR_RESOLUTION_OFF || config->depth_mode == K4A_DEPTH_MODE_OFF) { - sync->fps_period = MICRO_SECONDS(1) / camera_fps; - sync->fps_1_quarter_period = sync->fps_period / 4; - sync->depth_delay_off_color_usec = config->depth_delay_off_color_usec; - sync->sync_captures = true; - - if (config->color_resolution == K4A_COLOR_RESOLUTION_OFF || config->depth_mode == K4A_DEPTH_MODE_OFF) - { - // Only 1 sensor is running, disable synchronization - sync->sync_captures = false; - } + // Only 1 sensor is running, disable synchronization + sync->sync_captures = false; } - if (K4A_SUCCEEDED(result)) - { - queue_enable(sync->color.queue); - queue_enable(sync->depth_ir.queue); - queue_enable(sync->sync_queue); + queue_enable(sync->color.queue); + queue_enable(sync->depth_ir.queue); + queue_enable(sync->sync_queue); - // Not taking the lock as we don't need to syncronize this on start - sync->running = true; - } + // Not taking the lock as we don't need to synchronize this on start + sync->running = true; - return result; + return K4A_RESULT_SUCCEEDED; } void capturesync_stop(capturesync_t capturesync_handle) diff --git a/src/color/mfcamerareader.cpp b/src/color/mfcamerareader.cpp index 7d0a98e35..7183621c5 100644 --- a/src/color/mfcamerareader.cpp +++ b/src/color/mfcamerareader.cpp @@ -484,6 +484,10 @@ k4a_result_t CMFCameraReader::Start(const UINT32 width, LOG_WARNING("Start request in started state", 0); } + if (FAILED(hr)) + { + LOG_ERROR("Failing with HRESULT:%08X", hr); + } return k4aResultFromHRESULT(hr); } @@ -511,32 +515,32 @@ void CMFCameraReader::Stop() if (SUCCEEDED(hr = m_spSourceReader->Flush((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM))) { m_flushing = true; + + lock.Unlock(); // Wait without lock + do + { + // Wait until async operations are terminated for 10 sec + switch (WaitForSingleObject(m_hStreamFlushed, 10000)) + { + case WAIT_OBJECT_0: + // Flushing completed + return; + case WAIT_TIMEOUT: + LOG_ERROR("Timeout waiting for m_hStreamFlushed"); + break; + case WAIT_FAILED: + LOG_ERROR("WaitForSingleObject on m_hStreamFlushed failed (%d)", GetLastError()); + assert(false); + break; + default: + break; + } + } while (1); } else { LOG_ERROR("Failed to request flush for stop: 0x%08x", hr); } - - lock.Unlock(); // Wait without lock - do - { - // Wait until async operations are terminated for 10 sec - switch (WaitForSingleObject(m_hStreamFlushed, 10000)) - { - case WAIT_OBJECT_0: - // Flushing completed - return; - case WAIT_TIMEOUT: - LOG_ERROR("Timeout waiting for m_hStreamFlushed"); - break; - case WAIT_FAILED: - LOG_ERROR("WaitForSingleObject on m_hStreamFlushed failed (%d)", GetLastError()); - assert(false); - break; - default: - break; - } - } while (1); } } @@ -691,6 +695,10 @@ k4a_result_t CMFCameraReader::GetCameraControlCapabilities(const k4a_color_contr capabilities->valid = true; } + if (FAILED(hr)) + { + LOG_ERROR("Failing command %u with HRESULT:%08X", command, hr); + } return k4aResultFromHRESULT(hr); } @@ -819,6 +827,10 @@ k4a_result_t CMFCameraReader::GetCameraControl(const k4a_color_control_command_t *pValue = (int32_t)propertyValue; } + if (FAILED(hr)) + { + LOG_ERROR("Failing command %u with HRESULT:%08X", command, hr); + } return k4aResultFromHRESULT(hr); } @@ -925,9 +937,14 @@ k4a_result_t CMFCameraReader::SetCameraControl(const k4a_color_control_command_t } break; default: + LOG_ERROR("Failing, unknown command %u", command); return K4A_RESULT_FAILED; } + if (FAILED(hr)) + { + LOG_ERROR("Failing command %u with HRESULT:%08X", command, hr); + } return k4aResultFromHRESULT(hr); } diff --git a/src/color/uvc_camerareader.cpp b/src/color/uvc_camerareader.cpp index a474a470c..f13a41add 100644 --- a/src/color/uvc_camerareader.cpp +++ b/src/color/uvc_camerareader.cpp @@ -120,6 +120,16 @@ k4a_result_t UVCCameraReader::Start(const uint32_t width, m_output_image_format = imageFormat; m_input_image_format = K4A_IMAGE_FORMAT_COLOR_MJPG; + if (m_decoder == nullptr) + { + m_decoder = tjInitDecompress(); + if (m_decoder == nullptr) + { + LOG_ERROR("MJPEG decoder initialization failed\n", 0); + return K4A_RESULT_FAILED; + } + } + frameFormat = UVC_COLOR_FORMAT_MJPEG; break; default: @@ -1121,6 +1131,7 @@ void UVCCameraReader::Callback(uvc_frame_t *frame) uint32_t iso_speed = 0; uint32_t white_balance = 0; bool decodeMJPEG = false; + bool drop_image = false; // Parse metadata size_t bufferLeft = (size_t)frame->metadata_bytes; @@ -1204,6 +1215,10 @@ void UVCCameraReader::Callback(uvc_frame_t *frame) { // Decode MJPG into BRGA32 result = DecodeMJPEGtoBGRA32((uint8_t *)frame->data, frame->data_bytes, buffer, buffer_size); + if (K4A_FAILED(result)) + { + drop_image = true; + } } else { @@ -1253,8 +1268,11 @@ void UVCCameraReader::Callback(uvc_frame_t *frame) capture_set_color_image(capture, image); } - // Calback to color - m_pCallback(result, capture, m_pCallbackContext); + if (!drop_image) + { + // Calback to color + m_pCallback(result, capture, m_pCallbackContext); + } if (image) { @@ -1275,16 +1293,6 @@ UVCCameraReader::DecodeMJPEGtoBGRA32(uint8_t *in_buf, const size_t in_size, uint { RETURN_VALUE_IF_ARG(K4A_RESULT_FAILED, m_width_pixels * m_height_pixels * 4 > out_size); - if (m_decoder == nullptr) - { - m_decoder = tjInitDecompress(); - if (m_decoder == nullptr) - { - LOG_ERROR("MJPEG decoder initialization failed\n", 0); - return K4A_RESULT_FAILED; - } - } - int decompressStatus = tjDecompress2(m_decoder, in_buf, (unsigned long)in_size, @@ -1297,7 +1305,10 @@ UVCCameraReader::DecodeMJPEGtoBGRA32(uint8_t *in_buf, const size_t in_size, uint if (decompressStatus != 0) { - LOG_ERROR("MJPEG decode failed: %d", decompressStatus); + // This can happen when the host PC is not reading data off the camera fast enough. We also have the option to + // move the use of libjpeg-turbo to a more recent version and use tjGetErrorCode() to get a better understanding + // of the status returned. + LOG_WARNING("MJPEG decode failed, dropping image: %d", decompressStatus); return K4A_RESULT_FAILED; } diff --git a/src/csharp/Examples/Recording/Microsoft.Azure.Kinect.Sensor.Examples.Recording.csproj b/src/csharp/Examples/Recording/Microsoft.Azure.Kinect.Sensor.Examples.Recording.csproj new file mode 100644 index 000000000..27d7bbb66 --- /dev/null +++ b/src/csharp/Examples/Recording/Microsoft.Azure.Kinect.Sensor.Examples.Recording.csproj @@ -0,0 +1,52 @@ + + + + + Exe + netcoreapp2.1 + dotnetrecording + + x64;x86 + false + ..\..\AzureKinectSensorSDK.ruleset + $(BaseOutputPath)\$(AssemblyName)\ + + + + + + + + + + stylecop.json + + + + + + k4a.dll + PreserveNewest + + + k4a.pdb + PreserveNewest + + + k4arecord.dll + PreserveNewest + + + k4arecord.pdb + PreserveNewest + + + + + depthengine_2_0.dll + PreserveNewest + + + + diff --git a/src/csharp/Examples/Recording/Program.cs b/src/csharp/Examples/Recording/Program.cs new file mode 100644 index 000000000..f7d4898ed --- /dev/null +++ b/src/csharp/Examples/Recording/Program.cs @@ -0,0 +1,102 @@ +using System; +using System.Linq.Expressions; +using Microsoft.Azure.Kinect.Sensor; +using Microsoft.Azure.Kinect.Sensor.Record; + +namespace Recording +{ + class Program + { + static void Main(string[] args) + { + int frame = 0; + + if (args.Length < 1) + { + Console.WriteLine("Please specify the name of an .mkv output file."); + return; + } + + string path = args[0]; + + try + { + Console.WriteLine($"Recording from device to \"{path}\"."); + + DeviceConfiguration configuration = new DeviceConfiguration() + { + CameraFPS = FPS.FPS30, + ColorFormat = ImageFormat.ColorMJPG, + ColorResolution = ColorResolution.R720p, + DepthMode = DepthMode.NFOV_2x2Binned, + SynchronizedImagesOnly = true + }; + using (Device device = Device.Open()) + using (Recorder recorder = Recorder.Create(path, device, configuration)) + { + + device.StartCameras(configuration); + device.StartImu(); + + recorder.AddImuTrack(); + recorder.WriteHeader(); + + for (frame = 0; frame < 100; frame++) + { + using (Capture capture = device.GetCapture()) + { + recorder.WriteCapture(capture); + Console.WriteLine($"Wrote capture ({capture.Color.DeviceTimestamp})"); + try + { + while (true) + { + // Throws TimeoutException when Imu sample is not available + ImuSample sample = device.GetImuSample(TimeSpan.Zero); + + recorder.WriteImuSample(sample); + Console.WriteLine($"Wrote imu ({sample.AccelerometerTimestamp})"); + } + } + catch (TimeoutException) + { + + } + } + } + } + + Console.WriteLine($"Wrote {frame} frames to output.mkv"); + + using (Playback playback = Playback.Open(@"output.mkv")) + { + Console.WriteLine($"Tracks = {playback.TrackCount}"); + Console.WriteLine($"RecordingLength = {playback.RecordingLength}"); + + for (int i = 0; i < playback.TrackCount; i++) + { + string name = playback.GetTrackName(i); + string codecId = playback.GetTrackCodecId(name); + + Console.WriteLine($" Track {i}: {name} ({codecId}) (builtin={playback.GetTrackIsBuiltin(name)})"); + } + Capture capture; + while (null != (capture = playback.GetNextCapture())) + { + Console.WriteLine($"Color timestamp: {capture.Color.DeviceTimestamp} Depth timestamp: {capture.Depth.DeviceTimestamp}"); + } + } + + } catch (AzureKinectException exception) + { + Console.WriteLine(exception.ToString()); + Console.WriteLine(); + Console.WriteLine("Azure Kinect log messages:"); + foreach (LogMessage m in exception.LogMessages) + { + Console.WriteLine(m.ToString()); + } + } + } + } +} diff --git a/src/csharp/Examples/WPF/Microsoft.Azure.Kinect.Sensor.Examples.WPFViewer.csproj b/src/csharp/Examples/WPF/Microsoft.Azure.Kinect.Sensor.Examples.WPFViewer.csproj index c7c7cc0a0..f9723e9d6 100644 --- a/src/csharp/Examples/WPF/Microsoft.Azure.Kinect.Sensor.Examples.WPFViewer.csproj +++ b/src/csharp/Examples/WPF/Microsoft.Azure.Kinect.Sensor.Examples.WPFViewer.csproj @@ -159,7 +159,9 @@ k4a.pdb PreserveNewest - + + depthengine_2_0.dll PreserveNewest diff --git a/src/csharp/Examples/WinForms/Microsoft.Azure.Kinect.Sensor.Examples.WinForms.csproj b/src/csharp/Examples/WinForms/Microsoft.Azure.Kinect.Sensor.Examples.WinForms.csproj index a8882acf2..f52258d7d 100644 --- a/src/csharp/Examples/WinForms/Microsoft.Azure.Kinect.Sensor.Examples.WinForms.csproj +++ b/src/csharp/Examples/WinForms/Microsoft.Azure.Kinect.Sensor.Examples.WinForms.csproj @@ -148,7 +148,9 @@ k4a.pdb PreserveNewest - + + depthengine_2_0.dll PreserveNewest diff --git a/src/csharp/K4a.sln b/src/csharp/K4a.sln index a2cb9c758..5c3f2e0e3 100644 --- a/src/csharp/K4a.sln +++ b/src/csharp/K4a.sln @@ -40,86 +40,154 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{D9 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{94E07BE5-5E5C-488B-A5CD-0D7D9EBCC725}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Kinect.Sensor.Examples.Recording", "Examples\Recording\Microsoft.Azure.Kinect.Sensor.Examples.Recording.csproj", "{568BBB67-4EE0-4A0D-AD69-5D10386E2D40}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Kinect.Sensor.Record", "Record\Microsoft.Azure.Kinect.Sensor.Record.csproj", "{71EAC57F-4023-4B45-8F9E-4A7C05A6BDB3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Kinect.Sensor.Record.UnitTests", "Tests\Record.UnitTests\Microsoft.Azure.Kinect.Sensor.Record.UnitTests.csproj", "{4CAEC910-CEC0-41CD-8E47-AF20F5570203}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {847B31D5-C253-4766-BF81-032F4670589D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {847B31D5-C253-4766-BF81-032F4670589D}.Debug|Any CPU.Build.0 = Debug|Any CPU {847B31D5-C253-4766-BF81-032F4670589D}.Debug|x64.ActiveCfg = Debug|Any CPU {847B31D5-C253-4766-BF81-032F4670589D}.Debug|x64.Build.0 = Debug|Any CPU {847B31D5-C253-4766-BF81-032F4670589D}.Debug|x86.ActiveCfg = Debug|Any CPU {847B31D5-C253-4766-BF81-032F4670589D}.Debug|x86.Build.0 = Debug|Any CPU + {847B31D5-C253-4766-BF81-032F4670589D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {847B31D5-C253-4766-BF81-032F4670589D}.Release|Any CPU.Build.0 = Release|Any CPU {847B31D5-C253-4766-BF81-032F4670589D}.Release|x64.ActiveCfg = Release|Any CPU {847B31D5-C253-4766-BF81-032F4670589D}.Release|x64.Build.0 = Release|Any CPU {847B31D5-C253-4766-BF81-032F4670589D}.Release|x86.ActiveCfg = Release|Any CPU {847B31D5-C253-4766-BF81-032F4670589D}.Release|x86.Build.0 = Release|Any CPU + {4762DD42-3CF3-4742-9AEA-5D39781FD2A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4762DD42-3CF3-4742-9AEA-5D39781FD2A6}.Debug|Any CPU.Build.0 = Debug|Any CPU {4762DD42-3CF3-4742-9AEA-5D39781FD2A6}.Debug|x64.ActiveCfg = Debug|Any CPU {4762DD42-3CF3-4742-9AEA-5D39781FD2A6}.Debug|x64.Build.0 = Debug|Any CPU {4762DD42-3CF3-4742-9AEA-5D39781FD2A6}.Debug|x86.ActiveCfg = Debug|Any CPU {4762DD42-3CF3-4742-9AEA-5D39781FD2A6}.Debug|x86.Build.0 = Debug|Any CPU + {4762DD42-3CF3-4742-9AEA-5D39781FD2A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4762DD42-3CF3-4742-9AEA-5D39781FD2A6}.Release|Any CPU.Build.0 = Release|Any CPU {4762DD42-3CF3-4742-9AEA-5D39781FD2A6}.Release|x64.ActiveCfg = Release|Any CPU {4762DD42-3CF3-4742-9AEA-5D39781FD2A6}.Release|x64.Build.0 = Release|Any CPU {4762DD42-3CF3-4742-9AEA-5D39781FD2A6}.Release|x86.ActiveCfg = Release|Any CPU {4762DD42-3CF3-4742-9AEA-5D39781FD2A6}.Release|x86.Build.0 = Release|Any CPU + {618E28C5-0624-463F-9ADB-040BA5DAED68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {618E28C5-0624-463F-9ADB-040BA5DAED68}.Debug|Any CPU.Build.0 = Debug|Any CPU {618E28C5-0624-463F-9ADB-040BA5DAED68}.Debug|x64.ActiveCfg = Debug|Any CPU {618E28C5-0624-463F-9ADB-040BA5DAED68}.Debug|x64.Build.0 = Debug|Any CPU {618E28C5-0624-463F-9ADB-040BA5DAED68}.Debug|x86.ActiveCfg = Debug|Any CPU {618E28C5-0624-463F-9ADB-040BA5DAED68}.Debug|x86.Build.0 = Debug|Any CPU + {618E28C5-0624-463F-9ADB-040BA5DAED68}.Release|Any CPU.ActiveCfg = Release|Any CPU + {618E28C5-0624-463F-9ADB-040BA5DAED68}.Release|Any CPU.Build.0 = Release|Any CPU {618E28C5-0624-463F-9ADB-040BA5DAED68}.Release|x64.ActiveCfg = Release|Any CPU {618E28C5-0624-463F-9ADB-040BA5DAED68}.Release|x64.Build.0 = Release|Any CPU {618E28C5-0624-463F-9ADB-040BA5DAED68}.Release|x86.ActiveCfg = Release|Any CPU {618E28C5-0624-463F-9ADB-040BA5DAED68}.Release|x86.Build.0 = Release|Any CPU + {CCD99E9D-1EE2-41F5-AD3F-4110A466A9A4}.Debug|Any CPU.ActiveCfg = Debug|x86 {CCD99E9D-1EE2-41F5-AD3F-4110A466A9A4}.Debug|x64.ActiveCfg = Debug|x64 {CCD99E9D-1EE2-41F5-AD3F-4110A466A9A4}.Debug|x64.Build.0 = Debug|x64 {CCD99E9D-1EE2-41F5-AD3F-4110A466A9A4}.Debug|x86.ActiveCfg = Debug|x86 {CCD99E9D-1EE2-41F5-AD3F-4110A466A9A4}.Debug|x86.Build.0 = Debug|x86 + {CCD99E9D-1EE2-41F5-AD3F-4110A466A9A4}.Release|Any CPU.ActiveCfg = Release|x86 {CCD99E9D-1EE2-41F5-AD3F-4110A466A9A4}.Release|x64.ActiveCfg = Release|x64 {CCD99E9D-1EE2-41F5-AD3F-4110A466A9A4}.Release|x64.Build.0 = Release|x64 {CCD99E9D-1EE2-41F5-AD3F-4110A466A9A4}.Release|x86.ActiveCfg = Release|x86 {CCD99E9D-1EE2-41F5-AD3F-4110A466A9A4}.Release|x86.Build.0 = Release|x86 + {8A14FB66-07CD-4E4C-A533-89DE0AFF4FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A14FB66-07CD-4E4C-A533-89DE0AFF4FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU {8A14FB66-07CD-4E4C-A533-89DE0AFF4FCB}.Debug|x64.ActiveCfg = Debug|x64 {8A14FB66-07CD-4E4C-A533-89DE0AFF4FCB}.Debug|x64.Build.0 = Debug|x64 {8A14FB66-07CD-4E4C-A533-89DE0AFF4FCB}.Debug|x86.ActiveCfg = Debug|x86 {8A14FB66-07CD-4E4C-A533-89DE0AFF4FCB}.Debug|x86.Build.0 = Debug|x86 + {8A14FB66-07CD-4E4C-A533-89DE0AFF4FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A14FB66-07CD-4E4C-A533-89DE0AFF4FCB}.Release|Any CPU.Build.0 = Release|Any CPU {8A14FB66-07CD-4E4C-A533-89DE0AFF4FCB}.Release|x64.ActiveCfg = Release|x64 {8A14FB66-07CD-4E4C-A533-89DE0AFF4FCB}.Release|x64.Build.0 = Release|x64 {8A14FB66-07CD-4E4C-A533-89DE0AFF4FCB}.Release|x86.ActiveCfg = Release|x86 {8A14FB66-07CD-4E4C-A533-89DE0AFF4FCB}.Release|x86.Build.0 = Release|x86 + {6D4EC05A-3A81-4B92-8881-96F499F5986B}.Debug|Any CPU.ActiveCfg = Debug|x86 {6D4EC05A-3A81-4B92-8881-96F499F5986B}.Debug|x64.ActiveCfg = Debug|x64 {6D4EC05A-3A81-4B92-8881-96F499F5986B}.Debug|x64.Build.0 = Debug|x64 {6D4EC05A-3A81-4B92-8881-96F499F5986B}.Debug|x86.ActiveCfg = Debug|x86 {6D4EC05A-3A81-4B92-8881-96F499F5986B}.Debug|x86.Build.0 = Debug|x86 + {6D4EC05A-3A81-4B92-8881-96F499F5986B}.Release|Any CPU.ActiveCfg = Release|x86 {6D4EC05A-3A81-4B92-8881-96F499F5986B}.Release|x64.ActiveCfg = Release|x64 {6D4EC05A-3A81-4B92-8881-96F499F5986B}.Release|x64.Build.0 = Release|x64 {6D4EC05A-3A81-4B92-8881-96F499F5986B}.Release|x86.ActiveCfg = Release|x86 {6D4EC05A-3A81-4B92-8881-96F499F5986B}.Release|x86.Build.0 = Release|x86 + {41510BD0-7F25-470B-A1DC-12E1DB1AB3B7}.Debug|Any CPU.ActiveCfg = Debug|x86 {41510BD0-7F25-470B-A1DC-12E1DB1AB3B7}.Debug|x64.ActiveCfg = Debug|x64 {41510BD0-7F25-470B-A1DC-12E1DB1AB3B7}.Debug|x64.Build.0 = Debug|x64 {41510BD0-7F25-470B-A1DC-12E1DB1AB3B7}.Debug|x86.ActiveCfg = Debug|x86 {41510BD0-7F25-470B-A1DC-12E1DB1AB3B7}.Debug|x86.Build.0 = Debug|x86 + {41510BD0-7F25-470B-A1DC-12E1DB1AB3B7}.Release|Any CPU.ActiveCfg = Release|x86 {41510BD0-7F25-470B-A1DC-12E1DB1AB3B7}.Release|x64.ActiveCfg = Release|x64 {41510BD0-7F25-470B-A1DC-12E1DB1AB3B7}.Release|x64.Build.0 = Release|x64 {41510BD0-7F25-470B-A1DC-12E1DB1AB3B7}.Release|x86.ActiveCfg = Release|x86 {41510BD0-7F25-470B-A1DC-12E1DB1AB3B7}.Release|x86.Build.0 = Release|x86 + {FCD1E629-1E96-4BDD-A247-35B50F31137A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCD1E629-1E96-4BDD-A247-35B50F31137A}.Debug|Any CPU.Build.0 = Debug|Any CPU {FCD1E629-1E96-4BDD-A247-35B50F31137A}.Debug|x64.ActiveCfg = Debug|Any CPU {FCD1E629-1E96-4BDD-A247-35B50F31137A}.Debug|x64.Build.0 = Debug|Any CPU {FCD1E629-1E96-4BDD-A247-35B50F31137A}.Debug|x86.ActiveCfg = Debug|Any CPU {FCD1E629-1E96-4BDD-A247-35B50F31137A}.Debug|x86.Build.0 = Debug|Any CPU + {FCD1E629-1E96-4BDD-A247-35B50F31137A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCD1E629-1E96-4BDD-A247-35B50F31137A}.Release|Any CPU.Build.0 = Release|Any CPU {FCD1E629-1E96-4BDD-A247-35B50F31137A}.Release|x64.ActiveCfg = Release|Any CPU {FCD1E629-1E96-4BDD-A247-35B50F31137A}.Release|x64.Build.0 = Release|Any CPU {FCD1E629-1E96-4BDD-A247-35B50F31137A}.Release|x86.ActiveCfg = Release|Any CPU {FCD1E629-1E96-4BDD-A247-35B50F31137A}.Release|x86.Build.0 = Release|Any CPU + {E1B3CC41-BC1C-47B7-A6A6-AA50E6994C05}.Debug|Any CPU.ActiveCfg = Debug|x86 {E1B3CC41-BC1C-47B7-A6A6-AA50E6994C05}.Debug|x64.ActiveCfg = Debug|x64 {E1B3CC41-BC1C-47B7-A6A6-AA50E6994C05}.Debug|x64.Build.0 = Debug|x64 {E1B3CC41-BC1C-47B7-A6A6-AA50E6994C05}.Debug|x86.ActiveCfg = Debug|x86 {E1B3CC41-BC1C-47B7-A6A6-AA50E6994C05}.Debug|x86.Build.0 = Debug|x86 + {E1B3CC41-BC1C-47B7-A6A6-AA50E6994C05}.Release|Any CPU.ActiveCfg = Release|x86 {E1B3CC41-BC1C-47B7-A6A6-AA50E6994C05}.Release|x64.ActiveCfg = Release|x64 {E1B3CC41-BC1C-47B7-A6A6-AA50E6994C05}.Release|x64.Build.0 = Release|x64 {E1B3CC41-BC1C-47B7-A6A6-AA50E6994C05}.Release|x86.ActiveCfg = Release|x86 {E1B3CC41-BC1C-47B7-A6A6-AA50E6994C05}.Release|x86.Build.0 = Release|x86 + {568BBB67-4EE0-4A0D-AD69-5D10386E2D40}.Debug|Any CPU.ActiveCfg = Debug|x86 + {568BBB67-4EE0-4A0D-AD69-5D10386E2D40}.Debug|x64.ActiveCfg = Debug|x64 + {568BBB67-4EE0-4A0D-AD69-5D10386E2D40}.Debug|x64.Build.0 = Debug|x64 + {568BBB67-4EE0-4A0D-AD69-5D10386E2D40}.Debug|x86.ActiveCfg = Debug|x86 + {568BBB67-4EE0-4A0D-AD69-5D10386E2D40}.Debug|x86.Build.0 = Debug|x86 + {568BBB67-4EE0-4A0D-AD69-5D10386E2D40}.Release|Any CPU.ActiveCfg = Release|x86 + {568BBB67-4EE0-4A0D-AD69-5D10386E2D40}.Release|x64.ActiveCfg = Release|x64 + {568BBB67-4EE0-4A0D-AD69-5D10386E2D40}.Release|x64.Build.0 = Release|x64 + {568BBB67-4EE0-4A0D-AD69-5D10386E2D40}.Release|x86.ActiveCfg = Release|x86 + {568BBB67-4EE0-4A0D-AD69-5D10386E2D40}.Release|x86.Build.0 = Release|x86 + {71EAC57F-4023-4B45-8F9E-4A7C05A6BDB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71EAC57F-4023-4B45-8F9E-4A7C05A6BDB3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71EAC57F-4023-4B45-8F9E-4A7C05A6BDB3}.Debug|x64.ActiveCfg = Debug|x64 + {71EAC57F-4023-4B45-8F9E-4A7C05A6BDB3}.Debug|x64.Build.0 = Debug|x64 + {71EAC57F-4023-4B45-8F9E-4A7C05A6BDB3}.Debug|x86.ActiveCfg = Debug|Any CPU + {71EAC57F-4023-4B45-8F9E-4A7C05A6BDB3}.Debug|x86.Build.0 = Debug|Any CPU + {71EAC57F-4023-4B45-8F9E-4A7C05A6BDB3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71EAC57F-4023-4B45-8F9E-4A7C05A6BDB3}.Release|Any CPU.Build.0 = Release|Any CPU + {71EAC57F-4023-4B45-8F9E-4A7C05A6BDB3}.Release|x64.ActiveCfg = Release|Any CPU + {71EAC57F-4023-4B45-8F9E-4A7C05A6BDB3}.Release|x64.Build.0 = Release|Any CPU + {71EAC57F-4023-4B45-8F9E-4A7C05A6BDB3}.Release|x86.ActiveCfg = Release|Any CPU + {71EAC57F-4023-4B45-8F9E-4A7C05A6BDB3}.Release|x86.Build.0 = Release|Any CPU + {4CAEC910-CEC0-41CD-8E47-AF20F5570203}.Debug|Any CPU.ActiveCfg = Debug|x86 + {4CAEC910-CEC0-41CD-8E47-AF20F5570203}.Debug|x64.ActiveCfg = Debug|x64 + {4CAEC910-CEC0-41CD-8E47-AF20F5570203}.Debug|x64.Build.0 = Debug|x64 + {4CAEC910-CEC0-41CD-8E47-AF20F5570203}.Debug|x86.ActiveCfg = Debug|x86 + {4CAEC910-CEC0-41CD-8E47-AF20F5570203}.Debug|x86.Build.0 = Debug|x86 + {4CAEC910-CEC0-41CD-8E47-AF20F5570203}.Release|Any CPU.ActiveCfg = Release|x86 + {4CAEC910-CEC0-41CD-8E47-AF20F5570203}.Release|x64.ActiveCfg = Release|x64 + {4CAEC910-CEC0-41CD-8E47-AF20F5570203}.Release|x64.Build.0 = Release|x64 + {4CAEC910-CEC0-41CD-8E47-AF20F5570203}.Release|x86.ActiveCfg = Release|x86 + {4CAEC910-CEC0-41CD-8E47-AF20F5570203}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -134,6 +202,8 @@ Global {5C3A9F92-56B4-4A7B-86EC-BACBE07C5AAE} = {21E41070-E020-49B0-9976-54F92B2251DD} {FCD1E629-1E96-4BDD-A247-35B50F31137A} = {5C3A9F92-56B4-4A7B-86EC-BACBE07C5AAE} {E1B3CC41-BC1C-47B7-A6A6-AA50E6994C05} = {5C3A9F92-56B4-4A7B-86EC-BACBE07C5AAE} + {568BBB67-4EE0-4A0D-AD69-5D10386E2D40} = {D946946D-56B5-4F64-B4FC-5C79F15295C4} + {4CAEC910-CEC0-41CD-8E47-AF20F5570203} = {21E41070-E020-49B0-9976-54F92B2251DD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9BC05C93-252F-4030-ACA6-41B4B54F9C86} diff --git a/src/csharp/Record/DataBlock.cs b/src/csharp/Record/DataBlock.cs new file mode 100644 index 000000000..a9c7e2cda --- /dev/null +++ b/src/csharp/Record/DataBlock.cs @@ -0,0 +1,115 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Buffers; +using System.Runtime.InteropServices; + +namespace Microsoft.Azure.Kinect.Sensor.Record +{ + /// + /// Represents a block of data from a custom recording track. + /// + public class DataBlock : IDisposable, IMemoryOwner + { + // The native handle for this data block. + private readonly NativeMethods.k4a_playback_data_block_t handle; + + // To detect redundant calls to Dispose + private bool disposedValue = false; + + private byte[] buffer = null; + + /// + /// Initializes a new instance of the class. + /// + /// Native handle to the data block. + internal DataBlock(NativeMethods.k4a_playback_data_block_t handle) + { + this.handle = handle; + } + + /// + /// Gets the memory with the custom data. + /// + public Memory Memory + { + get + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(DataBlock)); + } + + if (this.buffer == null) + { + ulong bufferSize = NativeMethods.k4a_playback_data_block_get_buffer_size(this.handle); + + this.buffer = new byte[bufferSize]; + + IntPtr bufferPtr = NativeMethods.k4a_playback_data_block_get_buffer(this.handle); + + if (bufferPtr != IntPtr.Zero) + { + Marshal.Copy(bufferPtr, this.buffer, 0, checked((int)bufferSize)); + } + else + { + this.buffer = null; + } + } + + return this.buffer; + } + } + } + + /// + /// Gets the device timestamp associated with the data. + /// + public TimeSpan DeviceTimestamp + { + get + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(DataBlock)); + } + + ulong timeStamp = NativeMethods.k4a_playback_data_block_get_device_timestamp_usec(this.handle); + + return TimeSpan.FromTicks(checked((long)timeStamp) * 10); + } + } + } + + /// + public void Dispose() + { + this.Dispose(true); + + GC.SuppressFinalize(this); + } + + /// + /// Handle the disposing of the object. + /// + /// true when called by Dispose(), false when called by the finalizer. + protected virtual void Dispose(bool disposing) + { + lock (this) + { + this.handle.Close(); + + this.disposedValue = true; + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectAddAttachmentException.cs b/src/csharp/Record/Exceptions/AzureKinectAddAttachmentException.cs new file mode 100644 index 000000000..787b7d55a --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectAddAttachmentException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when adding an attachment to a recording. + /// + [Serializable] + public class AzureKinectAddAttachmentException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectAddAttachmentException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectAddAttachmentException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectAddAttachmentException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectAddAttachmentException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectAddAttachmentException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectAddAttachmentException($"result = {result}", tracer.LogMessages); + } + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectAddCustomSubtitleTrackException.cs b/src/csharp/Record/Exceptions/AzureKinectAddCustomSubtitleTrackException.cs new file mode 100644 index 000000000..f4e449bdd --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectAddCustomSubtitleTrackException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when adding a custom subtitle track. + /// + [Serializable] + public class AzureKinectAddCustomSubtitleTrackException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectAddCustomSubtitleTrackException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectAddCustomSubtitleTrackException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectAddCustomSubtitleTrackException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectAddCustomSubtitleTrackException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectAddCustomSubtitleTrackException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectAddCustomSubtitleTrackException($"result = {result}", tracer.LogMessages); + } + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectAddCustomVideoTrackException.cs b/src/csharp/Record/Exceptions/AzureKinectAddCustomVideoTrackException.cs new file mode 100644 index 000000000..2d157d264 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectAddCustomVideoTrackException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when adding a custom video track. + /// + [Serializable] + public class AzureKinectAddCustomVideoTrackException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectAddCustomVideoTrackException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectAddCustomVideoTrackException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectAddCustomVideoTrackException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectAddCustomVideoTrackException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectAddCustomVideoTrackException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectAddCustomVideoTrackException($"result = {result}", tracer.LogMessages); + } + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectAddImuTrackException.cs b/src/csharp/Record/Exceptions/AzureKinectAddImuTrackException.cs new file mode 100644 index 000000000..5a58ff402 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectAddImuTrackException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when adding an IMU track to a recording. + /// + [Serializable] + public class AzureKinectAddImuTrackException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectAddImuTrackException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectAddImuTrackException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectAddImuTrackException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectAddImuTrackException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectAddImuTrackException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectAddImuTrackException($"result = {result}", tracer.LogMessages); + } + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectAddTagException.cs b/src/csharp/Record/Exceptions/AzureKinectAddTagException.cs new file mode 100644 index 000000000..10bb009ad --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectAddTagException.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when adding a tag to a recording. + /// + [Serializable] + public class AzureKinectAddTagException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectAddTagException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectAddTagException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectAddTagException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectAddTagException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectAddTagException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectAddTagException($"result = {result}", tracer.LogMessages); + } + } + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The tracer is that is capturing logging messages. + /// The result native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(LoggingTracer tracer, T result) + where T : System.Enum + { + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectAddTagException($"result = {result}", tracer.LogMessages); + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectCreateRecordingException.cs b/src/csharp/Record/Exceptions/AzureKinectCreateRecordingException.cs new file mode 100644 index 000000000..2acd0be5f --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectCreateRecordingException.cs @@ -0,0 +1,120 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when creating an Azure Kinect sensor recording. + /// + [Serializable] + public class AzureKinectCreateRecordingException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectCreateRecordingException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectCreateRecordingException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectCreateRecordingException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectCreateRecordingException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectCreateRecordingException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// File name of the create. + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(string fileName, Func function) + where T : System.Enum + { + + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectCreateRecordingException($"fileName = \"{fileName}\"\r\nresult = {result}", tracer.LogMessages); + } + } + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// File name of the create. + /// The tracer is that is capturing logging messages. + /// The result native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(string fileName, LoggingTracer tracer, T result) + where T : System.Enum + { + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectCreateRecordingException($"fileName = \"{fileName}\"\r\nresult = {result}", tracer.LogMessages); + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectFlushException.cs b/src/csharp/Record/Exceptions/AzureKinectFlushException.cs new file mode 100644 index 000000000..02f14be0f --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectFlushException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when an error occurs during flushing. + /// + [Serializable] + public class AzureKinectFlushException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectFlushException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectFlushException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectFlushException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectFlushException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectFlushException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectFlushException($"result = {result}", tracer.LogMessages); + } + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectGetCalibrationException.cs b/src/csharp/Record/Exceptions/AzureKinectGetCalibrationException.cs new file mode 100644 index 000000000..7186c30bc --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectGetCalibrationException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when getting calibration from a recording. + /// + [Serializable] + public class AzureKinectGetCalibrationException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectGetCalibrationException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectGetCalibrationException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectGetCalibrationException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectGetCalibrationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectGetCalibrationException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectGetCalibrationException($"result = {result}", tracer.LogMessages); + } + } + } + } +} \ No newline at end of file diff --git a/src/csharp/Record/Exceptions/AzureKinectGetCaptureException.cs b/src/csharp/Record/Exceptions/AzureKinectGetCaptureException.cs new file mode 100644 index 000000000..2065ffc65 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectGetCaptureException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when getting the next or previous capture. + /// + [Serializable] + public class AzureKinectGetCaptureException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectGetCaptureException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectGetCaptureException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectGetCaptureException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectGetCaptureException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectGetCaptureException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectGetCaptureException($"result = {result}", tracer.LogMessages); + } + } + } + } +} \ No newline at end of file diff --git a/src/csharp/Record/Exceptions/AzureKinectGetDataBlockException.cs b/src/csharp/Record/Exceptions/AzureKinectGetDataBlockException.cs new file mode 100644 index 000000000..c5b532785 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectGetDataBlockException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when getting a data block. + /// + [Serializable] + public class AzureKinectGetDataBlockException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectGetDataBlockException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectGetDataBlockException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectGetDataBlockException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectGetDataBlockException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectGetDataBlockException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectGetDataBlockException($"result = {result}", tracer.LogMessages); + } + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectGetImuSampleException.cs b/src/csharp/Record/Exceptions/AzureKinectGetImuSampleException.cs new file mode 100644 index 000000000..2a2655eab --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectGetImuSampleException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when reading an IMU sample. + /// + [Serializable] + public class AzureKinectGetImuSampleException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectGetImuSampleException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectGetImuSampleException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectGetImuSampleException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectGetImuSampleException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectGetImuSampleException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectGetImuSampleException($"result = {result}", tracer.LogMessages); + } + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectGetRawCalibrationException.cs b/src/csharp/Record/Exceptions/AzureKinectGetRawCalibrationException.cs new file mode 100644 index 000000000..155f9ec89 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectGetRawCalibrationException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when getting raw calibration from a recording. + /// + [Serializable] + public class AzureKinectGetRawCalibrationException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectGetRawCalibrationException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectGetRawCalibrationException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectGetRawCalibrationException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectGetRawCalibrationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectGetRawCalibrationException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectGetRawCalibrationException($"result = {result}", tracer.LogMessages); + } + } + } + } +} \ No newline at end of file diff --git a/src/csharp/Record/Exceptions/AzureKinectGetTagException.cs b/src/csharp/Record/Exceptions/AzureKinectGetTagException.cs new file mode 100644 index 000000000..67ff7d4b2 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectGetTagException.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when getting a tag value. + /// + [Serializable] + public class AzureKinectGetTagException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectGetTagException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectGetTagException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectGetTagException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectGetTagException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectGetTagException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectGetTagException($"result = {result}", tracer.LogMessages); + } + } + } + + /// + /// Throws an if the result of the function is not + /// a success. + /// + /// The tracer is that is capturing logging messages. + /// The result native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(LoggingTracer tracer, T result) + where T : System.Enum + { + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectGetTagException($"result = {result}", tracer.LogMessages); + } + } + } +} \ No newline at end of file diff --git a/src/csharp/Record/Exceptions/AzureKinectGetTrackCodecContextException.cs b/src/csharp/Record/Exceptions/AzureKinectGetTrackCodecContextException.cs new file mode 100644 index 000000000..d668e0b6d --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectGetTrackCodecContextException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when getting a codec context from a track. + /// + [Serializable] + public class AzureKinectGetTrackCodecContextException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectGetTrackCodecContextException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectGetTrackCodecContextException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectGetTrackCodecContextException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectGetTrackCodecContextException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectGetTrackCodecContextException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectGetTrackCodecContextException($"result = {result}", tracer.LogMessages); + } + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectGetTrackNameException.cs b/src/csharp/Record/Exceptions/AzureKinectGetTrackNameException.cs new file mode 100644 index 000000000..fa38f4d91 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectGetTrackNameException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when getting a track name. + /// + [Serializable] + public class AzureKinectGetTrackNameException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectGetTrackNameException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectGetTrackNameException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectGetTrackNameException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectGetTrackNameException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectGetTrackNameException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectGetTrackNameException($"result = {result}", tracer.LogMessages); + } + } + } + } +} \ No newline at end of file diff --git a/src/csharp/Record/Exceptions/AzureKinectOpenPlaybackException.cs b/src/csharp/Record/Exceptions/AzureKinectOpenPlaybackException.cs new file mode 100644 index 000000000..c6e5972e4 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectOpenPlaybackException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when opening a recording for playback. + /// + [Serializable] + public class AzureKinectOpenPlaybackException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectOpenPlaybackException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectOpenPlaybackException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectOpenPlaybackException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectOpenPlaybackException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectOpenPlaybackException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectOpenPlaybackException($"result = {result}", tracer.LogMessages); + } + } + } + } +} \ No newline at end of file diff --git a/src/csharp/Record/Exceptions/AzureKinectRecordException.cs b/src/csharp/Record/Exceptions/AzureKinectRecordException.cs new file mode 100644 index 000000000..5f7404e81 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectRecordException.cs @@ -0,0 +1,150 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors occuring during record or playback. + /// + [Serializable] + public abstract class AzureKinectRecordException : AzureKinectException + { + /// + /// Initializes a new instance of the class. + /// + protected AzureKinectRecordException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + protected AzureKinectRecordException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + protected AzureKinectRecordException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectRecordException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectRecordException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Determines if the result is a success result. + /// + /// The type of result. + /// The result to check if it is a success. + /// True if the result is a success;otherwise false. + internal static bool IsSuccess(T result) + where T : Enum + { + switch (result) + { + case NativeMethods.k4a_result_t k4a_result: + return IsSuccess(k4a_result); + + case NativeMethods.k4a_wait_result_t k4a_result: + return IsSuccess(k4a_result); + + case NativeMethods.k4a_buffer_result_t k4a_result: + return IsSuccess(k4a_result); + + case NativeMethods.k4a_stream_result_t k4a_result: + return IsSuccess(k4a_result); + + default: + throw new ArgumentException("Result is not of a recognized result type.", nameof(result)); + } + } + + /// + /// Determines if the is a success. + /// + /// The result to check if it is a success. + /// True if the result is a success;otherwise false. + internal static bool IsSuccess(NativeMethods.k4a_result_t result) + { + return result == NativeMethods.k4a_result_t.K4A_RESULT_SUCCEEDED; + } + + /// + /// Determines if the is a success. + /// + /// The result to check if it is a success. + /// True if the result is a success;otherwise false. + internal static bool IsSuccess(NativeMethods.k4a_wait_result_t result) + { + return result == NativeMethods.k4a_wait_result_t.K4A_WAIT_RESULT_SUCCEEDED; + } + + /// + /// Determines if the is a success. + /// + /// The result to check if it is a success. + /// True if the result is a success;otherwise false. + internal static bool IsSuccess(NativeMethods.k4a_buffer_result_t result) + { + return result == NativeMethods.k4a_buffer_result_t.K4A_BUFFER_RESULT_SUCCEEDED; + } + + /// + /// Determines if the is a success. + /// + /// The result to check if it is a success. + /// True if the result is a success;otherwise false. + internal static bool IsSuccess(NativeMethods.k4a_stream_result_t result) + { + return result == NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_SUCCEEDED; + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectSeekException.cs b/src/csharp/Record/Exceptions/AzureKinectSeekException.cs new file mode 100644 index 000000000..e866e22c0 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectSeekException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when seeking. + /// + [Serializable] + public class AzureKinectSeekException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectSeekException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectSeekException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectSeekException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectSeekException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectSeekException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectSeekException($"result = {result}", tracer.LogMessages); + } + } + } + } +} \ No newline at end of file diff --git a/src/csharp/Record/Exceptions/AzureKinectSetColorConversionException.cs b/src/csharp/Record/Exceptions/AzureKinectSetColorConversionException.cs new file mode 100644 index 000000000..a62bf0054 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectSetColorConversionException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when setting a color conversion on playback. + /// + [Serializable] + public class AzureKinectSetColorConversionException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectSetColorConversionException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectSetColorConversionException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectSetColorConversionException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectSetColorConversionException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectSetColorConversionException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectSetColorConversionException($"result = {result}", tracer.LogMessages); + } + } + } + } +} \ No newline at end of file diff --git a/src/csharp/Record/Exceptions/AzureKinectTrackGetVideoSettingsException.cs b/src/csharp/Record/Exceptions/AzureKinectTrackGetVideoSettingsException.cs new file mode 100644 index 000000000..ad92032c7 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectTrackGetVideoSettingsException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when an error occurs getting track video settings. + /// + [Serializable] + public class AzureKinectTrackGetVideoSettingsException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectTrackGetVideoSettingsException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectTrackGetVideoSettingsException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectTrackGetVideoSettingsException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectTrackGetVideoSettingsException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectTrackGetVideoSettingsException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectTrackGetVideoSettingsException("result = {result}", tracer.LogMessages); + } + } + } + } +} \ No newline at end of file diff --git a/src/csharp/Record/Exceptions/AzureKinectWriteCaptureException.cs b/src/csharp/Record/Exceptions/AzureKinectWriteCaptureException.cs new file mode 100644 index 000000000..cee354d23 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectWriteCaptureException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when writing a capture to a recording. + /// + [Serializable] + public class AzureKinectWriteCaptureException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectWriteCaptureException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectWriteCaptureException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectWriteCaptureException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectWriteCaptureException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectWriteCaptureException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectWriteCaptureException($"result = {result}", tracer.LogMessages); + } + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectWriteCustomTrackDataException.cs b/src/csharp/Record/Exceptions/AzureKinectWriteCustomTrackDataException.cs new file mode 100644 index 000000000..d29fbffbf --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectWriteCustomTrackDataException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when writing a custom track. + /// + [Serializable] + public class AzureKinectWriteCustomTrackDataException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectWriteCustomTrackDataException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectWriteCustomTrackDataException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectWriteCustomTrackDataException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectWriteCustomTrackDataException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectWriteCustomTrackDataException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectWriteCustomTrackDataException($"result = {result}", tracer.LogMessages); + } + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectWriteHeaderException.cs b/src/csharp/Record/Exceptions/AzureKinectWriteHeaderException.cs new file mode 100644 index 000000000..dedbd03d4 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectWriteHeaderException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when writing the header. + /// + [Serializable] + public class AzureKinectWriteHeaderException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectWriteHeaderException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectWriteHeaderException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectWriteHeaderException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectWriteHeaderException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectWriteHeaderException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectWriteHeaderException("result = {result}", tracer.LogMessages); + } + } + } + } +} diff --git a/src/csharp/Record/Exceptions/AzureKinectWriteImuSampleException.cs b/src/csharp/Record/Exceptions/AzureKinectWriteImuSampleException.cs new file mode 100644 index 000000000..3b7bdac38 --- /dev/null +++ b/src/csharp/Record/Exceptions/AzureKinectWriteImuSampleException.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.Azure.Kinect.Sensor.Record.Exceptions +{ + /// + /// Represents errors that occur when writing an IMU sample. + /// + [Serializable] + public class AzureKinectWriteImuSampleException : AzureKinectRecordException + { + /// + /// Initializes a new instance of the class. + /// + public AzureKinectWriteImuSampleException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public AzureKinectWriteImuSampleException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public AzureKinectWriteImuSampleException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class + /// with serialized data. + /// + /// + /// The that holds the serialized object data about the + /// exception being thrown. + /// + /// The System.Runtime.Serialization.StreamingContext that + /// contains contextual information about the source or destination. + /// + protected AzureKinectWriteImuSampleException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + /// + /// The log messages that happened during the function call that generated this error. + /// + protected AzureKinectWriteImuSampleException(string message, ICollection logMessages) + : base(message, logMessages) + { + } + + /// + /// Throws an if the result of the function + /// is not a success. + /// + /// The native function to call. + /// The type of result to expect from the function call. + internal static void ThrowIfNotSuccess(Func function) + where T : System.Enum + { + using (LoggingTracer tracer = new LoggingTracer()) + { + T result = function(); + if (!AzureKinectRecordException.IsSuccess(result)) + { + throw new AzureKinectWriteImuSampleException($"result = {result}", tracer.LogMessages); + } + } + } + } +} diff --git a/src/csharp/Record/Microsoft.Azure.Kinect.Sensor.Record.csproj b/src/csharp/Record/Microsoft.Azure.Kinect.Sensor.Record.csproj new file mode 100644 index 000000000..7a462b98f --- /dev/null +++ b/src/csharp/Record/Microsoft.Azure.Kinect.Sensor.Record.csproj @@ -0,0 +1,46 @@ + + + + + netstandard2.0 + latest + + false + ..\AzureKinectSensorSDK.ruleset + $(BaseOutputPath)\$(AssemblyName)\ + AnyCPU;x64 + + + + true + $(OutputPath)Microsoft.Azure.Kinect.Sensor.xml + true + ..\Microsoft.Azure.Kinect.Sensor.snk + + + + + stylecop.json + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/csharp/Record/NativeMethods.cs b/src/csharp/Record/NativeMethods.cs new file mode 100644 index 000000000..15f2f26ae --- /dev/null +++ b/src/csharp/Record/NativeMethods.cs @@ -0,0 +1,363 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.Azure.Kinect.Sensor.Record +{ +#pragma warning disable IDE1006 // Naming Styles +#pragma warning disable SA1600 // Elements should be documented +#pragma warning disable SA1602 // Enumeration items should be documented +#pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments + internal static class NativeMethods + { + private const CallingConvention k4aCallingConvention = CallingConvention.Cdecl; + + [UnmanagedFunctionPointer(k4aCallingConvention)] + public delegate void k4a_logging_message_cb_t(IntPtr context, LogLevel level, [MarshalAs(UnmanagedType.LPStr)] string file, int line, [MarshalAs(UnmanagedType.LPStr)] string message); + + public enum k4a_buffer_result_t + { + K4A_BUFFER_RESULT_SUCCEEDED = 0, + K4A_BUFFER_RESULT_FAILED, + K4A_BUFFER_RESULT_TOO_SMALL, + } + + public enum k4a_wait_result_t + { + K4A_WAIT_RESULT_SUCCEEDED = 0, + K4A_WAIT_RESULT_FAILED, + K4A_WAIT_RESULT_TIMEOUT, + } + + public enum k4a_result_t + { + K4A_RESULT_SUCCEEDED = 0, + K4A_RESULT_FAILED, + } + + public enum k4a_stream_result_t + { + K4A_STREAM_RESULT_SUCCEEDED = 0, + K4A_STREAM_RESULT_FAILED, + K4A_STREAM_RESULT_EOF, + } + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention, CharSet = CharSet.Ansi)] + public static extern k4a_result_t k4a_record_create(string path, IntPtr device, k4a_device_configuration_t deviceConfiguration, out k4a_record_t handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention, CharSet = CharSet.Ansi)] + public static extern k4a_result_t k4a_record_add_tag(k4a_record_t handle, string name, string value); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention, CharSet = CharSet.Ansi)] + public static extern k4a_result_t k4a_record_add_imu_track(k4a_record_t handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention, CharSet = CharSet.Ansi)] + public static extern k4a_result_t k4a_record_add_attachment(k4a_record_t handle, string attachment_name, byte[] buffer, UIntPtr buffer_size); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention, CharSet = CharSet.Ansi)] + public static extern k4a_result_t k4a_record_add_custom_video_track(k4a_record_t handle, string track_name, string codec_id, byte[] codec_context, UIntPtr codec_context_size, RecordVideoSettings track_settings); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention, CharSet = CharSet.Ansi)] + public static extern k4a_result_t k4a_record_add_custom_subtitle_track(k4a_record_t handle, string track_name, string codec_id, byte[] codec_context, UIntPtr codec_context_size, RecordSubtitleSettings track_settings); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_result_t k4a_record_write_header(k4a_record_t handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_result_t k4a_record_write_capture(k4a_record_t handle, IntPtr capture); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_result_t k4a_record_write_imu_sample(k4a_record_t handle, k4a_imu_sample_t imu_sample); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_result_t k4a_record_write_custom_track_data(k4a_record_t handle, string track_name, ulong device_timestamp_usec, byte[] custom_data, UIntPtr custom_data_size); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_result_t k4a_record_flush(k4a_record_t handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern void k4a_record_close(IntPtr handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_result_t k4a_playback_open(string path, out k4a_playback_t handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_buffer_result_t k4a_playback_get_raw_calibration(k4a_playback_t handle, byte[] data, ref UIntPtr data_size); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_result_t k4a_playback_get_calibration(k4a_playback_t playback_handle, out Calibration calibration); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_result_t k4a_playback_get_record_configuration(k4a_playback_t playback_handle, ref k4a_record_configuration_t configuration); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern bool k4a_playback_check_track_exists(k4a_playback_t playback_handle, string track_name); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern UIntPtr k4a_playback_get_track_count(k4a_playback_t playback_handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_buffer_result_t k4a_playback_get_track_name(k4a_playback_t playback_handle, UIntPtr track_index, StringBuilder track_name, ref UIntPtr track_name_size); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern bool k4a_playback_track_is_builtin(k4a_playback_t playback_handle, string track_name); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_result_t k4a_playback_track_get_video_settings(k4a_playback_t playback_handle, string track_name, out RecordVideoSettings video_settings); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_buffer_result_t k4a_playback_track_get_codec_id(k4a_playback_t playback_handle, string track_name, StringBuilder codec_id, ref UIntPtr codec_id_size); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_buffer_result_t k4a_playback_track_get_codec_context( + k4a_playback_t playback_handle, + string track_name, + byte[] codec_context, + ref UIntPtr codec_context_size); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention, CharSet = CharSet.Ansi)] + public static extern k4a_buffer_result_t k4a_playback_get_tag( + k4a_playback_t playback_handle, + string track_name, + StringBuilder value, + ref UIntPtr codec_context_size); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_result_t k4a_playback_set_color_conversion( + k4a_playback_t playback_handle, + ImageFormat target_format); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention, CharSet = CharSet.Ansi)] + public static extern k4a_buffer_result_t k4a_playback_get_attachment( + k4a_playback_t playback_handle, + string file_name, + byte[] data, + ref UIntPtr data_size); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_stream_result_t k4a_playback_get_next_capture( + k4a_playback_t playback_handle, + out IntPtr capture_handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_stream_result_t k4a_playback_get_previous_capture( + k4a_playback_t playback_handle, + out IntPtr capture_handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_stream_result_t k4a_playback_get_next_imu_sample( + k4a_playback_t playback_handle, + [Out] k4a_imu_sample_t imu_sample); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_stream_result_t k4a_playback_get_previous_imu_sample( + k4a_playback_t playback_handle, + [Out] k4a_imu_sample_t imu_sample); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_stream_result_t k4a_playback_get_next_data_block( + k4a_playback_t playback_handle, + string track_name, + out k4a_playback_data_block_t data_block); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_stream_result_t k4a_playback_get_previous_data_block( + k4a_playback_t playback_handle, + string track_name, + out k4a_playback_data_block_t data_block_handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern IntPtr k4a_playback_data_block_get_buffer(k4a_playback_data_block_t data_block_handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern ulong k4a_playback_data_block_get_device_timestamp_usec(k4a_playback_data_block_t data_block_handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern ulong k4a_playback_data_block_get_buffer_size(k4a_playback_data_block_t data_block_handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern void k4a_playback_data_block_release(IntPtr data_block_handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern k4a_result_t k4a_playback_seek_timestamp(k4a_playback_t playback_handle, ulong offset_usec, PlaybackSeekOrigin origin); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern ulong k4a_playback_get_recording_length_usec(k4a_playback_t playback_handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern ulong k4a_playback_get_last_timestamp_usec(k4a_playback_t playback_handle); + + [DllImport("k4arecord", CallingConvention = k4aCallingConvention)] + public static extern void k4a_playback_close(IntPtr playback_handle); + + [StructLayout(LayoutKind.Sequential)] + public struct k4a_version_t + { + public int major; + public int minor; + public int revision; + + public Version ToVersion() + { + return new Version(this.major, this.minor, this.revision); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct k4a_hardware_version_t + { + public k4a_version_t rgb; + public k4a_version_t depth; + public k4a_version_t audio; + public k4a_version_t depth_sensor; + public FirmwareBuild firmware_build; + public FirmwareSignature firmware_signature; + + public HardwareVersion ToHardwareVersion() + { + return new HardwareVersion + { + RGB = this.rgb.ToVersion(), + Depth = this.depth.ToVersion(), + Audio = this.audio.ToVersion(), + DepthSensor = this.depth_sensor.ToVersion(), + FirmwareBuild = this.firmware_build, + FirmwareSignature = this.firmware_signature, + }; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct k4a_device_configuration_t + { + public ImageFormat color_format; + public ColorResolution color_resolution; + public DepthMode depth_mode; + public FPS camera_fps; + public bool synchronized_images_only; + public int depth_delay_off_color_usec; + public WiredSyncMode wired_sync_mode; + public uint subordinate_delay_off_master_usec; + public bool disable_streaming_indicator; + + public static k4a_device_configuration_t FromDeviceConfiguration(DeviceConfiguration configuration) + { + // Ticks are in 100ns units + int depth_delay_off_color_usec = checked((int)( + configuration.DepthDelayOffColor.Ticks / 10)); + + uint subordinate_delay_off_master_usec = checked((uint)( + configuration.SuboridinateDelayOffMaster.Ticks / 10)); + + return new NativeMethods.k4a_device_configuration_t + { + color_format = configuration.ColorFormat, + color_resolution = configuration.ColorResolution, + depth_mode = configuration.DepthMode, + camera_fps = configuration.CameraFPS, + synchronized_images_only = configuration.SynchronizedImagesOnly, + depth_delay_off_color_usec = depth_delay_off_color_usec, + wired_sync_mode = configuration.WiredSyncMode, + subordinate_delay_off_master_usec = subordinate_delay_off_master_usec, + disable_streaming_indicator = configuration.DisableStreamingIndicator, + }; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct k4a_record_configuration_t + { + public ImageFormat color_format; + public ColorResolution color_resolution; + public DepthMode depth_mode; + public FPS camera_fps; + public byte color_track_enabled; + public byte depth_track_enabled; + public byte ir_track_enabled; + public byte imu_track_enabled; + public int depth_delay_off_color_usec; + public WiredSyncMode wired_sync_mode; + public uint subordinate_delay_off_master_usec; + public uint start_timestamp_offset_usec; + } + + public class k4a_record_t : Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid + { + private k4a_record_t() + : base(true) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.k4a_record_close(this.handle); + return true; + } + } + + public class k4a_playback_t : Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid + { + private k4a_playback_t() + : base(true) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.k4a_playback_close(this.handle); + return true; + } + } + + public class k4a_playback_data_block_t : Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid + { + private k4a_playback_data_block_t() + : base(true) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.k4a_playback_data_block_release(this.handle); + return true; + } + } + + [StructLayout(LayoutKind.Sequential)] + public class k4a_imu_sample_t + { + public float temperature { get; set; } + + public Vector3 acc_sample { get; set; } + + public ulong acc_timestamp_usec { get; set; } + + public Vector3 gyro_sample { get; set; } + + public ulong gyro_timestamp_usec { get; set; } + + public ImuSample ToImuSample() + { + return new ImuSample + { + Temperature = this.temperature, + AccelerometerSample = this.acc_sample, + AccelerometerTimestamp = TimeSpan.FromTicks(checked((long)this.acc_timestamp_usec) * 10), + GyroSample = this.gyro_sample, + GyroTimestamp = TimeSpan.FromTicks(checked((long)this.gyro_timestamp_usec) * 10), + }; + } + } + } +#pragma warning restore CA2101 // Specify marshaling for P/Invoke string arguments +#pragma warning restore SA1602 // Enumeration items should be documented +#pragma warning restore SA1600 // Elements should be documented +#pragma warning restore IDE1006 // Naming Styles +} diff --git a/src/csharp/Record/Playback.cs b/src/csharp/Record/Playback.cs new file mode 100644 index 000000000..578c17432 --- /dev/null +++ b/src/csharp/Record/Playback.cs @@ -0,0 +1,786 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Text; +using Microsoft.Azure.Kinect.Sensor.Record.Exceptions; + +namespace Microsoft.Azure.Kinect.Sensor.Record +{ + /// + /// Respresents a file being used to playback data from an Azure Kinect device. + /// + public class Playback : IDisposable + { + // The native handle for this recording. + private readonly NativeMethods.k4a_playback_t handle; + + // To detect redundant calls to Dispose + private bool disposedValue = false; + + // Cached values + private Calibration? calibration = null; + private RecordConfiguration recordConfiguration = null; + + private Playback(NativeMethods.k4a_playback_t handle) + { + this.handle = handle; + } + + /// + /// Gets get the camera calibration for Azure Kinect device used during recording. The output struct is used as input to all transformation functions. + /// + /// + /// The calibration may not exist if the device was not specified during recording. + /// + public Calibration? Calibration + { + get + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + if (!this.calibration.HasValue) + { + if (NativeMethods.k4a_playback_get_calibration(this.handle, out Calibration localCalibration) == NativeMethods.k4a_result_t.K4A_RESULT_SUCCEEDED) + { + this.calibration = localCalibration; + } + } + + return this.calibration; + } + } + } + + /// + /// Gets get the device configuration used during recording. + /// + public RecordConfiguration RecordConfiguration + { + get + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + if (this.recordConfiguration == null) + { + NativeMethods.k4a_record_configuration_t nativeConfig = new NativeMethods.k4a_record_configuration_t(); + + if (NativeMethods.k4a_playback_get_record_configuration(this.handle, ref nativeConfig) == NativeMethods.k4a_result_t.K4A_RESULT_SUCCEEDED) + { + this.recordConfiguration = RecordConfiguration.FromNative(nativeConfig); + } + } + + return this.recordConfiguration; + } + } + } + + /// + /// Gets get the number of tracks in a playback file. + /// + public int TrackCount + { + get + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + return checked((int)NativeMethods.k4a_playback_get_track_count(this.handle)); + } + } + } + + /// + /// Gets the length of the recording in microseconds. + /// + /// + /// The recording length, calculated as the difference between the first and last timestamp in the file. + /// + /// The recording length may be longer than an individual track if, for example, the IMU continues to run after the last + /// color image is recorded. + /// + public TimeSpan RecordingLength + { + get + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + long length = checked((long)NativeMethods.k4a_playback_get_recording_length_usec(this.handle)); + return TimeSpan.FromTicks(length * 10); + } + } + + /// + /// Opens an existing recording file for reading. + /// + /// Filesystem path of the existing recording. + /// An object representing the file for playback. + public static Playback Open(string path) + { + NativeMethods.k4a_playback_t handle = null; + + AzureKinectOpenPlaybackException.ThrowIfNotSuccess(() => NativeMethods.k4a_playback_open(path, out handle)); + + return new Playback(handle); + } + + /// + /// Get the raw calibration blob for the Azure Kinect device used during recording. + /// + /// The raw calibration may not exist if the device was not specified during recording. + public byte[] GetRawCalibration() + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + // Determine the required calibration size + UIntPtr size = new UIntPtr(0); + if (NativeMethods.k4a_playback_get_raw_calibration(this.handle, null, ref size) != NativeMethods.k4a_buffer_result_t.K4A_BUFFER_RESULT_TOO_SMALL) + { + throw new AzureKinectGetRawCalibrationException($"Unexpected result calling {nameof(NativeMethods.k4a_playback_get_raw_calibration)}"); + } + + // Allocate a string buffer + byte[] raw = new byte[size.ToUInt32()]; + + // Get the raw calibration + AzureKinectGetRawCalibrationException.ThrowIfNotSuccess(() => NativeMethods.k4a_playback_get_raw_calibration(this.handle, raw, ref size)); + + return raw; + } + } + + /// + /// Checks whether a track with the given track name exists in the playback file. + /// + /// The track name to be checked to see whether it exists or not. + /// True if the track exists in the file. + public bool CheckTrackExists(string trackName) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + if (trackName == null) + { + throw new ArgumentNullException(nameof(trackName)); + } + + return NativeMethods.k4a_playback_check_track_exists(this.handle, trackName); + } + } + + /// + /// Gets the name of a track at a specific index. + /// + /// The index of the track to read the name form. + /// The name of the track. + /// When used along with , this function can be used to enumerate all the available tracks + /// in a playback file. Additionally can be used to filter custom tracks. + public string GetTrackName(int index) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + if (index >= this.TrackCount || index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + // Determine the required string size + UIntPtr size = new UIntPtr(0); + if (NativeMethods.k4a_playback_get_track_name(this.handle, (UIntPtr)index, null, ref size) != NativeMethods.k4a_buffer_result_t.K4A_BUFFER_RESULT_TOO_SMALL) + { + throw new AzureKinectException($"Unexpected internal state calling {nameof(NativeMethods.k4a_playback_get_track_name)}"); + } + + // Allocate a string buffer + StringBuilder trackname = new StringBuilder((int)size.ToUInt32()); + + // Get the track name + AzureKinectGetTrackNameException.ThrowIfNotSuccess(() => NativeMethods.k4a_playback_get_track_name(this.handle, (UIntPtr)index, trackname, ref size)); + + return trackname.ToString(); + } + } + + /// + /// Checks whether a track is one of the built-in tracks: "COLOR", "DEPTH", etc... + /// + /// The track name to be checked to see whether it is a built-in track. + /// true if the track is built-in. If the provided track name does not exist, false will be returned. + public bool GetTrackIsBuiltin(string trackName) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + if (trackName == null) + { + throw new ArgumentNullException(nameof(trackName)); + } + + return NativeMethods.k4a_playback_track_is_builtin(this.handle, trackName); + } + } + + /// + /// Gets the video-specific track information for a particular video track. + /// + /// The track name to read video settings from. + /// The track's video settings. + public RecordVideoSettings GetTrackVideoSettings(string trackName) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + if (trackName == null) + { + throw new ArgumentNullException(nameof(trackName)); + } + + RecordVideoSettings recordVideoSettings = new RecordVideoSettings(); + + AzureKinectTrackGetVideoSettingsException.ThrowIfNotSuccess(() => NativeMethods.k4a_playback_track_get_video_settings(this.handle, trackName, out recordVideoSettings)); + + return recordVideoSettings; + } + } + + /// + /// Gets the codec id string for a particular track. + /// + /// The track name to read the codec id from. + /// Codec ID for the track. + /// + /// The codec ID is a string that corresponds to the codec of the track's data. Some of the existing formats are listed + /// here: https://www.matroska.org/technical/specs/codecid/index.html. It can also be custom defined by the user. + /// + public string GetTrackCodecId(string trackName) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + if (trackName == null) + { + throw new ArgumentNullException(nameof(trackName)); + } + + // Determine the required string size + UIntPtr size = new UIntPtr(0); + if (NativeMethods.k4a_playback_track_get_codec_id(this.handle, trackName, null, ref size) != NativeMethods.k4a_buffer_result_t.K4A_BUFFER_RESULT_TOO_SMALL) + { + throw new AzureKinectException($"Unexpected internal state calling {nameof(NativeMethods.k4a_playback_get_track_name)}"); + } + + // Allocate a string buffer + StringBuilder codec = new StringBuilder((int)size.ToUInt32()); + + // Get the codec id + AzureKinectGetTrackNameException.ThrowIfNotSuccess(() => NativeMethods.k4a_playback_track_get_codec_id(this.handle, trackName, codec, ref size)); + + return codec.ToString(); + } + } + + /// + /// Gets the codec context for a particular track. + /// + /// The track name to read the codec context from. + /// The codec context data. + /// + /// The codec context is a codec-specific buffer that contains any required codec metadata that is only known to the + /// codec. It is mapped to the matroska Codec Private field. + /// + public byte[] GetTrackCodecContext(string trackName) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + if (trackName == null) + { + throw new ArgumentNullException(nameof(trackName)); + } + + // Determine the required buffer size + UIntPtr size = new UIntPtr(0); + if (NativeMethods.k4a_playback_track_get_codec_context(this.handle, trackName, null, ref size) != NativeMethods.k4a_buffer_result_t.K4A_BUFFER_RESULT_TOO_SMALL) + { + throw new AzureKinectException($"Unexpected internal state calling {nameof(NativeMethods.k4a_playback_get_track_name)}"); + } + + // Allocate a buffer + byte[] context = new byte[checked((int)size)]; + + // Get the codec id + AzureKinectGetTrackNameException.ThrowIfNotSuccess(() => NativeMethods.k4a_playback_track_get_codec_context(this.handle, trackName, context, ref size)); + + return context; + } + } + + /// + /// Read the value of a tag from a recording. + /// + /// The name of the tag to read. + /// The value of the tag. + public string GetTag(string name) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + // Determine the required string size + UIntPtr size = new UIntPtr(0); + NativeMethods.k4a_buffer_result_t result; + +#pragma warning disable CA1508 // Avoid dead conditional code + using (LoggingTracer tracer = new LoggingTracer()) +#pragma warning restore CA1508 // Avoid dead conditional code + { + result = NativeMethods.k4a_playback_get_tag(this.handle, name, null, ref size); + + if (result == NativeMethods.k4a_buffer_result_t.K4A_BUFFER_RESULT_FAILED) + { + AzureKinectGetTagException.ThrowIfNotSuccess(tracer, result); + } + } + + if (result != NativeMethods.k4a_buffer_result_t.K4A_BUFFER_RESULT_TOO_SMALL) + { + throw new AzureKinectException($"Unexpected internal state calling {nameof(NativeMethods.k4a_playback_get_track_name)}"); + } + + // Allocate a string buffer + StringBuilder value = new StringBuilder((int)size.ToUInt32()); + + // Get the codec id + AzureKinectGetTagException.ThrowIfNotSuccess(() => NativeMethods.k4a_playback_get_tag(this.handle, name, value, ref size)); + + return value.ToString(); + } + } + + /// + /// Set the image format that color captures will be converted to. By default the conversion format will be the same as + /// the image format stored in the recording file, and no conversion will occur. + /// + /// The target image format to be returned in captures. + /// + /// Color format conversion occurs in the user-thread, so setting to anything other than the format + /// stored in the file may significantly increase the latency of and . + /// + public void SetColorConversion(ImageFormat targetFormat) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + AzureKinectSetColorConversionException.ThrowIfNotSuccess(() => NativeMethods.k4a_playback_set_color_conversion(this.handle, targetFormat)); + } + } + + /// + /// Reads an attachment file from a recording. + /// + /// The attachment file name. + /// The attachment data. + public byte[] GetAttachment(string fileName) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + // Determine the required buffer size + UIntPtr size = new UIntPtr(0); + if (NativeMethods.k4a_playback_get_attachment(this.handle, fileName, null, ref size) != NativeMethods.k4a_buffer_result_t.K4A_BUFFER_RESULT_TOO_SMALL) + { + throw new AzureKinectException($"Unexpected internal state calling {nameof(NativeMethods.k4a_playback_get_track_name)}"); + } + + // Allocate a buffer + byte[] buffer = new byte[checked((int)size)]; + + // Get the codec id + AzureKinectGetTrackNameException.ThrowIfNotSuccess(() => NativeMethods.k4a_playback_get_attachment(this.handle, fileName, buffer, ref size)); + + return buffer; + } + } + + /// + /// Read the next capture in the recording sequence. + /// + /// The next capture in the sequence, or null if at the end of the sequence. + /// + /// always returns the next capture in sequence after the most recently returned capture. + /// + /// The first call to after will return the capture + /// in the recording closest to the seek time with an image timestamp greater than or equal to the seek time. + /// + /// If a call was made to that returned null, the playback + /// position is at the beginning of the stream and will return the first capture in the + /// recording. + /// + /// Capture objects returned by the playback API will always contain at least one image, but may have images missing if + /// frames were dropped in the original recording. When calling , + /// , or , the image should be checked for null. + /// + public Capture GetNextCapture() + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + IntPtr captureHandle = IntPtr.Zero; + + switch (NativeMethods.k4a_playback_get_next_capture(this.handle, out captureHandle)) + { + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_EOF: + return null; + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_FAILED: + throw new AzureKinectGetCaptureException(); + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_SUCCEEDED: + return new Capture(true, captureHandle); + } + + return null; + } + } + + /// + /// Read the previous capture in the recording sequence. + /// + /// The previous capture in the sequence, or null if at the beginning of the sequence. + /// + /// always returns the previous capture in sequence after the most recently returned capture. + /// + /// The first call to after will return the capture + /// in the recording closest to the seek time with all image timestamps less than the seek time. + /// + /// If a call was made to that returned null, the playback + /// position is at the end of the stream and will return the last capture in the + /// recording. + /// + /// Capture objects returned by the playback API will always contain at least one image, but may have images missing if + /// frames were dropped in the original recording. When calling , + /// , or , the image should be checked for null. + /// + public Capture GetPreviousCapture() + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + IntPtr captureHandle = IntPtr.Zero; + + switch (NativeMethods.k4a_playback_get_previous_capture(this.handle, out captureHandle)) + { + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_EOF: + return null; + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_FAILED: + throw new AzureKinectGetCaptureException(); + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_SUCCEEDED: + return new Capture(true, captureHandle); + } + + return null; + } + } + + /// + /// Read the next IMU sample in the recording sequence. + /// + /// The next IMU sample in the sequence, or null if at the end of the sequence. + /// + /// always returns the next IMU sample in sequence after the most recently returned sample. + /// + /// The first call to after will return the sample + /// in the recording closest to the seek time with a timestamp greater than or equal to the seek time. + /// + /// If a call was made to that returned null, the playback + /// position is at the beginning of the stream and will return the first sample in the + /// recording. + /// + public ImuSample GetNextImuSample() + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + NativeMethods.k4a_imu_sample_t imu_sample = new NativeMethods.k4a_imu_sample_t(); + + switch (NativeMethods.k4a_playback_get_next_imu_sample(this.handle, imu_sample)) + { + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_EOF: + return null; + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_FAILED: + throw new AzureKinectGetImuSampleException(); + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_SUCCEEDED: + return imu_sample.ToImuSample(); + } + + return null; + } + } + + /// + /// Read the previous IMU sample in the recording sequence. + /// + /// The previous IMU sample in the sequence, or null if at the beginning of the sequence. + /// + /// always returns the previous IMU sample in sequence before the most recently returned sample. + /// + /// The first call to after will return the sample + /// in the recording closest to the seek time with a timestamp less than the seek time. + /// + /// If a call was made to that returned null, the playback + /// position is at the end of the stream and will return the last sample in the + /// recording. + /// + public ImuSample GetPreviousImuSample() + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + NativeMethods.k4a_imu_sample_t imu_sample = new NativeMethods.k4a_imu_sample_t(); + + switch (NativeMethods.k4a_playback_get_previous_imu_sample(this.handle, imu_sample)) + { + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_EOF: + return null; + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_FAILED: + throw new AzureKinectGetImuSampleException(); + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_SUCCEEDED: + return imu_sample.ToImuSample(); + } + + return null; + } + } + + /// + /// Read the next data block for a particular track. + /// + /// The name of the track to read the next data block from. + /// The next data block in the sequence, or null if at the end of the sequence. + /// + /// always returns the next data block in sequence after the most recently returned data block + /// for a particular track. + /// + /// The first call to after will return the data block + /// in the recording closest to the seek time with a timestamp greater than or equal to the seek time. + /// + /// If a call was made to that returned null for a particular track, the playback + /// position is at the beginning of the stream and will return the first data block in the + /// recording. + /// + public DataBlock GetNextDataBlock(string trackName) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + if (trackName == null) + { + throw new ArgumentNullException(nameof(trackName)); + } + + switch (NativeMethods.k4a_playback_get_next_data_block(this.handle, trackName, out NativeMethods.k4a_playback_data_block_t dataBlock)) + { + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_EOF: + return null; + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_FAILED: + throw new AzureKinectGetDataBlockException(); + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_SUCCEEDED: + return new DataBlock(dataBlock); + } + + return null; + } + } + + /// + /// Read the previous data block for a particular track. + /// + /// The name of the track to read the previous data block from. + /// The previous data block in the sequence, or null if at the beginning of the sequence. + /// + /// always returns the previous data block in sequence after the most recently returned data block + /// for a particular track. + /// + /// The first call to after will return the data block + /// in the recording closest to the seek time with a timestamp less than the seek time. + /// + /// If a call was made to that returned null for a particular track, the playback + /// position is at the end of the stream and will return the last data block in the + /// recording. + /// + public DataBlock GetPreviousDataBlock(string trackName) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + if (trackName == null) + { + throw new ArgumentNullException(nameof(trackName)); + } + + switch (NativeMethods.k4a_playback_get_previous_data_block(this.handle, trackName, out NativeMethods.k4a_playback_data_block_t dataBlock)) + { + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_EOF: + return null; + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_FAILED: + throw new AzureKinectGetDataBlockException(); + case NativeMethods.k4a_stream_result_t.K4A_STREAM_RESULT_SUCCEEDED: + return new DataBlock(dataBlock); + } + + return null; + } + } + + /// + /// Seek to a specific timestamp within a recording. + /// + /// The timestamp offset to seek to, relative to . + /// Specifies how the given timestamp should be interpreted. Seek can be done relative to the beginning or end of the + /// recording, or using an absolute device timestamp. + /// + /// The first device timestamp in a recording is usually non-zero. The recording file starts at the device timestamp + /// defined by , which is accessible via . + /// + /// The first call to after will return a capture containing an image + /// timestamp greater than or equal to the seek time. + /// + /// The first call to after will return a capture with + /// all image timstamps less than the seek time. + /// + /// The first call to and after will return the + /// first data with a timestamp greater than or equal to the seek time. + /// + /// The first call to and after will return the + /// first data with a timestamp less than the seek time. + /// + public void Seek(TimeSpan offset, PlaybackSeekOrigin origin = PlaybackSeekOrigin.Begin) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Playback)); + } + + AzureKinectSeekException.ThrowIfNotSuccess(() => NativeMethods.k4a_playback_seek_timestamp(this.handle, checked((ulong)(offset.Ticks / 10)), origin)); + } + } + + /// + public void Dispose() + { + this.Dispose(true); + + GC.SuppressFinalize(this); + } + + /// + /// Handle the disposing of the object. + /// + /// true when called by Dispose(), false when called by the finalizer. + protected virtual void Dispose(bool disposing) + { + lock (this) + { + this.handle.Close(); + + this.disposedValue = true; + } + } + } +} diff --git a/src/csharp/Record/PlaybackSeekOrigin.cs b/src/csharp/Record/PlaybackSeekOrigin.cs new file mode 100644 index 000000000..1f8caf5cf --- /dev/null +++ b/src/csharp/Record/PlaybackSeekOrigin.cs @@ -0,0 +1,29 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +namespace Microsoft.Azure.Kinect.Sensor.Record +{ + /// + /// The origin for relative seek operations. + /// + public enum PlaybackSeekOrigin + { + /// + /// The seek operation is relative to the beginning of the file. + /// + Begin = 0, + + /// + /// The seek operation is relative to the end of the file. + /// + End, + + /// + /// The seek operation is specified in the device time. + /// + DeviceTime, + } +} diff --git a/src/csharp/Record/RecordConfiguration.cs b/src/csharp/Record/RecordConfiguration.cs new file mode 100644 index 000000000..d77283b03 --- /dev/null +++ b/src/csharp/Record/RecordConfiguration.cs @@ -0,0 +1,110 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; + +namespace Microsoft.Azure.Kinect.Sensor.Record +{ + /// + /// Structure containing the device configuration used to record. + /// + public class RecordConfiguration + { + /// + /// Gets or sets the image format used to record the color camera. + /// + public ImageFormat ColorFormat { get; set; } + + /// + /// Gets or sets the image resolution used to record the color camera. + /// + public ColorResolution ColorResolution { get; set; } + + /// + /// Gets or sets the mode used to record the depth camera. + /// + public DepthMode DepthMode { get; set; } + + /// + /// Gets or sets the frame rate used to record the color and depth camera. + /// + public FPS CameraFPS { get; set; } + + /// + /// Gets or sets a value indicating whether the recording contains Color camera frames. + /// + public bool ColorTrackEnabled { get; set; } + + /// + /// Gets or sets a value indicating whether the recording contains Depth camera frames. + /// + public bool DepthTrackEnabled { get; set; } + + /// + /// Gets or sets a value indicating whether the recording contains IR camera frames. + /// + public bool IRTrackEnabled { get; set; } + + /// + /// Gets or sets a value indicating whether the recording contains IMU sample data. + /// + public bool ImuTrackEnabled { get; set; } + + /// + /// Gets or sets the delay between color and depth images in the recording. + /// + /// + /// A negative delay means depth images are first, and a positive delay means color images are first. + /// + public TimeSpan DepthDelayOffColor { get; set; } + + /// + /// Gets or sets external synchronization mode. + /// + public WiredSyncMode WiredSyncMode { get; set; } + + /// + /// Gets or sets the delay between this recording and the externally synced master camera. + /// + /// + /// This value is 0 unless is set to . + /// + public TimeSpan SubordinateDelayOffMaster { get; set; } + + /// + /// Gets or sets the timestamp offset of the start of the recording. + /// + /// + /// All recorded timestamps are offset by this value such that + /// the recording starts at timestamp 0. This value can be used to synchronize timestamps between 2 recording files. + /// + public TimeSpan StartTimestampOffset { get; set; } + + /// + /// Gets a object from a native object. + /// + /// Native object. + /// Managed object. + internal static RecordConfiguration FromNative(NativeMethods.k4a_record_configuration_t config) + { + return new RecordConfiguration() + { + ColorFormat = config.color_format, + ColorResolution = config.color_resolution, + DepthMode = config.depth_mode, + CameraFPS = config.camera_fps, + ColorTrackEnabled = config.color_track_enabled == 0 ? false : true, + DepthTrackEnabled = config.depth_track_enabled == 0 ? false : true, + IRTrackEnabled = config.ir_track_enabled == 0 ? false : true, + ImuTrackEnabled = config.imu_track_enabled == 0 ? false : true, + DepthDelayOffColor = TimeSpan.FromTicks(config.subordinate_delay_off_master_usec * 10), + WiredSyncMode = config.wired_sync_mode, + SubordinateDelayOffMaster = TimeSpan.FromTicks(config.subordinate_delay_off_master_usec * 10), + StartTimestampOffset = TimeSpan.FromTicks(config.start_timestamp_offset_usec), + }; + } + } +} diff --git a/src/csharp/Record/RecordSubtitleSettings.cs b/src/csharp/Record/RecordSubtitleSettings.cs new file mode 100644 index 000000000..d7b666b37 --- /dev/null +++ b/src/csharp/Record/RecordSubtitleSettings.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.Azure.Kinect.Sensor.Record +{ + /// + /// Settings for a recording subtitle track. + /// + [StructLayout(LayoutKind.Sequential)] + public class RecordSubtitleSettings + { + /// + /// Gets or sets a value indicating whether data will be grouped together to reduce overhead. + /// + /// + /// If set, only a single timestamp will be stored per batch, and an estimated timestamp will be use by and . + /// The estimated timestamp is calculated with the assumption that blocks are evenly spaced within a batch. + public bool HighFrequencyData { get; set; } + } +} diff --git a/src/csharp/Record/RecordVideoSettings.cs b/src/csharp/Record/RecordVideoSettings.cs new file mode 100644 index 000000000..bcdd1cc95 --- /dev/null +++ b/src/csharp/Record/RecordVideoSettings.cs @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System.Runtime.InteropServices; + +namespace Microsoft.Azure.Kinect.Sensor.Record +{ + /// + /// Structure containing additional metadata specific to custom video tracks. + /// + [StructLayout(LayoutKind.Sequential)] + public class RecordVideoSettings + { + /// + /// Gets or sets frame width of the video. + /// + public long Width { get; set; } + + /// + /// Gets or sets frame height of the video. + /// + public long Height { get; set; } + + /// + /// Gets or sets frame rate of the video. + /// + public long FrameRate { get; set; } + } +} diff --git a/src/csharp/Record/Recorder.cs b/src/csharp/Record/Recorder.cs new file mode 100644 index 000000000..c5b694272 --- /dev/null +++ b/src/csharp/Record/Recorder.cs @@ -0,0 +1,365 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ +using System; +using Microsoft.Azure.Kinect.Sensor.Record.Exceptions; + +namespace Microsoft.Azure.Kinect.Sensor.Record +{ + /// + /// Represents a writable sensor recording. + /// + public class Recorder : IDisposable + { + // The native handle for this recording. + private readonly NativeMethods.k4a_record_t handle; + + // To detect redundant calls to Dispose + private bool disposedValue = false; + + private Recorder(NativeMethods.k4a_record_t handle) + { + this.handle = handle; + } + + /// + /// Create a recording. + /// + /// Path to the recording. + /// Device to get properties from. May be null for user-generated recordings. + /// Parameters used to open the device. + /// A new recording object. + public static Recorder Create(string path, Device device, DeviceConfiguration deviceConfiguration) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + + if (deviceConfiguration == null) + { + throw new ArgumentNullException(nameof(deviceConfiguration)); + } + + NativeMethods.k4a_record_t handle = null; + NativeMethods.k4a_device_configuration_t configuration = NativeMethods.k4a_device_configuration_t.FromDeviceConfiguration(deviceConfiguration); + if (device != null) + { + // If a device was specified, lock that device to avoid disposal while in use. + // Device.Dispose will take the same lock. + lock (device) + { + AzureKinectCreateRecordingException.ThrowIfNotSuccess(path, () => NativeMethods.k4a_record_create(path, device.Handle, configuration, out handle)); + } + } + else + { + AzureKinectCreateRecordingException.ThrowIfNotSuccess(path, () => NativeMethods.k4a_record_create(path, IntPtr.Zero, configuration, out handle)); + } + + return new Recorder(handle); + } + + /// + /// Adds a tag to the recroding. + /// + /// Name of the tag. + /// Value of the tag. + public void AddTag(string name, string value) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Recorder)); + } + + AzureKinectAddTagException.ThrowIfNotSuccess(() => NativeMethods.k4a_record_add_tag(this.handle, name, value)); + } + } + + /// + /// Adds an IMU track to the recording. + /// + public void AddImuTrack() + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Recorder)); + } + + AzureKinectAddImuTrackException.ThrowIfNotSuccess(() => NativeMethods.k4a_record_add_imu_track(this.handle)); + } + } + + /// + /// Adds an attachment to a recording. + /// + /// The name of the attachment to be stored in the recording file. This name should be a valid filename with an extension. + /// The attachment data buffer. + public void AddAttachment(string attachmentName, byte[] buffer) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Recorder)); + } + + AzureKinectAddAttachmentException.ThrowIfNotSuccess(() => NativeMethods.k4a_record_add_attachment(this.handle, attachmentName, buffer, (UIntPtr)buffer.Length)); + } + } + + /// + /// Adds custom video tracks to the recording. + /// + /// The name of the custom video track to be added. + /// A UTF8 null terminated string containing the codec ID of the track. + /// Some of the existing formats are listed here: https://www.matroska.org/technical/specs/codecid/index.html. + /// The codec ID can also be custom defined by the user. Video codec ID's should start with 'V_'. + /// The codec context is a codec-specific buffer that contains any required codec metadata that is only known to the codec. It is mapped to the matroska 'CodecPrivate' element. + /// Additional metadata for the video track such as resolution and framerate. + /// + /// Built-in video tracks like the DEPTH, IR, and COLOR tracks will be created automatically when the k4a_record_create() + /// API is called.This API can be used to add additional video tracks to save custom data. + /// + /// Track names must be ALL CAPS and may only contain A-Z, 0-9, '-' and '_'. + /// + /// All tracks need to be added before the recording header is written. + /// + /// Call k4a_record_write_custom_track_data() with the same track_name to write data to this track. + /// + /// + public void AddCustomVideoTrack( + string trackName, + string codecId, + byte[] codecContext, + RecordVideoSettings trackSettings) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Recorder)); + } + + AzureKinectAddCustomVideoTrackException.ThrowIfNotSuccess(() => NativeMethods.k4a_record_add_custom_video_track( + this.handle, + trackName, + codecId, + codecContext, + (UIntPtr)codecContext.Length, + trackSettings)); + } + } + + /// + /// Adds custom subtitle tracks to the recording. + /// + /// The name of the custom subtitle track to be added. + /// A UTF8 null terminated string containing the codec ID of the track. + /// Some of the existing formats are listed here: https://www.matroska.org/technical/specs/codecid/index.html. The codec ID can also be custom defined by the user. + /// Subtitle codec ID's should start with 'S_'. + /// The codec context is a codec-specific buffer that contains any required codec metadata that is only known to the codec.It is mapped to the matroska 'CodecPrivate' element. + /// Additional metadata for the subtitle track. If null, the default settings will be used. + /// + /// Built-in subtitle tracks like the IMU track will be created automatically when the k4a_record_add_imu_track() API is + /// called.This API can be used to add additional subtitle tracks to save custom data. + /// + /// Track names must be ALL CAPS and may only contain A-Z, 0-9, '-' and '_'. + /// + /// All tracks need to be added before the recording header is written. + /// + /// Call k4a_record_write_custom_track_data() with the same track_name to write data to this track. + public void AddCustomSubtitleTrack( + string trackName, + string codecId, + byte[] codecContext, + RecordSubtitleSettings trackSettings) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Recorder)); + } + + AzureKinectAddCustomSubtitleTrackException.ThrowIfNotSuccess(() => NativeMethods.k4a_record_add_custom_subtitle_track( + this.handle, + trackName, + codecId, + codecContext, + (UIntPtr)codecContext.Length, + trackSettings)); + } + } + + /// + /// Writes the recording header and metadata to file. + /// + /// + /// This must be called before captures or any track data can be written. + /// + public void WriteHeader() + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Recorder)); + } + + AzureKinectWriteHeaderException.ThrowIfNotSuccess(() => NativeMethods.k4a_record_write_header(this.handle)); + } + } + + /// + /// Writes a capture to the recording file. + /// + /// Capture containing data to write. + /// + /// Captures must be written in increasing order of timestamp, and the file's header must already be written. + /// + /// k4a_record_write_capture() will write all images in the capture to the corresponding tracks in the recording file. + /// If any of the images fail to write, other images will still be written before a failure is returned. + /// + public void WriteCapture(Capture capture) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Recorder)); + } + + if (capture == null) + { + throw new ArgumentNullException(nameof(capture)); + } + + using (Capture reference = capture.Reference()) + { + AzureKinectWriteCaptureException.ThrowIfNotSuccess(() => NativeMethods.k4a_record_write_capture(this.handle, reference.Handle)); + } + } + } + + /// + /// Writes an IMU sample to the recording. + /// + /// Sample with the IMU data. + public void WriteImuSample(ImuSample imuSample) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Recorder)); + } + + if (imuSample == null) + { + throw new ArgumentNullException(nameof(imuSample)); + } + + NativeMethods.k4a_imu_sample_t sample = new NativeMethods.k4a_imu_sample_t() + { + temperature = imuSample.Temperature, + acc_sample = imuSample.AccelerometerSample, + acc_timestamp_usec = checked((ulong)imuSample.AccelerometerTimestamp.Ticks / 10), + gyro_sample = imuSample.GyroSample, + gyro_timestamp_usec = checked((ulong)imuSample.GyroTimestamp.Ticks / 10), + }; + + AzureKinectWriteImuSampleException.ThrowIfNotSuccess(() => NativeMethods.k4a_record_write_imu_sample(this.handle, sample)); + } + } + + /// + /// Writes data for a custom track to file. + /// + /// The name of the custom track that the data is going to be written to. + /// The timestamp for the custom track data. This timestamp should be in the same time domain as the device timestamp used for recording. + /// The buffer of custom track data. + /// + /// Custom track data must be written in increasing order of timestamp, and the file's header must already be written. + /// When writing custom track data at the same time as captures or IMU data, the custom data should be within 1 second of + /// the most recently written timestamp. + /// + public void WriteCustomTrackData( + string trackName, + TimeSpan deviceTimestamp, + byte[] customData) + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Recorder)); + } + + if (trackName == null) + { + throw new ArgumentNullException(nameof(trackName)); + } + + if (customData == null) + { + throw new ArgumentNullException(nameof(customData)); + } + + AzureKinectWriteCustomTrackDataException.ThrowIfNotSuccess(() => NativeMethods.k4a_record_write_custom_track_data( + this.handle, + trackName, + checked((ulong)deviceTimestamp.Ticks / 10), + customData, + (UIntPtr)customData.Length)); + } + } + + /// + /// Flushes all pending recording data to disk. + /// + /// + /// If an error occurs, best effort is made to flush as much data to disk as possible, but the integrity of the file is not guaranteed. + public void Flush() + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Recorder)); + } + + AzureKinectFlushException.ThrowIfNotSuccess(() => NativeMethods.k4a_record_flush(this.handle)); + } + } + + /// + public void Dispose() + { + this.Dispose(true); + + GC.SuppressFinalize(this); + } + + /// + /// Handle the disposing of the object. + /// + /// true when called by Dispose(), false when called by the finalizer. + protected virtual void Dispose(bool disposing) + { + lock (this) + { + this.handle.Close(); + + this.disposedValue = true; + } + } + } +} diff --git a/src/csharp/SDK/Allocator.cs b/src/csharp/SDK/Allocator.cs index 4b6419dc2..490e26cfc 100644 --- a/src/csharp/SDK/Allocator.cs +++ b/src/csharp/SDK/Allocator.cs @@ -92,7 +92,9 @@ public bool UseManagedAllocator AzureKinectException.ThrowIfNotSuccess(() => NativeMethods.k4a_set_allocator(this.allocateDelegate, this.freeDelegate)); this.hooked = true; } +#pragma warning disable CA1031 // Do not catch general exception types catch (Exception) +#pragma warning restore CA1031 // Do not catch general exception types { // Don't fail if we can't set the allocator since this code path is called during the global type // initialization. A failure to set the allocator is also not fatal, but will only cause a performance diff --git a/src/csharp/SDK/Calibration.cs b/src/csharp/SDK/Calibration.cs index 2e191b8df..72bd3d0db 100644 --- a/src/csharp/SDK/Calibration.cs +++ b/src/csharp/SDK/Calibration.cs @@ -197,6 +197,33 @@ public static Calibration GetFromRaw(byte[] raw, DepthMode depthMode, ColorResol } } + /// + /// Transform a 2D pixel coordinate from color camera into a 2D pixel coordinate of the depth camera. + /// + /// The 2D pixel color camera coordinates. + /// The depth image. + /// The 2D pixel in depth camera coordinates, or null if the source point is not valid in the depth camera coordinate system. + public Vector2? TransformColor2DToDepth2D(Vector2 sourcePoint2D, Image depth) + { + if (depth == null) + { + throw new ArgumentNullException(nameof(depth)); + } + + using (LoggingTracer tracer = new LoggingTracer()) + using (Image depthReference = depth.Reference()) + { + AzureKinectException.ThrowIfNotSuccess(tracer, NativeMethods.k4a_calibration_color_2d_to_depth_2d( + ref this, + ref sourcePoint2D, + depthReference.DangerousGetHandle(), + out Vector2 target_point2d, + out bool valid)); + + return valid ? (Vector2?)target_point2d : null; + } + } + /// /// Creates a Transformation object from this calibration. /// diff --git a/src/csharp/SDK/Capture.cs b/src/csharp/SDK/Capture.cs index b69844efb..ec1b767ce 100644 --- a/src/csharp/SDK/Capture.cs +++ b/src/csharp/SDK/Capture.cs @@ -37,6 +37,20 @@ public Capture() Allocator.Singleton.RegisterForDisposal(this); } + /// + /// Initializes a new instance of the class from an existing native handle. + /// + /// If false, the constructor will take a new reference on the handle. + /// Native handle to a k4a_capture_t. + /// + /// Disposing this Capture will always release a reference on the handle. + /// If is true, the handle may not be owned by any existing Capture object. + /// + public Capture(bool takeOwnership, IntPtr handle) + : this(new NativeMethods.k4a_capture_t(takeOwnership, handle)) + { + } + /// /// Initializes a new instance of the class. /// @@ -169,6 +183,32 @@ public float Temperature } } + /// + /// Gets the native handle. + /// + /// This is the value of the k4a_capture_t handle of the native library. + /// + /// This handle value can be used to interoperate with other native libraries that use + /// Azure Kinect objects. + /// + /// When using this handle value, the caller is responsible for ensuring that the + /// Capture object does not become disposed. + public IntPtr Handle + { + get + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Capture)); + } + + return this.handle.DangerousGetHandle(); + } + } + } + /// /// Creates a duplicate reference to the same Capture. /// diff --git a/src/csharp/SDK/Device.cs b/src/csharp/SDK/Device.cs index fba4d2df8..d091dc3d9 100644 --- a/src/csharp/SDK/Device.cs +++ b/src/csharp/SDK/Device.cs @@ -167,6 +167,32 @@ public HardwareVersion Version } } + /// + /// Gets the native handle. + /// + /// This is the value of the k4a_device_t handle of the native library. + /// + /// This handle value can be used to interoperate with other native libraries that use + /// Azure Kinect objects. + /// + /// When using this handle value, the caller is responsible for ensuring that the + /// Device object does not become disposed. + public IntPtr Handle + { + get + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Device)); + } + + return this.handle.DangerousGetHandle(); + } + } + } + /// /// Gets the number of currently connected devices. /// @@ -274,7 +300,9 @@ public Capture GetCapture(TimeSpan timeout) throw new ObjectDisposedException(nameof(Device)); } +#pragma warning disable CA1508 // Avoid dead conditional code using (LoggingTracer tracer = new LoggingTracer()) +#pragma warning restore CA1508 // Avoid dead conditional code { NativeMethods.k4a_wait_result_t result = NativeMethods.k4a_device_get_capture(this.handle, out NativeMethods.k4a_capture_t capture, (int)timeout.TotalMilliseconds); @@ -328,7 +356,9 @@ public ImuSample GetImuSample(TimeSpan timeout) throw new ObjectDisposedException(nameof(Device)); } +#pragma warning disable CA1508 // Avoid dead conditional code using (LoggingTracer tracer = new LoggingTracer()) +#pragma warning restore CA1508 // Avoid dead conditional code { NativeMethods.k4a_imu_sample_t sample = new NativeMethods.k4a_imu_sample_t(); NativeMethods.k4a_wait_result_t result = NativeMethods.k4a_device_get_imu_sample(this.handle, sample, (int)timeout.TotalMilliseconds); @@ -503,6 +533,27 @@ public void Dispose() GC.SuppressFinalize(this); } + /// + /// Gets the native handle. + /// + /// The native handle that is wrapped by this device. + /// The function is dangerous because there is no guarantee that the + /// handle will not be disposed once it is retrieved. This should only be called + /// by code that can ensure that the Capture object will not be disposed on another + /// thread. + internal NativeMethods.k4a_device_t DangerousGetHandle() + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Device)); + } + + return this.handle; + } + } + /// /// Releases unmanaged and - optionally - managed resources. /// @@ -513,12 +564,17 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - Allocator.Singleton.UnregisterForDisposal(this); + // Callers of DangerousGetHandle will lock this Device object + // to ensure the handle isn't disposed while in use. + lock (this) + { + Allocator.Singleton.UnregisterForDisposal(this); - this.handle.Close(); - this.handle = null; + this.handle.Close(); + this.handle = null; - this.disposedValue = true; + this.disposedValue = true; + } } } } diff --git a/src/csharp/SDK/Image.cs b/src/csharp/SDK/Image.cs index 8ba3e6217..185aaad0e 100644 --- a/src/csharp/SDK/Image.cs +++ b/src/csharp/SDK/Image.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -83,6 +83,20 @@ public Image(ImageFormat format, int widthPixels, int heightPixels) #pragma warning restore CA2000 // Dispose objects before losing scope } + /// + /// Initializes a new instance of the class from an existing native handle. + /// + /// If false, the constructor will take a new reference on the handle. + /// Native handle to a k4a_image_t. + /// + /// Disposing this Image will always release a reference on the handle. + /// If is true, the handle may not be owned by any existing Image object. + /// + public Image(bool takeOwnership, IntPtr handle) + : this(new NativeMethods.k4a_image_t(takeOwnership, handle)) + { + } + /// /// Initializes a new instance of the class. /// @@ -468,6 +482,32 @@ public int WhiteBalance } } + /// + /// Gets the native handle. + /// + /// This is the value of the k4a_image_t handle of the native library. + /// + /// This handle value can be used to interoperate with other native libraries that use + /// Azure Kinect objects. + /// + /// When using this handle value, the caller is responsible for ensuring that the + /// Image object does not become disposed. + public IntPtr Handle + { + get + { + lock (this) + { + if (this.disposedValue) + { + throw new ObjectDisposedException(nameof(Image)); + } + + return this.handle.DangerousGetHandle(); + } + } + } + /// /// Gets the pixels of the image. /// diff --git a/src/csharp/SDK/LargeArrayPool.cs b/src/csharp/SDK/LargeArrayPool.cs index a3823a329..0abbbee1c 100644 --- a/src/csharp/SDK/LargeArrayPool.cs +++ b/src/csharp/SDK/LargeArrayPool.cs @@ -34,7 +34,7 @@ public override byte[] Rent(int minimumLength) if (x.IsAlive) { buffer = (byte[])x.Target; - if (buffer.Length >= minimumLength) + if (buffer != null && buffer.Length >= minimumLength) { _ = this.pool.Remove(x); return buffer; diff --git a/src/csharp/SDK/Logger.cs b/src/csharp/SDK/Logger.cs index 50dfecad8..3888f8e93 100644 --- a/src/csharp/SDK/Logger.cs +++ b/src/csharp/SDK/Logger.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -18,12 +18,12 @@ public static class Logger private static readonly NativeMethods.k4a_logging_message_cb_t DebugMessageHandler = OnDebugMessage; private static bool isInitialized; - private static event Action LogMessageHandlers; - +#pragma warning disable CA1003 // Use generic event handler instances /// /// Occurs when the Azure Kinect Sensor SDK delivers a debug message. /// public static event Action LogMessage +#pragma warning restore CA1003 // Use generic event handler instances { add { @@ -47,6 +47,8 @@ public static event Action LogMessage } } + private static event Action LogMessageHandlers; + /// /// Initializes the class to begin receiving messages from the Azure Kinect Sensor SDK. /// diff --git a/src/csharp/SDK/Native/LoggingTracer.cs b/src/csharp/SDK/Native/LoggingTracer.cs index 265ab604c..b79d94ce6 100644 --- a/src/csharp/SDK/Native/LoggingTracer.cs +++ b/src/csharp/SDK/Native/LoggingTracer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -14,7 +14,7 @@ namespace Microsoft.Azure.Kinect.Sensor /// Represents a tracer for capturing thread specific logging messages for tracing native calls /// into the Azure Kinect Sensor SDK. /// - internal class LoggingTracer : IDisposable + public class LoggingTracer : IDisposable { private readonly int threadId; diff --git a/src/csharp/SDK/Native/NativeMethods.cs b/src/csharp/SDK/Native/NativeMethods.cs index 5ea3ec5de..d621ae3b9 100644 --- a/src/csharp/SDK/Native/NativeMethods.cs +++ b/src/csharp/SDK/Native/NativeMethods.cs @@ -5,6 +5,8 @@ // //------------------------------------------------------------------------------ using System; +using System.Globalization; +using System.Linq.Expressions; using System.Numerics; using System.Runtime.InteropServices; using System.Text; @@ -106,6 +108,15 @@ public static extern k4a_result_t k4a_calibration_3d_to_3d( CalibrationDeviceType target_camera, out Vector3 target_point3d); + [DllImport("k4a", CallingConvention = k4aCallingConvention)] + [NativeReference] + public static extern k4a_result_t k4a_calibration_color_2d_to_depth_2d( + [In] ref Calibration calibration, + ref Vector2 source_point2d, + k4a_image_t depth_image, + out Vector2 target_point2d, + out bool valid); + [DllImport("k4a", CallingConvention = k4aCallingConvention)] [NativeReference] public static extern k4a_result_t k4a_calibration_get_from_raw( @@ -130,6 +141,17 @@ public static extern k4a_result_t k4a_transformation_depth_image_to_color_camera k4a_image_t depth_image, k4a_image_t transformed_depth_image); + [DllImport("k4a", CallingConvention = k4aCallingConvention)] + [NativeReference] + public static extern k4a_result_t k4a_transformation_depth_image_to_color_camera_custom( + k4a_transformation_t transformation_handle, + k4a_image_t depth_image, + k4a_image_t custom_image, + k4a_image_t transformed_depth_image, + k4a_image_t transformed_custom_image, + TransformationInterpolationType interpolation_type, + uint invalid_custom_value); + [DllImport("k4a", CallingConvention = k4aCallingConvention)] [NativeReference] public static extern k4a_result_t k4a_transformation_color_image_to_depth_camera( @@ -440,6 +462,17 @@ protected override bool ReleaseHandle() public class k4a_capture_t : Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { + public k4a_capture_t(bool takeOwnership, IntPtr handle) + : base(true) + { + if (!takeOwnership) + { + NativeMethods.k4a_image_reference(handle); + } + + this.handle = handle; + } + private k4a_capture_t() : base(true) { @@ -464,6 +497,17 @@ protected override bool ReleaseHandle() public class k4a_image_t : Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { + public k4a_image_t(bool takeOwnership, IntPtr handle) + : base(true) + { + if (!takeOwnership) + { + NativeMethods.k4a_image_reference(handle); + } + + this.handle = handle; + } + private k4a_image_t() : base(true) { diff --git a/src/csharp/SDK/Transformation.cs b/src/csharp/SDK/Transformation.cs index 8843c0b93..42cbc0234 100644 --- a/src/csharp/SDK/Transformation.cs +++ b/src/csharp/SDK/Transformation.cs @@ -142,6 +142,144 @@ public void DepthImageToColorCamera(Image depth, Image transformed) } } + /// + /// Transforms a depth Image and a custom Image from the depth camera perspective to the color camera perspective. + /// + /// Depth image to transform. + /// Custom image to transform. + /// Parameter that controls how pixels in custom image should be interpolated when transformed to color camera space. + /// Defines the custom image pixel value that should be written to transformedCustom in case the corresponding depth pixel can not be transformed into the color camera space. + /// A depth image transformed in to the color camera perspective. + /// A custom image transformed in to the color camera perspective. + public (Image transformedDepth, Image transformedCustom) DepthImageToColorCameraCustom(Image depth, Image custom, TransformationInterpolationType interpolationType, uint invalidCustomValue) + { + if (depth == null) + { + throw new ArgumentNullException(nameof(depth)); + } + + if (custom == null) + { + throw new ArgumentNullException(nameof(custom)); + } + + Image transformedDepth = new Image( + ImageFormat.Depth16, + this.calibration.ColorCameraCalibration.ResolutionWidth, + this.calibration.ColorCameraCalibration.ResolutionHeight, + this.calibration.ColorCameraCalibration.ResolutionWidth * sizeof(short)) + { + DeviceTimestamp = depth.DeviceTimestamp, + }; + + int bytesPerPixel; + switch (custom.Format) + { + case ImageFormat.Custom8: + bytesPerPixel = sizeof(byte); + break; + case ImageFormat.Custom16: + bytesPerPixel = sizeof(short); + break; + default: + throw new NotSupportedException("Failed to support this format of custom image!"); + } + + Image transformedCustom = new Image( + custom.Format, + this.calibration.ColorCameraCalibration.ResolutionWidth, + this.calibration.ColorCameraCalibration.ResolutionHeight, + this.calibration.ColorCameraCalibration.ResolutionWidth * bytesPerPixel) + { + DeviceTimestamp = custom.DeviceTimestamp, + }; + + this.DepthImageToColorCameraCustom(depth, custom, transformedDepth, transformedCustom, interpolationType, invalidCustomValue); + + return (transformedDepth, transformedCustom); + } + + /// + /// Transforms a depth Image and a custom Image from the depth camera perspective to the color camera perspective. + /// + /// Depth image to transform. + /// Custom image to transform. + /// An transformed depth image to hold the output. + /// An transformed custom image to hold the output. + /// Parameter that controls how pixels in custom image should be interpolated when transformed to color camera space. + /// Defines the custom image pixel value that should be written to transformedCustom in case the corresponding depth pixel can not be transformed into the color camera space. + /// + /// The Image must be of the resolution of the color camera, and + /// of the pixel format of the depth image. + /// The Image must be of the resolution of the color camera, and + /// of the pixel format of the custom image. + /// + public void DepthImageToColorCameraCustom(Image depth, Image custom, Image transformedDepth, Image transformedCustom, TransformationInterpolationType interpolationType, uint invalidCustomValue) + { + if (depth == null) + { + throw new ArgumentNullException(nameof(depth)); + } + + if (custom == null) + { + throw new ArgumentNullException(nameof(custom)); + } + + if (transformedDepth == null) + { + throw new ArgumentNullException(nameof(transformedDepth)); + } + + if (transformedCustom == null) + { + throw new ArgumentNullException(nameof(transformedCustom)); + } + + if (custom.Format != ImageFormat.Custom8 && custom.Format != ImageFormat.Custom16) + { + throw new NotSupportedException("Failed to support this format of custom image!"); + } + + if (transformedCustom.Format != ImageFormat.Custom8 && transformedCustom.Format != ImageFormat.Custom16) + { + throw new NotSupportedException("Failed to support this format of transformed custom image!"); + } + + if (custom.Format != transformedCustom.Format) + { + throw new NotSupportedException("Failed to support this different format of custom image and transformed custom image!!"); + } + + lock (this) + { + // Create a new reference to the Image objects so that they cannot be disposed while + // we are performing the transformation + using (Image depthReference = depth.Reference()) + using (Image customReference = custom.Reference()) + using (Image transformedDepthReference = transformedDepth.Reference()) + using (Image transformedCustomReference = transformedCustom.Reference()) + { + // Ensure changes made to the managed memory are visible to the native layer + depthReference.FlushMemory(); + customReference.FlushMemory(); + + AzureKinectException.ThrowIfNotSuccess(() => NativeMethods.k4a_transformation_depth_image_to_color_camera_custom( + this.handle, + depthReference.DangerousGetHandle(), + customReference.DangerousGetHandle(), + transformedDepthReference.DangerousGetHandle(), + transformedCustom.DangerousGetHandle(), + interpolationType, + invalidCustomValue)); + + // Copy the native memory back to managed memory if required + transformedDepthReference.InvalidateMemory(); + transformedCustom.InvalidateMemory(); + } + } + } + /// /// Transforms an Image from the color camera perspective to the depth camera perspective. /// diff --git a/src/csharp/SDK/TransformationInterpolationType.cs b/src/csharp/SDK/TransformationInterpolationType.cs new file mode 100644 index 000000000..3728ded49 --- /dev/null +++ b/src/csharp/SDK/TransformationInterpolationType.cs @@ -0,0 +1,28 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Azure.Kinect.Sensor +{ + /// + /// Specifies a type of transformation interpolation. + /// + [Native.NativeReference("k4a_transformation_interpolation_type_t")] + public enum TransformationInterpolationType + { + /// + /// Nearest neighbor interpolation. + /// + [Native.NativeReference("K4A_TRANSFORMATION_INTERPOLATION_TYPE_NEAREST")] + Nearest = 0, + + /// + /// Linear interpolation. + /// + [Native.NativeReference("K4A_TRANSFORMATION_INTERPOLATION_TYPE_LINEAR")] + Linear, + } +} diff --git a/src/csharp/Tests/Record.UnitTests/LoopbackTests.cs b/src/csharp/Tests/Record.UnitTests/LoopbackTests.cs new file mode 100644 index 000000000..d80b5b94b --- /dev/null +++ b/src/csharp/Tests/Record.UnitTests/LoopbackTests.cs @@ -0,0 +1,216 @@ +using System; +using Microsoft.Azure.Kinect.Sensor; +using Microsoft.Azure.Kinect.Sensor.Record; +using NUnit.Framework; + +namespace Tests +{ + /// + /// Loopback Tests write to a recording, and then read the recording back to verify the API. + /// + public class LoopbackTests + { + private string recordingPath; + + /// + /// Allocate a path for the recording. + /// + [SetUp] + public void Setup() + { + this.recordingPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "testfile.mkv"); + } + + /// + /// Delete the temporary recording. + /// + [TearDown] + public void TearDown() + { + System.IO.File.Delete(this.recordingPath); + } + + /// + /// Writes each of the data types to a file and reads them back. + /// Verfies as many properties as possible. + /// + [Test] + public void LoopbackTest1() + { + DeviceConfiguration deviceConfiguration = new DeviceConfiguration() + { + CameraFPS = FPS.FPS30, + ColorFormat = ImageFormat.ColorNV12, + ColorResolution = ColorResolution.R720p, + DepthDelayOffColor = TimeSpan.FromMilliseconds(123), + DepthMode = DepthMode.NFOV_2x2Binned, + DisableStreamingIndicator = true, + SuboridinateDelayOffMaster = TimeSpan.FromMilliseconds(456), + SynchronizedImagesOnly = true, + WiredSyncMode = WiredSyncMode.Subordinate, + }; + + using (Recorder record = Recorder.Create(this.recordingPath, null, deviceConfiguration)) +#pragma warning restore CA1508 // Avoid dead conditional code + { + record.AddImuTrack(); + record.AddCustomVideoTrack("CUSTOM_VIDEO", "V_CUSTOM1", new byte[] { 1, 2, 3 }, new RecordVideoSettings() { FrameRate = 1, Height = 10, Width = 20 }); + record.AddCustomSubtitleTrack("CUSTOM_SUBTITLE", "S_CUSTOM1", new byte[] { 4, 5, 6, 7 }, new RecordSubtitleSettings() { HighFrequencyData = false }); + record.AddTag("MYTAG1", "one"); + record.AddTag("MYTAG2", "two"); + + record.WriteHeader(); + + for (int i = 0; i < 10; i++) + { + double timeStamp = 10.0 + (i * 1.0); + +#pragma warning disable CA1508 // Avoid dead conditional code + using (Capture c = new Capture()) +#pragma warning restore CA1508 // Avoid dead conditional code + { + c.Color = new Image(ImageFormat.ColorNV12, 1280, 720); + c.IR = new Image(ImageFormat.IR16, 320, 288); + c.Depth = new Image(ImageFormat.Depth16, 320, 288); + c.Temperature = 25.0f; + c.Color.DeviceTimestamp = TimeSpan.FromSeconds(timeStamp); + c.Depth.DeviceTimestamp = TimeSpan.FromSeconds(timeStamp) + deviceConfiguration.DepthDelayOffColor; + c.IR.DeviceTimestamp = TimeSpan.FromSeconds(timeStamp) + deviceConfiguration.DepthDelayOffColor; + + c.Color.Exposure = TimeSpan.FromMilliseconds(12); + c.Color.ISOSpeed = 100; + c.Color.SystemTimestampNsec = System.Diagnostics.Stopwatch.GetTimestamp(); + c.Color.WhiteBalance = 2; + + c.Depth.SystemTimestampNsec = System.Diagnostics.Stopwatch.GetTimestamp(); + + c.IR.SystemTimestampNsec = System.Diagnostics.Stopwatch.GetTimestamp(); + + record.WriteCapture(c); + } + + for (int y = 0; y < 10; y++) + { + ImuSample imuSample = new ImuSample() + { + AccelerometerSample = new System.Numerics.Vector3(1.0f, 2.0f, 3.0f), + GyroSample = new System.Numerics.Vector3(4.0f, 5.0f, 6.0f), + AccelerometerTimestamp = TimeSpan.FromSeconds(timeStamp + (0.1 * y)), + GyroTimestamp = TimeSpan.FromSeconds(timeStamp + (0.1 * y)), + Temperature = 26.0f, + }; + + record.WriteImuSample(imuSample); + } + + byte[] customData = new byte[i + 1]; + for (int x = 0; x < customData.Length; x++) + { + customData[x] = (byte)(i + x); + } + + record.WriteCustomTrackData("CUSTOM_VIDEO", TimeSpan.FromSeconds(timeStamp), customData); + + for (int x = 0; x < customData.Length; x++) + { + customData[x] = (byte)(i + x + 1); + } + + record.WriteCustomTrackData("CUSTOM_SUBTITLE", TimeSpan.FromSeconds(timeStamp), customData); + + record.Flush(); + } + } + + using (Playback playback = Playback.Open(this.recordingPath)) + { + Assert.IsTrue(playback.CheckTrackExists("CUSTOM_VIDEO")); + Assert.IsTrue(playback.CheckTrackExists("CUSTOM_SUBTITLE")); + Assert.AreEqual("V_CUSTOM1", playback.GetTrackCodecId("CUSTOM_VIDEO")); + Assert.AreEqual(new byte[] { 1, 2, 3 }, playback.GetTrackCodecContext("CUSTOM_VIDEO")); + + Assert.AreEqual("one", playback.GetTag("MYTAG1")); + Assert.AreEqual("two", playback.GetTag("MYTAG2")); + + Assert.AreEqual(FPS.FPS30, playback.RecordConfiguration.CameraFPS); + Assert.AreEqual(ImageFormat.ColorNV12, playback.RecordConfiguration.ColorFormat); + Assert.AreEqual(TimeSpan.FromMilliseconds(456), playback.RecordConfiguration.SubordinateDelayOffMaster); + + Assert.IsFalse(playback.Calibration.HasValue); + + for (int i = 0; i < 10; i++) + { + double timeStamp = 10.0 + (i * 1.0); + + using (Capture c = playback.GetNextCapture()) + { + // Not captured in recording + // Assert.AreEqual(25.0f, c.Temperature); + Assert.AreEqual(ImageFormat.ColorNV12, c.Color.Format); + Assert.AreEqual(1280, c.Color.WidthPixels); + Assert.AreEqual(720, c.Color.HeightPixels); + + Assert.AreEqual(TimeSpan.FromSeconds(timeStamp), c.Color.DeviceTimestamp); + Assert.AreEqual(TimeSpan.FromSeconds(timeStamp) + deviceConfiguration.DepthDelayOffColor, c.Depth.DeviceTimestamp); + Assert.AreEqual(TimeSpan.FromSeconds(timeStamp) + deviceConfiguration.DepthDelayOffColor, c.IR.DeviceTimestamp); + + // Not captured in recording + // Assert.AreEqual(TimeSpan.FromMilliseconds(12), c.Color.Exposure); + + // Not captured in recording + // Assert.AreEqual(100, c.Color.ISOSpeed); + Assert.AreEqual(0, c.Color.SystemTimestampNsec); + + // Not captured in recording + // Assert.AreEqual(2, c.Color.WhiteBalance); + } + + for (int y = 0; y < 10; y++) + { + ImuSample imuSample = new ImuSample() + { + AccelerometerSample = new System.Numerics.Vector3(1.0f, 2.0f, 3.0f), + GyroSample = new System.Numerics.Vector3(4.0f, 5.0f, 6.0f), + AccelerometerTimestamp = TimeSpan.FromSeconds(timeStamp + 0.1 * y), + GyroTimestamp = TimeSpan.FromSeconds(timeStamp + 0.1 * y), + Temperature = 26.0f, + }; + + ImuSample readSample = playback.GetNextImuSample(); + + Assert.AreEqual(imuSample.AccelerometerSample, readSample.AccelerometerSample); + Assert.AreEqual(imuSample.GyroSample, readSample.GyroSample); + Assert.AreEqual(imuSample.AccelerometerTimestamp, readSample.AccelerometerTimestamp); + Assert.AreEqual(imuSample.GyroTimestamp, readSample.GyroTimestamp); + + // Not captured in recording + // Assert.AreEqual(imuSample.Temperature, readSample.Temperature); + } + + byte[] customData = new byte[i + 1]; + for (int x = 0; x < customData.Length; x++) + { + customData[x] = (byte)(i + x); + } + + using (DataBlock videoBlock = playback.GetNextDataBlock("CUSTOM_VIDEO")) + { + Assert.AreEqual(customData, videoBlock.Memory.ToArray()); + Assert.AreEqual(TimeSpan.FromSeconds(timeStamp), videoBlock.DeviceTimestamp); + } + + for (int x = 0; x < customData.Length; x++) + { + customData[x] = (byte)(i + x + 1); + } + + using (DataBlock subtitleBlock = playback.GetNextDataBlock("CUSTOM_SUBTITLE")) + { + Assert.AreEqual(customData, subtitleBlock.Memory.ToArray()); + Assert.AreEqual(TimeSpan.FromSeconds(timeStamp), subtitleBlock.DeviceTimestamp); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/csharp/Tests/Record.UnitTests/Microsoft.Azure.Kinect.Sensor.Record.UnitTests.csproj b/src/csharp/Tests/Record.UnitTests/Microsoft.Azure.Kinect.Sensor.Record.UnitTests.csproj new file mode 100644 index 000000000..4f7a9e27f --- /dev/null +++ b/src/csharp/Tests/Record.UnitTests/Microsoft.Azure.Kinect.Sensor.Record.UnitTests.csproj @@ -0,0 +1,65 @@ + + + + + netcoreapp2.1 + + false + + x64;x86 + false + ..\..\AzureKinectSensorSDK.ruleset + $(BaseOutputPath)\$(AssemblyName)\ + + + + false + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + stylecop.json + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/src/depth/depth.c b/src/depth/depth.c index 2f7f072a8..8f5e48398 100644 --- a/src/depth/depth.c +++ b/src/depth/depth.c @@ -26,14 +26,11 @@ static k4a_version_t g_min_fw_version_rgb = { 1, 5, 92 }; // 1. static k4a_version_t g_min_fw_version_depth = { 1, 5, 66 }; // 1.5.66 static k4a_version_t g_min_fw_version_audio = { 1, 5, 14 }; // 1.5.14 static k4a_version_t g_min_fw_version_depth_config = { 5006, 27, 0 }; // 5006.27 (iteration is not used, set to 0) -static k4a_version_t g_suggested_fw_version_rgb = { 1, 6, 102 }; // 1.6.102 -static k4a_version_t g_suggested_fw_version_depth = { 1, 6, 75 }; // 1.6.75 +static k4a_version_t g_suggested_fw_version_rgb = { 1, 6, 110 }; // 1.6.110 +static k4a_version_t g_suggested_fw_version_depth = { 1, 6, 79 }; // 1.6.79 static k4a_version_t g_suggested_fw_version_audio = { 1, 6, 14 }; // 1.6.14 static k4a_version_t g_suggested_fw_version_depth_config = { 6109, 7, 0 }; // 6109.7 (iteration is not used, set to 0) -#define MINOR_VERSION_OFFSET_1 100 // Some variants of development FW offset minor version with 100 -#define MINOR_VERSION_OFFSET_2 200 // Some variants of development FW offset minor version with 200 - typedef struct _depth_context_t { depthmcu_t depthmcu; @@ -64,59 +61,9 @@ bool is_fw_version_compatable(const char *fw_type, bool is_fw_version_compatable(const char *fw_type, k4a_version_t *fw_version, k4a_version_t *fw_min_version, bool error) { - typedef enum - { - FW_OK, - FW_TOO_LOW, - FW_UNKNOWN - } fw_check_state_t; - - fw_check_state_t fw = FW_UNKNOWN; - - // Check major version - if (fw_version->major > fw_min_version->major) - { - fw = FW_OK; - } - else if (fw_version->major < fw_min_version->major) - { - fw = FW_TOO_LOW; - } - - // Check minor version - if (fw == FW_UNKNOWN) - { - uint32_t minor = fw_version->minor; - if (fw_version->minor > MINOR_VERSION_OFFSET_2) - { - minor = fw_version->minor - MINOR_VERSION_OFFSET_2; - } - else if (fw_version->minor > MINOR_VERSION_OFFSET_1) - { - minor = fw_version->minor - MINOR_VERSION_OFFSET_1; - } - - if (minor > fw_min_version->minor) - { - fw = FW_OK; - } - else if (minor < fw_min_version->minor) - { - fw = FW_TOO_LOW; - } - } - - // Check iteration version - if (fw == FW_UNKNOWN) - { - fw = FW_TOO_LOW; - if (fw_version->iteration >= fw_min_version->iteration) - { - fw = FW_OK; - } - } + bool fw_version_good = k4a_is_version_greater_or_equal(fw_version, fw_min_version); - if (fw != FW_OK) + if (!fw_version_good) { if (error) { @@ -141,7 +88,7 @@ bool is_fw_version_compatable(const char *fw_type, k4a_version_t *fw_version, k4 fw_min_version->iteration); } } - return (fw == FW_OK); + return (fw_version_good); } k4a_result_t depth_create(depthmcu_t depthmcu, diff --git a/src/depth_mcu/depth_mcu.c b/src/depth_mcu/depth_mcu.c index 52ab40ad1..c1582f80c 100644 --- a/src/depth_mcu/depth_mcu.c +++ b/src/depth_mcu/depth_mcu.c @@ -57,7 +57,7 @@ void depthmcu_depth_capture_ready(k4a_result_t status, k4a_image_t image_handle, k4a_result_t depthmcu_create(uint32_t device_index, depthmcu_t *depthmcu_handle) { - RETURN_VALUE_IF_ARG(K4A_RESULT_FAILED, device_index >= 10); + RETURN_VALUE_IF_ARG(K4A_RESULT_FAILED, device_index >= 100); RETURN_VALUE_IF_ARG(K4A_RESULT_FAILED, depthmcu_handle == NULL); depthmcu_context_t *depthmcu = depthmcu_t_create(depthmcu_handle); diff --git a/src/dewrapper/dewrapper.c b/src/dewrapper/dewrapper.c index d640bb9fd..78ab5001c 100644 --- a/src/dewrapper/dewrapper.c +++ b/src/dewrapper/dewrapper.c @@ -135,7 +135,7 @@ static k4a_result_t depth_engine_start_helper(dewrapper_context_t *dewrapper, assert(dewrapper->calibration_memory != NULL); // Max comput time is the configured FPS - *depth_engine_max_compute_time_ms = 1000 / k4a_convert_fps_to_uint(fps); + *depth_engine_max_compute_time_ms = HZ_TO_PERIOD_MS(k4a_convert_fps_to_uint(fps)); result = K4A_RESULT_FROM_BOOL(*depth_engine_max_compute_time_ms != 0); if (K4A_SUCCEEDED(result)) diff --git a/src/record/internal/matroska_write.cpp b/src/record/internal/matroska_write.cpp index a1a452672..1f0f96d0c 100644 --- a/src/record/internal/matroska_write.cpp +++ b/src/record/internal/matroska_write.cpp @@ -474,11 +474,23 @@ static void matroska_writer_thread(k4a_record_context_t *context) if (!context->pending_clusters.empty()) { oldest_cluster = context->pending_clusters.front(); - if (oldest_cluster->time_end_ns + CLUSTER_WRITE_DELAY_NS < context->most_recent_timestamp) + if (context->most_recent_timestamp >= oldest_cluster->time_end_ns) { - assert(oldest_cluster->time_start_ns >= context->last_written_timestamp); - context->pending_clusters.pop_front(); - context->last_written_timestamp = oldest_cluster->time_end_ns; + uint64_t age = context->most_recent_timestamp - oldest_cluster->time_end_ns; + if (age > CLUSTER_WRITE_DELAY_NS) + { + assert(oldest_cluster->time_start_ns >= context->last_written_timestamp); + context->pending_clusters.pop_front(); + context->last_written_timestamp = oldest_cluster->time_end_ns; + if (age > CLUSTER_WRITE_QUEUE_WARNING_NS) + { + LOG_ERROR("Disk write speed is too low, write queue is filling up.", 0); + } + } + else + { + oldest_cluster = NULL; + } } else { diff --git a/src/record/sdk/CMakeLists.txt b/src/record/sdk/CMakeLists.txt index dfdce24b5..2e2329552 100644 --- a/src/record/sdk/CMakeLists.txt +++ b/src/record/sdk/CMakeLists.txt @@ -39,12 +39,15 @@ target_include_directories(k4arecord PUBLIC target_link_libraries(k4arecord PRIVATE k4ainternal::record k4ainternal::playback - k4a::k4a k4ainternal::logging ebml::ebml matroska::matroska ) +target_link_libraries(k4arecord PUBLIC + k4a::k4a +) + # Define alias for k4arecord add_library(k4a::k4arecord ALIAS k4arecord) diff --git a/src/record/sdk/record.cpp b/src/record/sdk/record.cpp index 97d8ede60..e0dfa1f2b 100644 --- a/src/record/sdk/record.cpp +++ b/src/record/sdk/record.cpp @@ -82,6 +82,9 @@ k4a_result_t k4a_record_create(const char *path, case K4A_IMAGE_FORMAT_COLOR_MJPG: color_mode_str << "MJPG_" << color_height << "P"; break; + case K4A_IMAGE_FORMAT_COLOR_BGRA32: + color_mode_str << "BGRA_" << color_height << "P"; + break; default: LOG_ERROR("Unsupported color_format specified in recording: %d", device_config.color_format); result = K4A_RESULT_FAILED; @@ -373,7 +376,10 @@ k4a_result_t k4a_record_add_tag(const k4a_record_t recording_handle, const char return K4A_RESULT_FAILED; } - add_tag(context, name, value); + if (NULL == add_tag(context, name, value)) + { + return K4A_RESULT_FAILED; + } return K4A_RESULT_SUCCEEDED; } diff --git a/src/sdk/k4a.c b/src/sdk/k4a.c index 5b74f9ec8..c97641d6d 100644 --- a/src/sdk/k4a.c +++ b/src/sdk/k4a.c @@ -681,13 +681,14 @@ static k4a_result_t validate_configuration(k4a_context_t *device, const k4a_devi if (config->wired_sync_mode == K4A_WIRED_SYNC_MODE_SUBORDINATE && config->subordinate_delay_off_master_usec != 0) { - uint32_t fps_in_usec = 1000000 / k4a_convert_fps_to_uint(config->camera_fps); + uint32_t fps_in_usec = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(config->camera_fps)); if (config->subordinate_delay_off_master_usec > fps_in_usec) { result = K4A_RESULT_FAILED; - LOG_ERROR( - "The configured subordinate device delay from the master device cannot exceed one frame interval.", - 0); + LOG_ERROR("The configured subordinate device delay from the master device cannot exceed one frame " + "interval of %d. User requested %d", + fps_in_usec, + config->subordinate_delay_off_master_usec); } } @@ -715,11 +716,14 @@ static k4a_result_t validate_configuration(k4a_context_t *device, const k4a_devi if (depth_enabled && color_enabled) { - int64_t fps = 1000000 / k4a_convert_fps_to_uint(config->camera_fps); + int64_t fps = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(config->camera_fps)); if (config->depth_delay_off_color_usec < -fps || config->depth_delay_off_color_usec > fps) { result = K4A_RESULT_FAILED; - LOG_ERROR("The configured depth_delay_off_color_usec must be within +/- one frame interval.", 0); + LOG_ERROR("The configured depth_delay_off_color_usec must be within +/- one frame interval of %d. User " + "requested %d", + fps, + config->depth_delay_off_color_usec); } } else if (!depth_enabled && !color_enabled) diff --git a/src/transformation/intrinsic_transformation.c b/src/transformation/intrinsic_transformation.c index 0cfdd9391..200f06e89 100644 --- a/src/transformation/intrinsic_transformation.c +++ b/src/transformation/intrinsic_transformation.c @@ -6,6 +6,11 @@ #include +// We don't like globals if we can help it. This one is for reducing critical logging noise when recorded files are used +// with Rational 6KT calibration. Production devices never had this calibration but recordings were made with this +// calibration. So we fire the warning 1 time instead of every time a transformation call is made +static int g_deprecated_6kt_message_fired = false; + static k4a_result_t transformation_project_internal(const k4a_calibration_camera_t *camera_calibration, const float xy[2], float uv[2], @@ -32,8 +37,10 @@ static k4a_result_t transformation_project_internal(const k4a_calibration_camera return K4A_RESULT_FAILED; } - if (camera_calibration->intrinsics.type == K4A_CALIBRATION_LENS_DISTORTION_MODEL_RATIONAL_6KT) + if (camera_calibration->intrinsics.type == K4A_CALIBRATION_LENS_DISTORTION_MODEL_RATIONAL_6KT && + g_deprecated_6kt_message_fired == false) { + g_deprecated_6kt_message_fired = true; LOG_CRITICAL("Rational 6KT is deprecated (only supported early internal devices). Please replace your Azure " "Kinect with a retail device.", 0); @@ -248,8 +255,10 @@ static k4a_result_t transformation_unproject_internal(const k4a_calibration_came return K4A_RESULT_FAILED; } - if (camera_calibration->intrinsics.type == K4A_CALIBRATION_LENS_DISTORTION_MODEL_RATIONAL_6KT) + if (camera_calibration->intrinsics.type == K4A_CALIBRATION_LENS_DISTORTION_MODEL_RATIONAL_6KT && + g_deprecated_6kt_message_fired == false) { + g_deprecated_6kt_message_fired = true; LOG_CRITICAL("Rational 6KT is deprecated (only supported early internal devices). Please replace your Azure " "Kinect with a retail device.", 0); @@ -309,7 +318,7 @@ static k4a_result_t transformation_unproject_internal(const k4a_calibration_came float yy = xy[1] * xy[1]; xy[0] -= (yy + 3.f * xx) * p2 + two_xy * p1; - xy[1] -= (xx + 3.f * xx) * p1 + two_xy * p2; + xy[1] -= (xx + 3.f * yy) * p1 + two_xy * p2; // add on center of distortion xy[0] += codx; diff --git a/src/transformation/rgbz.c b/src/transformation/rgbz.c index 31da02a31..4243d6fb0 100644 --- a/src/transformation/rgbz.c +++ b/src/transformation/rgbz.c @@ -8,9 +8,15 @@ #include #include +#if defined(__amd64__) || defined(_M_AMD64) || defined(__i386__) || defined(_M_IX86) +#define K4A_USING_SSE #include // SSE2 #include // SSE3 #include // SSE4.1 +#elif defined(__aarch64__) || defined(_M_ARM64) +#define K4A_USING_NEON +#include +#endif typedef struct _k4a_transformation_input_image_t { @@ -54,6 +60,28 @@ typedef struct _k4a_bounding_box_t int bottom_right[2]; } k4a_bounding_box_t; +// g_transformation_instruction_type is set to SSE, NEON, None, or NULL +static char g_transformation_instruction_type[5] = { 0 }; + +// Share g_transformation_instruction_type with tests to confirm this is built correctly. +char *transformation_get_instruction_type(void); +char *transformation_get_instruction_type(void) +{ + return g_transformation_instruction_type; +} + +// Set the special instruction +static void set_special_instruction_optimization(char *opt) +{ + // Only set this once + if (g_transformation_instruction_type[0] == '\0') + { + size_t sz = MIN(sizeof(opt), sizeof(g_transformation_instruction_type) - 1); + memcpy(g_transformation_instruction_type, opt, sz); + LOG_INFO("Compiled special instruction type is: %s\n", opt); + } +} + static k4a_transformation_image_descriptor_t transformation_init_image_descriptor(int width, int height, int stride, k4a_image_format_t format) { @@ -73,8 +101,8 @@ static bool transformation_compare_image_descriptors(const k4a_transformation_im descriptor1->stride_bytes != descriptor2->stride_bytes || descriptor1->format != descriptor2->format) { LOG_ERROR("Unexpected image descriptor. " - "Expected width_pixels: %d, height_pixels: %d, stride_bytes: %d, format: %d. " - "Actual width_pixels: %d, height_pixels: %d, stride_bytes: %d, format: %d. ", + "Descriptor 1: width_pixels: %d, height_pixels: %d, stride_bytes: %d, format: %d. " + "Descriptor 2: width_pixels: %d, height_pixels: %d, stride_bytes: %d, format: %d. ", descriptor1->width_pixels, descriptor1->height_pixels, descriptor1->stride_bytes, @@ -1055,42 +1083,108 @@ k4a_buffer_result_t transformation_color_image_to_depth_camera_internal( return K4A_BUFFER_RESULT_SUCCEEDED; } +#if !defined(K4A_USING_SSE) && !defined(K4A_USING_NEON) // This is the same function as transformation_depth_to_xyz without the SSE // instructions. This code is kept here for readability. -// static void transformation_depth_to_xyz(k4a_transformation_xy_tables_t *xy_tables, -// const void *depth_image_data, -// void *xyz_image_data) -// { -// const uint16_t *depth_image_data_uint16 = (const uint16_t *)depth_image_data; -// int16_t *xyz_data_int16 = (int16_t *)xyz_image_data; -// int16_t x, y, z; - -// for (int i = 0; i < xy_tables->width * xy_tables->height; i++) -// { -// float x_tab = xy_tables->x_table[i]; - -// if (!isnan(x_tab)) -// { -// z = (int16_t)depth_image_data_uint16[i]; -// x = (int16_t)(floorf(x_tab * (float)z + 0.5f)); -// y = (int16_t)(floorf(xy_tables->y_table[i] * (float)z + 0.5f)); -// } -// else -// { -// x = 0; -// y = 0; -// z = 0; -// } - -// xyz_data_int16[3 * i + 0] = x; -// xyz_data_int16[3 * i + 1] = y; -// xyz_data_int16[3 * i + 2] = z; -// } -// } - -static void transformation_depth_to_xyz_sse(k4a_transformation_xy_tables_t *xy_tables, - const void *depth_image_data, - void *xyz_image_data) +static void transformation_depth_to_xyz(k4a_transformation_xy_tables_t *xy_tables, + const void *depth_image_data, + void *xyz_image_data) +{ + const uint16_t *depth_image_data_uint16 = (const uint16_t *)depth_image_data; + int16_t *xyz_data_int16 = (int16_t *)xyz_image_data; + int16_t x, y, z; + + set_special_instruction_optimization("None"); + + for (int i = 0; i < xy_tables->width * xy_tables->height; i++) + { + float x_tab = xy_tables->x_table[i]; + + if (!isnan(x_tab)) + { + z = (int16_t)depth_image_data_uint16[i]; + x = (int16_t)(floorf(x_tab * (float)z + 0.5f)); + y = (int16_t)(floorf(xy_tables->y_table[i] * (float)z + 0.5f)); + } + else + { + x = 0; + y = 0; + z = 0; + } + + xyz_data_int16[3 * i + 0] = x; + xyz_data_int16[3 * i + 1] = y; + xyz_data_int16[3 * i + 2] = z; + } +} + +#elif defined(K4A_USING_NEON) +// convert from float to int using NEON is round to zero +// make separate function to do floor +static inline int32x4_t neon_floor(float32x4_t v) +{ + int32x4_t v0 = vcvtq_s32_f32(v); + int32x4_t a0 = vreinterpretq_s32_u32(vcgtq_f32(vcvtq_f32_s32(v0), v)); + return vaddq_s32(v0, a0); +} + +static void transformation_depth_to_xyz(k4a_transformation_xy_tables_t *xy_tables, + const void *depth_image_data, + void *xyz_image_data) +{ + float *x_tab = (float *)xy_tables->x_table; + float *y_tab = (float *)xy_tables->y_table; + const uint16_t *depth_image_data_uint16 = (const uint16_t *)depth_image_data; + int16_t *xyz_data_int16 = (int16_t *)xyz_image_data; + float32x4_t half = vdupq_n_f32(0.5f); + + set_special_instruction_optimization("NEON"); + + for (int i = 0; i < xy_tables->width * xy_tables->height / 8; i++) + { + // 8 elements in 1 loop + int offset = i * 8; + float32x4_t x_tab_lo = vld1q_f32(x_tab + offset); + float32x4_t x_tab_hi = vld1q_f32(x_tab + offset + 4); + // equivalent to isnan + uint32x4_t valid_lo = vceqq_f32(x_tab_lo, x_tab_lo); + uint32x4_t valid_hi = vceqq_f32(x_tab_hi, x_tab_hi); + // each element in valid is a mask which corresponds to isnan + uint16x8_t valid = vcombine_u16(vmovn_u32(valid_lo), vmovn_u32(valid_hi)); + uint16x8_t v_0 = vandq_u16(vld1q_u16(depth_image_data_uint16 + offset), valid); + // v_z corresponds to z in naive code + int16x8_t v_z = vreinterpretq_s16_u16(v_0); + // expand v_z to compute x and y + float32x4_t v_z_lo = vcvtq_f32_u32(vmovl_u16(vget_low_u16(v_0))); + float32x4_t v_z_hi = vcvtq_f32_u32(vmovl_u16(vget_high_u16(v_0))); + // load x_table and y_table + float32x4_t t_x_lo = vld1q_f32(x_tab + offset); + float32x4_t t_x_hi = vld1q_f32(x_tab + offset + 4); + float32x4_t t_y_lo = vld1q_f32(y_tab + offset); + float32x4_t t_y_hi = vld1q_f32(y_tab + offset + 4); + // main computation of x and y + int32x4_t v_x_lo = neon_floor(vmlaq_f32(half, v_z_lo, t_x_lo)); + int32x4_t v_x_hi = neon_floor(vmlaq_f32(half, v_z_hi, t_x_hi)); + int32x4_t v_y_lo = neon_floor(vmlaq_f32(half, v_z_lo, t_y_lo)); + int32x4_t v_y_hi = neon_floor(vmlaq_f32(half, v_z_hi, t_y_hi)); + int16x8_t v_x = vcombine_s16(vmovn_s32(v_x_lo), vmovn_s32(v_x_hi)); + int16x8_t v_y = vcombine_s16(vmovn_s32(v_y_lo), vmovn_s32(v_y_hi)); + // use scatter store instruction + int16x8x3_t store; + store.val[0] = v_x; // x0 x1 .. x14 x15 + store.val[1] = v_y; // y0 y1 .. y14 y15 + store.val[2] = v_z; // z0 z1 .. z14 z15 + // x0 y0 z0 x1 y1 z1 .. x15 y15 z15 + vst3q_s16(xyz_data_int16 + offset * 3, store); + } +} + +#else /* defined(K4A_USING_SSE) */ + +static void transformation_depth_to_xyz(k4a_transformation_xy_tables_t *xy_tables, + const void *depth_image_data, + void *xyz_image_data) { const __m128i *depth_image_data_m128i = (const __m128i *)depth_image_data; #if defined(__clang__) || defined(__GNUC__) @@ -1104,6 +1198,8 @@ static void transformation_depth_to_xyz_sse(k4a_transformation_xy_tables_t *xy_t __m128 *y_table_m128 = (__m128 *)y_table; __m128i *xyz_data_m128i = (__m128i *)xyz_image_data; + set_special_instruction_optimization("SSE"); + const int16_t pos0 = 0x0100; const int16_t pos1 = 0x0302; const int16_t pos2 = 0x0504; @@ -1159,6 +1255,7 @@ static void transformation_depth_to_xyz_sse(k4a_transformation_xy_tables_t *xy_t *xyz_data_m128i++ = _mm_blend_epi16(_mm_blend_epi16(x, y, 0x49), z, 0x92); } } +#endif k4a_buffer_result_t transformation_depth_image_to_point_cloud_internal(k4a_transformation_xy_tables_t *xy_tables, @@ -1207,7 +1304,7 @@ transformation_depth_image_to_point_cloud_internal(k4a_transformation_xy_tables_ return K4A_BUFFER_RESULT_FAILED; } - transformation_depth_to_xyz_sse(xy_tables, (const void *)depth_image_data, (void *)xyz_image_data); + transformation_depth_to_xyz(xy_tables, (const void *)depth_image_data, (void *)xyz_image_data); return K4A_BUFFER_RESULT_SUCCEEDED; } diff --git a/src/usbcommand/usbcommand.c b/src/usbcommand/usbcommand.c index 5f341a791..0d841972f 100644 --- a/src/usbcommand/usbcommand.c +++ b/src/usbcommand/usbcommand.c @@ -319,6 +319,12 @@ static k4a_result_t find_libusb_device(uint32_t device_index, // We have a container ID match found = true; } + else + { + char container_id_string[UUID_STR_LENGTH]; + uuid_to_string(&usbcmd->container_id, container_id_string, sizeof(container_id_string)); + LOG_INFO("Found non matching Container ID: %s ", container_id_string); + } } if (!found) @@ -712,7 +718,7 @@ static k4a_result_t usb_cmd_io(usbcmd_t usbcmd_handle, (response_packet.packet_type != USB_CMD_PACKET_TYPE_RESPONSE)) { LOG_ERROR("Command(%08X) sequence ended in failure, " - "transationId %08X == %08X " + "TransactionId %08X == %08X " "Response size 0x%08X == 0x%08X " "Packet status 0x%08x == 0x%08x " "Packet type 0x%08x == 0x%08x", @@ -1026,7 +1032,7 @@ k4a_result_t usb_cmd_get_device_count(uint32_t *p_device_count) LOG_ERROR("List too large", 0); return K4A_RESULT_FAILED; } - if (count == 0) + if (count <= 0) { LOG_ERROR("No devices found", 0); return K4A_RESULT_FAILED; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 13f4ad355..11ef0ab66 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,6 +12,7 @@ add_subdirectory(executables) add_subdirectory(ExternLibraries) add_subdirectory(FirmwareTests) add_subdirectory(global) +add_subdirectory(latency) add_subdirectory(logging) add_subdirectory(IMUTests) add_subdirectory(multidevice) diff --git a/tests/ColorTests/FunctionalTest/color_ft.cpp b/tests/ColorTests/FunctionalTest/color_ft.cpp index 75907e738..8a9df2692 100644 --- a/tests/ColorTests/FunctionalTest/color_ft.cpp +++ b/tests/ColorTests/FunctionalTest/color_ft.cpp @@ -716,7 +716,7 @@ int32_t color_control_test::map_manual_exposure(int32_t value, bool sixty_hertz) // Limit exposure setting based on FPS setting. int32_t color_control_test::limit_exposure_to_fps_setting(int32_t value, bool sixty_hertz, k4a_fps_t fps) { - int fps_usec = 1000000 / k4a_convert_fps_to_uint(fps); + int fps_usec = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(fps)); int last_exposure; if (value < fps_usec) @@ -745,23 +745,6 @@ int32_t color_control_test::limit_exposure_to_fps_setting(int32_t value, bool si // returns true if the exposure setting on the read image has been updated. bool color_control_test::validate_image_exposure_setting(int test_value, bool sixty_hertz, k4a_fps_t fps) { - // TODO remove this if block to test 50Hz and values larger than 10,000us - // https://github.com/microsoft/Azure-Kinect-Sensor-SDK/issues/448 - // Fixed by firmware version 1.6.104 - if ((!sixty_hertz && test_value >= 10000) /* Ignoring 50Hz setting */) - { - return true; - } -#ifndef _WIN32 - // TODO remove this if block to test 60Hz and exposures of 8,330us and 16,670us - // https://github.com/microsoft/Azure-Kinect-Sensor-SDK/issues/792 - if (sixty_hertz && (test_value == 8330 || test_value == 16670 || test_value == 33330 || test_value == 41670 || - test_value == 66670 || test_value == 50000 || test_value == 83330 || test_value == 100000 || - test_value == 116670 || test_value == 133330)) - { - return true; - } -#endif test_value = limit_exposure_to_fps_setting(test_value, sixty_hertz, fps); @@ -947,11 +930,7 @@ void color_control_test::control_test_worker(const k4a_color_control_command_t c TEST_P(color_control_test, control_test) { auto as = GetParam(); - if (as.command != K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE) - { - control_test_worker(as.command, as.default_mode, as.default_value); - } - else + if (as.command == K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE) { SET_POWER_LINE_FREQ(K4A_POWER_LINE_60HZ); control_test_worker(as.command, as.default_mode, EXPOSURE_TIME_ABSOLUTE_CONTROL_DEFAULT_60_HZ_VALUE); @@ -959,6 +938,24 @@ TEST_P(color_control_test, control_test) SET_POWER_LINE_FREQ(K4A_POWER_LINE_50HZ); control_test_worker(as.command, as.default_mode, EXPOSURE_TIME_ABSOLUTE_CONTROL_DEFAULT_50_HZ_VALUE); } + else if (as.command == K4A_COLOR_CONTROL_GAIN) + { + k4a_hardware_version_t version; + ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_get_version(m_device, &version)); + k4a_version_t new_gain_default = { 1, 6, 107 }; + if (k4a_is_version_greater_or_equal(&version.rgb, &new_gain_default)) + { + control_test_worker(as.command, as.default_mode, 128); + } + else + { + control_test_worker(as.command, as.default_mode, 0); + } + } + else + { + control_test_worker(as.command, as.default_mode, as.default_value); + } } INSTANTIATE_TEST_CASE_P( diff --git a/tests/IMUTests/FunctionalTest/imu_ft.cpp b/tests/IMUTests/FunctionalTest/imu_ft.cpp index 14a1fdccb..9d09841f3 100644 --- a/tests/IMUTests/FunctionalTest/imu_ft.cpp +++ b/tests/IMUTests/FunctionalTest/imu_ft.cpp @@ -11,7 +11,9 @@ //**************Symbolic Constant Macros (defines) ************* #define STREAM_RUN_TIME_SEC 4 +#define STARTUP_DELAY_5S_IN_USEC (5 * 1000 * 1000) #define ERROR_START_STREAM_TIME 10000 +#define SECOND_TO_MICROSECONDS(sec) (sec * 1000 * 1000) // Total ACC range is +/- 147.15 m/s^2. #define MIN_ACC_READING -15.0f @@ -78,35 +80,71 @@ static bool is_float_in_range(float value, float min, float max, const char *des static void RunStreamConfig(k4a_device_t device, uint32_t expected_fps) { uint32_t stream_count; - int32_t timeout_ms; + int32_t timeout_ms = ERROR_START_STREAM_TIME; k4a_imu_sample_t imu_sample; TICK_COUNTER_HANDLE tick_count; tickcounter_ms_t start_ms; tickcounter_ms_t end_ms; tickcounter_ms_t delta_ms; uint32_t error_tolerance; + int first_sample_inspected = 0; + k4a_wait_result_t wresult = K4A_WAIT_RESULT_SUCCEEDED; + k4a_device_configuration_t config; - stream_count = STREAM_RUN_TIME_SEC * expected_fps; tick_count = tickcounter_create(); - k4a_device_configuration_t config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL; + { + // Typically this code only causes a delay when k4a_device_start_cameras was called less than + // STARTUP_DELAY_5S_IN_USEC seconds ago. Delay start of test upto 5 sec - IMU / Color camera firmware take a + // couple seconds to zero out timestamps. The SDK's color module may not properly filter out timestamps that + // will go backwards if started while the free running timestamp on the firmware is under 5s from the previous + // start. This is directly related to how the IMU module uses "color_camera_start_tick" + config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL; + config.depth_mode = K4A_DEPTH_MODE_PASSIVE_IR; + ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_cameras(device, &config)); + ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_imu(device)); + ASSERT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_imu_sample(device, &imu_sample, timeout_ms)); + while (imu_sample.acc_timestamp_usec < STARTUP_DELAY_5S_IN_USEC) + { + ASSERT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_imu_sample(device, &imu_sample, timeout_ms)); + } + k4a_device_stop_imu(device); + k4a_device_stop_cameras(device); + } + config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL; config.color_format = K4A_IMAGE_FORMAT_COLOR_MJPG; config.color_resolution = K4A_COLOR_RESOLUTION_2160P; config.depth_mode = K4A_DEPTH_MODE_NFOV_UNBINNED; config.camera_fps = K4A_FRAMES_PER_SECOND_30; + config.synchronized_images_only = false; ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_cameras(device, &config)); // start streaming. ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_imu(device)); - // allow stream start time + // Allow stream start time by tossing out first 'n' samples timeout_ms = ERROR_START_STREAM_TIME; - ASSERT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_imu_sample(device, &imu_sample, timeout_ms)); + stream_count = 0; + while (wresult != K4A_WAIT_RESULT_FAILED && stream_count < 10) + { + k4a_capture_t capture; + // Toss out the first n samples + ASSERT_NE(wresult = k4a_device_get_capture(device, &capture, timeout_ms), K4A_WAIT_RESULT_FAILED); + k4a_capture_release(capture); + stream_count++; + } + + // Drain IMU queue + while (wresult == K4A_WAIT_RESULT_SUCCEEDED) + { + ASSERT_NE(wresult = k4a_device_get_imu_sample(device, &imu_sample, 0), K4A_WAIT_RESULT_FAILED); + } // Start clock on getting frames tickcounter_get_current_ms(tick_count, &start_ms); - timeout_ms = 2000; + + stream_count = STREAM_RUN_TIME_SEC * expected_fps; uint64_t last_gyro_dev_ts = 0; uint64_t last_acc_dev_ts = 0; @@ -114,10 +152,27 @@ static void RunStreamConfig(k4a_device_t device, uint32_t expected_fps) { // get frames as available ASSERT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_imu_sample(device, &imu_sample, timeout_ms)); - + if (!first_sample_inspected) + { + // Time stamps should not go backwards and the first time stamps should be around zero as the color camera + // staring will device time stamps reset to zero. + ASSERT_LT(imu_sample.acc_timestamp_usec, SECOND_TO_MICROSECONDS(2)); + ASSERT_LT(imu_sample.gyro_timestamp_usec, SECOND_TO_MICROSECONDS(2)); + std::cout << "Initial Timestamps are: " << imu_sample.gyro_timestamp_usec << " and " + << imu_sample.gyro_timestamp_usec << "\n"; + first_sample_inspected = 1; + } + else + { + // Make sure not more than 10 samples were dropped. + ASSERT_LT(imu_sample.acc_timestamp_usec - last_acc_dev_ts, 10 * 1000000 / K4A_IMU_SAMPLE_RATE) + << " Last Sample " << last_acc_dev_ts << " Current Sample " << imu_sample.acc_timestamp_usec << "\n"; + ASSERT_LT(imu_sample.gyro_timestamp_usec - last_gyro_dev_ts, 10 * 1000000 / K4A_IMU_SAMPLE_RATE) + << " Last Sample " << last_gyro_dev_ts << " Current Sample " << imu_sample.gyro_timestamp_usec << "\n"; + } ASSERT_GT(imu_sample.acc_timestamp_usec, last_acc_dev_ts); - last_acc_dev_ts = imu_sample.acc_timestamp_usec; ASSERT_GT(imu_sample.gyro_timestamp_usec, last_gyro_dev_ts); + last_acc_dev_ts = imu_sample.acc_timestamp_usec; last_gyro_dev_ts = imu_sample.gyro_timestamp_usec; ASSERT_NE(imu_sample.temperature, 0); @@ -128,6 +183,19 @@ static void RunStreamConfig(k4a_device_t device, uint32_t expected_fps) ASSERT_EQ(true, is_float_in_range(imu_sample.gyro_sample.xyz.y, MIN_GYRO_READING, MAX_GYRO_READING, "GYRO_Y")); ASSERT_EQ(true, is_float_in_range(imu_sample.gyro_sample.xyz.z, MIN_GYRO_READING, MAX_GYRO_READING, "GYRO_Z")); + k4a_capture_t capture; + ASSERT_NE(wresult = k4a_device_get_capture(device, &capture, 0), K4A_WAIT_RESULT_FAILED); + if (wresult == K4A_WAIT_RESULT_SUCCEEDED) + { + // printf("IMU PTS delta %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 " \n", + // (int64_t)imu_sample.gyro_timestamp_usec - ts_c_dev, + // (int64_t)imu_sample.acc_timestamp_usec - ts_c_dev, + // (int64_t)imu_sample.gyro_timestamp_usec - ts_ir_dev, + // (int64_t)imu_sample.acc_timestamp_usec - ts_ir_dev); + + k4a_capture_release(capture); + } + stream_count--; }; diff --git a/tests/RecordTests/FunctionalTest/CMakeLists.txt b/tests/RecordTests/FunctionalTest/CMakeLists.txt index b9d4a32eb..9dcba4fa8 100644 --- a/tests/RecordTests/FunctionalTest/CMakeLists.txt +++ b/tests/RecordTests/FunctionalTest/CMakeLists.txt @@ -1,3 +1,11 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +add_executable(k4a_cpp_ft k4a_cpp_ft.cpp) + +target_link_libraries(k4a_cpp_ft PRIVATE + k4ainternal::utcommon + k4a::k4arecord +) + +k4a_add_tests(TARGET k4a_cpp_ft TEST_TYPE FUNCTIONAL) diff --git a/tests/RecordTests/FunctionalTest/k4a_cpp_ft.cpp b/tests/RecordTests/FunctionalTest/k4a_cpp_ft.cpp new file mode 100644 index 000000000..819284ddb --- /dev/null +++ b/tests/RecordTests/FunctionalTest/k4a_cpp_ft.cpp @@ -0,0 +1,553 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include +#include +#include + +#include + +using namespace k4a; + +const std::string MKV_FILE_NAME("./k4a_cpp_ft.mkv"); +const std::string MKV_FILE_NAME_2ND("./k4a_cpp_ft_2.mkv"); + +int main(int argc, char **argv) +{ + return k4a_test_common_main(argc, argv); +} + +class k4a_cpp_ft : public ::testing::Test +{ +public: + virtual void SetUp() + { + // remove old test files, incase old test run crashed + std::remove(MKV_FILE_NAME.c_str()); + std::remove(MKV_FILE_NAME_2ND.c_str()); + } + + virtual void TearDown() {} +}; + +static void use_device_in_a_function(const device &kinect) +{ + k4a_hardware_version_t version = kinect.get_version(); + ASSERT_GE(version.rgb.major, (uint32_t)1); + ASSERT_GE(version.rgb.minor, (uint32_t)6); + ASSERT_GE(version.depth.major, (uint32_t)1); + ASSERT_GE(version.depth.minor, (uint32_t)6); + ASSERT_GE(version.audio.major, (uint32_t)1); + ASSERT_GE(version.audio.minor, (uint32_t)6); + ASSERT_EQ(version.firmware_build, K4A_FIRMWARE_BUILD_RELEASE); + ASSERT_EQ(version.firmware_signature, K4A_FIRMWARE_SIGNATURE_MSFT); +} + +TEST_F(k4a_cpp_ft, k4a) +{ + device kinect = device::open(0); + ASSERT_TRUE(kinect); + ASSERT_TRUE(kinect.is_valid()); + kinect.close(); + ASSERT_FALSE(kinect); + ASSERT_FALSE(kinect.is_valid()); + + kinect = device::open(0); + ASSERT_TRUE(kinect); + ASSERT_TRUE(kinect.is_valid()); + + { + device kinect2; + ASSERT_FALSE(kinect2); + } + + ASSERT_THROW(device kinect2 = device::open(0), error); + kinect.close(); + kinect = kinect.open(0); + + { + ASSERT_LE((uint32_t)1, device::get_installed_count()); + } + + { + // assignment operation deleted, make sure we can still pass + use_device_in_a_function(kinect); + + // device kinect3 = kinect; // This assignment operator is deleted. + } + + // should not throw exception + (void)kinect.is_sync_out_connected(); + (void)kinect.is_sync_in_connected(); + + { + // should not throw exception + calibration cal = kinect.get_calibration(K4A_DEPTH_MODE_NFOV_2X2BINNED, K4A_COLOR_RESOLUTION_1440P); + calibration cal2 = cal; + ASSERT_EQ(cal.color_resolution, cal2.color_resolution); + } + + { + std::vector raw_cal = kinect.get_raw_calibration(); + calibration cal = kinect.get_calibration(K4A_DEPTH_MODE_NFOV_2X2BINNED, K4A_COLOR_RESOLUTION_1440P); + ASSERT_EQ(cal.color_resolution, K4A_COLOR_RESOLUTION_1440P); + + cal = calibration::get_from_raw(raw_cal.data(), + raw_cal.size(), + K4A_DEPTH_MODE_NFOV_2X2BINNED, + K4A_COLOR_RESOLUTION_1080P); + ASSERT_EQ(cal.color_resolution, K4A_COLOR_RESOLUTION_1080P); + } + + { + k4a_color_control_mode_t mode; + int32_t value; + kinect.set_color_control(K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE, K4A_COLOR_CONTROL_MODE_AUTO, 0); + kinect.get_color_control(K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE, &mode, &value); + ASSERT_EQ(K4A_COLOR_CONTROL_MODE_AUTO, mode); + } + + { + std::string sernum = kinect.get_serialnum(); + } + + { + k4a_imu_sample_t sample = { 0 }; + capture cap1, cap2; + k4a_device_configuration_t config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL; + config.color_resolution = K4A_COLOR_RESOLUTION_1080P; + config.depth_mode = K4A_DEPTH_MODE_PASSIVE_IR; + config.synchronized_images_only = true; + kinect.start_cameras(&config); + kinect.start_imu(); + ASSERT_TRUE(kinect.get_capture(&cap1)); + ASSERT_TRUE(kinect.get_capture(&cap2)); + ASSERT_TRUE(kinect.get_imu_sample(&sample)); + ASSERT_TRUE(cap1 != cap2); + kinect.stop_cameras(); + kinect.stop_imu(); + + ASSERT_NE(sample.acc_timestamp_usec, 0); + ASSERT_NE(sample.gyro_timestamp_usec, 0); + + ASSERT_LT(0, cap1.get_temperature_c()); + ASSERT_LT(0, cap2.get_temperature_c()); + cap1.set_temperature_c(0.0f); + cap2.set_temperature_c(0.0f); + + { + capture shallow_copy; + capture moved_copy; + shallow_copy = cap1; // test = operator + ASSERT_TRUE(shallow_copy == cap1); // test == operator + + moved_copy = std::move(cap1); // test = (&&) operator + ASSERT_TRUE(cap1 == nullptr); // test == nullptr_t operator + ASSERT_TRUE(cap1 == cap1); // test == operator + ASSERT_TRUE(moved_copy != nullptr); // test != nullptr_t operator + ASSERT_TRUE(moved_copy != cap2); // test != operator + + ASSERT_EQ(0, moved_copy.get_temperature_c()); // use moved copy + moved_copy.set_temperature_c(10.0f); // use moved copy + + // restore cap1 + cap1 = std::move(moved_copy); // test = (&&) operator + } + + image color, ir, depth; + color = cap1.get_color_image(); + ir = cap1.get_ir_image(); + depth = cap1.get_depth_image(); + ASSERT_TRUE(depth == nullptr); // test == operator + ASSERT_FALSE(depth != nullptr); // test != operator + + { + image shallow_copy; + image moved_copy; + shallow_copy = color; // test = operator + ASSERT_TRUE(shallow_copy == color); // test == operator + + moved_copy = std::move(color); // test = (&&) operator + ASSERT_TRUE(color == nullptr); // test == nullptr_t operator + ASSERT_TRUE(color == color); // test == operator + ASSERT_TRUE(moved_copy != nullptr); // test != nullptr_t operator + ASSERT_TRUE(moved_copy != ir); // test != operator + + // restore color + color = std::move(moved_copy); // test = (&&) operator + } + + { + // Capture class bool operation, is_valid(), and reset() + ASSERT_TRUE(cap1); + ASSERT_TRUE(cap1.is_valid()); + cap1.reset(); + ASSERT_FALSE(cap1); + ASSERT_FALSE(cap1.is_valid()); + cap1.reset(); // should not crash + ASSERT_FALSE(cap1); + ASSERT_FALSE(cap1.is_valid()); + } + + { + // test reset(), bool operator, is_valid() + image im = image::create(K4A_IMAGE_FORMAT_COLOR_NV12, 1024, 768, 1024 * 3); + ASSERT_TRUE(im); + ASSERT_TRUE(im.is_valid()); + im.reset(); + ASSERT_TRUE(im == nullptr); + ASSERT_FALSE(im); + ASSERT_FALSE(im.is_valid()); + im.reset(); // should not crash + ASSERT_TRUE(im == nullptr); + ASSERT_FALSE(im); + ASSERT_FALSE(im.is_valid()); + } + + { + // test .set_XXX_image && .reset + image im = image::create(K4A_IMAGE_FORMAT_COLOR_NV12, 1024, 768, 1024 * 3); + capture temp_cap = capture::create(); + temp_cap.set_color_image(im); + temp_cap.set_ir_image(im); + ASSERT_TRUE(temp_cap.get_color_image() == im); + ASSERT_TRUE(temp_cap.get_ir_image() == im); + ASSERT_TRUE(temp_cap.get_depth_image() == nullptr); + + temp_cap.set_color_image(nullptr); + temp_cap.set_ir_image(nullptr); + temp_cap.set_depth_image(im); + ASSERT_TRUE(temp_cap.get_color_image() == nullptr); + ASSERT_TRUE(temp_cap.get_ir_image() == nullptr); + ASSERT_TRUE(temp_cap.get_depth_image() == im); + + temp_cap.reset(); + ASSERT_TRUE(temp_cap == nullptr); + } + + uint8_t *non_const_null = nullptr; + ASSERT_TRUE(color.get_buffer() != non_const_null); + ASSERT_TRUE(color.get_buffer() != nullptr); + + ASSERT_TRUE(color.get_size() > 0); + ASSERT_TRUE(color.get_format() == K4A_IMAGE_FORMAT_COLOR_MJPG); + ASSERT_TRUE(color.get_width_pixels() == 1920); + ASSERT_TRUE(color.get_height_pixels() == 1080); + ASSERT_TRUE(color.get_stride_bytes() == 0); + ASSERT_TRUE(color.get_device_timestamp() != std::chrono::microseconds(0)); + ASSERT_TRUE(color.get_system_timestamp() != std::chrono::microseconds(0)); + ASSERT_TRUE(color.get_exposure() != std::chrono::microseconds(0)); + ASSERT_TRUE(color.get_white_balance() != 0); + ASSERT_TRUE(color.get_iso_speed() != 0); + + // test that they don't fail + color.set_timestamp(std::chrono::microseconds(0x1234)); + color.set_exposure_time(std::chrono::microseconds(500)); + color.set_white_balance(500); + color.set_iso_speed(500); + } + + kinect.close(); +} + +static void test_record(void) +{ + record recorder; + device kinect = device::open(0); + k4a_device_configuration_t config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL; + config.color_resolution = K4A_COLOR_RESOLUTION_1080P; + config.depth_mode = K4A_DEPTH_MODE_NFOV_UNBINNED; + config.synchronized_images_only = true; + kinect.start_cameras(&config); + kinect.start_imu(); + + { + // Test bool operator, close(), is_valid() + recorder = record::create(MKV_FILE_NAME.c_str(), kinect, config); + ASSERT_TRUE(recorder); + ASSERT_TRUE(recorder.is_valid()); + recorder.close(); + ASSERT_FALSE(recorder); + ASSERT_FALSE(recorder.is_valid()); + recorder.close(); // should not crash + ASSERT_FALSE(recorder); + ASSERT_FALSE(recorder.is_valid()); + } + + recorder = record::create(MKV_FILE_NAME.c_str(), kinect, config); + ASSERT_TRUE(recorder); + + { + record recorder2 = record::create(MKV_FILE_NAME_2ND.c_str(), kinect, config); + ASSERT_TRUE(recorder2); + + record recorder_empty; + ASSERT_FALSE(recorder_empty); + + record recorder_moved = std::move(recorder2); + + recorder_empty.close(); + recorder_moved.close(); + std::remove(MKV_FILE_NAME_2ND.c_str()); + } + + recorder.add_tag("K4A_CPP_FT_ADD_TAG", "K4A_CPP_FT_ADD_TAG"); + recorder.add_imu_track(); + + std::string k4a_cpp_ft_attachment = "K4A_CPP_FT_ADD_ATTACHMENT"; + recorder.add_attachment(k4a_cpp_ft_attachment.c_str(), + (const uint8_t *)k4a_cpp_ft_attachment.data(), + k4a_cpp_ft_attachment.size()); + + k4a_record_video_settings_t vid_settings = { 1920, 1080, 30 }; + std::string k4a_cpp_ft_custom_vid_track = "K4A_CPP_FT_CUSTOM_VID_TRACK"; + recorder.add_custom_video_track(k4a_cpp_ft_custom_vid_track.c_str(), "V_MPEG1", nullptr, 0, &vid_settings); + + k4a_record_subtitle_settings_t st_track = { false }; + std::string k4a_cpp_ft_custom_subtitle_track = "CUSTOM_K4A_SUBTITLE_TRACE"; + recorder.add_custom_subtitle_track(k4a_cpp_ft_custom_subtitle_track.c_str(), "V_MPEG1", nullptr, 0, &st_track); + + recorder.write_header(); + + for (int x = 0; x < 100; x++) + { + capture capture; + k4a_imu_sample_t imu; + if (kinect.get_capture(&capture, std::chrono::milliseconds(1000))) + { + recorder.write_capture(capture); + } + + auto start = std::chrono::high_resolution_clock::now(); + while (kinect.get_imu_sample(&imu, std::chrono::milliseconds(0))) + { + recorder.write_imu_sample(imu); + if (std::chrono::high_resolution_clock::now() - start < std::chrono::milliseconds(100)) + { + break; + } + } + image color = capture.get_color_image(); + recorder.write_custom_track_data(k4a_cpp_ft_custom_vid_track.c_str(), + color.get_device_timestamp(), + color.get_buffer(), + color.get_size()); + color.reset(); + + image depth = capture.get_depth_image(); + recorder.write_custom_track_data(k4a_cpp_ft_custom_subtitle_track.c_str(), + depth.get_device_timestamp(), + depth.get_buffer(), + depth.get_size()); + capture.reset(); + } + recorder.flush(); + kinect.stop_cameras(); + kinect.stop_imu(); +} + +static void test_playback(void) +{ + playback pb = playback::open(MKV_FILE_NAME.c_str()); + ASSERT_TRUE(pb); // bool operation + ASSERT_TRUE(pb.is_valid()); + + { + playback pb_missing_file; + ASSERT_THROW(pb_missing_file = playback::open("./This_file_is_not_here.mkv"), error); + + playback pb_empty; + ASSERT_FALSE(pb_empty); // bool operation + ASSERT_FALSE(pb_empty.is_valid()); + ASSERT_FALSE(pb_missing_file); // bool operation + ASSERT_FALSE(pb_missing_file.is_valid()); + + pb.close(); + ASSERT_FALSE(pb); // bool operation + ASSERT_FALSE(pb.is_valid()); + pb = playback::open(MKV_FILE_NAME.c_str()); + } + + { + // playback pb_empty = pb; // deleted operation + } + + playback pback; + ASSERT_TRUE(pb); // bool operation + ASSERT_FALSE(pback); // bool operation + pback = std::move(pb); + ASSERT_FALSE(pb); // bool operation + ASSERT_TRUE(pback); // bool operation + + std::vector raw_cal = pback.get_raw_calibration(); + std::cout << "calibration is : "; + for (const uint8_t &data_char : raw_cal) + { + std::cout << data_char; + } + std::cout << "\n\n"; + + k4a_record_configuration_t config = pback.get_record_configuration(); + + calibration cal = pback.get_calibration(); + { + device kinect = device::open(0); + calibration device_cal = kinect.get_calibration(config.depth_mode, config.color_resolution); + ASSERT_TRUE(cal.color_resolution == device_cal.color_resolution); + ASSERT_TRUE(cal.depth_mode == device_cal.depth_mode); + } + + pback.set_color_conversion(K4A_IMAGE_FORMAT_COLOR_NV12); + pback.set_color_conversion(K4A_IMAGE_FORMAT_COLOR_BGRA32); + + std::chrono::microseconds length = pback.get_recording_length(); + + pback.seek_timestamp(std::chrono::microseconds(0), K4A_PLAYBACK_SEEK_BEGIN); + pback.seek_timestamp(std::chrono::microseconds(0), K4A_PLAYBACK_SEEK_END); + pback.seek_timestamp(length / 2, K4A_PLAYBACK_SEEK_DEVICE_TIME); + + int capture_count_forward = 0; + int imu_count_forward = 0; + int capture_count_backward = 0; + int imu_count_backward = 0; + int vid_block_count_forward = 0; + int subtitle_block_count_forward = 0; + int vid_block_count_backward = 0; + int subtitle_block_count_backward = 0; + std::string k4a_cpp_ft_custom_vid_track = "K4A_CPP_FT_CUSTOM_VID_TRACK"; + std::string k4a_cpp_ft_custom_subtitle_track = "CUSTOM_K4A_SUBTITLE_TRACE"; + + // walk the file forward + { + pback.seek_timestamp(std::chrono::microseconds(0), K4A_PLAYBACK_SEEK_BEGIN); + capture cap; + data_block block; + k4a_imu_sample_t imu; + + while (pback.get_next_capture(&cap)) + { + capture_count_forward++; + } + + while (pback.get_next_imu_sample(&imu)) + { + imu_count_forward++; + } + + while (pback.get_next_data_block(k4a_cpp_ft_custom_vid_track.c_str(), &block)) + { + vid_block_count_forward++; + } + + while (pback.get_next_data_block(k4a_cpp_ft_custom_subtitle_track.c_str(), &block)) + { + subtitle_block_count_forward++; + + ASSERT_TRUE(block); + ASSERT_TRUE(block.is_valid()); + block.reset(); + ASSERT_FALSE(block); + ASSERT_FALSE(block.is_valid()); + } + + ASSERT_GT(capture_count_forward, 0); + ASSERT_GT(imu_count_forward, 0); + ASSERT_EQ(imu_count_forward, capture_count_forward); + ASSERT_EQ(capture_count_forward, vid_block_count_forward); + ASSERT_EQ(capture_count_forward, subtitle_block_count_forward); + } + + // walk the file backwards + { + capture cap; + k4a_imu_sample_t imu; + data_block block; + + while (pback.get_previous_capture(&cap)) + { + capture_count_backward++; + } + + while (pback.get_previous_imu_sample(&imu)) + { + imu_count_backward++; + } + + while (pback.get_previous_data_block(k4a_cpp_ft_custom_vid_track.c_str(), &block)) + { + vid_block_count_backward++; + } + + while (pback.get_previous_data_block(k4a_cpp_ft_custom_subtitle_track.c_str(), &block)) + { + subtitle_block_count_backward++; + } + + ASSERT_GT(capture_count_backward, 0); + ASSERT_GT(imu_count_backward, 0); + ASSERT_EQ(imu_count_backward, capture_count_backward); + ASSERT_EQ(capture_count_backward, vid_block_count_backward); + ASSERT_EQ(capture_count_backward, subtitle_block_count_backward); + } + + ASSERT_EQ(capture_count_forward, capture_count_backward); + ASSERT_EQ(imu_count_forward, imu_count_backward); + + // walk the file forward after seek to end + { + pback.seek_timestamp(std::chrono::microseconds(0), K4A_PLAYBACK_SEEK_END); + + capture cap; + int capture_count = 0; + int imu_count = 0; + int vid_block_count = 0; + int subtitle_block_count = 0; + data_block block; + + while (pback.get_next_capture(&cap)) + { + capture_count++; + } + + k4a_imu_sample_t imu; + while (pback.get_next_imu_sample(&imu)) + { + imu_count++; + } + + while (pback.get_next_data_block(k4a_cpp_ft_custom_vid_track.c_str(), &block)) + { + vid_block_count++; + ASSERT_NE(block.get_device_timestamp_usec().count(), 0); + ASSERT_NE(block.get_buffer_size(), (size_t)0); + ASSERT_NE(block.get_buffer(), nullptr); + } + + while (pback.get_next_data_block(k4a_cpp_ft_custom_subtitle_track.c_str(), &block)) + { + subtitle_block_count++; + } + + ASSERT_EQ(capture_count, 0); + ASSERT_EQ(imu_count, 0); + ASSERT_EQ(capture_count, vid_block_count); + ASSERT_EQ(capture_count, subtitle_block_count); + } + + std::string k4a_cpp_ft_attachment = "K4A_CPP_FT_ADD_ATTACHMENT"; + std::string bad_attachment_name = "BAD_ATTACHMENT_NAME"; + + std::vector data; + ASSERT_FALSE(pback.get_attachment(bad_attachment_name.c_str(), &data)); + ASSERT_TRUE(pback.get_attachment(k4a_cpp_ft_attachment.c_str(), &data)); + ASSERT_EQ(data.size(), k4a_cpp_ft_attachment.size()); + ASSERT_TRUE(memcmp(&data[0], k4a_cpp_ft_attachment.data(), data.size()) == 0); +} + +TEST_F(k4a_cpp_ft, record_and_playback) +{ + test_record(); + test_playback(); + + ASSERT_FALSE(std::remove(MKV_FILE_NAME.c_str())); +} diff --git a/tests/RecordTests/UnitTest/CMakeLists.txt b/tests/RecordTests/UnitTest/CMakeLists.txt index 7b2e80f0c..394facb84 100644 --- a/tests/RecordTests/UnitTest/CMakeLists.txt +++ b/tests/RecordTests/UnitTest/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable(playback_perf playback_perf.cpp test_helpers.cpp) target_link_libraries(record_ut PRIVATE k4ainternal::utcommon k4ainternal::record + k4a::k4arecord ) target_link_libraries(playback_ut PRIVATE diff --git a/tests/RecordTests/UnitTest/playback_ut.cpp b/tests/RecordTests/UnitTest/playback_ut.cpp index 3419a8f4e..ced7e36d1 100644 --- a/tests/RecordTests/UnitTest/playback_ut.cpp +++ b/tests/RecordTests/UnitTest/playback_ut.cpp @@ -92,7 +92,7 @@ TEST_F(playback_ut, open_large_file) k4a_capture_t capture = NULL; k4a_stream_result_t stream_result = K4A_STREAM_RESULT_FAILED; uint64_t timestamps[3] = { 0, 1000, 1000 }; - uint64_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(config.camera_fps); + uint64_t timestamp_delta = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(config.camera_fps)); size_t i = 0; for (; i < 50; i++) { @@ -177,7 +177,7 @@ TEST_F(playback_ut, open_delay_offset_file) k4a_capture_t capture = NULL; k4a_stream_result_t stream_result = K4A_STREAM_RESULT_FAILED; uint64_t timestamps[3] = { 0, 10000, 10000 }; - uint64_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(config.camera_fps); + uint64_t timestamp_delta = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(config.camera_fps)); // Read forward for (size_t i = 0; i < test_frame_count; i++) @@ -281,7 +281,7 @@ TEST_F(playback_ut, playback_seek_test) k4a_capture_t capture = NULL; k4a_stream_result_t stream_result = K4A_STREAM_RESULT_FAILED; uint64_t timestamps[3] = { 0, 1000, 1000 }; - uint64_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(config.camera_fps); + uint64_t timestamp_delta = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(config.camera_fps)); k4a_imu_sample_t imu_sample = { 0 }; uint64_t imu_timestamp = 1150; @@ -546,7 +546,7 @@ TEST_F(playback_ut, open_skipped_frames_file) k4a_capture_t capture = NULL; k4a_stream_result_t stream_result = K4A_STREAM_RESULT_FAILED; uint64_t timestamps[3] = { 1000000, 1001000, 1001000 }; - uint64_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(config.camera_fps); + uint64_t timestamp_delta = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(config.camera_fps)); // Test initial state stream_result = k4a_playback_get_previous_capture(handle, &capture); @@ -817,7 +817,7 @@ TEST_F(playback_ut, open_start_offset_file) k4a_stream_result_t stream_result = K4A_STREAM_RESULT_FAILED; uint64_t timestamps[3] = { 1000000, 1000000, 1000000 }; uint64_t imu_timestamp = 1001150; - uint64_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(config.camera_fps); + uint64_t timestamp_delta = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(config.camera_fps)); uint64_t last_timestamp = k4a_playback_get_recording_length_usec(handle) + (uint64_t)config.start_timestamp_offset_usec; ASSERT_EQ(last_timestamp, (uint64_t)config.start_timestamp_offset_usec + 3333150); @@ -987,6 +987,41 @@ TEST_F(playback_ut, open_depth_only_file) k4a_playback_close(handle); } +TEST_F(playback_ut, open_bgra_color_file) +{ + k4a_playback_t handle = NULL; + k4a_result_t result = k4a_playback_open("record_test_bgra_color.mkv", &handle); + ASSERT_EQ(result, K4A_RESULT_SUCCEEDED); + + // Read recording configuration + k4a_record_configuration_t config; + result = k4a_playback_get_record_configuration(handle, &config); + ASSERT_EQ(result, K4A_RESULT_SUCCEEDED); + ASSERT_EQ(config.color_format, K4A_IMAGE_FORMAT_COLOR_BGRA32); + ASSERT_EQ(config.color_resolution, K4A_COLOR_RESOLUTION_1080P); + ASSERT_EQ(config.depth_mode, K4A_DEPTH_MODE_OFF); + ASSERT_EQ(config.camera_fps, K4A_FRAMES_PER_SECOND_30); + ASSERT_TRUE(config.color_track_enabled); + ASSERT_FALSE(config.depth_track_enabled); + ASSERT_FALSE(config.ir_track_enabled); + ASSERT_FALSE(config.imu_track_enabled); + ASSERT_EQ(config.depth_delay_off_color_usec, 0); + ASSERT_EQ(config.wired_sync_mode, K4A_WIRED_SYNC_MODE_STANDALONE); + ASSERT_EQ(config.subordinate_delay_off_master_usec, (uint32_t)0); + ASSERT_EQ(config.start_timestamp_offset_usec, (uint32_t)0); + + uint64_t timestamps[3] = { 0, 0, 0 }; + + k4a_capture_t capture = NULL; + k4a_stream_result_t stream_result = k4a_playback_get_next_capture(handle, &capture); + ASSERT_EQ(stream_result, K4A_STREAM_RESULT_SUCCEEDED); + ASSERT_TRUE( + validate_test_capture(capture, timestamps, config.color_format, config.color_resolution, config.depth_mode)); + k4a_capture_release(capture); + + k4a_playback_close(handle); +} + int main(int argc, char **argv) { k4a_unittest_init(); diff --git a/tests/RecordTests/UnitTest/record_ut.cpp b/tests/RecordTests/UnitTest/record_ut.cpp index bc1f4c723..d45b0c4af 100644 --- a/tests/RecordTests/UnitTest/record_ut.cpp +++ b/tests/RecordTests/UnitTest/record_ut.cpp @@ -2,9 +2,12 @@ // Licensed under the MIT License. #include +#include // Module being tested #include +#include +#include #include #include @@ -129,6 +132,81 @@ TEST_F(record_ut, new_cluster_out_of_order) ASSERT_EQ(context->pending_clusters.size(), 3u); } +// This test's goal is to fill up the write queue by saturating disk write. +// It should trigger the write speed warning message in the logs. +// Since this test is unlikely to complete, and needs to be manually run, it is disabled. +TEST_F(record_ut, DISABLED_bgra_color_max_disk_write) +{ + k4a_device_configuration_t record_config = {}; + record_config.color_format = K4A_IMAGE_FORMAT_COLOR_BGRA32; + record_config.color_resolution = K4A_COLOR_RESOLUTION_2160P; + record_config.depth_mode = K4A_DEPTH_MODE_OFF; + record_config.camera_fps = K4A_FRAMES_PER_SECOND_30; + + std::cout + << "A 'Disk write speed is too low, write queue is filling up.' log message is expected after about 4 seconds." + << std::endl; + std::cout + << "If the test completes without this log message, the check may be broken, or the test disk may be too fast." + << std::endl; + std::cout + << "If the test crashes due to an out-of-memory condition without logging a disk warning, the check is broken." + << std::endl; + + k4a_record_t handle = NULL; + k4a_result_t result = k4a_record_create("record_test_bgra_color.mkv", NULL, record_config, &handle); + ASSERT_EQ(result, K4A_RESULT_SUCCEEDED); + + result = k4a_record_write_header(handle); + ASSERT_EQ(result, K4A_RESULT_SUCCEEDED); + + uint64_t timestamp_ns = 0; + for (int i = 0; i < 1000; i++) + { + k4a_capture_t capture = NULL; + result = k4a_capture_create(&capture); + ASSERT_EQ(result, K4A_RESULT_SUCCEEDED); + + uint32_t width = 3840; + uint32_t height = 2160; + uint32_t stride = width * 4; + size_t buffer_size = height * stride; + uint8_t *buffer = new uint8_t[height * stride]; + memset(buffer, 0xFF, height * stride); + + k4a_image_t color_image = NULL; + result = k4a_image_create_from_buffer(record_config.color_format, + (int)width, + (int)height, + (int)stride, + buffer, + buffer_size, + [](void *_buffer, void *ctx) { + delete[](uint8_t *) _buffer; + (void)ctx; + }, + NULL, + &color_image); + ASSERT_EQ(result, K4A_RESULT_SUCCEEDED); + + k4a_image_set_device_timestamp_usec(color_image, timestamp_ns / 1000); + k4a_capture_set_color_image(capture, color_image); + k4a_image_release(color_image); + + result = k4a_record_write_capture(handle, capture); + ASSERT_EQ(result, K4A_RESULT_SUCCEEDED); + k4a_capture_release(capture); + timestamp_ns += 1_s / 30; + } + + result = k4a_record_flush(handle); + ASSERT_EQ(result, K4A_RESULT_SUCCEEDED); + + k4a_record_close(handle); + + ASSERT_EQ(std::remove("record_test_bgra_color.mkv"), 0); +} + int main(int argc, char **argv) { return k4a_test_common_main(argc, argv); diff --git a/tests/RecordTests/UnitTest/sample_recordings.cpp b/tests/RecordTests/UnitTest/sample_recordings.cpp index f2dd5470d..bdf6998ef 100644 --- a/tests/RecordTests/UnitTest/sample_recordings.cpp +++ b/tests/RecordTests/UnitTest/sample_recordings.cpp @@ -35,6 +35,10 @@ void SampleRecordings::SetUp() k4a_device_configuration_t record_config_depth_only = record_config_full; record_config_depth_only.color_resolution = K4A_COLOR_RESOLUTION_OFF; + k4a_device_configuration_t record_config_bgra_color = record_config_full; + record_config_bgra_color.color_format = K4A_IMAGE_FORMAT_COLOR_BGRA32; + record_config_bgra_color.depth_mode = K4A_DEPTH_MODE_OFF; + { k4a_record_t handle = NULL; k4a_result_t result = k4a_record_create("record_test_empty.mkv", NULL, record_config_empty, &handle); @@ -61,7 +65,7 @@ void SampleRecordings::SetUp() uint64_t timestamps[3] = { 0, 1000, 1000 }; // Offset the Depth and IR tracks by 1ms to test uint64_t imu_timestamp = 1150; - uint32_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(record_config_full.camera_fps); + uint32_t timestamp_delta = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(record_config_full.camera_fps)); k4a_capture_t capture = NULL; for (size_t i = 0; i < test_frame_count; i++) { @@ -104,7 +108,7 @@ void SampleRecordings::SetUp() uint64_t timestamps[3] = { 0, (uint64_t)record_config_delay.depth_delay_off_color_usec, (uint64_t)record_config_delay.depth_delay_off_color_usec }; - uint32_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(record_config_delay.camera_fps); + uint32_t timestamp_delta = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(record_config_delay.camera_fps)); k4a_capture_t capture = NULL; for (size_t i = 0; i < test_frame_count; i++) { @@ -165,7 +169,7 @@ void SampleRecordings::SetUp() } uint64_t timestamps[3] = { 1000000, 1001000, 1001000 }; // Start recording at 1s - uint32_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(record_config_full.camera_fps); + uint32_t timestamp_delta = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(record_config_full.camera_fps)); for (size_t i = 0; i < test_frame_count; i++) { // Create a known pattern of dropped / missing frames that can be tested against @@ -231,7 +235,7 @@ void SampleRecordings::SetUp() uint64_t timestamps[3] = { 1000000, 1000000, 1000000 }; uint64_t imu_timestamp = 1001150; - uint32_t timestamp_delta = 1000000 / k4a_convert_fps_to_uint(record_config_delay.camera_fps); + uint32_t timestamp_delta = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(record_config_delay.camera_fps)); k4a_capture_t capture = NULL; for (size_t i = 0; i < test_frame_count; i++) { @@ -305,6 +309,28 @@ void SampleRecordings::SetUp() result = k4a_record_flush(handle); ASSERT_EQ(result, K4A_RESULT_SUCCEEDED); + k4a_record_close(handle); + } + { // Create a recording file with BGRA color + k4a_record_t handle = NULL; + k4a_result_t result = k4a_record_create("record_test_bgra_color.mkv", NULL, record_config_bgra_color, &handle); + ASSERT_EQ(result, K4A_RESULT_SUCCEEDED); + + result = k4a_record_write_header(handle); + ASSERT_EQ(result, K4A_RESULT_SUCCEEDED); + + uint64_t timestamps[3] = { 0, 0, 0 }; + k4a_capture_t capture = create_test_capture(timestamps, + record_config_bgra_color.color_format, + record_config_bgra_color.color_resolution, + record_config_bgra_color.depth_mode); + result = k4a_record_write_capture(handle, capture); + ASSERT_EQ(result, K4A_RESULT_SUCCEEDED); + k4a_capture_release(capture); + + result = k4a_record_flush(handle); + ASSERT_EQ(result, K4A_RESULT_SUCCEEDED); + k4a_record_close(handle); } } @@ -319,6 +345,7 @@ void SampleRecordings::TearDown() ASSERT_EQ(std::remove("record_test_offset.mkv"), 0); ASSERT_EQ(std::remove("record_test_color_only.mkv"), 0); ASSERT_EQ(std::remove("record_test_depth_only.mkv"), 0); + ASSERT_EQ(std::remove("record_test_bgra_color.mkv"), 0); } void CustomTrackRecordings::SetUp() diff --git a/tests/RecordTests/UnitTest/test_helpers.cpp b/tests/RecordTests/UnitTest/test_helpers.cpp index f3d22e393..783dcf7b4 100644 --- a/tests/RecordTests/UnitTest/test_helpers.cpp +++ b/tests/RecordTests/UnitTest/test_helpers.cpp @@ -96,6 +96,10 @@ bool validate_test_capture(k4a_capture_t capture, { color_stride = width * 2; } + else if (color_format == K4A_IMAGE_FORMAT_COLOR_BGRA32) + { + color_stride = width * 4; + } k4a_image_t color_image = k4a_capture_get_color_image(capture); if (color_image == NULL) diff --git a/tests/Transformation/transformation.cpp b/tests/Transformation/transformation.cpp index 1e5fbd923..f5e4bd14f 100644 --- a/tests/Transformation/transformation.cpp +++ b/tests/Transformation/transformation.cpp @@ -84,6 +84,9 @@ class transformation_ut : public ::testing::Test ASSERT_EQ_FLT(A[2], B[2]) \ } +// Export function from transformation.c to snoop on the compiler setting used. +extern "C" char *transformation_get_instruction_type(); + static k4a_transformation_image_descriptor_t image_get_descriptor(const k4a_image_t image) { k4a_transformation_image_descriptor_t descriptor; @@ -405,6 +408,27 @@ TEST_F(transformation_ut, transformation_depth_image_to_point_cloud) ASSERT_EQ(check_sum, reference_val); } + { + // Are we compiled for the correct instruction type +#if defined(__amd64__) || defined(_M_AMD64) || defined(__i386__) || defined(_M_IX86) +#define SPECIAL_INSTRUCTION_OPTIMIZATION "SSE\0" +#elif defined(__aarch64__) || defined(_M_ARM64) +#define SPECIAL_INSTRUCTION_OPTIMIZATION "NEON" +#else +// Omit defining this when not SSE or NEON. Should result in a build break. We are either SSE or Neon. +//#define SPECIAL_INSTRUCTION_OPTIMIZATION "None" +#endif + char *compile_type = transformation_get_instruction_type(); + ASSERT_NE(compile_type, (char *)nullptr); + ASSERT_NE(compile_type[0], '\0'); + std::cout << "*** K4A Sensor SDK Compile type is: " << compile_type << " ***\n"; + ASSERT_TRUE(memcmp(compile_type, SPECIAL_INSTRUCTION_OPTIMIZATION, strlen(compile_type)) == 0) + << "Expecting " << SPECIAL_INSTRUCTION_OPTIMIZATION << " but compiled for " << compile_type << "\n"; + ASSERT_TRUE(memcmp(compile_type, SPECIAL_INSTRUCTION_OPTIMIZATION, strlen(compile_type)) == 0) + << "Expecting " << SPECIAL_INSTRUCTION_OPTIMIZATION << " but compiled for " << compile_type << "\n"; + ASSERT_EQ(strlen(compile_type), strlen(SPECIAL_INSTRUCTION_OPTIMIZATION)); + } + image_dec_ref(depth_image); image_dec_ref(xyz_image); transformation_destroy(transformation_handle); diff --git a/tests/UnitTests/utcommon/inc/utcommon.h b/tests/UnitTests/utcommon/inc/utcommon.h index fdd32778b..c42446846 100644 --- a/tests/UnitTests/utcommon/inc/utcommon.h +++ b/tests/UnitTests/utcommon/inc/utcommon.h @@ -17,6 +17,9 @@ std::ostream &operator<<(std::ostream &s, const k4a_buffer_result_t &val); extern "C" { +// Generate a Random number between min and max and is inclive of both min and max +#define RAND_VALUE(min, max) (((int64_t)((max) - (min) + 1) * (int64_t)(rand()) / (int64_t)RAND_MAX) + min) + // Initialize default k4a specific unittest behavior void k4a_unittest_init(); void k4a_unittest_deinit(); @@ -26,6 +29,8 @@ void k4a_unittest_init_logging_with_processid(); #endif int k4a_test_common_main(int argc, char **argv); + +int64_t k4a_unittest_get_max_sync_delay_ms(k4a_fps_t fps); } #endif diff --git a/tests/UnitTests/utcommon/utcommon.cpp b/tests/UnitTests/utcommon/utcommon.cpp index b33852190..7ddc5df60 100644 --- a/tests/UnitTests/utcommon/utcommon.cpp +++ b/tests/UnitTests/utcommon/utcommon.cpp @@ -107,4 +107,22 @@ int k4a_test_common_main(int argc, char **argv) return ret; } + +int64_t k4a_unittest_get_max_sync_delay_ms(k4a_fps_t fps) +{ + int64_t max_delay = 0; + switch (fps) + { + case K4A_FRAMES_PER_SECOND_5: + max_delay = 660; + break; + case K4A_FRAMES_PER_SECOND_15: + max_delay = 220; + break; + case K4A_FRAMES_PER_SECOND_30: + max_delay = 110; + break; + } + return max_delay; +} } diff --git a/tests/latency/CMakeLists.txt b/tests/latency/CMakeLists.txt new file mode 100644 index 000000000..da7907c6d --- /dev/null +++ b/tests/latency/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +add_executable(latency_perf latency_perf.cpp) + +target_compile_definitions(latency_perf PRIVATE _CRT_SECURE_NO_WARNINGS) + +target_link_libraries(latency_perf PRIVATE + azure::aziotsharedutil + gtest::gtest + k4a::k4a + k4ainternal::logging + k4ainternal::utcommon) + +k4a_add_tests(TARGET latency_perf HARDWARE_REQUIRED TEST_TYPE PERF) diff --git a/tests/latency/latency_perf.cpp b/tests/latency/latency_perf.cpp new file mode 100644 index 000000000..5986d0687 --- /dev/null +++ b/tests/latency/latency_perf.cpp @@ -0,0 +1,1073 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +//************************ Includes ***************************** +#ifdef _WIN32 +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#endif + +#define LLD(val) ((int64_t)(val)) +#define STS_TO_MS(ts) (LLD((ts) / 1000000)) // System TS convertion to milliseconds + +static bool g_skip_delay_off_color_validation = false; +static int32_t g_depth_delay_off_color_usec = 0; +static uint8_t g_device_index = K4A_DEVICE_DEFAULT; +static k4a_wired_sync_mode_t g_wired_sync_mode = K4A_WIRED_SYNC_MODE_STANDALONE; +static int g_capture_count = 50; +static bool g_synchronized_images_only = false; +static bool g_no_startup_flush = false; +static uint32_t g_subordinate_delay_off_master_usec = 0; +static bool g_manual_exposure = true; +static uint32_t g_exposure_setting = 31000; // will round up to nearest value +static bool g_power_line_50_hz = false; + +using ::testing::ValuesIn; + +typedef struct _sys_pts_time_t +{ + uint64_t pts; + uint64_t system; +} sys_pts_time_t; + +static std::mutex g_lock_mutex; +static std::deque g_time_c; // Color image copy of data +static std::deque g_time_i; // Ir image copy of data + +struct latency_parameters +{ + int test_number; + const char *test_name; + k4a_fps_t fps; + k4a_image_format_t color_format; + k4a_color_resolution_t color_resolution; + k4a_depth_mode_t depth_mode; + + friend std::ostream &operator<<(std::ostream &os, const latency_parameters &obj) + { + return os << "test index: (" << obj.test_name << ") " << (int)obj.test_number; + } +}; + +struct thread_data +{ + volatile bool save_samples; + volatile bool exit; + volatile uint32_t imu_samples; + k4a_device_t device; +}; + +class latency_perf : public ::testing::Test, public ::testing::WithParamInterface +{ +public: + virtual void SetUp() + { + ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_open(g_device_index, &m_device)) << "Couldn't open device\n"; + ASSERT_NE(m_device, nullptr); + EXPECT_NE((FILE *)NULL, (m_file_handle = fopen("latency_testResults.csv", "a"))); + } + + virtual void TearDown() + { + if (m_device != nullptr) + { + k4a_device_close(m_device); + m_device = nullptr; + } + if (m_file_handle) + { + fclose(m_file_handle); + } + } + + void print_and_log(const char *message, const char *mode, int64_t ave, int64_t min, int64_t max); + void process_image(k4a_capture_t capture, + uint64_t current_system_ts, + bool process_color, + bool *image_first_pass, + std::deque *system_latency, + std::deque *system_latency_from_pts, + uint64_t *system_ts_last, + uint64_t *system_ts_from_pts_last); + + k4a_device_t m_device = nullptr; + FILE *m_file_handle; +}; + +static const char *get_string_from_color_format(k4a_image_format_t format) +{ + switch (format) + { + case K4A_IMAGE_FORMAT_COLOR_NV12: + return "K4A_IMAGE_FORMAT_COLOR_NV12"; + break; + case K4A_IMAGE_FORMAT_COLOR_YUY2: + return "K4A_IMAGE_FORMAT_COLOR_YUY2"; + break; + case K4A_IMAGE_FORMAT_COLOR_MJPG: + return "K4A_IMAGE_FORMAT_COLOR_MJPG"; + break; + case K4A_IMAGE_FORMAT_COLOR_BGRA32: + return "K4A_IMAGE_FORMAT_COLOR_BGRA32"; + break; + case K4A_IMAGE_FORMAT_DEPTH16: + return "K4A_IMAGE_FORMAT_DEPTH16"; + break; + case K4A_IMAGE_FORMAT_IR16: + return "K4A_IMAGE_FORMAT_IR16"; + break; + case K4A_IMAGE_FORMAT_CUSTOM8: + return "K4A_IMAGE_FORMAT_CUSTOM8"; + break; + case K4A_IMAGE_FORMAT_CUSTOM16: + return "K4A_IMAGE_FORMAT_CUSTOM16"; + break; + case K4A_IMAGE_FORMAT_CUSTOM: + return "K4A_IMAGE_FORMAT_CUSTOM"; + break; + } + assert(0); + return "K4A_IMAGE_FORMAT_UNKNOWN"; +} + +static const char *get_string_from_color_resolution(k4a_color_resolution_t resolution) +{ + switch (resolution) + { + case K4A_COLOR_RESOLUTION_OFF: + return "OFF"; + break; + case K4A_COLOR_RESOLUTION_720P: + return "1280 * 720 16:9"; + break; + case K4A_COLOR_RESOLUTION_1080P: + return "1920 * 1080 16:9"; + break; + case K4A_COLOR_RESOLUTION_1440P: + return "2560 * 1440 16:9"; + break; + case K4A_COLOR_RESOLUTION_1536P: + return "2048 * 1536 4:3"; + break; + case K4A_COLOR_RESOLUTION_2160P: + return "3840 * 2160 16:9"; + break; + case K4A_COLOR_RESOLUTION_3072P: + return "4096 * 3072 4:3"; + break; + } + assert(0); + return "Unknown resolution"; +} + +static const char *get_string_from_depth_mode(k4a_depth_mode_t mode) +{ + switch (mode) + { + case K4A_DEPTH_MODE_OFF: + return "K4A_DEPTH_MODE_OFF"; + break; + case K4A_DEPTH_MODE_NFOV_2X2BINNED: + return "K4A_DEPTH_MODE_NFOV_2X2BINNED"; + break; + case K4A_DEPTH_MODE_NFOV_UNBINNED: + return "K4A_DEPTH_MODE_NFOV_UNBINNED"; + break; + case K4A_DEPTH_MODE_WFOV_2X2BINNED: + return "K4A_DEPTH_MODE_WFOV_2X2BINNED"; + break; + case K4A_DEPTH_MODE_WFOV_UNBINNED: + return "K4A_DEPTH_MODE_WFOV_UNBINNED"; + break; + case K4A_DEPTH_MODE_PASSIVE_IR: + return "K4A_DEPTH_MODE_PASSIVE_IR"; + break; + } + assert(0); + return "Unknown Depth"; +} + +static bool get_system_time(uint64_t *time_nsec) +{ + k4a_result_t result = K4A_RESULT_SUCCEEDED; +#ifdef _WIN32 + LARGE_INTEGER qpc = { 0 }; + static LARGE_INTEGER freq = { 0 }; + result = K4A_RESULT_FROM_BOOL(QueryPerformanceCounter(&qpc) != 0); + if (K4A_FAILED(result)) + { + return false; + } + if (freq.QuadPart == 0) + { + result = K4A_RESULT_FROM_BOOL(QueryPerformanceFrequency(&freq) != 0); + if (K4A_FAILED(result)) + { + return false; + } + } + + // Calculate seconds in such a way we minimize overflow. + // Rollover happens, for a 1MHz Freq, when qpc.QuadPart > 0x003F FFFF FFFF FFFF; ~571 Years after boot. + *time_nsec = qpc.QuadPart / freq.QuadPart * 1000000000; + *time_nsec += qpc.QuadPart % freq.QuadPart * 1000000000 / freq.QuadPart; + +#else + struct timespec ts_time; + result = K4A_RESULT_FROM_BOOL(clock_gettime(CLOCK_MONOTONIC, &ts_time) == 0); + if (K4A_FAILED(result)) + { + return false; + } + // Rollover happens about ~136 years after boot. + *time_nsec = (uint64_t)ts_time.tv_sec * 1000000000 + (uint64_t)ts_time.tv_nsec; +#endif + return true; +} + +static int _latency_imu_thread(void *param) +{ + struct thread_data *data = (struct thread_data *)param; + k4a_result_t result; + k4a_imu_sample_t imu; + + result = k4a_device_start_imu(data->device); + if (K4A_FAILED(result)) + { + printf("Failed to start imu\n"); + return result; + } + + g_time_c.clear(); + g_time_i.clear(); + + while (data->exit == false) + { + k4a_wait_result_t wresult = k4a_device_get_imu_sample(data->device, &imu, 10); + if (wresult == K4A_WAIT_RESULT_FAILED) + { + printf("k4a_device_get_imu_sample failed\n"); + result = K4A_RESULT_FAILED; + break; + } + else if ((wresult == K4A_WAIT_RESULT_SUCCEEDED) && (data->save_samples)) + { + sys_pts_time_t time; + time.pts = imu.acc_timestamp_usec; + if (get_system_time(&time.system) == 0) + { + result = K4A_RESULT_FAILED; + break; + } + + // Save data to each of the queues + g_lock_mutex.lock(); + g_time_c.push_back(time); + g_time_i.push_back(time); + g_lock_mutex.unlock(); + } + }; + + k4a_device_stop_imu(data->device); + return result; +} + +// Drop the lock and sleep for Xms. This is to allow the queue to fill again. Return if we yield too long. +#define YIELD_THREAD(lock_var, count, message) \ + lock_var.unlock(); \ + printf("Lock dropped while %s\n", message); \ + ThreadAPI_Sleep(2); \ + if (++count > 15) \ + { \ + EXPECT_LT(count, 15); \ + return 0; \ + } \ + lock_var.lock(); + +static uint64_t lookup_system_ts(uint64_t pts_ts, bool color) +{ + sys_pts_time_t last_time; + uint64_t start_time_nsec; + uint64_t current_time_nsec; + int count = 0; + + bool found = false; + + std::deque *time_queue = &g_time_i; + if (color) + { + time_queue = &g_time_c; + } + + g_lock_mutex.lock(); + + // Record start time + if (get_system_time(&start_time_nsec) == 0) + { + printf("ERROR getting system time\n"); + EXPECT_TRUE(0); + g_lock_mutex.unlock(); + return 0; + } + + int delay_count = 0; + while (time_queue->empty()) + { + // Drop lock, wait, retake lock - Exit if taking too long + YIELD_THREAD(g_lock_mutex, delay_count, "Initializing") + } + + last_time = time_queue->front(); + time_queue->pop_front(); + + while (!found) + { + int x; + for (x = 0; !time_queue->empty(); x++) + { + last_time = time_queue->front(); + if (pts_ts > last_time.pts) + { + // Hold onto last_time for 1 more loop + last_time = time_queue->front(); + time_queue->pop_front(); + } + else + { + // We just found the first system time that is beyond the one we are looking for. + if ((pts_ts - last_time.pts) < (time_queue->front().pts - pts_ts)) + { + g_lock_mutex.unlock(); + found = true; + return last_time.system; + } + uint64_t ret_time = time_queue->front().system; + g_lock_mutex.unlock(); + + found = true; + return ret_time; + } + + if (get_system_time(¤t_time_nsec) == 0) + { + printf("ERROR getting system time\n"); + EXPECT_TRUE(0); + g_lock_mutex.unlock(); + return 0; + } + + if (STS_TO_MS(current_time_nsec - start_time_nsec) > 1000) + { + printf("Count for break is %d\n", count); + break; // Don't hold lock too long, run YIELD_THREAD below + } + } + + // Queue is drained or we held the lock too long. We need to let the IMU thread catch up. Drop lock, wait, + // retake lock - Exit if taking too long + YIELD_THREAD(g_lock_mutex, delay_count, "walking list."); + + // Update start time after the thread yield + if (get_system_time(&start_time_nsec) == 0) + { + printf("ERROR getting system time\n"); + EXPECT_TRUE(0); + g_lock_mutex.unlock(); + return 0; + } + } + + // Should not happen + EXPECT_FALSE(1); + g_lock_mutex.unlock(); + return 0; +} + +void latency_perf::print_and_log(const char *message, const char *mode, int64_t ave, int64_t min, int64_t max) +{ + printf(" %30s %30s: Ave=%" PRId64 " min=%" PRId64 " max=%" PRId64 "\n", message, mode, ave, min, max); + + if (m_file_handle) + { + char buffer[1024]; + snprintf(buffer, + sizeof(buffer), + "%s, %s (min ave max),%" PRId64 ",%" PRId64 ",%" PRId64 ",", + mode, + message, + min, + ave, + max); + fputs(buffer, m_file_handle); + } +} + +void latency_perf::process_image(k4a_capture_t capture, + uint64_t current_system_ts, + bool process_color, + bool *image_first_pass, + std::deque *system_latency, + std::deque *system_latency_from_pts, + uint64_t *system_ts_last, + uint64_t *system_ts_from_pts_last) +{ + k4a_image_t image; + if (process_color) + { + image = k4a_capture_get_color_image(capture); + } + else + { + image = k4a_capture_get_ir_image(capture); + } + + if (image) + { + uint64_t system_ts = k4a_image_get_system_timestamp_nsec(image); + + uint64_t system_ts_from_pts = lookup_system_ts(k4a_image_get_device_timestamp_usec(image), process_color); + + // Time from center of exposure until given to us from the SDK; based on Host system time. + uint64_t system_ts_latency = current_system_ts - system_ts; + + // Time from center of exposure PTS time (converted to system time based on low latency IMU data) until we + // read the frame; based on Host system time. + uint64_t system_ts_latency_from_pts = current_system_ts - system_ts_from_pts; + if (system_ts_from_pts > current_system_ts) + { + printf("Calculated %s pts system time %" PRId64 " is after our arrival system time %" PRId64 + " a diff of %" PRId64 "\n", + process_color ? "color" : "IR", + STS_TO_MS(system_ts_from_pts), + STS_TO_MS(current_system_ts), + STS_TO_MS(system_ts_from_pts - current_system_ts)); + + // Update values anyway + *system_ts_last = system_ts; + *system_ts_from_pts_last = system_ts_from_pts; + } + else + { + + if (!*image_first_pass) + { + system_latency->push_back(current_system_ts - system_ts); + system_latency_from_pts->push_back(system_ts_latency_from_pts); + + printf("| %9" PRId64 " [%5" PRId64 "] [%5" PRId64 "] ", + STS_TO_MS(system_ts), + STS_TO_MS(system_ts_latency), + STS_TO_MS(system_ts_latency_from_pts)); + + // TS should increase + EXPECT_GT(system_ts, *system_ts_last); + EXPECT_GT(system_ts_from_pts, *system_ts_from_pts_last); + } + *system_ts_last = system_ts; + *system_ts_from_pts_last = system_ts_from_pts; + *image_first_pass = false; + } + + k4a_image_release(image); + } + else + { + printf("| "); + } +} + +TEST_P(latency_perf, testTest) +{ + auto as = GetParam(); + const int32_t TIMEOUT_IN_MS = 1000; + k4a_capture_t capture = NULL; + int capture_count = g_capture_count; + bool failed = false; + k4a_device_configuration_t config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL; + thread_data thread = { 0 }; + THREAD_HANDLE th1 = NULL; + std::deque color_system_latency; + std::deque color_system_latency_from_pts; + std::deque ir_system_latency; + std::deque ir_system_latency_from_pts; + uint64_t current_system_ts = 0; + uint64_t color_system_ts_last = 0, color_system_ts_from_pts_last = 0; + uint64_t ir_system_ts_last = 0, ir_system_ts_from_pts_last = 0; + int32_t read_exposure = 0; + + printf("Capturing %d frames for test: %s\n", g_capture_count, as.test_name); + + { + int32_t power_line_setting = g_power_line_50_hz ? 1 : 2; + ASSERT_EQ(K4A_RESULT_SUCCEEDED, + k4a_device_set_color_control(m_device, + K4A_COLOR_CONTROL_POWERLINE_FREQUENCY, + K4A_COLOR_CONTROL_MODE_MANUAL, + power_line_setting)); + printf("Power line mode set to manual and %s.\n", power_line_setting == 1 ? "50Hz" : "60Hz"); + } + + if (g_manual_exposure) + { + k4a_color_control_mode_t read_mode; + ASSERT_EQ(K4A_RESULT_SUCCEEDED, + k4a_device_set_color_control(m_device, + K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE, + K4A_COLOR_CONTROL_MODE_MANUAL, + (int32_t)g_exposure_setting)); + ASSERT_EQ(K4A_RESULT_SUCCEEDED, + k4a_device_get_color_control(m_device, + K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE, + &read_mode, + &read_exposure)); + printf( + "Setting exposure to manual mode, exposure target is: %d. Actual mode is: %s. Actual value is: %d.\n", + g_exposure_setting, + read_mode == K4A_COLOR_CONTROL_MODE_AUTO ? "auto" : "manual", + read_exposure); + read_exposure = 0; // Clear this so we read it again after sensor is started. + } + else + { + ASSERT_EQ(K4A_RESULT_SUCCEEDED, + k4a_device_set_color_control(m_device, + K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE, + K4A_COLOR_CONTROL_MODE_AUTO, + 0)); + printf("Auto Exposure\n"); + read_exposure = 0; + } + + config.color_format = as.color_format; + config.color_resolution = as.color_resolution; + config.depth_mode = as.depth_mode; + config.camera_fps = as.fps; + config.depth_delay_off_color_usec = g_depth_delay_off_color_usec; + config.wired_sync_mode = g_wired_sync_mode; + config.synchronized_images_only = g_synchronized_images_only; + config.subordinate_delay_off_master_usec = g_subordinate_delay_off_master_usec; + + printf("Config being used is:\n"); + printf(" color_format:%d\n", config.color_format); + printf(" color_resolution:%d\n", config.color_resolution); + printf(" depth_mode:%d\n", config.depth_mode); + printf(" camera_fps:%d\n", config.camera_fps); + printf(" synchronized_images_only:%d\n", config.synchronized_images_only); + printf(" depth_delay_off_color_usec:%d\n", config.depth_delay_off_color_usec); + printf(" wired_sync_mode:%d\n", config.wired_sync_mode); + printf(" subordinate_delay_off_master_usec:%d\n", config.subordinate_delay_off_master_usec); + printf(" disable_streaming_indicator:%d\n", config.disable_streaming_indicator); + printf("\n"); + ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_cameras(m_device, &config)); + + thread.device = m_device; + ASSERT_EQ(THREADAPI_OK, ThreadAPI_Create(&th1, _latency_imu_thread, &thread)); + + if (!g_no_startup_flush) + { + // + // Wait for streams to start and then purge the data collected + // + if (as.fps == K4A_FRAMES_PER_SECOND_30) + { + printf("Flushing first 2s of data\n"); + ThreadAPI_Sleep(2000); + } + else if (as.fps == K4A_FRAMES_PER_SECOND_15) + { + printf("Flushing first 3s of data\n"); + ThreadAPI_Sleep(3000); + } + else + { + printf("Flushing first 4s of data\n"); + ThreadAPI_Sleep(4000); + } + while (K4A_WAIT_RESULT_SUCCEEDED == k4a_device_get_capture(m_device, &capture, 0)) + { + // Drain the queue + k4a_capture_release(capture); + }; + } + else + { + printf("Flushing no start of stream data\n"); + } + + // For consistent IMU timing, block entering the while loop until we get 1 sample + if (K4A_WAIT_RESULT_SUCCEEDED == k4a_device_get_capture(m_device, &capture, 1000)) + { + k4a_capture_release(capture); + capture = NULL; + } + + printf("Sys lat: is this difference in the system time recorded on the image and the system time when the image " + "was presented to the caller.\n"); + printf( + "PTS lat: Similar to Sys lat, but instead of using the system time assigned to the image (which is recorded by " + "the Host PC), the image PTS (which is center of exposure in single camera mode) is used to " + "calculate a more accurate system time from when the same PTS arrived from the least latent sensor source, " + "IMU. The IMU data received is turned into a list of PTS values and associated system ts's for when each " + "sample arrived on system.\n"); + printf("+---------------------------+---------------------------+\n"); + printf("| Color Info (ms) | IR 16 Info (ms) |\n"); + printf("| system [ sys ] [ PTS ] | system [ sys ] [ PTS ] |\n"); + printf("| ts [ lat ] [ lat ] | ts [ lat ] [ lat ] |\n"); + printf("+---------------------------+---------------------------+\n"); + + thread.save_samples = true; // start saving IMU samples + bool color_first_pass = true; + bool ir_first_pass = true; + capture_count++; // to account for dropping the first sample + while (capture_count-- > 0) + { + if (capture) + { + k4a_capture_release(capture); + } + + // Get a depth frame + k4a_wait_result_t wresult = k4a_device_get_capture(m_device, &capture, TIMEOUT_IN_MS); + if (wresult != K4A_WAIT_RESULT_SUCCEEDED) + { + if (wresult == K4A_WAIT_RESULT_TIMEOUT) + { + printf("Timed out waiting for a capture\n"); + } + else // wresult == K4A_WAIT_RESULT_FAILED: + { + printf("Failed to read a capture\n"); + capture_count = 0; + } + failed = true; + continue; + } + + if (get_system_time(¤t_system_ts) == 0) + { + printf("Timed out waiting for a capture\n"); + failed = true; + continue; + } + + if (read_exposure == 0) + { + k4a_image_t image = k4a_capture_get_color_image(capture); + if (image) + { + read_exposure = (int32_t)k4a_image_get_exposure_usec(image); + k4a_image_release(image); + } + } + + process_image(capture, + current_system_ts, + true, // Color Image + &color_first_pass, + &color_system_latency, + &color_system_latency_from_pts, + &color_system_ts_last, + &color_system_ts_from_pts_last); + process_image(capture, + current_system_ts, + false, // IR Image + &ir_first_pass, + &ir_system_latency, + &ir_system_latency_from_pts, + &ir_system_ts_last, + &ir_system_ts_from_pts_last); + + printf("|\n"); // End of line + } // End capture loop + + thread.exit = true; // shut down IMU thread + k4a_device_stop_cameras(m_device); + if (capture) + { + k4a_capture_release(capture); + } + + int thread_result; + ASSERT_EQ(THREADAPI_OK, ThreadAPI_Join(th1, &thread_result)); + ASSERT_EQ(thread_result, (int)K4A_RESULT_SUCCEEDED); + + printf("\nLatency Results:\n"); + + { + // init CSV line + if (m_file_handle != 0) + { + std::time_t date_time = std::time(NULL); + char buffer_date_time[100]; + std::strftime(buffer_date_time, sizeof(buffer_date_time), "%c", localtime(&date_time)); + + const char *computer_name = environment_get_variable("COMPUTERNAME"); + const char *disable_synchronization = environment_get_variable("K4A_DISABLE_SYNCHRONIZATION"); + + char buffer[1024]; + snprintf(buffer, + sizeof(buffer), + "%s, %s, %s, %s,%s, %s, fps, %d, %s, captures, %d, %d, %d,", + buffer_date_time, + computer_name ? computer_name : "computer name not set", + as.test_name, + disable_synchronization ? disable_synchronization : "0", + get_string_from_color_format(as.color_format), + get_string_from_color_resolution(as.color_resolution), + k4a_convert_fps_to_uint(as.fps), + get_string_from_depth_mode(as.depth_mode), + g_capture_count, + g_manual_exposure, + read_exposure); + fputs(buffer, m_file_handle); + } + } + { + uint64_t color_system_latency_ave = 0; + uint64_t min = (uint64_t)-1; + uint64_t max = 0; + for (size_t x = 0; x < color_system_latency.size(); x++) + { + color_system_latency_ave += color_system_latency[x]; + if (color_system_latency[x] < min) + { + min = color_system_latency[x]; + } + if (color_system_latency[x] > max) + { + max = color_system_latency[x]; + } + } + color_system_latency_ave = color_system_latency_ave / color_system_latency.size(); + print_and_log("Color System Time Latency", + get_string_from_color_format(config.color_format), + STS_TO_MS(color_system_latency_ave), + STS_TO_MS(min), + STS_TO_MS(max)); + } + { + uint64_t color_system_latency_from_pts_ave = 0; + uint64_t min = (uint64_t)-1; + uint64_t max = 0; + for (size_t x = 0; x < color_system_latency_from_pts.size(); x++) + { + color_system_latency_from_pts_ave += color_system_latency_from_pts[x]; + if (color_system_latency_from_pts[x] < min) + { + min = color_system_latency_from_pts[x]; + } + if (color_system_latency_from_pts[x] > max) + { + max = color_system_latency_from_pts[x]; + } + } + color_system_latency_from_pts_ave = color_system_latency_from_pts_ave / color_system_latency_from_pts.size(); + print_and_log("Color System Time PTS Latency", + get_string_from_color_format(config.color_format), + STS_TO_MS(color_system_latency_from_pts_ave), + STS_TO_MS(min), + STS_TO_MS(max)); + } + { + uint64_t ir_system_latency_ave = 0; + uint64_t min = (uint64_t)-1; + uint64_t max = 0; + for (size_t x = 0; x < ir_system_latency.size(); x++) + { + ir_system_latency_ave += ir_system_latency[x]; + if (ir_system_latency[x] < min) + { + min = ir_system_latency[x]; + } + if (ir_system_latency[x] > max) + { + max = ir_system_latency[x]; + } + } + ir_system_latency_ave = ir_system_latency_ave / ir_system_latency.size(); + print_and_log(" IR System Time Latency", + get_string_from_depth_mode(config.depth_mode), + STS_TO_MS(ir_system_latency_ave), + STS_TO_MS(min), + STS_TO_MS(max)); + } + { + uint64_t ir_system_latency_from_pts_ave = 0; + uint64_t min = (uint64_t)-1; + uint64_t max = 0; + for (size_t x = 0; x < ir_system_latency_from_pts.size(); x++) + { + ir_system_latency_from_pts_ave += ir_system_latency_from_pts[x]; + if (ir_system_latency_from_pts[x] < min) + { + min = ir_system_latency_from_pts[x]; + } + if (ir_system_latency_from_pts[x] > max) + { + max = ir_system_latency_from_pts[x]; + } + } + ir_system_latency_from_pts_ave = ir_system_latency_from_pts_ave / ir_system_latency_from_pts.size(); + print_and_log(" IR System Time PTS", + get_string_from_depth_mode(config.depth_mode), + STS_TO_MS(ir_system_latency_from_pts_ave), + STS_TO_MS(min), + STS_TO_MS(max)); + } + + printf("\n"); + if (m_file_handle != 0) + { + // Terminate line + fputs("\n", m_file_handle); + } + + ASSERT_EQ(K4A_RESULT_SUCCEEDED, + k4a_device_set_color_control(m_device, + K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE, + K4A_COLOR_CONTROL_MODE_AUTO, + 0)); + + ASSERT_EQ(failed, false); + return; +} + +// K4A_DEPTH_MODE_WFOV_UNBINNED is the most demanding depth mode, only runs at 15FPS or less + +// clang-format off +// PASSIVE_IR is fastest Depth Mode - YUY2 is fastest Color mode +static struct latency_parameters tests_30fps[] = { + // All Color modes with fast Depth + { 0, "FPS_30_MJPEG_2160P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_MJPG, K4A_COLOR_RESOLUTION_2160P, K4A_DEPTH_MODE_PASSIVE_IR}, + { 1, "FPS_30_MJPEG_1536P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_MJPG, K4A_COLOR_RESOLUTION_1536P, K4A_DEPTH_MODE_PASSIVE_IR}, + { 2, "FPS_30_MJPEG_1440P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_MJPG, K4A_COLOR_RESOLUTION_1440P, K4A_DEPTH_MODE_PASSIVE_IR}, + { 3, "FPS_30_MJPEG_1080P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_MJPG, K4A_COLOR_RESOLUTION_1080P, K4A_DEPTH_MODE_PASSIVE_IR}, + { 4, "FPS_30_MJPEG_0720P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_MJPG, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_PASSIVE_IR}, + { 5, "FPS_30_NV12__0720P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_NV12, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_PASSIVE_IR}, + { 6, "FPS_30_YUY2__0720P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_YUY2, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_PASSIVE_IR}, + { 7, "FPS_30_BGRA32_2160P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_BGRA32, K4A_COLOR_RESOLUTION_2160P, K4A_DEPTH_MODE_PASSIVE_IR}, + { 8, "FPS_30_BGRA32_1536P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_BGRA32, K4A_COLOR_RESOLUTION_1536P, K4A_DEPTH_MODE_PASSIVE_IR}, + { 9, "FPS_30_BGRA32_1440P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_BGRA32, K4A_COLOR_RESOLUTION_1440P, K4A_DEPTH_MODE_PASSIVE_IR}, + { 10, "FPS_30_BGRA32_1080P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_BGRA32, K4A_COLOR_RESOLUTION_1080P, K4A_DEPTH_MODE_PASSIVE_IR}, + { 11, "FPS_30_BGRA32_0720P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_BGRA32, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_PASSIVE_IR}, + + // All Depth Modes with fastest Color + { 12, "FPS_30_YUY2__0720P_NFOV_2X2BINNED", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_YUY2, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_NFOV_2X2BINNED}, + { 13, "FPS_30_YUY2__0720P_NFOV_UNBINNED", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_YUY2, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_NFOV_UNBINNED}, + { 14, "FPS_30_YUY2__0720P_WFOV_2X2BINNED", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_YUY2, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_WFOV_2X2BINNED}, + { 15, "FPS_30_YUY2__0720P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_30, K4A_IMAGE_FORMAT_COLOR_YUY2, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_PASSIVE_IR}, + +}; + +INSTANTIATE_TEST_CASE_P(30FPS_TESTS, latency_perf, ValuesIn(tests_30fps)); + +static struct latency_parameters tests_15fps[] = { + // All Color modes with fast Depth + { 0, "FPS_15_MJPEG_3072P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_15, K4A_IMAGE_FORMAT_COLOR_MJPG, K4A_COLOR_RESOLUTION_3072P, K4A_DEPTH_MODE_PASSIVE_IR}, + { 1, "FPS_15_BGRA32_3072P_PASSIVE_IR", K4A_FRAMES_PER_SECOND_15, K4A_IMAGE_FORMAT_COLOR_BGRA32, K4A_COLOR_RESOLUTION_3072P, K4A_DEPTH_MODE_PASSIVE_IR}, + + // All Depth Modes with fastest Color + { 2, "FPS_15_YUY2__0720P_WFOV_UNBINNED", K4A_FRAMES_PER_SECOND_15, K4A_IMAGE_FORMAT_COLOR_YUY2, K4A_COLOR_RESOLUTION_720P, K4A_DEPTH_MODE_WFOV_UNBINNED}, +}; + +INSTANTIATE_TEST_CASE_P(15FPS_TESTS, latency_perf, ValuesIn(tests_15fps)); +// clang-format on + +int main(int argc, char **argv) +{ + bool error = false; + k4a_unittest_init(); + + ::testing::InitGoogleTest(&argc, argv); + + for (int i = 1; i < argc; ++i) + { + char *argument = argv[i]; + for (int j = 0; argument[j]; j++) + { + argument[j] = (char)tolower(argument[j]); + } + if (strcmp(argument, "--depth_delay_off_color") == 0) + { + if (i + 1 <= argc) + { + + g_depth_delay_off_color_usec = (int32_t)strtol(argv[i + 1], NULL, 10); + printf("Setting g_depth_delay_off_color_usec = %d\n", g_depth_delay_off_color_usec); + i++; + } + else + { + printf("Error: depth_delay_off_color parameter missing\n"); + error = true; + } + } + else if (strcmp(argument, "--skip_delay_off_color_validation") == 0) + { + g_skip_delay_off_color_validation = true; + } + else if (strcmp(argument, "--master") == 0) + { + g_wired_sync_mode = K4A_WIRED_SYNC_MODE_MASTER; + printf("Setting g_wired_sync_mode = K4A_WIRED_SYNC_MODE_MASTER\n"); + } + else if (strcmp(argument, "--subordinate") == 0) + { + g_wired_sync_mode = K4A_WIRED_SYNC_MODE_SUBORDINATE; + printf("Setting g_wired_sync_mode = K4A_WIRED_SYNC_MODE_SUBORDINATE\n"); + } + else if (strcmp(argument, "--synchronized_images_only") == 0) + { + g_synchronized_images_only = true; + printf("g_synchronized_images_only = true\n"); + } + else if (strcmp(argument, "--no_startup_flush") == 0) + { + g_no_startup_flush = true; + printf("g_no_startup_flush = true\n"); + } + else if (strcmp(argument, "--60hz") == 0) + { + g_power_line_50_hz = false; + printf("g_power_line_50_hz = false\n"); + } + else if (strcmp(argument, "--50hz") == 0) + { + g_power_line_50_hz = true; + printf("g_power_line_50_hz = true\n"); + } + else if (strcmp(argument, "--index") == 0) + { + if (i + 1 <= argc) + { + g_device_index = (uint8_t)strtol(argv[i + 1], NULL, 10); + printf("setting g_device_index = %d\n", g_device_index); + i++; + } + else + { + printf("Error: index parameter missing\n"); + error = true; + } + } + else if (strcmp(argument, "--subordinate_delay_off_master_usec") == 0) + { + if (i + 1 <= argc) + { + g_subordinate_delay_off_master_usec = (uint32_t)strtol(argv[i + 1], NULL, 10); + printf("g_subordinate_delay_off_master_usec = %d\n", g_subordinate_delay_off_master_usec); + i++; + } + else + { + printf("Error: index parameter missing\n"); + error = true; + } + } + else if (strcmp(argument, "--capture_count") == 0) + { + if (i + 1 <= argc) + { + g_capture_count = (int)strtol(argv[i + 1], NULL, 10); + printf("g_capture_count g_device_index = %d\n", g_capture_count); + i++; + } + else + { + printf("Error: index parameter missing\n"); + error = true; + } + } + else if (strcmp(argument, "--exposure") == 0) + { + if (i + 1 <= argc) + { + g_exposure_setting = (uint32_t)strtol(argv[i + 1], NULL, 10); + printf("g_exposure_setting = %d\n", g_exposure_setting); + g_manual_exposure = true; + i++; + } + else + { + printf("Error: index parameter missing\n"); + error = true; + } + } + else if (strcmp(argument, "--auto") == 0) + { + g_manual_exposure = false; + printf("Auto Exposure Enabled\n"); + } + + if ((strcmp(argument, "-h") == 0) || (strcmp(argument, "/h") == 0) || (strcmp(argument, "-?") == 0) || + (strcmp(argument, "/?") == 0)) + { + error = true; + } + } + + if (error) + { + printf("\n\nOptional Custom Test Settings:\n"); + printf(" --depth_delay_off_color <+/- microseconds>\n"); + printf(" This is the time delay the depth image capture is delayed off the color.\n"); + printf(" valid ranges for this are -1 frame time to +1 frame time. The percentage\n"); + printf(" needs to be multiplied by 100 to achieve correct behavior; 10000 is \n"); + printf(" 100.00%%, 100 is 1.00%%.\n"); + printf(" --skip_delay_off_color_validation\n"); + printf(" Set this when don't want the results of color to depth timestamp \n" + " measurements to allow your test run to fail. They will still be logged\n" + " to output and the CSV file.\n"); + printf(" --master\n"); + printf(" Run device in master mode\n"); + printf(" --subordinate\n"); + printf(" Run device in subordinate mode\n"); + printf(" --index\n"); + printf(" The device index to target when calling k4a_device_open()\n"); + printf(" --capture_count\n"); + printf(" The number of captures the test should read; default is 100\n"); + printf(" --synchronized_images_only\n"); + printf(" By default this setting is false, enabling this will for the test to wait for\n"); + printf(" both and depth images to be available.\n"); + printf(" --subordinate_delay_off_master_usec <+ microseconds>\n"); + printf(" This is the time delay the device captures off the master devices capture sync\n"); + printf(" pulse. This value needs to be less than one image sample period, i.e for 30FPS \n"); + printf(" this needs to be less than 33333us.\n"); + printf(" --no_startup_flush\n"); + printf(" By default the test will wait for streams to run for X seconds to stabilize. This\n"); + printf(" disables that.\n"); + printf(" --exposure \n"); + printf(" Deault is manual exposure with an exposure of 33,333us. This will test with the manual exposure " + "setting\n"); + printf(" that is passed in.\n"); + printf(" --auto\n"); + printf(" By default the test uses manual exposure. This will test with auto exposure.\n"); + printf(" --60hz\n"); + printf(" Sets the power line compensation frequency to 60Hz\n"); + printf(" --50hz\n"); + printf(" Sets the power line compensation frequency to 50Hz\n"); + + return 1; // Indicates an error or warning + } + int results = RUN_ALL_TESTS(); + k4a_unittest_deinit(); + return results; +} diff --git a/tests/multidevice/multidevice.cpp b/tests/multidevice/multidevice.cpp index 844b5e808..9ea14d7a6 100644 --- a/tests/multidevice/multidevice.cpp +++ b/tests/multidevice/multidevice.cpp @@ -16,9 +16,145 @@ // event the test regresses. #define WAIT_TEST_INFINITE (5 * 60 * 1000) +#define LLD(val) ((int64_t)(val)) +#define NULL_IMAGE 0 +#define NULL_DEVICE 0 + +const int SAMPLES_TO_STABILIZE = 10; + +static int32_t g_sample_count = 100; +static uint32_t g_subordinate_delay = 0; +static int32_t g_m_depth_delay = 0; +static int32_t g_s_depth_delay = 0; +static k4a_fps_t g_frame_rate = (k4a_fps_t)-1; + int main(int argc, char **argv) { - return k4a_test_common_main(argc, argv); + bool error = false; + k4a_unittest_init(); + + srand((unsigned int)time(0)); // use current time as seed for random generator + + ::testing::InitGoogleTest(&argc, argv); + + for (int i = 1; i < argc; ++i) + { + char *argument = argv[i]; + for (int j = 0; argument[j]; j++) + { + argument[j] = (char)tolower(argument[j]); + } + if (strcmp(argument, "--m_depth_delay") == 0) + { + if (i + 1 <= argc) + { + g_m_depth_delay = (int32_t)strtol(argv[i + 1], NULL, 10); + printf("Setting g_m_depth_delay = %d\n", g_m_depth_delay); + i++; + } + else + { + printf("Error: g_m_depth_delay parameter missing\n"); + error = true; + } + } + else if (strcmp(argument, "--s_depth_delay") == 0) + { + if (i + 1 <= argc) + { + g_s_depth_delay = (int32_t)strtol(argv[i + 1], NULL, 10); + printf("Setting g_s_depth_delay = %d\n", g_s_depth_delay); + i++; + } + else + { + printf("Error: g_s_depth_delay parameter missing\n"); + error = true; + } + } + else if (strcmp(argument, "--subordinate_delay") == 0) + { + if (i + 1 <= argc) + { + g_subordinate_delay = (uint32_t)strtol(argv[i + 1], NULL, 10); + printf("Setting g_subordinate_delay = %u\n", g_subordinate_delay); + i++; + } + else + { + printf("Error: g_subordinate_delay parameter missing\n"); + error = true; + } + } + else if (strcmp(argument, "--fps") == 0) + { + if (i + 1 <= argc) + { + int32_t frame_rate; + frame_rate = (int32_t)strtol(argv[i + 1], NULL, 10); + if (frame_rate == 5) + { + g_frame_rate = K4A_FRAMES_PER_SECOND_5; + } + else if (frame_rate == 15) + { + g_frame_rate = K4A_FRAMES_PER_SECOND_15; + } + else if (frame_rate == 30) + { + g_frame_rate = K4A_FRAMES_PER_SECOND_30; + } + else if (frame_rate == K4A_FRAMES_PER_SECOND_5 || frame_rate == K4A_FRAMES_PER_SECOND_15 || + frame_rate == K4A_FRAMES_PER_SECOND_30) + { + g_frame_rate = (k4a_fps_t)frame_rate; + } + else + { + printf("Error: --fps parameter invalid: %d\n", frame_rate); + error = true; + } + if (!error) + { + printf("Setting frame_rate = %d\n", g_frame_rate); + i++; + } + } + else + { + printf("Error: frame_rate parameter missing\n"); + error = true; + } + } + else + { + error = true; + printf("Error: Command %s unknown\n", argument); + } + + if ((strcmp(argument, "-h") == 0) || (strcmp(argument, "/h") == 0) || (strcmp(argument, "-?") == 0) || + (strcmp(argument, "/?") == 0)) + { + error = true; + } + } + + if (error) + { + printf("\n\nOptional Custom Test Settings:\n"); + printf(" --m_depth_delay <+/- microseconds>\n"); + printf(" This is the depth capture delay off of the color capture for the master Kinect.\n"); + printf(" --s_depth_delay <+/- microseconds>\n"); + printf(" This is the depth capture delay off of the color capture for the subordinate Kinect.\n"); + printf(" --subordinate_delay <+ microseconds>\n"); + printf(" This is the subordinate delay off of the master Kinect\n"); + printf(" --fps <5,15,30 FPS\n"); + printf(" This is the frame rate to run the test at\n"); + return 1; // Indicates an error or warning + } + int results = RUN_ALL_TESTS(); + k4a_unittest_deinit(); + return results; } class multidevice_ft : public ::testing::Test @@ -48,6 +184,32 @@ class multidevice_ft : public ::testing::Test k4a_device_t m_device2 = nullptr; }; +class multidevice_sync_ft : public ::testing::Test +{ +public: + virtual void SetUp() + { + ASSERT_EQ(m_master, nullptr); + ASSERT_EQ(m_subordinate, nullptr); + } + + virtual void TearDown() + { + if (m_master != nullptr) + { + k4a_device_close(m_master); + m_master = nullptr; + } + if (m_subordinate != nullptr) + { + k4a_device_close(m_subordinate); + m_subordinate = nullptr; + } + } + k4a_device_t m_master = nullptr; + k4a_device_t m_subordinate = nullptr; +}; + TEST_F(multidevice_ft, open_close_two) { ASSERT_LE((uint32_t)2, k4a_device_get_installed_count()); @@ -157,6 +319,377 @@ TEST_F(multidevice_ft, stream_two_2_then_1) m_device1 = NULL; } +#define RETURN_K4A_RESULT_LE(msg1, msg2, v1, v2) \ + if (!(v1 <= v2)) \ + { \ + printf("%s(%d): ERROR: expected %s <= %s\n %" PRId64 " vs %" PRId64 "\n", \ + __FILE__, \ + __LINE__, \ + msg1, \ + msg2, \ + LLD(v1), \ + LLD(v2)); \ + return K4A_RESULT_FAILED; \ + } + +#define RETURN_K4A_RESULT_EQ(msg1, msg2, v1, v2) \ + if (!(v1 == v2)) \ + { \ + printf("%s(%d): ERROR: expected %s == %s\n %" PRId64 " vs %" PRId64 "\n", \ + __FILE__, \ + __LINE__, \ + msg1, \ + msg2, \ + LLD(v1), \ + LLD(v2)); \ + return K4A_RESULT_FAILED; \ + } + +#define RETURN_K4A_RESULT_NE(msg1, msg2, v1, v2) \ + if (!(v1 != v2)) \ + { \ + printf("%s(%d): ERROR: expected %s != %s\n %" PRId64 " vs %" PRId64 "\n", \ + __FILE__, \ + __LINE__, \ + msg1, \ + msg2, \ + LLD(v1), \ + LLD(v2)); \ + return K4A_RESULT_FAILED; \ + } + +#define R_EXPECT_LE(v1, v2) RETURN_K4A_RESULT_LE(#v1, #v2, v1, v2) +#define R_EXPECT_EQ(v1, v2) RETURN_K4A_RESULT_EQ(#v1, #v2, v1, v2) +#define R_EXPECT_NE(v1, v2) RETURN_K4A_RESULT_NE(#v1, #v2, v1, v2) + +static k4a_result_t open_master_and_subordinate(k4a_device_t *master, k4a_device_t *subordinate) +{ + *master = NULL; + *subordinate = NULL; + + uint32_t devices_present = k4a_device_get_installed_count(); + R_EXPECT_LE((int64_t)2, devices_present); + + for (uint32_t x = 0; x < devices_present; x++) + { + k4a_device_t device; + R_EXPECT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_open(x, &device)); + + bool sync_in_cable_present; + bool sync_out_cable_present; + + R_EXPECT_EQ(K4A_RESULT_SUCCEEDED, + k4a_device_get_sync_jack(device, &sync_in_cable_present, &sync_out_cable_present)); + + if (*master == NULL && sync_out_cable_present) + { + *master = device; + device = NULL; + } + else if (*subordinate == NULL && sync_in_cable_present) + { + *subordinate = device; + device = NULL; + } + + if (device) + { + k4a_device_close(device); + } + } + + R_EXPECT_NE(NULL_DEVICE, *master); + R_EXPECT_NE(NULL_DEVICE, *subordinate); + return K4A_RESULT_SUCCEEDED; +} + +static k4a_result_t set_power_and_exposure(k4a_device_t device, int exposure_setting, int power_line_setting) +{ + int read_power_line_setting; + int read_exposure; + k4a_color_control_mode_t read_mode; + + R_EXPECT_EQ(K4A_RESULT_SUCCEEDED, + k4a_device_set_color_control(device, + K4A_COLOR_CONTROL_POWERLINE_FREQUENCY, + K4A_COLOR_CONTROL_MODE_MANUAL, + power_line_setting)); + + R_EXPECT_EQ(K4A_RESULT_SUCCEEDED, + k4a_device_get_color_control(device, + K4A_COLOR_CONTROL_POWERLINE_FREQUENCY, + &read_mode, + &read_power_line_setting)); + R_EXPECT_EQ(read_power_line_setting, power_line_setting); + + R_EXPECT_EQ(K4A_RESULT_SUCCEEDED, + k4a_device_set_color_control(device, + K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE, + K4A_COLOR_CONTROL_MODE_MANUAL, + (int32_t)exposure_setting)); + R_EXPECT_EQ(K4A_RESULT_SUCCEEDED, + k4a_device_get_color_control(device, + K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE, + &read_mode, + &read_exposure)); + R_EXPECT_EQ(exposure_setting, read_exposure); + return K4A_RESULT_SUCCEEDED; +} + +static k4a_result_t get_syncd_captures(k4a_device_t master, + k4a_device_t sub, + k4a_capture_t *cap_m, + k4a_capture_t *cap_s, + uint32_t subordinate_delay_off_master_usec, + int64_t max_sync_delay) +{ + const int timeout_ms = 10000; + int64_t ts_m, ts_s, ts_s_adj; + k4a_image_t image_m, image_s; + int tries = 0; + + R_EXPECT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_capture(master, cap_m, timeout_ms)); + R_EXPECT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_capture(sub, cap_s, timeout_ms)); + + R_EXPECT_NE(NULL_IMAGE, (image_m = k4a_capture_get_color_image(*cap_m))); + R_EXPECT_NE(NULL_IMAGE, (image_s = k4a_capture_get_color_image(*cap_s))); + ts_m = (int64_t)k4a_image_get_device_timestamp_usec(image_m); + ts_s = (int64_t)k4a_image_get_device_timestamp_usec(image_s); + k4a_image_release(image_m); + k4a_image_release(image_s); + + ts_s_adj = ts_s - subordinate_delay_off_master_usec; + + int64_t ts_delta = std::abs(ts_m - ts_s_adj); + while (ts_delta > max_sync_delay) + { + // bail out if it never happens + R_EXPECT_LE(tries++, 100); + + if (ts_m < ts_s_adj) + { + printf("Master too old m:%9" PRId64 " s:%9" PRId64 " adj sub:%9" PRId64 " adj delta:%9" PRId64 "\n", + ts_m, + ts_s, + ts_s_adj, + ts_delta); + k4a_capture_release(*cap_m); + R_EXPECT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_capture(master, cap_m, 10000)); + R_EXPECT_NE(NULL_IMAGE, (image_m = k4a_capture_get_color_image(*cap_m))); + ts_m = (int64_t)k4a_image_get_device_timestamp_usec(image_m); + k4a_image_release(image_m); + } + else + { + printf("Sub too old m:%9" PRId64 " s:%9" PRId64 " adj sub:%9" PRId64 " adj delta:%9" PRId64 "\n", + ts_m, + ts_s, + ts_s_adj, + ts_delta); + k4a_capture_release(*cap_s); + R_EXPECT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_capture(sub, cap_s, 10000)); + R_EXPECT_NE(NULL_IMAGE, (image_s = k4a_capture_get_color_image(*cap_s))); + ts_s = (int64_t)k4a_image_get_device_timestamp_usec(image_s); + ts_s_adj = ts_s - subordinate_delay_off_master_usec; + k4a_image_release(image_s); + } + + ts_delta = std::abs(ts_m - ts_s_adj); + } + return K4A_RESULT_SUCCEEDED; +} + +static k4a_result_t +verify_ts(int64_t ts_1, int64_t ts_2, int64_t ts_offset, int64_t max_sync_delay, const char *error_message) +{ + int64_t ts_1_adjust = ts_1 + ts_offset; + int64_t ts_result = std::abs(ts_1_adjust - ts_2); + if (ts_result > max_sync_delay) + { + printf(" ERROR timestamps are not within range.\n TS1 + TS_Offset should be ~= TS2. %s\n ts1=%" PRId64 + " ts2=%" PRId64 " ts_offset=%" PRId64 " diff=%" PRId64 "\n", + error_message, + ts_1, + ts_2, + ts_offset, + ts_result); + R_EXPECT_LE(ts_result, max_sync_delay); + } + return K4A_RESULT_SUCCEEDED; +} + +TEST_F(multidevice_sync_ft, multi_sync_validation) +{ + if (g_frame_rate != K4A_FRAMES_PER_SECOND_5 && g_frame_rate != K4A_FRAMES_PER_SECOND_15 && + g_frame_rate != K4A_FRAMES_PER_SECOND_30) + { +#if defined(__aarch64__) || defined(_M_ARM64) + // Jetson Nano can't handle 2 30FPS streams + printf("Using 5 or 15FPS for ARM64 build\n"); + int frame_rate_rand = (int)RAND_VALUE(0, 1); +#else + printf("Using 5, 15, or 30FPS for AMD64/x86 build\n"); + int frame_rate_rand = (int)RAND_VALUE(0, 2); +#endif + switch (frame_rate_rand) + { + case 0: + g_frame_rate = K4A_FRAMES_PER_SECOND_5; + break; + case 1: + g_frame_rate = K4A_FRAMES_PER_SECOND_15; + break; + default: + g_frame_rate = K4A_FRAMES_PER_SECOND_30; + break; + } + } + + int32_t fps_in_usec = 1000000 / (int32_t)k4a_convert_fps_to_uint(g_frame_rate); + if (g_m_depth_delay == 0) + { + g_m_depth_delay = (int32_t)RAND_VALUE(-fps_in_usec, fps_in_usec); + } + if (g_s_depth_delay == 0) + { + g_s_depth_delay = (int32_t)RAND_VALUE(-fps_in_usec, fps_in_usec); + } + if (g_subordinate_delay == 0) + { + g_subordinate_delay = (uint32_t)RAND_VALUE(0, fps_in_usec); + } + + ASSERT_EQ(open_master_and_subordinate(&m_master, &m_subordinate), K4A_RESULT_SUCCEEDED); + + ASSERT_EQ(K4A_RESULT_SUCCEEDED, set_power_and_exposure(m_master, 8330, 2)) << "Master Device"; + ASSERT_EQ(K4A_RESULT_SUCCEEDED, set_power_and_exposure(m_subordinate, 8330, 2)) << "Subordinate Device"; + + k4a_device_configuration_t default_config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL; + default_config.color_format = K4A_IMAGE_FORMAT_COLOR_MJPG; + default_config.color_resolution = K4A_COLOR_RESOLUTION_2160P; + default_config.depth_mode = K4A_DEPTH_MODE_NFOV_2X2BINNED; + default_config.camera_fps = g_frame_rate; + default_config.subordinate_delay_off_master_usec = 0; + default_config.depth_delay_off_color_usec = 0; + default_config.synchronized_images_only = true; + + k4a_device_configuration_t s_config = default_config; + s_config.wired_sync_mode = K4A_WIRED_SYNC_MODE_SUBORDINATE; + s_config.depth_delay_off_color_usec = g_s_depth_delay; + s_config.subordinate_delay_off_master_usec = g_subordinate_delay; + ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_cameras(m_subordinate, &s_config)) << "Subordinate Device"; + + k4a_device_configuration_t m_config = default_config; + m_config.wired_sync_mode = K4A_WIRED_SYNC_MODE_MASTER; + m_config.depth_delay_off_color_usec = g_m_depth_delay; + ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_cameras(m_master, &m_config)) << "Master Device"; + + printf("Test Running with the following settings:\n"); + printf(" Frame Rate: %s\n", + g_frame_rate == K4A_FRAMES_PER_SECOND_5 ? + "5" : + (g_frame_rate == K4A_FRAMES_PER_SECOND_15 ? + "15" : + (g_frame_rate == K4A_FRAMES_PER_SECOND_30 ? "30" : "Unknown"))); + printf(" Master depth_delay_off_color_usec: %d\n", m_config.depth_delay_off_color_usec); + printf(" Sub depth_delay_off_color_usec: %d\n", s_config.depth_delay_off_color_usec); + printf(" Sub subordinate_delay_off_master_usec: %d\n", s_config.subordinate_delay_off_master_usec); + + printf("\nDelta = Time off master color. All times in usec\n"); + printf("Master Color, Master IR(Delta), Sub Color(Delta), Sub IR(Delta)\n"); + printf("---------------------------------------------------------------\n"); + + int64_t ts_m_c_old = 0; + float sequential_frames = 0; + + for (int x = 0; x < g_sample_count; x++) + { + k4a_capture_t cap_m, cap_s; + int64_t ts_m_c, ts_m_ir, ts_s_c, ts_s_ir; + k4a_image_t image_c_m, image_ir_m, image_c_s, image_ir_s; + int64_t max_sync_delay = k4a_unittest_get_max_sync_delay_ms(g_frame_rate); + + ASSERT_EQ(K4A_RESULT_SUCCEEDED, + get_syncd_captures(m_master, + m_subordinate, + &cap_m, + &cap_s, + s_config.subordinate_delay_off_master_usec, + max_sync_delay)); + + ASSERT_FALSE(NULL_IMAGE == (image_c_m = k4a_capture_get_color_image(cap_m))); + ASSERT_FALSE(NULL_IMAGE == (image_c_s = k4a_capture_get_color_image(cap_s))); + ASSERT_FALSE(NULL_IMAGE == (image_ir_m = k4a_capture_get_ir_image(cap_m))); + ASSERT_FALSE(NULL_IMAGE == (image_ir_s = k4a_capture_get_ir_image(cap_s))); + + ts_m_c = (int64_t)k4a_image_get_device_timestamp_usec(image_c_m); + ts_s_c = (int64_t)k4a_image_get_device_timestamp_usec(image_c_s); + ts_m_ir = (int64_t)k4a_image_get_device_timestamp_usec(image_ir_m); + ts_s_ir = (int64_t)k4a_image_get_device_timestamp_usec(image_ir_s); + + printf("%9" PRId64 ", %9" PRId64 "(%5" PRId64 "), %9" PRId64 "(%5" PRId64 "), %9" PRId64 "(%5" PRId64 ") %s\n", + ts_m_c, + ts_m_ir, + ts_m_ir - ts_m_c, + ts_s_c, + ts_s_c - ts_m_c, + ts_s_ir, + ts_s_ir - ts_s_c, + x > SAMPLES_TO_STABILIZE ? "Validating" : "Stabilizing"); + + if (x >= SAMPLES_TO_STABILIZE) + { + if (std::abs(ts_m_c - ts_m_c_old) < (fps_in_usec * 11 / 10)) + { + // If we are within 110% of expected FPS we count that as 2 back to back frames + sequential_frames++; + } + else + { + float dropped = (float)(std::abs(ts_m_c - ts_m_c_old) / fps_in_usec); + printf(" WARNING %.1f frames were dropped\n", dropped); + } + + ASSERT_EQ(K4A_RESULT_SUCCEEDED, + verify_ts(ts_m_c, + ts_m_ir, + m_config.depth_delay_off_color_usec, + max_sync_delay, + "TS1 is Master Color, TS2 is Master Ir")); + ASSERT_EQ(K4A_RESULT_SUCCEEDED, + verify_ts(ts_s_c, + ts_s_ir, + s_config.depth_delay_off_color_usec, + max_sync_delay, + "TS1 is Subordinate Color, TS2 is Subordinate Ir")); + ASSERT_EQ(K4A_RESULT_SUCCEEDED, + verify_ts(ts_m_c, + ts_s_c, + (int64_t)s_config.subordinate_delay_off_master_usec, + max_sync_delay, + "TS1 is Master Color, TS2 is Subordinate Color")); + } + ts_m_c_old = ts_m_c; + + k4a_image_release(image_c_m); + k4a_image_release(image_c_s); + k4a_image_release(image_ir_m); + k4a_image_release(image_ir_s); + + k4a_capture_release(cap_m); + k4a_capture_release(cap_s); + } + k4a_device_close(m_master); + m_master = nullptr; + k4a_device_close(m_subordinate); + m_subordinate = nullptr; + + // Ensure 90% frames are arriving in the required amount of time - this is a sanity check that the FW is + // capable of meeting the demands of the frame rate for 2 devices. If for some reason we were only running at a + // fraction of the framerate this would fail. + ASSERT_GE(sequential_frames, ((g_sample_count - SAMPLES_TO_STABILIZE) * 9 / 10)); +} + TEST_F(multidevice_ft, ensure_color_camera_is_enabled) { bool master_device_found = false; @@ -268,12 +801,7 @@ static int parallel_start_thread(void *param) return result; } -#ifdef _WIN32 TEST_F(multidevice_ft, start_parallel) -#else -// GitHub: #769 https://github.com/microsoft/Azure-Kinect-Sensor-SDK/issues/769 -TEST_F(multidevice_ft, DISABLED_start_parallel) -#endif { LOCK_HANDLE lock; THREAD_HANDLE th1, th2; @@ -381,3 +909,77 @@ TEST_F(multidevice_ft, close_parallel) Lock_Deinit(lock); } + +TEST_F(multidevice_sync_ft, multi_sync_no_color) +{ + k4a_device_t master, subordinate; + k4a_fps_t frame_rate = K4A_FRAMES_PER_SECOND_30; + + ASSERT_EQ(open_master_and_subordinate(&master, &subordinate), K4A_RESULT_SUCCEEDED); + + ASSERT_EQ(K4A_RESULT_SUCCEEDED, set_power_and_exposure(master, 8330, 2)); + ASSERT_EQ(K4A_RESULT_SUCCEEDED, set_power_and_exposure(subordinate, 8330, 2)); + + k4a_device_configuration_t default_config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL; + default_config.color_format = K4A_IMAGE_FORMAT_COLOR_MJPG; + default_config.color_resolution = K4A_COLOR_RESOLUTION_2160P; + default_config.depth_mode = K4A_DEPTH_MODE_NFOV_2X2BINNED; + default_config.camera_fps = frame_rate; + default_config.subordinate_delay_off_master_usec = 0; + default_config.depth_delay_off_color_usec = 0; + default_config.synchronized_images_only = true; + + k4a_device_configuration_t s_config = default_config; + s_config.wired_sync_mode = K4A_WIRED_SYNC_MODE_SUBORDINATE; + s_config.color_resolution = K4A_COLOR_RESOLUTION_OFF; + s_config.synchronized_images_only = false; + ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_cameras(subordinate, &s_config)); + + k4a_device_configuration_t m_config = default_config; + m_config.wired_sync_mode = K4A_WIRED_SYNC_MODE_MASTER; + ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_cameras(master, &m_config)); + + for (int x = 0; x < 20; x++) + { + k4a_capture_t capture; + ASSERT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_capture(master, &capture, 10000)); + k4a_capture_release(capture); + ASSERT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_capture(subordinate, &capture, 10000)); + k4a_capture_release(capture); + } + + k4a_device_stop_cameras(master); + k4a_device_stop_cameras(subordinate); + + ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_cameras(subordinate, &s_config)); + ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_cameras(master, &m_config)); + + for (int x = 0; x < 20; x++) + { + k4a_capture_t capture; + ASSERT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_capture(master, &capture, 10000)); + k4a_capture_release(capture); + ASSERT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_capture(subordinate, &capture, 10000)); + k4a_capture_release(capture); + } + + // Reverse the stop order from above and then start again to ensure all starts as expected. + k4a_device_stop_cameras(subordinate); + k4a_device_stop_cameras(master); + + ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_cameras(subordinate, &s_config)); + ASSERT_EQ(K4A_RESULT_SUCCEEDED, k4a_device_start_cameras(master, &m_config)); + + for (int x = 0; x < 20; x++) + { + k4a_capture_t capture; + ASSERT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_capture(master, &capture, 10000)); + k4a_capture_release(capture); + ASSERT_EQ(K4A_WAIT_RESULT_SUCCEEDED, k4a_device_get_capture(subordinate, &capture, 10000)); + k4a_capture_release(capture); + } + + // Close master first and make sure not hang or crashes. + k4a_device_close(master); + k4a_device_close(subordinate); +} diff --git a/tests/throughput/throughput_perf.cpp b/tests/throughput/throughput_perf.cpp index d3097b8e4..5863c9c48 100644 --- a/tests/throughput/throughput_perf.cpp +++ b/tests/throughput/throughput_perf.cpp @@ -15,7 +15,8 @@ #include #include -#define TS_TO_MS(ts) ((long long)((ts) / 1)) // TS convertion to milliseconds +#define LLD(val) ((int64_t)(val)) +#define TS_TO_MS(ts) (LLD((ts) / 1)) // TS convertion to milliseconds #define K4A_IMU_SAMPLE_RATE 1666 // +/- 2% @@ -234,7 +235,7 @@ TEST_P(throughput_perf, testTest) int missed_count = 0; int not_synchronized_count = 0; uint64_t last_ts = UINT64_MAX; - uint64_t fps_in_usec = 0; + int32_t fps_in_usec = 0; uint64_t last_color_ts = 0; uint64_t last_depth16_ts = 0; uint64_t last_ir16_ts = 0; @@ -245,6 +246,7 @@ TEST_P(throughput_perf, testTest) k4a_device_configuration_t config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL; thread_data thread = { 0 }; THREAD_HANDLE th1 = NULL; + int64_t max_sync_delay = 0; printf("Capturing %d frames for test: %s\n", g_capture_count, as.test_name); @@ -288,7 +290,7 @@ TEST_P(throughput_perf, testTest) printf("Auto Exposure\n"); } - fps_in_usec = 1000000 / k4a_convert_fps_to_uint(as.fps); + fps_in_usec = HZ_TO_PERIOD_US(k4a_convert_fps_to_uint(as.fps)); config.color_format = as.color_format; config.color_resolution = as.color_resolution; @@ -301,9 +303,11 @@ TEST_P(throughput_perf, testTest) if (g_depth_delay_off_color_usec == 0) { // Create delay that can be +fps to -fps - config.depth_delay_off_color_usec = (int32_t)(2 * fps_in_usec * ((uint64_t)rand()) / RAND_MAX - fps_in_usec); + config.depth_delay_off_color_usec = (int32_t)RAND_VALUE(-fps_in_usec, fps_in_usec); } + max_sync_delay = k4a_unittest_get_max_sync_delay_ms(as.fps); + printf("Config being used is:\n"); printf(" color_format:%d\n", config.color_format); printf(" color_resolution:%d\n", config.color_resolution); @@ -391,10 +395,10 @@ TEST_P(throughput_perf, testTest) ts = k4a_image_get_device_timestamp_usec(image); adjusted_max_ts = std::max(ts, adjusted_max_ts); static_assert(sizeof(ts) == 8, "this should not be wrong"); - printf(" %9lld[%6lld][%6lld]", + printf(" %9" PRId64 "[%6" PRId64 "][%6" PRId64 "]", TS_TO_MS(ts), TS_TO_MS(ts - last_color_ts), - (long long int)k4a_image_get_exposure_usec(image)); + LLD(k4a_image_get_exposure_usec(image))); // TS should increase EXPECT_GT(ts, last_color_ts); @@ -414,7 +418,7 @@ TEST_P(throughput_perf, testTest) depth = true; ts = k4a_image_get_device_timestamp_usec(image); adjusted_max_ts = std::max(ts - (uint64_t)config.depth_delay_off_color_usec, adjusted_max_ts); - printf(" | %9lld[%6lld]", TS_TO_MS(ts), TS_TO_MS(ts - last_ir16_ts)); + printf(" | %9" PRId64 "[%6" PRId64 "]", TS_TO_MS(ts), TS_TO_MS(ts - last_ir16_ts)); // TS should increase EXPECT_GT(ts, last_ir16_ts); @@ -433,7 +437,7 @@ TEST_P(throughput_perf, testTest) { ts = k4a_image_get_device_timestamp_usec(image); adjusted_max_ts = std::max(ts - (uint64_t)config.depth_delay_off_color_usec, adjusted_max_ts); - printf(" | %9lld[%6lld]", TS_TO_MS(ts), TS_TO_MS(ts - last_depth16_ts)); + printf(" | %9" PRId64 "[%6" PRId64 "]", TS_TO_MS(ts), TS_TO_MS(ts - last_depth16_ts)); // TS should increase EXPECT_GT(ts, last_depth16_ts); @@ -464,11 +468,9 @@ TEST_P(throughput_perf, testTest) printf(" | %6" PRId64, delta); delta -= config.depth_delay_off_color_usec; - if (delta < 0) - { - delta *= -1; - } - if (delta > 1000) + delta = std::abs(delta); + + if (delta > max_sync_delay) { not_synchronized_count++; } @@ -497,21 +499,21 @@ TEST_P(throughput_perf, testTest) // the color image is delayed due to perf issues. When this happens we just ignore the sample because our // time stamp logic has already moved beyond the time this sample was supposed to arrive at. } - else if ((adjusted_max_ts - last_ts) >= (fps_in_usec * 15 / 10)) + else if ((adjusted_max_ts - last_ts) >= ((unsigned)(fps_in_usec * 15 / 10))) { // Calc how many captures we didn't get. If the delta between the last two time stamps is more than 1.5 // * fps_in_usec then we count - int missed_this_period = ((int)((adjusted_max_ts - last_ts) / fps_in_usec)); + int32_t missed_this_period = (int32_t)(adjusted_max_ts - last_ts) / fps_in_usec; missed_this_period--; // We got a new time stamp to do this math, so this count has 1 too many, remove // it - if (((adjusted_max_ts - last_ts) % fps_in_usec) > fps_in_usec / 2) + if (((adjusted_max_ts - last_ts) % ((unsigned)fps_in_usec)) > ((unsigned)fps_in_usec) / 2) { missed_this_period++; } - printf("Missed %d captures before previous capture %lld %lld\n", + printf("Missed %d captures before previous capture %" PRId64 " %" PRId64 "\n", missed_this_period, - (long long)adjusted_max_ts, - (long long)last_ts); + LLD(adjusted_max_ts), + LLD(last_ts)); if (missed_this_period > capture_count) { missed_count += capture_count; @@ -634,7 +636,7 @@ TEST_P(throughput_perf, testTest) "only, %d, missing capture periods, %d, imu %%, %0.01f, not_synchronized, %d, %d\n", buffer_date_time, failed ? "FAILED" : "PASSED", - computer_name ? computer_name : "compture name not set", + computer_name ? computer_name : "computer name not set", user_name ? user_name : "user name not set", as.test_name, get_string_from_color_format(as.color_format), diff --git a/tools/k4afastcapture_streaming/k4afastcapture.cpp b/tools/k4afastcapture_streaming/k4afastcapture.cpp index e81edc1b8..56e86aec5 100644 --- a/tools/k4afastcapture_streaming/k4afastcapture.cpp +++ b/tools/k4afastcapture_streaming/k4afastcapture.cpp @@ -206,7 +206,7 @@ void K4AFastCapture::Run(int streamingLength) } k4a_capture_release(m_capture); - int32_t timeout_ms = 1000 / camera_fps; + int32_t timeout_ms = HZ_TO_PERIOD_MS(camera_fps); std::cout << "[Streaming Service] Streaming from sensors..." << std::endl; while (remainingFrames-- > 0 && m_streaming) { diff --git a/tools/k4arecorder/main.cpp b/tools/k4arecorder/main.cpp index b8d2778a7..3cd9ea7cf 100644 --- a/tools/k4arecorder/main.cpp +++ b/tools/k4arecorder/main.cpp @@ -119,7 +119,8 @@ int main(int argc, char **argv) k4a_wired_sync_mode_t wired_sync_mode = K4A_WIRED_SYNC_MODE_STANDALONE; int32_t depth_delay_off_color_usec = 0; uint32_t subordinate_delay_off_master_usec = 0; - int absoluteExposureValue = 0; + int absoluteExposureValue = defaultExposureAuto; + int gain = defaultGainAuto; char *recording_filename; CmdParser::OptionParser cmd_parser; @@ -323,16 +324,35 @@ int main(int argc, char **argv) subordinate_delay_off_master_usec = (uint32_t)delay; }); cmd_parser.RegisterOption("-e|--exposure-control", - "Set manual exposure value (-11 to 1) for the RGB camera (default: auto exposure)", + "Set manual exposure value from 2 us to 200,000us for the RGB camera (default: \n" + "auto exposure). This control also supports MFC settings of -11 to 1).", 1, [&](const std::vector &args) { int exposureValue = std::stoi(args[0]); - if (exposureValue < -11 || exposureValue > 1) + if (exposureValue >= -11 && exposureValue <= 1) { - throw std::runtime_error("Exposure value range is -11 to 1."); + absoluteExposureValue = static_cast(exp2f((float)exposureValue) * + 1000000.0f); } - absoluteExposureValue = static_cast(exp2f((float)exposureValue) * - 1000000.0f); + else if (exposureValue >= 2 && exposureValue <= 200000) + { + absoluteExposureValue = exposureValue; + } + else + { + throw std::runtime_error("Exposure value range is 2 to 5s, or -11 to 1."); + } + }); + cmd_parser.RegisterOption("-g|--gain", + "Set cameras manual gain. The valid range is 0 to 255. (default: auto)", + 1, + [&](const std::vector &args) { + int gainSetting = std::stoi(args[0]); + if (gainSetting < 0 || gainSetting > 255) + { + throw std::runtime_error("Gain value must be between 0 and 255."); + } + gain = gainSetting; }); int args_left = 0; @@ -409,5 +429,6 @@ int main(int argc, char **argv) recording_length, &device_config, recording_imu_enabled, - absoluteExposureValue); + absoluteExposureValue, + gain); } diff --git a/tools/k4arecorder/recorder.cpp b/tools/k4arecorder/recorder.cpp index a4bf1bd32..9f3bcf86a 100644 --- a/tools/k4arecorder/recorder.cpp +++ b/tools/k4arecorder/recorder.cpp @@ -50,7 +50,8 @@ int do_recording(uint8_t device_index, int recording_length, k4a_device_configuration_t *device_config, bool record_imu, - int32_t absoluteExposureValue) + int32_t absoluteExposureValue, + int32_t gain) { const uint32_t installed_devices = k4a_device_get_installed_count(); if (device_index >= installed_devices) @@ -91,14 +92,14 @@ int do_recording(uint8_t device_index, return 1; } - if (absoluteExposureValue != 0) + if (absoluteExposureValue != defaultExposureAuto) { if (K4A_FAILED(k4a_device_set_color_control(device, K4A_COLOR_CONTROL_EXPOSURE_TIME_ABSOLUTE, K4A_COLOR_CONTROL_MODE_MANUAL, absoluteExposureValue))) { - std::cerr << "Runtime error: k4a_device_set_color_control() failed " << std::endl; + std::cerr << "Runtime error: k4a_device_set_color_control() for manual exposure failed " << std::endl; } } else @@ -108,7 +109,16 @@ int do_recording(uint8_t device_index, K4A_COLOR_CONTROL_MODE_AUTO, 0))) { - std::cerr << "Runtime error: k4a_device_set_color_control() failed " << std::endl; + std::cerr << "Runtime error: k4a_device_set_color_control() for auto exposure failed " << std::endl; + } + } + + if (gain != defaultGainAuto) + { + if (K4A_FAILED( + k4a_device_set_color_control(device, K4A_COLOR_CONTROL_GAIN, K4A_COLOR_CONTROL_MODE_MANUAL, gain))) + { + std::cerr << "Runtime error: k4a_device_set_color_control() for manual gain failed " << std::endl; } } diff --git a/tools/k4arecorder/recorder.h b/tools/k4arecorder/recorder.h index b87fc31ed..c9db471d6 100644 --- a/tools/k4arecorder/recorder.h +++ b/tools/k4arecorder/recorder.h @@ -9,11 +9,15 @@ extern std::atomic_bool exiting; +static const int32_t defaultExposureAuto = -12; +static const int32_t defaultGainAuto = -1; + int do_recording(uint8_t device_index, char *recording_filename, int recording_length, k4a_device_configuration_t *device_config, bool record_imu, - int32_t absoluteExposureValue); + int32_t absoluteExposureValue, + int32_t gain); #endif /* RECORDER_H */ diff --git a/tools/k4aviewer/k4adevicedockcontrol.cpp b/tools/k4aviewer/k4adevicedockcontrol.cpp index 4ec22ac43..8fe56879e 100644 --- a/tools/k4aviewer/k4adevicedockcontrol.cpp +++ b/tools/k4aviewer/k4adevicedockcontrol.cpp @@ -588,6 +588,13 @@ K4ADockControlStatus K4ADeviceDockControl::Show() // clang-format on + if (ImGui::Button("Refresh")) + { + LoadColorSettingsCache(); + } + + ImGui::SameLine(); + if (ImGui::Button("Reset to default##RGB")) { ApplyDefaultColorSettings(); diff --git a/tools/k4aviewer/k4arecordingdockcontrol.cpp b/tools/k4aviewer/k4arecordingdockcontrol.cpp index 01f58d039..f5e21b656 100644 --- a/tools/k4aviewer/k4arecordingdockcontrol.cpp +++ b/tools/k4aviewer/k4arecordingdockcontrol.cpp @@ -92,6 +92,9 @@ K4ARecordingDockControl::K4ARecordingDockControl(std::string &&path, k4a::playba { colorFormatSS << m_recordConfiguration.color_format; colorResolutionSS << m_recordConfiguration.color_resolution; + + recording.set_color_conversion(K4A_IMAGE_FORMAT_COLOR_BGRA32); + m_recordConfiguration.color_format = K4A_IMAGE_FORMAT_COLOR_BGRA32; } else { @@ -474,12 +477,10 @@ void K4ARecordingDockControl::SetViewType(K4AWindowSet::ViewType viewType) try { k4a::calibration calibration = m_playbackThreadState.Recording.get_calibration(); - bool colorPointCloudAvailable = m_recordConfiguration.color_track_enabled && - m_recordConfiguration.color_format == K4A_IMAGE_FORMAT_COLOR_BGRA32; K4AWindowSet::StartPointCloudWindow(m_filenameLabel.c_str(), std::move(calibration), &m_playbackThreadState.CaptureDataSource, - colorPointCloudAvailable); + m_recordConfiguration.color_track_enabled); } catch (const k4a::error &e) {