Compare commits

...

6 commits

Author SHA1 Message Date
Steve Markgraf
dccfb3a5f2 lib: add support for dual 12 bit FPGA source 2025-08-23 21:41:32 +02:00
Steve Markgraf
ff55b5acc6 add internal buffering and dedicated output thread 2025-08-23 21:38:04 +02:00
Steve Markgraf
ae0d94eba6 lib: add IQ converter 2025-08-23 21:37:58 +02:00
Steve Markgraf
de7c63b090 add 8 bit IQ support 2025-08-23 21:37:53 +02:00
Steve Markgraf
768c4f5900 Add support for hsdaohSDR 2025-08-23 21:37:46 +02:00
Steve Markgraf
c478ee1edc Support for format conversion and multiple streams 2025-08-23 21:37:27 +02:00
12 changed files with 1429 additions and 122 deletions

134
include/filters.h Normal file
View file

@ -0,0 +1,134 @@
/*
Copyright (C) 2014, Youssef Touil <youssef@airspy.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef FILTERS_H
#define FILTERS_H
#include <stdint.h>
#define HB_KERNEL_FLOAT_LEN 47
const float HB_KERNEL_FLOAT[HB_KERNEL_FLOAT_LEN] =
{
-0.000998606272947510,
0.000000000000000000,
0.001695637278417295,
0.000000000000000000,
-0.003054430179754289,
0.000000000000000000,
0.005055504379767936,
0.000000000000000000,
-0.007901319195893647,
0.000000000000000000,
0.011873357051047719,
0.000000000000000000,
-0.017411159379930066,
0.000000000000000000,
0.025304817427568772,
0.000000000000000000,
-0.037225225204559217,
0.000000000000000000,
0.057533286997004301,
0.000000000000000000,
-0.102327462004259350,
0.000000000000000000,
0.317034472508947400,
0.500000000000000000,
0.317034472508947400,
0.000000000000000000,
-0.102327462004259350,
0.000000000000000000,
0.057533286997004301,
0.000000000000000000,
-0.037225225204559217,
0.000000000000000000,
0.025304817427568772,
0.000000000000000000,
-0.017411159379930066,
0.000000000000000000,
0.011873357051047719,
0.000000000000000000,
-0.007901319195893647,
0.000000000000000000,
0.005055504379767936,
0.000000000000000000,
-0.003054430179754289,
0.000000000000000000,
0.001695637278417295,
0.000000000000000000,
-0.000998606272947510
};
#define HB_KERNEL_INT16_LEN 47
const int16_t HB_KERNEL_INT16[HB_KERNEL_INT16_LEN] =
{
-33,
0,
56,
0,
-100,
0,
166,
0,
-259,
0,
389,
0,
-571,
0,
829,
0,
-1220,
0,
1885,
0,
-3353,
0,
10389,
16384,
10389,
0,
-3353,
0,
1885,
0,
-1220,
0,
829,
0,
-571,
0,
389,
0,
-259,
0,
166,
0,
-100,
0,
56,
0,
-33
};
#endif // FILTERS_H

10
include/format_convert.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef __FORMAT_CONVERT_H
#define __FORMAT_CONVERT_H
void hsdaoh_unpack_pio_12bit(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info);
void hsdaoh_unpack_pio_12bit_dual(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info);
void hsdaoh_unpack_pio_10bit_iq(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info);
void hsdaoh_unpack_pio_8bit_iq(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info);
void hsdaoh_unpack_pio_pcm1802_audio(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info);
void hsdaoh_unpack_fpga_12bit_dual(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info);
#endif

View file

@ -34,16 +34,25 @@ extern "C" {
#include <stdbool.h>
#include <hsdaoh_export.h>
#define HSDAOH_MAX_BUF_SIZE (1920 * 1080 * 2)
typedef struct hsdaoh_data_info {
void *ctx;
unsigned char *buf;
uint32_t len; /* buffer length */
size_t len; /* buffer length */
uint16_t stream_id;
bool device_error; /* device error happened, terminate application */
} hsdaoh_data_info_t;
typedef struct hsdaoh_dev hsdaoh_dev_t;
typedef enum
{
OUT_FMT_RAW,
OUT_FMT_UNPACKED,
OUT_FMT_FLOAT
} hsdaoh_output_format_t;
HSDAOH_API uint32_t hsdaoh_get_device_count(void);
HSDAOH_API const char* hsdaoh_get_device_name(uint32_t index);
@ -105,11 +114,14 @@ typedef void(*hsdaoh_read_cb_t)(hsdaoh_data_info_t *data_info);
* \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
* \param buf_num optional buffer count
* set to 0 for default buffer count (16)
* \return 0 on success
*/
HSDAOH_API int hsdaoh_start_stream(hsdaoh_dev_t *dev,
hsdaoh_read_cb_t cb,
void *ctx);
void *ctx,
unsigned int buf_num);
/*!
* Stop streaming data from the device.

134
include/hsdaoh_private.h Normal file
View file

@ -0,0 +1,134 @@
#ifndef __HSDAOH_PRIVATE_H
#define __HSDAOH_PRIVATE_H
enum hsdaoh_async_status {
HSDAOH_INACTIVE = 0,
HSDAOH_CANCELING,
HSDAOH_RUNNING
};
struct llist {
uint8_t *data;
size_t len;
uint16_t sid;
uint16_t format;
struct llist *next;
};
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;
int hid_interface;
uint8_t edid_seq_cnt;
int frames_since_error;
int discard_start_frames;
unsigned int in_order_cnt;
uint16_t last_frame_cnt;
uint16_t last_crc[2];
uint16_t idle_cnt;
bool stream_synced;
unsigned int width, height, fps;
bool output_float;
iqconverter_float_t *cnv_f;
/* status */
int dev_lost;
bool driver_active;
unsigned int xfer_errors;
char manufact[256];
char product[256];
/* buffering */
pthread_t hsdaoh_output_worker_thread;
pthread_mutex_t ll_mutex;
pthread_cond_t cond;
unsigned int highest_numq;
unsigned int global_numq;
struct llist *ll_buffers;
unsigned int llbuf_num;
};
enum
{
RAW_8BIT,
RAW_16BIT,
RAW_24BIT,
RAW_32BIT,
RAW_64BIT,
PIO_1BIT,
PIO_2BIT,
PIO_3BIT,
PIO_4BIT,
PIO_5BIT,
PIO_6BIT,
PIO_7BIT,
PIO_8BIT,
PIO_8BIT_DUAL,
PIO_8BIT_IQ,
PIO_9BIT,
PIO_10BIT,
PIO_10BIT_DUAL,
PIO_10BIT_IQ,
PIO_11BIT,
PIO_12BIT,
PIO_12BIT_DUAL,
PIO_12BIT_IQ,
PIO_13BIT,
PIO_14BIT,
PIO_14BIT_DUAL,
PIO_14BIT_IQ,
PIO_15BIT,
PIO_16BIT,
PIO_16BIT_DUAL,
PIO_16BIT_IQ,
PIO_17BIT,
PIO_18BIT,
PIO_19BIT,
PIO_20BIT,
PIO_24BIT,
PIO_24BIT_IQ,
PIO_28BIT,
PIO_32BIT,
PIO_32BIT_IQ,
PIO_PCM1802_AUDIO,
// Placeholder for internal ADC data from pico
FPGA_1BIT = 256,
FPGA_2BIT,
FPGA_3BIT,
FPGA_4BIT,
FPGA_5BIT,
FPGA_6BIT,
FPGA_7BIT,
FPGA_8BIT,
FPGA_8BIT_DUAL,
FPGA_8BIT_DDR,
FPGA_8BIT_IQ,
FPGA_9BIT,
FPGA_10BIT,
FPGA_10BIT_DUAL,
FPGA_10BIT_DDR,
FPGA_10BIT_IQ,
FPGA_11BIT,
FPGA_12BIT,
FPGA_12BIT_DUAL,
FPGA_12BIT_DDR,
FPGA_12BIT_IQ,
};
#endif

View file

@ -0,0 +1,47 @@
/*
Copyright (C) 2014, Youssef Touil <youssef@airspy.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef IQCONVERTER_FLOAT_H
#define IQCONVERTER_FLOAT_H
#include <stdint.h>
#define IQCONVERTER_NZEROS 2
#define IQCONVERTER_NPOLES 2
typedef struct {
float avg;
float hbc;
int len;
int fir_index;
int delay_index;
float *fir_kernel;
float *fir_queue;
float *delay_line;
} iqconverter_float_t;
iqconverter_float_t *iqconverter_float_create(const float *hb_kernel, int len);
void iqconverter_float_free(iqconverter_float_t *cnv);
void iqconverter_float_reset(iqconverter_float_t *cnv);
void iqconverter_float_process(iqconverter_float_t *cnv, float *samples, int len);
#endif // IQCONVERTER_FLOAT_H

View file

@ -18,7 +18,7 @@
########################################################################
# Setup shared library variant
########################################################################
add_library(hsdaoh SHARED libhsdaoh.c)
add_library(hsdaoh SHARED libhsdaoh.c format_convert.c iqconverter_float.c)
target_link_libraries(hsdaoh ${LIBUSB_LIBRARIES} ${LIBUVC_LIBRARIES} ${THREADS_PTHREADS_LIBRARY})
target_include_directories(hsdaoh PUBLIC
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
@ -36,7 +36,7 @@ generate_export_header(hsdaoh)
########################################################################
# Setup static library variant
########################################################################
add_library(hsdaoh_static STATIC libhsdaoh.c)
add_library(hsdaoh_static STATIC libhsdaoh.c format_convert.c iqconverter_float.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>
@ -46,6 +46,7 @@ target_include_directories(hsdaoh_static PUBLIC
${THREADS_PTHREADS_INCLUDE_DIR}
)
set_property(TARGET hsdaoh_static APPEND PROPERTY COMPILE_DEFINITIONS "hsdaoh_STATIC" )
set_property(TARGET hsdaoh_static PROPERTY POSITION_INDEPENDENT_CODE ON)
if(NOT WIN32)
# Force same library filename for static and shared variants of the library
set_target_properties(hsdaoh_static PROPERTIES OUTPUT_NAME hsdaoh)

269
src/format_convert.c Normal file
View file

@ -0,0 +1,269 @@
/*
* hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks
* Format conversion routines
*
* Copyright (C) 2024-2025 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 <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <libusb.h>
#include <libuvc/libuvc.h>
#include <iqconverter_float.h>
#include <hsdaoh.h>
#include <hsdaoh_private.h>
#include <format_convert.h>
static inline void hsdaoh_16bit_to_float(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info, uint16_t *buf, size_t length, float scale, bool duplicate)
{
unsigned int i, j = 0;
float *floats = malloc(sizeof(float) * dev->width * dev->height * 2 * 2);
if (!floats)
return;
for (unsigned int i = 0; i < length; i++) {
float sample_i = buf[i];
floats[j++] = (sample_i - scale) * (1/scale);
if (duplicate)
floats[j++] = (sample_i - scale) * (1/scale);
}
// iqconverter_float_process(dev->cnv_f, (float *) floats, j);
data_info->buf = (uint8_t *)floats;
data_info->len = j * sizeof(float);
dev->cb(data_info);
free(floats);
}
// We receive three 16-bit words containing four 12-bit samples (sample A - D)
// First word: A03 A02 A01 A00 B11 B10 B09 B08 B07 B06 B05 B04 B03 B02 B01 B00
// Second word: A07 A06 A05 A04 C11 C10 C09 C08 C07 C06 C05 C04 C03 C02 C01 C00
// Third word: A11 A10 A09 A08 D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00
void hsdaoh_unpack_pio_12bit(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info)
{
uint16_t *in = (uint16_t *)data_info->buf;
size_t inlen = data_info->len / sizeof(uint16_t);
uint16_t *out = malloc(sizeof(uint16_t) * dev->width * dev->height * 2);
unsigned int j = 0;
for (unsigned int i = 0; i < inlen; i += 3) {
out[j++] = (in[i+2] & 0xf000) >> 4 | (in[i+1] & 0xf000) >> 8 | (in[i] >> 12);
out[j++] = in[i ] & 0x0fff;
out[j++] = in[i+1] & 0x0fff;
out[j++] = in[i+2] & 0x0fff;
}
if (dev->output_float) {
hsdaoh_16bit_to_float(dev, data_info, out, j, 2047.5, true);
} else {
data_info->buf = (uint8_t *)out;
data_info->len = j * sizeof(uint16_t);
dev->cb(data_info);
}
free(out);
}
// We receive three 32-bit words containing four 24-bit samples (sample A - D)
// First word: A07 A06 A05 A04 A03 A02 A01 A00 B23 B22 B21 B20 B19 B18 B17 B16 B15 B14 B13 B12 B11 B10 B09 B08 B07 B06 B05 B04 B03 B02 B01 B00
// Second word: A15 A14 A13 A12 A11 A10 A09 A08 C23 C22 C21 C20 C19 C18 C17 C16 C15 C14 C13 C12 C11 C10 C09 C08 C07 C06 C05 C04 C03 C02 C01 C00
// Third word: A23 A22 A21 A20 A19 A18 A17 A16 D23 D22 D21 D20 D19 D18 D17 D16 D15 D14 D13 D12 D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00
void hsdaoh_unpack_pio_12bit_dual(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info)
{
uint32_t *in = (uint32_t *)data_info->buf;
size_t inlen = data_info->len / sizeof(uint32_t);
uint32_t *out = malloc(sizeof(uint32_t) * dev->width * dev->height * 2);
uint16_t *out16_1 = malloc(sizeof(uint16_t) * dev->width * dev->height * 2);
uint16_t *out16_2 = malloc(sizeof(uint16_t) * dev->width * dev->height * 2);
unsigned int i = 0, j = 0;
for (i = 0; i < inlen; i += 3) {
out[j++] = (in[i+2] & 0xff000000) >> 8 | (in[i+1] & 0xff000000) >> 16 | (in[i] >> 24);
out[j++] = in[i ] & 0x00ffffff;
out[j++] = in[i+1] & 0x00ffffff;
out[j++] = in[i+2] & 0x00ffffff;
}
for (i = 0; i < j; i++) {
out16_1[i] = (out[i] >> 12) & 0x0fff;
out16_2[i] = out[i] & 0x0fff;
}
if (dev->output_float) {
hsdaoh_16bit_to_float(dev, data_info, out16_1, i, 2047.5, true);
} else {
data_info->buf = (uint8_t *)out16_1;
data_info->len = i * sizeof(uint16_t);
dev->cb(data_info);
}
data_info->stream_id += 1;
if (dev->output_float) {
hsdaoh_16bit_to_float(dev, data_info, out16_2, i, 2047.5, true);
} else {
data_info->buf = (uint8_t *)out16_2;
data_info->len = i * sizeof(uint16_t);
dev->cb(data_info);
}
free(out);
free(out16_1);
free(out16_2);
}
// We receive the samples as 16 bit words containing 8 bit I, 8 bit Q
void hsdaoh_unpack_pio_8bit_iq(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info)
{
uint16_t *in = (uint16_t *)data_info->buf;
size_t inlen = data_info->len / sizeof(uint16_t);
uint16_t *iq_samps = malloc(sizeof(uint16_t) * dev->width * dev->height * 2 * 2);
unsigned int i = 0;
unsigned int out_samps = 0;
for (i = 0; i < inlen; i++) {
iq_samps[out_samps++] = in[i] & 0xff;
iq_samps[out_samps++] = in[i] >> 8;
}
if (dev->output_float) {
hsdaoh_16bit_to_float(dev, data_info, iq_samps, out_samps, 127.5, false);
} else {
data_info->buf = (uint8_t *)in;
data_info->len = inlen;
dev->cb(data_info);
}
free(iq_samps);
}
// We receive five 16-bit words containing four 20-bit samples (sample A - D)
// First word: A15 A14 A13 A12 A11 A10 A09 A08 A07 A06 A05 A04 A03 A02 A01 A00
// Second word: A19 A18 A17 A16 B11 B10 B09 B08 B07 B06 B05 B04 B03 B02 B01 B00
// Third word: B19 B18 B17 B16 B15 B14 B13 B12 C07 C06 C05 C04 C03 C02 C01 C00
// Fourth word: C19 C18 C17 C16 C15 C14 C13 C12 C11 C10 C09 C08 D03 D02 D01 D00
// Fifth word: D19 D18 D17 D16 D15 D14 D13 D12 D11 D10 D09 D08 D07 D06 D05 D04
void hsdaoh_unpack_pio_10bit_iq(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info)
{
uint16_t *in = (uint16_t *)data_info->buf;
size_t inlen = data_info->len / sizeof(uint16_t);
uint32_t *out = malloc(sizeof(uint32_t) * dev->width * dev->height * 2);
uint16_t *iq_samps = malloc(sizeof(uint16_t) * dev->width * dev->height * 2 * 2);
unsigned int i = 0;
unsigned int j = 0;
for (i = 0; i < inlen; i += 5) {
out[j++] = ((in[i+1] & 0xf000) << 4) | in[i]; // Sample A
out[j++] = ((in[i+2] & 0xff00) << 4) | (in[i+1] & 0x0fff); // Sample B
out[j++] = ((in[i+3] & 0xfff0) << 4) | (in[i+2] & 0x00ff); // Sample C
out[j++] = (in[i+4] << 4) | (in[i+3] & 0x000f); // Sample D
}
unsigned int out_samps = 0;
// convert 20 bit words to interleaved 10 bit IQ samples
// the LSBs are stored at the top 4 bits of the 20 bit word, to easily allow
// switching to an 8 bit IQ mode with another PIO program
for (i = 0; i < j; i++) {
iq_samps[out_samps++] = ((out[i] & 0x000000ff) << 2) | ((out[i] >> 16) & 3);
iq_samps[out_samps++] = ((out[i] & 0x0000ff00) >> 6) | ((out[i] >> 18) & 3);
}
if (dev->output_float) {
hsdaoh_16bit_to_float(dev, data_info, iq_samps, out_samps, 511.5, false);
} else {
data_info->buf = (uint8_t *)iq_samps;
data_info->len = out_samps * sizeof(uint16_t);
dev->cb(data_info);
}
free(out);
free(iq_samps);
}
void hsdaoh_unpack_pio_pcm1802_audio(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info)
{
uint32_t *in = (uint32_t *)data_info->buf;
size_t inlen = data_info->len / sizeof(uint32_t);
/* convert from S24LE to S32LE */
for (unsigned int i = 0; i < inlen; i++)
in[i] <<= 8;
data_info->buf = (uint8_t *)in;
data_info->len = inlen * sizeof(uint32_t);
dev->cb(data_info);
}
void hsdaoh_unpack_fpga_12bit_dual(hsdaoh_dev_t *dev, hsdaoh_data_info_t *data_info)
{
uint16_t *in = (uint16_t *)data_info->buf;
size_t inlen = data_info->len / sizeof(uint16_t);
unsigned int j = 0;
uint16_t *out16_1 = malloc(sizeof(uint16_t) * inlen * 2);
uint16_t *out16_2 = malloc(sizeof(uint16_t) * inlen * 2);
if (!out16_1 || !out16_2)
return;
/* extract packed 2x12 bit samples */
for (unsigned int i = 0; i < inlen; i += 3) {
uint16_t lsbs = in[i+2];
out16_1[j] = ((in[i+0] & 0xff00) >> 4) | ((lsbs >> 0) & 0xf);
out16_2[j++] = ((in[i+0] & 0x00ff) << 4) | ((lsbs >> 4) & 0xf);
out16_1[j] = ((in[i+1] & 0xff00) >> 4) | ((lsbs >> 8) & 0xf);
out16_2[j++] = ((in[i+1] & 0x00ff) << 4) | ((lsbs >> 12) & 0xf);
}
if (dev->output_float) {
hsdaoh_16bit_to_float(dev, data_info, out16_1, j, 2047.5, true);
} else {
data_info->buf = (uint8_t *)out16_1;
data_info->len = j * sizeof(uint16_t);
dev->cb(data_info);
}
data_info->stream_id += 1;
if (dev->output_float) {
hsdaoh_16bit_to_float(dev, data_info, out16_2, j, 2047.5, true);
} else {
data_info->buf = (uint8_t *)out16_2;
data_info->len = j * sizeof(uint16_t);
dev->cb(data_info);
}
free(out16_1);
free(out16_2);
}

View file

@ -37,19 +37,24 @@
#include "hsdaoh.h"
#define FD_NUMS 4
static int do_exit = 0;
static uint32_t bytes_to_read = 0;
static hsdaoh_dev_t *dev = NULL;
typedef struct file_ctx {
FILE *files[FD_NUMS];
} file_ctx_t;
void usage(void)
{
fprintf(stderr,
"hsdaoh_file, HDMI data acquisition tool\n\n"
"Usage:\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");
"\t[-b maximum number of buffers (default: 16)]\n"
"\t[-0 to -3 filename of steam 0 to stream 3 (a '-' dumps samples to stdout)]\n"
"\tfilename (of stream 0) (a '-' dumps samples to stdout)\n\n");
exit(1);
}
@ -75,36 +80,31 @@ static void sighandler(int signum)
}
#endif
void hsdaoh_callback(hsdaoh_data_info_t *data_info)
static void hsdaoh_callback(hsdaoh_data_info_t *data_info)
{
unsigned char *buf = data_info->buf;
uint32_t len = data_info->len;
void *ctx = data_info->ctx;
size_t nbytes = 0;
uint32_t len = data_info->len;
if (ctx) {
if (do_exit)
return;
if (!data_info->ctx || do_exit)
return;
if ((bytes_to_read > 0) && (bytes_to_read < len)) {
len = bytes_to_read;
do_exit = 1;
if (data_info->stream_id >= FD_NUMS)
return;
file_ctx_t *f = (file_ctx_t *)data_info->ctx;
FILE *file = f->files[data_info->stream_id];
if (!file)
return;
while (nbytes < len) {
nbytes += fwrite(data_info->buf + nbytes, 1, len - nbytes, file);
if (ferror(file)) {
fprintf(stderr, "Error writing file, samples lost, exiting!\n");
hsdaoh_stop_stream(dev);
break;
}
while (nbytes < len) {
nbytes += fwrite(buf + nbytes, 1, len - nbytes, (FILE*)ctx);
if (ferror((FILE*)ctx)) {
fprintf(stderr, "Error writing file, samples lost, exiting!\n");
hsdaoh_stop_stream(dev);
break;
}
}
if (bytes_to_read > 0)
bytes_to_read -= len;
}
}
@ -113,23 +113,39 @@ int main(int argc, char **argv)
#ifndef _WIN32
struct sigaction sigact;
#endif
char *filename = NULL;
char *filenames[FD_NUMS] = { NULL, };
int n_read;
int r, opt;
int ppm_error = 0;
FILE *file;
file_ctx_t f;
int dev_index = 0;
unsigned int num_bufs = 0;
bool fname0_used = false;
bool have_file = false;
while ((opt = getopt(argc, argv, "d:n:p:d:")) != -1) {
while ((opt = getopt(argc, argv, "0:1:2:3:d:b:")) != -1) {
switch (opt) {
case 'd':
dev_index = (uint32_t)atoi(optarg);
break;
case 'p':
ppm_error = atoi(optarg);
case 'b':
num_bufs = (unsigned int)atoi(optarg);
break;
case 'n':
bytes_to_read = (uint32_t)atof(optarg) * 2;
case '0':
fname0_used = true;
have_file = true;
filenames[0] = optarg;
break;
case '1':
have_file = true;
filenames[1] = optarg;
break;
case '2':
have_file = true;
filenames[2] = optarg;
break;
case '3':
have_file = true;
filenames[3] = optarg;
break;
default:
usage();
@ -137,15 +153,17 @@ int main(int argc, char **argv)
}
}
if (argc <= optind) {
usage();
} else {
filename = argv[optind];
if (!fname0_used) {
if (argc <= optind) {
if (!have_file)
usage();
} else {
filenames[0] = argv[optind];
}
}
if (dev_index < 0) {
if (dev_index < 0)
exit(1);
}
r = hsdaoh_open(&dev, (uint32_t)dev_index);
if (r < 0) {
@ -164,21 +182,27 @@ int main(int argc, char **argv)
SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE );
#endif
if (strcmp(filename, "-") == 0) { /* Write samples to stdout */
file = stdout;
for (int i = 0; i < FD_NUMS; i++) {
f.files[i] = NULL;
if (!filenames[i])
continue;
if (strcmp(filenames[i], "-") == 0) { /* Write samples to stdout */
f.files[i] = stdout;
#ifdef _WIN32
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdin), _O_BINARY);
#endif
} else {
file = fopen(filename, "wb");
if (!file) {
fprintf(stderr, "Failed to open %s\n", filename);
goto out;
} else {
f.files[i] = fopen(filenames[i], "wb");
if (!f.files[i]) {
fprintf(stderr, "Failed to open %s\n", filenames[i]);
goto out;
}
}
}
fprintf(stderr, "Reading samples...\n");
r = hsdaoh_start_stream(dev, hsdaoh_callback, (void *)file);
r = hsdaoh_start_stream(dev, hsdaoh_callback, (void *)&f, num_bufs);
while (!do_exit) {
usleep(50000);
@ -189,10 +213,13 @@ int main(int argc, char **argv)
else
fprintf(stderr, "\nLibrary error %d, exiting...\n", r);
if (file != stdout)
fclose(file);
hsdaoh_close(dev);
for (int i = 0; i < FD_NUMS; i++) {
if (f.files[i] && (f.files[i] != stdout))
fclose(f.files[i]);
}
out:
return r >= 0 ? r : -r;
}

View file

@ -153,6 +153,9 @@ void hsdaoh_callback(hsdaoh_data_info_t *data_info)
uint32_t len = data_info->len;
void *ctx = data_info->ctx;
if (data_info->stream_id != 0)
return;
if(!do_exit) {
struct llist *rpt = (struct llist*)malloc(sizeof(struct llist));
rpt->data = (char*)malloc(len);
@ -469,7 +472,7 @@ int main(int argc, char **argv)
r = pthread_create(&command_thread, &attr, command_worker, NULL);
pthread_attr_destroy(&attr);
r = hsdaoh_start_stream(dev, hsdaoh_callback, NULL);
r = hsdaoh_start_stream(dev, hsdaoh_callback, NULL, 0);
while (!do_exit) {
usleep(50000);
}

View file

@ -228,6 +228,9 @@ static void hsdaoh_callback(hsdaoh_data_info_t *data_info)
uint32_t len = data_info->len;
void *ctx = data_info->ctx;
if (data_info->stream_id != 0)
return;
/* verify the counter value */
uint16_t *cnt = (uint16_t *)buf;
int n = len / sizeof(uint16_t);
@ -291,7 +294,7 @@ int main(int argc, char **argv)
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);
r = hsdaoh_start_stream(dev, hsdaoh_callback, NULL, 0);
while (!do_exit)
usleep(50000);

502
src/iqconverter_float.c Normal file
View file

@ -0,0 +1,502 @@
/*
Copyright (C) 2014, Youssef Touil <youssef@airspy.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "iqconverter_float.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
#include <malloc.h>
#define _aligned_malloc __mingw_aligned_malloc
#define _aligned_free __mingw_aligned_free
#define _inline inline
#define FIR_STANDARD
#elif defined(__APPLE__)
#include <malloc/malloc.h>
#define _aligned_malloc(size, alignment) malloc(size)
#define _aligned_free(mem) free(mem)
#define _inline inline
#define FIR_STANDARD
#elif defined(__FreeBSD__)
#define USE_SSE2
#include <immintrin.h>
#define _inline inline
#define _aligned_free(mem) free(mem)
void *_aligned_malloc(size_t size, size_t alignment)
{
void *result;
if (posix_memalign(&result, alignment, size) == 0)
return result;
return 0;
}
#elif defined(__GNUC__) && !defined(__MINGW64_VERSION_MAJOR)
#include <malloc.h>
#define _aligned_malloc(size, alignment) memalign(alignment, size)
#define _aligned_free(mem) free(mem)
#define _inline inline
#else
#if (_MSC_VER >= 1800)
//#define USE_SSE2
//#include <immintrin.h>
#endif
#endif
#define SIZE_FACTOR 32
#define DEFAULT_ALIGNMENT 16
#define HPF_COEFF 0.01f
#if defined(_MSC_VER)
#define ALIGNED __declspec(align(DEFAULT_ALIGNMENT))
#else
#define ALIGNED
#endif
iqconverter_float_t *iqconverter_float_create(const float *hb_kernel, int len)
{
int i, j;
size_t buffer_size;
iqconverter_float_t *cnv = (iqconverter_float_t *) _aligned_malloc(sizeof(iqconverter_float_t), DEFAULT_ALIGNMENT);
cnv->len = len / 2 + 1;
cnv->hbc = hb_kernel[len / 2];
buffer_size = cnv->len * sizeof(float);
cnv->fir_kernel = (float *) _aligned_malloc(buffer_size, DEFAULT_ALIGNMENT);
cnv->fir_queue = (float *) _aligned_malloc(buffer_size * SIZE_FACTOR, DEFAULT_ALIGNMENT);
cnv->delay_line = (float *) _aligned_malloc(buffer_size / 2, DEFAULT_ALIGNMENT);
iqconverter_float_reset(cnv);
for (i = 0, j = 0; i < cnv->len; i++, j += 2)
{
cnv->fir_kernel[i] = hb_kernel[j];
}
return cnv;
}
void iqconverter_float_free(iqconverter_float_t *cnv)
{
_aligned_free(cnv->fir_kernel);
_aligned_free(cnv->fir_queue);
_aligned_free(cnv->delay_line);
_aligned_free(cnv);
}
void iqconverter_float_reset(iqconverter_float_t *cnv)
{
cnv->avg = 0.0f;
cnv->fir_index = 0;
cnv->delay_index = 0;
memset(cnv->delay_line, 0, cnv->len * sizeof(float) / 2);
memset(cnv->fir_queue, 0, cnv->len * sizeof(float) * SIZE_FACTOR);
}
static _inline float process_fir_taps(const float *kernel, const float *queue, int len)
{
int i;
#ifdef USE_SSE2
__m128 acc = _mm_set_ps(0, 0, 0, 0);
#else
float sum = 0.0f;
#endif
if (len >= 8)
{
int it = len >> 3;
#ifdef USE_SSE2
for (i = 0; i < it; i++)
{
__m128 head1 = _mm_loadu_ps(queue);
__m128 kern1 = _mm_load_ps(kernel);
__m128 head2 = _mm_loadu_ps(queue + 4);
__m128 kern2 = _mm_load_ps(kernel + 4);
__m128 mul1 = _mm_mul_ps(kern1, head1);
__m128 mul2 = _mm_mul_ps(kern2, head2);
mul1 = _mm_add_ps(mul1, mul2);
acc = _mm_add_ps(acc, mul1);
queue += 8;
kernel += 8;
}
#else
for (i = 0; i < it; i++)
{
sum += kernel[0] * queue[0]
+ kernel[1] * queue[1]
+ kernel[2] * queue[2]
+ kernel[3] * queue[3]
+ kernel[4] * queue[4]
+ kernel[5] * queue[5]
+ kernel[6] * queue[6]
+ kernel[7] * queue[7];
queue += 8;
kernel += 8;
}
#endif
len &= 7;
}
if (len >= 4)
{
#ifdef USE_SSE2
__m128 head = _mm_loadu_ps(queue);
__m128 kern = _mm_load_ps(kernel);
__m128 mul = _mm_mul_ps(kern, head);
acc = _mm_add_ps(acc, mul);
#else
sum += kernel[0] * queue[0]
+ kernel[1] * queue[1]
+ kernel[2] * queue[2]
+ kernel[3] * queue[3];
#endif
kernel += 4;
queue += 4;
len &= 3;
}
#ifdef USE_SSE2
__m128 t = _mm_add_ps(acc, _mm_movehl_ps(acc, acc));
acc = _mm_add_ss(t, _mm_shuffle_ps(t, t, 1));
#ifdef __FreeBSD__
float sum = acc[0];
#else
float sum = acc.m128_f32[0];
#endif
#endif
if (len >= 2)
{
sum += kernel[0] * queue[0]
+ kernel[1] * queue[1];
//kernel += 2;
//queue += 2;
//len &= 1;
}
//if (len >= 1)
//{
// sum += kernel[0] * queue[0];
//}
return sum;
}
static void fir_interleaved_4(iqconverter_float_t *cnv, float *samples, int len)
{
int i;
int fir_index = cnv->fir_index;
int fir_len = cnv->len;
float *fir_kernel = cnv->fir_kernel;
float *fir_queue = cnv->fir_queue;
float *queue;
float acc;
for (i = 0; i < len; i += 2)
{
queue = fir_queue + fir_index;
queue[0] = samples[i];
acc = fir_kernel[0] * (queue[0] + queue[4 - 1])
+ fir_kernel[1] * (queue[1] + queue[4 - 2]);
samples[i] = acc;
if (--fir_index < 0)
{
fir_index = fir_len * (SIZE_FACTOR - 1);
memcpy(fir_queue + fir_index + 1, fir_queue, (fir_len - 1) * sizeof(float));
}
}
cnv->fir_index = fir_index;
}
static void fir_interleaved_8(iqconverter_float_t *cnv, float *samples, int len)
{
int i;
int fir_index = cnv->fir_index;
int fir_len = cnv->len;
float *fir_kernel = cnv->fir_kernel;
float *fir_queue = cnv->fir_queue;
float *queue;
float acc;
for (i = 0; i < len; i += 2)
{
queue = fir_queue + fir_index;
queue[0] = samples[i];
acc = fir_kernel[0] * (queue[0] + queue[8 - 1])
+ fir_kernel[1] * (queue[1] + queue[8 - 2])
+ fir_kernel[2] * (queue[2] + queue[8 - 3])
+ fir_kernel[3] * (queue[3] + queue[8 - 4]);
samples[i] = acc;
if (--fir_index < 0)
{
fir_index = fir_len * (SIZE_FACTOR - 1);
memcpy(fir_queue + fir_index + 1, fir_queue, (fir_len - 1) * sizeof(float));
}
}
cnv->fir_index = fir_index;
}
static void fir_interleaved_12(iqconverter_float_t *cnv, float *samples, int len)
{
int i;
int fir_index = cnv->fir_index;
int fir_len = cnv->len;
float *fir_kernel = cnv->fir_kernel;
float *fir_queue = cnv->fir_queue;
float *queue;
float acc = 0;
for (i = 0; i < len; i += 2)
{
queue = fir_queue + fir_index;
queue[0] = samples[i];
acc = fir_kernel[0] * (queue[0] + queue[12 - 1])
+ fir_kernel[1] * (queue[1] + queue[12 - 2])
+ fir_kernel[2] * (queue[2] + queue[12 - 3])
+ fir_kernel[3] * (queue[3] + queue[12 - 4])
+ fir_kernel[4] * (queue[4] + queue[12 - 5])
+ fir_kernel[5] * (queue[5] + queue[12 - 6]);
samples[i] = acc;
if (--fir_index < 0)
{
fir_index = fir_len * (SIZE_FACTOR - 1);
memcpy(fir_queue + fir_index + 1, fir_queue, (fir_len - 1) * sizeof(float));
}
}
cnv->fir_index = fir_index;
}
static void fir_interleaved_24(iqconverter_float_t *cnv, float *samples, int len)
{
int i;
int fir_index = cnv->fir_index;
int fir_len = cnv->len;
float *fir_kernel = cnv->fir_kernel;
float *fir_queue = cnv->fir_queue;
float *queue;
float acc = 0;
for (i = 0; i < len; i += 2)
{
queue = fir_queue + fir_index;
queue[0] = samples[i];
acc = fir_kernel[0] * (queue[0] + queue[24 - 1])
+ fir_kernel[1] * (queue[1] + queue[24 - 2])
+ fir_kernel[2] * (queue[2] + queue[24 - 3])
+ fir_kernel[3] * (queue[3] + queue[24 - 4])
+ fir_kernel[4] * (queue[4] + queue[24 - 5])
+ fir_kernel[5] * (queue[5] + queue[24 - 6])
+ fir_kernel[6] * (queue[6] + queue[24 - 7])
+ fir_kernel[7] * (queue[7] + queue[24 - 8])
+ fir_kernel[8] * (queue[8] + queue[24 - 9])
+ fir_kernel[9] * (queue[9] + queue[24 - 10])
+ fir_kernel[10] * (queue[10] + queue[24 - 11])
+ fir_kernel[11] * (queue[11] + queue[24 - 12]);
samples[i] = acc;
if (--fir_index < 0)
{
fir_index = fir_len * (SIZE_FACTOR - 1);
memcpy(fir_queue + fir_index + 1, fir_queue, (fir_len - 1) * sizeof(float));
}
}
cnv->fir_index = fir_index;
}
static void fir_interleaved_generic(iqconverter_float_t *cnv, float *samples, int len)
{
int i;
int fir_index = cnv->fir_index;
int fir_len = cnv->len;
float *fir_kernel = cnv->fir_kernel;
float *fir_queue = cnv->fir_queue;
float *queue;
for (i = 0; i < len; i += 2)
{
queue = fir_queue + fir_index;
queue[0] = samples[i];
samples[i] = process_fir_taps(fir_kernel, queue, fir_len);
if (--fir_index < 0)
{
fir_index = fir_len * (SIZE_FACTOR - 1);
memcpy(fir_queue + fir_index + 1, fir_queue, (fir_len - 1) * sizeof(float));
}
}
cnv->fir_index = fir_index;
}
static void fir_interleaved(iqconverter_float_t *cnv, float *samples, int len)
{
switch (cnv->len)
{
case 4:
fir_interleaved_4(cnv, samples, len);
break;
case 8:
fir_interleaved_8(cnv, samples, len);
break;
case 12:
fir_interleaved_12(cnv, samples, len);
break;
case 24:
fir_interleaved_24(cnv, samples, len);
break;
default:
fir_interleaved_generic(cnv, samples, len);
break;
}
}
static void delay_interleaved(iqconverter_float_t *cnv, float *samples, int len)
{
int i;
ALIGNED int index;
ALIGNED int half_len;
ALIGNED float res;
half_len = cnv->len >> 1;
index = cnv->delay_index;
for (i = 0; i < len; i += 2)
{
res = cnv->delay_line[index];
cnv->delay_line[index] = samples[i];
samples[i] = res;
if (++index >= half_len)
{
index = 0;
}
}
cnv->delay_index = index;
}
#define SCALE (0.01f)
static void remove_dc(iqconverter_float_t *cnv, float *samples, int len)
{
int i;
ALIGNED float avg = cnv->avg;
for (i = 0; i < len; i++)
{
samples[i] -= avg;
avg += SCALE * samples[i];
}
cnv->avg = avg;
}
static void translate_fs_4(iqconverter_float_t *cnv, float *samples, int len)
{
int i;
ALIGNED float hbc = cnv->hbc;
#ifdef USE_SSE2
float *buf = samples;
ALIGNED __m128 vec;
ALIGNED __m128 rot = _mm_set_ps(hbc, 1.0f, -hbc, -1.0f);
for (i = 0; i < len / 4; i++, buf +=4)
{
vec = _mm_loadu_ps(buf);
vec = _mm_mul_ps(vec, rot);
_mm_storeu_ps(buf, vec);
}
#else
int j;
for (i = 0; i < len / 4; i++)
{
j = i << 2;
samples[j + 0] = -samples[j + 0];
samples[j + 1] = -samples[j + 1] * hbc;
//samples[j + 2] = samples[j + 2];
samples[j + 3] = samples[j + 3] * hbc;
}
#endif
fir_interleaved(cnv, samples, len);
delay_interleaved(cnv, samples + 1, len);
}
void iqconverter_float_process(iqconverter_float_t *cnv, float *samples, int len)
{
remove_dc(cnv, samples, len);
translate_fs_4(cnv, samples, len);
}

View file

@ -40,52 +40,19 @@
#endif
#include <inttypes.h>
#include <pthread.h>
#include <libusb.h>
#include <libuvc/libuvc.h>
#include <iqconverter_float.h>
#include <filters.h>
#include <hsdaoh.h>
#include <hsdaoh_private.h>
#include <format_convert.h>
#include <crc.h>
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;
int hid_interface;
uint8_t edid_seq_cnt;
int frames_since_error;
int discard_start_frames;
unsigned int in_order_cnt;
uint16_t last_frame_cnt;
uint16_t last_crc[2];
uint16_t idle_cnt;
bool stream_synced;
unsigned int width, height, fps;
/* status */
int dev_lost;
bool driver_active;
unsigned int xfer_errors;
char manufact[256];
char product[256];
};
#define DEFAULT_BUFFERS 16
typedef struct hsdaoh_adapter {
uint16_t vid;
@ -95,7 +62,7 @@ typedef struct hsdaoh_adapter {
static hsdaoh_adapter_t known_devices[] = {
{ 0x345f, 0x2130, "MS2130" },
{ 0x534d, 0x2130, "MS2130 OEM?" },
{ 0x534d, 0x2130, "MS2130 OEM" },
{ 0x345f, 0x2131, "MS2131" },
};
@ -105,16 +72,33 @@ enum crc_config {
CRC16_2_LINE /* Line contains CRC of the line before the last line */
};
#define DEFAULT_MAX_STREAMS 8
typedef struct
{
uint64_t data_cnt;
uint32_t srate;
uint32_t reserved1;
char reserved2[16];
} __attribute__((packed, aligned(1))) stream_info_t;
typedef struct
{
uint32_t magic;
uint16_t framecounter;
uint8_t pack_state;
uint8_t reserved1;
uint8_t crc_config;
uint8_t data_width;
uint8_t data_signedness;
uint16_t version;
uint32_t flags;
uint32_t reserved2[8];
uint16_t stream0_format;
uint16_t max_streamid;
stream_info_t stream_info[DEFAULT_MAX_STREAMS];
} __attribute__((packed, aligned(1))) metadata_t;
#define FLAG_STREAM_ID_PRESENT (1 << 0)
#define FLAG_FORMAT_ID_PRESENT (1 << 1)
#define CTRL_TIMEOUT 300
int hsdaoh_get_hid_feature_report(hsdaoh_dev_t *dev, unsigned char *data, size_t length)
@ -509,6 +493,7 @@ int hsdaoh_open(hsdaoh_dev_t **out_dev, uint32_t index)
goto err;
dev->dev_lost = 0;
dev->cnv_f = iqconverter_float_create(HB_KERNEL_FLOAT, HB_KERNEL_FLOAT_LEN);
found:
*out_dev = dev;
@ -540,11 +525,144 @@ int hsdaoh_close(hsdaoh_dev_t *dev)
uvc_unref_device(dev->uvc_dev);
uvc_exit(dev->uvc_ctx);
iqconverter_float_free(dev->cnv_f);
free(dev);
return 0;
}
// maybe rename to preferred output format
// and add real output format to data_info_t
int hsdaoh_set_output_format(hsdaoh_dev_t *dev, hsdaoh_output_format_t format)
{
if (!dev)
return -1;
return 0;
}
void hsdaoh_output(hsdaoh_dev_t *dev, uint16_t sid, int format, uint8_t *data, size_t len)
{
hsdaoh_data_info_t data_info;
data_info.ctx = dev->cb_ctx;
data_info.stream_id = sid;
data_info.buf = data;
data_info.len = len;
switch (format) {
case PIO_8BIT_IQ:
hsdaoh_unpack_pio_8bit_iq(dev, &data_info);
break;
case PIO_10BIT_IQ:
hsdaoh_unpack_pio_10bit_iq(dev, &data_info);
break;
case PIO_12BIT:
hsdaoh_unpack_pio_12bit(dev, &data_info);
break;
case PIO_12BIT_DUAL:
hsdaoh_unpack_pio_12bit_dual(dev, &data_info);
break;
case PIO_PCM1802_AUDIO:
hsdaoh_unpack_pio_pcm1802_audio(dev, &data_info);
break;
case FPGA_12BIT_DUAL:
hsdaoh_unpack_fpga_12bit_dual(dev, &data_info);
break;
default:
dev->cb(&data_info);
break;
}
}
static void *hsdaoh_output_worker(void *arg)
{
struct llist *curelem, *prev;
struct timespec ts;
struct timeval tp;
fd_set writefds;
int r = 0;
hsdaoh_dev_t *dev = (hsdaoh_dev_t *)arg;
while(1) {
if (dev->async_status != HSDAOH_RUNNING)
pthread_exit(NULL);
pthread_mutex_lock(&dev->ll_mutex);
gettimeofday(&tp, NULL);
ts.tv_sec = tp.tv_sec+1;
ts.tv_nsec = tp.tv_usec * 1000;
r = pthread_cond_timedwait(&dev->cond, &dev->ll_mutex, &ts);
if (r == ETIMEDOUT) {
pthread_mutex_unlock(&dev->ll_mutex);
continue;
}
curelem = dev->ll_buffers;
dev->ll_buffers = NULL;
pthread_mutex_unlock(&dev->ll_mutex);
while (curelem != NULL) {
hsdaoh_output(dev, curelem->sid, curelem->format, curelem->data, curelem->len);
prev = curelem;
curelem = curelem->next;
free(prev->data);
free(prev);
}
}
}
void hsdaoh_enqueue_data(hsdaoh_dev_t *dev, uint16_t sid, int format, uint8_t *data, size_t len)
{
if (dev->async_status != HSDAOH_RUNNING) {
free(data);
return;
}
struct llist *rpt = (struct llist*)malloc(sizeof(struct llist));
rpt->data = data;
rpt->len = len;
rpt->sid = sid;
rpt->format = format;
rpt->next = NULL;
pthread_mutex_lock(&dev->ll_mutex);
if (dev->ll_buffers == NULL) {
dev->ll_buffers = rpt;
} else {
struct llist *cur = dev->ll_buffers;
unsigned int num_queued = 0;
while (cur->next != NULL) {
cur = cur->next;
num_queued++;
}
if (dev->llbuf_num && dev->llbuf_num == num_queued-2) {
struct llist *curelem;
fprintf(stderr, "Buffer dropped due to overrun!\n");
free(dev->ll_buffers->data);
curelem = dev->ll_buffers->next;
free(dev->ll_buffers);
dev->ll_buffers = curelem;
}
cur->next = rpt;
if (num_queued > dev->highest_numq) {
fprintf(stderr, "Maximum buffer queue length: %d\n", num_queued);
dev->highest_numq = num_queued;
}
dev->global_numq = num_queued;
}
pthread_cond_signal(&dev->cond);
pthread_mutex_unlock(&dev->ll_mutex);
}
/* callback for idle/filler data */
inline int hsdaoh_check_idle_cnt(hsdaoh_dev_t *dev, uint16_t *buf, size_t length)
{
@ -566,17 +684,14 @@ inline int hsdaoh_check_idle_cnt(hsdaoh_dev_t *dev, uint16_t *buf, size_t length
/* 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);
meta[i/2] = (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);
@ -602,6 +717,9 @@ void hsdaoh_process_frame(hsdaoh_dev_t *dev, uint8_t *data, int size)
dev->last_frame_cnt = meta.framecounter;
int frame_errors = 0;
unsigned int stream0_payload_bytes = 0;
uint16_t stream0_format = 0;
uint8_t *stream0_data = malloc(dev->width-1 * dev->height * 2);
for (unsigned int i = 0; i < dev->height; i++) {
uint8_t *line_dat = data + (dev->width * sizeof(uint16_t) * i);
@ -610,6 +728,14 @@ void hsdaoh_process_frame(hsdaoh_dev_t *dev, uint8_t *data, int size)
uint16_t payload_len = le16toh(((uint16_t *)line_dat)[dev->width - 1]);
uint16_t crc = le16toh(((uint16_t *)line_dat)[dev->width - 2]);
uint16_t stream_id = le16toh(((uint16_t *)line_dat)[dev->width - 3]);
uint16_t format = (meta.flags & FLAG_FORMAT_ID_PRESENT) ? stream_id >> 6 : RAW_8BIT;
if (meta.flags & FLAG_STREAM_ID_PRESENT)
stream_id &= 0x3f;
else {
stream_id = 0;
format = meta.stream0_format;
}
/* we only use 12 bits, the upper 4 bits are reserved for the metadata */
payload_len &= 0x0fff;
@ -635,20 +761,25 @@ void hsdaoh_process_frame(hsdaoh_dev_t *dev, uint8_t *data, int size)
dev->last_crc[0] = crc16_ccitt(line_dat, dev->width * sizeof(uint16_t));
}
if (payload_len > 0)
memmove(data + frame_payload_bytes, line_dat, payload_len * sizeof(uint16_t));
if ((payload_len > 0) && dev->stream_synced) {
unsigned int out_len = payload_len * sizeof(uint16_t);
frame_payload_bytes += payload_len * sizeof(uint16_t);
if (!(meta.flags & FLAG_STREAM_ID_PRESENT) || stream_id == 0) {
memcpy(stream0_data + stream0_payload_bytes, line_dat, out_len);
stream0_payload_bytes += out_len;
stream0_format = format;
} else {
uint8_t *out_data = malloc(out_len);
memcpy(out_data, line_dat, out_len);
hsdaoh_enqueue_data(dev, stream_id, format, out_data, out_len);
}
}
}
hsdaoh_data_info_t data_info;
data_info.stream_id = 0;
data_info.buf = (uint8_t *)data;
data_info.len = frame_payload_bytes;
data_info.ctx = dev->cb_ctx;
if (dev->cb && dev->stream_synced)
dev->cb(&data_info);
if (dev->stream_synced && stream0_payload_bytes)
hsdaoh_enqueue_data(dev, 0, stream0_format, stream0_data, stream0_payload_bytes);
else
free(stream0_data);
if (frame_errors && dev->stream_synced) {
fprintf(stderr,"%d frame errors, %d frames since last error\n", frame_errors, dev->frames_since_error);
@ -687,7 +818,7 @@ void _uvc_callback(uvc_frame_t *frame, void *ptr)
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 hsdaoh_start_stream(hsdaoh_dev_t *dev, hsdaoh_read_cb_t cb, void *ctx, unsigned int buf_num)
{
int r = 0;
@ -697,12 +828,29 @@ int hsdaoh_start_stream(hsdaoh_dev_t *dev, hsdaoh_read_cb_t cb, void *ctx)
if (HSDAOH_INACTIVE != dev->async_status)
return -2;
iqconverter_float_reset(dev->cnv_f);
dev->async_status = HSDAOH_RUNNING;
dev->async_cancel = 0;
dev->cb = cb;
dev->cb_ctx = ctx;
// dev->output_float = true;
/* initialize with a threshold */
dev->highest_numq = 9;
dev->llbuf_num = (buf_num == 0) ? DEFAULT_BUFFERS : buf_num;
pthread_mutex_init(&dev->ll_mutex, NULL);
pthread_cond_init(&dev->cond, NULL);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
r = pthread_create(&dev->hsdaoh_output_worker_thread, &attr, hsdaoh_output_worker, (void *)dev);
pthread_attr_destroy(&attr);
uvc_error_t res;
uvc_stream_ctrl_t ctrl;
@ -739,10 +887,27 @@ int hsdaoh_stop_stream(hsdaoh_dev_t *dev)
if (HSDAOH_RUNNING == dev->async_status) {
dev->async_status = HSDAOH_CANCELING;
dev->async_cancel = 1;
pthread_cond_signal(&dev->cond);
/* End the stream. Blocks until last callback is serviced */
uvc_stop_streaming(dev->uvc_devh);
void *status;
struct llist *curelem, *prev;
pthread_join(dev->hsdaoh_output_worker_thread, &status);
curelem = dev->ll_buffers;
dev->ll_buffers = NULL;
while (curelem != 0) {
prev = curelem;
curelem = curelem->next;
free(prev->data);
free(prev);
}
dev->global_numq = 0;
return 0;
}