mirror of
https://github.com/radio95-rnt/rds95.git
synced 2026-02-27 12:53:53 +01:00
first commit
This commit is contained in:
82
src/CMakeLists.txt
Normal file
82
src/CMakeLists.txt
Normal file
@@ -0,0 +1,82 @@
|
||||
# This is from kuba's version of MiniRDS
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# Project name and version
|
||||
project(minirds VERSION 1.0)
|
||||
|
||||
# Define options
|
||||
option(ODA_RTP "Enable ODA (RT+)" ON)
|
||||
|
||||
option(RDS2 "Enable RDS2 capabilities" OFF)
|
||||
option(RDS2_QUADRATURE_CARRIER "Shift RDS2 stream carriers by 90, 180, and 270 degrees" ON)
|
||||
option(RDS2_SYMBOL_SHIFTING "Enable RDS2 symbol shifting" ON)
|
||||
option(RDS2_DEBUG "Enable RDS2 debugging" OFF)
|
||||
|
||||
option(STATIC_LIBSAMPLERATE "Use a static libsamplerate library (.a)" OFF)
|
||||
|
||||
|
||||
set(LIBSAMPLERATE_DIR "./libsamplerate" CACHE STRING "Directory for static libsamplerate")
|
||||
|
||||
# Set compiler and flags
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wextra -pedantic -O2 -std=c18 -DVERSION=\"${PROJECT_VERSION}\"")
|
||||
|
||||
# Define sources
|
||||
set(SOURCES
|
||||
minirds.c
|
||||
waveforms.c
|
||||
rds.c
|
||||
)
|
||||
|
||||
# Handle RDS2 options
|
||||
if(RDS2)
|
||||
add_definitions(-DRDS2)
|
||||
set(SOURCES ${SOURCES} rds2.c)
|
||||
if(RDS2_QUADRATURE_CARRIER)
|
||||
add_definitions(-DRDS2_QUADRATURE_CARRIER)
|
||||
endif()
|
||||
if(RDS2_SYMBOL_SHIFTING)
|
||||
add_definitions(-DRDS2_SYMBOL_SHIFTING)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(RDS2_DEBUG)
|
||||
add_definitions(-DRDS2_DEBUG)
|
||||
endif()
|
||||
|
||||
if(ODA_RTP)
|
||||
add_definitions(-DODA)
|
||||
add_definitions(-DODA_RTP)
|
||||
endif()
|
||||
|
||||
set(SOURCES
|
||||
${SOURCES}
|
||||
fm_mpx.c
|
||||
control_pipe.c
|
||||
osc.c
|
||||
resampler.c
|
||||
modulator.c
|
||||
lib.c
|
||||
ascii_cmd.c
|
||||
)
|
||||
|
||||
# Define the executable
|
||||
add_executable(minirds ${SOURCES})
|
||||
|
||||
# Handle libsamplerate linkage
|
||||
if(STATIC_LIBSAMPLERATE)
|
||||
target_include_directories(minirds PRIVATE ${LIBSAMPLERATE_DIR}/include)
|
||||
target_link_libraries(minirds PRIVATE ${LIBSAMPLERATE_DIR}/lib/libsamplerate.a)
|
||||
else()
|
||||
find_library(SAMPLERATE_LIBRARY samplerate)
|
||||
target_link_libraries(minirds PRIVATE ${SAMPLERATE_LIBRARY})
|
||||
endif()
|
||||
|
||||
# Link additional libraries
|
||||
target_link_libraries(minirds PRIVATE m pthread pulse pulse-simple)
|
||||
|
||||
# Install target
|
||||
install(TARGETS minirds DESTINATION /usr/local/bin)
|
||||
# Add profiling flags globally
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2")
|
||||
395
src/ascii_cmd.c
Normal file
395
src/ascii_cmd.c
Normal file
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* MiniRDS - FM RDS Encoder
|
||||
* contains code from:
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "rds.h"
|
||||
#include "fm_mpx.h"
|
||||
#include "lib.h"
|
||||
#include "ascii_cmd.h"
|
||||
|
||||
#define CMD_MATCHES(a) (ustrcmp(cmd, (unsigned char *)a) == 0)
|
||||
|
||||
void process_ascii_cmd(unsigned char *str) {
|
||||
unsigned char *cmd, *arg;
|
||||
uint16_t cmd_len = 0;
|
||||
|
||||
cmd_len = _strnlen((const char*)str, CTL_BUFFER_SIZE);
|
||||
|
||||
if (cmd_len > 3 && str[2] == ' ') {
|
||||
cmd = str;
|
||||
cmd[2] = 0;
|
||||
arg = str + 3;
|
||||
|
||||
if (CMD_MATCHES("CG")) {
|
||||
/* this stays */
|
||||
uint16_t blocks[4];
|
||||
int count = sscanf((const char*)arg, "%hX %hX %hX %hX",
|
||||
&blocks[0], &blocks[1],
|
||||
&blocks[2], &blocks[3]);
|
||||
if (count == 4) {
|
||||
set_rds_cg(blocks);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd_len > 4 && str[3] == ' ') {
|
||||
cmd = str;
|
||||
cmd[3] = 0;
|
||||
arg = str + 4;
|
||||
|
||||
if (CMD_MATCHES("MPX")) {
|
||||
#ifdef RDS2
|
||||
float gains[5];
|
||||
if (sscanf((char *)arg, "%f,%f,%f,%f,%f",
|
||||
&gains[0], &gains[1], &gains[2], &gains[3],
|
||||
&gains[4]) == 5) {
|
||||
set_carrier_volume(0, gains[0]);
|
||||
set_carrier_volume(1, gains[1]);
|
||||
set_carrier_volume(2, gains[2]);
|
||||
set_carrier_volume(3, gains[3]);
|
||||
set_carrier_volume(4, gains[4]);
|
||||
}
|
||||
#else
|
||||
float gains[2];
|
||||
if (sscanf((char *)arg, "%f,%f",
|
||||
&gains[0], &gains[1]) == 2)
|
||||
{
|
||||
set_carrier_volume(0, gains[0]);
|
||||
set_carrier_volume(1, gains[1]);
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
if (CMD_MATCHES("VOL")) {
|
||||
arg[4] = 0;
|
||||
set_output_volume(strtof((char *)arg, NULL));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(cmd_len == 5) {
|
||||
cmd = str;
|
||||
if(CMD_MATCHES("AFCH=")) {
|
||||
clear_rds_af();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (cmd_len > 5 && str[4] == '=') {
|
||||
/* compatibilty with existing (standarts)*/
|
||||
cmd = str;
|
||||
cmd[4] = 0;
|
||||
arg = str + 5;
|
||||
if (CMD_MATCHES("TEXT")) {
|
||||
arg[RT_LENGTH * 2] = 0;
|
||||
set_rds_rt1(xlat(arg));
|
||||
return;
|
||||
}
|
||||
if (CMD_MATCHES("PTYN")) {
|
||||
arg[PTYN_LENGTH] = 0;
|
||||
set_rds_ptyn(xlat(arg));
|
||||
return;
|
||||
}
|
||||
if (CMD_MATCHES("AFCH")) {
|
||||
if(arg[0] == 'A' || arg[0] == 'B') {
|
||||
return;
|
||||
}
|
||||
clear_rds_af();
|
||||
uint8_t arg_count;
|
||||
rds_af_t new_af;
|
||||
uint8_t af[MAX_AFS], *af_iter;
|
||||
arg_count = sscanf((char *)arg,
|
||||
"%hhx,%hhx,%hhx,%hhx,%hhx," /* AF list */
|
||||
"%hhx,%hhx,%hhx,%hhx,%hhx,"
|
||||
"%hhx,%hhx,%hhx,%hhx,%hhx,"
|
||||
"%hhx,%hhx,%hhx,%hhx,%hhx,"
|
||||
"%hhx,%hhx,%hhx,%hhx,%hhx",
|
||||
&af[0], &af[1], &af[2], &af[3], &af[4],
|
||||
&af[5], &af[6], &af[7], &af[8], &af[9],
|
||||
&af[10], &af[11], &af[12], &af[13], &af[14],
|
||||
&af[15], &af[16], &af[17], &af[18], &af[19],
|
||||
&af[20], &af[21], &af[22], &af[23], &af[24]);
|
||||
|
||||
af_iter = af;
|
||||
memset(&new_af, 0, sizeof(struct rds_af_t));
|
||||
while (arg_count-- != 0) {
|
||||
uint8_t current_value = *af_iter;
|
||||
float frequency = (875.0 + current_value) / 10.0;
|
||||
add_rds_af(&new_af, frequency);
|
||||
af_iter++;
|
||||
}
|
||||
set_rds_af(new_af);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(cmd_len == 4) {
|
||||
cmd = str;
|
||||
if(CMD_MATCHES("TPS=")) {
|
||||
set_rds_tpson(0);
|
||||
return;
|
||||
}
|
||||
if(CMD_MATCHES("LPS=")) {
|
||||
set_rds_lpson(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (cmd_len > 4 && str[3] == '=') {
|
||||
cmd = str;
|
||||
cmd[3] = 0;
|
||||
arg = str + 4;
|
||||
if (CMD_MATCHES("TPS")) {
|
||||
arg[PS_LENGTH * 2] = 0;
|
||||
set_rds_tps(xlat(arg));
|
||||
set_rds_tpson(1);
|
||||
return;
|
||||
}
|
||||
if (CMD_MATCHES("RT1")) {
|
||||
arg[RT_LENGTH * 2] = 0;
|
||||
set_rds_rt1(xlat(arg));
|
||||
return;
|
||||
}
|
||||
if (CMD_MATCHES("PTY")) {
|
||||
arg[2] = 0;
|
||||
set_rds_pty(strtoul((char *)arg, NULL, 10));
|
||||
return;
|
||||
}
|
||||
if (CMD_MATCHES("ECC")) {
|
||||
arg[2] = 0;
|
||||
set_rds_ecc(strtoul((char *)arg, NULL, 16));
|
||||
return;
|
||||
}
|
||||
if (CMD_MATCHES("LIC")) {
|
||||
arg[2] = 0;
|
||||
set_rds_lic(strtoul((char *)arg, NULL, 16));
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ODA_RTP
|
||||
if (CMD_MATCHES("RTP")) {
|
||||
char tag_names[2][32];
|
||||
uint8_t tags[6];
|
||||
if (sscanf((char *)arg, "%hhu,%hhu,%hhu,%hhu,%hhu,%hhu",
|
||||
&tags[0], &tags[1], &tags[2], &tags[3],
|
||||
&tags[4], &tags[5]) == 6) {
|
||||
set_rds_rtplus_tags(tags);
|
||||
} else if (sscanf((char *)arg, "%31[^,],%hhu,%hhu,%31[^,],%hhu,%hhu",
|
||||
tag_names[0], &tags[1], &tags[2],
|
||||
tag_names[1], &tags[4], &tags[5]) == 6) {
|
||||
tags[0] = get_rtp_tag_id(tag_names[0]);
|
||||
tags[3] = get_rtp_tag_id(tag_names[1]);
|
||||
set_rds_rtplus_tags(tags);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (CMD_MATCHES("LPS")) {
|
||||
arg[LPS_LENGTH] = 0;
|
||||
set_rds_lpson(1);
|
||||
set_rds_lps(arg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (CMD_MATCHES("PIN")) {
|
||||
uint8_t pin[3];
|
||||
if (sscanf((char *)arg, "%hhu,%hhu,%hhu",
|
||||
&pin[0], &pin[1], &pin[2]) == 3) {
|
||||
set_rds_pin(pin[0], pin[1], pin[2]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(cmd_len == 3) {
|
||||
cmd = str;
|
||||
if(CMD_MATCHES("AF=")) {
|
||||
clear_rds_af();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (cmd_len > 3 && str[2] == '=') {
|
||||
cmd = str;
|
||||
cmd[2] = 0;
|
||||
arg = str + 3;
|
||||
|
||||
if (CMD_MATCHES("PS")) {
|
||||
if(arg[0] == '\0') arg[0] = ' '; /* fix for strings that start with a space idk why but tps works fine with space started strings */
|
||||
arg[PS_LENGTH * 2] = 0;
|
||||
set_rds_ps(xlat(arg));
|
||||
return;
|
||||
}
|
||||
if (CMD_MATCHES("CT")) {
|
||||
set_rds_ct(arg[0]);
|
||||
return;
|
||||
}
|
||||
if (CMD_MATCHES("DI")) {
|
||||
arg[2] = 0;
|
||||
set_rds_di(strtoul((char *)arg, NULL, 10));
|
||||
return;
|
||||
}
|
||||
if (CMD_MATCHES("TP")) {
|
||||
set_rds_tp(arg[0]);
|
||||
return;
|
||||
}
|
||||
if (CMD_MATCHES("TA")) {
|
||||
set_rds_ta(arg[0]);
|
||||
return;
|
||||
}
|
||||
if (CMD_MATCHES("MS")) {
|
||||
set_rds_ms(arg[0]);
|
||||
return;
|
||||
}
|
||||
if (CMD_MATCHES("PI")) {
|
||||
arg[4] = 0;
|
||||
set_rds_pi(strtoul((char *)arg, NULL, 16));
|
||||
return;
|
||||
}
|
||||
|
||||
if (CMD_MATCHES("AF")) {
|
||||
if(arg[0] == 'A' || arg[0] == 'B') {
|
||||
return;
|
||||
}
|
||||
clear_rds_af();
|
||||
uint8_t arg_count;
|
||||
rds_af_t new_af;
|
||||
float af[MAX_AFS], *af_iter;
|
||||
arg_count = sscanf((char *)arg,
|
||||
"%f,%f,%f,%f,%f," /* AF list */
|
||||
"%f,%f,%f,%f,%f,"
|
||||
"%f,%f,%f,%f,%f,"
|
||||
"%f,%f,%f,%f,%f,"
|
||||
"%f,%f,%f,%f,%f",
|
||||
&af[0], &af[1], &af[2], &af[3], &af[4],
|
||||
&af[5], &af[6], &af[7], &af[8], &af[9],
|
||||
&af[10], &af[11], &af[12], &af[13], &af[14],
|
||||
&af[15], &af[16], &af[17], &af[18], &af[19],
|
||||
&af[20], &af[21], &af[22], &af[23], &af[24]);
|
||||
af_iter = af;
|
||||
memset(&new_af, 0, sizeof(struct rds_af_t));
|
||||
while (arg_count-- != 0) {
|
||||
add_rds_af(&new_af, *af_iter++);
|
||||
}
|
||||
set_rds_af(new_af);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd_len > 2 && str[1] == '=') {
|
||||
cmd = str;
|
||||
cmd[1] = 0;
|
||||
arg = str + 2;
|
||||
if (CMD_MATCHES("G")) {
|
||||
uint16_t blocks[4];
|
||||
if(cmd_len == 18){
|
||||
/* RDS2 Group*/
|
||||
/* do a ifdef rds2 here when implementing*/
|
||||
} else if(cmd_len == 14) {
|
||||
/* RDS1 Group*/
|
||||
blocks[0] = get_rds_pi();
|
||||
int count = sscanf((char *)arg, "%4hx%4hx%4hx", &blocks[1], &blocks[2], &blocks[3]);
|
||||
if(count == 3) {
|
||||
set_rds_cg(blocks);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (cmd_len > 6 && str[5] == '=') {
|
||||
cmd = str;
|
||||
cmd[5] = 0;
|
||||
arg = str + 6;
|
||||
|
||||
if (CMD_MATCHES("PINEN")) {
|
||||
arg[1] = 0;
|
||||
set_rds_pin_enabled(strtoul((char *)arg, NULL, 10));
|
||||
return;
|
||||
}
|
||||
|
||||
if (CMD_MATCHES("RT1EN")) {
|
||||
set_rds_rt1_enabled(arg[0]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd_len > 6 && str[5] == '=') {
|
||||
cmd = str;
|
||||
cmd[5] = 0;
|
||||
arg = str + 6;
|
||||
if (CMD_MATCHES("LEVEL")) {
|
||||
uint8_t val = strtoul((char *)arg, NULL, 10);
|
||||
val /= 255;
|
||||
val *= 15; /* max value*/
|
||||
#ifdef RDS2
|
||||
set_carrier_volume(1, val);
|
||||
set_carrier_volume(2, val);
|
||||
#else
|
||||
set_carrier_volume(1, val);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (cmd_len > 7 && str[6] == '=') {
|
||||
cmd = str;
|
||||
cmd[6] = 0;
|
||||
arg = str + 7;
|
||||
|
||||
if (CMD_MATCHES("RDSGEN")) {
|
||||
arg[1] = 0;
|
||||
set_rdsgen(strtoul((char *)arg, NULL, 10));
|
||||
return;
|
||||
}
|
||||
|
||||
if (CMD_MATCHES("PTYNEN")) {
|
||||
arg[1] = 0;
|
||||
set_rds_ptyn_enabled(strtoul((char *)arg, NULL, 10));
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ODA_RTP
|
||||
if (CMD_MATCHES("RTPRUN")) {
|
||||
arg[1] = 0;
|
||||
set_rds_rtplus_flags(strtoul((char *)arg, NULL, 10));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (cmd_len > 6 && str[5] == '=') {
|
||||
cmd = str;
|
||||
cmd[5] = 0;
|
||||
arg = str + 6;
|
||||
if (CMD_MATCHES("LEVEL")) {
|
||||
uint8_t val = strtoul((char *)arg, NULL, 10);
|
||||
val /= 255;
|
||||
val *= 15; /* max value*/
|
||||
#ifdef RDS2
|
||||
set_carrier_volume(1, val);
|
||||
set_carrier_volume(2, val);
|
||||
#else
|
||||
set_carrier_volume(1, val);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (CMD_MATCHES("ECCEN")) {
|
||||
set_rds_ecclic_toggle(arg[0]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/ascii_cmd.h
Normal file
23
src/ascii_cmd.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define CMD_BUFFER_SIZE 255
|
||||
#define CTL_BUFFER_SIZE (CMD_BUFFER_SIZE * 2)
|
||||
#define READ_TIMEOUT_MS 100
|
||||
|
||||
extern void process_ascii_cmd(unsigned char *cmd);
|
||||
33
src/common.h
Normal file
33
src/common.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019-2021 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* common includes for mpxgen */
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* workaround for missing pi definition */
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#define M_2PI (M_PI * 2.0)
|
||||
84
src/control_pipe.c
Normal file
84
src/control_pipe.c
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "ascii_cmd.h"
|
||||
#include "control_pipe.h"
|
||||
|
||||
static int fd;
|
||||
static struct pollfd poller;
|
||||
|
||||
/*
|
||||
* Opens a file (pipe) to be used to control the RDS coder.
|
||||
*/
|
||||
int open_control_pipe(char *filename) {
|
||||
fd = open(filename, O_RDONLY | O_NONBLOCK);
|
||||
if (fd == -1) return -1;
|
||||
|
||||
/* setup the poller */
|
||||
poller.fd = fd;
|
||||
poller.events = POLLIN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Polls the control file (pipe), and if a command is received,
|
||||
* calls process_ascii_cmd.
|
||||
*/
|
||||
void poll_control_pipe() {
|
||||
static unsigned char pipe_buf[CTL_BUFFER_SIZE];
|
||||
static unsigned char cmd_buf[CMD_BUFFER_SIZE];
|
||||
struct timeval timeout;
|
||||
int ret;
|
||||
fd_set set;
|
||||
char *token;
|
||||
|
||||
FD_ZERO(&set);
|
||||
FD_SET(fd, &set);
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = READ_TIMEOUT_MS * 1000;
|
||||
|
||||
/* check for new commands */
|
||||
if (poll(&poller, 1, READ_TIMEOUT_MS) <= 0) return;
|
||||
|
||||
/* return early if there are no new commands */
|
||||
if (poller.revents == 0) return;
|
||||
|
||||
memset(pipe_buf, 0, CTL_BUFFER_SIZE);
|
||||
|
||||
ret = select(fd + 1, &set, NULL, NULL, &timeout);
|
||||
if (ret == -1 || ret == 0) {
|
||||
return;
|
||||
} else {
|
||||
read(fd, pipe_buf, CTL_BUFFER_SIZE - 1);
|
||||
}
|
||||
|
||||
/* handle commands per line this is really good because if were sending text commands very quick after eachother then we can get a rt of for example 'Now its 12:00RT Now its 12:01' */
|
||||
token = strtok((char *)pipe_buf, "\n");
|
||||
while (token != NULL) {
|
||||
memset(cmd_buf, 0, CMD_BUFFER_SIZE);
|
||||
memcpy(cmd_buf, token, CMD_BUFFER_SIZE - 1);
|
||||
token = strtok(NULL, "\n");
|
||||
|
||||
process_ascii_cmd(cmd_buf);
|
||||
}
|
||||
}
|
||||
void close_control_pipe() {
|
||||
if (fd > 0) close(fd);
|
||||
}
|
||||
25
src/control_pipe.h
Normal file
25
src/control_pipe.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
extern int open_control_pipe(char *filename);
|
||||
extern void close_control_pipe();
|
||||
extern void poll_control_pipe();
|
||||
178
src/fm_mpx.c
Normal file
178
src/fm_mpx.c
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "rds.h"
|
||||
#ifdef RDS2
|
||||
#include "rds2.h"
|
||||
#endif
|
||||
#include "fm_mpx.h"
|
||||
#include "osc.h"
|
||||
|
||||
/*
|
||||
* Local oscillator objects
|
||||
* this is where the MPX waveforms are stored
|
||||
*
|
||||
*/
|
||||
static struct osc_t osc_57k;
|
||||
#ifdef RDS2
|
||||
static struct osc_t osc_67k;
|
||||
static struct osc_t osc_71k;
|
||||
static struct osc_t osc_76k;
|
||||
#endif
|
||||
|
||||
static float mpx_vol;
|
||||
|
||||
static uint8_t rdsgen;
|
||||
|
||||
void set_output_volume(float vol) {
|
||||
if (vol > 100.0f) vol = 100.0f;
|
||||
mpx_vol = vol / 100.0f;
|
||||
}
|
||||
|
||||
void set_rdsgen(uint8_t gen) {
|
||||
#ifdef RDS2
|
||||
if (gen > 2) gen = 2;
|
||||
#else
|
||||
if (gen > 1) gen = 1;
|
||||
#endif
|
||||
rdsgen = gen;
|
||||
}
|
||||
|
||||
/* subcarrier volumes */
|
||||
static float volumes[MPX_SUBCARRIER_END] = {
|
||||
0.0f, /* pilot tone: 0% */
|
||||
1.0f, /* RDS: 7.5% modulation */
|
||||
#ifdef RDS2
|
||||
/* RDS2 */
|
||||
0.05f, //66.5 khz 5%
|
||||
0.05f, //71.25 khz 5%
|
||||
0.0f //76 khz 0%
|
||||
#endif
|
||||
};
|
||||
|
||||
void set_carrier_volume(uint8_t carrier, float new_volume) {
|
||||
/* check for valid index */
|
||||
if (carrier >= MPX_SUBCARRIER_END) return;
|
||||
|
||||
/* don't allow levels over 15% */
|
||||
if (new_volume >= 15.0f) new_volume = 15.0f;
|
||||
|
||||
volumes[carrier] = new_volume / 100.0f;
|
||||
}
|
||||
|
||||
void fm_mpx_init(uint32_t sample_rate) {
|
||||
/* initialize the subcarrier oscillators */
|
||||
osc_init(&osc_57k, sample_rate, 57000.0f);
|
||||
rdsgen = 1;
|
||||
#ifdef RDS2
|
||||
osc_init(&osc_67k, sample_rate, 66500.0f);
|
||||
osc_init(&osc_71k, sample_rate, 71250.0f);
|
||||
osc_init(&osc_76k, sample_rate, 76000.0f);
|
||||
rdsgen = 2;
|
||||
#endif
|
||||
}
|
||||
|
||||
void fm_rds_get_frames(float *outbuf, size_t num_frames) {
|
||||
size_t j = 0;
|
||||
float out;
|
||||
|
||||
for (size_t i = 0; i < num_frames; i++) {
|
||||
out = 0.0f;
|
||||
|
||||
#ifdef RDS2
|
||||
out += osc_get_cos(&osc_57k)
|
||||
* get_rds_sample(0, 0)
|
||||
* volumes[MPX_SUBCARRIER_RDS_STREAM_0];
|
||||
#else
|
||||
out += get_rds_sample(0)
|
||||
* volumes[MPX_SUBCARRIER_RDS_STREAM_0];
|
||||
#endif
|
||||
#ifdef RDS2
|
||||
#ifdef RDS2_QUADRATURE_CARRIER
|
||||
/* RDS2 is quadrature phase */
|
||||
|
||||
/* 90 degree shift */
|
||||
if(rdsgen == 2 && volumes[MPX_SUBCARRIER_RDS2_STREAM_1] != 0) {
|
||||
out += osc_get_sin(&osc_67k)
|
||||
* get_rds_sample(1, 0)
|
||||
* volumes[MPX_SUBCARRIER_RDS2_STREAM_1];
|
||||
}
|
||||
|
||||
/* 180 degree shift */
|
||||
if(rdsgen == 2 && volumes[MPX_SUBCARRIER_RDS2_STREAM_2] != 0) {
|
||||
out += -osc_get_cos(&osc_71k)
|
||||
* get_rds_sample(2, 0)
|
||||
* volumes[MPX_SUBCARRIER_RDS2_STREAM_2];
|
||||
}
|
||||
|
||||
/* 270 degree shift */
|
||||
if(rdsgen == 2 && volumes[MPX_SUBCARRIER_RDS2_STREAM_3] != 0) {
|
||||
out += -osc_get_sin(&osc_76k)
|
||||
* get_rds_sample(3, 0)
|
||||
* volumes[MPX_SUBCARRIER_RDS2_STREAM_3];
|
||||
}
|
||||
#else
|
||||
if(rdsgen == 2 && volumes[MPX_SUBCARRIER_RDS2_STREAM_1] != 0) {
|
||||
out += osc_get_cos(&osc_67k)
|
||||
* get_rds_sample(1, 0)
|
||||
* volumes[MPX_SUBCARRIER_RDS2_STREAM_1];
|
||||
}
|
||||
|
||||
if(rdsgen == 2 && volumes[MPX_SUBCARRIER_RDS2_STREAM_1] != 0) {
|
||||
out += osc_get_cos(&osc_71k)
|
||||
* get_rds_sample(2, 0)
|
||||
* volumes[MPX_SUBCARRIER_RDS2_STREAM_2];
|
||||
}
|
||||
|
||||
if(rdsgen == 2 && volumes[MPX_SUBCARRIER_RDS2_STREAM_3] != 0) {
|
||||
out += osc_get_cos(&osc_76k)
|
||||
* get_rds_sample(3, 0)
|
||||
* volumes[MPX_SUBCARRIER_RDS2_STREAM_3];
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* update oscillator */
|
||||
osc_update_pos(&osc_57k);
|
||||
#ifdef RDS2
|
||||
osc_update_pos(&osc_67k);
|
||||
osc_update_pos(&osc_71k);
|
||||
osc_update_pos(&osc_76k);
|
||||
#endif
|
||||
|
||||
/* declipper */
|
||||
out = fminf(+1.0f, out);
|
||||
out = fmaxf(-1.0f, out);
|
||||
|
||||
/* adjust volume and put into channel */
|
||||
if(rdsgen != 0) outbuf[j] = out * mpx_vol;
|
||||
j++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void fm_mpx_exit() {
|
||||
osc_exit(&osc_57k);
|
||||
#ifdef RDS2
|
||||
osc_exit(&osc_67k);
|
||||
osc_exit(&osc_71k);
|
||||
osc_exit(&osc_76k);
|
||||
#endif
|
||||
}
|
||||
45
src/fm_mpx.h
Normal file
45
src/fm_mpx.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* MPX */
|
||||
#define NUM_MPX_FRAMES_IN 512
|
||||
#define NUM_MPX_FRAMES_OUT (NUM_MPX_FRAMES_IN * 2)
|
||||
/*
|
||||
* The sample rate at which the MPX generation runs at
|
||||
*/
|
||||
#define MPX_SAMPLE_RATE RDS_SAMPLE_RATE
|
||||
|
||||
#define OUTPUT_SAMPLE_RATE 192000
|
||||
|
||||
enum mpx_subcarriers {
|
||||
MPX_SUBCARRIER_ST_PILOT,
|
||||
MPX_SUBCARRIER_RDS_STREAM_0,
|
||||
#ifdef RDS2
|
||||
MPX_SUBCARRIER_RDS2_STREAM_1,
|
||||
MPX_SUBCARRIER_RDS2_STREAM_2,
|
||||
MPX_SUBCARRIER_RDS2_STREAM_3,
|
||||
#endif
|
||||
MPX_SUBCARRIER_END
|
||||
};
|
||||
|
||||
extern void fm_mpx_init(uint32_t sample_rate);
|
||||
extern void fm_rds_get_frames(float *outbuf, size_t num_frames);
|
||||
extern void fm_mpx_exit();
|
||||
extern void set_output_volume(float vol);
|
||||
extern void set_rdsgen(uint8_t gen);
|
||||
extern void set_carrier_volume(uint8_t carrier, float new_volume);
|
||||
519
src/lib.c
Normal file
519
src/lib.c
Normal file
@@ -0,0 +1,519 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2021 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "rds.h"
|
||||
#include <time.h>
|
||||
|
||||
/*
|
||||
* Stuff common for both RDS and RDS2
|
||||
*
|
||||
*/
|
||||
|
||||
/* needed workaround implicit declaration (edit: do we need this? ) */
|
||||
extern int nanosleep(const struct timespec *req, struct timespec *rem);
|
||||
|
||||
/* millisecond sleep */
|
||||
void msleep(unsigned long ms) {
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000ul; /* whole seconds */
|
||||
ts.tv_nsec = (ms % 1000ul) * 1000; /* remainder, in nanoseconds */
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
/* just like strlen */
|
||||
size_t _strnlen(const char *s, size_t maxlen) {
|
||||
size_t len = 0;
|
||||
while (s[len] != 0 && len < maxlen)
|
||||
len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
/* unsigned equivalent of strcmp */
|
||||
int ustrcmp(const unsigned char *s1, const unsigned char *s2) {
|
||||
unsigned char c1, c2;
|
||||
|
||||
do {
|
||||
c1 = *s1++;
|
||||
c2 = *s2++;
|
||||
if (c1 == '\0')
|
||||
return c1 - c2;
|
||||
} while (c1 == c2);
|
||||
|
||||
return c1 - c2;
|
||||
}
|
||||
|
||||
#ifdef ODA_RTP
|
||||
static char *rtp_content_types[64] = {
|
||||
/* dummy */
|
||||
"DUMMY_CLASS",
|
||||
/* item */
|
||||
"ITEM.TITLE",
|
||||
"ITEM.ALBUM",
|
||||
"ITEM.TRACKNUMBER",
|
||||
"ITEM.ARTIST",
|
||||
"ITEM.COMPOSITION",
|
||||
"ITEM.MOVEMENT",
|
||||
"ITEM.CONDUCTOR",
|
||||
"ITEM.COMPOSER",
|
||||
"ITEM.BAND",
|
||||
"ITEM.COMMENT",
|
||||
"ITEM.GENRE",
|
||||
/* info */
|
||||
"INFO.NEWS",
|
||||
"INFO.NEWS.LOCAL",
|
||||
"INFO.STOCKMARKET",
|
||||
"INFO.SPORT",
|
||||
"INFO.LOTTERY",
|
||||
"INFO.HOROSCOPE",
|
||||
"INFO.DAILY_DIVERSION",
|
||||
"INFO.HEALTH",
|
||||
"INFO.EVENT",
|
||||
"INFO.SCENE",
|
||||
"INFO.CINEMA",
|
||||
"INFO.TV",
|
||||
"INFO.DATE_TIME",
|
||||
"INFO.WEATHER",
|
||||
"INFO.TRAFFIC",
|
||||
"INFO.ALARM",
|
||||
"INFO.ADVERTISEMENT",
|
||||
"INFO.URL",
|
||||
"INFO.OTHER",
|
||||
/* program */
|
||||
"STATIONNAME.SHORT",
|
||||
"STATIONNAME.LONG",
|
||||
"PROGRAMME.NOW",
|
||||
"PROGRAMME.NEXT",
|
||||
"PROGRAMME.PART",
|
||||
"PROGRAMME.HOST",
|
||||
"PROGRAMME.EDITORIAL_STAFF",
|
||||
"PROGRAMME.FREQUENCY",
|
||||
"PROGRAMME.HOMEPAGE",
|
||||
"PROGRAMME.SUBCHANNEL",
|
||||
/* interactivity */
|
||||
"PHONE.HOTLINE",
|
||||
"PHONE.STUDIO",
|
||||
"PHONE.OTHER",
|
||||
"SMS.STUDIO",
|
||||
"SMS.OTHER",
|
||||
"EMAIL.HOTLINE",
|
||||
"EMAIL.STUDIO",
|
||||
"EMAIL.OTHER",
|
||||
"MMS.OTHER",
|
||||
"CHAT",
|
||||
"CHAT.CENTRE",
|
||||
"VOTE.QUESTION",
|
||||
"VOTE.CENTRE",
|
||||
/* rfu */
|
||||
"RFU_1",
|
||||
"RFU_2",
|
||||
/* private classes */
|
||||
"PRIVATE_1",
|
||||
"PRIVATE_2",
|
||||
"PRIVATE_3",
|
||||
/* descriptor */
|
||||
"PLACE",
|
||||
"APPOINTMENT",
|
||||
"IDENTIFIER",
|
||||
"PURCHASE",
|
||||
"GET_DATA"
|
||||
};
|
||||
|
||||
uint8_t get_rtp_tag_id(char *rtp_tag_name) {
|
||||
uint8_t tag_id = 0;
|
||||
for (uint8_t i = 0; i < 64; i++) {
|
||||
if (strcmp(rtp_tag_name, rtp_content_types[i]) == 0) {
|
||||
tag_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return tag_id;
|
||||
}
|
||||
|
||||
char *get_rtp_tag_name(uint8_t rtp_tag) {
|
||||
if (rtp_tag > 63) rtp_tag = 0;
|
||||
return rtp_content_types[rtp_tag];
|
||||
}
|
||||
#endif
|
||||
static uint16_t offset_words[] = {
|
||||
0x0FC, /* A */
|
||||
0x198, /* B */
|
||||
0x168, /* C */
|
||||
0x1B4, /* D */
|
||||
0x350 /* C' */
|
||||
};
|
||||
|
||||
/* CRC-16 ITU-T/CCITT checkword calculation */
|
||||
/* wanna ask where is this used */
|
||||
uint16_t crc16(uint8_t *data, size_t len) {
|
||||
uint16_t crc = 0xffff;
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
crc = (crc >> 8) | (crc << 8);
|
||||
crc ^= data[i];
|
||||
crc ^= (crc & 0xff) >> 4;
|
||||
crc ^= (crc << 8) << 4;
|
||||
crc ^= ((crc & 0xff) << 4) << 1;
|
||||
}
|
||||
|
||||
return crc ^ 0xffff;
|
||||
}
|
||||
|
||||
/* Calculate the checkword for each block and emit the bits */
|
||||
#ifdef RDS2
|
||||
void add_checkwords(uint16_t *blocks, uint8_t *bits, bool rds2)
|
||||
#else
|
||||
void add_checkwords(uint16_t *blocks, uint8_t *bits)
|
||||
#endif
|
||||
{
|
||||
size_t i, j;
|
||||
uint8_t bit, msb;
|
||||
uint16_t block, block_crc, check, offset_word;
|
||||
bool group_type_b = false;
|
||||
#ifdef RDS2
|
||||
bool tunneling_type_b = false;
|
||||
#endif
|
||||
|
||||
/* if b11 is 1, then type B */
|
||||
#ifdef RDS2
|
||||
if (!rds2 && IS_TYPE_B(blocks))
|
||||
#else
|
||||
if (IS_TYPE_B(blocks))
|
||||
#endif
|
||||
group_type_b = true;
|
||||
|
||||
#ifdef RDS2
|
||||
/* for when version B groups are coded in RDS2 */
|
||||
if (rds2 && IS_TUNNELING(blocks) && IS_TYPE_B(blocks))
|
||||
tunneling_type_b = true;
|
||||
#endif
|
||||
|
||||
for (i = 0; i < GROUP_LENGTH; i++) {
|
||||
#ifdef RDS2
|
||||
/*
|
||||
* If tunneling type B groups use offset
|
||||
* word C' for block 3
|
||||
*/
|
||||
if (rds2 && i == 2 && tunneling_type_b) {
|
||||
offset_word = offset_words[4];
|
||||
} else
|
||||
#endif
|
||||
/* Group version B needs C' for block 3 */
|
||||
if (i == 2 && group_type_b) {
|
||||
offset_word = offset_words[4];
|
||||
} else {
|
||||
offset_word = offset_words[i];
|
||||
}
|
||||
|
||||
block = blocks[i];
|
||||
|
||||
/* Classical CRC computation */
|
||||
block_crc = 0;
|
||||
for (j = 0; j < BLOCK_SIZE; j++) {
|
||||
bit = (block & (INT16_15 >> j)) != 0;
|
||||
msb = (block_crc >> (POLY_DEG - 1)) & 1;
|
||||
block_crc <<= 1;
|
||||
if (msb ^ bit) block_crc ^= POLY;
|
||||
*bits++ = bit;
|
||||
}
|
||||
check = block_crc ^ offset_word;
|
||||
for (j = 0; j < POLY_DEG; j++) {
|
||||
*bits++ = (check & ((1 << (POLY_DEG - 1)) >> j)) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* AF stuff
|
||||
*/
|
||||
uint8_t add_rds_af(struct rds_af_t *af_list, float freq) {
|
||||
uint16_t af;
|
||||
uint8_t entries_reqd = 1; /* default for FM */
|
||||
|
||||
/*
|
||||
* check how many slots are required for the frequency
|
||||
*
|
||||
* LF/MF (AM) needs 2 entries: one for the
|
||||
* AM follows code and the freq code itself
|
||||
*/
|
||||
if (freq < 87.6f || freq > 107.9f) { /* is LF/MF */
|
||||
entries_reqd = 2;
|
||||
}
|
||||
|
||||
/* check if the AF list is full */
|
||||
if (af_list->num_afs + entries_reqd > MAX_AFS) {
|
||||
/* Too many AF entries */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* check if new frequency is valid */
|
||||
if (freq >= 87.6f && freq <= 107.9f) { /* FM */
|
||||
af = (uint16_t)(freq * 10.0f) - 875;
|
||||
af_list->afs[af_list->num_entries] = af;
|
||||
af_list->num_entries += 1;
|
||||
} else if (freq >= 153.0f && freq <= 279.0f) { /* LF */
|
||||
af = (uint16_t)(freq - 153.0f) / 9 + 1;
|
||||
af_list->afs[af_list->num_entries + 0] = AF_CODE_LFMF_FOLLOWS;
|
||||
af_list->afs[af_list->num_entries + 1] = af;
|
||||
af_list->num_entries += 2;
|
||||
} else if (freq >= 531.0f && freq <= 1602.0f) { /* AM 9 kHz spacing */
|
||||
af = (uint16_t)(freq - 531.0f) / 9 + 16;
|
||||
af_list->afs[af_list->num_entries + 0] = AF_CODE_LFMF_FOLLOWS;
|
||||
af_list->afs[af_list->num_entries + 1] = af;
|
||||
af_list->num_entries += 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
|
||||
af_list->num_afs++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *show_af_list(struct rds_af_t af_list) {
|
||||
float freq;
|
||||
bool is_lfmf = false;
|
||||
static char outstr[255];
|
||||
uint8_t outstrlen = 0;
|
||||
|
||||
outstrlen += sprintf(outstr, "AF: %u,", af_list.num_afs);
|
||||
|
||||
for (uint8_t i = 0; i < af_list.num_entries; i++) {
|
||||
if (af_list.afs[i] == AF_CODE_LFMF_FOLLOWS) {
|
||||
/* The next AF will be for LF/MF */
|
||||
is_lfmf = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_lfmf) {
|
||||
if (af_list.afs[i] >= 1 && af_list.afs[i] <= 15) { /* LF */
|
||||
freq = 153.0f + ((float)(af_list.afs[i] - 1) * 9.0f);
|
||||
outstrlen += sprintf(outstr + outstrlen, " (LF)%.0f", freq);
|
||||
} else { /*if (af_list.afs[i] >= 16 && af_list.afs[i] <= 135) {*/ /* MF */
|
||||
freq = 531.0f + ((float)(af_list.afs[i] - 16) * 9.0f);
|
||||
outstrlen += sprintf(outstr + outstrlen, " (MF)%.0f", freq);
|
||||
}
|
||||
is_lfmf = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* FM */
|
||||
freq = (float)((uint16_t)af_list.afs[i] + 875) / 10.0f;
|
||||
outstrlen += sprintf(outstr + outstrlen, " %.1f", freq);
|
||||
}
|
||||
|
||||
return outstr;
|
||||
}
|
||||
|
||||
/*
|
||||
* UTF-8 to RDS char set converter
|
||||
*
|
||||
* Translates certain chars into their RDS equivalents
|
||||
* NOTE!! Only applies to PS, RT and PTYN. LPS uses UTF-8 (SCB = 1)
|
||||
*
|
||||
*/
|
||||
#define XLATSTRLEN 255
|
||||
unsigned char *xlat(unsigned char *str) {
|
||||
static unsigned char new_str[XLATSTRLEN];
|
||||
uint8_t i = 0;
|
||||
|
||||
while (*str != 0 && i < XLATSTRLEN) {
|
||||
switch (*str) {
|
||||
case 0xc2:
|
||||
str++;
|
||||
switch (*str) {
|
||||
case 0xa1: new_str[i] = 0x8e; break; /* INVERTED EXCLAMATION MARK */
|
||||
case 0xa3: new_str[i] = 0xaa; break; /* POUND SIGN */
|
||||
case 0xa7: new_str[i] = 0xbf; break; /* SECTION SIGN */
|
||||
case 0xa9: new_str[i] = 0xa2; break; /* COPYRIGHT SIGN */
|
||||
case 0xaa: new_str[i] = 0xa0; break; /* FEMININE ORDINAL INDICATOR */
|
||||
case 0xb0: new_str[i] = 0xbb; break; /* DEGREE SIGN */
|
||||
case 0xb1: new_str[i] = 0xb4; break; /* PLUS-MINUS SIGN */
|
||||
case 0xb2: new_str[i] = 0xb2; break; /* SUPERSCRIPT TWO */
|
||||
case 0xb3: new_str[i] = 0xb3; break; /* SUPERSCRIPT THREE */
|
||||
case 0xb5: new_str[i] = 0xb8; break; /* MIKRO SIGN */
|
||||
case 0xb9: new_str[i] = 0xb1; break; /* SUPERSCRIPT ONE */
|
||||
case 0xba: new_str[i] = 0xb0; break; /* MASCULINE ORDINAL INDICATOR */
|
||||
case 0xbc: new_str[i] = 0xbc; break; /* VULGAR FRACTION ONE QUARTER */
|
||||
case 0xbd: new_str[i] = 0xbd; break; /* VULGAR FRACTION ONE HALF */
|
||||
case 0xbe: new_str[i] = 0xbe; break; /* VULGAR FRACTION THREE QUARTERS */
|
||||
case 0xbf: new_str[i] = 0xb9; break; /* INVERTED QUESTION MARK */
|
||||
default: new_str[i] = ' '; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc3:
|
||||
str++;
|
||||
switch (*str) {
|
||||
case 0x80: new_str[i] = 0xc1; break; /* LATIN CAPITAL LETTER A WITH GRAVE */
|
||||
case 0x81: new_str[i] = 0xc0; break; /* LATIN CAPITAL LETTER A WITH ACUTE */
|
||||
case 0x82: new_str[i] = 0xd0; break; /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
|
||||
case 0x83: new_str[i] = 0xe0; break; /* LATIN CAPITAL LETTER A WITH TILDE */
|
||||
case 0x84: new_str[i] = 0xd1; break; /* LATIN CAPITAL LETTER A WITH DIAERESIS */
|
||||
case 0x85: new_str[i] = 0xe1; break; /* LATIN CAPITAL LETTER A WITH RING ABOVE */
|
||||
case 0x86: new_str[i] = 0xe2; break; /* LATIN CAPITAL LETTER AE */
|
||||
case 0x87: new_str[i] = 0x8b; break; /* LATIN CAPITAL LETTER C WITH CEDILLA */
|
||||
case 0x88: new_str[i] = 0xc3; break; /* LATIN CAPITAL LETTER E WITH GRAVE */
|
||||
case 0x89: new_str[i] = 0xc2; break; /* LATIN CAPITAL LETTER E WITH ACUTE */
|
||||
case 0x8a: new_str[i] = 0xd2; break; /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
|
||||
case 0x8b: new_str[i] = 0xd3; break; /* LATIN CAPITAL LETTER E WITH DIAERESIS */
|
||||
case 0x8c: new_str[i] = 0xc5; break; /* LATIN CAPITAL LETTER I WITH GRAVE */
|
||||
case 0x8d: new_str[i] = 0xc4; break; /* LATIN CAPITAL LETTER I WITH ACUTE */
|
||||
case 0x8e: new_str[i] = 0xd4; break; /* LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
|
||||
case 0x8f: new_str[i] = 0xd5; break; /* LATIN CAPITAL LETTER I WITH DIAERESIS */
|
||||
case 0x90: new_str[i] = 0xce; break; /* LATIN CAPITAL LETTER ETH */
|
||||
case 0x91: new_str[i] = 0x8a; break; /* LATIN CAPITAL LETTER N WITH TILDE */
|
||||
case 0x92: new_str[i] = 0xc7; break; /* LATIN CAPITAL LETTER O WITH GRAVE */
|
||||
case 0x93: new_str[i] = 0xc6; break; /* LATIN CAPITAL LETTER O WITH ACUTE */
|
||||
case 0x94: new_str[i] = 0xd6; break; /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
|
||||
case 0x95: new_str[i] = 0xe6; break; /* LATIN CAPITAL LETTER O WITH TILDE */
|
||||
case 0x96: new_str[i] = 0xd7; break; /* LATIN CAPITAL LETTER O WITH DIAERESIS */
|
||||
case 0x98: new_str[i] = 0xe7; break; /* LATIN CAPITAL LETTER O WITH STROKE */
|
||||
case 0x99: new_str[i] = 0xc9; break; /* LATIN CAPITAL LETTER U WITH GRAVE */
|
||||
case 0x9a: new_str[i] = 0xc8; break; /* LATIN CAPITAL LETTER U WITH ACUTE */
|
||||
case 0x9b: new_str[i] = 0xd8; break; /* LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
|
||||
case 0x9c: new_str[i] = 0xd9; break; /* LATIN CAPITAL LETTER U WITH DIAERESIS */
|
||||
case 0x9d: new_str[i] = 0xe5; break; /* LATIN CAPITAL LETTER Y WITH ACUTE */
|
||||
case 0x9e: new_str[i] = 0xe8; break; /* LATIN CAPITAL LETTER THORN */
|
||||
case 0xa0: new_str[i] = 0x81; break; /* LATIN SMALL LETTER A WITH GRAVE */
|
||||
case 0xa1: new_str[i] = 0x80; break; /* LATIN SMALL LETTER A WITH ACUTE */
|
||||
case 0xa2: new_str[i] = 0x90; break; /* LATIN SMALL LETTER A WITH CIRCUMFLEX */
|
||||
case 0xa3: new_str[i] = 0xf0; break; /* LATIN SMALL LETTER A WITH TILDE */
|
||||
case 0xa4: new_str[i] = 0x91; break; /* LATIN SMALL LETTER A WITH DIAERESIS */
|
||||
case 0xa5: new_str[i] = 0xf1; break; /* LATIN SMALL LETTER A WITH RING ABOVE */
|
||||
case 0xa6: new_str[i] = 0xf2; break; /* LATIN SMALL LETTER AE */
|
||||
case 0xa7: new_str[i] = 0x9b; break; /* LATIN SMALL LETTER C WITH CEDILLA */
|
||||
case 0xa8: new_str[i] = 0x83; break; /* LATIN SMALL LETTER E WITH GRAVE */
|
||||
case 0xa9: new_str[i] = 0x82; break; /* LATIN SMALL LETTER E WITH ACUTE */
|
||||
case 0xaa: new_str[i] = 0x92; break; /* LATIN SMALL LETTER E WITH CIRCUMFLEX */
|
||||
case 0xab: new_str[i] = 0x93; break; /* LATIN SMALL LETTER E WITH DIAERESIS */
|
||||
case 0xac: new_str[i] = 0x85; break; /* LATIN SMALL LETTER I WITH GRAVE */
|
||||
case 0xad: new_str[i] = 0x84; break; /* LATIN SMALL LETTER I WITH ACUTE */
|
||||
case 0xae: new_str[i] = 0x94; break; /* LATIN SMALL LETTER I WITH CIRCUMFLEX */
|
||||
case 0xaf: new_str[i] = 0x95; break; /* LATIN SMALL LETTER I WITH DIAERESIS */
|
||||
case 0xb0: new_str[i] = 0xef; break; /* LATIN SMALL LETTER ETH */
|
||||
case 0xb1: new_str[i] = 0x9a; break; /* LATIN SMALL LETTER N WITH TILDE */
|
||||
case 0xb2: new_str[i] = 0x87; break; /* LATIN SMALL LETTER O WITH GRAVE */
|
||||
case 0xb3: new_str[i] = 0x86; break; /* LATIN SMALL LETTER O WITH ACUTE */
|
||||
case 0xb4: new_str[i] = 0x96; break; /* LATIN SMALL LETTER O WITH CIRCUMFLEX */
|
||||
case 0xb5: new_str[i] = 0xf6; break; /* LATIN SMALL LETTER O WITH TILDE */
|
||||
case 0xb6: new_str[i] = 0x97; break; /* LATIN SMALL LETTER O WITH DIAERESIS */
|
||||
case 0xb7: new_str[i] = 0xba; break; /* DIVISION SIGN */
|
||||
case 0xb8: new_str[i] = 0xf7; break; /* LATIN SMALL LETTER O WITH STROKE */
|
||||
case 0xb9: new_str[i] = 0x89; break; /* LATIN SMALL LETTER U WITH GRAVE */
|
||||
case 0xba: new_str[i] = 0x88; break; /* LATIN SMALL LETTER U WITH ACUTE */
|
||||
case 0xbb: new_str[i] = 0x98; break; /* LATIN SMALL LETTER U WITH CIRCUMFLEX */
|
||||
case 0xbc: new_str[i] = 0x99; break; /* LATIN SMALL LETTER U WITH DIAERESIS */
|
||||
case 0xbd: new_str[i] = 0xf5; break; /* LATIN SMALL LETTER Y WITH ACUTE */
|
||||
case 0xbe: new_str[i] = 0xf8; break; /* LATIN SMALL LETTER THORN */
|
||||
default: new_str[i] = ' '; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc4:
|
||||
str++;
|
||||
switch (*str) {
|
||||
case 0x87: new_str[i] = 0xfb; break; /* LATIN SMALL LETTER C WITH ACUTE */
|
||||
case 0x8c: new_str[i] = 0xcb; break; /* LATIN CAPITAL LETTER C WITH CARON */
|
||||
case 0x8d: new_str[i] = 0xdb; break; /* LATIN SMALL LETTER C WITH CARON */
|
||||
case 0x91: new_str[i] = 0xde; break; /* LATIN SMALL LETTER D WITH STROKE */
|
||||
case 0x9b: new_str[i] = 0xa5; break; /* LATIN SMALL LETTER E WITH CARON */
|
||||
case 0xb0: new_str[i] = 0xb5; break; /* LATIN CAPITAL LETTER I WITH DOT ABOVE */
|
||||
case 0xb1: new_str[i] = 0x9f; break; /* LATIN SMALL LETTER DOTLESS I */
|
||||
case 0xb2: new_str[i] = 0x8f; break; /* LATIN CAPITAL LIGATURE IJ */
|
||||
case 0xb3: new_str[i] = 0x9f; break; /* LATIN SMALL LIGATURE IJ */
|
||||
case 0xbf: new_str[i] = 0xcf; break; /* LATIN CAPITAL LETTER L WITH MIDDLE DOT */
|
||||
default: new_str[i] = ' '; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc5:
|
||||
str++;
|
||||
switch (*str) {
|
||||
case 0x80: new_str[i] = 0xdf; break; /* LATIN SMALL LETTER L WITH MIDDLE DOT */
|
||||
case 0x84: new_str[i] = 0xb6; break; /* LATIN SMALL LETTER N WITH ACUTE */
|
||||
case 0x88: new_str[i] = 0xa6; break; /* LATIN SMALL LETTER N WITH CARON */
|
||||
case 0x8a: new_str[i] = 0xe9; break; /* LATIN CAPITAL LETTER ENG */
|
||||
case 0x8b: new_str[i] = 0xf9; break; /* LATIN SMALL LETTER ENG */
|
||||
case 0x91: new_str[i] = 0xa7; break; /* LATIN SMALL LETTER O WITH DOUBLE ACUTE */
|
||||
case 0x92: new_str[i] = 0xe3; break; /* LATIN CAPITAL LIGATURE OE */
|
||||
case 0x93: new_str[i] = 0xf3; break; /* LATIN SMALL LIGATURE OE */
|
||||
case 0x94: new_str[i] = 0xea; break; /* LATIN CAPITAL LETTER R WITH ACUTE */
|
||||
case 0x95: new_str[i] = 0xfa; break; /* LATIN SMALL LETTER R WITH ACUTE */
|
||||
case 0x98: new_str[i] = 0xca; break; /* LATIN CAPITAL LETTER R WITH CARON */
|
||||
case 0x99: new_str[i] = 0xda; break; /* LATIN SMALL LETTER R WITH CARON */
|
||||
case 0x9a: new_str[i] = 0xec; break; /* LATIN CAPITAL LETTER S WITH ACUTE */
|
||||
case 0x9b: new_str[i] = 0xfc; break; /* LATIN SMALL LETTER S WITH ACUTE */
|
||||
case 0x9e: new_str[i] = 0x8c; break; /* LATIN CAPITAL LETTER S WITH CEDILLA */
|
||||
case 0x9f: new_str[i] = 0x9c; break; /* LATIN SMALL LETTER S WITH CEDILLA */
|
||||
case 0xa0: new_str[i] = 0xcc; break; /* LATIN CAPITAL LETTER S WITH CARON */
|
||||
case 0xa1: new_str[i] = 0xdc; break; /* LATIN SMALL LETTER S WITH CARON */
|
||||
case 0xa6: new_str[i] = 0xee; break; /* LATIN CAPITAL LETTER T WITH STROKE */
|
||||
case 0xa7: new_str[i] = 0xfe; break; /* LATIN SMALL LETTER T WITH STROKE */
|
||||
case 0xb1: new_str[i] = 0xb7; break; /* LATIN SMALL LETTER U WITH DOUBLE ACUTE */
|
||||
case 0xb5: new_str[i] = 0xf4; break; /* LATIN SMALL LETTER W WITH CIRCUMFLEX */
|
||||
case 0xb7: new_str[i] = 0xe4; break; /* LATIN SMALL LETTER Y WITH CIRCUMFLEX */
|
||||
case 0xb9: new_str[i] = 0xed; break; /* LATIN CAPITAL LETTER Z WITH ACUTE */
|
||||
case 0xba: new_str[i] = 0xfd; break; /* LATIN SMALL LETTER Z WITH ACUTE */
|
||||
case 0xbd: new_str[i] = 0xcd; break; /* LATIN CAPITAL LETTER Z WITH CARON */
|
||||
case 0xbe: new_str[i] = 0xdd; break; /* LATIN SMALL LETTER Z WITH CARON */
|
||||
default: new_str[i] = ' '; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc7:
|
||||
str++;
|
||||
switch (*str) {
|
||||
case 0xa6: new_str[i] = 0xa4; break; /* LATIN CAPITAL LETTER G WITH CARON */
|
||||
case 0xa7: new_str[i] = 0x9d; break; /* LATIN SMALL LETTER G WITH CARON */
|
||||
default: new_str[i] = ' '; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xce:
|
||||
str++;
|
||||
switch (*str) {
|
||||
case 0xb1: new_str[i] = 0xa1; break; /* GREEK SMALL LETTER ALPHA */
|
||||
case 0xb2: new_str[i] = 0x8d; break; /* GREEK SMALL LETTER BETA */
|
||||
default: new_str[i] = ' '; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xcf:
|
||||
str++;
|
||||
switch (*str) {
|
||||
case 0x80: new_str[i] = 0xa8; break; /* GREEK SMALL LETTER PI */
|
||||
default: new_str[i] = ' '; break;
|
||||
}
|
||||
break;
|
||||
|
||||
default: /* 0-127 or unknown */
|
||||
switch (*str) {
|
||||
case '$': new_str[i] = 0xab; break; /* DOLLAR SIGN */
|
||||
default: new_str[i] = *str; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
str++;
|
||||
}
|
||||
|
||||
new_str[i] = 0;
|
||||
return new_str;
|
||||
}
|
||||
34
src/lib.h
Normal file
34
src/lib.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2021 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
extern void msleep(unsigned long ms);
|
||||
|
||||
extern size_t _strnlen(const char *s, size_t maxlen);
|
||||
extern int ustrcmp(const unsigned char *s1, const unsigned char *s2);
|
||||
|
||||
extern uint8_t get_rtp_tag_id(char *rtp_tag_name);
|
||||
extern char *get_rtp_tag_name(uint8_t rtp_tag);
|
||||
#ifdef RDS2
|
||||
extern void add_checkwords(uint16_t *blocks, uint8_t *bits, bool rds2);
|
||||
#else
|
||||
extern void add_checkwords(uint16_t *blocks, uint8_t *bits);
|
||||
#endif
|
||||
extern uint8_t add_rds_af(struct rds_af_t *af_list, float freq);
|
||||
extern char *show_af_list(struct rds_af_t af_list);
|
||||
extern uint16_t crc16(uint8_t *data, size_t len);
|
||||
extern unsigned char *xlat(unsigned char *str);
|
||||
15
src/make-station-logo.sh
Executable file
15
src/make-station-logo.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Create station logo data to build into the mpxgen executable for RDS2
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <logo file>"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ -f "$1" ]; then
|
||||
echo 'static const unsigned char station_logo[] = {' > rds2_image_data.c
|
||||
xxd -i < "$1" >> rds2_image_data.c
|
||||
echo '};' >> rds2_image_data.c
|
||||
echo 'static const unsigned int station_logo_len = sizeof(station_logo);' >> rds2_image_data.c
|
||||
fi
|
||||
354
src/minirds.c
Normal file
354
src/minirds.c
Normal file
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include <signal.h>
|
||||
#include <getopt.h>
|
||||
#include <pthread.h>
|
||||
#include <pulse/simple.h>
|
||||
|
||||
#include "rds.h"
|
||||
#include "fm_mpx.h"
|
||||
#include "control_pipe.h"
|
||||
#include "resampler.h"
|
||||
#include "lib.h"
|
||||
#include "ascii_cmd.h"
|
||||
|
||||
static uint8_t stop_rds;
|
||||
|
||||
static void stop() {
|
||||
printf("Received an stopping signal\n");
|
||||
stop_rds = 1;
|
||||
}
|
||||
|
||||
static inline void float2char2channel(
|
||||
float *inbuf, char *outbuf, size_t frames) {
|
||||
uint16_t j = 0, k = 0;
|
||||
|
||||
for (uint16_t i = 0; i < frames; i++) {
|
||||
int16_t sample = lroundf(inbuf[j++] * 16383.5f);
|
||||
|
||||
// Direct memory write is more efficient for byte conversion
|
||||
outbuf[k++] = sample & 0xFF;
|
||||
outbuf[k++] = (sample >> 8) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
/* threads */
|
||||
static void *control_pipe_worker() {
|
||||
while (!stop_rds) {
|
||||
poll_control_pipe();
|
||||
msleep(READ_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
close_control_pipe();
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
static void show_help(char *name) {
|
||||
printf(
|
||||
"This is MiniRDS, a lightweight RDS encoder.\n"
|
||||
"Version %f\n"
|
||||
"\n"
|
||||
"Usage: %s [options]\n"
|
||||
"\n"
|
||||
" -i,--pi Program Identification code\n"
|
||||
" [default: 3073]\n"
|
||||
" -s,--ps Program Service name\n"
|
||||
" [default: \"radio95\"]\n"
|
||||
" -r,--rt1 Radio Text 1\n"
|
||||
" [default: (nothing)]\n"
|
||||
" -p,--pty Program Type\n"
|
||||
" [default: 0]\n"
|
||||
" -T,--tp Traffic Program\n"
|
||||
" [default: 0]\n"
|
||||
" -A,--af Alternative Frequency (FM/LF/MF)\n"
|
||||
" (more than one AF may be passed)\n"
|
||||
" -P,--ptyn Program Type Name\n"
|
||||
" -l,--lps Long PS\n"
|
||||
" -e,--ecc ECC code\n"
|
||||
" -d,--di DI code\n"
|
||||
" -C,--ctl FIFO control pipe\n"
|
||||
#ifdef RDS2
|
||||
" -I,--img RDS2 Logo path\n"
|
||||
#endif
|
||||
" -h,--help Show this help text and exit\n"
|
||||
" -v,--version Show version and exit\n"
|
||||
"\n",
|
||||
VERSION,
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
static void show_version() {
|
||||
printf("MiniRDS version (radio95 Edit) %f\n", VERSION);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int opt;
|
||||
char control_pipe[51];
|
||||
struct rds_params_t rds_params = {
|
||||
.ps = "radio95",
|
||||
.rt1 = "",
|
||||
.pi = 0x305F,
|
||||
.ecc = 0xE2,
|
||||
.lps = "radio95 - Radio Nowotomyskie"
|
||||
};
|
||||
float volume = 100.0f;
|
||||
|
||||
/* buffers */
|
||||
float *mpx_buffer;
|
||||
float *out_buffer;
|
||||
char *dev_out;
|
||||
|
||||
int8_t r;
|
||||
size_t frames;
|
||||
|
||||
/* SRC */
|
||||
SRC_STATE *src_state;
|
||||
SRC_DATA src_data;
|
||||
|
||||
/* PASIMPLE */
|
||||
pa_simple *device;
|
||||
pa_sample_spec format;
|
||||
|
||||
/* pthread */
|
||||
pthread_attr_t attr;
|
||||
pthread_t control_pipe_thread;
|
||||
pthread_mutex_t control_pipe_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_cond_t control_pipe_cond;
|
||||
|
||||
const char *short_opt = "R:i:s:r:p:T:A:P:l:e:L:d:C:"
|
||||
#ifdef RDS2
|
||||
"I:"
|
||||
#endif
|
||||
"hv";
|
||||
|
||||
struct option long_opt[] =
|
||||
{
|
||||
{"rds", required_argument, NULL, 'R'},
|
||||
{"pi", required_argument, NULL, 'i'},
|
||||
{"ps", required_argument, NULL, 's'},
|
||||
{"rt1", required_argument, NULL, 'r'},
|
||||
{"pty", required_argument, NULL, 'p'},
|
||||
{"tp", required_argument, NULL, 'T'},
|
||||
{"af", required_argument, NULL, 'A'},
|
||||
{"ptyn", required_argument, NULL, 'P'},
|
||||
{"lps", required_argument, NULL, 'l'},
|
||||
{"ecc", required_argument, NULL, 'e'},
|
||||
{"lic", required_argument, NULL, 'L'},
|
||||
{"di", required_argument, NULL, 'd'},
|
||||
{"ctl", required_argument, NULL, 'C'},
|
||||
#ifdef RDS2
|
||||
{"img", required_argument, NULL, 'I'},
|
||||
#endif
|
||||
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"version", no_argument, NULL, 'v'},
|
||||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
memset(control_pipe, 0, 51);
|
||||
|
||||
keep_parsing_opts:
|
||||
|
||||
opt = getopt_long(argc, argv, short_opt, long_opt, NULL);
|
||||
if (opt == -1) goto done_parsing_opts;
|
||||
|
||||
switch (opt) {
|
||||
case 'i': /* pi */
|
||||
rds_params.pi = strtoul(optarg, NULL, 16);
|
||||
break;
|
||||
|
||||
case 's': /* ps */
|
||||
memcpy(rds_params.ps, xlat((unsigned char *)optarg), PS_LENGTH);
|
||||
break;
|
||||
|
||||
case 'r': /* rt1 */
|
||||
memcpy(rds_params.rt1, xlat((unsigned char *)optarg), RT_LENGTH);
|
||||
break;
|
||||
|
||||
case 'p': /* pty */
|
||||
rds_params.pty = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'T': /* tp */
|
||||
rds_params.tp = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'A': /* af */
|
||||
if (add_rds_af(&rds_params.af, strtof(optarg, NULL)) == 1) return 1;
|
||||
break;
|
||||
|
||||
case 'P': /* ptyn */
|
||||
memcpy(rds_params.ptyn, xlat((unsigned char *)optarg), PTYN_LENGTH);
|
||||
break;
|
||||
|
||||
case 'l': /* lps */
|
||||
memcpy(rds_params.lps, (unsigned char *)optarg, LPS_LENGTH);
|
||||
break;
|
||||
|
||||
case 'e': /* ecc */
|
||||
rds_params.ecc = strtoul(optarg, NULL, 16);
|
||||
break;
|
||||
|
||||
case 'L': /* lic */
|
||||
rds_params.lic = strtoul(optarg, NULL, 16);
|
||||
break;
|
||||
|
||||
case 'C': /* ctl */
|
||||
memcpy(control_pipe, optarg, 50);
|
||||
break;
|
||||
|
||||
#ifdef RDS2
|
||||
case 'I': /* img */
|
||||
memcpy(rds_params.rds2_image_path, optarg, 50);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 'v': /* version */
|
||||
show_version();
|
||||
return 0;
|
||||
|
||||
case 'h': /* help */
|
||||
case '?':
|
||||
default:
|
||||
show_help(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
goto keep_parsing_opts;
|
||||
|
||||
done_parsing_opts:
|
||||
|
||||
/* Initialize pthread stuff */
|
||||
pthread_mutex_init(&control_pipe_mutex, NULL);
|
||||
pthread_cond_init(&control_pipe_cond, NULL);
|
||||
pthread_attr_init(&attr);
|
||||
|
||||
/* Setup buffers */
|
||||
mpx_buffer = malloc(NUM_MPX_FRAMES_IN * 2 * sizeof(float));
|
||||
out_buffer = malloc(NUM_MPX_FRAMES_OUT * 2 * sizeof(float));
|
||||
dev_out = malloc(NUM_MPX_FRAMES_OUT * 2 * sizeof(int16_t) * sizeof(char));
|
||||
|
||||
/* Gracefully stop the encoder on SIGINT or SIGTERM */
|
||||
signal(SIGINT, stop);
|
||||
signal(SIGTERM, stop);
|
||||
|
||||
/* Initialize the baseband generator */
|
||||
fm_mpx_init(MPX_SAMPLE_RATE);
|
||||
set_output_volume(volume);
|
||||
|
||||
/* Initialize the RDS modulator */
|
||||
init_rds_encoder(rds_params);
|
||||
|
||||
/* PASIMPLE format */
|
||||
format.format = PA_SAMPLE_S16LE;
|
||||
format.channels = 1;
|
||||
format.rate = OUTPUT_SAMPLE_RATE;
|
||||
|
||||
device = pa_simple_new(
|
||||
NULL, // Default PulseAudio server
|
||||
"MiniRDS", // Application name
|
||||
PA_STREAM_PLAYBACK, // Direction (playback)
|
||||
"RDS", // Default device
|
||||
"RDS Generator", // Stream description
|
||||
&format, // Sample format
|
||||
NULL, // Default channel map
|
||||
NULL, // Default buffering attributes
|
||||
NULL // Error variable
|
||||
);
|
||||
if (device == NULL) {
|
||||
fprintf(stderr, "Error: cannot open sound device.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* SRC out (MPX -> output) */
|
||||
memset(&src_data, 0, sizeof(SRC_DATA));
|
||||
src_data.input_frames = NUM_MPX_FRAMES_IN;
|
||||
src_data.output_frames = NUM_MPX_FRAMES_OUT;
|
||||
src_data.src_ratio =
|
||||
(double)OUTPUT_SAMPLE_RATE / (double)MPX_SAMPLE_RATE;
|
||||
src_data.data_in = mpx_buffer;
|
||||
src_data.data_out = out_buffer;
|
||||
|
||||
r = resampler_init(&src_state, 1);
|
||||
if (r < 0) {
|
||||
fprintf(stderr, "Could not create output resampler.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Initialize the control pipe reader */
|
||||
if (control_pipe[0]) {
|
||||
if (open_control_pipe(control_pipe) == 0) {
|
||||
fprintf(stderr, "Reading control commands on %s.\n", control_pipe);
|
||||
/* Create control pipe polling worker */
|
||||
r = pthread_create(&control_pipe_thread, &attr, control_pipe_worker, NULL);
|
||||
if (r < 0) {
|
||||
fprintf(stderr, "Could not create control pipe thread.\n");
|
||||
control_pipe[0] = 0;
|
||||
goto exit;
|
||||
} else {
|
||||
fprintf(stderr, "Created control pipe thread.\n");
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Failed to open control pipe: %s.\n", control_pipe);
|
||||
control_pipe[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
fm_rds_get_frames(mpx_buffer, NUM_MPX_FRAMES_IN);
|
||||
|
||||
if (resample(src_state, src_data, &frames) < 0) break;
|
||||
|
||||
float2char2channel(out_buffer, dev_out, frames);
|
||||
|
||||
/* num_bytes = audio frames( * channels) * bytes per sample */
|
||||
if (pa_simple_write(device, dev_out, frames * sizeof(int16_t), NULL) != 0) {
|
||||
fprintf(stderr, "Error: could not play audio.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (stop_rds) {
|
||||
fprintf(stderr, "Stopping the loop...\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
resampler_exit(src_state);
|
||||
|
||||
exit:
|
||||
if (control_pipe[0]) {
|
||||
/* shut down threads */
|
||||
fprintf(stderr, "Waiting for pipe thread to shut down.\n");
|
||||
pthread_cond_signal(&control_pipe_cond);
|
||||
pthread_join(control_pipe_thread, NULL);
|
||||
}
|
||||
|
||||
pthread_attr_destroy(&attr);
|
||||
pa_simple_free(device);
|
||||
fm_mpx_exit();
|
||||
exit_rds_encoder();
|
||||
|
||||
free(mpx_buffer);
|
||||
free(out_buffer);
|
||||
free(dev_out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
202
src/modulator.c
Normal file
202
src/modulator.c
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2021 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "rds.h"
|
||||
#ifdef RDS2
|
||||
#include "rds2.h"
|
||||
#endif
|
||||
#include "fm_mpx.h"
|
||||
#include "waveforms.h"
|
||||
#include "modulator.h"
|
||||
|
||||
static struct rds_t **rds_ctx;
|
||||
static float **waveform;
|
||||
|
||||
/*
|
||||
* Create the RDS objects
|
||||
*
|
||||
*/
|
||||
void init_rds_objects() {
|
||||
rds_ctx = malloc(NUM_STREAMS * sizeof(struct rds_t));
|
||||
|
||||
for (uint8_t i = 0; i < NUM_STREAMS; i++) {
|
||||
rds_ctx[i] = malloc(sizeof(struct rds_t));
|
||||
rds_ctx[i]->bit_buffer = malloc(BITS_PER_GROUP);
|
||||
rds_ctx[i]->sample_buffer =
|
||||
malloc(SAMPLE_BUFFER_SIZE * sizeof(float));
|
||||
|
||||
#ifdef RDS2_SYMBOL_SHIFTING
|
||||
/*
|
||||
* symbol shifting to reduce total power of aggregate carriers
|
||||
*
|
||||
* see:
|
||||
* https://ietresearch.onlinelibrary.wiley.com/doi/pdf/10.1049/el.2019.0292
|
||||
* for more information
|
||||
*/
|
||||
switch (i) {
|
||||
case 1:
|
||||
/* time offset of 1/2 */
|
||||
rds_ctx[i]->symbol_shift = SAMPLES_PER_BIT / 2;
|
||||
rds_ctx[i]->symbol_shift_buf = malloc(
|
||||
rds_ctx[i]->symbol_shift * sizeof(float));
|
||||
memset(rds_ctx[i]->symbol_shift_buf, 0,
|
||||
rds_ctx[i]->symbol_shift * sizeof(float));
|
||||
break;
|
||||
case 2:
|
||||
/* time offset of 1/4 */
|
||||
rds_ctx[i]->symbol_shift = SAMPLES_PER_BIT / 4;
|
||||
rds_ctx[i]->symbol_shift_buf = malloc(
|
||||
rds_ctx[i]->symbol_shift * sizeof(float));
|
||||
memset(rds_ctx[i]->symbol_shift_buf, 0,
|
||||
rds_ctx[i]->symbol_shift * sizeof(float));
|
||||
break;
|
||||
case 3:
|
||||
/* time offset of 3/4 */
|
||||
rds_ctx[i]->symbol_shift = (SAMPLES_PER_BIT / 4) * 3;
|
||||
rds_ctx[i]->symbol_shift_buf = malloc(
|
||||
rds_ctx[i]->symbol_shift * sizeof(float));
|
||||
memset(rds_ctx[i]->symbol_shift_buf, 0,
|
||||
rds_ctx[i]->symbol_shift * sizeof(float));
|
||||
break;
|
||||
default: /* stream 0 */
|
||||
/* no time offset */
|
||||
rds_ctx[i]->symbol_shift = 0;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
rds_ctx[i]->symbol_shift_buf_idx = 0;
|
||||
|
||||
}
|
||||
|
||||
waveform = malloc(2 * sizeof(float));
|
||||
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
waveform[i] = malloc(FILTER_SIZE * sizeof(float));
|
||||
for (uint16_t j = 0; j < FILTER_SIZE; j++) {
|
||||
waveform[i][j] = i ?
|
||||
+waveform_biphase[j] : -waveform_biphase[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void exit_rds_objects() {
|
||||
for (uint8_t i = 0; i < NUM_STREAMS; i++) {
|
||||
int has_symbol_shift = rds_ctx[i]->symbol_shift;
|
||||
if (has_symbol_shift) {
|
||||
free(rds_ctx[i]->symbol_shift_buf);
|
||||
}
|
||||
free(rds_ctx[i]->sample_buffer);
|
||||
free(rds_ctx[i]->bit_buffer);
|
||||
free(rds_ctx[i]);
|
||||
}
|
||||
free(rds_ctx);
|
||||
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
free(waveform[i]);
|
||||
}
|
||||
|
||||
free(waveform);
|
||||
}
|
||||
|
||||
/* Get an RDS sample. This generates the envelope of the waveform using
|
||||
* pre-generated elementary waveform samples.
|
||||
*/
|
||||
#ifdef RDS2
|
||||
float get_rds_sample(uint8_t stream_num, uint8_t rds2tunneling) {
|
||||
#else
|
||||
float get_rds_sample(uint8_t stream_num) {
|
||||
#endif
|
||||
struct rds_t *rds;
|
||||
uint16_t idx;
|
||||
float *cur_waveform;
|
||||
float sample;
|
||||
|
||||
/* select context */
|
||||
rds = rds_ctx[stream_num];
|
||||
|
||||
if (rds->sample_count == SAMPLES_PER_BIT) {
|
||||
if (rds->bit_pos == BITS_PER_GROUP) {
|
||||
#ifdef RDS2
|
||||
if(!rds2tunneling) {
|
||||
if (stream_num > 0) {
|
||||
get_rds2_bits(stream_num, rds->bit_buffer);
|
||||
} else {
|
||||
get_rds_bits(rds->bit_buffer);
|
||||
}
|
||||
} else {
|
||||
get_rds_bits(rds->bit_buffer);
|
||||
}
|
||||
#else
|
||||
get_rds_bits(rds->bit_buffer);
|
||||
#endif
|
||||
rds->bit_pos = 0;
|
||||
}
|
||||
|
||||
/* do differential encoding */
|
||||
rds->cur_bit = rds->bit_buffer[rds->bit_pos++];
|
||||
rds->prev_output = rds->cur_output;
|
||||
rds->cur_output = rds->prev_output ^ rds->cur_bit;
|
||||
|
||||
idx = rds->in_sample_index;
|
||||
cur_waveform = waveform[rds->cur_output];
|
||||
|
||||
uint16_t buffer_remaining = SAMPLE_BUFFER_SIZE - idx;
|
||||
if (buffer_remaining >= FILTER_SIZE) {
|
||||
// Process continuous chunk
|
||||
for (uint16_t i = 0; i < FILTER_SIZE; i++) {
|
||||
rds->sample_buffer[idx + i] += cur_waveform[i];
|
||||
}
|
||||
idx += FILTER_SIZE;
|
||||
} else {
|
||||
// Process in two parts to handle wrapping
|
||||
for (uint16_t i = 0; i < buffer_remaining; i++) {
|
||||
rds->sample_buffer[idx + i] += cur_waveform[i];
|
||||
}
|
||||
for (uint16_t i = 0; i < FILTER_SIZE - buffer_remaining; i++) {
|
||||
rds->sample_buffer[i] += cur_waveform[buffer_remaining + i];
|
||||
}
|
||||
idx = FILTER_SIZE - buffer_remaining;
|
||||
}
|
||||
rds->in_sample_index += SAMPLES_PER_BIT;
|
||||
if (rds->in_sample_index == SAMPLE_BUFFER_SIZE)
|
||||
rds->in_sample_index = 0;
|
||||
|
||||
rds->sample_count = 0;
|
||||
}
|
||||
rds->sample_count++;
|
||||
|
||||
if (rds->symbol_shift) {
|
||||
rds->symbol_shift_buf[rds->symbol_shift_buf_idx++] =
|
||||
rds->sample_buffer[rds->out_sample_index];
|
||||
|
||||
if (rds->symbol_shift_buf_idx == rds->symbol_shift)
|
||||
rds->symbol_shift_buf_idx = 0;
|
||||
|
||||
sample = rds->symbol_shift_buf[rds->symbol_shift_buf_idx];
|
||||
|
||||
goto done;
|
||||
}
|
||||
|
||||
sample = rds->sample_buffer[rds->out_sample_index];
|
||||
done:
|
||||
rds->sample_buffer[rds->out_sample_index++] = 0;
|
||||
if (rds->out_sample_index == SAMPLE_BUFFER_SIZE)
|
||||
rds->out_sample_index = 0;
|
||||
return sample;
|
||||
}
|
||||
43
src/modulator.h
Normal file
43
src/modulator.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2021 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef RDS2
|
||||
#define NUM_STREAMS 4
|
||||
#else
|
||||
#define NUM_STREAMS 1
|
||||
#endif
|
||||
|
||||
/* RDS signal context */
|
||||
typedef struct rds_t {
|
||||
uint8_t *bit_buffer; /* BITS_PER_GROUP */
|
||||
uint8_t bit_pos;
|
||||
float *sample_buffer; /* SAMPLE_BUFFER_SIZE */
|
||||
uint8_t prev_output;
|
||||
uint8_t cur_output;
|
||||
uint8_t cur_bit;
|
||||
uint8_t sample_count;
|
||||
uint16_t in_sample_index;
|
||||
uint16_t out_sample_index;
|
||||
uint8_t symbol_shift;
|
||||
float *symbol_shift_buf;
|
||||
uint8_t symbol_shift_buf_idx;
|
||||
} rds_t;
|
||||
|
||||
extern void init_rds_objects();
|
||||
extern void exit_rds_objects();
|
||||
|
||||
108
src/osc.c
Normal file
108
src/osc.c
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "osc.h"
|
||||
|
||||
/*
|
||||
* Code for MPX oscillator
|
||||
*
|
||||
* This uses lookup tables to speed up the waveform generation
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* DDS function generator
|
||||
*
|
||||
* Create wave constants for a given frequency
|
||||
*/
|
||||
static void create_wave(uint32_t rate, float freq,
|
||||
float *sin_wave, float *cos_wave, uint16_t *max_phase) {
|
||||
double sin_sample, cos_sample;
|
||||
/* used to determine if we have completed a cycle */
|
||||
uint16_t periods = round((double)rate/freq);
|
||||
const double w = M_2PI * freq / rate;
|
||||
|
||||
/* First value of a sine wave is always 0 */
|
||||
*sin_wave++ = 0.0f;
|
||||
*cos_wave++ = 1.0f;
|
||||
|
||||
for(uint16_t i = 1; i < periods; i++) {
|
||||
sin_sample = sin(w * i);
|
||||
cos_sample = cos(w * i);
|
||||
*sin_wave++ = (float)sin_sample;
|
||||
*cos_wave++ = (float)cos_sample;
|
||||
}
|
||||
|
||||
*max_phase = periods;
|
||||
}
|
||||
|
||||
/*
|
||||
* Oscillator object initialization
|
||||
*
|
||||
*/
|
||||
void osc_init(struct osc_t *osc, uint32_t sample_rate, float freq) {
|
||||
|
||||
/* sample rate for the objects */
|
||||
osc->sample_rate = sample_rate;
|
||||
|
||||
/* waveform tables */
|
||||
osc->sin_wave = malloc(osc->sample_rate * sizeof(float));
|
||||
osc->cos_wave = malloc(osc->sample_rate * sizeof(float));
|
||||
|
||||
/* set current position to 0 */
|
||||
osc->cur = 0;
|
||||
|
||||
/* create waveform data and load into lookup tables */
|
||||
create_wave(osc->sample_rate, freq,
|
||||
osc->sin_wave, osc->cos_wave,
|
||||
&osc->max);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a single waveform sample
|
||||
*
|
||||
* Cosine is needed for SSB generation
|
||||
*
|
||||
*/
|
||||
float osc_get_cos(struct osc_t *osc) {
|
||||
return osc->cos_wave[osc->cur];
|
||||
}
|
||||
|
||||
float osc_get_sin(struct osc_t *osc) {
|
||||
return osc->sin_wave[osc->cur];
|
||||
}
|
||||
|
||||
/*
|
||||
* Shift the oscillator to the next position
|
||||
*
|
||||
*/
|
||||
void osc_update_pos(struct osc_t *osc) {
|
||||
if (++osc->cur == osc->max) osc->cur = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unload waveform tables
|
||||
*
|
||||
*/
|
||||
void osc_exit(struct osc_t *osc) {
|
||||
free(osc->sin_wave);
|
||||
free(osc->cos_wave);
|
||||
osc->cur = 0;
|
||||
osc->max = 0;
|
||||
}
|
||||
50
src/osc.h
Normal file
50
src/osc.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* context for MPX oscillator */
|
||||
typedef struct osc_t {
|
||||
/* the sample rate at which the oscillator operates */
|
||||
uint32_t sample_rate;
|
||||
|
||||
/*
|
||||
* frequency for this instance
|
||||
*
|
||||
*/
|
||||
float freq;
|
||||
|
||||
/*
|
||||
* Arrays of carrier wave constants
|
||||
*
|
||||
*/
|
||||
float *sin_wave_out_of_phase;
|
||||
float *sin_wave;
|
||||
float *cos_wave;
|
||||
|
||||
/*
|
||||
* Wave phase
|
||||
*
|
||||
*/
|
||||
uint16_t cur;
|
||||
uint16_t max;
|
||||
} osc_t;
|
||||
|
||||
extern void osc_init(struct osc_t *osc, uint32_t sample_rate, const float freq);
|
||||
extern float osc_get_sin(struct osc_t *osc);
|
||||
extern float osc_get_cos(struct osc_t *osc);
|
||||
extern void osc_update_pos(struct osc_t *osc);
|
||||
extern void osc_exit(struct osc_t *osc);
|
||||
778
src/rds.c
Normal file
778
src/rds.c
Normal file
@@ -0,0 +1,778 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "rds.h"
|
||||
#include "modulator.h"
|
||||
#ifdef RDS2
|
||||
#include "rds2.h"
|
||||
#endif
|
||||
#include "lib.h"
|
||||
#include <time.h>
|
||||
|
||||
static struct rds_params_t rds_data;
|
||||
|
||||
/* RDS data controls */
|
||||
static struct {
|
||||
/* Slow Labeling Codes */
|
||||
uint8_t ecclic_enabled;
|
||||
uint8_t ecc_or_lic;
|
||||
/* Program Item Number */
|
||||
uint8_t pin_enabled;
|
||||
|
||||
/* Program Service */
|
||||
uint8_t ps_update;
|
||||
uint8_t tps_update;
|
||||
|
||||
/* Radio Text*/
|
||||
uint8_t rt1_enabled;
|
||||
uint8_t rt_update;
|
||||
uint8_t rt_ab;
|
||||
uint8_t rt_segments;
|
||||
|
||||
/* PTYN */
|
||||
uint8_t ptyn_enabled;
|
||||
uint8_t ptyn_update;
|
||||
uint8_t ptyn_ab;
|
||||
|
||||
/* Long PS */
|
||||
uint8_t lps_on;
|
||||
uint8_t lps_update;
|
||||
uint8_t lps_segments;
|
||||
|
||||
uint8_t custom_group_in;
|
||||
uint16_t custom_group[GROUP_LENGTH];
|
||||
|
||||
/* TODO: add UDG */
|
||||
} rds_state;
|
||||
|
||||
#ifdef ODA
|
||||
/* ODA */
|
||||
static struct rds_oda_t odas[MAX_ODAS];
|
||||
static struct {
|
||||
uint8_t current;
|
||||
uint8_t count;
|
||||
} oda_state;
|
||||
#endif
|
||||
|
||||
#ifdef ODA_RTP
|
||||
/* RT+ */
|
||||
static struct {
|
||||
uint8_t group;
|
||||
uint8_t enabled;
|
||||
uint8_t running;
|
||||
uint8_t toggle;
|
||||
uint8_t type[2];
|
||||
uint8_t start[2];
|
||||
uint8_t len[2];
|
||||
} rtplus_cfg;
|
||||
#endif
|
||||
|
||||
#ifdef ODA
|
||||
static void register_oda(uint8_t group, uint16_t aid, uint16_t scb) {
|
||||
|
||||
if (oda_state.count >= MAX_ODAS) return; /* can't accept more ODAs */
|
||||
|
||||
odas[oda_state.count].group = group;
|
||||
odas[oda_state.count].aid = aid;
|
||||
odas[oda_state.count].scb = scb;
|
||||
oda_state.count++;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Get the next AF entry
|
||||
*/
|
||||
static uint16_t get_next_af() {
|
||||
static uint8_t af_state;
|
||||
uint16_t out;
|
||||
|
||||
if (rds_data.af.num_afs) {
|
||||
if (af_state == 0) {
|
||||
out = (AF_CODE_NUM_AFS_BASE + rds_data.af.num_afs) << 8;
|
||||
out |= rds_data.af.afs[0];
|
||||
af_state += 1;
|
||||
} else {
|
||||
out = rds_data.af.afs[af_state] << 8;
|
||||
if (rds_data.af.afs[af_state + 1])
|
||||
out |= rds_data.af.afs[af_state + 1];
|
||||
else
|
||||
out |= AF_CODE_FILLER;
|
||||
af_state += 2;
|
||||
}
|
||||
if (af_state >= rds_data.af.num_entries) af_state = 0;
|
||||
} else {
|
||||
out = AF_CODE_NO_AF << 8 | AF_CODE_FILLER;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/* PS group (0A) */
|
||||
static void get_rds_ps_group(uint16_t *blocks) {
|
||||
static unsigned char ps_text[PS_LENGTH];
|
||||
static unsigned char tps_text[PS_LENGTH];
|
||||
static uint8_t ps_csegment;
|
||||
|
||||
if (ps_csegment == 0 && rds_state.ps_update) {
|
||||
memcpy(ps_text, rds_data.ps, PS_LENGTH);
|
||||
rds_state.ps_update = 0; /* rewind */
|
||||
}
|
||||
if(ps_csegment == 0 && rds_state.tps_update) {
|
||||
memcpy(tps_text, rds_data.tps, PS_LENGTH);
|
||||
rds_state.tps_update = 0; /* rewind */
|
||||
}
|
||||
|
||||
/* TA */
|
||||
blocks[1] |= rds_data.ta << 4;
|
||||
|
||||
/* MS */
|
||||
blocks[1] |= rds_data.ms << 3;
|
||||
|
||||
/* DI */
|
||||
blocks[1] |= ((rds_data.di >> (3 - ps_csegment)) & INT8_0) << 2; /* DI is a 4 bit number, we also have 4 segments, so we send a bit of the di number depending on the segment, so 0b0101 would be like: 0 1 0 1*/
|
||||
|
||||
/* PS segment address */
|
||||
blocks[1] |= ps_csegment;
|
||||
|
||||
/* AF */
|
||||
blocks[2] = get_next_af();
|
||||
|
||||
/* PS */
|
||||
if(rds_data.ta && rds_data.traffic_ps_on) {
|
||||
blocks[3] = tps_text[ps_csegment * 2] << 8 | tps_text[ps_csegment * 2 + 1];
|
||||
} else {
|
||||
/* TODO: Add DPS */
|
||||
blocks[3] = ps_text[ps_csegment * 2] << 8 | ps_text[ps_csegment * 2 + 1];
|
||||
}
|
||||
|
||||
ps_csegment++;
|
||||
if (ps_csegment >= 4) ps_csegment = 0;
|
||||
}
|
||||
|
||||
/* RT group (2A) */
|
||||
static uint8_t get_rds_rt_group(uint16_t *blocks) {
|
||||
static unsigned char rt_text[RT_LENGTH];
|
||||
static uint8_t rt_state;
|
||||
|
||||
if (rds_state.rt_update) {
|
||||
memcpy(rt_text, rds_data.rt1, RT_LENGTH);
|
||||
rds_state.rt_ab ^= 1;
|
||||
rds_state.rt_update = 0;
|
||||
rt_state = 0; /* rewind when new RT arrives */
|
||||
}
|
||||
|
||||
if(rt_text[0] == '\r' || !rds_state.rt1_enabled) {
|
||||
/* RT strings lesser than 64 in size should have been ended with a \r if a return is on [0] then that means that our string is empty and thus we can not generate this group*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
blocks[1] |= 2 << 12;
|
||||
blocks[1] |= rds_state.rt_ab << 4;
|
||||
blocks[1] |= rt_state;
|
||||
blocks[2] = rt_text[rt_state * 4 ] << 8;
|
||||
blocks[2] |= rt_text[rt_state * 4 + 1];
|
||||
blocks[3] = rt_text[rt_state * 4 + 2] << 8;
|
||||
blocks[3] |= rt_text[rt_state * 4 + 3];
|
||||
|
||||
rt_state++;
|
||||
if (rt_state >= rds_state.rt_segments) rt_state = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ODA group (3A)
|
||||
*/
|
||||
#ifdef ODA
|
||||
static void get_rds_oda_group(uint16_t *blocks) {
|
||||
/* select ODA */
|
||||
struct rds_oda_t this_oda = odas[oda_state.current];
|
||||
|
||||
#ifdef ODA_RTP
|
||||
if(this_oda.aid == ODA_AID_RTPLUS && rtplus_cfg.enabled == 0) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
blocks[1] |= 3 << 12;
|
||||
|
||||
blocks[1] |= GET_GROUP_TYPE(this_oda.group) << 1;
|
||||
blocks[1] |= GET_GROUP_VER(this_oda.group);
|
||||
blocks[2] = this_oda.scb;
|
||||
blocks[3] = this_oda.aid;
|
||||
|
||||
oda_state.current++;
|
||||
if (oda_state.current >= oda_state.count) oda_state.current = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static uint8_t get_rds_ct_group(uint16_t *blocks) {
|
||||
static uint8_t latest_minutes;
|
||||
struct tm *utc, *local_time;
|
||||
time_t now;
|
||||
uint8_t l;
|
||||
uint32_t mjd;
|
||||
int16_t offset;
|
||||
|
||||
/* Check time */
|
||||
now = time(NULL);
|
||||
utc = gmtime(&now);
|
||||
|
||||
if (utc->tm_min != latest_minutes) {
|
||||
/* Generate CT group */
|
||||
latest_minutes = utc->tm_min;
|
||||
|
||||
l = utc->tm_mon <= 1 ? 1 : 0;
|
||||
mjd = 14956 + utc->tm_mday +
|
||||
(uint32_t)((utc->tm_year - l) * 365.25f) +
|
||||
(uint32_t)((utc->tm_mon + (1+1) + l * 12) * 30.6001f);
|
||||
|
||||
blocks[1] |= 4 << 12 | (mjd >> 15);
|
||||
blocks[2] = (mjd << 1) | (utc->tm_hour >> 4);
|
||||
blocks[3] = (utc->tm_hour & INT16_L4) << 12 | utc->tm_min << 6;
|
||||
|
||||
/* get local time (for the offset) */
|
||||
local_time = localtime(&now);
|
||||
|
||||
/* tm_gmtoff doesn't exist in POSIX but __tm_gmtoff does */
|
||||
offset = local_time->__tm_gmtoff / (30 * 60); /*__tm_gmtoff is time in seconds from utc, so utc + __tm_gmtoff = local time*/
|
||||
if (offset < 0) blocks[3] |= 1 << 5; /* if less than 0 then set the flag*/
|
||||
blocks[3] |= abs(offset); /* offset should be halfs of a hour, so offset 1 = 30 minutes, 2 = 1 hour*/
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* PTYN group (10A) */
|
||||
static void get_rds_ptyn_group(uint16_t *blocks) {
|
||||
static unsigned char ptyn_text[PTYN_LENGTH];
|
||||
static uint8_t ptyn_state;
|
||||
|
||||
if (ptyn_state == 0 && rds_state.ptyn_update) {
|
||||
memcpy(ptyn_text, rds_data.ptyn, PTYN_LENGTH);
|
||||
rds_state.ptyn_ab ^= 1;
|
||||
rds_state.ptyn_update = 0;
|
||||
}
|
||||
|
||||
blocks[1] |= 10 << 12 | ptyn_state;
|
||||
blocks[1] |= rds_state.ptyn_ab << 4; /* no one knew about ptyn's ab until i saw sdr++'s decoder, it had decoded ptyn but not show it? it also did decode the ab flag */
|
||||
blocks[2] = ptyn_text[ptyn_state * 4 + 0] << 8;
|
||||
blocks[2] |= ptyn_text[ptyn_state * 4 + 1];
|
||||
blocks[3] = ptyn_text[ptyn_state * 4 + 2] << 8;
|
||||
blocks[3] |= ptyn_text[ptyn_state * 4 + 3];
|
||||
|
||||
ptyn_state++;
|
||||
if (ptyn_state == 2) ptyn_state = 0;
|
||||
}
|
||||
|
||||
/* Long PS group (15A) */
|
||||
static void get_rds_lps_group(uint16_t *blocks) {
|
||||
static unsigned char lps_text[LPS_LENGTH];
|
||||
static uint8_t lps_state;
|
||||
|
||||
if (lps_state == 0 && rds_state.lps_update) {
|
||||
memcpy(lps_text, rds_data.lps, LPS_LENGTH);
|
||||
rds_state.lps_update = 0;
|
||||
}
|
||||
|
||||
blocks[1] |= 15 << 12 | lps_state;
|
||||
/* does lps have an ab? */
|
||||
blocks[2] = lps_text[lps_state * 4 ] << 8;
|
||||
blocks[2] |= lps_text[lps_state * 4 + 1];
|
||||
blocks[3] = lps_text[lps_state * 4 + 2] << 8;
|
||||
blocks[3] |= lps_text[lps_state * 4 + 3];
|
||||
|
||||
lps_state++;
|
||||
if (lps_state == rds_state.lps_segments) lps_state = 0;
|
||||
}
|
||||
|
||||
static void get_rds_ecc_group(uint16_t *blocks) {
|
||||
blocks[1] |= 1 << 12;
|
||||
if(rds_state.ecclic_enabled) {
|
||||
blocks[2] = rds_data.ecc;
|
||||
}
|
||||
|
||||
if(rds_state.pin_enabled) {
|
||||
blocks[3] = rds_data.pin_day << 11; /* first 5 bits */
|
||||
blocks[3] |= rds_data.pin_hour << 6; /* 5-10 bits from start */
|
||||
blocks[3] |= rds_data.pin_minute; /* 10-16 bits from start */
|
||||
}
|
||||
}
|
||||
|
||||
static void get_rds_lic_group(uint16_t *blocks) {
|
||||
blocks[1] |= 1 << 12;
|
||||
if(rds_state.ecclic_enabled) {
|
||||
blocks[2] = 0x3000; /* 0b0011000000000000 first 1 bit is the linkage actuator, the 3 after are variant codes which is 3 in this case after theres space for data */
|
||||
blocks[2] |= rds_data.lic;
|
||||
}
|
||||
|
||||
if(rds_state.pin_enabled) {
|
||||
/* pin is also in lic as it also is a 1a group*/
|
||||
blocks[3] = rds_data.pin_day << 11;
|
||||
blocks[3] |= rds_data.pin_hour << 6;
|
||||
blocks[3] |= rds_data.pin_minute;
|
||||
}
|
||||
}
|
||||
|
||||
/* RT+ */
|
||||
#ifdef ODA_RTP
|
||||
static void init_rtplus(uint8_t group) {
|
||||
register_oda(group, ODA_AID_RTPLUS, 0);
|
||||
rtplus_cfg.group = group;
|
||||
rtplus_cfg.enabled = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ODA_RTP
|
||||
/* RT+ group */
|
||||
static void get_rds_rtplus_group(uint16_t *blocks) {
|
||||
/* RT+ block format */
|
||||
blocks[1] |= GET_GROUP_TYPE(rtplus_cfg.group) << 12;
|
||||
blocks[1] |= GET_GROUP_VER(rtplus_cfg.group) << 11;
|
||||
blocks[1] |= rtplus_cfg.toggle << 4 | rtplus_cfg.running << 3;
|
||||
blocks[1] |= (rtplus_cfg.type[0] & INT8_U5) >> 3;
|
||||
|
||||
blocks[2] = (rtplus_cfg.type[0] & INT8_L3) << 13;
|
||||
blocks[2] |= (rtplus_cfg.start[0] & INT8_L6) << 7;
|
||||
blocks[2] |= (rtplus_cfg.len[0] & INT8_L6) << 1;
|
||||
blocks[2] |= (rtplus_cfg.type[1] & INT8_U3) >> 5;
|
||||
|
||||
blocks[3] = (rtplus_cfg.type[1] & INT8_L5) << 11;
|
||||
blocks[3] |= (rtplus_cfg.start[1] & INT8_L6) << 5;
|
||||
blocks[3] |= rtplus_cfg.len[1] & INT8_L5;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Lower priority groups are placed in a subsequence
|
||||
*/
|
||||
static uint8_t get_rds_other_groups(uint16_t *blocks) {
|
||||
static uint8_t group_counter[GROUP_15B];
|
||||
|
||||
/* Type 1A groups */
|
||||
if (rds_data.ecc || rds_data.lic) {
|
||||
if (++group_counter[GROUP_1A] >= 22) {
|
||||
group_counter[GROUP_1A] = 0;
|
||||
if(rds_data.ecc && rds_data.lic) {
|
||||
if(rds_state.ecc_or_lic == 0) {
|
||||
get_rds_ecc_group(blocks);
|
||||
} else {
|
||||
get_rds_lic_group(blocks);
|
||||
}
|
||||
rds_state.ecc_or_lic ^= 1;
|
||||
} else if(rds_data.ecc) {
|
||||
get_rds_ecc_group(blocks);
|
||||
} else if(rds_data.ecc) {
|
||||
get_rds_lic_group(blocks);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Type 3A groups */
|
||||
#ifdef ODA
|
||||
if (oda_state.count) {
|
||||
if (++group_counter[GROUP_3A] >= 25) {
|
||||
group_counter[GROUP_3A] = 0;
|
||||
get_rds_oda_group(blocks);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Type 10A groups */
|
||||
if (rds_state.ptyn_enabled && rds_data.ptyn[0]) {
|
||||
if (++group_counter[GROUP_10A] >= 13) {
|
||||
group_counter[GROUP_10A] = 0;
|
||||
/* Do not generate a 10A group if PTYN is off */
|
||||
get_rds_ptyn_group(blocks);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* RT+ groups */
|
||||
#ifdef ODA_RTP
|
||||
if (++group_counter[rtplus_cfg.group] >= 30) {
|
||||
group_counter[rtplus_cfg.group] = 0;
|
||||
if(rtplus_cfg.enabled == 0) {
|
||||
return 0;
|
||||
}
|
||||
get_rds_rtplus_group(blocks);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Codes a group once every 3 groups
|
||||
* Ex: 0A, 2A, 0A, 12A, 2A, 0A, 2A, 12A, etc
|
||||
*/
|
||||
static uint8_t get_rds_long_text_groups(uint16_t *blocks) {
|
||||
static uint8_t group_selector = 0;
|
||||
static uint8_t group_slot_counter = 0;
|
||||
|
||||
/* exit early until the 4th call */
|
||||
if (++group_slot_counter < 4)
|
||||
return 0;
|
||||
|
||||
/* reset the slot counter */
|
||||
group_slot_counter = 0;
|
||||
|
||||
/* 3:1 ratio */
|
||||
switch (group_selector) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3: /* Long PS */
|
||||
if (rds_data.lps[0] && rds_state.lps_on) {
|
||||
get_rds_lps_group(blocks);
|
||||
goto group_coded;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* if no group was coded */
|
||||
if (++group_selector >= 8) group_selector = 0;
|
||||
return 0;
|
||||
|
||||
group_coded:
|
||||
if (++group_selector >= 8) group_selector = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Get a custom group if there is one*/
|
||||
static uint8_t get_rds_custom_groups(uint16_t *blocks) {
|
||||
if(rds_state.custom_group_in) {
|
||||
rds_state.custom_group_in = 0;
|
||||
blocks[0] = rds_state.custom_group[0];
|
||||
blocks[1] = rds_state.custom_group[1];
|
||||
blocks[2] = rds_state.custom_group[2];
|
||||
blocks[3] = rds_state.custom_group[3];
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Creates an RDS group.
|
||||
* This generates sequences of the form 0A, 2A, 0A, 2A, 0A, 2A, etc.
|
||||
*/
|
||||
static void get_rds_group(uint16_t *blocks) {
|
||||
static uint8_t state;
|
||||
|
||||
/* Basic block data */
|
||||
blocks[0] = rds_data.pi;
|
||||
blocks[1] = rds_data.tp << 10;
|
||||
blocks[1] |= rds_data.pty << 5;
|
||||
blocks[2] = 0;
|
||||
blocks[3] = 0;
|
||||
|
||||
/* Generate block content */
|
||||
|
||||
/* CT (clock time) has priority over other group types */
|
||||
if (rds_data.ct && get_rds_ct_group(blocks)) {
|
||||
goto group_coded;
|
||||
}
|
||||
|
||||
/* Next to top priority, if want top then just disable CT i guess */
|
||||
if(get_rds_custom_groups(blocks)) {
|
||||
goto group_coded;
|
||||
}
|
||||
|
||||
/* Longer text groups get medium priority */
|
||||
if (get_rds_long_text_groups(blocks)) {
|
||||
goto group_coded;
|
||||
}
|
||||
|
||||
/* Other groups */
|
||||
if (get_rds_other_groups(blocks)) {
|
||||
goto group_coded;
|
||||
}
|
||||
|
||||
/* Standard group sequence
|
||||
This group sequence is way better than the offcial one, official goes like: 0A 2A which is shitty here it is 0A 0A 0A 0A 2A 2A 2A 2A (2A) so we're in sequence and for example immiedietly get PS and quite fast get RT */
|
||||
if(state < 6) {
|
||||
/* 0A */
|
||||
get_rds_ps_group(blocks);
|
||||
state++;
|
||||
} else if(state >= 6) {
|
||||
/* 2A */
|
||||
if(!get_rds_rt_group(blocks)) { /* try to generate 2A if can't generate 2A than that means our text is empty so we generate PS instead of wasting groups on nothing*/
|
||||
get_rds_ps_group(blocks);
|
||||
}
|
||||
state++;
|
||||
}
|
||||
if (state >= 12) state = 0;
|
||||
|
||||
group_coded:
|
||||
/* for version B groups. good for custom groups */
|
||||
if (IS_TYPE_B(blocks)) {
|
||||
blocks[2] = rds_data.pi;
|
||||
}
|
||||
}
|
||||
|
||||
void get_rds_bits(uint8_t *bits) {
|
||||
static uint16_t out_blocks[GROUP_LENGTH];
|
||||
get_rds_group(out_blocks);
|
||||
#ifdef RDS2
|
||||
add_checkwords(out_blocks, bits, false);
|
||||
#else
|
||||
add_checkwords(out_blocks, bits);
|
||||
#endif
|
||||
}
|
||||
|
||||
void init_rds_encoder(struct rds_params_t rds_params) {
|
||||
|
||||
/* AF */
|
||||
if (rds_params.af.num_afs) {
|
||||
set_rds_af(rds_params.af);
|
||||
fprintf(stderr, show_af_list(rds_params.af));
|
||||
}
|
||||
|
||||
set_rds_pi(rds_params.pi);
|
||||
set_rds_ps(rds_params.ps);
|
||||
rds_state.rt_ab = 1;
|
||||
set_rds_rt1_enabled(1);
|
||||
set_rds_rt1(rds_params.rt1);
|
||||
set_rds_pty(rds_params.pty);
|
||||
rds_state.ptyn_ab = 1;
|
||||
set_rds_ptyn(rds_params.ptyn);
|
||||
set_rds_lps(rds_params.lps);
|
||||
set_rds_lpson(1);
|
||||
set_rds_tp(rds_params.tp);
|
||||
set_rds_ecc(rds_params.ecc);
|
||||
set_rds_lic(rds_params.lic);
|
||||
set_rds_ecclic_toggle(1);
|
||||
set_rds_pin_enabled(1);
|
||||
set_rds_ct(1);
|
||||
set_rds_ms(1);
|
||||
set_rds_di(DI_STEREO | DI_DPTY);
|
||||
|
||||
/* Assign the RT+ AID to group 11A */
|
||||
#ifdef ODA_RTP
|
||||
init_rtplus(GROUP_11A);
|
||||
#endif
|
||||
|
||||
/* initialize modulator objects */
|
||||
init_rds_objects();
|
||||
#ifdef RDS2
|
||||
init_rds2_encoder(rds_params.rds2_image_path);
|
||||
#endif
|
||||
}
|
||||
|
||||
void exit_rds_encoder() {
|
||||
exit_rds_objects();
|
||||
#ifdef RDS2
|
||||
exit_rds2_encoder();
|
||||
#endif
|
||||
}
|
||||
|
||||
void set_rds_pi(uint16_t pi_code) {
|
||||
rds_data.pi = pi_code;
|
||||
}
|
||||
|
||||
void set_rds_ecc(uint8_t ecc) {
|
||||
rds_data.ecc = ecc; /* ecc is 8 bits, so a byte */
|
||||
}
|
||||
|
||||
void set_rds_lic(uint8_t lic) {
|
||||
rds_data.lic = lic & INT16_L12; /* lic is 12 bits according to the docs, but the values can be less, for example english is 0x09 */
|
||||
}
|
||||
|
||||
void set_rds_ecclic_toggle(uint8_t toggle) {
|
||||
rds_state.ecclic_enabled = toggle & INT8_0;
|
||||
}
|
||||
|
||||
void set_rds_pin_enabled(uint8_t enabled) {
|
||||
rds_state.pin_enabled = enabled & INT8_0;
|
||||
}
|
||||
|
||||
void set_rds_pin(uint8_t day, uint8_t hour, uint8_t minute) {
|
||||
rds_data.pin_day = (day & INT8_L5);
|
||||
rds_data.pin_hour = (hour & INT8_L5);
|
||||
rds_data.pin_minute = (minute & INT8_L6);
|
||||
}
|
||||
|
||||
void set_rds_rt1_enabled(uint8_t rt1en) {
|
||||
rds_state.rt1_enabled = rt1en & INT8_0;
|
||||
}
|
||||
void set_rds_rt1(unsigned char *rt1) {
|
||||
uint8_t i = 0, len = 0;
|
||||
|
||||
rds_state.rt_update = 1;
|
||||
memset(rds_data.rt1, ' ', RT_LENGTH);
|
||||
while (*rt1 != 0 && len < RT_LENGTH)
|
||||
rds_data.rt1[len++] = *rt1++;
|
||||
|
||||
if (len < RT_LENGTH) {
|
||||
rds_state.rt_segments = 0;
|
||||
|
||||
/* Terminate RT with '\r' (carriage return) if RT
|
||||
* is < 64 characters long
|
||||
*/
|
||||
rds_data.rt1[len++] = '\r';
|
||||
|
||||
/* find out how many segments are needed */
|
||||
while (i < len) {
|
||||
i += 4;
|
||||
rds_state.rt_segments++;
|
||||
}
|
||||
} else {
|
||||
/* Default to 16 if RT is 64 characters long */
|
||||
rds_state.rt_segments = 16;
|
||||
}
|
||||
}
|
||||
|
||||
void set_rds_ps(unsigned char *ps) {
|
||||
uint8_t len = 0;
|
||||
|
||||
rds_state.ps_update = 1;
|
||||
memset(rds_data.ps, ' ', PS_LENGTH);
|
||||
while (*ps != 0 && len < PS_LENGTH)
|
||||
rds_data.ps[len++] = *ps++;
|
||||
}
|
||||
void set_rds_tpson(uint8_t tpson) {
|
||||
rds_data.traffic_ps_on = tpson & INT8_0;
|
||||
}
|
||||
void set_rds_tps(unsigned char *tps) {
|
||||
uint8_t len = 0;
|
||||
|
||||
rds_state.tps_update = 1;
|
||||
memset(rds_data.tps, ' ', PS_LENGTH);
|
||||
while (*tps != 0 && len < PS_LENGTH)
|
||||
rds_data.tps[len++] = *tps++;
|
||||
}
|
||||
|
||||
void set_rds_lpson(uint8_t lpson) {
|
||||
rds_state.lps_on = lpson & INT8_0;
|
||||
}
|
||||
|
||||
void set_rds_lps(unsigned char *lps) {
|
||||
uint8_t i = 0, len = 0;
|
||||
|
||||
if (!lps[0]) {
|
||||
memset(rds_data.lps, 0, LPS_LENGTH);
|
||||
return;
|
||||
}
|
||||
rds_state.lps_update = 1;
|
||||
memset(rds_data.lps, '\r', LPS_LENGTH);
|
||||
while (*lps != 0 && len < LPS_LENGTH)
|
||||
rds_data.lps[len++] = *lps++;
|
||||
|
||||
if (len < LPS_LENGTH) {
|
||||
rds_state.lps_segments = 0;
|
||||
|
||||
/* increment to allow adding an '\r' in all cases */
|
||||
len++;
|
||||
|
||||
/* find out how many segments are needed */
|
||||
while (i < len) {
|
||||
i += 4;
|
||||
rds_state.lps_segments++;
|
||||
}
|
||||
} else {
|
||||
/* default to 8 if LPS is 32 characters long */
|
||||
rds_state.lps_segments = 8;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ODA_RTP
|
||||
void set_rds_rtplus_flags(uint8_t flags) {
|
||||
if(flags == 2) {
|
||||
rtplus_cfg.enabled = 0;
|
||||
} else {
|
||||
rtplus_cfg.enabled = 1;
|
||||
}
|
||||
rtplus_cfg.running = flags & INT8_0;
|
||||
}
|
||||
|
||||
void set_rds_rtplus_tags(uint8_t *tags) {
|
||||
rtplus_cfg.type[0] = tags[0] & INT8_L6;
|
||||
rtplus_cfg.start[0] = tags[1] & INT8_L6;
|
||||
rtplus_cfg.len[0] = tags[2] & INT8_L6;
|
||||
rtplus_cfg.type[1] = tags[3] & INT8_L6;
|
||||
rtplus_cfg.start[1] = tags[4] & INT8_L6;
|
||||
rtplus_cfg.len[1] = tags[5] & INT8_L5;
|
||||
|
||||
rtplus_cfg.toggle ^= 1;
|
||||
rtplus_cfg.running = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
void set_rds_af(struct rds_af_t new_af_list) {
|
||||
memcpy(&rds_data.af, &new_af_list, sizeof(struct rds_af_t));
|
||||
}
|
||||
|
||||
void clear_rds_af() {
|
||||
memset(&rds_data.af, 0, sizeof(struct rds_af_t));
|
||||
}
|
||||
|
||||
void set_rds_pty(uint8_t pty) {
|
||||
rds_data.pty = pty & INT8_L5;
|
||||
}
|
||||
|
||||
void set_rds_ptyn_enabled(uint8_t enabled) {
|
||||
rds_state.ptyn_enabled = enabled & INT8_0;
|
||||
}
|
||||
|
||||
void set_rds_ptyn(unsigned char *ptyn) {
|
||||
uint8_t len = 0;
|
||||
|
||||
if (!ptyn[0]) {
|
||||
memset(rds_data.ptyn, 0, PTYN_LENGTH);
|
||||
return;
|
||||
}
|
||||
|
||||
rds_state.ptyn_update = 1;
|
||||
memset(rds_data.ptyn, ' ', PTYN_LENGTH);
|
||||
while (*ptyn != 0 && len < PTYN_LENGTH)
|
||||
rds_data.ptyn[len++] = *ptyn++;
|
||||
}
|
||||
|
||||
void set_rds_ta(uint8_t ta) {
|
||||
rds_data.ta = ta & INT8_0;
|
||||
}
|
||||
|
||||
void set_rds_tp(uint8_t tp) {
|
||||
rds_data.tp = tp & INT8_0;
|
||||
}
|
||||
|
||||
void set_rds_ms(uint8_t ms) {
|
||||
rds_data.ms = ms & INT8_0;
|
||||
}
|
||||
|
||||
void set_rds_di(uint8_t di) {
|
||||
rds_data.di = di & INT8_L4;
|
||||
}
|
||||
|
||||
void set_rds_ct(uint8_t ct) {
|
||||
rds_data.ct = ct & INT8_0;
|
||||
}
|
||||
|
||||
uint16_t get_rds_pi() {
|
||||
/* this is for custom groups, specifically 'G=' */
|
||||
return rds_data.pi;
|
||||
}
|
||||
|
||||
void set_rds_cg(uint16_t* blocks) {
|
||||
rds_state.custom_group[0] = blocks[0];
|
||||
rds_state.custom_group[1] = blocks[1];
|
||||
rds_state.custom_group[2] = blocks[2];
|
||||
rds_state.custom_group[3] = blocks[3];
|
||||
rds_state.custom_group_in = 1;
|
||||
}
|
||||
336
src/rds.h
Normal file
336
src/rds.h
Normal file
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef RDS_H
|
||||
#define RDS_H
|
||||
|
||||
/* The RDS error-detection code generator polynomial is
|
||||
* x^10 + x^8 + x^7 + x^5 + x^4 + x^3 + x^0
|
||||
*/
|
||||
#define POLY 0x1B9
|
||||
#define POLY_DEG 10
|
||||
#define BLOCK_SIZE 16
|
||||
|
||||
#define GROUP_LENGTH 4
|
||||
#define BITS_PER_GROUP (GROUP_LENGTH * (BLOCK_SIZE + POLY_DEG))
|
||||
#define RDS_SAMPLE_RATE 190000
|
||||
#define SAMPLES_PER_BIT 160
|
||||
#define FILTER_SIZE 1120
|
||||
#define SAMPLE_BUFFER_SIZE (SAMPLES_PER_BIT + FILTER_SIZE)
|
||||
|
||||
/* Text items
|
||||
*
|
||||
*/
|
||||
#define RT_LENGTH 64
|
||||
#define PS_LENGTH 8
|
||||
#define PTYN_LENGTH 8
|
||||
#define LPS_LENGTH 32
|
||||
|
||||
/* AF list size
|
||||
*
|
||||
*/
|
||||
#define MAX_AFS 25
|
||||
|
||||
typedef struct rds_af_t {
|
||||
uint8_t num_entries;
|
||||
uint8_t num_afs;
|
||||
uint8_t afs[MAX_AFS];
|
||||
} rds_af_t;
|
||||
|
||||
/* AF codes */
|
||||
#define AF_CODE_FILLER 205
|
||||
#define AF_CODE_NO_AF 224
|
||||
#define AF_CODE_NUM_AFS_BASE AF_CODE_NO_AF
|
||||
#define AF_CODE_LFMF_FOLLOWS 250
|
||||
|
||||
typedef struct rds_params_t {
|
||||
uint16_t pi; /*Program Identification*/
|
||||
uint16_t lic; /* Language Identification code. lic should have 12 bits, 16 is the closest */
|
||||
uint8_t ecc; /* Extended Country Code*/
|
||||
uint8_t ta; /* Traffic Annoucement*/
|
||||
uint8_t pty; /* Program Type */
|
||||
uint8_t tp; /* Traffic Program */
|
||||
uint8_t ms; /* Music/Speech */
|
||||
uint8_t di; /* Decoder ID */
|
||||
|
||||
/* Program Service */
|
||||
unsigned char ps[PS_LENGTH];
|
||||
/* Traffic PS */
|
||||
uint8_t traffic_ps_on;
|
||||
unsigned char tps[PS_LENGTH];
|
||||
|
||||
/* Radio Text */
|
||||
unsigned char rt1[RT_LENGTH];
|
||||
|
||||
/* Program Type Name */
|
||||
unsigned char ptyn[PTYN_LENGTH];
|
||||
|
||||
/* Alternative Frequencies */
|
||||
struct rds_af_t af;
|
||||
|
||||
/* Clock-time */
|
||||
uint8_t ct;
|
||||
|
||||
/* Long PS */
|
||||
unsigned char lps[LPS_LENGTH];
|
||||
|
||||
/* Program Item Number */
|
||||
uint8_t pin_day;
|
||||
uint8_t pin_hour;
|
||||
uint8_t pin_minute;
|
||||
|
||||
#ifdef RDS2
|
||||
/* RDS2 image path*/
|
||||
char rds2_image_path[51];
|
||||
#endif
|
||||
} rds_params_t;
|
||||
/* Here, the first member of the struct must be a scalar to avoid a
|
||||
warning on -Wmissing-braces with GCC < 4.8.3
|
||||
(bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119)
|
||||
*/
|
||||
|
||||
/* Group type
|
||||
*
|
||||
* 0-15
|
||||
*/
|
||||
#define GROUP_TYPE_0 ( 0 << 4)
|
||||
#define GROUP_TYPE_1 ( 1 << 4)
|
||||
#define GROUP_TYPE_2 ( 2 << 4)
|
||||
#define GROUP_TYPE_3 ( 3 << 4)
|
||||
#define GROUP_TYPE_4 ( 4 << 4)
|
||||
#define GROUP_TYPE_5 ( 5 << 4)
|
||||
#define GROUP_TYPE_6 ( 6 << 4)
|
||||
#define GROUP_TYPE_7 ( 7 << 4)
|
||||
#define GROUP_TYPE_8 ( 8 << 4)
|
||||
#define GROUP_TYPE_9 ( 9 << 4)
|
||||
#define GROUP_TYPE_10 (10 << 4)
|
||||
#define GROUP_TYPE_11 (11 << 4)
|
||||
#define GROUP_TYPE_12 (12 << 4)
|
||||
#define GROUP_TYPE_13 (13 << 4)
|
||||
#define GROUP_TYPE_14 (14 << 4)
|
||||
#define GROUP_TYPE_15 (15 << 4)
|
||||
|
||||
/* Group versions
|
||||
*
|
||||
* The first 4 bits are the group number and the remaining 4 are
|
||||
* the group version
|
||||
*/
|
||||
#define GROUP_VER_A 0
|
||||
#define GROUP_VER_B 1
|
||||
|
||||
/* Version A groups */
|
||||
#define GROUP_0A (GROUP_TYPE_0 | GROUP_VER_A)
|
||||
#define GROUP_1A (GROUP_TYPE_1 | GROUP_VER_A)
|
||||
#define GROUP_2A (GROUP_TYPE_2 | GROUP_VER_A)
|
||||
#define GROUP_3A (GROUP_TYPE_3 | GROUP_VER_A)
|
||||
#define GROUP_4A (GROUP_TYPE_4 | GROUP_VER_A)
|
||||
#define GROUP_5A (GROUP_TYPE_5 | GROUP_VER_A)
|
||||
#define GROUP_6A (GROUP_TYPE_6 | GROUP_VER_A)
|
||||
#define GROUP_7A (GROUP_TYPE_7 | GROUP_VER_A)
|
||||
#define GROUP_8A (GROUP_TYPE_8 | GROUP_VER_A)
|
||||
#define GROUP_9A (GROUP_TYPE_9 | GROUP_VER_A)
|
||||
#define GROUP_10A (GROUP_TYPE_10 | GROUP_VER_A)
|
||||
#define GROUP_11A (GROUP_TYPE_11 | GROUP_VER_A)
|
||||
#define GROUP_12A (GROUP_TYPE_12 | GROUP_VER_A)
|
||||
#define GROUP_13A (GROUP_TYPE_13 | GROUP_VER_A)
|
||||
#define GROUP_14A (GROUP_TYPE_14 | GROUP_VER_A)
|
||||
#define GROUP_15A (GROUP_TYPE_15 | GROUP_VER_A)
|
||||
|
||||
/* Version B groups */
|
||||
#define GROUP_0B (GROUP_TYPE_0 | GROUP_VER_B)
|
||||
#define GROUP_1B (GROUP_TYPE_1 | GROUP_VER_B)
|
||||
#define GROUP_2B (GROUP_TYPE_2 | GROUP_VER_B)
|
||||
#define GROUP_3B (GROUP_TYPE_3 | GROUP_VER_B)
|
||||
#define GROUP_4B (GROUP_TYPE_4 | GROUP_VER_B)
|
||||
#define GROUP_5B (GROUP_TYPE_5 | GROUP_VER_B)
|
||||
#define GROUP_6B (GROUP_TYPE_6 | GROUP_VER_B)
|
||||
#define GROUP_7B (GROUP_TYPE_7 | GROUP_VER_B)
|
||||
#define GROUP_8B (GROUP_TYPE_8 | GROUP_VER_B)
|
||||
#define GROUP_9B (GROUP_TYPE_9 | GROUP_VER_B)
|
||||
#define GROUP_10B (GROUP_TYPE_10 | GROUP_VER_B)
|
||||
#define GROUP_11B (GROUP_TYPE_11 | GROUP_VER_B)
|
||||
#define GROUP_12B (GROUP_TYPE_12 | GROUP_VER_B)
|
||||
#define GROUP_13B (GROUP_TYPE_13 | GROUP_VER_B)
|
||||
#define GROUP_14B (GROUP_TYPE_14 | GROUP_VER_B)
|
||||
#define GROUP_15B (GROUP_TYPE_15 | GROUP_VER_B)
|
||||
|
||||
#define GET_GROUP_TYPE(x) ((x >> 4) & 15)
|
||||
#define GET_GROUP_VER(x) (x & 1) /* only check bit 0 */
|
||||
|
||||
#define DI_STEREO (1 << 0) /* 1 - Stereo */
|
||||
#define DI_AH (1 << 1) /* 2 - Artificial Head */
|
||||
#define DI_COMPRESSED (1 << 2) /* 4 - Compressed */
|
||||
#define DI_DPTY (1 << 3) /* 8 - Dynamic PTY */
|
||||
|
||||
/* Bit mask */
|
||||
|
||||
/* 8 bit */
|
||||
#define INT8_ALL 0xff
|
||||
/* Lower */
|
||||
#define INT8_L1 0x01
|
||||
#define INT8_L2 0x03
|
||||
#define INT8_L3 0x07
|
||||
#define INT8_L4 0x0f
|
||||
#define INT8_L5 0x1f
|
||||
#define INT8_L6 0x3f
|
||||
#define INT8_L7 0x7f
|
||||
/* Upper */
|
||||
#define INT8_U7 0xfe
|
||||
#define INT8_U6 0xfc
|
||||
#define INT8_U5 0xf8
|
||||
#define INT8_U4 0xf0
|
||||
#define INT8_U3 0xe0
|
||||
#define INT8_U2 0xc0
|
||||
#define INT8_U1 0x80
|
||||
/* Single */
|
||||
#define INT8_0 0x01
|
||||
#define INT8_1 0x02
|
||||
#define INT8_2 0x04
|
||||
#define INT8_3 0x08
|
||||
#define INT8_4 0x10
|
||||
#define INT8_5 0x20
|
||||
#define INT8_6 0x40
|
||||
#define INT8_7 0x80
|
||||
|
||||
/* 16 bit */
|
||||
#define INT16_ALL 0xffff
|
||||
/* Lower */
|
||||
#define INT16_L1 0x0001
|
||||
#define INT16_L2 0x0003
|
||||
#define INT16_L3 0x0007
|
||||
#define INT16_L4 0x000f
|
||||
#define INT16_L5 0x001f
|
||||
#define INT16_L6 0x003f
|
||||
#define INT16_L7 0x007f
|
||||
#define INT16_L8 0x00ff
|
||||
#define INT16_L9 0x01ff
|
||||
#define INT16_L10 0x03ff
|
||||
#define INT16_L11 0x07ff
|
||||
#define INT16_L12 0x0fff
|
||||
#define INT16_L13 0x1fff
|
||||
#define INT16_L14 0x3fff
|
||||
#define INT16_L15 0x7fff
|
||||
/* Upper */
|
||||
#define INT16_U15 0xfffe
|
||||
#define INT16_U14 0xfffc
|
||||
#define INT16_U13 0xfff8
|
||||
#define INT16_U12 0xfff0
|
||||
#define INT16_U11 0xffe0
|
||||
#define INT16_U10 0xffc0
|
||||
#define INT16_U9 0xff80
|
||||
#define INT16_U8 0xff00
|
||||
#define INT16_U7 0xfe00
|
||||
#define INT16_U6 0xfc00
|
||||
#define INT16_U5 0xf800
|
||||
#define INT16_U4 0xf000
|
||||
#define INT16_U3 0xe000
|
||||
#define INT16_U2 0xc000
|
||||
#define INT16_U1 0x8000
|
||||
/* Single */
|
||||
#define INT16_0 0x0001
|
||||
#define INT16_1 0x0002
|
||||
#define INT16_2 0x0004
|
||||
#define INT16_3 0x0008
|
||||
#define INT16_4 0x0010
|
||||
#define INT16_5 0x0020
|
||||
#define INT16_6 0x0040
|
||||
#define INT16_7 0x0080
|
||||
#define INT16_8 0x0100
|
||||
#define INT16_9 0x0200
|
||||
#define INT16_10 0x0400
|
||||
#define INT16_11 0x0800
|
||||
#define INT16_12 0x1000
|
||||
#define INT16_13 0x2000
|
||||
#define INT16_14 0x4000
|
||||
#define INT16_15 0x8000
|
||||
|
||||
/* 18 bit (for RDS2) */
|
||||
#define INT18_U2 0x30000
|
||||
#define INT18_L4 0x0000f
|
||||
|
||||
#define IS_TYPE_B(a) (a[1] & INT16_11)
|
||||
#ifdef RDS2
|
||||
#define IS_TUNNELING(a) (a[0] == 0x0000)
|
||||
#endif
|
||||
|
||||
#ifdef ODA
|
||||
/*
|
||||
* RDS ODA ID group
|
||||
*
|
||||
* This struct is for defining ODAs that will be transmitted
|
||||
*
|
||||
* Can signal version A or B data groups
|
||||
*/
|
||||
typedef struct rds_oda_t {
|
||||
uint8_t group;
|
||||
uint16_t aid;
|
||||
uint16_t scb;
|
||||
} rds_oda_t;
|
||||
|
||||
#define MAX_ODAS 8
|
||||
|
||||
/*
|
||||
* ODA AID
|
||||
*
|
||||
* Extensive list: https://www.nrscstandards.org/committees/dsm/archive/rds-oda-aids.pdf
|
||||
*/
|
||||
#define ODA_AID_RTPLUS 0x4bd7
|
||||
#endif
|
||||
/* RDS2 */
|
||||
#define ODA_AID_RFT 0xff7f
|
||||
#define ODA_AID_RFTPLUS 0xff80
|
||||
|
||||
extern void init_rds_encoder(struct rds_params_t rds_params);
|
||||
extern void exit_rds_encoder();
|
||||
extern void get_rds_bits(uint8_t *bits);
|
||||
extern void set_rds_pi(uint16_t pi_code);
|
||||
extern void set_rds_ecc(uint8_t ecc);
|
||||
extern void set_rds_lic(uint8_t lic);
|
||||
extern void set_rds_ecclic_toggle(uint8_t toggle);
|
||||
extern void set_rds_pin_enabled(uint8_t enabled);
|
||||
extern void set_rds_pin(uint8_t day, uint8_t hour, uint8_t minute);
|
||||
extern void set_rds_rt1_enabled(uint8_t rt1en);
|
||||
extern void set_rds_rt1(unsigned char *rt1);
|
||||
extern void set_rds_ps(unsigned char *ps);
|
||||
extern void set_rds_tpson(uint8_t tpson);
|
||||
extern void set_rds_tps(unsigned char *ps);
|
||||
extern void set_rds_lpson(uint8_t lpson);
|
||||
extern void set_rds_lps(unsigned char *lps);
|
||||
#ifdef ODA_RTP
|
||||
extern void set_rds_rtplus_flags(uint8_t flags);
|
||||
extern void set_rds_rtplus_tags(uint8_t *tags);
|
||||
#endif
|
||||
extern void set_rds_ta(uint8_t ta);
|
||||
extern void set_rds_pty(uint8_t pty);
|
||||
extern void set_rds_ptyn_enabled(uint8_t enabled);
|
||||
extern void set_rds_ptyn(unsigned char *ptyn);
|
||||
extern void set_rds_af(struct rds_af_t new_af_list);
|
||||
extern void clear_rds_af();
|
||||
extern void set_rds_tp(uint8_t tp);
|
||||
extern void set_rds_ms(uint8_t ms);
|
||||
extern void set_rds_ct(uint8_t ct);
|
||||
extern void set_rds_di(uint8_t di);
|
||||
#ifdef RDS2
|
||||
extern float get_rds_sample(uint8_t stream_num, uint8_t rds2tunneling);
|
||||
#else
|
||||
extern float get_rds_sample(uint8_t stream_num);
|
||||
#endif
|
||||
extern uint16_t get_rds_pi();
|
||||
extern void set_rds_cg(uint16_t* blocks);
|
||||
|
||||
#endif /* RDS_H */
|
||||
452
src/rds2.c
Normal file
452
src/rds2.c
Normal file
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019-2020 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "rds.h"
|
||||
#include "rds2.h"
|
||||
#include "lib.h"
|
||||
|
||||
/*
|
||||
* RDS2-specific stuff
|
||||
*/
|
||||
|
||||
|
||||
/* RFT */
|
||||
|
||||
/* fallback station logo */
|
||||
#include "rds2_image_data.c"
|
||||
|
||||
/* station logo */
|
||||
static struct rft_t station_logo_stream;
|
||||
|
||||
static void init_rft(struct rft_t *rft, uint8_t file_id,
|
||||
bool usecrc, uint8_t crc_mode, char *file_path) {
|
||||
uint32_t seg_counter = 0;
|
||||
uint32_t crc_chunk_counter = 0;
|
||||
uint16_t crc_chunk_size = 0;
|
||||
FILE *image_file_hdlr;
|
||||
|
||||
rft->file_path = malloc(MAX_FILE_NAME_LEN + 1);
|
||||
rft->file_path[MAX_FILE_NAME_LEN] = 0;
|
||||
rft->file_data = malloc(MAX_IMAGE_LEN);
|
||||
rft->crcs = malloc(MAX_CRC_CHUNK_ADDR * sizeof(uint16_t));
|
||||
|
||||
/* unused data must be padded with zeroes */
|
||||
memset(rft->file_data, 0, MAX_IMAGE_LEN);
|
||||
|
||||
strncpy(rft->file_path, file_path, MAX_FILE_NAME_LEN);
|
||||
|
||||
/* open file in binary mode */
|
||||
image_file_hdlr = fopen(file_path, "rb");
|
||||
if (image_file_hdlr != NULL) {
|
||||
fseek(image_file_hdlr, 0, SEEK_END);
|
||||
rft->file_len = ftell(image_file_hdlr);
|
||||
rewind(image_file_hdlr);
|
||||
|
||||
/* image cannot be larger than 163840 bytes */
|
||||
if (rft->file_len > MAX_IMAGE_LEN) {
|
||||
#ifdef RDS2_DEBUG
|
||||
fprintf(stderr, "W: Image too large!\n");
|
||||
#endif
|
||||
rft->file_len = MAX_IMAGE_LEN;
|
||||
}
|
||||
|
||||
/* copy image data to local buffer */
|
||||
fread(rft->file_data, rft->file_len, 1, image_file_hdlr);
|
||||
|
||||
fclose(image_file_hdlr);
|
||||
} else { /* fallback in case file can't be read at startup */
|
||||
rft->file_len = station_logo_len;
|
||||
|
||||
if (rft->file_len > MAX_IMAGE_LEN) {
|
||||
rft->file_len = MAX_IMAGE_LEN;
|
||||
}
|
||||
|
||||
memcpy(rft->file_data, station_logo, rft->file_len);
|
||||
}
|
||||
|
||||
/* keep track of file lengths so we know when to toggle */
|
||||
rft->prev_file_len = rft->file_len;
|
||||
rft->channel = 0;
|
||||
rft->file_id = file_id;
|
||||
rft->toggle = 0;
|
||||
rft->num_segs = 0;
|
||||
|
||||
/* determine how many segments we need */
|
||||
while (seg_counter < rft->file_len) {
|
||||
seg_counter += 5;
|
||||
rft->num_segs++;
|
||||
}
|
||||
|
||||
rft->use_crc = usecrc;
|
||||
|
||||
/* if CRC is disabled exit early */
|
||||
if (!rft->use_crc) {
|
||||
rft->crc_mode = 0;
|
||||
rft->num_crc_chunks = 1; /* only 1 */
|
||||
rft->crcs[0] = 0x0000;
|
||||
return;
|
||||
}
|
||||
|
||||
rft->crc_mode = crc_mode & 7;
|
||||
|
||||
switch (rft->crc_mode) {
|
||||
case RFT_CRC_MODE_ENTIRE_FILE:
|
||||
/* no-op */
|
||||
break;
|
||||
|
||||
case RFT_CRC_MODE_16_GROUPS:
|
||||
crc_chunk_size = 16;
|
||||
break;
|
||||
|
||||
case RFT_CRC_MODE_32_GROUPS:
|
||||
crc_chunk_size = 32;
|
||||
break;
|
||||
|
||||
case RFT_CRC_MODE_64_GROUPS:
|
||||
crc_chunk_size = 64;
|
||||
break;
|
||||
|
||||
case RFT_CRC_MODE_128_GROUPS:
|
||||
crc_chunk_size = 128;
|
||||
break;
|
||||
|
||||
case RFT_CRC_MODE_256_GROUPS:
|
||||
crc_chunk_size = 256;
|
||||
break;
|
||||
|
||||
case RFT_CRC_MODE_RFU: /* reserved - use auto mode */
|
||||
case RFT_CRC_MODE_AUTO:
|
||||
if (rft->file_len >= 81920) {
|
||||
crc_chunk_size = 64;
|
||||
rft->crc_mode = RFT_CRC_MODE_64_GROUPS;
|
||||
} else if (rft->file_len > 40960) {
|
||||
crc_chunk_size = 32;
|
||||
rft->crc_mode = RFT_CRC_MODE_32_GROUPS;
|
||||
} else {
|
||||
crc_chunk_size = 16;
|
||||
rft->crc_mode = RFT_CRC_MODE_16_GROUPS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (rft->crc_mode) { /* chunked modes */
|
||||
/* RFT packet size = chunk size * 5 data bytes per group */
|
||||
rft->pkt_size = crc_chunk_size * 5;
|
||||
|
||||
/* the remainder (if file size is not a multiple of pkt_size) */
|
||||
rft->pkt_size_rem = rft->file_len % rft->pkt_size;
|
||||
|
||||
/* calculate number of CRC chunks */
|
||||
while (crc_chunk_counter <= rft->file_len) {
|
||||
crc_chunk_counter += rft->pkt_size;
|
||||
rft->num_crc_chunks++;
|
||||
}
|
||||
|
||||
rft->crc_chunks = malloc(rft->pkt_size);
|
||||
|
||||
/* calculate CRC's for chunks */
|
||||
for (uint16_t i = 0; i < rft->num_crc_chunks; i++) {
|
||||
memset(rft->crc_chunks, 0, rft->pkt_size);
|
||||
memcpy(rft->crc_chunks,
|
||||
rft->file_data + i * rft->pkt_size, rft->pkt_size);
|
||||
if ((i == rft->num_crc_chunks - 1) && rft->pkt_size_rem) {
|
||||
/* last chunk may have less bytes */
|
||||
rft->crcs[i] = crc16(rft->crc_chunks, rft->pkt_size_rem);
|
||||
} else {
|
||||
rft->crcs[i] = crc16(rft->crc_chunks, rft->pkt_size);
|
||||
}
|
||||
}
|
||||
|
||||
free(rft->crc_chunks);
|
||||
} else { /* CRC of entire file */
|
||||
rft->num_crc_chunks = 1; /* only 1 */
|
||||
rft->crcs[0] = crc16(rft->file_data, rft->file_len);
|
||||
}
|
||||
}
|
||||
|
||||
static void update_rft(struct rft_t *rft) {
|
||||
uint32_t seg_counter = 0;
|
||||
FILE *image_file_hdlr;
|
||||
|
||||
/* open file in binary mode */
|
||||
image_file_hdlr = fopen(rft->file_path, "rb");
|
||||
if (image_file_hdlr == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
rft->prev_file_len = rft->file_len;
|
||||
|
||||
/* update size field */
|
||||
fseek(image_file_hdlr, 0, SEEK_END);
|
||||
rft->file_len = ftell(image_file_hdlr);
|
||||
rewind(image_file_hdlr);
|
||||
|
||||
/* file didn't change so return early */
|
||||
if (rft->file_len == rft->prev_file_len) {
|
||||
fclose(image_file_hdlr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* image cannot be larger than 163840 bytes */
|
||||
if (rft->file_len > MAX_IMAGE_LEN) {
|
||||
#ifdef RDS2_DEBUG
|
||||
fprintf(stderr, "W: Image too large!\n");
|
||||
#endif
|
||||
rft->file_len = MAX_IMAGE_LEN;
|
||||
}
|
||||
|
||||
/* copy image data to local buffer */
|
||||
fread(rft->file_data, rft->file_len, 1, image_file_hdlr);
|
||||
|
||||
fclose(image_file_hdlr);
|
||||
|
||||
rft->num_segs = 0;
|
||||
|
||||
/* recalculate # of needed segments */
|
||||
while (seg_counter < rft->file_len) {
|
||||
seg_counter += 5;
|
||||
rft->num_segs++;
|
||||
}
|
||||
|
||||
if (rft->crc_mode) {
|
||||
/* recalculate CRC's for chunks */
|
||||
for (uint16_t i = 0; i < rft->num_crc_chunks; i++) {
|
||||
memset(rft->crc_chunks, 0, rft->pkt_size);
|
||||
memcpy(rft->crc_chunks,
|
||||
rft->file_data + i * rft->pkt_size, rft->pkt_size);
|
||||
if ((i == rft->num_crc_chunks - 1) && rft->pkt_size_rem) {
|
||||
/* last chunk may have less bytes */
|
||||
rft->crcs[i] = crc16(rft->crc_chunks, rft->pkt_size_rem);
|
||||
} else {
|
||||
rft->crcs[i] = crc16(rft->crc_chunks, rft->pkt_size);
|
||||
}
|
||||
}
|
||||
|
||||
free(rft->crc_chunks);
|
||||
} else { /* CRC of entire file */
|
||||
rft->num_crc_chunks = 1; /* only 1 */
|
||||
rft->crcs[0] = crc16(rft->file_data, rft->file_len);
|
||||
}
|
||||
|
||||
if (++rft->file_version == 8) {
|
||||
rft->file_version = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_rft(struct rft_t *rft) {
|
||||
free(rft->file_data);
|
||||
free(rft->file_path);
|
||||
free(rft->crcs);
|
||||
}
|
||||
|
||||
/*
|
||||
* RFT variant 0 (file metadata)
|
||||
*
|
||||
* Carries:
|
||||
* - Function Header
|
||||
* - Pipe Number
|
||||
* - ODA ID
|
||||
* - Variant code (0)
|
||||
* - CRC-16 Flag
|
||||
* - File ID
|
||||
* - File size (18 bits)
|
||||
*/
|
||||
static void get_rft_var_0_data_group(struct rft_t *rft, uint16_t *blocks) {
|
||||
/* function header */
|
||||
blocks[0] = 1 << 15;
|
||||
blocks[0] |= (rft->channel & INT8_L4); /* pipe number */
|
||||
|
||||
/* ODA ID */
|
||||
blocks[1] = ODA_AID_RFT;
|
||||
|
||||
blocks[2] = (0 & INT18_L4) << 12; /* variant code */
|
||||
blocks[2] |= ((rft->use_crc ? 1 : 0) & INT8_L1) << 11; /* CRC */
|
||||
blocks[2] |= (rft->file_version & INT8_L3) << 8; /* file version */
|
||||
blocks[2] |= (rft->file_id & INT8_L6) << 2; /* file ID */
|
||||
blocks[2] |= (rft->file_len & INT18_U2) >> 16;
|
||||
|
||||
blocks[3] = rft->file_len & INT16_ALL;
|
||||
}
|
||||
|
||||
/*
|
||||
* RFT variant 1 (chunk to CRC mappings)
|
||||
*
|
||||
* Carries:
|
||||
* - Function Header
|
||||
* - Pipe Number
|
||||
* - ODA ID
|
||||
* - Variant Code (1)
|
||||
* - CRC Mode
|
||||
* - Chunk Address (9 bits)
|
||||
* - CRC
|
||||
*/
|
||||
static void get_rft_var_1_data_group(struct rft_t *rft, uint16_t *blocks) {
|
||||
/* function header */
|
||||
blocks[0] = 1 << 15;
|
||||
blocks[0] |= (rft->channel & INT8_L4); /* pipe number */
|
||||
|
||||
/* ODA ID */
|
||||
blocks[1] = ODA_AID_RFT;
|
||||
|
||||
blocks[2] = (1 & INT8_L4) << 12; /* variant code */
|
||||
blocks[2] |= (rft->crc_mode & INT8_L3) << 9; /* CRC mode */
|
||||
/* chunk address */
|
||||
blocks[2] |= rft->crc_chunk_addr & INT16_L9;
|
||||
|
||||
/* CRC */
|
||||
blocks[3] = rft->crcs[rft->crc_chunk_addr];
|
||||
|
||||
if (++rft->crc_chunk_addr > rft->num_crc_chunks) {
|
||||
#ifdef RDS2_DEBUG
|
||||
fprintf(stderr, "File CRC sending complete\n");
|
||||
#endif
|
||||
rft->crc_chunk_addr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* RFT variants 2-15 (custom variants)
|
||||
*
|
||||
* Carries:
|
||||
* - Function Header
|
||||
* - Pipe Number
|
||||
* - Variant code
|
||||
* - Variant data:
|
||||
* - 2-7 carries file-related data
|
||||
* - 8-15 ODA data that is not related
|
||||
*/
|
||||
#if 0
|
||||
static void get_rft_var_2_15_data_group(struct rft_t *rft, uint16_t *blocks) {
|
||||
/* function header */
|
||||
blocks[0] = (2 << 6) << 8;
|
||||
blocks[0] |= (rft->channel & INT8_L4); /* pipe number */
|
||||
|
||||
/* ODA ID */
|
||||
blocks[1] = ODA_AID_RFT;
|
||||
|
||||
blocks[2] = (8 & INT8_L4) << 12; /* variant code */
|
||||
blocks[2] |= 0 & INT16_L12;
|
||||
blocks[3] = 0 & INT16_ALL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* RFT data group (for file data TXing)
|
||||
*
|
||||
* Carries:
|
||||
* - Function Header
|
||||
* - Pipe Number
|
||||
* - Toggle Bit
|
||||
* - Segment address (15 bits)
|
||||
* - File data (5 bytes per group)
|
||||
*/
|
||||
static void get_rft_file_data_group(struct rft_t *rft, uint16_t *blocks) {
|
||||
/* function header */
|
||||
blocks[0] = 2 << 12;
|
||||
blocks[0] |= (rft->channel & INT8_L4) << 8; /* pipe number */
|
||||
|
||||
/* toggle bit */
|
||||
blocks[0] |= (rft->toggle & INT16_L1) << 7;
|
||||
|
||||
/* segment address */
|
||||
blocks[0] |= ((rft->seg_addr_img & INT16_U8) >> 8) & INT16_L7;
|
||||
blocks[1] = (rft->seg_addr_img & INT16_L8) << 8;
|
||||
|
||||
/* image data */
|
||||
blocks[1] |= rft->file_data[rft->seg_addr_img * 5 + 0];
|
||||
blocks[2] = rft->file_data[rft->seg_addr_img * 5 + 1] << 8;
|
||||
blocks[2] |= rft->file_data[rft->seg_addr_img * 5 + 2];
|
||||
blocks[3] = rft->file_data[rft->seg_addr_img * 5 + 3] << 8;
|
||||
blocks[3] |= rft->file_data[rft->seg_addr_img * 5 + 4];
|
||||
|
||||
if (++rft->seg_addr_img > rft->num_segs) {
|
||||
#ifdef RDS2_DEBUG
|
||||
fprintf(stderr, "File sending complete\n");
|
||||
#endif
|
||||
rft->seg_addr_img = 0;
|
||||
rft->toggle ^= 1;
|
||||
update_rft(rft);
|
||||
}
|
||||
}
|
||||
|
||||
static void get_rft_stream(uint16_t *blocks) {
|
||||
static uint8_t rft_state;
|
||||
|
||||
switch (rft_state) {
|
||||
case 0:
|
||||
get_rft_var_0_data_group(&station_logo_stream, blocks);
|
||||
break;
|
||||
case 1:
|
||||
get_rft_var_1_data_group(&station_logo_stream, blocks);
|
||||
break;
|
||||
case 2:
|
||||
get_rft_var_0_data_group(&station_logo_stream, blocks);
|
||||
break;
|
||||
case 3:
|
||||
get_rft_var_1_data_group(&station_logo_stream, blocks);
|
||||
break;
|
||||
|
||||
default:
|
||||
get_rft_file_data_group(&station_logo_stream, blocks);
|
||||
break;
|
||||
}
|
||||
|
||||
rft_state++;
|
||||
if (rft_state == 50) rft_state = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* RDS 2 group sequence
|
||||
*/
|
||||
static void get_rds2_group(uint8_t stream_num, uint16_t *blocks) {
|
||||
|
||||
switch (stream_num) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
default:
|
||||
get_rft_stream(blocks);
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef RDS2_DEBUG
|
||||
fprintf(stderr, "Stream %u: %04x %04x %04x %04x\n",
|
||||
stream_num, blocks[0], blocks[1], blocks[2], blocks[3]);
|
||||
#endif
|
||||
}
|
||||
|
||||
void get_rds2_bits(uint8_t stream, uint8_t *bits) {
|
||||
static uint16_t out_blocks[GROUP_LENGTH];
|
||||
get_rds2_group(stream, out_blocks);
|
||||
add_checkwords(out_blocks, bits, true);
|
||||
}
|
||||
|
||||
void init_rds2_encoder(char *station_logo_path) {
|
||||
/* create a new stream for the station logo */
|
||||
init_rft(&station_logo_stream,
|
||||
0 /* file ID */,
|
||||
false /* don't use crc */,
|
||||
RFT_CRC_MODE_AUTO,
|
||||
station_logo_path
|
||||
);
|
||||
}
|
||||
|
||||
void exit_rds2_encoder() {
|
||||
exit_rft(&station_logo_stream);
|
||||
}
|
||||
71
src/rds2.h
Normal file
71
src/rds2.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019-2020 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
typedef struct rds2_oda_t {
|
||||
uint16_t aid;
|
||||
uint8_t channel;
|
||||
} rds2_oda_t;
|
||||
|
||||
#define GET_RDS2_ODA_CHANNEL(x) (x & INT8_L5)
|
||||
|
||||
#define MAX_FILE_NAME_LEN 64 /* does not include terminating null char */
|
||||
#define MAX_IMAGE_LEN 163840
|
||||
#define MAX_CRC_CHUNK_ADDR 511
|
||||
|
||||
/* RFT CRC mode */
|
||||
enum rft_crc_mode {
|
||||
RFT_CRC_MODE_ENTIRE_FILE, /* over entire file (universal) */
|
||||
RFT_CRC_MODE_16_GROUPS, /* chunks of 16 groups (size <= 40,960) */
|
||||
RFT_CRC_MODE_32_GROUPS, /* chunks of 32 groups (40,960 < size <= 81,920) */
|
||||
RFT_CRC_MODE_64_GROUPS, /* chunks of 64 groups (size > 81,960) */
|
||||
RFT_CRC_MODE_128_GROUPS, /* chunks of 128 groups (size > 81,960) */
|
||||
RFT_CRC_MODE_256_GROUPS, /* chunks of 256 groups (size > 81,960) */
|
||||
RFT_CRC_MODE_RFU, /* reserved */
|
||||
RFT_CRC_MODE_AUTO /* automatic between 1-3 based on size */
|
||||
};
|
||||
|
||||
/* RDS2 File Transfer */
|
||||
typedef struct rft_t {
|
||||
uint8_t channel;
|
||||
|
||||
char *file_path;
|
||||
unsigned char *file_data;
|
||||
size_t file_len;
|
||||
size_t prev_file_len;
|
||||
uint8_t file_version;
|
||||
uint8_t file_id;
|
||||
uint8_t variant_code;
|
||||
uint8_t toggle;
|
||||
|
||||
uint16_t pkt_size;
|
||||
uint16_t pkt_size_rem;
|
||||
uint16_t seg_addr_img;
|
||||
uint16_t num_segs;
|
||||
|
||||
/* CRC chunk map */
|
||||
bool use_crc;
|
||||
uint8_t crc_mode;
|
||||
uint16_t crc_chunk_addr;
|
||||
uint16_t num_crc_chunks;
|
||||
uint8_t *crc_chunks;
|
||||
uint16_t *crcs;
|
||||
} rft_t;
|
||||
|
||||
extern void get_rds2_bits(uint8_t stream_num, uint8_t *bits);
|
||||
extern void init_rds2_encoder(char *station_logo_path);
|
||||
extern void exit_rds2_encoder();
|
||||
181
src/rds2_image_data.c
Normal file
181
src/rds2_image_data.c
Normal file
@@ -0,0 +1,181 @@
|
||||
static const unsigned char station_logo[] = {
|
||||
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01,
|
||||
0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xff, 0xe2, 0x01, 0xd8,
|
||||
0x49, 0x43, 0x43, 0x5f, 0x50, 0x52, 0x4f, 0x46, 0x49, 0x4c, 0x45, 0x00,
|
||||
0x01, 0x01, 0x00, 0x00, 0x01, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x04, 0x30,
|
||||
0x00, 0x00, 0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59,
|
||||
0x5a, 0x20, 0x07, 0xe0, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x61, 0x63, 0x73, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0xf6, 0xd6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x09, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00,
|
||||
0x00, 0x24, 0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00,
|
||||
0x00, 0x14, 0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x28, 0x00, 0x00,
|
||||
0x00, 0x14, 0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x3c, 0x00, 0x00,
|
||||
0x00, 0x14, 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00,
|
||||
0x00, 0x14, 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x64, 0x00, 0x00,
|
||||
0x00, 0x28, 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x64, 0x00, 0x00,
|
||||
0x00, 0x28, 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x64, 0x00, 0x00,
|
||||
0x00, 0x28, 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x01, 0x8c, 0x00, 0x00,
|
||||
0x00, 0x3c, 0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00,
|
||||
0x00, 0x08, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x73, 0x00, 0x52, 0x00, 0x47,
|
||||
0x00, 0x42, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x6f, 0xa2, 0x00, 0x00, 0x38, 0xf5, 0x00, 0x00, 0x03, 0x90, 0x58, 0x59,
|
||||
0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x99, 0x00, 0x00,
|
||||
0xb7, 0x85, 0x00, 0x00, 0x18, 0xda, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x24, 0xa0, 0x00, 0x00, 0x0f, 0x84, 0x00, 0x00,
|
||||
0xb6, 0xcf, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xf6, 0xd6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x70, 0x61,
|
||||
0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02,
|
||||
0x66, 0x66, 0x00, 0x00, 0xf2, 0xa7, 0x00, 0x00, 0x0d, 0x59, 0x00, 0x00,
|
||||
0x13, 0xd0, 0x00, 0x00, 0x0a, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00,
|
||||
0x00, 0x20, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x47, 0x00, 0x6f, 0x00, 0x6f,
|
||||
0x00, 0x67, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x49, 0x00, 0x6e,
|
||||
0x00, 0x63, 0x00, 0x2e, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31,
|
||||
0x00, 0x36, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x1b, 0x12, 0x14, 0x17, 0x14,
|
||||
0x11, 0x1b, 0x17, 0x16, 0x17, 0x1e, 0x1c, 0x1b, 0x20, 0x28, 0x42, 0x2b,
|
||||
0x28, 0x25, 0x25, 0x28, 0x51, 0x3a, 0x3d, 0x30, 0x42, 0x60, 0x55, 0x65,
|
||||
0x64, 0x5f, 0x55, 0x5d, 0x5b, 0x6a, 0x78, 0x99, 0x81, 0x6a, 0x71, 0x90,
|
||||
0x73, 0x5b, 0x5d, 0x85, 0xb5, 0x86, 0x90, 0x9e, 0xa3, 0xab, 0xad, 0xab,
|
||||
0x67, 0x80, 0xbc, 0xc9, 0xba, 0xa6, 0xc7, 0x99, 0xa8, 0xab, 0xa4, 0xff,
|
||||
0xdb, 0x00, 0x43, 0x01, 0x1c, 0x1e, 0x1e, 0x28, 0x23, 0x28, 0x4e, 0x2b,
|
||||
0x2b, 0x4e, 0xa4, 0x6e, 0x5d, 0x6e, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
|
||||
0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
|
||||
0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
|
||||
0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
|
||||
0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xff, 0xc0, 0x00, 0x11,
|
||||
0x08, 0x00, 0xcd, 0x00, 0xcd, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01,
|
||||
0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x1a, 0x00, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x05, 0x04, 0x06, 0x02, 0x03, 0x01, 0xff, 0xc4, 0x00, 0x3c, 0x10,
|
||||
0x00, 0x01, 0x03, 0x03, 0x02, 0x02, 0x05, 0x09, 0x05, 0x08, 0x03, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x11, 0x12,
|
||||
0x21, 0x06, 0x31, 0x13, 0x22, 0x41, 0x91, 0xa1, 0x14, 0x32, 0x51, 0x61,
|
||||
0x71, 0x73, 0x81, 0xb1, 0xd1, 0x34, 0x36, 0x52, 0xc2, 0xe1, 0x16, 0x24,
|
||||
0x33, 0x35, 0x42, 0x44, 0x82, 0xc1, 0x54, 0xb2, 0xf0, 0xff, 0xc4, 0x00,
|
||||
0x14, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x11,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00,
|
||||
0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xe6, 0x40, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x56,
|
||||
0xc7, 0x68, 0x65, 0xd1, 0x26, 0x57, 0xcc, 0xe8, 0xfa, 0x3d, 0x3c, 0x93,
|
||||
0x39, 0xce, 0x7e, 0x85, 0x2a, 0x7e, 0x14, 0x6a, 0xac, 0x8b, 0x51, 0x3b,
|
||||
0x91, 0x35, 0x2a, 0x31, 0x18, 0x89, 0x9c, 0x76, 0x2a, 0xfd, 0x00, 0xe6,
|
||||
0x01, 0x42, 0x9e, 0xd8, 0xda, 0xab, 0xa3, 0xa8, 0xa0, 0xaa, 0x8d, 0xcd,
|
||||
0x6a, 0xff, 0x00, 0x15, 0x76, 0xd4, 0x89, 0xcf, 0x09, 0xdb, 0xdb, 0xdc,
|
||||
0x5b, 0x4e, 0x13, 0xa7, 0xe8, 0xb0, 0xb5, 0x32, 0xf4, 0x9f, 0x8b, 0x09,
|
||||
0x8e, 0xef, 0xd4, 0x0e, 0x50, 0x1b, 0x6e, 0xb6, 0xc9, 0x6d, 0x93, 0xa4,
|
||||
0x72, 0x2a, 0x3d, 0x8e, 0x4c, 0xb1, 0xe8, 0x98, 0xcf, 0xea, 0x6d, 0xb2,
|
||||
0xd8, 0x16, 0xe1, 0x0f, 0x94, 0x4d, 0x22, 0xc7, 0x12, 0xae, 0x1a, 0x8d,
|
||||
0x4d, 0xdd, 0xe9, 0x5f, 0x56, 0xe0, 0x67, 0xb1, 0xdb, 0xa3, 0xb9, 0xd4,
|
||||
0xbe, 0x29, 0x1e, 0xe6, 0x23, 0x59, 0xab, 0x2d, 0xf6, 0xa1, 0xf9, 0x55,
|
||||
0x6f, 0x8e, 0x1b, 0xe3, 0x68, 0x1a, 0xf7, 0x2b, 0x16, 0x46, 0x33, 0x52,
|
||||
0xf3, 0xeb, 0x63, 0xea, 0x74, 0x76, 0xab, 0x2a, 0xdb, 0x2e, 0x2f, 0x92,
|
||||
0x39, 0x3a, 0x48, 0x5f, 0x1a, 0xa2, 0x2a, 0xf3, 0x6a, 0xe5, 0x36, 0x5f,
|
||||
0x49, 0x12, 0xee, 0xc5, 0x97, 0x8a, 0x56, 0x36, 0xbd, 0x58, 0xaf, 0x92,
|
||||
0x36, 0xa3, 0x9b, 0xcd, 0xb9, 0x46, 0xee, 0x80, 0x78, 0xbf, 0x5a, 0x62,
|
||||
0xb5, 0xf4, 0x1d, 0x14, 0x8f, 0x7f, 0x49, 0xab, 0x3a, 0xb1, 0xb6, 0x31,
|
||||
0xf5, 0x24, 0x96, 0xb8, 0x8a, 0x86, 0x5a, 0x3f, 0x27, 0xe9, 0x2b, 0x26,
|
||||
0xa9, 0xd7, 0xab, 0x1d, 0x22, 0xaa, 0xe9, 0xc6, 0x39, 0x6f, 0xeb, 0x3f,
|
||||
0x6d, 0x1c, 0x3d, 0x25, 0x74, 0x29, 0x51, 0x3c, 0x8b, 0x14, 0x4e, 0x5e,
|
||||
0xaa, 0x22, 0x65, 0xce, 0x4f, 0x4f, 0xa8, 0x08, 0x87, 0xa8, 0xa3, 0x7c,
|
||||
0xd2, 0xb2, 0x28, 0xd3, 0x53, 0xde, 0xa8, 0xd6, 0xa7, 0xa5, 0x54, 0xe9,
|
||||
0x6a, 0xb8, 0x4d, 0xbd, 0x1a, 0xad, 0x25, 0x43, 0xb5, 0xa2, 0x79, 0xb2,
|
||||
0x22, 0x6f, 0xf1, 0x4e, 0x44, 0xbb, 0x03, 0x7a, 0x2b, 0xf4, 0x0c, 0x95,
|
||||
0x34, 0xb9, 0xae, 0x73, 0x55, 0x1d, 0xd8, 0xba, 0x57, 0x6e, 0xf0, 0x2b,
|
||||
0xd3, 0x70, 0x9c, 0x29, 0x1f, 0xef, 0x35, 0x12, 0x39, 0xeb, 0xd9, 0x1e,
|
||||
0x11, 0x13, 0xbd, 0x17, 0x3e, 0x04, 0xbb, 0xcd, 0x8e, 0x4b, 0x6b, 0x52,
|
||||
0x68, 0xde, 0xb2, 0xc0, 0xab, 0x85, 0x5c, 0x61, 0x5a, 0xbe, 0xbf, 0xa9,
|
||||
0x53, 0x8a, 0x22, 0xaf, 0x92, 0x68, 0x3c, 0x99, 0xb2, 0xbe, 0x14, 0x4c,
|
||||
0xe2, 0x24, 0x55, 0xc3, 0xb3, 0xcd, 0x71, 0xf0, 0xc7, 0xc4, 0xd5, 0x77,
|
||||
0x47, 0xb7, 0x86, 0x5c, 0x95, 0x19, 0x59, 0x12, 0x38, 0xd1, 0xfb, 0xe5,
|
||||
0x75, 0x65, 0x3f, 0xd8, 0x11, 0x6c, 0x76, 0x58, 0x6e, 0x74, 0xf2, 0x49,
|
||||
0x24, 0xaf, 0x62, 0xb5, 0xfa, 0x51, 0x1b, 0x8f, 0x41, 0x4b, 0xf6, 0x4e,
|
||||
0x97, 0xfe, 0x44, 0xde, 0x07, 0xef, 0x07, 0x7d, 0x86, 0x7f, 0x7b, 0xfe,
|
||||
0x90, 0xf3, 0x51, 0x64, 0xb9, 0x4b, 0x2c, 0xf2, 0xb2, 0xe0, 0xac, 0xd4,
|
||||
0xf7, 0x39, 0x8c, 0xd6, 0xec, 0x22, 0x2a, 0xed, 0xbf, 0x67, 0x70, 0x10,
|
||||
0xef, 0x54, 0x0c, 0xb7, 0x56, 0x24, 0x11, 0xbd, 0xcf, 0x45, 0x62, 0x3b,
|
||||
0x2e, 0xe7, 0xda, 0x60, 0x3e, 0xf5, 0xac, 0xa8, 0x8a, 0xa5, 0xf1, 0xd5,
|
||||
0xab, 0xd6, 0x56, 0x6c, 0xba, 0x9d, 0x93, 0xe0, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x07, 0x4f, 0xc1, 0x9e, 0x6d, 0x5f, 0xb5, 0x9f,
|
||||
0x98, 0x97, 0x7a, 0xad, 0xa9, 0x75, 0xda, 0xa3, 0x13, 0x48, 0xd4, 0x63,
|
||||
0x95, 0x8d, 0x46, 0xb9, 0x51, 0x11, 0x39, 0x78, 0x95, 0x38, 0x33, 0xcd,
|
||||
0xab, 0xf6, 0xb3, 0xf3, 0x10, 0xee, 0xff, 0x00, 0xcd, 0x6a, 0xfd, 0xeb,
|
||||
0xbe, 0x60, 0x7d, 0x2d, 0x16, 0xea, 0x8b, 0x85, 0x4e, 0x20, 0x7f, 0x47,
|
||||
0xd1, 0xe1, 0xce, 0x93, 0xf0, 0xef, 0xb6, 0x3d, 0x7f, 0x42, 0xe3, 0x38,
|
||||
0x6a, 0x76, 0xd4, 0xa5, 0x42, 0x5c, 0xde, 0xb3, 0x22, 0xe7, 0x5a, 0xc7,
|
||||
0x95, 0xff, 0x00, 0xb1, 0x97, 0x84, 0x2a, 0x62, 0x8a, 0x6a, 0x88, 0x64,
|
||||
0x7a, 0x35, 0xf2, 0xa3, 0x74, 0x65, 0x71, 0x9c, 0x67, 0x6f, 0x6e, 0xe6,
|
||||
0x9a, 0xae, 0x1e, 0xa5, 0x8e, 0xa2, 0x4a, 0xba, 0x9a, 0xd7, 0x36, 0x15,
|
||||
0x72, 0xbd, 0xc8, 0xa9, 0xbe, 0xfb, 0xe3, 0x3f, 0xa0, 0x0e, 0x31, 0x6a,
|
||||
0x79, 0x35, 0x3b, 0xb1, 0xba, 0x3d, 0x53, 0x3f, 0x0f, 0xd0, 0xd8, 0xd7,
|
||||
0x2d, 0x3f, 0x0b, 0xb5, 0xd1, 0x75, 0x1c, 0x94, 0xc8, 0xa8, 0xa9, 0xb6,
|
||||
0x15, 0x53, 0x9f, 0x89, 0x97, 0x8c, 0x7e, 0xc7, 0x07, 0xbc, 0x5f, 0x91,
|
||||
0xf6, 0xb0, 0xd5, 0xc1, 0x5f, 0x6a, 0x4a, 0x39, 0x5c, 0x8b, 0x23, 0x18,
|
||||
0xb1, 0xbd, 0x8b, 0xb2, 0xab, 0x79, 0x22, 0xa7, 0xc3, 0x00, 0x4f, 0xe0,
|
||||
0xf9, 0xa4, 0x5a, 0xa9, 0xe2, 0x57, 0xaa, 0xb1, 0x59, 0xaf, 0x0a, 0xbd,
|
||||
0xb9, 0x4d, 0xfc, 0x4f, 0x8d, 0xc7, 0xef, 0x7b, 0x3d, 0xf4, 0x5f, 0x26,
|
||||
0x97, 0x6d, 0x96, 0xea, 0x2b, 0x6d, 0x53, 0xe2, 0x82, 0x47, 0x3e, 0x67,
|
||||
0xb3, 0x2a, 0x8f, 0x54, 0x55, 0x6b, 0x51, 0x7d, 0x49, 0xb7, 0x3f, 0x02,
|
||||
0x15, 0xc7, 0xef, 0x7b, 0x3d, 0xf4, 0x5f, 0x26, 0x81, 0xab, 0x8c, 0xff,
|
||||
0x00, 0xb3, 0xff, 0x00, 0x3f, 0xca, 0x6f, 0xb8, 0x48, 0xfa, 0x5e, 0x19,
|
||||
0x47, 0x40, 0xf5, 0x63, 0x9b, 0x14, 0x68, 0x8e, 0x45, 0xdd, 0x39, 0x27,
|
||||
0xc8, 0xc1, 0xc6, 0x7f, 0xd9, 0xff, 0x00, 0x9f, 0xe5, 0x36, 0x5a, 0xea,
|
||||
0xa9, 0x2e, 0xd6, 0x96, 0xd1, 0x4c, 0xa9, 0xad, 0x18, 0x8c, 0x7b, 0x35,
|
||||
0x61, 0x57, 0x1c, 0x95, 0x3b, 0x91, 0x40, 0xc5, 0xc2, 0x35, 0x53, 0x3e,
|
||||
0x79, 0xe1, 0x7c, 0x8e, 0x7b, 0x34, 0x23, 0x91, 0x1c, 0xaa, 0xb8, 0x5c,
|
||||
0xf6, 0x77, 0x93, 0x6f, 0xeb, 0xd0, 0xdf, 0xa7, 0x74, 0x5d, 0x47, 0x35,
|
||||
0xcd, 0x72, 0x2a, 0x6d, 0x85, 0xd2, 0x8b, 0x9e, 0xf3, 0xa6, 0xa5, 0xa3,
|
||||
0xa0, 0xb1, 0x43, 0x24, 0x8b, 0x2a, 0xb5, 0x1d, 0xcd, 0xf2, 0xb9, 0x32,
|
||||
0xb8, 0xec, 0x43, 0x8f, 0xb8, 0xd5, 0x2d, 0x6d, 0x74, 0xd5, 0x18, 0xc2,
|
||||
0x3d, 0xdb, 0x22, 0xf6, 0x27, 0x24, 0xf0, 0xc0, 0x1d, 0x1d, 0xa3, 0x88,
|
||||
0x27, 0xad, 0xa8, 0x8a, 0x95, 0xd4, 0xad, 0x73, 0xd7, 0xce, 0x91, 0x1d,
|
||||
0x84, 0x44, 0x4e, 0xdc, 0x63, 0xff, 0x00, 0x29, 0xe3, 0x8b, 0xeb, 0x34,
|
||||
0xc5, 0x15, 0x1b, 0x57, 0x77, 0x2f, 0x48, 0xff, 0x00, 0x67, 0x24, 0xf1,
|
||||
0xcf, 0x71, 0xeb, 0x84, 0xa8, 0xd2, 0x2a, 0x69, 0x2b, 0x5f, 0xb2, 0xc9,
|
||||
0xd5, 0x6a, 0xaf, 0x63, 0x53, 0x9a, 0xf7, 0xfc, 0x8e, 0x7a, 0xe7, 0x56,
|
||||
0xb5, 0xd5, 0xf3, 0x54, 0x7f, 0x4b, 0x9d, 0xd5, 0x4f, 0x43, 0x53, 0x64,
|
||||
0xf0, 0x03, 0x6d, 0x9a, 0xf0, 0xfb, 0x6c, 0x0f, 0x8d, 0xb4, 0xab, 0x32,
|
||||
0x39, 0xda, 0xb3, 0xab, 0x18, 0xdb, 0xd8, 0x6c, 0xb3, 0x5e, 0xab, 0x6a,
|
||||
0xef, 0x2c, 0x8e, 0x59, 0x11, 0x62, 0x97, 0x57, 0x53, 0x09, 0x86, 0xec,
|
||||
0xaa, 0x98, 0x3c, 0xf0, 0xad, 0xce, 0x1a, 0x74, 0x92, 0x92, 0x77, 0xb6,
|
||||
0x34, 0x7b, 0xb5, 0xb1, 0xce, 0xd9, 0x33, 0x8d, 0xd1, 0x57, 0xe0, 0x85,
|
||||
0x88, 0x6d, 0x14, 0x14, 0x55, 0x6f, 0xae, 0x6a, 0xab, 0x15, 0x32, 0xbd,
|
||||
0x67, 0x22, 0x31, 0x99, 0xe6, 0xa8, 0x04, 0x4e, 0x30, 0x6e, 0x2e, 0x31,
|
||||
0x3b, 0xd3, 0x12, 0x27, 0x8a, 0x90, 0x4a, 0x7c, 0x43, 0x5d, 0x1d, 0x7d,
|
||||
0xc5, 0x5f, 0x0e, 0xf1, 0xc6, 0xd4, 0x62, 0x3b, 0xf1, 0x73, 0x5c, 0xf8,
|
||||
0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xd9
|
||||
};
|
||||
static const unsigned int station_logo_len = sizeof(station_logo);
|
||||
52
src/resampler.c
Normal file
52
src/resampler.c
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "resampler.h"
|
||||
|
||||
int8_t resampler_init(SRC_STATE **src_state, uint8_t channels) {
|
||||
int src_error;
|
||||
|
||||
*src_state = src_new(CONVERTER_TYPE, channels, &src_error);
|
||||
|
||||
if (*src_state == NULL) {
|
||||
fprintf(stderr, "Error: src_new failed: %s\n", src_strerror(src_error));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t resample(SRC_STATE *src_state, SRC_DATA src_data, size_t *frames_generated) {
|
||||
int src_error;
|
||||
|
||||
src_error = src_process(src_state, &src_data);
|
||||
|
||||
if (src_error) {
|
||||
fprintf(stderr, "Error: src_process failed: %s\n", src_strerror(src_error));
|
||||
return -1;
|
||||
}
|
||||
|
||||
*frames_generated = src_data.output_frames_gen;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void resampler_exit(SRC_STATE *src_state) {
|
||||
src_delete(src_state);
|
||||
}
|
||||
25
src/resampler.h
Normal file
25
src/resampler.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* mpxgen - FM multiplex encoder with Stereo and RDS
|
||||
* Copyright (C) 2019 Anthony96922
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <samplerate.h>
|
||||
|
||||
#define CONVERTER_TYPE SRC_SINC_MEDIUM_QUALITY
|
||||
|
||||
extern int8_t resampler_init(SRC_STATE **src_state, uint8_t channels);
|
||||
extern int8_t resample(SRC_STATE *src_state, SRC_DATA src_data, size_t *frames_generated);
|
||||
extern void resampler_exit(SRC_STATE *src_state);
|
||||
BIN
src/station_logo.jpg
Normal file
BIN
src/station_logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
27
src/supported_cmds.txt
Normal file
27
src/supported_cmds.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
AF - Mode A only
|
||||
AFCH - Mode A only
|
||||
DI
|
||||
MS
|
||||
PI
|
||||
PS
|
||||
TPS
|
||||
PTY
|
||||
PTYN
|
||||
PTYNEN
|
||||
TEXT | RT1
|
||||
RT1EN
|
||||
TA
|
||||
TP
|
||||
CT
|
||||
LEVEL - Not redecommended, if possible use `MPX`
|
||||
RDSGEN
|
||||
ECC
|
||||
ECCEN
|
||||
G
|
||||
LIC
|
||||
PIN
|
||||
PINEN
|
||||
RTP
|
||||
RTPRUN
|
||||
|
||||
(Magic RDS 3 'Program' page is fully supported.)
|
||||
26
src/todo.txt
Normal file
26
src/todo.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
Current TODO for RDS encoding:
|
||||
- UECP Decoding
|
||||
- Dynamic PS (may not be implemented)
|
||||
- Bidirectionality
|
||||
|
||||
ASCII commands to implement:
|
||||
- Group sequence
|
||||
- Short RT
|
||||
|
||||
ASCII Group seqeunce:
|
||||
0 - Four 0As (PS x 4)
|
||||
1 - 1A (ECC/LIC, PIN)
|
||||
2 - 2A (RT)
|
||||
A - 10A (PTYN)
|
||||
E - 14A 14B (EON, yet to implement)
|
||||
X - UDG1 (yet to implement, max 8 groups per udg)
|
||||
Y - UDG2 (yet to implement)
|
||||
R - RT+ 3A 11A (do i switch between them like ecc lic?)
|
||||
F - 15A (LPS)
|
||||
Max 24 items in a sequence.
|
||||
|
||||
Note that most of this is takes out of pira.cz's P164 RDS Encoder's manual: https://pira.cz/rds/p164man.pdf
|
||||
|
||||
I plan for this's commands to be similiar to PIRA32's or P164's (both are made by pira.cz)
|
||||
|
||||
In theory, now you can use the MiniRDS as a PIRA32 type device in Magic RDS 4 (but not everything is supported)
|
||||
21
src/uecp_rds_todo.txt
Normal file
21
src/uecp_rds_todo.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
This aims to have the around the same options as UECP should have, this is what is not yet implemented:
|
||||
- PIN (low priority, how do i decode that?)
|
||||
- EON AF (very low priority)
|
||||
- Linkage information (very low priority)
|
||||
- (ODA skipped)
|
||||
- TDC (low priority)
|
||||
- EWS (how do i even implement that? can someone give docs?)
|
||||
- IH (low priority)
|
||||
- (Paging skipped)
|
||||
- (No CT time adjustments)
|
||||
- RDS on/off (low priority)
|
||||
- RDS phase (might be medium, 9.5 degree steps)
|
||||
- (ARI skipped)
|
||||
|
||||
Others:
|
||||
- Encoder identification (site and encoder address)
|
||||
- (PSN skipped)
|
||||
- (EON skipped)
|
||||
- Data set select
|
||||
- Group sequence
|
||||
- (other sequence related comamnds skipped)
|
||||
7
src/waveforms.c
Normal file
7
src/waveforms.c
Normal file
File diff suppressed because one or more lines are too long
7
src/waveforms.h
Normal file
7
src/waveforms.h
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
/* This file was automatically generated by "generate_waveforms.py".
|
||||
(C) 2014 Christophe Jacquet.
|
||||
Released under the GNU GPL v3 license.
|
||||
*/
|
||||
|
||||
extern float waveform_biphase[1120];
|
||||
Reference in New Issue
Block a user