initial commit

This commit is contained in:
Steve Markgraf 2024-05-05 15:17:01 +02:00
commit 46af0781e7
18 changed files with 3873 additions and 0 deletions

44
.gitignore vendored Normal file
View 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
View 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}")

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

180
src/getopt/getopt.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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;
}