This commit is contained in:
2025-07-05 18:46:34 +01:00
parent c6602d3e5b
commit 56b55f1d6d
5 changed files with 9184 additions and 0 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
*/build/ */build/
*/.cache/

50
fm/.clang-format Normal file
View File

@@ -0,0 +1,50 @@
---
BasedOnStyle: Microsoft
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: 'true'
AlignConsecutiveAssignments: 'false'
AlignEscapedNewlines: Left
AlignOperands: 'true'
AlignTrailingComments: 'true'
AllowAllArgumentsOnNextLine: 'true'
AllowShortBlocksOnASingleLine: 'false'
AllowShortCaseLabelsOnASingleLine: 'true'
AllowShortIfStatementsOnASingleLine: Never
BinPackArguments: 'false'
BinPackParameters: 'false'
IncludeBlocks: Regroup
BraceWrapping:
AfterClass: true
AfterControlStatement: true
AfterEnum: true # <-- You need this
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: false # <-- And this
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBraces: Custom # <-- And this
BreakBeforeBinaryOperators: All
ColumnLimit: 0
IndentCaseLabels: 'false'
Language: Cpp
MaxEmptyLinesToKeep: '1'
PointerAlignment: Left
ReflowComments: 'true'
SortIncludes: 'true'
SortUsingDeclarations: 'true'
SpaceAfterLogicalNot: 'false'
SpaceBeforeAssignmentOperators: 'true'
SpaceBeforeCpp11BracedList: 'true'
SpaceBeforeInheritanceColon: 'true'
TabWidth: '4'
UseTab: Never
AccessModifierOffset: '-4'
...

21
fm/CMakeLists.txt Normal file
View File

@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.10)
project(fm_demodulate)
file(GLOB SRC_FILES src/*.cpp)
add_executable(fm_receiver ${SRC_FILES})
target_include_directories(fm_receiver PRIVATE src)
find_package(ALSA REQUIRED)
target_link_libraries(fm_receiver PRIVATE ALSA::ALSA)
find_library(RTLSDR_LIB rtlsdr)
find_path(RTLSDR_INCLUDE rtl-sdr.h)
if(NOT RTLSDR_LIB OR NOT RTLSDR_INCLUDE)
message(FATAL_ERROR "librtlsdr not found")
endif()
target_include_directories(fm_receiver PRIVATE ${RTLSDR_INCLUDE})
target_link_libraries(fm_receiver PRIVATE ${RTLSDR_LIB})

9003
fm/src/dr_wav.h Normal file

File diff suppressed because it is too large Load Diff

109
fm/src/main.cpp Normal file
View File

@@ -0,0 +1,109 @@
#include <algorithm>
#include <alsa/asoundlib.h>
#include <cmath>
#include <complex>
#include <cstring>
#include <iostream>
#include <rtl-sdr.h>
#include <signal.h>
#include <vector>
#define DR_WAV_IMPLEMENTATION
#include "dr_wav.h"
#define DEFAULT_SAMPLE_RATE 2400000 // 2.4 MS/s
#define AUDIO_SAMPLE_RATE 48000
#define RTL_FM_DEMOD_BUF_LEN 16384 // 16kB
#include <stdio.h>
#include <stdlib.h>
volatile bool stop_flag = 0;
void handle_sigint(int sig)
{
(void)sig;
stop_flag = 1;
}
int main()
{
signal(SIGINT, handle_sigint);
rtlsdr_dev_t* dev = NULL;
rtlsdr_open(&dev, 0);
rtlsdr_set_center_freq(dev, 96700000);
rtlsdr_set_sample_rate(dev, DEFAULT_SAMPLE_RATE);
rtlsdr_set_tuner_gain_mode(dev, 1);
rtlsdr_set_tuner_gain(dev, 400); // 40dB
rtlsdr_reset_buffer(dev);
snd_pcm_t* pcm_handle;
snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
snd_pcm_set_params(pcm_handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 1, AUDIO_SAMPLE_RATE, 1, 500000);
drwav wav;
drwav_data_format format = {
.container = drwav_container_riff,
.format = DR_WAVE_FORMAT_PCM,
.channels = 1,
.sampleRate = 48000,
.bitsPerSample = 16};
// Open file for writing
drwav_init_file_write(&wav, "output.wav", &format, NULL);
uint8_t rtl_buf[RTL_FM_DEMOD_BUF_LEN];
float iq_buf[RTL_FM_DEMOD_BUF_LEN / 2];
float demod_buf[RTL_FM_DEMOD_BUF_LEN / 2];
int16_t audio_buf[8192];
while (!stop_flag)
{
int n_read;
rtlsdr_read_sync(dev, rtl_buf, RTL_FM_DEMOD_BUF_LEN, &n_read);
int n_samples = n_read / 2;
// i/q around 0
for (int i = 0; i < n_samples; i++)
{
int8_t i_sample = rtl_buf[2 * i] - 127;
int8_t q_sample = rtl_buf[2 * i + 1] - 127;
iq_buf[i] = atan2(q_sample, i_sample);
}
// differential phase
static float prev_phase = 0;
for (int i = 0; i < n_samples; i++)
{
float dphi = iq_buf[i] - prev_phase;
// keep dphi within -pi and pi (as phase is circular)
if (dphi > M_PI)
dphi -= 2 * M_PI;
if (dphi < -M_PI)
dphi += 2 * M_PI;
prev_phase = iq_buf[i];
// low pass
static float y = 0;
float x = dphi;
y = y + (x - y) * 0.294f; // alpha 50us 48kHz
iq_buf[i] = y;
}
// then decimate
int out_index = 0;
for (int i = 0; i < n_samples; i += 50)
{
if (out_index < 8192)
{
audio_buf[out_index++] = (int16_t)(iq_buf[i] * 32767.0f);
}
}
snd_pcm_writei(pcm_handle, audio_buf, out_index);
drwav_write_pcm_frames(&wav, out_index, audio_buf);
}
drwav_uninit(&wav);
rtlsdr_close(dev);
}