├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── channel.c ├── channel.h ├── cmake_uninstall.cmake.in ├── correlator ├── autocorrelator.c ├── autocorrelator.h ├── correlator.c └── correlator.h ├── decode.c ├── decode.h ├── deinterleave ├── deinterleave.c └── deinterleave.h ├── diffcode ├── diffcode.c └── diffcode.h ├── ecc ├── descramble.c ├── descramble.h ├── rs.c ├── rs.h ├── viterbi.c └── viterbi.h ├── jpeg ├── huffman.c ├── huffman.h ├── jpeg.c └── jpeg.h ├── main.c ├── math ├── arm_simd32.h ├── int.c └── int.h ├── output ├── bmp_out.c ├── bmp_out.h ├── png_out.c └── png_out.h ├── parser ├── mcu_parser.c ├── mcu_parser.h ├── mpdu_parser.c └── mpdu_parser.h ├── protocol ├── cadu.h ├── mcu.c ├── mcu.h ├── mpdu.c ├── mpdu.h ├── vcdu.c └── vcdu.h ├── raw_channel.c ├── raw_channel.h ├── utils.c └── utils.h /.gitignore: -------------------------------------------------------------------------------- 1 | **/tags 2 | build/ 3 | docs/*.pdf 4 | **/cachegrind.out* 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | option(USE_PNG "Enable PNG output" ON) 4 | 5 | project(meteor_decode 6 | VERSION 1.1.2 7 | DESCRIPTION "LRPT decoder" 8 | LANGUAGES C) 9 | add_definitions(-DVERSION="${CMAKE_PROJECT_VERSION}") 10 | 11 | if (NOT CMAKE_BUILD_TYPE) 12 | set(CMAKE_BUILD_TYPE "Release") 13 | endif() 14 | 15 | set(CMAKE_C_STANDARD 99) 16 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -Wextra -Wimplicit-fallthrough") 17 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -march=native -ffast-math -ftree-vectorize") 18 | 19 | # ARM architectures need -mfpu=auto in order to enable NEON when available, 20 | # but that option is unrecognized by x86 gcc (and possibly others): only 21 | # add it to the release flags when the compiler's target is arm 22 | # This is not a problem for arm64, as NEON support is mandatory for that arch 23 | execute_process(COMMAND "${CMAKE_C_COMPILER}" "-dumpmachine" COMMAND "grep" "arm" OUTPUT_QUIET RESULT_VARIABLE is_arm) 24 | if (is_arm EQUAL "0") 25 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mcpu=native -mfpu=auto") 26 | endif() 27 | 28 | 29 | set(LIBRARY_SOURCES 30 | correlator/correlator.c correlator/correlator.h 31 | correlator/autocorrelator.c correlator/autocorrelator.h 32 | 33 | deinterleave/deinterleave.c deinterleave/deinterleave.h 34 | 35 | diffcode/diffcode.c diffcode/diffcode.h 36 | 37 | ecc/descramble.c ecc/descramble.h 38 | ecc/rs.c ecc/rs.h 39 | ecc/viterbi.c ecc/viterbi.h 40 | 41 | jpeg/huffman.c jpeg/huffman.h 42 | jpeg/jpeg.c jpeg/jpeg.h 43 | 44 | math/int.c math/int.h 45 | math/arm_simd32.h 46 | 47 | parser/mcu_parser.c parser/mcu_parser.h 48 | parser/mpdu_parser.c parser/mpdu_parser.h 49 | 50 | protocol/mcu.c protocol/mcu.h 51 | protocol/mpdu.c protocol/mpdu.h 52 | protocol/vcdu.c protocol/vcdu.h 53 | 54 | 55 | channel.c channel.h 56 | raw_channel.c raw_channel.h 57 | decode.c decode.h 58 | utils.c utils.h 59 | ) 60 | 61 | set(EXEC_SOURCES 62 | output/bmp_out.c output/bmp_out.h 63 | ) 64 | 65 | set(COMMON_INC_DIRS 66 | ${PROJECT_SOURCE_DIR} 67 | correlator/ deinterleave/ diffcode/ ecc/ jpeg/ math/ parser/ protocol/ 68 | ) 69 | 70 | 71 | # Enable PNG if requested at configure time AND libpng is present 72 | if (USE_PNG) 73 | find_library(PNG_LIBRARY NAMES png libpng) 74 | if (PNG_LIBRARY) 75 | add_definitions(-DUSE_PNG) 76 | set(EXEC_SOURCES ${EXEC_SOURCES} output/png_out.c output/png_out.h) 77 | else() 78 | message(WARNING "libpng not found, PNG output will be unavailable") 79 | endif() 80 | endif() 81 | 82 | # Main library target 83 | add_library(lrpt_static STATIC ${LIBRARY_SOURCES}) 84 | target_include_directories(lrpt_static PRIVATE ${COMMON_INC_DIRS}) 85 | 86 | # Shared library target 87 | add_library(lrpt SHARED ${LIBRARY_SOURCES}) 88 | target_include_directories(lrpt PRIVATE ${COMMON_INC_DIRS}) 89 | 90 | # Main executable target 91 | add_executable(meteor_decode main.c ${EXEC_SOURCES}) 92 | target_include_directories(meteor_decode PRIVATE ${COMMON_INC_DIRS}) 93 | target_link_libraries(meteor_decode PRIVATE lrpt_static) 94 | 95 | # Add links to PNG library if enabled 96 | if(USE_PNG AND PNG_LIBRARY) 97 | target_link_libraries(meteor_decode PRIVATE png) 98 | endif() 99 | 100 | # Install targets 101 | install(TARGETS meteor_decode DESTINATION bin) 102 | 103 | # uninstall target 104 | if(NOT TARGET uninstall) 105 | configure_file( 106 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" 107 | "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" 108 | IMMEDIATE @ONLY) 109 | 110 | add_custom_target(uninstall 111 | COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) 112 | endif() 113 | 114 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2021 dbdexter-dev 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the “Software”), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Meteor-M series LRPT decoder 2 | ======================================= 3 | 4 | This is a free, open-source LRPT decoder. It reads samples from a 8-bit 5 | soft-QPSK file and outputs an image displaying the specified APIDs. 6 | 7 | Features: 8 | - Support for regular (72k) and interleaved (80k) modes 9 | - Support for differential decoding 10 | - Automatic RGB123/RGB125 composite output based on active APIDs 11 | - APID 70 raw dump 12 | - Native BMP output 13 | - Optional PNG output (requires libpng) 14 | - Split channels output 15 | - Read samples from stdin (pass `-` in place of a filename) 16 | - Ctrl-C at any point to write the image and exit (useful when decoding a stream of symbols) 17 | 18 | 19 | Build/install instructions 20 | -------------------------- 21 | 22 | ``` 23 | mkdir build && cd build 24 | cmake .. 25 | make 26 | sudo make install 27 | ``` 28 | 29 | By default, support for both BMP and PNG output formats will be compiled in, 30 | provided that `libpng` is installed. 31 | The output format will then be chosen based on the extension of the output image. 32 | 33 | If you don't need PNG support, you can disable it by running 34 | `cmake -DUSE_PNG=OFF ..` when configuring. 35 | 36 | 37 | Sample output 38 | ------------- 39 | ``` 40 | 24.54% vit(avg): 1126 rs(sum): 28 APID 64 seq: 2009311 11:30:23.468 41 | ^ ^ ^ ^ ^ ^ 42 | file average Reed-Solomon current current VCDU timestamp 43 | progress Viterbi path errors corrected APID sequence reported by the 44 | length per (-1 if too many number satellite (MSK time) 45 | byte (lower errors detected) 46 | is better, 47 | ~1100 is usually enough 48 | to get a decode) 49 | ``` 50 | 51 | 52 | Usage 53 | ----- 54 | 55 | ``` 56 | meteor_decode [options] input.s 57 | -7, --70 Dump APID70 data in a separate file 58 | -a, --apid R,G,B Specify APIDs to parse (default: autodetect) 59 | -B, --batch Batch mode (disable all non-printable characters) 60 | -d, --diff Perform differential decoding 61 | -i, --int Deinterleave samples (aka 80k mode) 62 | -o, --output Output composite image to 63 | -q, --quiet Disable decoder status output 64 | -s, --split Write each APID in a separate file 65 | -t, --statfile Write .stat file 66 | 67 | -h, --help Print this help screen 68 | -v, --version Print version information 69 | ``` 70 | 71 | Typical use cases: 72 | - Meteor-M2, RGB123/125: `meteor_decode -o ` 73 | - Meteor-M2, RGB122: `meteor_decode -o -a 65,65,64` 74 | -------------------------------------------------------------------------------- /channel.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "channel.h" 6 | #include "protocol/mpdu.h" 7 | #include "protocol/mcu.h" 8 | #include "utils.h" 9 | 10 | #define PIXELS_PER_STRIP (MCU_PER_LINE*8*8) 11 | 12 | static const uint8_t black_strip[MCU_PER_MPDU][8][8]; 13 | static void cache_strip(Channel *ch, const uint8_t (*strip)[8][8]); 14 | 15 | int 16 | channel_init(Channel *ch, int apid) 17 | { 18 | ch->mcu_seq = 0; 19 | ch->mpdu_seq = -1; 20 | ch->apid = apid; 21 | 22 | ch->offset = 0; 23 | ch->len = STRIPS_PER_ALLOC * PIXELS_PER_STRIP; 24 | ch->pixels = malloc(ch->len); 25 | 26 | return 0; 27 | } 28 | 29 | void 30 | channel_close(Channel *ch) 31 | { 32 | if (ch->pixels) { 33 | free(ch->pixels); 34 | ch->pixels = NULL; 35 | } 36 | } 37 | 38 | void 39 | channel_append_strip(Channel *ch, const uint8_t (*strip)[8][8], unsigned int mcu_seq, unsigned int mpdu_seq) 40 | { 41 | int i; 42 | int mpdu_delta, mcu_delta; 43 | int lines_lost, strips_lost; 44 | 45 | /* Handle misalignments. Can occur after a satellite buffer overflow */ 46 | mcu_seq -= (mcu_seq % MCU_PER_MPDU); 47 | 48 | /* Align image to the given MPDU and MCU sequence numbers */ 49 | mpdu_delta = (mpdu_seq - ch->mpdu_seq - 1 + MPDU_MAX_SEQ) % MPDU_MAX_SEQ; 50 | mcu_delta = (mcu_seq - ch->mcu_seq + MCU_PER_LINE) % MCU_PER_LINE; 51 | 52 | lines_lost = (ch->mpdu_seq < 0) ? 0 : mpdu_delta / MPDU_PER_PERIOD; 53 | strips_lost = mcu_delta / MCU_PER_MPDU + lines_lost * MPDU_PER_LINE; 54 | 55 | /* Align images and update current mpdu seq counter */ 56 | for (i = strips_lost; i>0; i--) { 57 | cache_strip(ch, black_strip); 58 | } 59 | ch->mpdu_seq = mpdu_seq; 60 | ch->mcu_seq = mcu_seq; 61 | 62 | /* Copy strip into cache */ 63 | cache_strip(ch, strip ? strip : black_strip); 64 | } 65 | 66 | static void 67 | cache_strip(Channel *ch, const uint8_t (*strip)[8][8]) 68 | { 69 | int row, block; 70 | unsigned int old_len; 71 | 72 | /* If this write would go out of bounds, allocate more memory and initialize 73 | * it to black pixels */ 74 | if (ch->offset + PIXELS_PER_STRIP > ch->len) { 75 | old_len = ch->len; 76 | ch->len += STRIPS_PER_ALLOC*PIXELS_PER_STRIP; 77 | 78 | ch->pixels = realloc(ch->pixels, ch->len); 79 | memset(ch->pixels+old_len, '\0', ch->len - old_len); 80 | } 81 | 82 | if (!ch->pixels) return; 83 | 84 | /* Reshape strip and add to the local pixel array */ 85 | for (row=0; row<8; row++) { 86 | for (block=0; blockpixels[ch->offset + row*MCU_PER_LINE*8 + (ch->mcu_seq+block)*8], strip[block][row], 8); 88 | } 89 | } 90 | 91 | ch->mcu_seq += MCU_PER_MPDU; 92 | if (ch->mcu_seq >= MCU_PER_LINE) { 93 | /* Advance both the MCU seq (rolling over) and the MPDU seq (moving 94 | * forward by the frame period minus this channel's size). Rollover 95 | * isn't really necessary here because channel_append_strip() already 96 | * computes the difference modulo MPDU_MAX_SEQ. */ 97 | ch->mcu_seq = 0; 98 | ch->mpdu_seq += MPDU_PER_PERIOD - MPDU_PER_LINE; 99 | ch->offset += PIXELS_PER_STRIP; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /channel.h: -------------------------------------------------------------------------------- 1 | #ifndef channel_h 2 | #define channel_h 3 | 4 | #include 5 | #include 6 | #include "protocol/mpdu.h" 7 | 8 | #define STRIPS_PER_ALLOC 32 9 | 10 | typedef struct { 11 | int mcu_seq, mpdu_seq; 12 | uint8_t *pixels; 13 | unsigned long offset, len; 14 | int apid; 15 | } Channel; 16 | 17 | /** 18 | * Initialize a Channel object 19 | * 20 | * @param ch the channel to initialize 21 | * @param apid APID associated with this channel 22 | * @return 0 on success 23 | * anything else on failure 24 | */ 25 | int channel_init(Channel *ch, int apid); 26 | 27 | /** 28 | * Finalize a channel, flushing any internal caches and closing the output file 29 | * 30 | * @param ch the channel to close 31 | */ 32 | void channel_close(Channel *ch); 33 | 34 | /** 35 | * Append a (8n)x8 strip to the current channel, compensating for lost strips 36 | * based on the MCU sequence number and the MPDU sequence number. 37 | * 38 | * @param ch the channel to append the strip to 39 | * @param strip pointer to multiple 8x8 blocks to write 40 | * @param mcu_seq sequence number associated with the strip 41 | * @param mpdu_seq sequence number associated with the MPDU the strip was in 42 | */ 43 | void channel_append_strip(Channel *ch, const uint8_t (*strip)[8][8], unsigned int mcu_seq, unsigned int mpdu_seq); 44 | 45 | #endif /* channel_h */ 46 | -------------------------------------------------------------------------------- /cmake_uninstall.cmake.in: -------------------------------------------------------------------------------- 1 | if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") 2 | message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") 3 | endif() 4 | 5 | file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) 6 | string(REGEX REPLACE "\n" ";" files "${files}") 7 | foreach(file ${files}) 8 | message(STATUS "Uninstalling $ENV{DESTDIR}${file}") 9 | if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 10 | exec_program( 11 | "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" 12 | OUTPUT_VARIABLE rm_out 13 | RETURN_VALUE rm_retval 14 | ) 15 | if(NOT "${rm_retval}" STREQUAL 0) 16 | message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") 17 | endif() 18 | else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 19 | message(STATUS "File $ENV{DESTDIR}${file} does not exist.") 20 | endif() 21 | endforeach() 22 | 23 | -------------------------------------------------------------------------------- /correlator/autocorrelator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "autocorrelator.h" 4 | 5 | static const uint8_t _syncwords[] = {0x27, 0x4E, 0xD8, 0xB1}; 6 | 7 | int 8 | autocorrelate(enum phase *rotation, int period, uint8_t *restrict hard, int len) 9 | { 10 | int i, j, k; 11 | uint8_t tmp, xor, window; 12 | int ones_count[8*period]; 13 | int average_bit[8*period]; 14 | int corr, best_corr, best_idx; 15 | 16 | /* Make len a multiple of the period */ 17 | len -= len % period; 18 | 19 | for (i=0; i<(int)LEN(ones_count); i++) { 20 | ones_count[i] = 0; 21 | average_bit[i] = 0; 22 | } 23 | 24 | /* XOR the bitstream with a delayed version of itself */ 25 | for (i=0; i= 0; j -= period) { 29 | xor = hard[j] ^ tmp; 30 | tmp = hard[j]; 31 | hard[j] = xor; 32 | 33 | /* Keep track of the average value of each bit in the period window */ 34 | for (k=0; k<8; k++) { 35 | average_bit[8*i + 7-k] += tmp & (1<> 1) | ((*hard << (i%8)) & 0x80); 47 | ones_count[i % (8*period)] += count_ones(window); 48 | } 49 | 50 | best_idx = 0; 51 | best_corr = ones_count[0] - len/64; /* Give offset 0 a small boost */ 52 | for (i=1; i<(int)LEN(ones_count); i++) { 53 | if (ones_count[i] < best_corr) { 54 | best_corr = ones_count[i]; 55 | best_idx = i; 56 | } 57 | } 58 | 59 | /* Collect the average syncword bits */ 60 | tmp = 0; 61 | for (i=7; i>=0; i--) { 62 | tmp |= (average_bit[best_idx+i] > 0 ? 1< corr) { 71 | best_corr = corr; 72 | *rotation = i; 73 | } 74 | } 75 | 76 | 77 | return best_idx; 78 | } 79 | -------------------------------------------------------------------------------- /correlator/autocorrelator.h: -------------------------------------------------------------------------------- 1 | #ifndef autocorrelator_h 2 | #define autocorrelator_h 3 | 4 | #include 5 | #include "utils.h" 6 | 7 | /** 8 | * Correlate the given set of samples with a delayed copy of itself to find the 9 | * interleaving synchronization marker 10 | * 11 | * @param rotation the rotation to which the synchronization word correlates the 12 | * best to. Will be only written to by the function 13 | * @param period distance between two synchronization markers 14 | * @param hard pointer to a byte buffer containing the hard samples to find the 15 | * synchronization marker in 16 | * @param len length of the byte buffer, in bytes 17 | * @return the offset with the highest correlation to the syncword 18 | */ 19 | int autocorrelate(enum phase *rotation, int period, uint8_t *restrict hard, int len); 20 | 21 | #endif /* autocorrelator_h */ 22 | -------------------------------------------------------------------------------- /correlator/correlator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "correlator.h" 5 | #include "ecc/viterbi.h" 6 | #include "utils.h" 7 | 8 | #define ROTATIONS 4 9 | 10 | static uint64_t hard_rotate_u64(uint64_t word, enum phase amount); 11 | static inline int correlate_u64(uint64_t x, uint64_t y); 12 | 13 | static uint64_t _syncwords[ROTATIONS]; 14 | 15 | void 16 | correlator_init(uint64_t syncword) 17 | { 18 | int i; 19 | 20 | for (i=0; i> 1); 24 | } 25 | } 26 | 27 | 28 | int 29 | correlate(enum phase *restrict best_phase, uint8_t *restrict hard_cadu, int len) 30 | { 31 | enum phase phase; 32 | int corr, best_corr, best_offset; 33 | int i, j; 34 | uint64_t window; 35 | uint8_t tmp; 36 | 37 | best_corr = 0; 38 | best_offset = 0; 39 | *best_phase = PHASE_0; 40 | 41 | window = ((uint64_t)hard_cadu[0] << 56) | 42 | ((uint64_t)hard_cadu[1] << 48) | 43 | ((uint64_t)hard_cadu[2] << 40) | 44 | ((uint64_t)hard_cadu[3] << 32) | 45 | ((uint64_t)hard_cadu[4] << 24) | 46 | ((uint64_t)hard_cadu[5] << 16) | 47 | ((uint64_t)hard_cadu[6] << 8) | 48 | ((uint64_t)hard_cadu[7] << 0); 49 | hard_cadu += 8; 50 | 51 | /* Prioritize offset 0 */ 52 | for (phase=PHASE_0; phase<=PHASE_270; phase++) { 53 | if (correlate_u64(_syncwords[phase], window) > CORR_THR) { 54 | *best_phase = phase; 55 | return 0; 56 | } 57 | } 58 | 59 | /* For each byte in the CADU */ 60 | for (i=0; i best_corr) { 72 | best_corr = corr; 73 | best_offset = i*8 + j; 74 | *best_phase = phase; 75 | } 76 | } 77 | 78 | /* Advance window by one */ 79 | window = (window << 1) | ((tmp >> (7-j)) & 0x1); 80 | } 81 | } 82 | 83 | return best_offset; 84 | } 85 | 86 | 87 | /* Static functions {{{ */ 88 | static uint64_t 89 | hard_rotate_u64(uint64_t word, enum phase amount) 90 | { 91 | const uint64_t i = word & 0xaaaaaaaaaaaaaaaa; 92 | const uint64_t q = word & 0x5555555555555555; 93 | 94 | switch (amount) { 95 | case PHASE_0: 96 | break; 97 | case PHASE_90: 98 | word = ((i ^ 0xaaaaaaaaaaaaaaaa) >> 1) | (q << 1); 99 | break; 100 | case PHASE_180: 101 | word = word ^ 0xffffffffffffffff; 102 | break; 103 | case PHASE_270: 104 | word = (i >> 1) | ((q ^ 0x5555555555555555) << 1); 105 | break; 106 | default: 107 | break; 108 | } 109 | 110 | return word; 111 | } 112 | 113 | static inline int 114 | correlate_u64(uint64_t x, uint64_t y) 115 | { 116 | int corr; 117 | uint64_t v = x ^ y; 118 | 119 | for (corr = 0; v; corr++) { 120 | v &= v-1; 121 | } 122 | 123 | return 64 - corr; 124 | } 125 | /* }}} */ 126 | -------------------------------------------------------------------------------- /correlator/correlator.h: -------------------------------------------------------------------------------- 1 | #ifndef correlator_h 2 | #define correlator_h 3 | 4 | #include 5 | #include "utils.h" 6 | 7 | #define CORR_THR 42 /* Empirical minimum correlation threshold for offset 0: 8 | * if the correlation is > this value, we assume that 9 | * the packet starts at offset 0 */ 10 | 11 | /** 12 | * Initialize the correlator with a synchronization sequence 13 | * 14 | * @param syncword one of the four rotations representing the synchronization 15 | * word 16 | */ 17 | void correlator_init(uint64_t syncword); 18 | 19 | /** 20 | * Look for the synchronization word inside a buffer 21 | * 22 | * @param best_phase the rotation to which the synchronization word correlates 23 | * the best to. Will only be written to by the function 24 | * @param hard_cadu pointer to a byte buffer containing the data to correlate 25 | * the syncword to 26 | * @param len length of the byte buffer, in bytes 27 | * @return the offset with the highest correlation to the syncword 28 | */ 29 | int correlate(enum phase *best_phase, uint8_t *hard_cadu, int len); 30 | 31 | #endif /* correlator_h */ 32 | -------------------------------------------------------------------------------- /decode.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "channel.h" 4 | #include "correlator/autocorrelator.h" 5 | #include "correlator/correlator.h" 6 | #include "decode.h" 7 | #include "diffcode/diffcode.h" 8 | #include "deinterleave/deinterleave.h" 9 | #include "ecc/descramble.h" 10 | #include "ecc/rs.h" 11 | #include "ecc/viterbi.h" 12 | #include "protocol/cadu.h" 13 | #include "parser/mpdu_parser.h" 14 | #include "parser/mcu_parser.h" 15 | #include "utils.h" 16 | 17 | static int read_samples(int (*read)(int8_t *dst, size_t len), int8_t *dst, size_t len); 18 | 19 | static int _rs, _vit; 20 | static uint32_t _vcdu_seq; 21 | static int _diffcoded; 22 | static int _interleaved; 23 | static enum { READ, PARSE_MPDU, VIT_SECOND } _state; 24 | 25 | #ifndef NDEBUG 26 | FILE *_vcdu; 27 | #endif 28 | 29 | 30 | void 31 | decode_init(int diffcoded, int interleaved) 32 | { 33 | uint64_t convolved_syncword; 34 | 35 | /* Initialize subsystems */ 36 | conv_encode_u32(&convolved_syncword, 0, SYNCWORD); 37 | correlator_init(convolved_syncword); 38 | viterbi_init(); 39 | descramble_init(); 40 | rs_init(); 41 | mpdu_parser_init(); 42 | 43 | #ifndef NDEBUG 44 | _vcdu = fopen("/tmp/vcdu.data", "wb"); 45 | #endif 46 | 47 | _rs = 0; 48 | _vit = 0; 49 | _vcdu_seq = 0; 50 | _diffcoded = diffcoded; 51 | _interleaved = interleaved; 52 | _state = READ; 53 | } 54 | 55 | DecoderState 56 | decode_soft_cadu(Mpdu *dst, int (*read)(int8_t *dst, size_t len)) 57 | { 58 | static int8_t soft_cadu[INTER_SIZE(2*CADU_SOFT_LEN)]; 59 | static int offset; 60 | static int vit; 61 | static Cadu cadu; 62 | 63 | uint8_t hard_cadu[CONV_CADU_LEN]; 64 | int errors; 65 | unsigned int i; 66 | enum phase rotation; 67 | 68 | switch (_state) { 69 | case READ: 70 | /* Read a CADU worth of samples */ 71 | for (i=0; i 0) { 84 | if (read_samples(read, soft_cadu+CADU_SOFT_LEN, offset)) return EOF_REACHED; 85 | if (_diffcoded) diff_decode(soft_cadu+CADU_SOFT_LEN, offset); 86 | } 87 | 88 | /* Derotate */ 89 | soft_derotate(soft_cadu+offset, CADU_SOFT_LEN, rotation); 90 | 91 | /* Finish decoding the past frame (output is VITERBI_DELAY bits late) */ 92 | vit = viterbi_decode(((uint8_t*)&cadu) + sizeof(Cadu)-VITERBI_DELAY, 93 | soft_cadu+offset, 94 | VITERBI_DELAY); 95 | 96 | /* Descramble and error correct */ 97 | descramble(&cadu); 98 | errors = rs_fix(&cadu.data); 99 | _rs = errors; 100 | #ifndef NDEBUG 101 | fwrite(&cadu, sizeof(cadu), 1, _vcdu); 102 | fflush(_vcdu); 103 | #endif 104 | 105 | /* If RS reports failure, reinitialize the internal state of the 106 | * MPDU decoder and finish up the Viterbi decode process. */ 107 | if (errors < 0) { 108 | mpdu_parser_init(); 109 | _state = VIT_SECOND; 110 | return STATS_ONLY; 111 | } 112 | 113 | _vcdu_seq = vcdu_counter(&cadu.data); 114 | _state = PARSE_MPDU; 115 | __attribute__((fallthrough)); 116 | case PARSE_MPDU: 117 | /* Parse the next MPDU in the decoded VCDU */ 118 | switch (mpdu_reconstruct(dst, &cadu.data)) { 119 | case PARSED: 120 | return MPDU_READY; 121 | case PROCEED: 122 | _state = VIT_SECOND; 123 | break; 124 | default: 125 | break; 126 | } 127 | break; 128 | 129 | case VIT_SECOND: 130 | /* Viterbi decode (2/2) */ 131 | vit += viterbi_decode((uint8_t*)&cadu, 132 | soft_cadu+offset+2*8*VITERBI_DELAY, 133 | sizeof(Cadu)-VITERBI_DELAY); 134 | _vit = vit / sizeof(Cadu); 135 | _state = READ; 136 | break; 137 | 138 | default: 139 | _state = READ; 140 | break; 141 | 142 | } 143 | 144 | return NOT_READY; 145 | } 146 | 147 | int 148 | decode_get_rs() 149 | { 150 | return _rs; 151 | } 152 | 153 | int 154 | decode_get_vit() 155 | { 156 | return _vit; 157 | } 158 | 159 | uint32_t 160 | decode_get_vcdu_seq() 161 | { 162 | return _vcdu_seq; 163 | } 164 | 165 | static int 166 | read_samples(int (*read)(int8_t *dst, size_t len), int8_t *dst, size_t len) 167 | { 168 | static int offset; 169 | static int8_t from_prev[INTER_MARKER_STRIDE]; 170 | int deint_offset; 171 | int num_samples; 172 | uint8_t hard[INTER_SIZE(len)]; 173 | static enum phase rotation; 174 | 175 | /* If not interleaved, directly read and return */ 176 | if (!_interleaved) return !read(dst, len); 177 | 178 | /* Retrieve enough samples so that the deinterleaver will output 179 | * $len samples. Use the internal cache first */ 180 | num_samples = deinterleave_num_samples(len); 181 | if (offset) { 182 | memcpy(dst, from_prev, MIN(offset, num_samples)); 183 | memcpy(from_prev, from_prev+offset, offset-MIN(offset, num_samples)); 184 | } 185 | if (num_samples-offset > 0 && !read(dst+offset, num_samples-offset)) return 1; 186 | offset -= MIN(offset, num_samples); 187 | 188 | if (num_samples < INTER_MARKER_STRIDE*8) { 189 | /* Not enough bytes to reliably find sync marker offset: assume the 190 | * offset is correct, and just derotate and deinterleave what we read */ 191 | soft_derotate(dst, num_samples, rotation); 192 | deinterleave(dst, dst, len); 193 | } else { 194 | /* Find synchronization marker (offset with the best autocorrelation) */ 195 | soft_to_hard(hard, dst, num_samples & ~0x7); 196 | offset = autocorrelate(&rotation, INTER_MARKER_STRIDE/8, hard, num_samples/8); 197 | 198 | /* Get where the deinterleaver expects the next marker to be */ 199 | deint_offset = deinterleave_expected_sync_offset(); 200 | 201 | /* Compute the delta between the expected marker position and the 202 | * one found by the correlator */ 203 | offset = (offset - deint_offset + INTER_MARKER_INTERSAMPS + 1) % INTER_MARKER_STRIDE; 204 | offset = offset > INTER_MARKER_STRIDE/2 ? offset-INTER_MARKER_STRIDE : offset; 205 | 206 | /* If the offset is positive, read more 207 | * bits to get $num_samples valid samples. If the offset is negative, 208 | * copy the last few bytes into the local cache */ 209 | if (offset > 0) { 210 | if (!read(dst+num_samples, offset)) return 1; 211 | } else { 212 | memcpy(from_prev, dst+num_samples+offset, -offset); 213 | } 214 | 215 | /* Correct rotation for these samples */ 216 | soft_derotate(dst, num_samples+offset, rotation); 217 | 218 | /* Deinterleave */ 219 | deinterleave(dst, dst+offset, len); 220 | offset = offset < 0 ? -offset : 0; 221 | } 222 | 223 | return 0; 224 | } 225 | -------------------------------------------------------------------------------- /decode.h: -------------------------------------------------------------------------------- 1 | #ifndef decode_h 2 | #define decode_h 3 | 4 | #include 5 | #include 6 | #include "protocol/mpdu.h" 7 | 8 | #define CADU_SOFT_CHUNK 2048 /* Bytes per read, affects interleaving. A higher 9 | number means more stable synchronization, but 10 | when the sync is off, a lot of bytes will be 11 | lost. Lower number means better recovery from 12 | phase inversions and such, but makes locking 13 | on to a weak signal harder */ 14 | 15 | typedef enum { 16 | EOF_REACHED=0, NOT_READY, MPDU_READY, STATS_ONLY 17 | } DecoderState; 18 | 19 | /** 20 | * Initialize the decoder's internal structures 21 | * 22 | * @param diffcoded 1 if the samples are differentially coded, 0 otherwise 23 | * @param interleaved 1 if the samples are interleaved (80k mode), 0 otherwise 24 | */ 25 | void decode_init(int diffcoded, int interleaved); 26 | 27 | /** 28 | * Fetch a CADU usinge the given function pointer, and decode all the valid 29 | * MPDUs in it. 30 | * 31 | * @param dst pointer to the destination MPDU buffer 32 | * @param read_samples function to use to fetch new soft samples 33 | * 34 | * @return EOF_REACHED if a call to read_samples returned 0 bytes 35 | * NOT_READY if more processing is required before a MPDU is ready 36 | * MPDU_READY if dst was updated with a new MPDU 37 | * STATS_ONLY if no MPDU was read, but the internal statistics were 38 | * updated 39 | * 40 | */ 41 | DecoderState decode_soft_cadu(Mpdu *dst, int(*read_samples)(int8_t *dst, size_t len)); 42 | 43 | 44 | /** 45 | * Various accessors to private decoder data: Reed-solomon errors, average 46 | * Viterbi cost, VCDU sequence number 47 | */ 48 | int decode_get_rs(); 49 | int decode_get_vit(); 50 | uint32_t decode_get_vcdu_seq(); 51 | 52 | #endif /* decode_h */ 53 | -------------------------------------------------------------------------------- /deinterleave/deinterleave.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "deinterleave.h" 4 | 5 | static int8_t _deint[INTER_BRANCH_COUNT * INTER_BRANCH_COUNT * INTER_BRANCH_DELAY]; 6 | static int _cur_branch = 0; 7 | static int _offset = 0; 8 | 9 | void 10 | deinterleave(int8_t *dst, const int8_t *src, size_t len) 11 | { 12 | int delay, write_idx, read_idx; 13 | size_t i; 14 | 15 | read_idx = (_offset + INTER_BRANCH_COUNT*INTER_BRANCH_DELAY) % sizeof(_deint); 16 | assert(len < sizeof(_deint)); 17 | 18 | /* Write bits to the deinterleaver */ 19 | for (i=0; i0; len--) { 38 | *dst++ = _deint[read_idx]; 39 | read_idx = (read_idx + 1) % sizeof(_deint); 40 | } 41 | } 42 | 43 | size_t 44 | deinterleave_num_samples(size_t output_count) 45 | { 46 | int num_syncs; 47 | 48 | if (!output_count) return 0; 49 | 50 | num_syncs = (_cur_branch ? 0 : 1) 51 | + (output_count - (INTER_MARKER_INTERSAMPS - _cur_branch) + INTER_MARKER_INTERSAMPS-1) 52 | / INTER_MARKER_INTERSAMPS; 53 | 54 | return output_count + 8*num_syncs; 55 | } 56 | 57 | int 58 | deinterleave_expected_sync_offset() 59 | { 60 | return _cur_branch ? INTER_MARKER_INTERSAMPS - _cur_branch : 0; 61 | } 62 | -------------------------------------------------------------------------------- /deinterleave/deinterleave.h: -------------------------------------------------------------------------------- 1 | #ifndef deinterleave_h 2 | #define deinterleave_h 3 | 4 | #include 5 | #include 6 | 7 | #define INTER_MARKER 0x27 8 | #define INTER_MARKER_STRIDE 80 9 | #define INTER_MARKER_INTERSAMPS (INTER_MARKER_STRIDE - 8) 10 | 11 | #define INTER_BRANCH_COUNT 36 12 | #define INTER_BRANCH_DELAY 2048 13 | 14 | #define INTER_SIZE(x) (x*10/9+8) 15 | 16 | /** 17 | * Deinterleave a set of soft samples, and extract a corresponding number of 18 | * bits from the deinterleaver 19 | * 20 | * @param dst pointer to a buffer where the deinterleaved samples should be written 21 | * @param src pointer to the raw interleaved samples 22 | * @param len number of samples to read. len*72/80 bits will be written 23 | */ 24 | void deinterleave(int8_t *dst, const int8_t *src, size_t len); 25 | 26 | /** 27 | * Compute the number of samples to write into the deinterleaver in order to 28 | * obtain a certain number of samples from it 29 | * 30 | * @param output_count desired number of deinterleaved samples 31 | * @return number of samples to write in order to extract $output_count bits 32 | */ 33 | size_t deinterleave_num_samples(size_t output_count); 34 | 35 | /** 36 | * Get where the deinterleaver expects the next marker to be 37 | * 38 | * @return number of samples expected before the synchronization marker 39 | */ 40 | int deinterleave_expected_sync_offset(); 41 | 42 | #endif /* deinterleave_h */ 43 | -------------------------------------------------------------------------------- /diffcode/diffcode.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "diffcode.h" 4 | #include "math/int.h" 5 | 6 | static inline int8_t signsqrt(int x); 7 | static int _prev_i, _prev_q; 8 | 9 | void 10 | diff_decode(int8_t *soft_cadu, size_t len) 11 | { 12 | size_t i; 13 | int x, y, tmpi, tmpq; 14 | 15 | tmpq = soft_cadu[0]; 16 | tmpi = soft_cadu[1]; 17 | 18 | soft_cadu[0] = signsqrt(tmpq * _prev_q); 19 | soft_cadu[1] = signsqrt(-tmpi * _prev_i); 20 | 21 | for (i=2; i 0) ? int_sqrt(x) : -int_sqrt(-x); 40 | } 41 | -------------------------------------------------------------------------------- /diffcode/diffcode.h: -------------------------------------------------------------------------------- 1 | #ifndef diffcode_h 2 | #define diffcode_h 3 | 4 | #include 5 | #include 6 | 7 | void diff_decode(int8_t *soft_cadu, size_t len); 8 | 9 | #endif /* diffcode_h */ 10 | -------------------------------------------------------------------------------- /ecc/descramble.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "descramble.h" 3 | #include "utils.h" 4 | 5 | static uint8_t _noise[NOISE_PERIOD]; 6 | 7 | void 8 | descramble_init() 9 | { 10 | int i, j; 11 | uint8_t state, newbit; 12 | 13 | state = 0xFF; 14 | for (i=0; i> 7 & 0x1) ^ (state >> 5 & 0x1) ^ (state >> 3 & 0x1) ^ 18 | (state >> 0 & 0x1); 19 | 20 | _noise[i] = _noise[i] << 1 | (state & 0x1); 21 | state = (state >> 1) | (newbit << 7); 22 | } 23 | } 24 | } 25 | 26 | void 27 | descramble(Cadu *c) 28 | { 29 | int i; 30 | uint8_t *const cadu_data = (uint8_t*)(&c->data); 31 | 32 | for (i=0; i 2 | #include 3 | #include 4 | #include "protocol/vcdu.h" 5 | #include "rs.h" 6 | #include "utils.h" 7 | 8 | static int fix_block(uint8_t *data); 9 | 10 | static uint8_t gfmul(uint8_t x, uint8_t y); 11 | static uint8_t gfdiv(uint8_t x, uint8_t y); 12 | static uint8_t gfpow(uint8_t x, int exp); 13 | static void poly_deriv(uint8_t *dst, const uint8_t *poly, int len); 14 | static uint8_t poly_eval(const uint8_t *poly, uint8_t x, int len); 15 | static void poly_mul(uint8_t *dst, const uint8_t *poly1, const uint8_t *poly2, int len_1, int len_2); 16 | 17 | static uint8_t _alpha[RS_N+1]; 18 | static uint8_t _logtable[RS_N+1]; 19 | static uint8_t _gaproot[RS_N]; 20 | static uint8_t _zeroes[RS_T]; 21 | 22 | void 23 | rs_init() 24 | { 25 | int i, tmp; 26 | int exp; 27 | 28 | _alpha[0] = 1; 29 | _logtable[1] = 0; 30 | /* Not entirely sure what's going on here, but the logtable doesn't have 31 | * this number anywhere, and setting log[0] = 183 fixes the _gaproot error */ 32 | _logtable[0] = 183; 33 | 34 | /* Initialize the exponent and logarithm tables */ 35 | for (i=1; i RS_N ? tmp ^ GEN_POLY : tmp); 38 | _alpha[i] = tmp; 39 | _logtable[tmp] = i; 40 | } 41 | 42 | _logtable[255] = 183; 43 | _alpha[255] = 0; 44 | 45 | /* Compute the gap'th log */ 46 | for (i=0; i=0; len--) { 189 | ret = gfmul(ret, x) ^ poly[len]; 190 | } 191 | 192 | return ret; 193 | } 194 | 195 | static void 196 | poly_deriv(uint8_t *dst, const uint8_t *poly, int len) 197 | { 198 | int i, j; 199 | 200 | for (i=1; i 5 | #include "protocol/vcdu.h" 6 | 7 | #define RS_N 255 8 | #define RS_K 223 9 | #define RS_T (RS_N - RS_K) 10 | #define RS_T2 (RS_T / 2) 11 | #define GEN_POLY 0x187 12 | #define FIRST_ROOT 112 13 | #define ROOT_SKIP 11 14 | #define INTERLEAVING 4 15 | 16 | /** 17 | * Initialize the Reed-Solomon decoder 18 | */ 19 | void rs_init(); 20 | 21 | /** 22 | * Attempt to fix the data inside a VCDU. 23 | * 24 | * @param c the VCDU to error correct. If all errors can be corrected, bytes 25 | * will be modified in-place. 26 | * @return -1 errors could not be corrected 27 | * >=0 number of errors corrected. The VCDU argument can be assumed to 28 | * be free of any errors in this case. 29 | */ 30 | int rs_fix(Vcdu *c); 31 | 32 | #endif /* rs_h */ 33 | -------------------------------------------------------------------------------- /ecc/viterbi.c: -------------------------------------------------------------------------------- 1 | #ifdef __ARM_NEON 2 | #include 3 | #endif 4 | #include 5 | #include 6 | #ifdef __ARM_FEATURE_SIMD32 7 | #include "math/arm_simd32.h" 8 | #endif 9 | #include "protocol/cadu.h" 10 | #include "utils.h" 11 | #include "viterbi.h" 12 | 13 | #define NEXT_DEPTH(x) (((x) + 1) % LEN(_prev)) 14 | #define PREV_DEPTH(x) (((x) - 1 + LEN(_prev)) % LEN(_prev)) 15 | #define BETTER_METRIC(x, y) ((x) > (y)) /* Higher metric is better */ 16 | #define POLY_TOP_BITS ((G1 >> (K-1) & 1) << 1 | (G2 >> (K-1))) 17 | #define TWIN_METRIC(metric, x, y) (\ 18 | POLY_TOP_BITS == 0x0 ? (metric) : \ 19 | POLY_TOP_BITS == 0x1 ? (metric)-2*(x) : \ 20 | POLY_TOP_BITS == 0x2 ? (metric)-2*(y) : (-metric)) 21 | 22 | static int parity(uint32_t word); 23 | static int metric(int x, int y, int coding); 24 | static void update_metrics(int8_t x, int8_t y, int depth); 25 | static void backtrace(uint8_t *out, uint8_t state, int depth, int bitskip, int bitcount); 26 | 27 | /* Backend arrays accessed via pointers defined below */ 28 | static int16_t _raw_metrics[NUM_STATES]; 29 | static int16_t _raw_next_metrics[NUM_STATES]; 30 | 31 | static uint8_t _output_lut[NUM_STATES]; /* Encoder output given a state */ 32 | static int16_t *_metrics, *_next_metrics; /* Pointers to current and previous metrics for each state */ 33 | static uint8_t _prev[MEM_DEPTH][NUM_STATES/2]; /* Trellis diagram (pairs of states share the same predecessor) */ 34 | static int _depth; /* Current memory depth in the trellis array */ 35 | 36 | uint32_t 37 | conv_encode_u32(uint64_t *output, uint32_t state, uint32_t data) 38 | { 39 | int i; 40 | uint8_t tmp; 41 | 42 | *output = 0; 43 | for (i=31; i>=0; i--) { 44 | /* Compute new state */ 45 | state = ((state >> 1) | ((data >> i) << (K-1))) & (NUM_STATES - 1); 46 | 47 | /* Compute output */ 48 | tmp = parity(state & G1) << 1 | parity(state & G2); 49 | *output |= (uint64_t)tmp << (i<<1); 50 | } 51 | 52 | return state; 53 | } 54 | 55 | void 56 | viterbi_init() 57 | { 58 | int i, input, state, next_state, output; 59 | 60 | /* Precompute the output given a state and an input */ 61 | for (state=0; state> 1) | (input << (K-1)); 64 | output = parity(next_state & G1) << 1 | parity(next_state & G2); 65 | 66 | /* The ARM SIMD32 optimized algorithm stores the local metrics in a 67 | * single uint32_t, so the output computed here is used as a shift value 68 | * rather than an index in an array. For this reason, [0, 1, 2, 3] 69 | * indices become [0, 8, 16, 24] shifts */ 70 | #if defined(__ARM_FEATURE_SIMD32) && !defined(__ARM_NEON) 71 | _output_lut[state] = output << 3; 72 | #else 73 | _output_lut[state] = output; 74 | #endif 75 | } 76 | 77 | /* Initialize current Viterbi depth */ 78 | _depth = 0; 79 | 80 | /* Initialize state metrics in the backtrack memory */ 81 | for (i=0; i>3))); 101 | 102 | total_metric = 0; 103 | for(; bytecount > 0; bytecount -= MEM_BACKTRACE >> 3) { 104 | /* Viterbi forward step */ 105 | for (i=MEM_START; i<(int)MEM_DEPTH; i++) { 106 | _depth = NEXT_DEPTH(_depth); 107 | 108 | y = *soft_cadu++; 109 | x = *soft_cadu++; 110 | 111 | update_metrics(-x, -y, _depth); 112 | } 113 | 114 | /* Find the state with the best metric */ 115 | best_state = 0; 116 | best_metric = _metrics[0]; 117 | for (i=1; i> 3; 135 | } 136 | 137 | return total_metric; 138 | } 139 | 140 | 141 | /* Static functions {{{ */ 142 | static int 143 | parity(uint32_t word) 144 | { 145 | /* From bit twiddling hacks */ 146 | word ^= (word >> 1); 147 | word ^= (word >> 2); 148 | word = (word & 0x11111111) * 0x11111111; 149 | return (word >> 28) & 0x1; 150 | } 151 | 152 | static void 153 | update_metrics(int8_t x, int8_t y, int depth) 154 | { 155 | int16_t *const metrics = _metrics; 156 | int16_t *const next_metrics = _next_metrics; 157 | uint8_t *const prev_state = _prev[depth]; 158 | uint8_t state; 159 | 160 | #if defined(__ARM_NEON) && (POLY_TOP_BITS == 0x3 || POLY_TOP_BITS == 0x0) 161 | const int8_t local_metrics[4] = {metric(x, y, 0), metric(x, y, 1), 162 | metric(x, y, 2), metric(x, y, 3)}; 163 | uint8_t start_states[] = {0, 2, 4, 6, 8, 10, 12, 14}; 164 | 165 | int16x8x2_t deint; 166 | int16x8_t best, next_metrics_vec; 167 | int16x8_t step_dup; 168 | 169 | uint16x8_t rev_compare; 170 | uint8x8_t states, prev; 171 | int8x8_t cost_vec; 172 | int16x8_t metrics_vec; 173 | 174 | /* Load initial states and local metrics. States is just a collection of all 175 | * the even states currently being considered */ 176 | states = vld1_u8(start_states); 177 | const int8x8_t local_metrics_lut = vld1_s8(local_metrics); 178 | 179 | for (state=0; state> 16; 259 | 260 | /* Compute the metrics of the ns0/ns1/ns2/ns3 transitions */ 261 | lm0 = (int8_t)((local_metrics >> _output_lut[state<<1]) & 0xFF); 262 | lm1 = TWIN_METRIC(lm0, x, y); /* metric to ns0/1 given in=1 */ 263 | lm2 = lm1; /* metric to ns2/3 given in=0 */ 264 | lm3 = TWIN_METRIC(lm2, x, y); /* metric to ns2/3 given in=1 */ 265 | 266 | /* Save new metrics */ 267 | lms = (lm0<<16) + lm2; 268 | *(uint32_t*)&next_metrics[ns0] = __sadd16(best01_23, lms); 269 | lms = POLY_TOP_BITS == 0x00 ? lms 270 | : POLY_TOP_BITS == 0x03 ? (uint32_t)__ssub16(0, (uint32_t)lms) 271 | : (uint32_t)((lm1<<16) + lm3); 272 | *(uint32_t*)&next_metrics[ns1] = __sadd16(best01_23, lms); 273 | } 274 | #else 275 | 276 | #if defined(__ARM_NEON) && !(POLY_TOP_BITS == 0x3 || POLY_TOP_BITS == 0x0) 277 | #warn "NEON acceleration unimplemented for the given G1/G2, using default implementation" 278 | #endif 279 | const int local_metrics[4] = {metric(x, y, 0), metric(x, y, 1), 280 | metric(x, y, 2), metric(x, y, 3)}; 281 | int16_t metric0, metric1, metric2, metric3, best01, best23; 282 | int16_t lm0, lm1, lm2, lm3; 283 | uint8_t ns0, ns1, ns2, ns3, prev01, prev23; 284 | 285 | for (state=0; state 0; bitskip--) { 343 | state = _prev[depth][state & ~(1<<(K-1))]; 344 | depth = PREV_DEPTH(depth); 345 | } 346 | 347 | /* Preemptively advance out: bits are written in reverse order */ 348 | bytecount = bitcount >> 3; 349 | out += bytecount; 350 | 351 | /* Backtrace while writing bits */ 352 | for (;bytecount > 0; bytecount--) { 353 | tmp = 0; 354 | /* Process each byte separately */ 355 | for (i=0; i<8; i++) { 356 | tmp |= (state >> (K-1)) << i; 357 | state = _prev[depth][state & ~(1<<(K-1))]; 358 | depth = PREV_DEPTH(depth); 359 | } 360 | 361 | /* Copy byte to output, then go to the previous byte */ 362 | *--out = tmp; 363 | } 364 | } 365 | 366 | static inline int 367 | metric(int x, int y, int coding) 368 | { 369 | /* NOTE: metric is shifted down by 1 so that it can fit inside an int8_t, 370 | * which makes NEON acceleration so much better. The results are exactly the 371 | * same as far as I can tell, even though we're losing 1 bit of precision on 372 | * the metric, and the upside is that this makes the decoding ~10% faster */ 373 | return MAX(-128, MIN(127, (((coding >> 1) ? x : -x) + 374 | ((coding & 1) ? y : -y))>>1)); 375 | } 376 | /* }}} */ 377 | -------------------------------------------------------------------------------- /ecc/viterbi.h: -------------------------------------------------------------------------------- 1 | #ifndef viterbi_h 2 | #define viterbi_h 3 | 4 | #include 5 | #include "protocol/cadu.h" 6 | 7 | #define G1 0x79 /* Connection polynomial #1 */ 8 | #define G2 0x5B /* Connection polynomial #2 */ 9 | #define K 7 /* Constraint length */ 10 | #define NUM_STATES (1<> 3)) \ 21 | || (VITERBI_DELAY % (MEM_BACKTRACE >> 3)) 22 | #error "Incompatible MEM_BACKTRACE size" 23 | #endif 24 | 25 | /** 26 | * Convolutionally encode a 32-bit word given a starting state. The connection 27 | * polynomials used will be G1 and G2, and the constraint length will be K as 28 | * #define'd above. 29 | * 30 | * @param output pointer to the memory region where the encoded data should be 31 | * placed 32 | * @param state the starting internal state of the convolutional encoder 33 | * @param data the data to encode 34 | * @return the convolutional encoder's final state 35 | */ 36 | uint32_t conv_encode_u32(uint64_t *output, uint32_t state, uint32_t data); 37 | 38 | /** 39 | * Initialize the Viterbi decoder 40 | */ 41 | void viterbi_init(); 42 | 43 | /** 44 | * Decode soft symbols into bits using the Viterbi algorithm 45 | * 46 | * @param out pointer to the memory region where the decoded bytes should be 47 | * written to 48 | * @param in pointer to the soft symbols to feed to the decoder 49 | * @param bytecount number of bytes to write to the output. Must be 1/16th the 50 | * nmuber of valid soft symbols supplied to the decoder. 51 | * @return the total metric of the best path 52 | */ 53 | int viterbi_decode(uint8_t *out, int8_t *in, int bytecount); 54 | 55 | #endif /* viterbi_h */ 56 | -------------------------------------------------------------------------------- /jpeg/huffman.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "huffman.h" 4 | #include "utils.h" 5 | 6 | #define EOB 0x00 7 | 8 | static int get_dc_cat(uint16_t codeword); 9 | 10 | static const uint8_t _dc_prefix_size[12] = {2, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9}; 11 | static const uint8_t _ac_table_size[17] = {0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125}; 12 | static const uint8_t _ac_table[] = 13 | { 14 | 1, 2, 15 | 3, 16 | 0, 4, 17, 17 | 5, 18, 33, 18 | 49, 65, 19 | 6, 19, 81, 97, 20 | 7, 34, 113, 21 | 20, 50, 129, 145, 161, 22 | 8, 35, 66, 177, 193, 23 | 21, 82, 209, 240, 24 | 36, 51, 98, 114, 25 | 130, 26 | 9, 10, 22, 23, 24, 25, 26, 37, 38, 39, 40, 41, 42, 52, 53, 54, 55, 56, 57, 27 | 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 28 | 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, 131, 29 | 132, 133, 134, 135, 136, 137, 138, 146, 147, 148, 149, 150, 151, 152, 153, 30 | 154, 162, 163, 164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 31 | 183, 184, 185, 186, 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 32 | 212, 213, 214, 215, 216, 217, 218, 225, 226, 227, 228, 229, 230, 231, 232, 33 | 233, 234, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250 34 | }; 35 | 36 | int 37 | huffman_decode(int16_t (*dst)[8][8], const uint8_t *src, int count, int maxlen) 38 | { 39 | int i, j, r, runlength; 40 | unsigned int bit_idx; 41 | uint32_t dc_info, ac_info, ac_buf; 42 | int dc_category; 43 | uint16_t dc_extra_bits; 44 | uint16_t ac_category, ac_extra_bits; 45 | uint16_t first_coeff; 46 | int dc_sign, dc_coeff; 47 | int ac_sign, ac_coeff; 48 | int ac_idx; 49 | int bytecount; 50 | 51 | bit_idx = 0; 52 | bytecount = 0; 53 | dc_coeff = 0; 54 | 55 | for (i=0; i>16)) < 0) return 0; 59 | dc_sign = (dc_info >> (31 - _dc_prefix_size[dc_category])) & 0x1; 60 | dc_extra_bits = (dc_info >> (31 - _dc_prefix_size[dc_category] - dc_category + 1)) & ((1<<(dc_category-1))-1); 61 | 62 | /* Update the DC coefficient if the category is nonzero */ 63 | if (dc_category) { 64 | dc_coeff += dc_extra_bits + (dc_sign ? (1<<(dc_category-1)) : 1-(1<<(dc_category))); 65 | } 66 | dst[i][0][0] = dc_coeff; 67 | 68 | bit_idx += _dc_prefix_size[dc_category] + dc_category; 69 | src += bit_idx/8; 70 | bytecount += bit_idx/8; 71 | bit_idx %= 8; 72 | 73 | /* Deal with corrupted data: sometimes a packet header is fine but the 74 | * data contains a bunch of zeroes, which all code run=0 size=1 AC 75 | * coefficients. This causes the Huffman decoder to consume all the 76 | * bytes in the MPDU, and then keep going past the end of the buffer, 77 | * unless this condition is checked. */ 78 | if (bytecount >= maxlen) { 79 | break; 80 | } 81 | 82 | /* Decompress AC coefficients */ 83 | for (r=1; r<64; r++) { 84 | ac_buf = read_bits(src, bit_idx, 32); 85 | 86 | /* Find the AC code in the AC Huffman table */ 87 | first_coeff = 0; 88 | ac_idx = 0; 89 | for (j=2; j<(int)LEN(_ac_table_size); j++) { 90 | ac_info = ac_buf >> (32 - j); 91 | 92 | /* If the coefficient belongs to this range, decompress it */ 93 | if (ac_info - first_coeff < _ac_table_size[j]) { 94 | ac_info = _ac_table[ac_idx + ac_info - first_coeff]; 95 | break; 96 | } 97 | 98 | first_coeff = (first_coeff + _ac_table_size[j]) << 1; 99 | ac_idx += _ac_table_size[j]; 100 | } 101 | bit_idx += j; 102 | 103 | if (ac_info == EOB) { 104 | for (; r<64; r++) { 105 | dst[i][r/8][r%8] = 0; 106 | } 107 | } else { 108 | /* Split the codeword into runlength and bit count */ 109 | runlength = ac_info >> 4 & 0x0F; 110 | ac_category = ac_info & 0x0F; 111 | 112 | ac_sign = read_bits(src, bit_idx, 1); 113 | ac_extra_bits = read_bits(src, bit_idx+1, ac_category-1); 114 | 115 | if (ac_category > 0) { 116 | ac_coeff = ac_extra_bits + (ac_sign ? ((1<<(ac_category-1))) : 1-(1<<(ac_category))); 117 | } else { 118 | ac_coeff = 0; 119 | } 120 | 121 | /* Write runlength zeroes */ 122 | for (; runlength>0 && r<64-1; runlength--, r++) { 123 | dst[i][r/8][r%8] = 0; 124 | } 125 | 126 | /* Write coefficient */ 127 | dst[i][r/8][r%8] = ac_coeff; 128 | bit_idx += ac_category; 129 | } 130 | 131 | src += bit_idx/8; 132 | bytecount += bit_idx/8; 133 | bit_idx %= 8; 134 | if (bytecount >= maxlen) { 135 | break; 136 | } 137 | } 138 | 139 | if (bytecount >= maxlen) { 140 | break; 141 | } 142 | } 143 | 144 | //assert(bytecount < maxlen); 145 | 146 | return 0; 147 | } 148 | 149 | /* Static functions {{{ */ 150 | static int 151 | get_dc_cat(uint16_t codeword) 152 | { 153 | if (codeword >> 14 == 0) return 0; 154 | if (codeword >> 13 < 7) return (codeword >> 13) - 1; 155 | if (codeword >> 12 < 0xF) return 6; 156 | if (codeword >> 11 < 0x1F) return 7; 157 | if (codeword >> 10 < 0x3F) return 8; 158 | if (codeword >> 9 < 0x7F) return 9; 159 | if (codeword >> 8 < 0xFF) return 10; 160 | if (codeword >> 7 < 0x1FF) return 11; 161 | return -1; 162 | } 163 | /* }}} */ 164 | -------------------------------------------------------------------------------- /jpeg/huffman.h: -------------------------------------------------------------------------------- 1 | #ifndef huffman_h 2 | #define huffman_h 3 | 4 | #include 5 | 6 | /** 7 | * Decompress Huffman-compressed data into 8x8 blocks 8 | * 9 | * @param dst pointer to 8x8 blocks to write the decompressed data to 10 | * @param src pointer to the compressed data 11 | * @param count number of blocks to decompress 12 | * @param maxlen maximum number of compressed bytes available 13 | * @param dc the DC coefficient for the previous MCU (0 for he first one) 14 | * @return -1 if an error is encountered while decompressing 15 | * 0 on success 16 | */ 17 | int huffman_decode(int16_t (*dst)[8][8], const uint8_t *src, int count, int maxlen); 18 | 19 | #endif /* huffman_h */ 20 | -------------------------------------------------------------------------------- /jpeg/jpeg.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "jpeg.h" 3 | #include "utils.h" 4 | #define Q_SHIFT 14 5 | #define Q_2 0x2000 /* 1/2, Q14 */ 6 | #define Q_1SQRT2 0x2d41 /* 1/sqrt(2), Q14 */ 7 | 8 | static void unzigzag(int16_t block[8][8]); 9 | static int quantization(int quality, int x, int y); 10 | static void dequantize(int16_t block[8][8], int quality); 11 | static void inverse_dct(uint8_t dst[8][8], int16_t src[8][8]); 12 | static int16_t qmul(int32_t x, int32_t y); 13 | 14 | /* Quantization table, standard 50% quality JPEG */ 15 | static const uint8_t _quant[8][8] = 16 | { 17 | {16, 11, 10, 16, 24, 40, 51, 61}, 18 | {12, 12, 14, 19, 26, 58, 60, 55}, 19 | {14, 13, 16, 24, 40, 57, 69, 56}, 20 | {14, 17, 22, 29, 51, 87, 80, 62}, 21 | {18, 22, 37, 56, 68, 109,103,77}, 22 | {24, 35, 55, 64, 81, 104,113,92}, 23 | {49, 64, 78, 87, 103,121,120,101}, 24 | {72, 92, 95, 98, 112,100,103,99} 25 | }; 26 | 27 | /* 8x8 reverse zigzag pattern */ 28 | static const uint8_t _zigzag_lut[64] = 29 | { 30 | 0, 1, 8, 16, 9, 2, 3, 10, 31 | 17, 24, 32, 25, 18, 11, 4, 5, 32 | 12, 19, 26, 33, 40, 48, 41, 34, 33 | 27, 20, 13, 6, 7, 14, 21, 28, 34 | 35, 42, 49, 56, 57, 50, 43, 36, 35 | 29, 22, 15, 23, 30, 37, 44, 51, 36 | 58, 59, 52, 45, 38, 31, 39, 46, 37 | 53, 60, 61, 54, 47, 55, 62, 63 38 | }; 39 | 40 | /* Cosine lookup table, Q14 */ 41 | static const int16_t _cos_lut[8][8] = 42 | { 43 | {0x4000, 0x3ec5, 0x3b21, 0x3537, 0x2d41, 0x238e, 0x187e, 0x0c7c}, 44 | {0x4000, 0x3537, 0x187e, (int16_t)0xf384, (int16_t)0xd2bf, (int16_t)0xc13b, (int16_t)0xc4df, (int16_t)0xdc72}, 45 | {0x4000, 0x238e, (int16_t)0xe782, (int16_t)0xc13b, (int16_t)0xd2bf, 0x0c7c, 0x3b21, 0x3537}, 46 | {0x4000, 0x0c7c, (int16_t)0xc4df, (int16_t)0xdc72, 0x2d41, 0x3537, (int16_t)0xe782, (int16_t)0xc13b}, 47 | {0x4000, (int16_t)0xf384, (int16_t)0xc4df, 0x238e, 0x2d41, (int16_t)0xcac9, (int16_t)0xe782, 0x3ec5}, 48 | {0x4000, (int16_t)0xdc72, (int16_t)0xe782, 0x3ec5, (int16_t)0xd2bf, (int16_t)0xf384, 0x3b21, (int16_t)0xcac9}, 49 | {0x4000, (int16_t)0xcac9, 0x187e, 0x0c7c, (int16_t)0xd2bf, 0x3ec5, (int16_t)0xc4df, 0x238e}, 50 | {0x4000, (int16_t)0xc13b, 0x3b21, (int16_t)0xcac9, 0x2d41, (int16_t)0xdc72, 0x187e, (int16_t)0xf384}, 51 | }; 52 | 53 | void 54 | jpeg_decode(uint8_t dst[8][8], int16_t src[8][8], int q) 55 | { 56 | static int last_q; 57 | 58 | /* Try to generate a semi-valid strip if q=0 (happens on overflows of M2) */ 59 | q = q > 0 ? q : last_q; 60 | last_q = q; 61 | 62 | unzigzag(src); 63 | dequantize(src, q); 64 | inverse_dct(dst, src); 65 | } 66 | 67 | /* Static functions {{{ */ 68 | static void 69 | unzigzag(int16_t block[8][8]) 70 | { 71 | int i, j; 72 | int16_t tmp[64]; 73 | 74 | for (i=0; i<8; i++) { 75 | for (j=0; j<8; j++) { 76 | tmp[_zigzag_lut[i*8+j]] = block[i][j]; 77 | } 78 | } 79 | for (i=0; i<8; i++) { 80 | for (j=0; j<8; j++) { 81 | block[i][j] = tmp[i*8+j]; 82 | } 83 | } 84 | } 85 | 86 | static void 87 | dequantize(int16_t block[8][8], int quality) 88 | { 89 | int i, j; 90 | 91 | for (i=0; i<8; i++) { 92 | for (j=0; j<8; j++) { 93 | block[i][j] = ((int32_t)block[i][j] * quantization(quality, i, j)); 94 | } 95 | } 96 | } 97 | 98 | static void 99 | inverse_dct(uint8_t dst[8][8], int16_t src[8][8]) 100 | { 101 | int i, j, u, v; 102 | int16_t alpha; 103 | int32_t work[8][8]; 104 | int32_t row[8]; 105 | 106 | memset(work, 0, sizeof(work)); 107 | 108 | /* IDCT: cols */ 109 | for (i=0; i<8; i++) { 110 | alpha = i ? 0x4000 : Q_1SQRT2; 111 | for (j=0; j<8; j++) { 112 | for (u=0; u<8; u++) { 113 | work[j][u] += qmul(alpha, _cos_lut[u][i]) * src[j][i]; 114 | } 115 | } 116 | } 117 | 118 | /* IDCT: rows */ 119 | for (j=0; j<8; j++) { 120 | memset(row, 0, sizeof(row)); 121 | 122 | for (i=0; i<8; i++) { 123 | alpha = i ? 0x4000 : Q_1SQRT2; 124 | for (v=0; v<8; v++) { 125 | row[v] += ((int64_t)work[i][j] * qmul(alpha, _cos_lut[v][i])) >> Q_SHIFT; 126 | } 127 | } 128 | 129 | /* Rescale from -512...512 to 0..256 */ 130 | for (i=0; i<8; i++) { 131 | dst[i][j] = MAX(0, MIN(255, ((row[i]/4) >> Q_SHIFT) + 128)); 132 | } 133 | } 134 | 135 | } 136 | 137 | static int 138 | quantization(int quality, int x, int y) 139 | { 140 | int compr_ratio; 141 | if (quality < 50) { 142 | compr_ratio = 5000 / quality; 143 | } else { 144 | compr_ratio = 200 - 2*quality; 145 | } 146 | 147 | return MAX(1, (((int)_quant[x][y] * compr_ratio / 50) + 1) / 2); 148 | } 149 | 150 | static int16_t 151 | qmul(int32_t x, int32_t y) 152 | { 153 | const int32_t result = x * y; 154 | return result >> Q_SHIFT; 155 | } 156 | /* }}} */ 157 | -------------------------------------------------------------------------------- /jpeg/jpeg.h: -------------------------------------------------------------------------------- 1 | #ifndef jpeg_h 2 | #define jpeg_h 3 | #include 4 | 5 | /** 6 | * Decode an 8x8 jpeg block into an 8x8 image. 7 | * 8 | * @param dst pointer to the destination 0x0 pixel array 9 | * @param src pointer to the encoded jpeg block 10 | * @param q the quality factor used when encoding the block 11 | */ 12 | void jpeg_decode(uint8_t dst[8][8], int16_t src[8][8], int q); 13 | 14 | 15 | #endif /* jpeg_h */ 16 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "channel.h" 7 | #include "decode.h" 8 | #include "output/bmp_out.h" 9 | #include "parser/mcu_parser.h" 10 | #include "raw_channel.h" 11 | #include "utils.h" 12 | 13 | #ifdef USE_PNG 14 | #include "output/png_out.h" 15 | #endif 16 | 17 | #define CLR "\033[2K\r" 18 | 19 | #define NUM_CHANNELS 3 20 | #define MAX_FNAME_LEN 256 21 | #define SHORTOPTS "7a:bBdhio:qstv" 22 | 23 | static int read_wrapper(int8_t *src, size_t len); 24 | static int preferred_channel(int apid); 25 | static int parse_apids(int *apids, char *optarg); 26 | static void process_mpdu(Mpdu *mpdu, Channel *ch[NUM_CHANNELS], RawChannel *apid_70, int quiet); 27 | static void write_stat_and_close(FILE *fd); 28 | static void sigint_handler(int val); 29 | 30 | static FILE *_soft_file; 31 | static uint64_t _first_time, _last_time; 32 | static volatile int _running; 33 | 34 | static struct option longopts[] = { 35 | { "70", 0, NULL, '7' }, 36 | { "apid", 1, NULL, 'a' }, 37 | { "batch", 0, NULL, 'B' }, 38 | { "batch-alt",0,NULL, 'b' }, 39 | { "diff", 0, NULL, 'd' }, 40 | { "help", 0, NULL, 'h' }, 41 | { "int", 0, NULL, 'i' }, 42 | { "output", 1, NULL, 'o' }, 43 | { "quiet", 0, NULL, 'q' }, 44 | { "split", 0, NULL, 's' }, 45 | { "statfile",0, NULL, 't' }, 46 | { "version", 0, NULL, 'v' }, 47 | }; 48 | 49 | int 50 | main(int argc, char *argv[]) 51 | { 52 | char *input_fname, *output_fname=NULL, *extension; 53 | char split_fname[MAX_FNAME_LEN], stat_fname[MAX_FNAME_LEN], apid_70_fname[MAX_FNAME_LEN]; 54 | char auto_out_fname[MAX_FNAME_LEN]; 55 | size_t file_len; 56 | int mpdu_count=0, height; 57 | float percent; 58 | int i, j, c, retval; 59 | int duplicate; 60 | uint32_t last_vcdu_seq=0; 61 | Mpdu mpdu; 62 | Channel ch_instance[NUM_CHANNELS], *ch[NUM_CHANNELS]; 63 | RawChannel ch_apid_70; 64 | DecoderState status; 65 | void *img_out; 66 | 67 | /* Pointers to functions to initialize, write and close images (will point 68 | * to different functions based on the output format) */ 69 | int (*img_init)(void **img, const char *fname, int width, int height, int mono); 70 | int (*img_write_rgb)(void *img, Channel *red, Channel *green, Channel *blue); 71 | int (*img_write_mono)(void *img, Channel *ch); 72 | int (*img_finalize)(void *img); 73 | 74 | /* Default parameters {{{ */ 75 | int apids[NUM_CHANNELS] = {-1, -1, -1}; 76 | int diffcoded = 0; 77 | int interleaved = 0; 78 | int batch = 0; 79 | int split_output = 0; 80 | int write_stat = 0; 81 | int write_apid_70 = 0; 82 | int quiet = 0; 83 | /* }}} */ 84 | /* Parse command-line options {{{ */ 85 | optind = 0; 86 | while ((c = getopt_long(argc, argv, SHORTOPTS, longopts, NULL)) != -1) { 87 | switch (c) { 88 | case '7': 89 | write_apid_70 = 1; 90 | break; 91 | case 'a': 92 | if (parse_apids(apids, optarg)) { 93 | fprintf(stderr, "Invalid APIDs specified\n"); 94 | usage(argv[0]); 95 | exit(1); 96 | } 97 | break; 98 | case 'b': 99 | case 'B': 100 | batch = 1; 101 | break; 102 | case 'o': 103 | output_fname = optarg; 104 | break; 105 | case 'd': 106 | diffcoded = 1; 107 | break; 108 | case 'i': 109 | interleaved = 1; 110 | break; 111 | case 's': 112 | split_output = 1; 113 | break; 114 | case 'q': 115 | quiet = 1; 116 | break; 117 | case 'h': 118 | usage(argv[0]); 119 | return 0; 120 | case 'v': 121 | version(); 122 | return 0; 123 | case 't': 124 | write_stat = 1; 125 | break; 126 | default: 127 | usage(argv[0]); 128 | return 1; 129 | } 130 | } 131 | 132 | if (argc - optind < 1) { 133 | usage(argv[0]); 134 | return 1; 135 | } 136 | 137 | input_fname = argv[optind]; 138 | 139 | /* If no output filename is specified, make one up based on the input file */ 140 | if (!output_fname) { 141 | /* If the input is stdin, use a generic output filename */ 142 | if (!strcmp(input_fname, "-")) { 143 | gen_fname(auto_out_fname, LEN(auto_out_fname)); 144 | } else { 145 | 146 | /* Find where extension starts */ 147 | for (i = strlen(input_fname); i>=0 && input_fname[i] != '.'; i--); 148 | if (i < 0) i = strlen(input_fname); 149 | 150 | if (i > MAX_FNAME_LEN - 4) { 151 | fprintf(stderr, "Automatic filename too long, please specify a different filename\n"); 152 | usage(argv[0]); 153 | return 1; 154 | } 155 | 156 | /* Copy input file name without extension, add ".bmp" extension to it */ 157 | memcpy(auto_out_fname, input_fname, i); 158 | sprintf(auto_out_fname + i, ".bmp"); 159 | } 160 | 161 | output_fname = auto_out_fname; 162 | } 163 | 164 | img_init = bmp_init; 165 | img_write_rgb = bmp_write_rgb; 166 | img_write_mono = bmp_write_mono; 167 | img_finalize = bmp_finalize; 168 | #ifdef USE_PNG 169 | /* If the image extension is .png, change image write pointers */ 170 | if (!strncmp(output_fname+strlen(output_fname)-4, ".png", 4)) { 171 | img_init = png_init; 172 | img_write_rgb = png_write_rgb; 173 | img_write_mono = png_write_mono; 174 | img_finalize = png_finalize; 175 | } 176 | #endif 177 | /* }}} */ 178 | 179 | /* Open input file */ 180 | if (!strcmp(input_fname, "-")) { 181 | _soft_file = stdin; 182 | } else if (!(_soft_file = fopen(input_fname, "rb"))) { 183 | fprintf(stderr, "Could not open input file\n"); 184 | return 1; 185 | } 186 | 187 | /* Get file length */ 188 | fseek(_soft_file, 0, SEEK_END); 189 | file_len = MAX(0, ftell(_soft_file)); 190 | fseek(_soft_file, 0, SEEK_SET); 191 | 192 | /* Initialize channels, duping pointers when two APIDs are the same */ 193 | for (i=0; ioffset, MAX(ch[1]->offset, ch[2]->offset)) 264 | / (MCU_PER_LINE*8); 265 | 266 | if (!quiet) printf(batch ? "\n\n" : CLR); 267 | printf("MPDUs received: %d (%d lines)\n", mpdu_count, height); 268 | printf("Onboard time elapsed: %s\n", mpdu_time(_last_time - _first_time)); 269 | 270 | /* If at least one line was received, write output image(s) {{{ */ 271 | if (height) { 272 | if (split_output) { 273 | /* Separate file extension from the rest of the file name */ 274 | for (extension=output_fname + strlen(output_fname); *extension != '.' && extension > output_fname; extension--); 275 | if (extension == output_fname) { 276 | extension = output_fname + strlen(output_fname); 277 | } else { 278 | *extension++ = '\0'; 279 | } 280 | 281 | /* Write each channel separately */ 282 | for (i=0; iapid == ch[j]->apid) { 287 | duplicate = 1; 288 | break; 289 | } 290 | } 291 | if (duplicate) continue; 292 | 293 | /* Generate a filename for the current channel */ 294 | sprintf(split_fname, "%s_%02d%s%s", output_fname, ch[i]->apid, *extension ? "." : "", extension); 295 | printf("Saving channel to %s... ", split_fname); 296 | fflush(stdout); 297 | 298 | /* Dump channel to monochrome image */ 299 | retval = img_init(&img_out, split_fname, MCU_PER_LINE*8, height, 1); 300 | retval |= img_write_mono(img_out, ch[i]); 301 | retval |= img_finalize(img_out); 302 | 303 | if (retval) { 304 | printf("Failed!\n"); 305 | return 1; 306 | } 307 | 308 | /* Write .stat file if necessary */ 309 | if (write_stat) { 310 | sprintf(stat_fname, "%s_%02d.stat", output_fname, ch[i]->apid); 311 | write_stat_and_close(fopen(stat_fname, "wb")); 312 | } 313 | printf("Done.\n"); 314 | } 315 | 316 | } else { 317 | printf("Saving composite to %s... ", output_fname); 318 | fflush(stdout); 319 | 320 | /* Write composite RGB */ 321 | retval = img_init(&img_out, output_fname, MCU_PER_LINE*8, height, 0); 322 | retval |= img_write_rgb(img_out, ch[0], ch[1], ch[2]); 323 | retval |= img_finalize(img_out); 324 | 325 | if (retval) { 326 | printf("Failed!\n"); 327 | return 1; 328 | } 329 | printf("Done.\n"); 330 | 331 | if (write_stat) { 332 | sprintf(stat_fname, "%s.stat", output_fname); 333 | write_stat_and_close(fopen(stat_fname, "wb")); 334 | } 335 | } 336 | } 337 | /*}}}*/ 338 | 339 | /* Cleanup */ 340 | for (i=0; iapid != (int)apid; i++); 396 | if (i == NUM_CHANNELS) { 397 | i = preferred_channel(apid); 398 | if (ch[i]->apid < 0) { 399 | ch[i]->apid = apid; 400 | } else { 401 | for (i=0; iapid >= 0; i++); 402 | if (i == NUM_CHANNELS) break; 403 | ch[i]->apid = apid; 404 | } 405 | } 406 | if (i == NUM_CHANNELS || ch[i]->apid != (int)apid) break; 407 | 408 | 409 | /* AVHRR image data: decode JPEG into raw pixel data */ 410 | avhrr_decode(strip, &mpdu->data.mcu.avhrr, mpdu_len(mpdu)); 411 | 412 | /* Estimate number of lines lost compared to other channels based on 413 | * timestamps, and compensate for those */ 414 | if (ch[i]->mpdu_seq < 0) { 415 | lines_lost = (_last_time - _first_time) / MPDU_US_PER_LINE; 416 | ch[i]->mpdu_seq = (seq - MPDU_PER_PERIOD*lines_lost - 1 + MPDU_MAX_SEQ) % MPDU_MAX_SEQ; 417 | } 418 | 419 | /* Append decoded strip */ 420 | channel_append_strip(ch[i], strip, mpdu->data.mcu.avhrr.seq, seq); 421 | break; 422 | 423 | case 70: 424 | /* AVHRR calibration data: directly write to file */ 425 | raw_channel_write(apid_70, (uint8_t*)mpdu, 426 | sizeof(mpdu->id) + sizeof(mpdu->seq) + sizeof(mpdu->len) 427 | + sizeof(mpdu->data.time) + sizeof(mpdu->data.mcu.calib.data)); 428 | break; 429 | 430 | default: 431 | break; 432 | } 433 | 434 | if (first) first = 0; 435 | } 436 | 437 | static int 438 | parse_apids(int *apids, char *optarg) 439 | { 440 | int i, red, green, blue; 441 | 442 | if (sscanf(optarg, "%d,%d,%d", &red, &green, &blue) != 3) return 1; 443 | 444 | apids[0] = red; 445 | apids[1] = green; 446 | apids[2] = blue; 447 | 448 | for (i=0; i<3; i++) { 449 | if (apids[i] < 64 || apids[i] > 69) return 1; 450 | } 451 | 452 | return 0; 453 | } 454 | 455 | static int 456 | preferred_channel(int apid) 457 | { 458 | switch (apid) { 459 | case 64: return 2; 460 | case 65: return 1; 461 | case 66: return 0; 462 | case 67: return 1; 463 | case 68: return 0; 464 | case 69: return 2; 465 | default: return 0; 466 | } 467 | } 468 | 469 | static void 470 | write_stat_and_close(FILE *fd) 471 | { 472 | if (!fd) return; 473 | 474 | fprintf(fd, "%s\r\n", mpdu_time(_first_time)); 475 | fprintf(fd, "%s\r\n", mpdu_time(_last_time - _first_time)); 476 | fprintf(fd, "0\r\n"); /* Not sure what this is? */ 477 | fclose(fd); 478 | } 479 | 480 | static void 481 | sigint_handler(int val) 482 | { 483 | _running = 0; 484 | } 485 | -------------------------------------------------------------------------------- /math/arm_simd32.h: -------------------------------------------------------------------------------- 1 | #ifndef arm_simd32_h 2 | #define arm_simd32_h 3 | 4 | #include 5 | 6 | __attribute__((always_inline)) 7 | static inline int32_t 8 | __ssub16(int32_t x, int32_t y) 9 | { 10 | int32_t result; 11 | 12 | __asm volatile("ssub16 %0, %1, %2" : "=r"(result) 13 | : "r" (x), "r" (y)); 14 | return result; 15 | } 16 | 17 | __attribute__((always_inline)) 18 | static inline int32_t 19 | __sadd16(int32_t x, int32_t y) 20 | { 21 | int32_t result; 22 | 23 | __asm volatile("sadd16 %0, %1, %2" : "=r"(result) 24 | : "r" (x), "r" (y)); 25 | return result; 26 | } 27 | 28 | __attribute__((always_inline)) 29 | static inline uint32_t 30 | __sel(uint32_t x, uint32_t y) 31 | { 32 | uint32_t result; 33 | 34 | __asm volatile("sel %0, %1, %2" : "=r"(result) 35 | : "r" (x), "r" (y)); 36 | return result; 37 | } 38 | 39 | __attribute__((always_inline)) 40 | static inline uint32_t 41 | __pkhbt(uint32_t x, uint32_t y, uint32_t shift) 42 | { 43 | uint32_t result; 44 | 45 | __asm volatile("pkhbt %0, %1, %2, lsl %3" : "=r"(result) 46 | : "r" (x), "r" (y), "I" (shift)); 47 | return result; 48 | } 49 | 50 | __attribute__((always_inline)) 51 | static inline uint32_t 52 | __pkhtb(uint32_t x, uint32_t y, uint32_t shift) 53 | { 54 | uint32_t result; 55 | 56 | __asm volatile("pkhtb %0, %1, %2, asr %3" : "=r"(result) 57 | : "r" (x), "r" (y), "I" (shift)); 58 | return result; 59 | } 60 | 61 | __attribute__((always_inline)) 62 | static inline uint32_t 63 | __ror(uint32_t x, int amount) 64 | { 65 | uint32_t result; 66 | 67 | __asm volatile("ror %0, %1, %2" : "=r"(result) 68 | : "r" (x), "I" (amount)); 69 | return result; 70 | } 71 | 72 | #endif /* arm_simd32_h */ 73 | -------------------------------------------------------------------------------- /math/int.c: -------------------------------------------------------------------------------- 1 | #include "int.h" 2 | #include "utils.h" 3 | 4 | unsigned int 5 | int_sqrt(unsigned int x) 6 | { 7 | int i; 8 | const int x2 = x >> 1; 9 | int guess; 10 | 11 | guess = 1 << ((32-__builtin_clz(x)) >> 1); 12 | if (guess < 2) { 13 | return 0; 14 | } 15 | 16 | PRAGMA_UNROLL(4) 17 | for (i=0; i> 1) + x2/guess); 19 | } 20 | 21 | return guess; 22 | } 23 | -------------------------------------------------------------------------------- /math/int.h: -------------------------------------------------------------------------------- 1 | #ifndef math_int_h 2 | #define math_int_h 3 | 4 | #define SQRT_PRECISION 4 5 | 6 | /** 7 | * Fast integer square root 8 | * 9 | * @param x square root input 10 | * @return sqrt(x) rounded to the nearest integer 11 | */ 12 | unsigned int int_sqrt(unsigned int x); 13 | 14 | #endif /* math_int_h */ 15 | -------------------------------------------------------------------------------- /output/bmp_out.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "bmp_out.h" 5 | 6 | struct _bmpout { 7 | FILE *fd; 8 | unsigned int width, height; 9 | uint8_t *rgbrow; 10 | int ready; 11 | }; 12 | 13 | struct _bmphdr { 14 | /* Bitmap header */ 15 | char header[2]; 16 | uint32_t size; 17 | uint16_t reserved1, reserved2; 18 | uint32_t offset; 19 | 20 | /* DIB header, version BITMAPINFOHEADER */ 21 | uint32_t dib_size; 22 | int32_t width; 23 | int32_t height; 24 | uint16_t color_planes; 25 | uint16_t bpp; 26 | uint32_t compression; 27 | uint32_t bitmap_size; 28 | int32_t horiz_resolution; 29 | int32_t vert_resolution; 30 | uint32_t color_count; 31 | uint32_t important_color_count; 32 | }__attribute__((packed)); 33 | 34 | int 35 | bmp_init(void **_dst, const char *fname, int width, int height, int mono) 36 | { 37 | BmpOut **dst = (BmpOut**)_dst; 38 | const int dib_size = 40; 39 | BmpOut *bmp; 40 | struct _bmphdr header; 41 | int i; 42 | uint8_t color_entry[4]; 43 | 44 | 45 | *dst = NULL; 46 | if (!(bmp = malloc(sizeof(*bmp)))) return 1; 47 | *dst = bmp; 48 | bmp->ready = 0; 49 | 50 | if (!(bmp->fd = fopen(fname, "wb"))) return 1; 51 | if (!(bmp->rgbrow = malloc(3*width))) return 1; 52 | 53 | bmp->width = width; 54 | bmp->height = height; 55 | 56 | header.header[0] = 'B'; header.header[1] = 'M'; 57 | header.reserved1 = header.reserved2 = 0; 58 | header.compression = 0; 59 | header.color_planes = 1; 60 | header.color_count = 0; 61 | header.important_color_count = 0; 62 | header.horiz_resolution = header.vert_resolution = 0; 63 | 64 | header.dib_size = dib_size; 65 | header.bpp = mono ? 8 : 24; 66 | header.width = width; 67 | header.height = height; 68 | header.bitmap_size = width * height * header.bpp/8; 69 | header.size = header.bitmap_size; 70 | header.offset = sizeof(struct _bmphdr) + (mono ? 4*256: 0); 71 | 72 | /* Write BMP header */ 73 | fwrite(&header, sizeof(header), 1, bmp->fd); 74 | 75 | /* If monochrome, BMP must use a color table: write that too */ 76 | if (mono) { 77 | for (i=0; i<256; i++) { 78 | color_entry[0] = color_entry[1] = color_entry[2] = i; 79 | color_entry[3] = 0xFF; 80 | fwrite(&color_entry, 4, 1, bmp->fd); 81 | } 82 | } 83 | 84 | bmp->ready = 1; 85 | return 0; 86 | } 87 | 88 | int 89 | bmp_write_rgb(void *_bmp, Channel *red, Channel *green, Channel *blue) 90 | { 91 | BmpOut *bmp = (BmpOut*)_bmp; 92 | unsigned int i, col; 93 | uint8_t *rgbrow; 94 | unsigned int offset; 95 | 96 | if (!bmp || !bmp->ready) return 1; 97 | 98 | offset = (bmp->height-1)*bmp->width; 99 | 100 | for (i=0; iheight; i++) { 101 | rgbrow = bmp->rgbrow; 102 | 103 | for (col=0; colwidth; col++) { 104 | *rgbrow++ = (offset < blue->offset) ? blue->pixels[offset] : 0; 105 | *rgbrow++ = (offset < green->offset) ? green->pixels[offset] : 0; 106 | *rgbrow++ = (offset < red->offset) ? red->pixels[offset] : 0; 107 | offset++; 108 | } 109 | 110 | fwrite(bmp->rgbrow, 3*bmp->width, 1, bmp->fd); 111 | offset -= 2*bmp->width; 112 | } 113 | 114 | return 0; 115 | } 116 | 117 | int 118 | bmp_write_mono(void *_bmp, Channel *ch) 119 | { 120 | BmpOut *bmp = (BmpOut*)_bmp; 121 | int i; 122 | 123 | if (!bmp || !bmp->ready) return 1; 124 | 125 | for (i=bmp->height-1; i>=0; i--) { 126 | fwrite(ch->pixels + bmp->width*i, bmp->width, 1, bmp->fd); 127 | } 128 | 129 | return 0; 130 | } 131 | 132 | int 133 | bmp_finalize(void *_bmp) 134 | { 135 | BmpOut *bmp = _bmp; 136 | 137 | if (!bmp || !bmp->ready) return 1; 138 | 139 | fclose(bmp->fd); 140 | free(bmp->rgbrow); 141 | free(bmp); 142 | 143 | return 0; 144 | } 145 | -------------------------------------------------------------------------------- /output/bmp_out.h: -------------------------------------------------------------------------------- 1 | #ifndef bmp_out_h 2 | #define bmp_out_h 3 | 4 | #include "channel.h" 5 | 6 | typedef struct _bmpout BmpOut; 7 | 8 | int bmp_init(void **dst, const char *fname, int width, int height, int mono); 9 | int bmp_write_rgb(void *bmp, Channel *red, Channel *green, Channel *blue); 10 | int bmp_write_mono(void *bmp, Channel *ch); 11 | int bmp_finalize(void *bmp); 12 | 13 | #endif /* bmp_out_h */ 14 | -------------------------------------------------------------------------------- /output/png_out.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "png_out.h" 6 | #include "protocol/mpdu.h" 7 | 8 | struct _pngout{ 9 | FILE *fd; 10 | unsigned int width, height; 11 | uint8_t *rgbrow; 12 | png_structp png_ptr; 13 | png_infop info_ptr; 14 | int ready; 15 | }; 16 | 17 | int 18 | png_init(void **_dst, const char *fname, int width, int height, int mono) 19 | { 20 | PngOut **dst = (PngOut**)_dst; 21 | PngOut *png; 22 | 23 | *dst = NULL; 24 | if(!(png = malloc(sizeof(*png)))) return 1; 25 | *dst = png; 26 | png->ready = 0; 27 | 28 | png->width = width; 29 | png->height = height; 30 | 31 | if (!(png->fd = fopen(fname, "wb"))) return 1; 32 | if (!(png->rgbrow = malloc(3*width))) return 1; 33 | 34 | png->png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 35 | if (!png->png_ptr) return 1; 36 | 37 | png->info_ptr = png_create_info_struct(png->png_ptr); 38 | if (!png->info_ptr) return 1; 39 | 40 | if (setjmp(png_jmpbuf(png->png_ptr))) { 41 | png_destroy_write_struct(&png->png_ptr, &png->info_ptr); 42 | free(png->rgbrow); 43 | fclose(png->fd); 44 | return 2; 45 | } 46 | 47 | png_init_io(png->png_ptr, png->fd); 48 | png_set_IHDR(png->png_ptr, png->info_ptr, png->width, png->height, 8, 49 | (mono ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_RGB), 50 | PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, 51 | PNG_FILTER_TYPE_DEFAULT); 52 | png_write_info(png->png_ptr, png->info_ptr); 53 | 54 | png->ready = 1; 55 | return 0; 56 | } 57 | 58 | int 59 | png_write_rgb(void *_png, Channel *red, Channel *green, Channel *blue) 60 | { 61 | PngOut *png = _png; 62 | unsigned int i, col, offset; 63 | png_structp png_ptr = (png_structp)png->png_ptr; 64 | png_infop info_ptr = (png_infop)png->info_ptr; 65 | uint8_t *rgbrow; 66 | 67 | if (!png || !png->ready) return 1; 68 | 69 | if (setjmp(png_jmpbuf(png->png_ptr))) { 70 | png_destroy_write_struct(&png_ptr, &info_ptr); 71 | png->png_ptr = NULL; 72 | png->info_ptr = NULL; 73 | return 1; 74 | } 75 | 76 | offset = 0; 77 | 78 | /* Interleave the three color channels */ 79 | for (i=0; iheight; i++) { 80 | rgbrow = png->rgbrow; 81 | 82 | for (col=0; colwidth; col++) { 83 | *rgbrow++ = (offset < red->offset) ? red->pixels[offset] : 0; 84 | *rgbrow++ = (offset < green->offset) ? green->pixels[offset] : 0; 85 | *rgbrow++ = (offset < blue->offset) ? blue->pixels[offset] : 0; 86 | offset++; 87 | } 88 | 89 | png_write_row(png_ptr, png->rgbrow); 90 | } 91 | 92 | return 0; 93 | } 94 | 95 | int 96 | png_write_mono(void *_png, Channel *ch) 97 | { 98 | PngOut *png = _png; 99 | png_structp png_ptr = (png_structp)png->png_ptr; 100 | png_infop info_ptr = (png_infop)png->info_ptr; 101 | unsigned int i; 102 | 103 | if (!png || !png->ready) return 1; 104 | 105 | if (setjmp(png_jmpbuf(png->png_ptr))) { 106 | png_destroy_write_struct(&png_ptr, &info_ptr); 107 | png->png_ptr = NULL; 108 | png->info_ptr = NULL; 109 | return 1; 110 | } 111 | 112 | for (i=0; iheight; i++) { 113 | png_write_row(png_ptr, ch->pixels + (png->width*i)); 114 | } 115 | return 0; 116 | } 117 | 118 | int 119 | png_finalize(void *_png) 120 | { 121 | PngOut *png = _png; 122 | 123 | if (!png || !png->ready) return 1; 124 | 125 | png_write_end(png->png_ptr, png->info_ptr); 126 | png_free_data(png->png_ptr, png->info_ptr, PNG_FREE_ALL, -1); 127 | png_destroy_info_struct(png->png_ptr, &png->info_ptr); 128 | png_destroy_write_struct(&png->png_ptr, NULL); 129 | fclose(png->fd); 130 | free(png->rgbrow); 131 | free(png); 132 | 133 | return 0; 134 | } 135 | -------------------------------------------------------------------------------- /output/png_out.h: -------------------------------------------------------------------------------- 1 | #ifndef png_out_h 2 | #define png_out_h 3 | 4 | #include 5 | #include 6 | #include "channel.h" 7 | 8 | typedef struct _pngout PngOut; 9 | 10 | int png_init(void **png, const char *fname, int width, int height, int mono); 11 | int png_write_rgb(void *png, Channel *red, Channel *green, Channel *blue); 12 | int png_write_mono(void *png, Channel *ch); 13 | int png_finalize(void *png); 14 | 15 | #endif /* png_out_h */ 16 | -------------------------------------------------------------------------------- /parser/mcu_parser.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "jpeg/huffman.h" 3 | #include "jpeg/jpeg.h" 4 | #include "mcu_parser.h" 5 | #include "protocol/mcu.h" 6 | 7 | int 8 | avhrr_decode(uint8_t (*dst)[8][8], AVHRR *a, int len) 9 | { 10 | //const uint8_t quant_table = avhrr_quant_table(a); /* Unused by M2 */ 11 | const uint8_t q_factor = avhrr_q(a); 12 | int16_t tmp[MCU_PER_MPDU][8][8]; 13 | int i; 14 | 15 | if (!q_factor) return 1; 16 | 17 | /* Huffman decode, return on error */ 18 | if (huffman_decode(tmp, a->data, MCU_PER_MPDU, len)) { 19 | return 1; 20 | } 21 | 22 | /* JPEG decode each 8x8 block */ 23 | for (i=0; i 5 | #include "protocol/mcu.h" 6 | 7 | int avhrr_decode(uint8_t (*dst)[8][8], AVHRR *a, int len); 8 | 9 | #endif /* mcu_parser_h */ 10 | -------------------------------------------------------------------------------- /parser/mpdu_parser.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "mpdu_parser.h" 4 | 5 | static enum { 6 | IDLE, HEADER, DATA 7 | } _state; 8 | 9 | static uint16_t _offset, _frag_offset; 10 | 11 | void 12 | mpdu_parser_init() 13 | { 14 | _state = IDLE; 15 | _offset = 0; 16 | _frag_offset = 0; 17 | } 18 | 19 | ParserStatus 20 | mpdu_reconstruct(Mpdu *dst, Vcdu *src) 21 | { 22 | unsigned int bytes_left; 23 | unsigned int jmp_idle; 24 | 25 | /* If a packet with a huge corrupted size makes it past the RS error 26 | * checking, but the current VCDU is marked as containing a header pointer, 27 | * jump to idle after decoding. This ensures that we don't lose tons of 28 | * MPDUs because we think they are part of a huge MPDU that doesn't really 29 | * exist */ 30 | jmp_idle = (vcdu_header_present(src) && _offset == 0); 31 | 32 | /* If the VCDU contains known bad data, skip it completely */ 33 | if (!vcdu_version(src) || !vcdu_type(src)) return PROCEED; 34 | 35 | /* Prevent buffer overflows when the fragment offset gets out of hand */ 36 | if (_frag_offset >= sizeof(*dst)) { 37 | mpdu_parser_init(); 38 | return PROCEED; 39 | } 40 | 41 | switch (_state) { 42 | case IDLE: 43 | /* Get the pointer to the next header, set that as the new offset */ 44 | if (vcdu_header_present(src)) { 45 | _offset = vcdu_header_ptr(src); 46 | 47 | /* Return immediately on invalid header pointer */ 48 | if (_offset > VCDU_DATA_LENGTH) { 49 | return PROCEED; 50 | } 51 | 52 | _frag_offset = 0; 53 | _state = HEADER; 54 | return FRAGMENT; 55 | } 56 | return PROCEED; 57 | break; 58 | case HEADER: 59 | bytes_left = MPDU_HDR_LEN - _frag_offset; 60 | 61 | if (_offset + bytes_left < VCDU_DATA_LENGTH) { 62 | /* The header's end byte is contained in this VCDU: copy bytes */ 63 | memcpy((uint8_t*)dst + _frag_offset, src->mpdu_data + _offset, bytes_left); 64 | _frag_offset = 0; /* 0 bytes into the data fragment */ 65 | _offset += bytes_left; 66 | _state = DATA; 67 | return FRAGMENT; 68 | } 69 | 70 | /* The header's end byte is in the next VCDU: copy some bytes and 71 | * update the fragment offset */ 72 | memcpy((uint8_t*)dst + _frag_offset, src->mpdu_data + _offset, VCDU_DATA_LENGTH - _offset); 73 | _frag_offset += VCDU_DATA_LENGTH - _offset; 74 | _offset = 0; 75 | return PROCEED; 76 | break; 77 | case DATA: 78 | bytes_left = mpdu_len(dst) - _frag_offset; 79 | 80 | if (_offset + bytes_left < VCDU_DATA_LENGTH) { 81 | /* The end of this data segment is within the VCDU: copy bytes */ 82 | memcpy((uint8_t*)(&dst->data) + _frag_offset, src->mpdu_data + _offset, bytes_left); 83 | _frag_offset = 0; 84 | _offset += bytes_left; 85 | _state = jmp_idle ? IDLE : HEADER; 86 | return PARSED; 87 | } 88 | 89 | /* The data continues in the next VCDU: copy some bytes and update 90 | * the fragment offset */ 91 | memcpy((uint8_t*)(&dst->data) + _frag_offset, src->mpdu_data + _offset, VCDU_DATA_LENGTH - _offset); 92 | _frag_offset += VCDU_DATA_LENGTH - _offset; 93 | _offset = 0; 94 | _state = jmp_idle ? IDLE : DATA; 95 | return jmp_idle ? FRAGMENT : PROCEED; 96 | break; 97 | } 98 | 99 | return PROCEED; 100 | } 101 | -------------------------------------------------------------------------------- /parser/mpdu_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef mpdu_parser_h 2 | #define mpdu_parser_h 3 | 4 | #include 5 | #include "protocol/mpdu.h" 6 | #include "protocol/vcdu.h" 7 | 8 | typedef enum { 9 | PROCEED=0, 10 | FRAGMENT, 11 | PARSED 12 | } ParserStatus; 13 | 14 | /** 15 | * (Re-)initialize the MPDU parser. 16 | */ 17 | void mpdu_parser_init(); 18 | 19 | /** 20 | * Reconstruct an MPDU from a VCDU. Has an internal state machine that advances 21 | * based on the data encountered in the VCDU data unit zone. Typical usage: keep 22 | * calling in a loop on the same data until it returns PROCEED. 23 | * 24 | * @param dst the destination buffer to build the MPDU into 25 | * @param src the VCDU to process. 26 | * @return PROCEED if there is no data left to process inside the current VCDU 27 | * FRAGMENT if some data was processed but no MPDU is available yet 28 | * PARSED if a complete MPDU was reconstructed (might not be done with the VCDU though) 29 | */ 30 | ParserStatus mpdu_reconstruct(Mpdu *dst, Vcdu *src); 31 | 32 | #endif /* mpdu_parser_h */ 33 | -------------------------------------------------------------------------------- /protocol/cadu.h: -------------------------------------------------------------------------------- 1 | #ifndef cadu_h 2 | #define cadu_h 3 | 4 | #include 5 | #include "vcdu.h" 6 | 7 | #define CADU_DATA_LENGTH 1020 8 | #define CONV_CADU_LEN (2*sizeof(Cadu)) 9 | #define CADU_SOFT_LEN (2*8*sizeof(Cadu)) 10 | 11 | #define SYNCWORD 0x1ACFFC1D 12 | #define SYNC_LEN 4 13 | 14 | /* Data link layer, with added sync markers and pseudorandom noise */ 15 | typedef struct { 16 | uint32_t syncword; 17 | Vcdu data; 18 | } __attribute__((packed)) Cadu; 19 | 20 | #endif /* cadu_h */ 21 | -------------------------------------------------------------------------------- /protocol/mcu.c: -------------------------------------------------------------------------------- 1 | #include "mcu.h" 2 | 3 | extern inline uint8_t avhrr_seq(AVHRR *a); 4 | extern inline uint8_t avhrr_quant_table(AVHRR *a); 5 | extern inline uint8_t avhrr_ac_idx(AVHRR *a); 6 | extern inline uint8_t avhrr_dc_idx(AVHRR *a); 7 | extern inline uint8_t avhrr_q(AVHRR *a); 8 | -------------------------------------------------------------------------------- /protocol/mcu.h: -------------------------------------------------------------------------------- 1 | #ifndef mcu_h 2 | #define mcu_h 3 | 4 | #include 5 | 6 | #define MCUSEG_MAX_DATA_LEN 2048 7 | #define MCU_PER_MPDU 14 8 | 9 | typedef struct { 10 | uint8_t seq; 11 | uint8_t scan_hdr[2]; 12 | uint8_t segment_hdr[3]; 13 | uint8_t data[MCUSEG_MAX_DATA_LEN-6]; 14 | }__attribute__((packed)) AVHRR; 15 | 16 | typedef struct { 17 | uint8_t data[114]; 18 | uint8_t pad[MCUSEG_MAX_DATA_LEN - 114]; 19 | }__attribute__((packed)) Calib; 20 | 21 | typedef union { 22 | AVHRR avhrr; 23 | Calib calib; 24 | }__attribute__((packed)) McuSegment; 25 | 26 | inline uint8_t avhrr_seq(AVHRR *a) { return a->seq; } 27 | inline uint8_t avhrr_quant_table(AVHRR *a) { return a->scan_hdr[0]; } 28 | inline uint8_t avhrr_ac_idx(AVHRR *a) { return a->scan_hdr[1] & 0xF; } 29 | inline uint8_t avhrr_dc_idx(AVHRR *a) { return a->scan_hdr[1] >> 4; } 30 | inline uint8_t avhrr_q(AVHRR *a) { return a->segment_hdr[2]; } 31 | 32 | #endif /* mcu_h */ 33 | -------------------------------------------------------------------------------- /protocol/mpdu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "mpdu.h" 4 | 5 | static char _mpdu_time[sizeof("HH:MM:SS.mmm ")]; 6 | 7 | extern inline uint8_t mpdu_version(Mpdu *m); 8 | extern inline uint8_t mpdu_type(Mpdu *m); 9 | extern inline uint8_t mpdu_has_secondary_hdr(Mpdu *m); 10 | extern inline uint16_t mpdu_apid(Mpdu *m); 11 | extern inline uint8_t mpdu_seq_flag(Mpdu *m); 12 | extern inline uint16_t mpdu_seq(Mpdu *m); 13 | extern inline uint16_t mpdu_len(Mpdu *m); 14 | extern inline uint16_t mpdu_day(Mpdu *m); 15 | extern inline uint32_t mpdu_ms(Mpdu *m); 16 | extern inline uint16_t mpdu_us(Mpdu *m); 17 | extern inline uint64_t mpdu_raw_time(Mpdu *m); 18 | 19 | char* 20 | mpdu_time(uint64_t us) 21 | { 22 | unsigned hr, min, sec, ms; 23 | 24 | ms = us / 1000; 25 | hr = ms / 1000 / 60 / 60 % 24; 26 | min = ms / 1000 / 60 % 60; 27 | sec = ms / 1000 % 60; 28 | ms %= 1000; 29 | 30 | sprintf(_mpdu_time, "%02d:%02d:%02d.%03d", hr, min, sec, ms); 31 | return _mpdu_time; 32 | } 33 | -------------------------------------------------------------------------------- /protocol/mpdu.h: -------------------------------------------------------------------------------- 1 | #ifndef mpdu_h 2 | #define mpdu_h 3 | 4 | #include 5 | #include "mcu.h" 6 | 7 | #define MPDU_MAX_DATA_LEN sizeof(McuSegment) 8 | #define MPDU_HDR_LEN (sizeof(Mpdu)-sizeof(McuSegment)-sizeof(Timestamp)) 9 | #define MPDU_MAX_SEQ 16384 10 | #define MPDU_PER_LINE 14 11 | #define MPDU_PER_PERIOD (3*MPDU_PER_LINE + 1) 12 | #define MCU_PER_LINE (MCU_PER_MPDU * MPDU_PER_LINE) 13 | #define MPDU_US_PER_LINE (1220*1000) /* Imprecise, lower bound only */ 14 | #define US_PER_DAY ((uint64_t)1000L * 1000L * 86400L) 15 | 16 | typedef struct { 17 | uint8_t day[2]; 18 | uint8_t ms[4]; 19 | uint8_t us[2]; 20 | } __attribute__((packed)) Timestamp; 21 | 22 | typedef struct { 23 | uint8_t id[2]; 24 | uint8_t seq[2]; 25 | uint8_t len[2]; 26 | 27 | struct { 28 | Timestamp time; 29 | McuSegment mcu; 30 | }__attribute__((packed)) data; 31 | } __attribute__((packed)) Mpdu; 32 | 33 | inline uint8_t mpdu_version(Mpdu *m) { return m->id[0] >> 5; } 34 | inline uint8_t mpdu_type(Mpdu *m) { return m->id[0] >> 4 & 0x1; } 35 | inline uint8_t mpdu_has_secondary_hdr(Mpdu *m) { return m->id[0] >> 3 & 0x1; } 36 | inline uint16_t mpdu_apid(Mpdu *m) { return (m->id[0] & 0x7) << 8 | m->id[1]; } 37 | inline uint8_t mpdu_seq_flag(Mpdu *m) { return m->seq[0] >> 6; } 38 | inline uint16_t mpdu_seq(Mpdu *m) { return (m->seq[0] & 0x3F) << 8 | m->seq[1]; } 39 | inline uint16_t mpdu_len(Mpdu *m) { return (m->len[0] << 8 | m->len[1]) + 1; } 40 | inline uint16_t mpdu_day(Mpdu *m) { return m->data.time.day[0] << 8 | m->data.time.day[1]; } 41 | inline uint32_t mpdu_ms(Mpdu *m) { return (uint32_t)m->data.time.ms[0] << 24 | (uint32_t)m->data.time.ms[1] << 16 | (uint32_t)m->data.time.ms[2] << 8 | (uint32_t)m->data.time.ms[3]; } 42 | inline uint16_t mpdu_us(Mpdu *m) { return m->data.time.us[0] << 8 | m->data.time.us[1]; } 43 | inline uint64_t mpdu_raw_time(Mpdu *m) { return (uint64_t)mpdu_day(m)*86400LL*1000LL*1000LL + (uint64_t)mpdu_ms(m)*1000L + (uint64_t)mpdu_us(m); } 44 | 45 | char *mpdu_time(uint64_t us); 46 | #endif /* mpdu_h */ 47 | -------------------------------------------------------------------------------- /protocol/vcdu.c: -------------------------------------------------------------------------------- 1 | #include "vcdu.h" 2 | 3 | extern inline uint8_t vcdu_version(Vcdu *c); 4 | extern inline uint8_t vcdu_spacecraft_id(Vcdu *c); 5 | extern inline uint8_t vcdu_type(Vcdu *c); 6 | extern inline uint32_t vcdu_counter(Vcdu *c); 7 | extern inline uint8_t vcdu_replay(Vcdu *c); 8 | extern inline uint8_t vcdu_signalling_spare(Vcdu *c); 9 | extern inline uint8_t vcdu_encryption(Vcdu *c); 10 | extern inline uint8_t vcdu_encr_num(Vcdu *c); 11 | extern inline uint8_t vcdu_mpdu_spare(Vcdu *c); 12 | extern inline uint8_t vcdu_header_present(Vcdu *c); 13 | extern inline uint16_t vcdu_header_ptr(Vcdu *c); 14 | extern inline uint8_t* vcdu_data(Vcdu *c); 15 | extern inline uint8_t* vcdu_checksum(Vcdu *c); 16 | -------------------------------------------------------------------------------- /protocol/vcdu.h: -------------------------------------------------------------------------------- 1 | #ifndef vcdu_h 2 | #define vcdu_h 3 | 4 | #include 5 | 6 | #define VCDU_DATA_LENGTH 882 7 | #define VCDU_RS_LENGTH 128 8 | 9 | /* Data link layer */ 10 | typedef struct { 11 | /* VCDU primary header */ 12 | uint8_t primary_hdr[6]; 13 | 14 | /* VCDU insert zone */ 15 | uint8_t encryption; 16 | uint8_t encr_key_num; 17 | 18 | /* VCDU data unit zone */ 19 | uint8_t mpdu_hdr[2]; 20 | uint8_t mpdu_data[VCDU_DATA_LENGTH]; 21 | 22 | /* RS check symbols over the entire VCDU contents */ 23 | uint8_t checksum[VCDU_RS_LENGTH]; 24 | }__attribute__((packed)) Vcdu; 25 | 26 | /* Data link layer accessors */ 27 | inline uint8_t vcdu_version(Vcdu *c) { return c->primary_hdr[0] >> 6; } 28 | inline uint8_t vcdu_spacecraft_id(Vcdu *c) { return (c->primary_hdr[0] & 0x3F) << 2 | c->primary_hdr[1] >> 6;} 29 | inline uint8_t vcdu_type(Vcdu *c) { return c->primary_hdr[1] & 0x3F; } 30 | inline uint32_t vcdu_counter(Vcdu *c) { return c->primary_hdr[2] << 16 | c->primary_hdr[3] << 8 | c->primary_hdr[4]; } 31 | inline uint8_t vcdu_replay(Vcdu *c) { return c->primary_hdr[5] >> 7; } 32 | inline uint8_t vcdu_signalling_spare(Vcdu *c) { return c->primary_hdr[5] & 0x7F; } 33 | inline uint8_t vcdu_encryption(Vcdu *c) { return c->encryption; } 34 | inline uint8_t vcdu_encr_num(Vcdu *c) { return c->encr_key_num; } 35 | inline uint8_t vcdu_mpdu_spare(Vcdu *c) { return c->mpdu_hdr[0] >> 3; } 36 | inline uint16_t vcdu_header_ptr(Vcdu *c) { return (c->mpdu_hdr[0] & 0x7) << 8 | c->mpdu_hdr[1]; } 37 | inline uint8_t vcdu_header_present(Vcdu *c) { return !vcdu_mpdu_spare(c) && vcdu_header_ptr(c) != 0x7FF; } 38 | inline uint8_t* vcdu_data(Vcdu *c) { return c->mpdu_data; } 39 | inline uint8_t* vcdu_checksum(Vcdu *c) { return c->checksum; } 40 | 41 | #endif /* vcdu_h */ 42 | -------------------------------------------------------------------------------- /raw_channel.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "raw_channel.h" 3 | 4 | int 5 | raw_channel_init(RawChannel *ch, const char *fname) 6 | { 7 | return !(ch->fd = fopen(fname, "wb")); 8 | } 9 | 10 | void 11 | raw_channel_write(RawChannel *ch, uint8_t *data, int len) 12 | { 13 | if (!ch) return; 14 | fwrite(data, len, 1, ch->fd); 15 | } 16 | 17 | void 18 | raw_channel_close(RawChannel *ch) 19 | { 20 | fclose(ch->fd); 21 | } 22 | -------------------------------------------------------------------------------- /raw_channel.h: -------------------------------------------------------------------------------- 1 | #ifndef raw_channel_h 2 | #define raw_channel_h 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | FILE *fd; 9 | } RawChannel; 10 | 11 | /** 12 | * Initialize a raw channel object 13 | * 14 | * @param ch the channel to initalize 15 | * @param fname fname the file to dump the raw data to 16 | * @return 0 on success, non-zero on failure 17 | */ 18 | int raw_channel_init(RawChannel *ch, const char *fname); 19 | 20 | /** 21 | * Finalize a raw channel 22 | * 23 | * @param ch the channel to close 24 | */ 25 | void raw_channel_close(RawChannel *ch); 26 | 27 | 28 | /** 29 | * Write data to a raw channel 30 | * 31 | * @param ch the channel to write the data to 32 | * @param data pointer to the data to write 33 | * @param len the length of the data buffer to write 34 | */ 35 | void raw_channel_write(RawChannel *ch, uint8_t *data, int len); 36 | 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "utils.h" 7 | 8 | #ifndef VERSION 9 | #define VERSION "(unknown version)" 10 | #endif 11 | 12 | void 13 | soft_to_hard(uint8_t *restrict hard, int8_t *restrict soft, int len) 14 | { 15 | int i; 16 | 17 | assert(!(len & 0x7)); 18 | 19 | while (len > 0) { 20 | *hard = 0; 21 | 22 | for (i=7; i>=0; i--) { 23 | *hard |= (*soft < 0) << i; 24 | soft++; 25 | } 26 | 27 | hard++; 28 | len -= 8; 29 | } 30 | } 31 | 32 | void 33 | soft_derotate(int8_t *soft, int len, enum phase phase) 34 | { 35 | int8_t tmp; 36 | 37 | /* Prevent overflows when changing sign */ 38 | for (int i=0; i (-y, x) */ 47 | for (; len>0; len-=2) { 48 | tmp = *soft; 49 | *soft = -*(soft+1); 50 | *(soft+1) = tmp; 51 | soft += 2; 52 | } 53 | break; 54 | case PHASE_180: 55 | /* (x, y) -> (-x, -y) */ 56 | for (; len>0; len--) { 57 | *soft = -*soft; 58 | soft++; 59 | } 60 | break; 61 | case PHASE_90: 62 | /* (x, y) -> (y, -x) */ 63 | for (; len>0; len-=2) { 64 | tmp = *soft; 65 | *soft = *(soft+1); 66 | *(soft+1) = -tmp; 67 | soft += 2; 68 | } 69 | break; 70 | default: 71 | assert(0); 72 | break; 73 | } 74 | } 75 | 76 | int 77 | count_ones(uint64_t v) { 78 | int count; 79 | for (count = 0; v; count++) { 80 | v &= v-1; 81 | } 82 | return count; 83 | } 84 | 85 | void 86 | version() 87 | { 88 | printf("meteor_decode v" VERSION "\n"); 89 | } 90 | 91 | void 92 | usage(char *execname) 93 | { 94 | fprintf(stderr, "Usage: %s [options] input.s\n", execname); 95 | fprintf(stderr, 96 | " -7, --70 Dump APID70 data in a separate file\n" 97 | " -a, --apid R,G,B Specify APIDs to parse (default: autodetect)\n" 98 | " -B, --batch Batch mode (disable all non-printable characters)\n" 99 | " -d, --diff Perform differential decoding\n" 100 | " -i, --int Deinterleave samples (aka 80k mode)\n" 101 | " -o, --output Output composite image to \n" 102 | " -q, --quiet Disable decoder status output\n" 103 | " -s, --split Write each APID in a separate file\n" 104 | " -t, --statfile Write .stat file\n" 105 | "\n" 106 | " -h, --help Print this help screen\n" 107 | " -v, --version Print version information\n" 108 | ); 109 | } 110 | 111 | uint32_t 112 | read_bits(const uint8_t *src, int offset_bits, int bitcount) 113 | { 114 | uint64_t ret = 0; 115 | 116 | src += (offset_bits / 8); 117 | offset_bits %= 8; 118 | 119 | ret = (*src++) & ((1<<(8-offset_bits))-1); 120 | bitcount -= (8-offset_bits); 121 | 122 | while (bitcount > 0) { 123 | ret = ret << 8 | *src++; 124 | bitcount -= 8; 125 | } 126 | 127 | ret >>= -bitcount; 128 | return ret; 129 | } 130 | 131 | void 132 | gen_fname(char *buf, size_t len) 133 | { 134 | time_t t; 135 | struct tm *tm; 136 | 137 | t = time(NULL); 138 | tm = localtime(&t); 139 | 140 | strftime(buf, len, "LRPT_%Y_%m_%d-%H_%M.bmp", tm); 141 | } 142 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | #ifndef utils_h 2 | #define utils_h 3 | 4 | #include 5 | #include 6 | 7 | #define LEN(x) (sizeof(x)/sizeof(*x)) 8 | #define MIN(x, y) ((x) < (y) ? (x) : (y)) 9 | #define MAX(x, y) ((x) > (y) ? (x) : (y)) 10 | 11 | #define DO_PRAGMA(x) _Pragma(#x) 12 | 13 | /* Portable unroll pragma, for some reason clang defines __GNUC__ but uses the 14 | * non-GCC unroll pragma format */ 15 | #if defined(__clang__) 16 | #define PRAGMA_UNROLL(x) DO_PRAGMA(unroll x) 17 | #elif defined(__GNUC__) 18 | #define PRAGMA_UNROLL(x) DO_PRAGMA(GCC unroll x) 19 | #else 20 | #define PRAGMA_UNROLL(x) DO_PRAGMA(unroll x) 21 | #endif 22 | 23 | enum phase { 24 | PHASE_0=0, PHASE_90=1, PHASE_180=2, PHASE_270=3, // TODO might also need I<->Q swaps? 25 | PHASE_INV_0=4, PHASE_INV_90=5, PHASE_INV_180=6, PHASE_INV_270=7 26 | }; 27 | 28 | /** 29 | * Convert soft samples into hard samples 30 | * 31 | * @param hard pointer to the destination buffer that will hold the hard 32 | * samples. Must be at least len/8 bytes long 33 | * @param soft pointer to the soft samples to convert 34 | * @param len numebr of soft samples to convert 35 | */ 36 | void soft_to_hard(uint8_t *hard, int8_t *soft, int len); 37 | 38 | /** 39 | * Undo a rotation on a set of soft samples. 40 | * 41 | * @param soft the samples to rotate in-place 42 | * @param len number of samples to rotate 43 | * @param phase the phase rotation to undo 44 | */ 45 | void soft_derotate(int8_t *soft, int len, enum phase phase); 46 | 47 | /** 48 | * Count bits set inside of a variable 49 | * 50 | * @param v variable to count the bits of 51 | * @return number of bits set in the variable 52 | */ 53 | int count_ones(uint64_t v); 54 | 55 | /** 56 | * Read up to 32 misaligned bits from a byte buffer 57 | * 58 | * @param src pointer to the byte buffer to read bits from 59 | * @param offset_bits offset from the beginning of the pointer from which to 60 | * start reading 61 | * @param bitcount number of bits to read 62 | * @return bits read 63 | */ 64 | uint32_t read_bits(const uint8_t *src, int offset_bits, int bitcount); 65 | 66 | /** 67 | * Generate an automatic filename based on the current date and time 68 | * 69 | * @param buf buffer to write the filename to 70 | * @param len max length of the allocated buffer 71 | */ 72 | void gen_fname(char *buf, size_t len); 73 | 74 | /** 75 | * Print usage information 76 | * 77 | * @param execname name of the executable 78 | */ 79 | void usage(char *execname); 80 | 81 | /** 82 | * Print version information 83 | */ 84 | void version(); 85 | 86 | #endif /* utils_h */ 87 | --------------------------------------------------------------------------------