mirror of
https://github.com/steve-m/hsdaoh.git
synced 2025-12-10 07:44:41 +01:00
initial commit
This commit is contained in:
commit
46af0781e7
18 changed files with 3873 additions and 0 deletions
44
.gitignore
vendored
Normal file
44
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
Makefile
|
||||||
|
Makefile.in
|
||||||
|
.deps
|
||||||
|
.libs
|
||||||
|
*.o
|
||||||
|
*.lo
|
||||||
|
*.la
|
||||||
|
*.pc
|
||||||
|
aclocal.m4
|
||||||
|
acinclude.m4
|
||||||
|
aminclude.am
|
||||||
|
m4/*.m4
|
||||||
|
autom4te.cache
|
||||||
|
config.h*
|
||||||
|
config.sub
|
||||||
|
config.log
|
||||||
|
config.status
|
||||||
|
config.guess
|
||||||
|
configure
|
||||||
|
configure~
|
||||||
|
compile
|
||||||
|
depcomp
|
||||||
|
missing
|
||||||
|
ltmain.sh
|
||||||
|
install-sh
|
||||||
|
stamp-h1
|
||||||
|
libtool
|
||||||
|
Doxyfile
|
||||||
|
|
||||||
|
.tarball-version
|
||||||
|
.version
|
||||||
|
|
||||||
|
.*.swp
|
||||||
|
|
||||||
|
doc/
|
||||||
|
|
||||||
|
src/hsdaoh_file
|
||||||
|
src/hsdaoh_tcp
|
||||||
|
src/hsdaoh_test
|
||||||
|
|
||||||
|
CMakeCache.txt
|
||||||
|
*/CMakeFiles
|
||||||
|
CMakeFiles
|
||||||
|
*.cmake
|
||||||
225
CMakeLists.txt
Normal file
225
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
# Copyright 2012-2020 Osmocom Project
|
||||||
|
#
|
||||||
|
# This file is part of hsdaoh
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Project setup
|
||||||
|
########################################################################
|
||||||
|
cmake_minimum_required(VERSION 3.7.2)
|
||||||
|
|
||||||
|
# workaround for https://gitlab.kitware.com/cmake/cmake/issues/16967
|
||||||
|
if(${CMAKE_VERSION} VERSION_LESS "3.12.0")
|
||||||
|
project(hsdaoh)
|
||||||
|
else()
|
||||||
|
project(hsdaoh C)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
#select the release build type by default to get optimization flags
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE "Release")
|
||||||
|
message(STATUS "Build type not specified: defaulting to release.")
|
||||||
|
endif(NOT CMAKE_BUILD_TYPE)
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
include(GenerateExportHeader)
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
|
||||||
|
# Set the version information here
|
||||||
|
set(VERSION_INFO_MAJOR_VERSION 0) # increment major on api compatibility changes
|
||||||
|
set(VERSION_INFO_MINOR_VERSION 1) # increment minor on feature-level changes
|
||||||
|
set(VERSION_INFO_PATCH_VERSION 0) # increment patch for bug fixes and docs
|
||||||
|
include(Version) # setup version info
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Compiler specific setup
|
||||||
|
########################################################################
|
||||||
|
if(CMAKE_COMPILER_IS_GNUCC AND NOT WIN32)
|
||||||
|
ADD_DEFINITIONS(-Wall)
|
||||||
|
ADD_DEFINITIONS(-Wextra)
|
||||||
|
ADD_DEFINITIONS(-Wno-unused-parameter)
|
||||||
|
ADD_DEFINITIONS(-Wno-unused)
|
||||||
|
ADD_DEFINITIONS(-Wsign-compare)
|
||||||
|
#http://gcc.gnu.org/wiki/Visibility
|
||||||
|
add_definitions(-fvisibility=hidden)
|
||||||
|
elseif(MSVC14 OR MSVC14)
|
||||||
|
#pthread-w32 issue, timespec is now part of time.h
|
||||||
|
ADD_DEFINITIONS(-D_TIMESPEC_DEFINED)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Find build dependencies
|
||||||
|
########################################################################
|
||||||
|
find_package(Threads)
|
||||||
|
find_package(PkgConfig)
|
||||||
|
|
||||||
|
if(PKG_CONFIG_FOUND)
|
||||||
|
pkg_check_modules(LIBUSB libusb-1.0 IMPORTED_TARGET)
|
||||||
|
if(LIBUSB_LINK_LIBRARIES)
|
||||||
|
set(LIBUSB_LIBRARIES "${LIBUSB_LINK_LIBRARIES}")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
set(LIBUSB_LIBRARIES "" CACHE STRING "manual libusb path")
|
||||||
|
set(LIBUSB_INCLUDE_DIRS "" CACHE STRING "manual libusb includepath")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(PKG_CONFIG_FOUND)
|
||||||
|
pkg_check_modules(LIBUVC libuvc IMPORTED_TARGET)
|
||||||
|
if(LIBUVC_LINK_LIBRARIES)
|
||||||
|
set(LIBUVC_LIBRARIES "${LIBUVC_LINK_LIBRARIES}")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
set(LIBUVC_LIBRARIES "" CACHE STRING "manual libuvc path")
|
||||||
|
set(LIBUVC_INCLUDE_DIRS "" CACHE STRING "manual libuvc includepath")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
set(THREADS_PTHREADS_LIBRARY "" CACHE STRING "manual pthread-win32 path")
|
||||||
|
set(THREADS_PTHREADS_INCLUDE_DIR "" CACHE STRING "manual pthread-win32 includepath")
|
||||||
|
else()
|
||||||
|
set(THREADS_PTHREADS_LIBRARY "" CACHE INTERNAL "manual pthread-win32 path")
|
||||||
|
set(THREADS_PTHREADS_INCLUDE_DIR "" CACHE INTERNAL "manual pthread-win32 includepath")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(PKG_CONFIG_FOUND AND NOT LIBUSB_FOUND)
|
||||||
|
message(FATAL_ERROR "LibUSB 1.0 required to compile hsdaoh")
|
||||||
|
endif()
|
||||||
|
if(PKG_CONFIG_FOUND AND NOT LIBUVC_FOUND)
|
||||||
|
message(FATAL_ERROR "LibUVC required to compile hsdaoh")
|
||||||
|
endif()
|
||||||
|
if(NOT THREADS_FOUND)
|
||||||
|
message(FATAL_ERROR "pthreads(-win32) required to compile hsdaoh")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Create uninstall target
|
||||||
|
########################################################################
|
||||||
|
configure_file(
|
||||||
|
${PROJECT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake
|
||||||
|
@ONLY)
|
||||||
|
|
||||||
|
add_custom_target(uninstall
|
||||||
|
${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake
|
||||||
|
)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Install udev rules
|
||||||
|
########################################################################
|
||||||
|
option(INSTALL_UDEV_RULES "Install udev rules" ON)
|
||||||
|
if (INSTALL_UDEV_RULES)
|
||||||
|
install (
|
||||||
|
FILES hsdaoh.rules
|
||||||
|
DESTINATION "/etc/udev/rules.d"
|
||||||
|
COMPONENT "udev"
|
||||||
|
)
|
||||||
|
else (INSTALL_UDEV_RULES)
|
||||||
|
message (STATUS "Udev rules not being installed, install them with -DINSTALL_UDEV_RULES=ON")
|
||||||
|
endif (INSTALL_UDEV_RULES)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Install public header files
|
||||||
|
########################################################################
|
||||||
|
install(FILES
|
||||||
|
include/hsdaoh.h
|
||||||
|
include/hsdaoh_export.h
|
||||||
|
DESTINATION include
|
||||||
|
)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Add subdirectories
|
||||||
|
########################################################################
|
||||||
|
add_subdirectory(src)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Create Pkg Config File
|
||||||
|
########################################################################
|
||||||
|
FOREACH(inc ${LIBUSB_INCLUDEDIR})
|
||||||
|
LIST(APPEND hsdaoh_PC_CFLAGS "-I${inc}")
|
||||||
|
ENDFOREACH(inc)
|
||||||
|
|
||||||
|
FOREACH(lib ${LIBUSB_LIBRARY_DIRS})
|
||||||
|
LIST(APPEND hsdaoh_PC_LIBS "-L${lib}")
|
||||||
|
ENDFOREACH(lib)
|
||||||
|
|
||||||
|
FOREACH(inc ${LIBUVC_INCLUDEDIR})
|
||||||
|
LIST(APPEND hsdaoh_PC_CFLAGS "-I${inc}")
|
||||||
|
ENDFOREACH(inc)
|
||||||
|
|
||||||
|
FOREACH(lib ${LIBUVC_LIBRARY_DIRS})
|
||||||
|
LIST(APPEND hsdaoh_PC_LIBS "-L${lib}")
|
||||||
|
ENDFOREACH(lib)
|
||||||
|
|
||||||
|
# use space-separation format for the pc file
|
||||||
|
STRING(REPLACE ";" " " hsdaoh_PC_CFLAGS "${hsdaoh_PC_CFLAGS}")
|
||||||
|
STRING(REPLACE ";" " " hsdaoh_PC_LIBS "${hsdaoh_PC_LIBS}")
|
||||||
|
|
||||||
|
# unset these vars to avoid hard-coded paths to cross environment
|
||||||
|
IF(CMAKE_CROSSCOMPILING)
|
||||||
|
UNSET(hsdaoh_PC_CFLAGS)
|
||||||
|
UNSET(hsdaoh_PC_LIBS)
|
||||||
|
ENDIF(CMAKE_CROSSCOMPILING)
|
||||||
|
|
||||||
|
set(prefix "${CMAKE_INSTALL_PREFIX}")
|
||||||
|
set(exec_prefix \${prefix})
|
||||||
|
set(includedir \${prefix}/include)
|
||||||
|
set(libdir \${exec_prefix}/lib)
|
||||||
|
|
||||||
|
CONFIGURE_FILE(
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/libhsdaoh.pc.in
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/libhsdaoh.pc
|
||||||
|
@ONLY)
|
||||||
|
|
||||||
|
INSTALL(
|
||||||
|
FILES ${CMAKE_CURRENT_BINARY_DIR}/libhsdaoh.pc
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
|
||||||
|
)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Create CMake Config File
|
||||||
|
########################################################################
|
||||||
|
write_basic_package_version_file(
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/hsdaoh/hsdaohConfigVersion.cmake"
|
||||||
|
VERSION ${VERSION}
|
||||||
|
COMPATIBILITY AnyNewerVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
configure_file(cmake/hsdaohConfig.cmake
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/hsdaoh/hsdaohConfig.cmake"
|
||||||
|
COPYONLY
|
||||||
|
)
|
||||||
|
|
||||||
|
set(ConfigPackageLocation lib/cmake/hsdaoh)
|
||||||
|
install(EXPORT HSDAOH-export
|
||||||
|
FILE hsdaohTargets.cmake
|
||||||
|
NAMESPACE hsdaoh::
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/hsdaoh/
|
||||||
|
)
|
||||||
|
install(
|
||||||
|
FILES
|
||||||
|
cmake/hsdaohConfig.cmake
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/hsdaoh/hsdaohConfigVersion.cmake"
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/hsdaoh/
|
||||||
|
COMPONENT Devel
|
||||||
|
)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Print Summary
|
||||||
|
########################################################################
|
||||||
|
MESSAGE(STATUS "Building for version: ${VERSION} / ${LIBVER}")
|
||||||
|
MESSAGE(STATUS "Using install prefix: ${CMAKE_INSTALL_PREFIX}")
|
||||||
32
cmake/cmake_uninstall.cmake.in
Normal file
32
cmake/cmake_uninstall.cmake.in
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# http://www.vtk.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F
|
||||||
|
|
||||||
|
IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
|
||||||
|
MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"")
|
||||||
|
ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
|
||||||
|
|
||||||
|
FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
|
||||||
|
STRING(REGEX REPLACE "\n" ";" files "${files}")
|
||||||
|
FOREACH(file ${files})
|
||||||
|
MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"")
|
||||||
|
IF(EXISTS "$ENV{DESTDIR}${file}")
|
||||||
|
EXEC_PROGRAM(
|
||||||
|
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
|
||||||
|
OUTPUT_VARIABLE rm_out
|
||||||
|
RETURN_VALUE rm_retval
|
||||||
|
)
|
||||||
|
IF(NOT "${rm_retval}" STREQUAL 0)
|
||||||
|
MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
|
||||||
|
ENDIF(NOT "${rm_retval}" STREQUAL 0)
|
||||||
|
ELSEIF(IS_SYMLINK "$ENV{DESTDIR}${file}")
|
||||||
|
EXEC_PROGRAM(
|
||||||
|
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
|
||||||
|
OUTPUT_VARIABLE rm_out
|
||||||
|
RETURN_VALUE rm_retval
|
||||||
|
)
|
||||||
|
IF(NOT "${rm_retval}" STREQUAL 0)
|
||||||
|
MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
|
||||||
|
ENDIF(NOT "${rm_retval}" STREQUAL 0)
|
||||||
|
ELSE(EXISTS "$ENV{DESTDIR}${file}")
|
||||||
|
MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.")
|
||||||
|
ENDIF(EXISTS "$ENV{DESTDIR}${file}")
|
||||||
|
ENDFOREACH(file)
|
||||||
151
git-version-gen
Executable file
151
git-version-gen
Executable file
|
|
@ -0,0 +1,151 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Print a version string.
|
||||||
|
scriptversion=2010-01-28.01
|
||||||
|
|
||||||
|
# Copyright (C) 2007-2010 Free Software Foundation, Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
|
||||||
|
# It may be run two ways:
|
||||||
|
# - from a git repository in which the "git describe" command below
|
||||||
|
# produces useful output (thus requiring at least one signed tag)
|
||||||
|
# - from a non-git-repo directory containing a .tarball-version file, which
|
||||||
|
# presumes this script is invoked like "./git-version-gen .tarball-version".
|
||||||
|
|
||||||
|
# In order to use intra-version strings in your project, you will need two
|
||||||
|
# separate generated version string files:
|
||||||
|
#
|
||||||
|
# .tarball-version - present only in a distribution tarball, and not in
|
||||||
|
# a checked-out repository. Created with contents that were learned at
|
||||||
|
# the last time autoconf was run, and used by git-version-gen. Must not
|
||||||
|
# be present in either $(srcdir) or $(builddir) for git-version-gen to
|
||||||
|
# give accurate answers during normal development with a checked out tree,
|
||||||
|
# but must be present in a tarball when there is no version control system.
|
||||||
|
# Therefore, it cannot be used in any dependencies. GNUmakefile has
|
||||||
|
# hooks to force a reconfigure at distribution time to get the value
|
||||||
|
# correct, without penalizing normal development with extra reconfigures.
|
||||||
|
#
|
||||||
|
# .version - present in a checked-out repository and in a distribution
|
||||||
|
# tarball. Usable in dependencies, particularly for files that don't
|
||||||
|
# want to depend on config.h but do want to track version changes.
|
||||||
|
# Delete this file prior to any autoconf run where you want to rebuild
|
||||||
|
# files to pick up a version string change; and leave it stale to
|
||||||
|
# minimize rebuild time after unrelated changes to configure sources.
|
||||||
|
#
|
||||||
|
# It is probably wise to add these two files to .gitignore, so that you
|
||||||
|
# don't accidentally commit either generated file.
|
||||||
|
#
|
||||||
|
# Use the following line in your configure.ac, so that $(VERSION) will
|
||||||
|
# automatically be up-to-date each time configure is run (and note that
|
||||||
|
# since configure.ac no longer includes a version string, Makefile rules
|
||||||
|
# should not depend on configure.ac for version updates).
|
||||||
|
#
|
||||||
|
# AC_INIT([GNU project],
|
||||||
|
# m4_esyscmd([build-aux/git-version-gen .tarball-version]),
|
||||||
|
# [bug-project@example])
|
||||||
|
#
|
||||||
|
# Then use the following lines in your Makefile.am, so that .version
|
||||||
|
# will be present for dependencies, and so that .tarball-version will
|
||||||
|
# exist in distribution tarballs.
|
||||||
|
#
|
||||||
|
# BUILT_SOURCES = $(top_srcdir)/.version
|
||||||
|
# $(top_srcdir)/.version:
|
||||||
|
# echo $(VERSION) > $@-t && mv $@-t $@
|
||||||
|
# dist-hook:
|
||||||
|
# echo $(VERSION) > $(distdir)/.tarball-version
|
||||||
|
|
||||||
|
case $# in
|
||||||
|
1) ;;
|
||||||
|
*) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
tarball_version_file=$1
|
||||||
|
nl='
|
||||||
|
'
|
||||||
|
|
||||||
|
# First see if there is a tarball-only version file.
|
||||||
|
# then try "git describe", then default.
|
||||||
|
if test -f $tarball_version_file
|
||||||
|
then
|
||||||
|
v=`cat $tarball_version_file` || exit 1
|
||||||
|
case $v in
|
||||||
|
*$nl*) v= ;; # reject multi-line output
|
||||||
|
[0-9]*) ;;
|
||||||
|
*) v= ;;
|
||||||
|
esac
|
||||||
|
test -z "$v" \
|
||||||
|
&& echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -n "$v"
|
||||||
|
then
|
||||||
|
: # use $v
|
||||||
|
elif
|
||||||
|
v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
|
||||||
|
|| git describe --abbrev=4 HEAD 2>/dev/null` \
|
||||||
|
&& case $v in
|
||||||
|
[0-9]*) ;;
|
||||||
|
v[0-9]*) ;;
|
||||||
|
*) (exit 1) ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
# Is this a new git that lists number of commits since the last
|
||||||
|
# tag or the previous older version that did not?
|
||||||
|
# Newer: v6.10-77-g0f8faeb
|
||||||
|
# Older: v6.10-g0f8faeb
|
||||||
|
case $v in
|
||||||
|
*-*-*) : git describe is okay three part flavor ;;
|
||||||
|
*-*)
|
||||||
|
: git describe is older two part flavor
|
||||||
|
# Recreate the number of commits and rewrite such that the
|
||||||
|
# result is the same as if we were using the newer version
|
||||||
|
# of git describe.
|
||||||
|
vtag=`echo "$v" | sed 's/-.*//'`
|
||||||
|
numcommits=`git rev-list "$vtag"..HEAD | wc -l`
|
||||||
|
v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Change the first '-' to a '.', so version-comparing tools work properly.
|
||||||
|
# Remove the "g" in git describe's output string, to save a byte.
|
||||||
|
v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
|
||||||
|
else
|
||||||
|
v=UNKNOWN
|
||||||
|
fi
|
||||||
|
|
||||||
|
v=`echo "$v" |sed 's/^v//'`
|
||||||
|
|
||||||
|
# Don't declare a version "dirty" merely because a time stamp has changed.
|
||||||
|
git status > /dev/null 2>&1
|
||||||
|
|
||||||
|
dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
|
||||||
|
case "$dirty" in
|
||||||
|
'') ;;
|
||||||
|
*) # Append the suffix only if there isn't one already.
|
||||||
|
case $v in
|
||||||
|
*-dirty) ;;
|
||||||
|
*) v="$v-dirty" ;;
|
||||||
|
esac ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Omit the trailing newline, so that m4_esyscmd can use the result directly.
|
||||||
|
echo "$v" | tr -d '\012'
|
||||||
|
|
||||||
|
# Local variables:
|
||||||
|
# eval: (add-hook 'write-file-hooks 'time-stamp)
|
||||||
|
# time-stamp-start: "scriptversion="
|
||||||
|
# time-stamp-format: "%:y-%02m-%02d.%02H"
|
||||||
|
# time-stamp-end: "$"
|
||||||
|
# End:
|
||||||
25
hsdaoh.rules
Normal file
25
hsdaoh.rules
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#
|
||||||
|
# Copyright 2024 Steve Markgraf <steve@steve-m.de>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
# MS2130
|
||||||
|
SUBSYSTEMS=="usb", ATTRS{idVendor}=="345f", ATTRS{idProduct}=="2130", ENV{ID_SOFTWARE_RADIO}="1", MODE="0660", GROUP="plugdev"
|
||||||
|
|
||||||
|
# MS2130 OEM
|
||||||
|
SUBSYSTEMS=="usb", ATTRS{idVendor}=="534d", ATTRS{idProduct}=="2130", ENV{ID_SOFTWARE_RADIO}="1", MODE="0660", GROUP="plugdev"
|
||||||
|
|
||||||
|
# MS2131
|
||||||
|
SUBSYSTEMS=="usb", ATTRS{idVendor}=="345f", ATTRS{idProduct}=="2131", ENV{ID_SOFTWARE_RADIO}="1", MODE="0660", GROUP="plugdev"
|
||||||
27
include/CMakeLists.txt
Normal file
27
include/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Copyright 2012 OSMOCOM Project
|
||||||
|
#
|
||||||
|
# This file is part of hsdaoh
|
||||||
|
#
|
||||||
|
# GNU Radio is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3, or (at your option)
|
||||||
|
# any later version.
|
||||||
|
#
|
||||||
|
# GNU Radio is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with GNU Radio; see the file COPYING. If not, write to
|
||||||
|
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||||
|
# Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Install public header files
|
||||||
|
########################################################################
|
||||||
|
install(FILES
|
||||||
|
hsdaoh.h
|
||||||
|
hsdaoh_export.h
|
||||||
|
DESTINATION include
|
||||||
|
)
|
||||||
137
include/hsdaoh.h
Normal file
137
include/hsdaoh.h
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
/*
|
||||||
|
* hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 by Steve Markgraf <steve@steve-m.de>
|
||||||
|
*
|
||||||
|
* based on librtlsdr:
|
||||||
|
* Copyright (C) 2012-2024 by Steve Markgraf <steve@steve-m.de>
|
||||||
|
* Copyright (C) 2012 by Dimitri Stolnikov <horiz0n@gmx.net>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0+
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __HSDAOH_H
|
||||||
|
#define __HSDAOH_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <hsdaoh_export.h>
|
||||||
|
|
||||||
|
typedef struct hsdaoh_dev hsdaoh_dev_t;
|
||||||
|
|
||||||
|
HSDAOH_API uint32_t hsdaoh_get_device_count(void);
|
||||||
|
|
||||||
|
HSDAOH_API const char* hsdaoh_get_device_name(uint32_t index);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Get USB device strings.
|
||||||
|
*
|
||||||
|
* NOTE: The string arguments must provide space for up to 256 bytes.
|
||||||
|
*
|
||||||
|
* \param index the device index
|
||||||
|
* \param manufact manufacturer name, may be NULL
|
||||||
|
* \param product product name, may be NULL
|
||||||
|
* \param serial serial number, may be NULL
|
||||||
|
* \return 0 on success
|
||||||
|
*/
|
||||||
|
HSDAOH_API int hsdaoh_get_device_usb_strings(uint32_t index,
|
||||||
|
char *manufact,
|
||||||
|
char *product,
|
||||||
|
char *serial);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Get device index by USB serial string descriptor.
|
||||||
|
*
|
||||||
|
* \param serial serial string of the device
|
||||||
|
* \return device index of first device where the name matched
|
||||||
|
* \return -1 if name is NULL
|
||||||
|
* \return -2 if no devices were found at all
|
||||||
|
* \return -3 if devices were found, but none with matching name
|
||||||
|
*/
|
||||||
|
HSDAOH_API int hsdaoh_get_index_by_serial(const char *serial);
|
||||||
|
|
||||||
|
HSDAOH_API int hsdaoh_open(hsdaoh_dev_t **dev, uint32_t index);
|
||||||
|
|
||||||
|
HSDAOH_API int hsdaoh_close(hsdaoh_dev_t *dev);
|
||||||
|
|
||||||
|
/* configuration functions */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Get USB device strings.
|
||||||
|
*
|
||||||
|
* NOTE: The string arguments must provide space for up to 256 bytes.
|
||||||
|
*
|
||||||
|
* \param dev the device handle given by hsdaoh_open()
|
||||||
|
* \param manufact manufacturer name, may be NULL
|
||||||
|
* \param product product name, may be NULL
|
||||||
|
* \param serial serial number, may be NULL
|
||||||
|
* \return 0 on success
|
||||||
|
*/
|
||||||
|
HSDAOH_API int hsdaoh_get_usb_strings(hsdaoh_dev_t *dev, char *manufact,
|
||||||
|
char *product, char *serial);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Set the sample rate for the device
|
||||||
|
*
|
||||||
|
* \param dev the device handle given by hsdaoh_open()
|
||||||
|
* \param samp_rate the sample rate to be set
|
||||||
|
* \param ext_clock if true, use the IFCLK input insteafd of internal clock source
|
||||||
|
* if a Si5351 is connected, it will be configured
|
||||||
|
* \return 0 on success, -EINVAL on invalid rate
|
||||||
|
*/
|
||||||
|
HSDAOH_API int hsdaoh_set_sample_rate(hsdaoh_dev_t *dev, uint32_t rate, bool ext_clock);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Get actual sample rate the device is configured to.
|
||||||
|
*
|
||||||
|
* \param dev the device handle given by hsdaoh_open()
|
||||||
|
* \return 0 on error, sample rate in Hz otherwise
|
||||||
|
*/
|
||||||
|
HSDAOH_API uint32_t hsdaoh_get_sample_rate(hsdaoh_dev_t *dev);
|
||||||
|
|
||||||
|
/* streaming functions */
|
||||||
|
|
||||||
|
typedef void(*hsdaoh_read_cb_t)(unsigned char *buf, uint32_t len, void *ctx);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Start streaming data from the device.
|
||||||
|
*
|
||||||
|
* \param dev the device handle given by hsdaoh_open()
|
||||||
|
* \param cb callback function to return received data
|
||||||
|
* \param ctx user specific context to pass via the callback function
|
||||||
|
* \return 0 on success
|
||||||
|
*/
|
||||||
|
HSDAOH_API int hsdaoh_start_stream(hsdaoh_dev_t *dev,
|
||||||
|
hsdaoh_read_cb_t cb,
|
||||||
|
void *ctx);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Stop streaming data from the device.
|
||||||
|
*
|
||||||
|
* \param dev the device handle given by hsdaoh_open()
|
||||||
|
* \return 0 on success
|
||||||
|
*/
|
||||||
|
HSDAOH_API int hsdaoh_stop_stream(hsdaoh_dev_t *dev);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __HSDAOH_H */
|
||||||
48
include/hsdaoh_export.h
Normal file
48
include/hsdaoh_export.h
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks
|
||||||
|
*
|
||||||
|
* Copyright (C) 2012 by Hoernchen <la@tfc-server.de>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HSDAOH_EXPORT_H
|
||||||
|
#define HSDAOH_EXPORT_H
|
||||||
|
|
||||||
|
#if defined __GNUC__
|
||||||
|
# if __GNUC__ >= 4
|
||||||
|
# define __HSDAOH_EXPORT __attribute__((visibility("default")))
|
||||||
|
# define __HSDAOH_IMPORT __attribute__((visibility("default")))
|
||||||
|
# else
|
||||||
|
# define __HSDAOH_EXPORT
|
||||||
|
# define __HSDAOH_IMPORT
|
||||||
|
# endif
|
||||||
|
#elif _MSC_VER
|
||||||
|
# define __HSDAOH_EXPORT __declspec(dllexport)
|
||||||
|
# define __HSDAOH_IMPORT __declspec(dllimport)
|
||||||
|
#else
|
||||||
|
# define __HSDAOH_EXPORT
|
||||||
|
# define __HSDAOH_IMPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef hsdaoh_STATIC
|
||||||
|
# ifdef hsdaoh_EXPORTS
|
||||||
|
# define HSDAOH_API __HSDAOH_EXPORT
|
||||||
|
# else
|
||||||
|
# define HSDAOH_API __HSDAOH_IMPORT
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
#define HSDAOH_API
|
||||||
|
#endif
|
||||||
|
#endif /* HSDAOH_EXPORT_H */
|
||||||
7
include/hsdaoh_i2c.h
Normal file
7
include/hsdaoh_i2c.h
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#ifndef __I2C_H
|
||||||
|
#define __I2C_H
|
||||||
|
|
||||||
|
int hsdaoh_i2c_write_fn(void *dev, uint8_t i2c_addr, uint8_t *buffer, uint16_t len);
|
||||||
|
int hsdaoh_i2c_read_fn(void *dev, uint8_t i2c_addr, uint8_t *buffer, uint16_t len);
|
||||||
|
|
||||||
|
#endif
|
||||||
11
libhsdaoh.pc.in
Normal file
11
libhsdaoh.pc.in
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
prefix=@prefix@
|
||||||
|
exec_prefix=@exec_prefix@
|
||||||
|
libdir=@libdir@
|
||||||
|
includedir=@includedir@
|
||||||
|
|
||||||
|
Name: hsdaoh Library
|
||||||
|
Description: C Utility Library
|
||||||
|
Version: @VERSION@
|
||||||
|
Cflags: -I${includedir}/
|
||||||
|
Libs: -L${libdir} -lhsdaoh
|
||||||
|
Libs.private: -lusb-1.0 @HSDAOH_PC_LIBS@
|
||||||
128
src/CMakeLists.txt
Normal file
128
src/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# Copyright 2012-2024 Osmocom Project
|
||||||
|
#
|
||||||
|
# This file is part of hsdaoh
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Setup shared library variant
|
||||||
|
########################################################################
|
||||||
|
add_library(hsdaoh SHARED libhsdaoh.c)
|
||||||
|
target_link_libraries(hsdaoh ${LIBUSB_LIBRARIES} ${LIBUVC_LIBRARIES} ${THREADS_PTHREADS_LIBRARY})
|
||||||
|
target_include_directories(hsdaoh PUBLIC
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
|
||||||
|
$<INSTALL_INTERFACE:include> # <prefix>/include
|
||||||
|
${LIBUSB_INCLUDE_DIRS}
|
||||||
|
${LIBUVC_INCLUDE_DIRS}
|
||||||
|
${THREADS_PTHREADS_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
set_target_properties(hsdaoh PROPERTIES DEFINE_SYMBOL "hsdaoh_EXPORTS")
|
||||||
|
set_target_properties(hsdaoh PROPERTIES OUTPUT_NAME hsdaoh)
|
||||||
|
set_target_properties(hsdaoh PROPERTIES SOVERSION ${MAJOR_VERSION})
|
||||||
|
set_target_properties(hsdaoh PROPERTIES VERSION ${LIBVER})
|
||||||
|
generate_export_header(hsdaoh)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Setup static library variant
|
||||||
|
########################################################################
|
||||||
|
add_library(hsdaoh_static STATIC libhsdaoh.c)
|
||||||
|
target_link_libraries(hsdaoh m ${LIBUSB_LIBRARIES} ${LIBUVC_LIBRARIES} ${THREADS_PTHREADS_LIBRARY})
|
||||||
|
target_include_directories(hsdaoh_static PUBLIC
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
|
||||||
|
$<INSTALL_INTERFACE:include> # <prefix>/include
|
||||||
|
${LIBUSB_INCLUDE_DIRS}
|
||||||
|
${LIBUVC_INCLUDE_DIRS}
|
||||||
|
${THREADS_PTHREADS_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
set_property(TARGET hsdaoh_static APPEND PROPERTY COMPILE_DEFINITIONS "hsdaoh_STATIC" )
|
||||||
|
if(NOT WIN32)
|
||||||
|
# Force same library filename for static and shared variants of the library
|
||||||
|
set_target_properties(hsdaoh_static PROPERTIES OUTPUT_NAME hsdaoh)
|
||||||
|
endif()
|
||||||
|
generate_export_header(hsdaoh_static)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Set up Windows DLL resource files
|
||||||
|
########################################################################
|
||||||
|
IF(MSVC)
|
||||||
|
include(${CMAKE_SOURCE_DIR}/cmake/Modules/Version.cmake)
|
||||||
|
|
||||||
|
configure_file(
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/hsdaoh.rc.in
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/hsdaoh.rc
|
||||||
|
@ONLY)
|
||||||
|
target_sources(hsdaoh PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/hsdaoh.rc)
|
||||||
|
target_sources(hsdaoh_static PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/hsdaoh.rc)
|
||||||
|
ENDIF(MSVC)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Setup libraries used in executables
|
||||||
|
########################################################################
|
||||||
|
if(WIN32)
|
||||||
|
add_library(libgetopt_static STATIC
|
||||||
|
getopt/getopt.c
|
||||||
|
)
|
||||||
|
target_link_libraries(
|
||||||
|
hsdaoh
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# Build utility
|
||||||
|
########################################################################
|
||||||
|
add_executable(hsdaoh_file hsdaoh_file.c)
|
||||||
|
add_executable(hsdaoh_tcp hsdaoh_tcp.c)
|
||||||
|
add_executable(hsdaoh_test hsdaoh_test.c)
|
||||||
|
set(INSTALL_TARGETS hsdaoh hsdaoh_static hsdaoh_file hsdaoh_tcp hsdaoh_test)
|
||||||
|
|
||||||
|
target_link_libraries(hsdaoh_file hsdaoh
|
||||||
|
# ${LIBUSB_LIBRARIES}
|
||||||
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
|
)
|
||||||
|
target_link_libraries(hsdaoh_tcp hsdaoh
|
||||||
|
# ${LIBUSB_LIBRARIES}
|
||||||
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
|
)
|
||||||
|
target_link_libraries(hsdaoh_test hsdaoh
|
||||||
|
# ${LIBUSB_LIBRARIES}
|
||||||
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
|
)
|
||||||
|
if(UNIX)
|
||||||
|
if(APPLE OR CMAKE_SYSTEM MATCHES "OpenBSD")
|
||||||
|
target_link_libraries(hsdaoh_test m)
|
||||||
|
else()
|
||||||
|
target_link_libraries(hsdaoh_test m rt)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(hsdaoh_file libgetopt_static)
|
||||||
|
target_link_libraries(hsdaoh_tcp ws2_32 libgetopt_static)
|
||||||
|
target_link_libraries(hsdaoh_test libgetopt_static)
|
||||||
|
set_property(TARGET hsdaoh_file APPEND PROPERTY COMPILE_DEFINITIONS "hsdaoh_STATIC" )
|
||||||
|
set_property(TARGET hsdaoh_tcp APPEND PROPERTY COMPILE_DEFINITIONS "hsdaoh_STATIC" )
|
||||||
|
set_property(TARGET hsdaoh_test APPEND PROPERTY COMPILE_DEFINITIONS "hsdaoh_STATIC" )
|
||||||
|
endif()
|
||||||
|
########################################################################
|
||||||
|
# Install built library files & utilities
|
||||||
|
########################################################################
|
||||||
|
install(TARGETS hsdaoh EXPORT HSDAOH-export
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} # .so/.dylib file
|
||||||
|
)
|
||||||
|
install(TARGETS hsdaoh_static EXPORT HSDAOH-export
|
||||||
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} # .so/.dylib file
|
||||||
|
)
|
||||||
|
install(TARGETS hsdaoh_file hsdaoh_tcp hsdaoh_test
|
||||||
|
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
)
|
||||||
1059
src/getopt/getopt.c
Normal file
1059
src/getopt/getopt.c
Normal file
File diff suppressed because it is too large
Load diff
180
src/getopt/getopt.h
Normal file
180
src/getopt/getopt.h
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
/* Declarations for getopt.
|
||||||
|
Copyright (C) 1989-1994, 1996-1999, 2001 Free Software Foundation, Inc.
|
||||||
|
This file is part of the GNU C Library.
|
||||||
|
|
||||||
|
The GNU C Library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
The GNU C Library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with the GNU C Library; if not, write to the Free
|
||||||
|
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||||
|
02111-1307 USA. */
|
||||||
|
|
||||||
|
#ifndef _GETOPT_H
|
||||||
|
|
||||||
|
#ifndef __need_getopt
|
||||||
|
# define _GETOPT_H 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* If __GNU_LIBRARY__ is not already defined, either we are being used
|
||||||
|
standalone, or this is the first header included in the source file.
|
||||||
|
If we are being used with glibc, we need to include <features.h>, but
|
||||||
|
that does not exist if we are standalone. So: if __GNU_LIBRARY__ is
|
||||||
|
not defined, include <ctype.h>, which will pull in <features.h> for us
|
||||||
|
if it's from glibc. (Why ctype.h? It's guaranteed to exist and it
|
||||||
|
doesn't flood the namespace with stuff the way some other headers do.) */
|
||||||
|
#if !defined __GNU_LIBRARY__
|
||||||
|
# include <ctype.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* For communication from `getopt' to the caller.
|
||||||
|
When `getopt' finds an option that takes an argument,
|
||||||
|
the argument value is returned here.
|
||||||
|
Also, when `ordering' is RETURN_IN_ORDER,
|
||||||
|
each non-option ARGV-element is returned here. */
|
||||||
|
|
||||||
|
extern char *optarg;
|
||||||
|
|
||||||
|
/* Index in ARGV of the next element to be scanned.
|
||||||
|
This is used for communication to and from the caller
|
||||||
|
and for communication between successive calls to `getopt'.
|
||||||
|
|
||||||
|
On entry to `getopt', zero means this is the first call; initialize.
|
||||||
|
|
||||||
|
When `getopt' returns -1, this is the index of the first of the
|
||||||
|
non-option elements that the caller should itself scan.
|
||||||
|
|
||||||
|
Otherwise, `optind' communicates from one call to the next
|
||||||
|
how much of ARGV has been scanned so far. */
|
||||||
|
|
||||||
|
extern int optind;
|
||||||
|
|
||||||
|
/* Callers store zero here to inhibit the error message `getopt' prints
|
||||||
|
for unrecognized options. */
|
||||||
|
|
||||||
|
extern int opterr;
|
||||||
|
|
||||||
|
/* Set to an option character which was unrecognized. */
|
||||||
|
|
||||||
|
extern int optopt;
|
||||||
|
|
||||||
|
#ifndef __need_getopt
|
||||||
|
/* Describe the long-named options requested by the application.
|
||||||
|
The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
|
||||||
|
of `struct option' terminated by an element containing a name which is
|
||||||
|
zero.
|
||||||
|
|
||||||
|
The field `has_arg' is:
|
||||||
|
no_argument (or 0) if the option does not take an argument,
|
||||||
|
required_argument (or 1) if the option requires an argument,
|
||||||
|
optional_argument (or 2) if the option takes an optional argument.
|
||||||
|
|
||||||
|
If the field `flag' is not NULL, it points to a variable that is set
|
||||||
|
to the value given in the field `val' when the option is found, but
|
||||||
|
left unchanged if the option is not found.
|
||||||
|
|
||||||
|
To have a long-named option do something other than set an `int' to
|
||||||
|
a compiled-in constant, such as set a value from `optarg', set the
|
||||||
|
option's `flag' field to zero and its `val' field to a nonzero
|
||||||
|
value (the equivalent single-letter option character, if there is
|
||||||
|
one). For long options that have a zero `flag' field, `getopt'
|
||||||
|
returns the contents of the `val' field. */
|
||||||
|
|
||||||
|
struct option
|
||||||
|
{
|
||||||
|
# if (defined __STDC__ && __STDC__) || defined __cplusplus
|
||||||
|
const char *name;
|
||||||
|
# else
|
||||||
|
char *name;
|
||||||
|
# endif
|
||||||
|
/* has_arg can't be an enum because some compilers complain about
|
||||||
|
type mismatches in all the code that assumes it is an int. */
|
||||||
|
int has_arg;
|
||||||
|
int *flag;
|
||||||
|
int val;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Names for the values of the `has_arg' field of `struct option'. */
|
||||||
|
|
||||||
|
# define no_argument 0
|
||||||
|
# define required_argument 1
|
||||||
|
# define optional_argument 2
|
||||||
|
#endif /* need getopt */
|
||||||
|
|
||||||
|
|
||||||
|
/* Get definitions and prototypes for functions to process the
|
||||||
|
arguments in ARGV (ARGC of them, minus the program name) for
|
||||||
|
options given in OPTS.
|
||||||
|
|
||||||
|
Return the option character from OPTS just read. Return -1 when
|
||||||
|
there are no more options. For unrecognized options, or options
|
||||||
|
missing arguments, `optopt' is set to the option letter, and '?' is
|
||||||
|
returned.
|
||||||
|
|
||||||
|
The OPTS string is a list of characters which are recognized option
|
||||||
|
letters, optionally followed by colons, specifying that that letter
|
||||||
|
takes an argument, to be placed in `optarg'.
|
||||||
|
|
||||||
|
If a letter in OPTS is followed by two colons, its argument is
|
||||||
|
optional. This behavior is specific to the GNU `getopt'.
|
||||||
|
|
||||||
|
The argument `--' causes premature termination of argument
|
||||||
|
scanning, explicitly telling `getopt' that there are no more
|
||||||
|
options.
|
||||||
|
|
||||||
|
If OPTS begins with `--', then non-option arguments are treated as
|
||||||
|
arguments to the option '\0'. This behavior is specific to the GNU
|
||||||
|
`getopt'. */
|
||||||
|
|
||||||
|
#if (defined __STDC__ && __STDC__) || defined __cplusplus
|
||||||
|
# ifdef __GNU_LIBRARY__
|
||||||
|
/* Many other libraries have conflicting prototypes for getopt, with
|
||||||
|
differences in the consts, in stdlib.h. To avoid compilation
|
||||||
|
errors, only prototype getopt for the GNU C library. */
|
||||||
|
extern int getopt (int __argc, char *const *__argv, const char *__shortopts);
|
||||||
|
# else /* not __GNU_LIBRARY__ */
|
||||||
|
extern int getopt ();
|
||||||
|
# endif /* __GNU_LIBRARY__ */
|
||||||
|
|
||||||
|
# ifndef __need_getopt
|
||||||
|
extern int getopt_long (int __argc, char *const *__argv, const char *__shortopts,
|
||||||
|
const struct option *__longopts, int *__longind);
|
||||||
|
extern int getopt_long_only (int __argc, char *const *__argv,
|
||||||
|
const char *__shortopts,
|
||||||
|
const struct option *__longopts, int *__longind);
|
||||||
|
|
||||||
|
/* Internal only. Users should not call this directly. */
|
||||||
|
extern int _getopt_internal (int __argc, char *const *__argv,
|
||||||
|
const char *__shortopts,
|
||||||
|
const struct option *__longopts, int *__longind,
|
||||||
|
int __long_only);
|
||||||
|
# endif
|
||||||
|
#else /* not __STDC__ */
|
||||||
|
extern int getopt ();
|
||||||
|
# ifndef __need_getopt
|
||||||
|
extern int getopt_long ();
|
||||||
|
extern int getopt_long_only ();
|
||||||
|
|
||||||
|
extern int _getopt_internal ();
|
||||||
|
# endif
|
||||||
|
#endif /* __STDC__ */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Make sure we later can get all the definitions and declarations. */
|
||||||
|
#undef __need_getopt
|
||||||
|
|
||||||
|
#endif /* getopt.h */
|
||||||
34
src/hsdaoh.rc.in
Normal file
34
src/hsdaoh.rc.in
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
VS_VERSION_INFO VERSIONINFO
|
||||||
|
FILEVERSION 0,0,0,0
|
||||||
|
PRODUCTVERSION 0,0,0,0
|
||||||
|
FILEFLAGSMASK 0x3fL
|
||||||
|
#ifndef NDEBUG
|
||||||
|
FILEFLAGS 0x0L
|
||||||
|
#else
|
||||||
|
FILEFLAGS 0x1L
|
||||||
|
#endif
|
||||||
|
FILEOS VOS__WINDOWS32
|
||||||
|
FILETYPE VFT_DLL
|
||||||
|
FILESUBTYPE VFT2_DRV_INSTALLABLE
|
||||||
|
BEGIN
|
||||||
|
BLOCK "StringFileInfo"
|
||||||
|
BEGIN
|
||||||
|
BLOCK "040904b0"
|
||||||
|
BEGIN
|
||||||
|
VALUE "FileDescription", "hsdaoh"
|
||||||
|
VALUE "FileVersion", "@VERSION@"
|
||||||
|
VALUE "InternalName", "hsdaoh.dll"
|
||||||
|
VALUE "LegalCopyright", "Licensed under GPLv2"
|
||||||
|
VALUE "OriginalFilename", "hsdaoh.dll"
|
||||||
|
VALUE "ProductName", "hsdaoh"
|
||||||
|
VALUE "ProductVersion", "@VERSION@"
|
||||||
|
END
|
||||||
|
END
|
||||||
|
BLOCK "VarFileInfo"
|
||||||
|
BEGIN
|
||||||
|
VALUE "Translation", 0x409, 1200
|
||||||
|
END
|
||||||
|
END
|
||||||
198
src/hsdaoh_file.c
Normal file
198
src/hsdaoh_file.c
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
/*
|
||||||
|
* hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 by Steve Markgraf <steve@steve-m.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0+
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <unistd.h>
|
||||||
|
#else
|
||||||
|
#include <windows.h>
|
||||||
|
#include <io.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include "getopt/getopt.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "hsdaoh.h"
|
||||||
|
|
||||||
|
#define DEFAULT_SAMPLE_RATE 30000000
|
||||||
|
|
||||||
|
static int do_exit = 0;
|
||||||
|
static uint32_t bytes_to_read = 0;
|
||||||
|
static hsdaoh_dev_t *dev = NULL;
|
||||||
|
|
||||||
|
void usage(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr,
|
||||||
|
"hsdaoh_file, HDMI data acquisition tool\n\n"
|
||||||
|
"Usage:\n"
|
||||||
|
"\t[-s samplerate (default: 30 MHz)]\n"
|
||||||
|
"\t[-d device_index (default: 0)]\n"
|
||||||
|
"\t[-p ppm_error (default: 0)]\n"
|
||||||
|
"\t[-n number of samples to read (default: 0, infinite)]\n"
|
||||||
|
"\tfilename (a '-' dumps samples to stdout)\n\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
BOOL WINAPI
|
||||||
|
sighandler(int signum)
|
||||||
|
{
|
||||||
|
if (CTRL_C_EVENT == signum) {
|
||||||
|
fprintf(stderr, "Signal caught, exiting!\n");
|
||||||
|
do_exit = 1;
|
||||||
|
hsdaoh_stop_stream(dev);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static void sighandler(int signum)
|
||||||
|
{
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
fprintf(stderr, "Signal caught, exiting!\n");
|
||||||
|
do_exit = 1;
|
||||||
|
hsdaoh_stop_stream(dev);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void hsdaoh_callback(unsigned char *buf, uint32_t len, void *ctx)
|
||||||
|
{
|
||||||
|
if (ctx) {
|
||||||
|
if (do_exit)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((bytes_to_read > 0) && (bytes_to_read < len)) {
|
||||||
|
len = bytes_to_read;
|
||||||
|
do_exit = 1;
|
||||||
|
hsdaoh_stop_stream(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fwrite(buf, 1, len, (FILE*)ctx) != len) {
|
||||||
|
fprintf(stderr, "Short write, samples lost, exiting!\n");
|
||||||
|
hsdaoh_stop_stream(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes_to_read > 0)
|
||||||
|
bytes_to_read -= len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
#ifndef _WIN32
|
||||||
|
struct sigaction sigact;
|
||||||
|
#endif
|
||||||
|
char *filename = NULL;
|
||||||
|
int n_read;
|
||||||
|
int r, opt;
|
||||||
|
int ppm_error = 0;
|
||||||
|
FILE *file;
|
||||||
|
int dev_index = 0;
|
||||||
|
uint32_t samp_rate = DEFAULT_SAMPLE_RATE;
|
||||||
|
|
||||||
|
while ((opt = getopt(argc, argv, "d:s:n:p:d:")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'd':
|
||||||
|
dev_index = (uint32_t)atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
samp_rate = (uint32_t)atof(optarg);
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
ppm_error = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
bytes_to_read = (uint32_t)atof(optarg) * 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc <= optind) {
|
||||||
|
usage();
|
||||||
|
} else {
|
||||||
|
filename = argv[optind];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev_index < 0) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = hsdaoh_open(&dev, (uint32_t)dev_index);
|
||||||
|
if (r < 0) {
|
||||||
|
fprintf(stderr, "Failed to open hsdaoh device #%d.\n", dev_index);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
#ifndef _WIN32
|
||||||
|
sigact.sa_handler = sighandler;
|
||||||
|
sigemptyset(&sigact.sa_mask);
|
||||||
|
sigact.sa_flags = 0;
|
||||||
|
sigaction(SIGINT, &sigact, NULL);
|
||||||
|
sigaction(SIGTERM, &sigact, NULL);
|
||||||
|
sigaction(SIGQUIT, &sigact, NULL);
|
||||||
|
sigaction(SIGPIPE, &sigact, NULL);
|
||||||
|
#else
|
||||||
|
SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Set the sample rate */
|
||||||
|
r = hsdaoh_set_sample_rate(dev, samp_rate, 0);
|
||||||
|
if (r < 0)
|
||||||
|
fprintf(stderr, "WARNING: Failed to set sample rate.\n");
|
||||||
|
|
||||||
|
if (strcmp(filename, "-") == 0) { /* Write samples to stdout */
|
||||||
|
file = stdout;
|
||||||
|
#ifdef _WIN32
|
||||||
|
_setmode(_fileno(stdin), _O_BINARY);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
file = fopen(filename, "wb");
|
||||||
|
if (!file) {
|
||||||
|
fprintf(stderr, "Failed to open %s\n", filename);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Reading samples...\n");
|
||||||
|
r = hsdaoh_start_stream(dev, hsdaoh_callback, (void *)file);
|
||||||
|
|
||||||
|
while (!do_exit) {
|
||||||
|
usleep(50000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_exit)
|
||||||
|
fprintf(stderr, "\nUser cancel, exiting...\n");
|
||||||
|
else
|
||||||
|
fprintf(stderr, "\nLibrary error %d, exiting...\n", r);
|
||||||
|
|
||||||
|
if (file != stdout)
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
hsdaoh_close(dev);
|
||||||
|
out:
|
||||||
|
return r >= 0 ? r : -r;
|
||||||
|
}
|
||||||
532
src/hsdaoh_tcp.c
Normal file
532
src/hsdaoh_tcp.c
Normal file
|
|
@ -0,0 +1,532 @@
|
||||||
|
/*
|
||||||
|
* hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks
|
||||||
|
*
|
||||||
|
* Copyright (C) 2012 by Steve Markgraf <steve@steve-m.de>
|
||||||
|
* Copyright (C) 2012-2013 by Hoernchen <la@tfc-server.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0+
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#else
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include "getopt/getopt.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include "hsdaoh.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
|
|
||||||
|
typedef int socklen_t;
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define closesocket close
|
||||||
|
#define SOCKADDR struct sockaddr
|
||||||
|
#define SOCKET int
|
||||||
|
#define SOCKET_ERROR -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DEFAULT_PORT_STR "1234"
|
||||||
|
#define DEFAULT_SAMPLE_RATE_HZ 30000000
|
||||||
|
#define DEFAULT_MAX_NUM_BUFFERS 500
|
||||||
|
|
||||||
|
static SOCKET s;
|
||||||
|
|
||||||
|
static pthread_t tcp_worker_thread;
|
||||||
|
static pthread_t command_thread;
|
||||||
|
static pthread_cond_t exit_cond;
|
||||||
|
static pthread_mutex_t exit_cond_lock;
|
||||||
|
|
||||||
|
static pthread_mutex_t ll_mutex;
|
||||||
|
static pthread_cond_t cond;
|
||||||
|
|
||||||
|
struct llist {
|
||||||
|
char *data;
|
||||||
|
size_t len;
|
||||||
|
struct llist *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct { /* structure size must be multiple of 2 bytes */
|
||||||
|
char magic[4];
|
||||||
|
uint32_t tuner_type;
|
||||||
|
uint32_t tuner_gain_count;
|
||||||
|
} dongle_info_t;
|
||||||
|
|
||||||
|
static hsdaoh_dev_t *dev = NULL;
|
||||||
|
|
||||||
|
static int global_numq = 0;
|
||||||
|
static struct llist *ll_buffers = 0;
|
||||||
|
static int llbuf_num = DEFAULT_MAX_NUM_BUFFERS;
|
||||||
|
|
||||||
|
static volatile int do_exit = 0;
|
||||||
|
|
||||||
|
|
||||||
|
void usage(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "hsdaoh_tcp, a sample data server for hsdaoh\n\n");
|
||||||
|
fprintf(stderr, "Usage:\t[-a listen address]\n");
|
||||||
|
fprintf(stderr, "\t[-p listen port (default: %s)]\n", DEFAULT_PORT_STR);
|
||||||
|
fprintf(stderr, "\t[-b number of buffers (default: 15, set by library)]\n");
|
||||||
|
fprintf(stderr, "\t[-n max number of linked list buffers to keep (default: %d)]\n", DEFAULT_MAX_NUM_BUFFERS);
|
||||||
|
fprintf(stderr, "\t[-d device index (default: 0)]\n");
|
||||||
|
fprintf(stderr, "\t[-P ppm_error (default: 0)]\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
int gettimeofday(struct timeval *tv, void* ignored)
|
||||||
|
{
|
||||||
|
FILETIME ft;
|
||||||
|
unsigned __int64 tmp = 0;
|
||||||
|
if (NULL != tv) {
|
||||||
|
GetSystemTimeAsFileTime(&ft);
|
||||||
|
tmp |= ft.dwHighDateTime;
|
||||||
|
tmp <<= 32;
|
||||||
|
tmp |= ft.dwLowDateTime;
|
||||||
|
tmp /= 10;
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
tmp -= 11644473600000000Ui64;
|
||||||
|
#else
|
||||||
|
tmp -= 11644473600000000ULL;
|
||||||
|
#endif
|
||||||
|
tv->tv_sec = (long)(tmp / 1000000UL);
|
||||||
|
tv->tv_usec = (long)(tmp % 1000000UL);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL WINAPI
|
||||||
|
sighandler(int signum)
|
||||||
|
{
|
||||||
|
if (CTRL_C_EVENT == signum) {
|
||||||
|
fprintf(stderr, "Signal caught, exiting!\n");
|
||||||
|
do_exit = 1;
|
||||||
|
hsdaoh_stop_stream(dev);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static void sighandler(int signum)
|
||||||
|
{
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
fprintf(stderr, "Signal caught, exiting!\n");
|
||||||
|
hsdaoh_stop_stream(dev);
|
||||||
|
do_exit = 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void hsdaoh_callback(unsigned char *buf, uint32_t len, void *ctx)
|
||||||
|
{
|
||||||
|
if(!do_exit) {
|
||||||
|
struct llist *rpt = (struct llist*)malloc(sizeof(struct llist));
|
||||||
|
rpt->data = (char*)malloc(len);
|
||||||
|
memcpy(rpt->data, buf, len);
|
||||||
|
rpt->len = len;
|
||||||
|
rpt->next = NULL;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&ll_mutex);
|
||||||
|
|
||||||
|
if (ll_buffers == NULL) {
|
||||||
|
ll_buffers = rpt;
|
||||||
|
} else {
|
||||||
|
struct llist *cur = ll_buffers;
|
||||||
|
int num_queued = 0;
|
||||||
|
|
||||||
|
while (cur->next != NULL) {
|
||||||
|
cur = cur->next;
|
||||||
|
num_queued++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(llbuf_num && llbuf_num == num_queued-2){
|
||||||
|
struct llist *curelem;
|
||||||
|
|
||||||
|
free(ll_buffers->data);
|
||||||
|
curelem = ll_buffers->next;
|
||||||
|
free(ll_buffers);
|
||||||
|
ll_buffers = curelem;
|
||||||
|
}
|
||||||
|
|
||||||
|
cur->next = rpt;
|
||||||
|
|
||||||
|
if (num_queued > global_numq)
|
||||||
|
fprintf(stderr, "ll+, now %d\n", num_queued);
|
||||||
|
else if (num_queued < global_numq)
|
||||||
|
fprintf(stderr, "ll-, now %d\n", num_queued);
|
||||||
|
|
||||||
|
global_numq = num_queued;
|
||||||
|
}
|
||||||
|
pthread_cond_signal(&cond);
|
||||||
|
pthread_mutex_unlock(&ll_mutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *tcp_worker(void *arg)
|
||||||
|
{
|
||||||
|
struct llist *curelem,*prev;
|
||||||
|
int bytesleft,bytessent, index;
|
||||||
|
struct timeval tv= {1,0};
|
||||||
|
struct timespec ts;
|
||||||
|
struct timeval tp;
|
||||||
|
fd_set writefds;
|
||||||
|
int r = 0;
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
if(do_exit)
|
||||||
|
pthread_exit(0);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&ll_mutex);
|
||||||
|
gettimeofday(&tp, NULL);
|
||||||
|
ts.tv_sec = tp.tv_sec+5;
|
||||||
|
ts.tv_nsec = tp.tv_usec * 1000;
|
||||||
|
r = pthread_cond_timedwait(&cond, &ll_mutex, &ts);
|
||||||
|
if(r == ETIMEDOUT) {
|
||||||
|
pthread_mutex_unlock(&ll_mutex);
|
||||||
|
fprintf(stderr, "worker cond timeout\n");
|
||||||
|
sighandler(0);
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
curelem = ll_buffers;
|
||||||
|
ll_buffers = 0;
|
||||||
|
pthread_mutex_unlock(&ll_mutex);
|
||||||
|
|
||||||
|
while(curelem != 0) {
|
||||||
|
bytesleft = curelem->len;
|
||||||
|
index = 0;
|
||||||
|
bytessent = 0;
|
||||||
|
while(bytesleft > 0) {
|
||||||
|
FD_ZERO(&writefds);
|
||||||
|
FD_SET(s, &writefds);
|
||||||
|
tv.tv_sec = 1;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
r = select(s+1, NULL, &writefds, NULL, &tv);
|
||||||
|
if(r) {
|
||||||
|
bytessent = send(s, &curelem->data[index], bytesleft, 0);
|
||||||
|
bytesleft -= bytessent;
|
||||||
|
index += bytessent;
|
||||||
|
}
|
||||||
|
if(bytessent == SOCKET_ERROR || do_exit) {
|
||||||
|
fprintf(stderr, "worker socket bye\n");
|
||||||
|
sighandler(0);
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev = curelem;
|
||||||
|
curelem = curelem->next;
|
||||||
|
free(prev->data);
|
||||||
|
free(prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define __attribute__(x)
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
#endif
|
||||||
|
struct command{
|
||||||
|
unsigned char cmd;
|
||||||
|
unsigned int param;
|
||||||
|
}__attribute__((packed));
|
||||||
|
#ifdef _WIN32
|
||||||
|
#pragma pack(pop)
|
||||||
|
#endif
|
||||||
|
static void *command_worker(void *arg)
|
||||||
|
{
|
||||||
|
int left, received = 0;
|
||||||
|
fd_set readfds;
|
||||||
|
struct command cmd={0, 0};
|
||||||
|
struct timeval tv= {1, 0};
|
||||||
|
int r = 0;
|
||||||
|
uint32_t tmp;
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
left=sizeof(cmd);
|
||||||
|
while(left >0) {
|
||||||
|
FD_ZERO(&readfds);
|
||||||
|
FD_SET(s, &readfds);
|
||||||
|
tv.tv_sec = 1;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
r = select(s+1, &readfds, NULL, NULL, &tv);
|
||||||
|
if(r) {
|
||||||
|
received = recv(s, (char*)&cmd+(sizeof(cmd)-left), left, 0);
|
||||||
|
left -= received;
|
||||||
|
}
|
||||||
|
if(received == SOCKET_ERROR || do_exit) {
|
||||||
|
fprintf(stderr, "comm recv bye\n");
|
||||||
|
sighandler(0);
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch(cmd.cmd) {
|
||||||
|
case 0x01:
|
||||||
|
break;
|
||||||
|
case 0x02:
|
||||||
|
fprintf(stderr, "set sample rate %d\n", ntohl(cmd.param));
|
||||||
|
hsdaoh_set_sample_rate(dev, ntohl(cmd.param), false);
|
||||||
|
break;
|
||||||
|
case 0x03:
|
||||||
|
fprintf(stderr, "set gain mode %d\n", ntohl(cmd.param));
|
||||||
|
//hsdaoh_set_tuner_gain_mode(dev, ntohl(cmd.param));
|
||||||
|
break;
|
||||||
|
case 0x04:
|
||||||
|
fprintf(stderr, "set gain %d\n", ntohl(cmd.param));
|
||||||
|
// hsdaoh_set_tuner_gain(dev, ntohl(cmd.param));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cmd.cmd = 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int r, opt, i;
|
||||||
|
char *addr = "127.0.0.1";
|
||||||
|
const char *port = DEFAULT_PORT_STR;
|
||||||
|
uint32_t frequency = 100000000, samp_rate = DEFAULT_SAMPLE_RATE_HZ;
|
||||||
|
struct sockaddr_storage local, remote;
|
||||||
|
struct addrinfo *ai;
|
||||||
|
struct addrinfo *aiHead;
|
||||||
|
struct addrinfo hints = { 0 };
|
||||||
|
char hostinfo[NI_MAXHOST];
|
||||||
|
char portinfo[NI_MAXSERV];
|
||||||
|
char remhostinfo[NI_MAXHOST];
|
||||||
|
char remportinfo[NI_MAXSERV];
|
||||||
|
int aiErr;
|
||||||
|
uint32_t buf_num = 0;
|
||||||
|
int dev_index = 0;
|
||||||
|
int ppm_error = 0;
|
||||||
|
struct llist *curelem,*prev;
|
||||||
|
pthread_attr_t attr;
|
||||||
|
void *status;
|
||||||
|
struct timeval tv = {1,0};
|
||||||
|
struct linger ling = {1,0};
|
||||||
|
SOCKET listensocket = 0;
|
||||||
|
socklen_t rlen;
|
||||||
|
fd_set readfds;
|
||||||
|
u_long blockmode = 1;
|
||||||
|
dongle_info_t dongle_info;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSADATA wsd;
|
||||||
|
i = WSAStartup(MAKEWORD(2,2), &wsd);
|
||||||
|
#else
|
||||||
|
struct sigaction sigact, sigign;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while ((opt = getopt(argc, argv, "a:p:s:v:b:n:d:e")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'd':
|
||||||
|
dev_index = (uint32_t)atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
samp_rate = (uint32_t)atof(optarg);
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
addr = strdup(optarg);
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
port = strdup(optarg);
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
buf_num = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
llbuf_num = atoi(optarg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc < optind)
|
||||||
|
usage();
|
||||||
|
|
||||||
|
if (dev_index < 0) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
hsdaoh_open(&dev, (uint32_t)dev_index);
|
||||||
|
if (NULL == dev) {
|
||||||
|
fprintf(stderr, "Failed to open hsdaoh device #%d.\n", dev_index);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
sigact.sa_handler = sighandler;
|
||||||
|
sigemptyset(&sigact.sa_mask);
|
||||||
|
sigact.sa_flags = 0;
|
||||||
|
sigign.sa_handler = SIG_IGN;
|
||||||
|
sigaction(SIGINT, &sigact, NULL);
|
||||||
|
sigaction(SIGTERM, &sigact, NULL);
|
||||||
|
sigaction(SIGQUIT, &sigact, NULL);
|
||||||
|
sigaction(SIGPIPE, &sigign, NULL);
|
||||||
|
#else
|
||||||
|
SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Set the sample rate */
|
||||||
|
r = hsdaoh_set_sample_rate(dev, samp_rate, false);
|
||||||
|
if (r < 0)
|
||||||
|
fprintf(stderr, "WARNING: Failed to set sample rate.\n");
|
||||||
|
|
||||||
|
pthread_mutex_init(&exit_cond_lock, NULL);
|
||||||
|
pthread_mutex_init(&ll_mutex, NULL);
|
||||||
|
pthread_mutex_init(&exit_cond_lock, NULL);
|
||||||
|
pthread_cond_init(&cond, NULL);
|
||||||
|
pthread_cond_init(&exit_cond, NULL);
|
||||||
|
|
||||||
|
hints.ai_flags = AI_PASSIVE; /* Server mode. */
|
||||||
|
hints.ai_family = PF_UNSPEC; /* IPv4 or IPv6. */
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
hints.ai_protocol = IPPROTO_TCP;
|
||||||
|
|
||||||
|
if ((aiErr = getaddrinfo(addr,
|
||||||
|
port,
|
||||||
|
&hints,
|
||||||
|
&aiHead )) != 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "local address %s ERROR - %s.\n",
|
||||||
|
addr, gai_strerror(aiErr));
|
||||||
|
return(-1);
|
||||||
|
}
|
||||||
|
memcpy(&local, aiHead->ai_addr, aiHead->ai_addrlen);
|
||||||
|
|
||||||
|
for (ai = aiHead; ai != NULL; ai = ai->ai_next) {
|
||||||
|
aiErr = getnameinfo((struct sockaddr *)ai->ai_addr, ai->ai_addrlen,
|
||||||
|
hostinfo, NI_MAXHOST,
|
||||||
|
portinfo, NI_MAXSERV, NI_NUMERICSERV | NI_NUMERICHOST);
|
||||||
|
if (aiErr)
|
||||||
|
fprintf( stderr, "getnameinfo ERROR - %s.\n",hostinfo);
|
||||||
|
|
||||||
|
listensocket = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
||||||
|
if (listensocket < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
r = 1;
|
||||||
|
setsockopt(listensocket, SOL_SOCKET, SO_REUSEADDR, (char *)&r, sizeof(int));
|
||||||
|
setsockopt(listensocket, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling));
|
||||||
|
|
||||||
|
if (bind(listensocket, (struct sockaddr *)&local, aiHead->ai_addrlen))
|
||||||
|
fprintf(stderr, "rtl_tcp bind error: %s", strerror(errno));
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
ioctlsocket(listensocket, FIONBIO, &blockmode);
|
||||||
|
#else
|
||||||
|
r = fcntl(listensocket, F_GETFL, 0);
|
||||||
|
r = fcntl(listensocket, F_SETFL, r | O_NONBLOCK);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
fprintf(stderr, "listening...\n");
|
||||||
|
fprintf(stderr, "Use the device argument 'rtl_tcp=%s:%s' in OsmoSDR "
|
||||||
|
"(gr-osmosdr) source\n"
|
||||||
|
"to receive samples in GRC and control "
|
||||||
|
"rtl_tcp parameters (frequency, gain, ...).\n",
|
||||||
|
hostinfo, portinfo);
|
||||||
|
listen(listensocket,1);
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
FD_ZERO(&readfds);
|
||||||
|
FD_SET(listensocket, &readfds);
|
||||||
|
tv.tv_sec = 1;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
r = select(listensocket+1, &readfds, NULL, NULL, &tv);
|
||||||
|
if(do_exit) {
|
||||||
|
goto out;
|
||||||
|
} else if(r) {
|
||||||
|
rlen = sizeof(remote);
|
||||||
|
s = accept(listensocket,(struct sockaddr *)&remote, &rlen);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setsockopt(s, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling));
|
||||||
|
|
||||||
|
getnameinfo((struct sockaddr *)&remote, rlen,
|
||||||
|
remhostinfo, NI_MAXHOST,
|
||||||
|
remportinfo, NI_MAXSERV, NI_NUMERICSERV);
|
||||||
|
fprintf(stderr, "client accepted! %s %s\n", remhostinfo, remportinfo);
|
||||||
|
|
||||||
|
memset(&dongle_info, 0, sizeof(dongle_info));
|
||||||
|
memcpy(&dongle_info.magic, "RTL0", 4);
|
||||||
|
|
||||||
|
pthread_attr_init(&attr);
|
||||||
|
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
||||||
|
r = pthread_create(&tcp_worker_thread, &attr, tcp_worker, NULL);
|
||||||
|
r = pthread_create(&command_thread, &attr, command_worker, NULL);
|
||||||
|
pthread_attr_destroy(&attr);
|
||||||
|
|
||||||
|
r = hsdaoh_start_stream(dev, hsdaoh_callback, NULL);
|
||||||
|
while (!do_exit) {
|
||||||
|
usleep(50000);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_join(tcp_worker_thread, &status);
|
||||||
|
pthread_join(command_thread, &status);
|
||||||
|
|
||||||
|
closesocket(s);
|
||||||
|
|
||||||
|
fprintf(stderr, "all threads dead..\n");
|
||||||
|
curelem = ll_buffers;
|
||||||
|
ll_buffers = 0;
|
||||||
|
|
||||||
|
while(curelem != 0) {
|
||||||
|
prev = curelem;
|
||||||
|
curelem = curelem->next;
|
||||||
|
free(prev->data);
|
||||||
|
free(prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
do_exit = 0;
|
||||||
|
global_numq = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
hsdaoh_close(dev);
|
||||||
|
closesocket(listensocket);
|
||||||
|
closesocket(s);
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSACleanup();
|
||||||
|
#endif
|
||||||
|
fprintf(stderr, "bye!\n");
|
||||||
|
return r >= 0 ? r : -r;
|
||||||
|
}
|
||||||
298
src/hsdaoh_test.c
Normal file
298
src/hsdaoh_test.c
Normal file
|
|
@ -0,0 +1,298 @@
|
||||||
|
/*
|
||||||
|
* hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks
|
||||||
|
* hsdaoh_test, test and benchmark tool
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 by Steve Markgraf <steve@steve-m.de>
|
||||||
|
*
|
||||||
|
* based on rtl_test:
|
||||||
|
* Copyright (C) 2012-2014 by Steve Markgraf <steve@steve-m.de>
|
||||||
|
* Copyright (C) 2012-2014 by Kyle Keen <keenerd@gmail.com>
|
||||||
|
* Copyright (C) 2014 by Michael Tatarinov <kukabu@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0+
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <sys/time.h>
|
||||||
|
#else
|
||||||
|
#include <time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <unistd.h>
|
||||||
|
#else
|
||||||
|
#include <windows.h>
|
||||||
|
#include "getopt/getopt.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "hsdaoh.h"
|
||||||
|
|
||||||
|
#define DEFAULT_SAMPLE_RATE 30000000
|
||||||
|
|
||||||
|
#define MHZ(x) ((x)*1000*1000)
|
||||||
|
|
||||||
|
#define PPM_DURATION 10
|
||||||
|
#define PPM_DUMP_TIME 5
|
||||||
|
|
||||||
|
struct time_generic
|
||||||
|
/* holds all the platform specific values */
|
||||||
|
{
|
||||||
|
#ifndef _WIN32
|
||||||
|
time_t tv_sec;
|
||||||
|
long tv_nsec;
|
||||||
|
#else
|
||||||
|
long tv_sec;
|
||||||
|
long tv_nsec;
|
||||||
|
int init;
|
||||||
|
LARGE_INTEGER frequency;
|
||||||
|
LARGE_INTEGER ticks;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
static int do_exit = 0;
|
||||||
|
static hsdaoh_dev_t *dev = NULL;
|
||||||
|
|
||||||
|
static uint32_t samp_rate = DEFAULT_SAMPLE_RATE;
|
||||||
|
|
||||||
|
static uint32_t total_samples = 0;
|
||||||
|
static uint32_t dropped_samples = 0;
|
||||||
|
|
||||||
|
static unsigned int ppm_duration = PPM_DURATION;
|
||||||
|
|
||||||
|
void usage(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr,
|
||||||
|
"hsdaoh_test, a test tool for hsdaoh\n\n"
|
||||||
|
"Usage:\n"
|
||||||
|
"\t[-d device_index (default: 0)]\n"
|
||||||
|
"\t[-p[seconds] enable PPM error measurement (default: 10 seconds)]\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
BOOL WINAPI
|
||||||
|
sighandler(int signum)
|
||||||
|
{
|
||||||
|
if (CTRL_C_EVENT == signum) {
|
||||||
|
fprintf(stderr, "Signal caught, exiting!\n");
|
||||||
|
do_exit = 1;
|
||||||
|
hsdaoh_stop_stream(dev);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static void sighandler(int signum)
|
||||||
|
{
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
fprintf(stderr, "Signal caught, exiting!\n");
|
||||||
|
do_exit = 1;
|
||||||
|
hsdaoh_stop_stream(dev);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
static int ppm_gettime(struct time_generic *tg)
|
||||||
|
{
|
||||||
|
int rv = ENOSYS;
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
#ifdef __unix__
|
||||||
|
rv = clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
tg->tv_sec = ts.tv_sec;
|
||||||
|
tg->tv_nsec = ts.tv_nsec;
|
||||||
|
#elif __APPLE__
|
||||||
|
struct timeval tv;
|
||||||
|
|
||||||
|
rv = gettimeofday(&tv, NULL);
|
||||||
|
tg->tv_sec = tv.tv_sec;
|
||||||
|
tg->tv_nsec = tv.tv_usec * 1000;
|
||||||
|
#endif
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
static int ppm_gettime(struct time_generic *tg)
|
||||||
|
{
|
||||||
|
int rv;
|
||||||
|
int64_t frac;
|
||||||
|
if (!tg->init) {
|
||||||
|
QueryPerformanceFrequency(&tg->frequency);
|
||||||
|
tg->init = 1;
|
||||||
|
}
|
||||||
|
rv = QueryPerformanceCounter(&tg->ticks);
|
||||||
|
tg->tv_sec = tg->ticks.QuadPart / tg->frequency.QuadPart;
|
||||||
|
frac = (int64_t)(tg->ticks.QuadPart - (tg->tv_sec * tg->frequency.QuadPart));
|
||||||
|
tg->tv_nsec = (long)(frac * 1000000000L / (int64_t)tg->frequency.QuadPart);
|
||||||
|
return !rv;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int ppm_report(uint64_t nsamples, uint64_t interval)
|
||||||
|
{
|
||||||
|
double real_rate, ppm;
|
||||||
|
|
||||||
|
real_rate = nsamples * 1e9 / interval;
|
||||||
|
ppm = 1e6 * (real_rate / (double)samp_rate - 1.);
|
||||||
|
return (int)round(ppm);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ppm_test(uint32_t len)
|
||||||
|
{
|
||||||
|
static uint64_t nsamples = 0;
|
||||||
|
static uint64_t interval = 0;
|
||||||
|
static uint64_t nsamples_total = 0;
|
||||||
|
static uint64_t interval_total = 0;
|
||||||
|
struct time_generic ppm_now;
|
||||||
|
static struct time_generic ppm_recent;
|
||||||
|
static enum {
|
||||||
|
PPM_INIT_NO,
|
||||||
|
PPM_INIT_DUMP,
|
||||||
|
PPM_INIT_RUN
|
||||||
|
} ppm_init = PPM_INIT_NO;
|
||||||
|
|
||||||
|
ppm_gettime(&ppm_now);
|
||||||
|
|
||||||
|
if (ppm_init != PPM_INIT_RUN) {
|
||||||
|
/*
|
||||||
|
* Kyle Keen wrote:
|
||||||
|
* PPM_DUMP_TIME throws out the first N seconds of data.
|
||||||
|
* The dongle's PPM is usually very bad when first starting up,
|
||||||
|
* typically incorrect by more than twice the final value.
|
||||||
|
* Discarding the first few seconds allows the value to stabilize much faster.
|
||||||
|
*/
|
||||||
|
if (ppm_init == PPM_INIT_NO) {
|
||||||
|
ppm_recent.tv_sec = ppm_now.tv_sec + PPM_DUMP_TIME;
|
||||||
|
ppm_init = PPM_INIT_DUMP;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ppm_init == PPM_INIT_DUMP && ppm_recent.tv_sec < ppm_now.tv_sec)
|
||||||
|
return;
|
||||||
|
ppm_recent = ppm_now;
|
||||||
|
ppm_init = PPM_INIT_RUN;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsamples += (uint64_t)(len);
|
||||||
|
interval = (uint64_t)(ppm_now.tv_sec - ppm_recent.tv_sec);
|
||||||
|
if (interval < ppm_duration)
|
||||||
|
return;
|
||||||
|
interval *= 1000000000UL;
|
||||||
|
interval += (int64_t)(ppm_now.tv_nsec - ppm_recent.tv_nsec);
|
||||||
|
nsamples_total += nsamples;
|
||||||
|
interval_total += interval;
|
||||||
|
printf("real sample rate: %i current PPM: %i cumulative PPM: %i\n",
|
||||||
|
(int)((1000000000UL * nsamples) / interval),
|
||||||
|
ppm_report(nsamples, interval),
|
||||||
|
ppm_report(nsamples_total, interval_total));
|
||||||
|
ppm_recent = ppm_now;
|
||||||
|
nsamples = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t last_value = 0;
|
||||||
|
|
||||||
|
static void hsdaoh_callback(unsigned char *buf, uint32_t len, void *ctx)
|
||||||
|
{
|
||||||
|
/* verify the counter value */
|
||||||
|
uint16_t *cnt = (uint16_t *)buf;
|
||||||
|
int n = len / sizeof(uint16_t);
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (cnt[i] != ((last_value+1) & 0xffff) ) {
|
||||||
|
printf("Counter error: %02x != %02x\n", cnt[i], ((last_value+1) & 0xffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
last_value = cnt[i];
|
||||||
|
}
|
||||||
|
ppm_test(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
#ifndef _WIN32
|
||||||
|
struct sigaction sigact;
|
||||||
|
#endif
|
||||||
|
int n_read, r, opt, i;
|
||||||
|
int dev_index = 0;
|
||||||
|
|
||||||
|
while ((opt = getopt(argc, argv, "d:s:p:he")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'd':
|
||||||
|
dev_index = (uint32_t)atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
samp_rate = (uint32_t)atof(optarg);
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
if (optarg)
|
||||||
|
ppm_duration = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
default:
|
||||||
|
usage();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev_index < 0) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = hsdaoh_open(&dev, (uint32_t)dev_index);
|
||||||
|
if (r < 0) {
|
||||||
|
fprintf(stderr, "Failed to open hsdaoh device #%d.\n", dev_index);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
#ifndef _WIN32
|
||||||
|
sigact.sa_handler = sighandler;
|
||||||
|
sigemptyset(&sigact.sa_mask);
|
||||||
|
sigact.sa_flags = 0;
|
||||||
|
sigaction(SIGINT, &sigact, NULL);
|
||||||
|
sigaction(SIGTERM, &sigact, NULL);
|
||||||
|
sigaction(SIGQUIT, &sigact, NULL);
|
||||||
|
sigaction(SIGPIPE, &sigact, NULL);
|
||||||
|
#else
|
||||||
|
SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
fprintf(stderr, "Reporting PPM error measurement every %u seconds...\n", ppm_duration);
|
||||||
|
fprintf(stderr, "Press ^C after a few minutes.\n");
|
||||||
|
|
||||||
|
r = hsdaoh_start_stream(dev, hsdaoh_callback, NULL);
|
||||||
|
|
||||||
|
|
||||||
|
while (!do_exit) {
|
||||||
|
usleep(50000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_exit)
|
||||||
|
fprintf(stderr, "\nUser cancel, exiting...\n");
|
||||||
|
else
|
||||||
|
fprintf(stderr, "\nLibrary error %d, exiting...\n", r);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
hsdaoh_close(dev);
|
||||||
|
|
||||||
|
return r >= 0 ? r : -r;
|
||||||
|
}
|
||||||
737
src/libhsdaoh.c
Normal file
737
src/libhsdaoh.c
Normal file
|
|
@ -0,0 +1,737 @@
|
||||||
|
/*
|
||||||
|
* hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 by Steve Markgraf <steve@steve-m.de>
|
||||||
|
*
|
||||||
|
* portions based on librtlsdr:
|
||||||
|
* Copyright (C) 2012-2014 by Steve Markgraf <steve@steve-m.de>
|
||||||
|
* Copyright (C) 2012 by Dimitri Stolnikov <horiz0n@gmx.net>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0+
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <windows.h>
|
||||||
|
#define usleep(t) Sleep((t)/1000)
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <libusb.h>
|
||||||
|
#include <libuvc/libuvc.h>
|
||||||
|
//#include <math.h>
|
||||||
|
#include <hsdaoh_i2c.h>
|
||||||
|
#include <hsdaoh.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All libusb callback functions should be marked with the LIBUSB_CALL macro
|
||||||
|
* to ensure that they are compiled with the same calling convention as libusb.
|
||||||
|
*
|
||||||
|
* If the macro isn't available in older libusb versions, we simply define it.
|
||||||
|
*/
|
||||||
|
#ifndef LIBUSB_CALL
|
||||||
|
#define LIBUSB_CALL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum hsdaoh_async_status {
|
||||||
|
HSDAOH_INACTIVE = 0,
|
||||||
|
HSDAOH_CANCELING,
|
||||||
|
HSDAOH_RUNNING
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hsdaoh_dev {
|
||||||
|
libusb_context *ctx;
|
||||||
|
struct libusb_device_handle *devh;
|
||||||
|
hsdaoh_read_cb_t cb;
|
||||||
|
void *cb_ctx;
|
||||||
|
enum hsdaoh_async_status async_status;
|
||||||
|
int async_cancel;
|
||||||
|
uint16_t vid;
|
||||||
|
uint16_t pid;
|
||||||
|
|
||||||
|
/* UVC related */
|
||||||
|
uvc_context_t *uvc_ctx;
|
||||||
|
uvc_device_t *uvc_dev;
|
||||||
|
uvc_device_handle_t *uvc_devh;
|
||||||
|
|
||||||
|
uint32_t rate; /* Hz */
|
||||||
|
|
||||||
|
int hid_interface;
|
||||||
|
|
||||||
|
int frames_since_error;
|
||||||
|
int discard_start_frames;
|
||||||
|
uint16_t last_frame_cnt;
|
||||||
|
uint16_t idle_cnt;
|
||||||
|
|
||||||
|
unsigned int width, height, fps;
|
||||||
|
|
||||||
|
/* status */
|
||||||
|
int dev_lost;
|
||||||
|
int driver_active;
|
||||||
|
unsigned int xfer_errors;
|
||||||
|
char manufact[256];
|
||||||
|
char product[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct hsdaoh_adapter {
|
||||||
|
uint16_t vid;
|
||||||
|
uint16_t pid;
|
||||||
|
const char *name;
|
||||||
|
} hsdaoh_adapter_t;
|
||||||
|
|
||||||
|
static hsdaoh_adapter_t known_devices[] = {
|
||||||
|
{ 0x345f, 0x2130, "MS2130" },
|
||||||
|
{ 0x534d, 0x2130, "MS2130 OEM?" },
|
||||||
|
{ 0x345f, 0x2131, "MS2131" },
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t magic;
|
||||||
|
uint16_t framecounter;
|
||||||
|
uint8_t pack_state;
|
||||||
|
} __attribute__((packed, aligned(1))) metadata_t;
|
||||||
|
|
||||||
|
#define CTRL_TIMEOUT 300
|
||||||
|
|
||||||
|
int hsdaoh_get_hid_feature_report(hsdaoh_dev_t *dev, unsigned char *data, size_t length)
|
||||||
|
{
|
||||||
|
int report_number = data[0];
|
||||||
|
|
||||||
|
return libusb_control_transfer(dev->devh,
|
||||||
|
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN,
|
||||||
|
0x01, (3 << 8) | report_number,
|
||||||
|
dev->hid_interface,
|
||||||
|
(unsigned char *)data, length,
|
||||||
|
CTRL_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
int hsdaoh_send_hid_feature_report(hsdaoh_dev_t *dev, const unsigned char *data, size_t length)
|
||||||
|
{
|
||||||
|
int report_number = data[0];
|
||||||
|
|
||||||
|
return libusb_control_transfer(dev->devh,
|
||||||
|
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
|
||||||
|
0x09, (3 << 8) | report_number,
|
||||||
|
dev->hid_interface,
|
||||||
|
(unsigned char *)data, length,
|
||||||
|
CTRL_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
int hsdaoh_ms_write_register(hsdaoh_dev_t *dev, uint16_t addr, uint8_t val)
|
||||||
|
{
|
||||||
|
uint8_t cmd[8] = { 0xb6, (addr >> 8), (addr & 0xff), val,
|
||||||
|
0x00, 0x00, 0x00, 0x00 };
|
||||||
|
|
||||||
|
return hsdaoh_send_hid_feature_report(dev, cmd, sizeof(cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
int hsdaoh_read_register(hsdaoh_dev_t *dev, uint16_t addr, uint8_t *val)
|
||||||
|
{
|
||||||
|
int r = 0;
|
||||||
|
uint8_t cmd[8] = { 0xb5, (addr >> 8), (addr & 0xff), 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00 };
|
||||||
|
uint8_t resp[8];
|
||||||
|
|
||||||
|
r = hsdaoh_send_hid_feature_report(dev, cmd, sizeof(cmd));
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = hsdaoh_get_hid_feature_report(dev, resp, sizeof(resp));
|
||||||
|
|
||||||
|
if (val)
|
||||||
|
*val = resp[3];
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Switch the MS2130 to a transparent mode, YUYV data received via HDMI
|
||||||
|
* will be passed through unmodified */
|
||||||
|
void hsdaoh_ms_enable_transparent_mode(hsdaoh_dev_t *dev)
|
||||||
|
{
|
||||||
|
/* Note: those registers and settings have been
|
||||||
|
* found by try and error and observing changes to the output:
|
||||||
|
* no warranty! */
|
||||||
|
|
||||||
|
/* force YCbCr 4:2:2/YUV input, default is 0x04 (RGB) */
|
||||||
|
hsdaoh_ms_write_register(dev, 0xf039, 0x06);
|
||||||
|
|
||||||
|
/* disable sharpening */
|
||||||
|
hsdaoh_ms_write_register(dev, 0xf6b0, 0x00);
|
||||||
|
|
||||||
|
/* disable luma processing -> UVC brightness/contrast control has no effect anymore */
|
||||||
|
hsdaoh_ms_write_register(dev, 0xf6be, 0x11);
|
||||||
|
|
||||||
|
/* disable chroma processing -> UVC hue/saturation control has no effect anymore */
|
||||||
|
hsdaoh_ms_write_register(dev, 0xf6bf, 0x11);
|
||||||
|
|
||||||
|
/* disable luma horizontal scaling/subpixel interpolation */
|
||||||
|
hsdaoh_ms_write_register(dev, 0xf65c, 0x10);
|
||||||
|
|
||||||
|
/* disable luma vertical scaling/subpixel interpolation */
|
||||||
|
hsdaoh_ms_write_register(dev, 0xf65e, 0x10);
|
||||||
|
|
||||||
|
/* disable chroma interpolation */
|
||||||
|
hsdaoh_ms_write_register(dev, 0xf600, 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
int hsdaoh_i2c_write_fn(void *dev, uint8_t addr, uint8_t *buf, uint16_t len)
|
||||||
|
{
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hsdaoh_i2c_read_fn(void *dev, uint8_t addr, uint8_t *buf, uint16_t len)
|
||||||
|
{
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hsdaoh_get_usb_strings(hsdaoh_dev_t *dev, char *manufact, char *product,
|
||||||
|
char *serial)
|
||||||
|
{
|
||||||
|
struct libusb_device_descriptor dd;
|
||||||
|
libusb_device *device = NULL;
|
||||||
|
const int buf_max = 256;
|
||||||
|
int r = 0;
|
||||||
|
|
||||||
|
if (!dev || !dev->devh)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
device = libusb_get_device(dev->devh);
|
||||||
|
|
||||||
|
r = libusb_get_device_descriptor(device, &dd);
|
||||||
|
if (r < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (manufact) {
|
||||||
|
memset(manufact, 0, buf_max);
|
||||||
|
libusb_get_string_descriptor_ascii(dev->devh, dd.iManufacturer,
|
||||||
|
(unsigned char *)manufact,
|
||||||
|
buf_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (product) {
|
||||||
|
memset(product, 0, buf_max);
|
||||||
|
libusb_get_string_descriptor_ascii(dev->devh, dd.iProduct,
|
||||||
|
(unsigned char *)product,
|
||||||
|
buf_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serial) {
|
||||||
|
memset(serial, 0, buf_max);
|
||||||
|
libusb_get_string_descriptor_ascii(dev->devh, dd.iSerialNumber,
|
||||||
|
(unsigned char *)serial,
|
||||||
|
buf_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hsdaoh_set_sample_rate(hsdaoh_dev_t *dev, uint32_t samp_rate, bool ext_clock)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t hsdaoh_get_sample_rate(hsdaoh_dev_t *dev)
|
||||||
|
{
|
||||||
|
if (!dev)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return dev->rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static hsdaoh_adapter_t *find_known_device(uint16_t vid, uint16_t pid)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
hsdaoh_adapter_t *device = NULL;
|
||||||
|
|
||||||
|
for (i = 0; i < sizeof(known_devices)/sizeof(hsdaoh_adapter_t); i++ ) {
|
||||||
|
if (known_devices[i].vid == vid && known_devices[i].pid == pid) {
|
||||||
|
device = &known_devices[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t hsdaoh_get_device_count(void)
|
||||||
|
{
|
||||||
|
int i,r;
|
||||||
|
libusb_context *ctx;
|
||||||
|
libusb_device **list;
|
||||||
|
uint32_t device_count = 0;
|
||||||
|
struct libusb_device_descriptor dd;
|
||||||
|
ssize_t cnt;
|
||||||
|
|
||||||
|
r = libusb_init(&ctx);
|
||||||
|
if (r < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
cnt = libusb_get_device_list(ctx, &list);
|
||||||
|
|
||||||
|
for (i = 0; i < cnt; i++) {
|
||||||
|
libusb_get_device_descriptor(list[i], &dd);
|
||||||
|
|
||||||
|
if (find_known_device(dd.idVendor, dd.idProduct))
|
||||||
|
device_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_free_device_list(list, 1);
|
||||||
|
|
||||||
|
libusb_exit(ctx);
|
||||||
|
|
||||||
|
return device_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *hsdaoh_get_device_name(uint32_t index)
|
||||||
|
{
|
||||||
|
int i,r;
|
||||||
|
libusb_context *ctx;
|
||||||
|
libusb_device **list;
|
||||||
|
struct libusb_device_descriptor dd;
|
||||||
|
hsdaoh_adapter_t *device = NULL;
|
||||||
|
uint32_t device_count = 0;
|
||||||
|
ssize_t cnt;
|
||||||
|
|
||||||
|
r = libusb_init(&ctx);
|
||||||
|
if (r < 0)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
cnt = libusb_get_device_list(ctx, &list);
|
||||||
|
|
||||||
|
for (i = 0; i < cnt; i++) {
|
||||||
|
libusb_get_device_descriptor(list[i], &dd);
|
||||||
|
|
||||||
|
device = find_known_device(dd.idVendor, dd.idProduct);
|
||||||
|
|
||||||
|
if (device) {
|
||||||
|
device_count++;
|
||||||
|
|
||||||
|
if (index == device_count - 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_free_device_list(list, 1);
|
||||||
|
|
||||||
|
libusb_exit(ctx);
|
||||||
|
|
||||||
|
if (device)
|
||||||
|
return device->name;
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This function is a workaround for the fact that libuvc does not clear
|
||||||
|
* the halted UVC endpoint. After we properly close the UVC device,
|
||||||
|
* the endpoint becomes stalled, so after a restart of our library,
|
||||||
|
* no data is received.
|
||||||
|
* Thus, we need to manually claim the interface and clear the halted EP
|
||||||
|
* before we open the device with libuvc...
|
||||||
|
*/
|
||||||
|
int hsdaoh_clear_endpoint_halt(hsdaoh_dev_t *dev)
|
||||||
|
{
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (libusb_kernel_driver_active(dev->devh, 1) == 1) {
|
||||||
|
r = libusb_detach_kernel_driver(dev->devh, 1);
|
||||||
|
if (r < 0) {
|
||||||
|
fprintf(stderr, "Failed to detach UVC Kernel driver: %d\n", r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = libusb_claim_interface(dev->devh, dev->hid_interface);
|
||||||
|
if (r < 0) {
|
||||||
|
fprintf(stderr, "usb_claim_interface hid error %d\n", r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = libusb_claim_interface(dev->devh, 1);
|
||||||
|
if (r < 0) {
|
||||||
|
fprintf(stderr, "usb_claim_interface 1 error %d\n", r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = libusb_clear_halt(dev->devh, LIBUSB_ENDPOINT_IN + 3);
|
||||||
|
if (r < 0) {
|
||||||
|
fprintf(stderr, "error clearing endpoint halt %d\n", r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return libusb_release_interface(dev->devh, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _hsdaoh_open_uvc_device(hsdaoh_dev_t *dev)
|
||||||
|
{
|
||||||
|
uvc_error_t r;
|
||||||
|
|
||||||
|
/* Initialize UVC context. In theory it should be possible to re-use our
|
||||||
|
* libusb context, but for whatever reason that does not work */
|
||||||
|
r = uvc_init(&dev->uvc_ctx, NULL);
|
||||||
|
|
||||||
|
if (r < 0) {
|
||||||
|
uvc_perror(r, "uvc_init");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Locates the first attached UVC device, stores in uvc_dev */
|
||||||
|
r = uvc_find_device(dev->uvc_ctx, &dev->uvc_dev, dev->vid, dev->pid, NULL);
|
||||||
|
|
||||||
|
if (r < 0) {
|
||||||
|
uvc_perror(r, "uvc_find_device"); /* no devices found */
|
||||||
|
} else {
|
||||||
|
|
||||||
|
/* Try to open the device: requires exclusive access */
|
||||||
|
r = uvc_open(dev->uvc_dev, &dev->uvc_devh);
|
||||||
|
|
||||||
|
if (r < 0) {
|
||||||
|
uvc_perror(r, "uvc_open"); /* unable to open device */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)r;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hsdaoh_open(hsdaoh_dev_t **out_dev, uint32_t index)
|
||||||
|
{
|
||||||
|
int r;
|
||||||
|
int i;
|
||||||
|
libusb_device **list;
|
||||||
|
hsdaoh_dev_t *dev = NULL;
|
||||||
|
libusb_device *device = NULL;
|
||||||
|
uint32_t device_count = 0;
|
||||||
|
struct libusb_device_descriptor dd;
|
||||||
|
uint8_t reg;
|
||||||
|
ssize_t cnt;
|
||||||
|
|
||||||
|
dev = malloc(sizeof(hsdaoh_dev_t));
|
||||||
|
if (NULL == dev)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
memset(dev, 0, sizeof(hsdaoh_dev_t));
|
||||||
|
|
||||||
|
r = libusb_init(&dev->ctx);
|
||||||
|
if(r < 0){
|
||||||
|
free(dev);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LIBUSB_API_VERSION >= 0x01000106
|
||||||
|
libusb_set_option(dev->ctx, LIBUSB_OPTION_LOG_LEVEL, 3);
|
||||||
|
#else
|
||||||
|
libusb_set_debug(dev->ctx, 3);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
dev->dev_lost = 1;
|
||||||
|
|
||||||
|
cnt = libusb_get_device_list(dev->ctx, &list);
|
||||||
|
|
||||||
|
for (i = 0; i < cnt; i++) {
|
||||||
|
device = list[i];
|
||||||
|
|
||||||
|
libusb_get_device_descriptor(list[i], &dd);
|
||||||
|
|
||||||
|
if (find_known_device(dd.idVendor, dd.idProduct)) {
|
||||||
|
device_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index == device_count - 1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
device = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device) {
|
||||||
|
r = -1;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->vid = dd.idVendor;
|
||||||
|
dev->pid = dd.idProduct;
|
||||||
|
|
||||||
|
r = libusb_open(device, &dev->devh);
|
||||||
|
libusb_free_device_list(list, 1);
|
||||||
|
if (r < 0) {
|
||||||
|
fprintf(stderr, "usb_open error %d\n", r);
|
||||||
|
if(r == LIBUSB_ERROR_ACCESS)
|
||||||
|
fprintf(stderr, "Please fix the device permissions, e.g. "
|
||||||
|
"by installing the udev rules file\n");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->hid_interface = 4;
|
||||||
|
if (libusb_kernel_driver_active(dev->devh, dev->hid_interface) == 1) {
|
||||||
|
|
||||||
|
r = libusb_detach_kernel_driver(dev->devh, dev->hid_interface);
|
||||||
|
if (r < 0) {
|
||||||
|
fprintf(stderr, "Failed to detach HID Kernel driver: %d\n", r);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hsdaoh_clear_endpoint_halt(dev) < 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
_hsdaoh_open_uvc_device(dev);
|
||||||
|
|
||||||
|
|
||||||
|
//dev->rate = DEFAULT_SAMPLERATE;
|
||||||
|
dev->dev_lost = 0;
|
||||||
|
|
||||||
|
found:
|
||||||
|
*out_dev = dev;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
err:
|
||||||
|
if (dev) {
|
||||||
|
if (dev->ctx)
|
||||||
|
libusb_exit(dev->ctx);
|
||||||
|
|
||||||
|
free(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hsdaoh_close(hsdaoh_dev_t *dev)
|
||||||
|
{
|
||||||
|
if (!dev)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (HSDAOH_INACTIVE != dev->async_status)
|
||||||
|
uvc_stop_streaming(dev->uvc_devh);
|
||||||
|
|
||||||
|
|
||||||
|
uvc_close(dev->uvc_devh);
|
||||||
|
uvc_unref_device(dev->uvc_dev);
|
||||||
|
uvc_exit(dev->uvc_ctx);
|
||||||
|
|
||||||
|
libusb_release_interface(dev->devh, dev->hid_interface);
|
||||||
|
|
||||||
|
//TODO: only re-attach kernel driver if it was attached previously
|
||||||
|
if (!libusb_attach_kernel_driver(dev->devh, 4))
|
||||||
|
fprintf(stderr, "Reattached kernel driver\n");
|
||||||
|
else
|
||||||
|
fprintf(stderr, "Reattaching kernel driver failed!\n");
|
||||||
|
|
||||||
|
libusb_close(dev->devh);
|
||||||
|
libusb_exit(dev->ctx);
|
||||||
|
free(dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* callback for idle/filler data */
|
||||||
|
inline int hsdaoh_check_idle_cnt(hsdaoh_dev_t *dev, uint16_t *buf, size_t length)
|
||||||
|
{
|
||||||
|
int idle_counter_errors = 0;
|
||||||
|
|
||||||
|
if (length == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < length; i++) {
|
||||||
|
if (buf[i] != ((dev->idle_cnt+1) & 0xffff))
|
||||||
|
idle_counter_errors++;
|
||||||
|
|
||||||
|
dev->idle_cnt = buf[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return idle_counter_errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extract the metadata stored in the upper 4 bits of the last word of each line */
|
||||||
|
inline void hsdaoh_extract_metadata(uint8_t *data, metadata_t *metadata, unsigned int width)
|
||||||
|
{
|
||||||
|
int j = 0;
|
||||||
|
uint8_t *meta = (uint8_t *)metadata;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < sizeof(metadata_t)*2; i += 2)
|
||||||
|
meta[j++] = (data[((i+1)*width*2) - 1] >> 4) | (data[((i+2)*width*2) - 1] & 0xf0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hsdaoh_process_frame(hsdaoh_dev_t *dev, uint8_t *data, int size)
|
||||||
|
{
|
||||||
|
uint32_t frame_payload_bytes = 0;
|
||||||
|
|
||||||
|
metadata_t meta;
|
||||||
|
hsdaoh_extract_metadata(data, &meta, dev->width);
|
||||||
|
|
||||||
|
if (meta.magic != 0xda7acab1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* drop duplicated frames */
|
||||||
|
if (meta.framecounter == dev->last_frame_cnt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// FIXME: calculate number of dropped frames
|
||||||
|
if (meta.framecounter != ((dev->last_frame_cnt + 1) & 0xffff))
|
||||||
|
printf("Missed at least one frame, fcnt %d, expected %d!\n", meta.framecounter, ((dev->last_frame_cnt + 1) & 0xffff));
|
||||||
|
|
||||||
|
dev->last_frame_cnt = meta.framecounter;
|
||||||
|
|
||||||
|
int frame_error = 0;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < dev->height; i++) {
|
||||||
|
uint8_t *line_dat = data + (dev->width * 2 * i);
|
||||||
|
|
||||||
|
/* extract number of payload words from reserved field at end of line */
|
||||||
|
uint16_t payload_len = (line_dat[dev->width*2-1] << 8) | line_dat[dev->width*2-2];
|
||||||
|
|
||||||
|
/* we only use 12 bits, the upper 4 bits are reserved for the metadata */
|
||||||
|
payload_len &= 0x0fff;
|
||||||
|
|
||||||
|
if (payload_len > dev->width-1) {
|
||||||
|
fprintf(stderr, "Invalid payload length: %d\n", payload_len);
|
||||||
|
|
||||||
|
/* discard frame */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t idle_len = (dev->width-1) - payload_len;
|
||||||
|
int r = hsdaoh_check_idle_cnt(dev, (uint16_t *)line_dat + payload_len, idle_len);
|
||||||
|
|
||||||
|
if (r)
|
||||||
|
printf("%d idle counter errors in line %d\n", r, i);
|
||||||
|
|
||||||
|
if (payload_len > 0)
|
||||||
|
memmove(data + frame_payload_bytes, line_dat, payload_len * sizeof(uint16_t));
|
||||||
|
|
||||||
|
frame_payload_bytes += payload_len*2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev->cb)
|
||||||
|
dev->cb(data, frame_payload_bytes, dev->cb_ctx);
|
||||||
|
|
||||||
|
if (frame_error) {
|
||||||
|
fprintf(stderr,"%d counter errors, %d frames since last error\n", frame_error, dev->frames_since_error);
|
||||||
|
dev->frames_since_error = 0;
|
||||||
|
} else
|
||||||
|
dev->frames_since_error++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _uvc_callback(uvc_frame_t *frame, void *ptr)
|
||||||
|
{
|
||||||
|
hsdaoh_dev_t *dev = (hsdaoh_dev_t *)ptr;
|
||||||
|
|
||||||
|
if (frame->frame_format != UVC_COLOR_FORMAT_YUYV) {
|
||||||
|
fprintf(stderr, "Error: incorrect frame format!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* discard frame if it is incomplete (can happen after streaming was started) */
|
||||||
|
if (frame->data_bytes != (dev->width * dev->height * 2))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (dev->discard_start_frames) {
|
||||||
|
dev->discard_start_frames--;
|
||||||
|
|
||||||
|
if (dev->discard_start_frames == 5)
|
||||||
|
hsdaoh_ms_enable_transparent_mode(dev);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hsdaoh_process_frame(dev, (uint8_t *)frame->data, frame->data_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
int hsdaoh_start_stream(hsdaoh_dev_t *dev, hsdaoh_read_cb_t cb, void *ctx)
|
||||||
|
{
|
||||||
|
int r = 0;
|
||||||
|
|
||||||
|
if (!dev)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (HSDAOH_INACTIVE != dev->async_status)
|
||||||
|
return -2;
|
||||||
|
|
||||||
|
dev->async_status = HSDAOH_RUNNING;
|
||||||
|
dev->async_cancel = 0;
|
||||||
|
|
||||||
|
dev->cb = cb;
|
||||||
|
dev->cb_ctx = ctx;
|
||||||
|
|
||||||
|
uvc_error_t res;
|
||||||
|
uvc_stream_ctrl_t ctrl;
|
||||||
|
|
||||||
|
const uvc_format_desc_t *format_desc = uvc_get_format_descs(dev->uvc_devh);
|
||||||
|
const uvc_frame_desc_t *frame_desc = format_desc->frame_descs;
|
||||||
|
enum uvc_frame_format frame_format = UVC_FRAME_FORMAT_YUYV;
|
||||||
|
|
||||||
|
dev->width = 1920;
|
||||||
|
dev->height = 1080;
|
||||||
|
dev->fps = 60;
|
||||||
|
|
||||||
|
res = uvc_get_stream_ctrl_format_size(dev->uvc_devh, &ctrl, frame_format, dev->width, dev->height, dev->fps);
|
||||||
|
|
||||||
|
if (res < 0) {
|
||||||
|
uvc_perror(res, "get_mode"); /* device doesn't provide a matching stream */
|
||||||
|
} else {
|
||||||
|
/* start the UVC stream */
|
||||||
|
dev->discard_start_frames = 60;
|
||||||
|
res = uvc_start_streaming(dev->uvc_devh, &ctrl, _uvc_callback, (void *)dev, 0);
|
||||||
|
|
||||||
|
if (res < 0) {
|
||||||
|
uvc_perror(res, "start_streaming"); /* unable to start stream */
|
||||||
|
} else {
|
||||||
|
puts("Streaming...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hsdaoh_stop_stream(hsdaoh_dev_t *dev)
|
||||||
|
{
|
||||||
|
if (!dev)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* if streaming, try to cancel gracefully */
|
||||||
|
if (HSDAOH_RUNNING == dev->async_status) {
|
||||||
|
dev->async_status = HSDAOH_CANCELING;
|
||||||
|
dev->async_cancel = 1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* End the stream. Blocks until last callback is serviced */
|
||||||
|
// uvc_stop_streaming(dev->uvc_devh);
|
||||||
|
// puts("Done streaming.");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if called while in pending state, change the state forcefully */
|
||||||
|
#if 0
|
||||||
|
if (HSDAOH_INACTIVE != dev->async_status) {
|
||||||
|
dev->async_status = HSDAOH_INACTIVE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue