mirror of
https://github.com/radio95-rnt/fm95.git
synced 2026-02-27 03:23:54 +01:00
part 2?
This commit is contained in:
@@ -18,12 +18,17 @@ file(GLOB MODULATION_FILES "modulation/*.c")
|
||||
file(GLOB DSP_FILES "dsp/*.c")
|
||||
file(GLOB IO_FILES "io/*.c")
|
||||
|
||||
file(GLOB INIH_FILES "inih/*.c")
|
||||
|
||||
add_library(inih OBJECT ${INIH_FILES})
|
||||
|
||||
# add_library(libfm OBJECT ${LIB_FILES})
|
||||
add_library(libfmfilter OBJECT ${FILTER_FILES})
|
||||
add_library(libfmmodulation OBJECT ${MODULATION_FILES})
|
||||
add_library(libfmdsp OBJECT ${DSP_FILES})
|
||||
add_library(libfmio OBJECT ${IO_FILES})
|
||||
|
||||
|
||||
# Define DEBUG macro for Debug builds on libraries
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
# target_compile_definitions(libfm PRIVATE DEBUG=1)
|
||||
@@ -33,7 +38,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_definitions(libfmio PRIVATE DEBUG=1)
|
||||
endif()
|
||||
|
||||
set(FM_LIBS libfmfilter libfmmodulation libfmio libfmdsp pulse pulse-simple m liquid)
|
||||
set(FM_LIBS inih libfmfilter libfmmodulation libfmio libfmdsp pulse pulse-simple m liquid)
|
||||
|
||||
foreach(SRC_FILE ${SRC_FILES})
|
||||
get_filename_component(EXEC_NAME ${SRC_FILE} NAME_WE)
|
||||
|
||||
@@ -22,6 +22,8 @@ and one output:
|
||||
|
||||
## How to compile?
|
||||
|
||||
Note that you're required also to load submodules, if you don't know what that means then ask ChatGPT
|
||||
|
||||
To compile you need `cmake`, `liquid-dsp` and `libpulse-dev`, if you have those then do these commands:
|
||||
|
||||
```bash
|
||||
|
||||
131
fm95.md
Normal file
131
fm95.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# FM95 Configuration File
|
||||
|
||||
This document describes the configuration file format for the FM95 FM transmitter software.
|
||||
|
||||
## File Format
|
||||
|
||||
The configuration file uses standard INI format with sections and key-value pairs. Comments can be added using the `#` or `;` character at the beginning of a line.
|
||||
|
||||
## Configuration Sections
|
||||
|
||||
### [fm95] - Main Settings
|
||||
|
||||
This section contains the core FM95 transmission parameters.
|
||||
|
||||
#### Audio Processing
|
||||
|
||||
- **`stereo`** (0 or 1, default: 0)
|
||||
Enable stereo transmission. Set to 1 for stereo, 0 for mono.
|
||||
|
||||
- **`audio_volume`** (float)
|
||||
Audio input volume multiplier. 1.0 = unity gain, values > 1.0 may cause distortion.
|
||||
|
||||
- **`master_volume`** (float)
|
||||
Master output volume control. Affects overall transmission level.
|
||||
|
||||
- **`clipper_threshold`** (float)
|
||||
Audio clipper threshold to prevent overmodulation. Lower values = more aggressive clipping.
|
||||
|
||||
- **`preemphasis`** (float, microseconds)
|
||||
Pre-emphasis time constant in microseconds. Common values: 50 (Europe), 75 (North America).
|
||||
|
||||
#### Stereo Processing
|
||||
|
||||
- **`polar_stereo`** (0 or 1, default: 0)
|
||||
Enable polar stereo encoding.
|
||||
|
||||
#### RDS (Radio Data System)
|
||||
|
||||
- **`rds_streams`** (integer, range: 1-4)
|
||||
Number of RDS data streams to process simultaneously. More streams allow richer RDS data but require more processing power.
|
||||
|
||||
#### MPX (Multiplex) Settings
|
||||
|
||||
- **`mpx_power`** (float)
|
||||
MPX signal power level. For BS412.
|
||||
|
||||
- **`mpx_deviation`** (float, Hz)
|
||||
Maximum frequency deviation for MPX signals. Standard value is 75000 Hz for FM broadcast.
|
||||
|
||||
#### Calibration
|
||||
|
||||
- **`calibration`** (0-2, default: 0)
|
||||
Enable calibration mode for testing and adjustment. (2 enables 40 hz square wave instead of the 400 hz sine)
|
||||
|
||||
#### Frequency Deviation
|
||||
|
||||
- **`deviation`** (float, Hz)
|
||||
Maximum frequency deviation. Standard FM broadcast uses 75000 Hz. This setting affects the master_volume automatically.
|
||||
|
||||
### [devices] - Audio Device Configuration
|
||||
|
||||
This section specifies the audio devices to use for input and output.
|
||||
|
||||
- **`input`** (string, max 63 chars)
|
||||
Primary audio input device
|
||||
|
||||
- **`output`** (string, max 63 chars)
|
||||
MPX input
|
||||
|
||||
- **`mpx`** (string, max 63 chars)
|
||||
MPX (multiplex) input device for subcarrier input
|
||||
|
||||
- **`rds`** (string, max 63 chars)
|
||||
RDS signal input
|
||||
|
||||
## Example Configuration
|
||||
|
||||
```ini
|
||||
# FM95 Configuration File
|
||||
# Lines starting with # are comments
|
||||
|
||||
[fm95]
|
||||
# Enable stereo transmission
|
||||
stereo=1
|
||||
|
||||
# Audio levels (1.0 = unity gain)
|
||||
audio_volume=0.8
|
||||
master_volume=1.0
|
||||
|
||||
# Prevent overmodulation
|
||||
clipper_threshold=0.9
|
||||
|
||||
# Pre-emphasis for Europe (50µs) or North America (75µs)
|
||||
preemphasis=50
|
||||
|
||||
# Enhanced stereo processing
|
||||
polar_stereo=1
|
||||
|
||||
# RDS configuration
|
||||
rds_streams=2
|
||||
|
||||
# MPX settings
|
||||
mpx_power=0.1
|
||||
mpx_deviation=75000
|
||||
|
||||
# Standard FM deviation
|
||||
deviation=75000
|
||||
|
||||
# Enable calibration mode for testing
|
||||
calibration=0
|
||||
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **RDS Streams**: Maximum of 4 RDS streams are supported. Exceeding this limit will cause an error.
|
||||
|
||||
2. **String Length**: Device names are limited to 63 characters maximum.
|
||||
|
||||
3. **Deviation Interaction**: The `deviation` setting automatically adjusts `master_volume` using the formula: `master_volume *= (deviation / 75000.0)`. Set deviation last if you want to override master_volume.
|
||||
|
||||
4. **Audio Levels**: Keep audio levels reasonable to prevent distortion. Start with conservative values and adjust as needed.
|
||||
|
||||
5. **Preemphasis**: Use 50µs for European broadcast standards, 75µs for North American standards.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **No audio**: Check device names with system audio tools
|
||||
- **Distorted audio**: Reduce audio_volume and clipper_threshold
|
||||
- **Poor stereo separation**: Enable polar_stereo and adjust mpx_power
|
||||
- **RDS not working**: Verify rds device and ensure rds_streams ≤ 4
|
||||
189
src/fm95.c
189
src/fm95.c
@@ -1,6 +1,9 @@
|
||||
#include <getopt.h>
|
||||
#include <liquid/liquid.h>
|
||||
#include <stdbool.h>
|
||||
#include "../inih/ini.h"
|
||||
|
||||
#define DEFAULT_INI_PATH "/etc/fm95.conf"
|
||||
|
||||
#define LPF_ORDER 17
|
||||
|
||||
@@ -64,6 +67,9 @@ typedef struct
|
||||
float audio_volume;
|
||||
|
||||
uint32_t sample_rate;
|
||||
|
||||
// ini dont edit
|
||||
char ini_config_path[64];
|
||||
} FM95_Config;
|
||||
|
||||
typedef struct
|
||||
@@ -78,6 +84,10 @@ typedef struct {
|
||||
char mpx[64];
|
||||
char rds[64];
|
||||
} FM95_DeviceNames;
|
||||
typedef struct {
|
||||
FM95_Config* config;
|
||||
FM95_DeviceNames* devices;
|
||||
} FM95_SetupContext;
|
||||
|
||||
static void stop(int signum) {
|
||||
(void)signum;
|
||||
@@ -88,36 +98,9 @@ static void stop(int signum) {
|
||||
void show_help(char *name) {
|
||||
printf(
|
||||
"Usage: \t%s\n"
|
||||
"\t-s,--stereo\tForce Stereo [default: %d]\n"
|
||||
"\t-i,--input\tOverride input device [default: %s]\n"
|
||||
"\t-o,--output\tOverride output device [default: %s]\n"
|
||||
"\t-M,--mpx\tOverride MPX input device [default: %s]\n"
|
||||
"\t-r,--rds\tOverride RDS95 input device [default: %s]\n"
|
||||
"\t-R,--rds_strs\tSpecifies the number of the RDS streams provided by RDS95 [default: %d]\n"
|
||||
"\t-c,--clipper\tOverride the clipper threshold [default: %.2f]\n"
|
||||
"\t-O,--polar\tForce Polar Stereo (does not take effect with -s0%s)\n"
|
||||
"\t-e,--preemp\tOverride preemphasis [default: %.2f µs]\n"
|
||||
"\t-V,--calibrate\tEnable Calibration mode [default: off, option 2 enables a 60 hz square wave instead of the 400 hz sine wave]\n"
|
||||
"\t-p,--power\tSet the MPX power [default: %.1f]\n"
|
||||
"\t-P,--mpx_dev\tSet the MPX deviation [default: %.1f]\n"
|
||||
"\t-A,--master_vol\tSet master volume [default: %.3f]\n"
|
||||
"\t-v,--audio_vol\tSet audio volume [default: %.3f]\n"
|
||||
"\t-D,--deviation\tSet audio volume, but with the deviation (100%% being 75000) [default: %.1f]\n"
|
||||
,name
|
||||
,DEFAULT_STEREO
|
||||
,INPUT_DEVICE
|
||||
,OUTPUT_DEVICE
|
||||
,MPX_DEVICE
|
||||
,RDS_DEVICE
|
||||
,DEFAULT_RDS_STREAMS
|
||||
,DEFAULT_CLIPPER_THRESHOLD
|
||||
,(DEFAULT_STEREO_POLAR == 1) ? ", default" : ""
|
||||
,DEFAULT_PREEMPHASIS_TAU/0.000001
|
||||
,DEFAULT_MPX_POWER
|
||||
,DEFAULT_MPX_DEVIATION
|
||||
,DEFAULT_MASTER_VOLUME
|
||||
,DEFAULT_AUDIO_VOLUME
|
||||
,DEFAULT_DEVIATION
|
||||
"\t-c,--config\tOverride the default config path (%s)\n",
|
||||
name,
|
||||
DEFAULT_INI_PATH
|
||||
);
|
||||
}
|
||||
|
||||
@@ -268,84 +251,20 @@ int run_fm95(const FM95_Config config, FM95_Runtime* runtime) {
|
||||
}
|
||||
|
||||
|
||||
int parse_arguments(int argc, char **argv, FM95_Config* config, FM95_DeviceNames* dv) {
|
||||
int parse_arguments(int argc, char **argv, FM95_Config* config) {
|
||||
int opt;
|
||||
const char *short_opt = "s::i:o:M:r:R:c:O::e:V::p:P:A:v:D:h";
|
||||
const char *short_opt = "c:h";
|
||||
struct option long_opt[] =
|
||||
{
|
||||
{"stereo", optional_argument, NULL, 's'},
|
||||
{"input", required_argument, NULL, 'i'},
|
||||
{"output", required_argument, NULL, 'o'},
|
||||
{"mpx", required_argument, NULL, 'M'},
|
||||
{"rds", required_argument, NULL, 'r'},
|
||||
{"rds_strs", required_argument, NULL, 'R'},
|
||||
{"clipper", required_argument, NULL, 'c'},
|
||||
{"polar", optional_argument, NULL, 'O'},
|
||||
{"preemp", required_argument, NULL, 'e'},
|
||||
{"calibrate", optional_argument, NULL, 'V'},
|
||||
{"power", required_argument, NULL, 'p'},
|
||||
{"mpx_dev", required_argument, NULL, 'P'},
|
||||
{"master_vol", required_argument, NULL, 'A'},
|
||||
{"audio_vol", required_argument, NULL, 'v'},
|
||||
{"deviation", required_argument, NULL, 'D'},
|
||||
|
||||
{"config", required_argument, NULL, 'c'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
while((opt = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) {
|
||||
switch(opt) {
|
||||
case 's': // Stereo
|
||||
if(optarg) config->stereo = atoi(optarg);
|
||||
else config->stereo = 1;
|
||||
break;
|
||||
case 'i': // Input Device
|
||||
memcpy(dv->input, optarg, 63);
|
||||
break;
|
||||
case 'o': // Output Device
|
||||
memcpy(dv->output, optarg, 63);
|
||||
break;;
|
||||
case 'M': //MPX in
|
||||
memcpy(dv->mpx, optarg, 63);
|
||||
break;
|
||||
case 'r': // RDS in
|
||||
memcpy(dv->rds, optarg, 63);
|
||||
break;
|
||||
case 'R': // RDS Streams
|
||||
config->rds_streams = atoi(optarg);
|
||||
if(config->rds_streams > 4) {
|
||||
printf("RDS Streams more than 4? Nuh uh\n");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'c': //Clipper
|
||||
config->clipper_threshold = strtof(optarg, NULL);
|
||||
break;
|
||||
case 'O': //Polar
|
||||
if(optarg) config->polar_stereo = atoi(optarg);
|
||||
else config->polar_stereo = 1;
|
||||
break;
|
||||
case 'e': // Preemp
|
||||
config->preemphasis = strtof(optarg, NULL)*1.0e-6f;
|
||||
break;
|
||||
case 'V': // Calibration
|
||||
if(optarg) config->calibration = atoi(optarg);
|
||||
else config->calibration = 1;
|
||||
break;
|
||||
case 'p': // Power
|
||||
config->mpx_power = strtof(optarg, NULL);
|
||||
break;
|
||||
case 'P': // MPX deviation
|
||||
config->mpx_deviation = strtof(optarg, NULL);
|
||||
break;
|
||||
case 'A': // Master vol
|
||||
config->master_volume = strtof(optarg, NULL);
|
||||
break;
|
||||
case 'v': // Audio Volume
|
||||
config->audio_volume = strtof(optarg, NULL);
|
||||
break;
|
||||
case 'D': // Deviation
|
||||
config->master_volume *= (strtof(optarg, NULL)/75000.0f);
|
||||
case 'c':
|
||||
memcpy(config->ini_config_path, optarg, 63);
|
||||
break;
|
||||
case 'h':
|
||||
show_help(argv[0]);
|
||||
@@ -356,6 +275,66 @@ int parse_arguments(int argc, char **argv, FM95_Config* config, FM95_DeviceNames
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_handler(void* user, const char* section, const char* name, const char* value) {
|
||||
FM95_SetupContext* ctx = (FM95_SetupContext*)user;
|
||||
FM95_Config* pconfig = ctx->config;
|
||||
FM95_DeviceNames* dv = ctx->devices;
|
||||
|
||||
#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
|
||||
|
||||
if (MATCH("fm95", "stereo")) {
|
||||
pconfig->stereo = atoi(value);
|
||||
} else if (MATCH("devices", "input")) {
|
||||
strncpy(dv->input, value, 63);
|
||||
dv->input[63] = '\0';
|
||||
} else if (MATCH("devices", "output")) {
|
||||
strncpy(dv->output, value, 63);
|
||||
dv->output[63] = '\0';
|
||||
} else if (MATCH("devices", "mpx")) {
|
||||
strncpy(dv->mpx, value, 63);
|
||||
dv->mpx[63] = '\0';
|
||||
} else if (MATCH("devices", "rds")) {
|
||||
strncpy(dv->rds, value, 63);
|
||||
dv->rds[63] = '\0';
|
||||
} else if (MATCH("fm95", "rds_streams")) {
|
||||
pconfig->rds_streams = atoi(value);
|
||||
if(pconfig->rds_streams > 4) {
|
||||
printf("RDS Streams more than 4? Nuh uh\n");
|
||||
return 0;
|
||||
}
|
||||
} else if (MATCH("fm95", "clipper_threshold")) {
|
||||
pconfig->clipper_threshold = strtof(value, NULL);
|
||||
} else if (MATCH("fm95", "polar_stereo")) {
|
||||
pconfig->polar_stereo = atoi(value);
|
||||
} else if (MATCH("fm95", "preemphasis")) {
|
||||
pconfig->preemphasis = strtof(value, NULL) * 1.0e-6f;
|
||||
} else if (MATCH("fm95", "calibration")) {
|
||||
pconfig->calibration = atoi(value);
|
||||
} else if (MATCH("fm95", "mpx_power")) {
|
||||
pconfig->mpx_power = strtof(value, NULL);
|
||||
} else if (MATCH("fm95", "mpx_deviation")) {
|
||||
pconfig->mpx_deviation = strtof(value, NULL);
|
||||
} else if (MATCH("fm95", "master_volume")) {
|
||||
pconfig->master_volume = strtof(value, NULL);
|
||||
} else if (MATCH("fm95", "audio_volume")) {
|
||||
pconfig->audio_volume = strtof(value, NULL);
|
||||
} else if (MATCH("fm95", "deviation")) {
|
||||
pconfig->master_volume *= (strtof(value, NULL) / 75000.0f);
|
||||
} else {
|
||||
return 0; // Unknown section/name
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int parse_config(FM95_Config* config, FM95_DeviceNames* dv) {
|
||||
FM95_SetupContext ctx = {
|
||||
.config = config,
|
||||
.devices = dv
|
||||
};
|
||||
return ini_parse(config->ini_config_path, &config_handler, &ctx);
|
||||
}
|
||||
|
||||
int setup_audio(FM95_Runtime* runtime, const FM95_DeviceNames dv_names, const FM95_Config config, bool mpx_on, bool rds_on) {
|
||||
pa_buffer_attr input_buffer_atr = {
|
||||
.maxlength = buffer_maxlength,
|
||||
@@ -428,7 +407,9 @@ int main(int argc, char **argv) {
|
||||
.master_volume = DEFAULT_MASTER_VOLUME,
|
||||
.audio_volume = DEFAULT_AUDIO_VOLUME,
|
||||
|
||||
.sample_rate = DEFAULT_SAMPLE_RATE
|
||||
.sample_rate = DEFAULT_SAMPLE_RATE,
|
||||
|
||||
.ini_config_path = DEFAULT_INI_PATH
|
||||
};
|
||||
|
||||
FM95_DeviceNames dv_names = {
|
||||
@@ -439,9 +420,15 @@ int main(int argc, char **argv) {
|
||||
};
|
||||
|
||||
int err;
|
||||
err = parse_arguments(argc, argv, &config, &dv_names);
|
||||
err = parse_arguments(argc, argv, &config);
|
||||
if(err != 0) return err;
|
||||
|
||||
err = parse_config(&config, &dv_names);
|
||||
if(err != 0) {
|
||||
printf("Could not parse the config file. (error code as return code)\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
FM95_Runtime runtime;
|
||||
memset(&runtime, 0, sizeof(runtime));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user