/* * RTLSDR AM/NFM demodulator, mixer, streamer and recorder * * Copyright (c) 2014 Wong Man Hang * Copyright (c) 2015-2021 Tomasz Lemiech * * 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 . */ #include "config.h" #if defined WITH_BCM_VC && !defined __arm__ #error Broadcom VideoCore support can only be enabled on ARM builds #endif // From this point we may safely assume that WITH_BCM_VC implies __arm__ #ifdef WITH_BCM_VC #include "hello_fft/gpu_fft.h" #include "hello_fft/mailbox.h" #endif /* WITH_BCM_VC */ //#define GPIOX #include #include #include #include #include #include // uint8_t #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "input-common.h" #include "logging.h" #include "rtl_airband.h" #include "squelch.h" #include #ifdef WITH_PROFILING #include "gperftools/profiler.h" #endif /* WITH_PROFILING */ #include /*#include */ #include #include #include #include #include "mqtt/async_client.h" #include #include #include #include #include using namespace std; using namespace libconfig; struct gpiod_chip *chip; struct gpiod_line *led_squ; struct gpiod_line *led_vox; struct gpiod_line *led_control; struct gpiod_line *relay1; struct gpiod_line *relay2; #define MAX_BUFFERS 10 #define GPIOX using json = nlohmann::json; sem_t mutex_lock_main_thread, mutex_unlock_main_thread; sem_t mutex_lock_main_thread_mqtt, mutex_unlock_main_thread_mqtt; pthread_mutex_t lock_data; pthread_mutex_t lock_mqtt_data; device_t* devices; mixer_t* mixers; long int start_time; long int stop_time; int bel_start_on_time = 0; int bel_open_time = 0; int bel_prolong_time = 0; float bel_wave_out_level = 0; float bel_wave_in_level = 0; int bel_blink_time = 0; bool mqtt_activate=false; char* bel_mqtt_server; char* bel_mqtt_client_id; char* bel_mqtt_user_id; char* bel_mqtt_password; int squelch_status = 0; int audio_status = 0; int status_data = 0; int status_data_old = 0; int device_count, mixer_count; static int devices_running = 0; int tui = 0; // do not display textual user interface int shout_metadata_delay = 3; volatile int do_exit = 0; bool use_localtime = false; bool multiple_demod_threads = false; bool multiple_output_threads = false; bool log_scan_activity = false; char* stats_filepath = NULL; size_t fft_size_log = DEFAULT_FFT_SIZE_LOG; size_t fft_size = 1 << fft_size_log; #ifdef NFM float alpha = exp(-1.0f / (WAVE_RATE * 2e-4)); enum fm_demod_algo { FM_FAST_ATAN2, FM_QUADRI_DEMOD }; enum fm_demod_algo fm_demod = FM_FAST_ATAN2; #endif /* NFM */ #ifdef DEBUG char* debug_path; #endif /* DEBUG */ void sighandler(int sig) { log(LOG_NOTICE, "Got signal %d, exiting\n", sig); do_exit = 1; } template class TSQueue { private: // Underlying queue std::queue m_queue; // mutex for thread synchronization std::mutex m_mutex; // Condition variable for signaling std::condition_variable m_cond; public: // Pushes an element to the queue void push(T item) { // Acquire lock std::unique_lock lock(m_mutex); // Add item m_queue.push(item); // Notify one thread that // is waiting m_cond.notify_one(); } // Pops an element off the queue T pop() { // acquire lock std::unique_lock lock(m_mutex); // wait until queue is not empty m_cond.wait(lock, [this]() { return !m_queue.empty(); }); // retrieve item T item = m_queue.front(); m_queue.pop(); // return item return item; } int size() { return m_queue.size(); } }; TSQueue mqtt_queue; void* controller_thread(void* params) { device_t* dev = (device_t*)params; int i = 0; int consecutive_squelch_off = 0; int new_centerfreq = 0; struct timeval tv; if (dev->channels[0].freq_count < 2) return 0; while (!do_exit) { SLEEP(200); if (dev->channels[0].axcindicate == NO_SIGNAL) { if (consecutive_squelch_off < 10) { consecutive_squelch_off++; } else { i++; i %= dev->channels[0].freq_count; dev->channels[0].freq_idx = i; new_centerfreq = dev->channels[0].freqlist[i].frequency + 20 * (double)(dev->input->sample_rate / fft_size); if (input_set_centerfreq(dev->input, new_centerfreq) < 0) { break; } } } else { if (consecutive_squelch_off == 10) { if (log_scan_activity) log(LOG_INFO, "Activity on %7.3f MHz\n", dev->channels[0].freqlist[i].frequency / 1000000.0); if (i != dev->last_frequency) { // squelch has just opened on a new frequency - we might need to update outputs' metadata gettimeofday(&tv, NULL); tag_queue_put(dev, i, tv); dev->last_frequency = i; } } consecutive_squelch_off = 0; } } return 0; } void multiply(float ar, float aj, float br, float bj, float* cr, float* cj) { *cr = ar * br - aj * bj; *cj = aj * br + ar * bj; } class PublisherCallback : public virtual mqtt::callback { public: void connection_lost(const std::string& cause) override { std::cout << "Connection lost: " << cause << std::endl; } void delivery_complete(mqtt::delivery_token_ptr token) override { token=token; std::cout << "Message delivered" << std::endl; } void message_arrived (mqtt::const_message_ptr msg) override { std::cout << "Message arrived: " << msg->get_payload_str() << std::endl; mqtt_activate=true; } }; void* mqtt_control_thread(void*){ string address = bel_mqtt_server, clientID = bel_mqtt_client_id, username = bel_mqtt_user_id, password = bel_mqtt_password; const std::string TOPIC1("device/"+username+"/sq"); const std::string TOPIC2("device/"+username+"/light"); const int QOS = 1; //const int TIMEOUT = 10000; mqtt::async_client client(address, clientID,mqtt::create_options(MQTTVERSION_5)); mqtt::connect_options connOpts; mqtt::ssl_options ssl_options; connOpts.set_keep_alive_interval(20); connOpts.set_clean_session(true); connOpts.set_user_name(username); connOpts.set_password(password); connOpts.set_ssl(ssl_options); connOpts.set_automatic_reconnect(10, 40); try { PublisherCallback callback; client.set_callback(callback); mqtt::token_ptr connectionToken = client.connect(connOpts); connectionToken->wait(); mqtt::token_ptr subToken = client.subscribe(TOPIC2, QOS); subToken->wait(); while (true) { mqtt_msg_t temp_mess; //printf("Size %i\n",mqtt_queue.size()); temp_mess=mqtt_queue.pop(); Json::Value event; event["type"]=temp_mess.type; if (temp_mess.type==1){ event["sq"]=temp_mess.sq; event["freq"]=temp_mess.freq; event["name"]=temp_mess.name; event["mountpoint"]=temp_mess.mountpoint; } if (temp_mess.type==2){ event["bel_status"]=temp_mess.bel_status; } if (temp_mess.type==2){ event["type"]=temp_mess.type; event["status"]="connect"; } Json::StreamWriterBuilder builder; if (client.is_connected()){ mqtt::message_ptr pubMessage = mqtt::make_message(TOPIC1,Json::writeString(builder, event), QOS, false); client.publish(pubMessage)->wait(); } } mqtt::token_ptr disconnectionToken = client.disconnect(); disconnectionToken->wait(); } catch (const mqtt::exception& ex) { std::cerr << "MQTT Exception: " << ex.what() << std::endl; return 0; } printf("HEJ3"); return 0; } void* belysning_control_thread(void*){ struct timespec ts; struct timeval tv; long int sq_start_time=0; long int sq_stop_time=0; bool sq_status = false; long int audio_start_time=0; bool active=false; char *str ; memset(&str, 0, sizeof(str)); mqtt_msg_t mqtt_msg; mqtt_msg.sq = 0; mqtt_msg.freq = 0; mqtt_msg.name = str; mqtt_msg.audio = 0; mqtt_msg.mountpoint = str; mqtt_msg.bel_status = -1; mqtt_msg.type=2; int status_data_temp=0; gettimeofday(&tv, 0); start_time=tv.tv_sec; stop_time=tv.tv_sec+1; while (!do_exit) { status_data=status_data; int sem_try=0; bool activate=false; bool prolong=false; if (clock_gettime(CLOCK_REALTIME, &ts) == -1 && tui) printf("clock_gettime"); ts.tv_sec +=1; sem_try = sem_timedwait(&mutex_lock_main_thread, &ts); if (sem_try == 0){ status_data_temp=status_data; sem_post(&mutex_unlock_main_thread); if (sq_status == false && (status_data_temp & 1)){ sq_start_time = tv.tv_sec; sq_status = true; } if (sq_status == true && !(status_data_temp & 1)){ sq_stop_time = tv.tv_sec; sq_status = false; } //printf("ST %u, %u ",(status_data & 1),(status_data >> 1)); if ( (status_data_temp >> 1) ){ audio_start_time = tv.tv_sec; } prolong = true; } if (sq_status && sq_start_time < tv.tv_sec){ prolong = true; }else{ prolong = false; } //printf("DATA %u, %ld, %ld, %ld %u %ld\n",sq_status,sq_start_time,audio_start_time,tv.tv_sec,prolong,stop_time); //Check if we should start if (sq_status == true && sq_start_time+bel_open_time < tv.tv_sec && audio_start_time+bel_open_time < tv.tv_sec){ sq_start_time=sq_start_time; sq_stop_time=sq_stop_time; activate=true; } gettimeofday(&tv, 0); //First time activation if ((active==false && activate==true) || mqtt_activate){ start_time=tv.tv_sec; stop_time=tv.tv_sec+bel_start_on_time; mqtt_activate=false; active=true; mqtt_msg.bel_status = 1; mqtt_queue.push(mqtt_msg); if (tui) printf("Start\n"); } // Prolong if (active==true && prolong==true){ if (tv.tv_sec+bel_prolong_time >= stop_time){ stop_time=tv.tv_sec+bel_prolong_time; if (tui) printf("Prolong %u\n",bel_prolong_time); mqtt_msg.bel_status = 2; mqtt_queue.push(mqtt_msg); prolong = false; } } if (stop_time<=tv.tv_sec && active){ active=false; start_time=start_time; mqtt_msg.bel_status = 0; mqtt_queue.push(mqtt_msg); if (tui) printf("Stop\n"); } #ifdef GPIOX if (active) if ((stop_time= 0.0f) { angle = pi4 - pi4 * (x - yabs) / (x + yabs); } else { angle = pi34 - pi4 * (x + yabs) / (yabs - x); } if (y < 0.0f) { return -angle; } return angle; } float polar_disc_fast(float ar, float aj, float br, float bj) { float cr, cj; multiply(ar, aj, br, -bj, &cr, &cj); return (float)(fast_atan2(cj, cr) * M_1_PI); } float fm_quadri_demod(float ar, float aj, float br, float bj) { return (float)((br * aj - ar * bj) / (ar * ar + aj * aj + 1.0f) * M_1_PI); } #endif /* NFM */ class AFC { const status _prev_axcindicate; #ifdef WITH_BCM_VC float square(const GPU_FFT_COMPLEX* fft_results, size_t index) { return fft_results[index].re * fft_results[index].re + fft_results[index].im * fft_results[index].im; } #else float square(const fftwf_complex* fft_results, size_t index) { return fft_results[index][0] * fft_results[index][0] + fft_results[index][1] * fft_results[index][1]; } #endif /* WITH_BCM_VC */ template size_t check(const FFT_RESULTS* fft_results, const size_t base, const float base_value, unsigned char afc) { float threshold = 0; size_t bin; for (bin = base;; bin += STEP) { if (STEP < 0) { if (bin < -STEP) break; } else if ((size_t)(bin + STEP) >= fft_size) break; const float value = square(fft_results, (size_t)(bin + STEP)); if (value <= base_value) break; if (base == (size_t)bin) { threshold = (value - base_value) / (float)afc; } else { if ((value - base_value) < threshold) break; threshold += threshold / 10.0; } } return bin; } public: AFC(device_t* dev, int index) : _prev_axcindicate(dev->channels[index].axcindicate) {} template void finalize(device_t* dev, int index, const FFT_RESULTS* fft_results) { channel_t* channel = &dev->channels[index]; if (channel->afc == 0) return; const char axcindicate = channel->axcindicate; if (axcindicate != NO_SIGNAL && _prev_axcindicate == NO_SIGNAL) { const size_t base = dev->base_bins[index]; const float base_value = square(fft_results, base); size_t bin = check(fft_results, base, base_value, channel->afc); if (bin == base) bin = check(fft_results, base, base_value, channel->afc); if (dev->bins[index] != bin) { #ifdef AFC_LOGGING log(LOG_INFO, "AFC device=%d channel=%d: base=%zu prev=%zu now=%zu\n", dev->device, index, base, dev->bins[index], bin); #endif /* AFC_LOGGING */ dev->bins[index] = bin; if (bin > base) channel->axcindicate = AFC_UP; else if (bin < base) channel->axcindicate = AFC_DOWN; } } else if (axcindicate == NO_SIGNAL && _prev_axcindicate != NO_SIGNAL) dev->bins[index] = dev->base_bins[index]; } }; void init_demod(demod_params_t* params, Signal* signal, int device_start, int device_end) { assert(params != NULL); assert(signal != NULL); params->mp3_signal = signal; params->device_start = device_start; params->device_end = device_end; #ifndef WITH_BCM_VC params->fftin = fftwf_alloc_complex(fft_size); params->fftout = fftwf_alloc_complex(fft_size); params->fft = fftwf_plan_dft_1d(fft_size, params->fftin, params->fftout, FFTW_FORWARD, FFTW_MEASURE); #endif /* WITH_BCM_VC */ } void init_output(output_params_t* params, int device_start, int device_end, int mixer_start, int mixer_end) { assert(params != NULL); params->mp3_signal = new Signal; params->device_start = device_start; params->device_end = device_end; params->mixer_start = mixer_start; params->mixer_end = mixer_end; } int next_device(demod_params_t* params, int current) { current++; if (current < params->device_end) { return current; } return params->device_start; } void* demodulate(void* params) { assert(params != NULL); demod_params_t* demod_params = (demod_params_t*)params; debug_print("Starting demod thread, devices %d:%d, signal %p\n", demod_params->device_start, demod_params->device_end, demod_params->mp3_signal); // initialize fft engine #ifdef WITH_BCM_VC int mb = mbox_open(); struct GPU_FFT* fft; int ret = gpu_fft_prepare(mb, fft_size_log, GPU_FFT_FWD, FFT_BATCH, &fft); switch (ret) { case -1: log(LOG_CRIT, "Unable to enable V3D. Please check your firmware is up to date.\n"); error(); break; case -2: log(LOG_CRIT, "log2_N=%d not supported. Try between 8 and 17.\n", fft_size_log); error(); break; case -3: log(LOG_CRIT, "Out of memory. Try a smaller batch or increase GPU memory.\n"); error(); break; } #else fftwf_complex* fftin = demod_params->fftin; fftwf_complex* fftout = demod_params->fftout; #endif /* WITH_BCM_VC */ float ALIGNED32 levels_u8[256], levels_s8[256]; float* levels_ptr = NULL; for (int i = 0; i < 256; i++) { levels_u8[i] = (i - 127.5f) / 127.5f; } for (int16_t i = -127; i < 128; i++) { levels_s8[(uint8_t)i] = i / 128.0f; } // initialize fft window // blackman 7 // the whole matrix is computed #ifdef WITH_BCM_VC float ALIGNED32 window[fft_size * 2]; #else float ALIGNED32 window[fft_size]; #endif /* WITH_BCM_VC */ const double a0 = 0.27105140069342f; const double a1 = 0.43329793923448f; const double a2 = 0.21812299954311f; const double a3 = 0.06592544638803f; const double a4 = 0.01081174209837f; const double a5 = 0.00077658482522f; const double a6 = 0.00001388721735f; for (size_t i = 0; i < fft_size; i++) { double x = a0 - (a1 * cos((2.0 * M_PI * i) / (fft_size - 1))) + (a2 * cos((4.0 * M_PI * i) / (fft_size - 1))) - (a3 * cos((6.0 * M_PI * i) / (fft_size - 1))) + (a4 * cos((8.0 * M_PI * i) / (fft_size - 1))) - (a5 * cos((10.0 * M_PI * i) / (fft_size - 1))) + (a6 * cos((12.0 * M_PI * i) / (fft_size - 1))); #ifdef WITH_BCM_VC window[i * 2] = window[i * 2 + 1] = (float)x; #else window[i] = (float)x; #endif /* WITH_BCM_VC */ } #ifdef DEBUG struct timeval ts, te; gettimeofday(&ts, NULL); #endif /* DEBUG */ size_t available; int device_num = demod_params->device_start; while (true) { if (do_exit) { #ifdef WITH_BCM_VC log(LOG_INFO, "Freeing GPU memory\n"); gpu_fft_release(fft); #endif /* WITH_BCM_VC */ return NULL; } device_t* dev = devices + device_num; pthread_mutex_lock(&dev->input->buffer_lock); if (dev->input->bufe >= dev->input->bufs) available = dev->input->bufe - dev->input->bufs; else available = dev->input->buf_size - dev->input->bufs + dev->input->bufe; pthread_mutex_unlock(&dev->input->buffer_lock); if (devices_running == 0) { log(LOG_ERR, "All receivers failed, exiting\n"); do_exit = 1; continue; } if (dev->input->state != INPUT_RUNNING) { if (dev->input->state == INPUT_FAILED) { dev->input->state = INPUT_DISABLED; disable_device_outputs(dev); devices_running--; } device_num = next_device(demod_params, device_num); continue; } // number of input bytes per output wave sample (x 2 for I and Q) size_t bps = 2 * dev->input->bytes_per_sample * (size_t)round((double)dev->input->sample_rate / (double)WAVE_RATE); if (available < bps * FFT_BATCH + fft_size * dev->input->bytes_per_sample * 2) { // move to next device device_num = next_device(demod_params, device_num); SLEEP(10); continue; } if (dev->input->sfmt == SFMT_S16) { float const scale = 1.0f / dev->input->fullscale; #ifdef WITH_BCM_VC struct GPU_FFT_COMPLEX* ptr = fft->in; for (size_t b = 0; b < FFT_BATCH; b++, ptr += fft->step) { short* buf2 = (short*)(dev->input->buffer + dev->input->bufs + b * bps); for (size_t i = 0; i < fft_size; i++, buf2 += 2) { ptr[i].re = scale * (float)buf2[0] * window[i * 2]; ptr[i].im = scale * (float)buf2[1] * window[i * 2]; } } #else short* buf2 = (short*)(dev->input->buffer + dev->input->bufs); for (size_t i = 0; i < fft_size; i++, buf2 += 2) { fftin[i][0] = scale * (float)buf2[0] * window[i]; fftin[i][1] = scale * (float)buf2[1] * window[i]; } #endif /* WITH_BCM_VC */ } else if (dev->input->sfmt == SFMT_F32) { float const scale = 1.0f / dev->input->fullscale; #ifdef WITH_BCM_VC struct GPU_FFT_COMPLEX* ptr = fft->in; for (size_t b = 0; b < FFT_BATCH; b++, ptr += fft->step) { float* buf2 = (float*)(dev->input->buffer + dev->input->bufs + b * bps); for (size_t i = 0; i < fft_size; i++, buf2 += 2) { ptr[i].re = scale * buf2[0] * window[i * 2]; ptr[i].im = scale * buf2[1] * window[i * 2]; } } #else // WITH_BCM_VC float* buf2 = (float*)(dev->input->buffer + dev->input->bufs); for (size_t i = 0; i < fft_size; i++, buf2 += 2) { fftin[i][0] = scale * buf2[0] * window[i]; fftin[i][1] = scale * buf2[1] * window[i]; } #endif /* WITH_BCM_VC */ } else { // S8 or U8 levels_ptr = (dev->input->sfmt == SFMT_U8 ? levels_u8 : levels_s8); #ifdef WITH_BCM_VC sample_fft_arg sfa = {fft_size / 4, fft->in}; for (size_t i = 0; i < FFT_BATCH; i++) { samplefft(&sfa, dev->input->buffer + dev->input->bufs + i * bps, window, levels_ptr); sfa.dest += fft->step; } #else unsigned char* buf2 = dev->input->buffer + dev->input->bufs; for (size_t i = 0; i < fft_size; i++, buf2 += 2) { fftin[i][0] = levels_ptr[buf2[0]] * window[i]; fftin[i][1] = levels_ptr[buf2[1]] * window[i]; } #endif /* WITH_BCM_VC */ } #ifdef WITH_BCM_VC gpu_fft_execute(fft); #else fftwf_execute(demod_params->fft); #endif /* WITH_BCM_VC */ #ifdef WITH_BCM_VC for (int i = 0; i < dev->channel_count; i++) { float* wavein = dev->channels[i].wavein + dev->waveend; __builtin_prefetch(wavein, 1); const int bin = dev->bins[i]; const GPU_FFT_COMPLEX* fftout = fft->out + bin; for (int j = 0; j < FFT_BATCH; j++, ++wavein, fftout += fft->step) *wavein = sqrtf(fftout->im * fftout->im + fftout->re * fftout->re); } for (int j = 0; j < dev->channel_count; j++) { if (dev->channels[j].needs_raw_iq) { struct GPU_FFT_COMPLEX* ptr = fft->out; for (int job = 0; job < FFT_BATCH; job++) { dev->channels[j].iq_in[2 * (dev->waveend + job)] = ptr[dev->bins[j]].re; dev->channels[j].iq_in[2 * (dev->waveend + job) + 1] = ptr[dev->bins[j]].im; ptr += fft->step; } } } #else for (int j = 0; j < dev->channel_count; j++) { dev->channels[j].wavein[dev->waveend] = sqrtf(fftout[dev->bins[j]][0] * fftout[dev->bins[j]][0] + fftout[dev->bins[j]][1] * fftout[dev->bins[j]][1]); if (dev->channels[j].needs_raw_iq) { dev->channels[j].iq_in[2 * dev->waveend] = fftout[dev->bins[j]][0]; dev->channels[j].iq_in[2 * dev->waveend + 1] = fftout[dev->bins[j]][1]; } } #endif /* WITH_BCM_VC */ dev->waveend += FFT_BATCH; if (dev->waveend >= WAVE_BATCH + AGC_EXTRA) { for (int i = 0; i < dev->channel_count; i++) { AFC afc(dev, i); channel_t* channel = dev->channels + i; freq_t* fparms = channel->freqlist + channel->freq_idx; // set to NO_SIGNAL, will be updated to SIGNAL based on squelch below channel->axcindicate = NO_SIGNAL; for (int j = AGC_EXTRA; j < WAVE_BATCH + AGC_EXTRA; j++) { float& real = channel->iq_in[2 * (j - AGC_EXTRA)]; float& imag = channel->iq_in[2 * (j - AGC_EXTRA) + 1]; fparms->squelch.process_raw_sample(channel->wavein[j]); // If squelch is open / opening and using I/Q, then cleanup the signal and possibly update squelch. if (fparms->squelch.should_filter_sample() && channel->needs_raw_iq) { // remove phase rotation introduced by FFT sliding window float swf, cwf, re_tmp, im_tmp; sincosf_lut(channel->dm_phi, &swf, &cwf); multiply(real, imag, cwf, -swf, &re_tmp, &im_tmp); channel->dm_phi += channel->dm_dphi; channel->dm_phi &= 0xffffff; // apply lowpass filter, will be a no-op if not configured fparms->lowpass_filter.apply(re_tmp, im_tmp); // update I/Q and wave real = re_tmp; imag = im_tmp; channel->wavein[j] = sqrt(real * real + imag * imag); // update squelch post-cleanup if (fparms->lowpass_filter.enabled()) { fparms->squelch.process_filtered_sample(channel->wavein[j]); } } if (fparms->modulation == MOD_AM) { // if squelch is just opening then bootstrip agcavgfast with prior values of wavein if (fparms->squelch.first_open_sample()) { for (int k = j - AGC_EXTRA; k < j; k++) { if (channel->wavein[k] >= fparms->squelch.squelch_level()) { fparms->agcavgfast = fparms->agcavgfast * 0.9f + channel->wavein[k] * 0.1f; } } } // if squelch is just closing then fade out the prior samples of waveout else if (fparms->squelch.last_open_sample()) { for (int k = j - AGC_EXTRA + 1; k < j; k++) { channel->waveout[k] = channel->waveout[k - 1] * 0.94f; } } } float& waveout = channel->waveout[j]; // If squelch sees power then do modulation-specific processing if (fparms->squelch.should_process_audio()) { if (fparms->modulation == MOD_AM) { if (channel->wavein[j] > fparms->squelch.squelch_level()) { fparms->agcavgfast = fparms->agcavgfast * 0.995f + channel->wavein[j] * 0.005f; } waveout = (channel->wavein[j - AGC_EXTRA] - fparms->agcavgfast) / (fparms->agcavgfast * 1.5f); if (abs(waveout) > 0.8f) { waveout *= 0.85f; fparms->agcavgfast *= 1.15f; } if ((abs(waveout)>bel_wave_out_level && channel->wavein[j]>bel_wave_in_level) ){ #ifdef GPIOX gpiod_line_set_value(led_vox, 1); #endif audio_status=1; }else{ #ifdef GPIOX gpiod_line_set_value(led_vox, 0); #endif audio_status=0; } squelch_status = channel->freqlist[channel->freq_idx].oldstatus & 1; int temp_status = squelch_status + (audio_status << 1); if (channel->freqlist[channel->freq_idx].oldstatus !=temp_status && (fparms->banbel)){ pthread_mutex_lock(&lock_data); //fprintf(stderr,"HEJ %i,%i,%f,%f\n",channel->freqlist[channel->freq_idx].oldstatus ,temp_status,abs(waveout),channel->wavein[j]); status_data = temp_status; if (sem_post (&mutex_lock_main_thread) == -1) { perror ("sem_post: mutex_lock_main_thread"); exit (1); } if (sem_wait(&mutex_unlock_main_thread) == -1){ perror ("sem_post: mutex_unlock_main_thread"); exit (1); } //channel->freqlist[channel->freq_idx].oldstatus = temp_status;; pthread_mutex_unlock(&lock_data); } } #ifdef NFM else if (fparms->modulation == MOD_NFM) { // FM demod if (fm_demod == FM_FAST_ATAN2) { waveout = polar_disc_fast(real, imag, channel->pr, channel->pj); } else if (fm_demod == FM_QUADRI_DEMOD) { waveout = fm_quadri_demod(real, imag, channel->pr, channel->pj); } channel->pr = real; channel->pj = imag; // de-emphasis IIR + DC blocking fparms->agcavgfast = fparms->agcavgfast * 0.995f + waveout * 0.005f; waveout -= fparms->agcavgfast; waveout = waveout * (1.0f - channel->alpha) + channel->prev_waveout * channel->alpha; // save off waveout before notch and ampfactor channel->prev_waveout = waveout; } #endif /* NFM */ // process audio sample for CTCSS, will be no-op if not configured fparms->squelch.process_audio_sample(waveout); } // If squelch is still open then save samples to output if (fparms->squelch.is_open()) { // apply the notch filter, will be a no-op if not configured fparms->notch_filter.apply(waveout); // apply the ampfactor waveout *= fparms->ampfactor; // make sure the value is between +/- 1 (requirement for libmp3lame) if (isnan(waveout)) { waveout = 0.0; } else if (waveout > 1.0) { waveout = 1.0; } else if (waveout < -1.0) { waveout = -1.0; } channel->axcindicate = SIGNAL; if (channel->has_iq_outputs) { channel->iq_out[2 * (j - AGC_EXTRA)] = real; channel->iq_out[2 * (j - AGC_EXTRA) + 1] = imag; } // Squelch is closed } else { waveout = 0; if (channel->has_iq_outputs) { channel->iq_out[2 * (j - AGC_EXTRA)] = 0; channel->iq_out[2 * (j - AGC_EXTRA) + 1] = 0; } } } memmove(channel->wavein, channel->wavein + WAVE_BATCH, (dev->waveend - WAVE_BATCH) * sizeof(float)); if (channel->needs_raw_iq) { memmove(channel->iq_in, channel->iq_in + 2 * WAVE_BATCH, (dev->waveend - WAVE_BATCH) * sizeof(float) * 2); } #ifdef WITH_BCM_VC afc.finalize(dev, i, fft->out); #else afc.finalize(dev, i, demod_params->fftout); #endif /* WITH_BCM_VC */ if (tui || 1==1) { char symbol = fparms->squelch.signal_outside_filter() ? '~' : (char)channel->axcindicate; if (dev->mode == R_SCAN) { //GOTOXY(0, device_num * 17 + dev->row + 3); //printf("%4.0f/%3.0f%c %7.3f ", level_to_dBFS(fparms->squelch.signal_level()), level_to_dBFS(fparms->squelch.noise_level()), symbol, // (dev->channels[0].freqlist[channel->freq_idx].frequency / 1000000.0)); } else { //GOTOXY(i * 10, device_num * 17 + dev->row + 3); //printf("%4.0f/%3.0f%c ", level_to_dBFS(fparms->squelch.signal_level()), level_to_dBFS(fparms->squelch.noise_level()), symbol); } //BANBEL if (symbol=='*'){ #ifdef GPIOX gpiod_line_set_value(led_squ, 1); #endif squelch_status = 1; }else{ #ifdef GPIOX gpiod_line_set_value(led_squ, 0); #endif squelch_status = 0; } int temp_status = squelch_status + (audio_status << 1); if (channel->freqlist[channel->freq_idx].oldstatus != temp_status){ //fprintf(stderr,"BABEL %i %i\n",fparms->banbel,temp_status); if (fparms->banbel){ pthread_mutex_lock(&lock_data); status_data = temp_status; if (sem_post (&mutex_lock_main_thread) == -1) { perror ("sem_post: mutex_lock_main_thread"); exit (1); } if (sem_wait(&mutex_unlock_main_thread) == -1){ perror ("sem_post: mutex_unlock_main_thread"); exit (1); } pthread_mutex_unlock(&lock_data); } if ((temp_status & 1) != (channel->freqlist[channel->freq_idx].oldstatus & 1)) { mqtt_msg_t mqtt_msg; mqtt_msg.audio =(audio_status << 1); mqtt_msg.sq = squelch_status; mqtt_msg.freq = channel->freqlist->frequency; mqtt_msg.name = fparms->label; mqtt_msg.mountpoint = fparms->mountpoint; mqtt_msg.bel_status = -1; mqtt_msg.type=1; mqtt_queue.push(mqtt_msg); } channel->freqlist[channel->freq_idx].oldstatus = temp_status; //Here for mqtt } fflush(stdout); } if (channel->axcindicate != NO_SIGNAL) { channel->freqlist[channel->freq_idx].active_counter++; } } if (dev->waveavail == 1) { debug_print("devices[%d]: output channel overrun\n", device_num); dev->output_overrun_count++; } else { dev->waveavail = 1; } dev->waveend -= WAVE_BATCH; #ifdef DEBUG gettimeofday(&te, NULL); debug_bulk_print("waveavail %lu.%lu %lu\n", te.tv_sec, (unsigned long)te.tv_usec, (te.tv_sec - ts.tv_sec) * 1000000UL + te.tv_usec - ts.tv_usec); ts.tv_sec = te.tv_sec; ts.tv_usec = te.tv_usec; #endif /* DEBUG */ demod_params->mp3_signal->send(); dev->row++; if (dev->row == 12) { dev->row = 0; } } dev->input->bufs = (dev->input->bufs + bps * FFT_BATCH) % dev->input->buf_size; device_num = next_device(demod_params, device_num); } } void usage() { cout << "Usage: rtl_airband [options] [-c ]\n\ \t-h\t\t\tDisplay this help text\n\ \t-f\t\t\tRun in foreground, display textual waterfalls\n\ \t-F\t\t\tRun in foreground, do not display waterfalls (for running as a systemd service)\n"; #ifdef NFM cout << "\t-Q\t\t\tUse quadri correlator for FM demodulation (default is atan2)\n"; #endif /* NFM */ #ifdef DEBUG cout << "\t-d \t\tLog debugging information to (default is " << DEBUG_PATH << ")\n"; #endif /* DEBUG */ cout << "\t-e\t\t\tPrint messages to standard error (disables syslog logging)\n"; cout << "\t-c \tUse non-default configuration file\n\t\t\t\t(default: " << CFGFILE << ")\n\ \t-v\t\t\tDisplay version and exit\n"; exit(EXIT_SUCCESS); } static int count_devices_running() { int ret = 0; for (int i = 0; i < device_count; i++) { if (devices[i].input->state == INPUT_RUNNING) { ret++; } } return ret; } int main(int argc, char* argv[]) { #ifdef WITH_PROFILING ProfilerStart("rtl_airband.prof"); #endif /* WITH_PROFILING */ // initialization sem_init (&mutex_lock_main_thread, 1, 0); sem_init (&mutex_unlock_main_thread, 1, 0); if (pthread_mutex_init(&lock_data, NULL) != 0) { printf("\n mutex init has failed\n"); return 1; } if (pthread_mutex_init(&lock_mqtt_data, NULL) != 0) { printf("\n mutex init has failed\n"); return 1; } printf("SEM and locks OPEN\n"); #ifdef GPIOX char const *chipname = "gpiochip0"; unsigned int gpio16 = 16; unsigned int gpio20 = 20; unsigned int gpio26 = 26; unsigned int gpio24 = 24; unsigned int gpio25 = 25; chip = gpiod_chip_open_by_name(chipname); if (!chip) { perror("gpiod_chip_open_by_name"); return 1; } led_squ = gpiod_chip_get_line(chip, gpio26); led_vox = gpiod_chip_get_line(chip, gpio20); led_control = gpiod_chip_get_line(chip, gpio16); relay1 = gpiod_chip_get_line(chip, gpio25); relay2 = gpiod_chip_get_line(chip, gpio24); #endif #ifdef GPIOX gpiod_line_request_output(led_squ, "Consumer1", 0); gpiod_line_set_value(led_squ, 0); gpiod_line_request_output(led_vox, "Consumer2", 0); gpiod_line_set_value(led_vox, 0); gpiod_line_request_output(led_control, "Consumer3", 0); gpiod_line_set_value(led_control, 0); gpiod_line_request_output(relay1, "Consumer4", 0); gpiod_line_set_value(relay1, 1); gpiod_line_request_output(relay2, "Consumer5", 0); gpiod_line_set_value(relay2, 1); #endif #pragma GCC diagnostic ignored "-Wwrite-strings" char* cfgfile = CFGFILE; char* pidfile = PIDFILE; #pragma GCC diagnostic warning "-Wwrite-strings" int opt; char optstring[16] = "efFhvc:"; #ifdef NFM strcat(optstring, "Q"); #endif /* NFM */ #ifdef DEBUG strcat(optstring, "d:"); #endif /* DEBUG */ int foreground = 0; // daemonize int do_syslog = 1; while ((opt = getopt(argc, argv, optstring)) != -1) { switch (opt) { #ifdef NFM case 'Q': fm_demod = FM_QUADRI_DEMOD; break; #endif /* NFM */ #ifdef DEBUG case 'd': debug_path = strdup(optarg); break; #endif /* DEBUG */ case 'e': do_syslog = 0; break; case 'f': foreground = 1; tui = 1; break; case 'F': foreground = 1; tui = 0; break; case 'c': cfgfile = optarg; break; case 'v': cout << "RTLSDR-Airband version " << RTL_AIRBAND_VERSION << "\n"; exit(EXIT_SUCCESS); case 'h': default: usage(); break; } } #ifdef DEBUG if (!debug_path) debug_path = strdup(DEBUG_PATH); init_debug(debug_path); #endif /* DEBUG */ // If executing other than as root, GPU memory gets alloc'd and the // 'permission denied' message on /dev/mem kills rtl_airband without // releasing GPU memory. #ifdef WITH_BCM_VC // should probably do this check in other circumstances also. if (0 != getuid()) { cerr << "FFT library requires that rtl_airband be executed as root\n"; exit(1); } #endif /* WITH_BCM_VC */ // read config try { Config config; config.readFile(cfgfile); Setting& root = config.getRoot(); if (root.exists("pidfile")) pidfile = strdup(root["pidfile"]); if (root.exists("fft_size")) { int fsize = (int)(root["fft_size"]); fft_size_log = 0; for (size_t i = MIN_FFT_SIZE_LOG; i <= MAX_FFT_SIZE_LOG; i++) { if (fsize == 1 << i) { fft_size = (size_t)fsize; fft_size_log = i; break; } } if (fft_size_log == 0) { cerr << "Configuration error: invalid fft_size value (must be a power of two in range " << (1 << MIN_FFT_SIZE_LOG) << "-" << (1 << MAX_FFT_SIZE_LOG) << ")\n"; error(); } } if (root.exists("shout_metadata_delay")) shout_metadata_delay = (int)(root["shout_metadata_delay"]); if (shout_metadata_delay < 0 || shout_metadata_delay > 2 * TAG_QUEUE_LEN) { cerr << "Configuration error: shout_metadata_delay is out of allowed range (0-" << 2 * TAG_QUEUE_LEN << ")\n"; error(); } if (root.exists("localtime") && (bool)root["localtime"] == true) use_localtime = true; if (root.exists("multiple_demod_threads") && (bool)root["multiple_demod_threads"] == true) { #ifdef WITH_BCM_VC cerr << "Using multiple_demod_threads not supported with BCM VideoCore for FFT\n"; exit(1); #endif /* WITH_BCM_VC */ multiple_demod_threads = true; } if (root.exists("multiple_output_threads") && (bool)root["multiple_output_threads"] == true) { multiple_output_threads = true; } if (root.exists("log_scan_activity") && (bool)root["log_scan_activity"] == true) log_scan_activity = true; if (root.exists("stats_filepath")) stats_filepath = strdup(root["stats_filepath"]); #ifdef NFM if (root.exists("tau")) alpha = ((int)root["tau"] == 0 ? 0.0f : exp(-1.0f / (WAVE_RATE * 1e-6 * (int)root["tau"]))); #endif /* NFM */ if (root.exists("bel_start_on_time")) bel_start_on_time=(int)(root["bel_start_on_time"]); if (root.exists("bel_open_time")) bel_open_time=(int)(root["bel_open_time"]); if (root.exists("bel_blink_time")) bel_blink_time=(int)(root["bel_blink_time"]); if (root.exists("bel_wave_out_level")) bel_wave_out_level=(float)(root["bel_wave_out_level"]); if (root.exists("bel_wave_in_level")) bel_wave_in_level=(float)(root["bel_wave_in_level"]); if (root.exists("bel_prolong_time")) bel_prolong_time=(int)(root["bel_prolong_time"]); //MQTT if (root.exists("bel_mqtt_server")) bel_mqtt_server = strdup(root["bel_mqtt_server"]); if (root.exists("bel_mqtt_client_id")) bel_mqtt_client_id = strdup(root["bel_mqtt_client_id"]); if (root.exists("bel_mqtt_user_id")) bel_mqtt_user_id= strdup(root["bel_mqtt_user_id"]); if (root.exists("bel_mqtt_server")) bel_mqtt_password= strdup(root["bel_mqtt_password"]); Setting& devs = config.lookup("devices"); device_count = devs.getLength(); if (device_count < 1) { cerr << "Configuration error: no devices defined\n"; error(); } struct sigaction sigact, pipeact; memset(&sigact, 0, sizeof(sigact)); memset(&pipeact, 0, sizeof(pipeact)); pipeact.sa_handler = SIG_IGN; sigact.sa_handler = &sighandler; sigaction(SIGPIPE, &pipeact, NULL); sigaction(SIGHUP, &sigact, NULL); sigaction(SIGINT, &sigact, NULL); sigaction(SIGQUIT, &sigact, NULL); sigaction(SIGTERM, &sigact, NULL); devices = (device_t*)XCALLOC(device_count, sizeof(device_t)); shout_init(); if (do_syslog) { openlog("rtl_airband", LOG_PID, LOG_DAEMON); log_destination = SYSLOG; } else if (foreground) { log_destination = STDERR; } else { log_destination = NONE; } if (root.exists("mixers")) { Setting& mx = config.lookup("mixers"); mixers = (mixer_t*)XCALLOC(mx.getLength(), sizeof(struct mixer_t)); if ((mixer_count = parse_mixers(mx)) > 0) { mixers = (mixer_t*)XREALLOC(mixers, mixer_count * sizeof(struct mixer_t)); } else { free(mixers); } } else { mixer_count = 0; } uint32_t devs_enabled = parse_devices(devs); if (devs_enabled < 1) { cerr << "Configuration error: no devices defined\n"; error(); } device_count = devs_enabled; debug_print("mixer_count=%d\n", mixer_count); #ifdef DEBUG for (int z = 0; z < mixer_count; z++) { mixer_t* m = &mixers[z]; debug_print("mixer[%d]: name=%s, input_count=%d, output_count=%d\n", z, m->name, m->input_count, m->channel.output_count); } #endif /* DEBUG */ } catch (const FileIOException& e) { cerr << "Cannot read configuration file " << cfgfile << "\n"; error(); } catch (const ParseException& e) { cerr << "Error while parsing configuration file " << cfgfile << " line " << e.getLine() << ": " << e.getError() << "\n"; error(); } catch (const SettingNotFoundException& e) { cerr << "Configuration error: mandatory parameter missing: " << e.getPath() << "\n"; error(); } catch (const SettingTypeException& e) { cerr << "Configuration error: invalid parameter type: " << e.getPath() << "\n"; error(); } catch (const ConfigException& e) { cerr << "Unhandled config exception\n"; error(); } log(LOG_INFO, "RTLSDR-Airband version %s starting\n", RTL_AIRBAND_VERSION); if (!foreground) { int pid1, pid2; if ((pid1 = fork()) == -1) { cerr << "Cannot fork child process: " << strerror(errno) << "\n"; error(); } if (pid1) { waitpid(-1, NULL, 0); return (0); } else { if ((pid2 = fork()) == -1) { cerr << "Cannot fork child process: " << strerror(errno) << "\n"; error(); } if (pid2) { return (0); } else { int nullfd, dupfd; if ((nullfd = open("/dev/null", O_RDWR)) == -1) { log(LOG_CRIT, "Cannot open /dev/null: %s\n", strerror(errno)); error(); } for (dupfd = 0; dupfd <= 2; dupfd++) { if (dup2(nullfd, dupfd) == -1) { log(LOG_CRIT, "dup2(): %s\n", strerror(errno)); error(); } } if (nullfd > 2) close(nullfd); FILE* f = fopen(pidfile, "w"); if (f == NULL) { log(LOG_WARNING, "Cannot write pidfile: %s\n", strerror(errno)); } else { fprintf(f, "%ld\n", (long)getpid()); fclose(f); } } } } for (int i = 0; i < mixer_count; i++) { if (mixers[i].enabled == false) { continue; // no inputs connected = no need to initialize output } channel_t* channel = &mixers[i].channel; if (channel->need_mp3) { channel->lame = airlame_init(mixers[i].channel.mode, mixers[i].channel.highpass, mixers[i].channel.lowpass); channel->lamebuf = (unsigned char*)malloc(sizeof(unsigned char) * LAMEBUF_SIZE); } for (int k = 0; k < channel->output_count; k++) { output_t* output = channel->outputs + k; if (output->type == O_ICECAST) { shout_setup((icecast_data*)(output->data), channel->mode); } else if (output->type == O_UDP_STREAM) { udp_stream_data* sdata = (udp_stream_data*)(output->data); if (!udp_stream_init(sdata, channel->mode, (size_t)WAVE_BATCH * sizeof(float))) { cerr << "Failed to initialize mixer " << i << " output " << k << " - aborting\n"; error(); } #ifdef WITH_PULSEAUDIO } else if (output->type == O_PULSE) { pulse_init(); pulse_setup((pulse_data*)(output->data), channel->mode); #endif /* WITH_PULSEAUDIO */ } } } for (int i = 0; i < device_count; i++) { device_t* dev = devices + i; for (int j = 0; j < dev->channel_count; j++) { channel_t* channel = dev->channels + j; // If the channel has icecast or MP3 file output, we will attempt to // initialize a separate LAME context for MP3 encoding. if (channel->need_mp3) { channel->lame = airlame_init(channel->mode, channel->highpass, channel->lowpass); channel->lamebuf = (unsigned char*)malloc(sizeof(unsigned char) * LAMEBUF_SIZE); } for (int k = 0; k < channel->output_count; k++) { output_t* output = channel->outputs + k; if (output->type == O_ICECAST) { shout_setup((icecast_data*)(output->data), channel->mode); } else if (output->type == O_UDP_STREAM) { udp_stream_data* sdata = (udp_stream_data*)(output->data); if (!udp_stream_init(sdata, channel->mode, (size_t)WAVE_BATCH * sizeof(float))) { cerr << "Failed to initialize device " << i << " channel " << j << " output " << k << " - aborting\n"; error(); } #ifdef WITH_PULSEAUDIO } else if (output->type == O_PULSE) { pulse_init(); pulse_setup((pulse_data*)(output->data), channel->mode); #endif /* WITH_PULSEAUDIO */ } } } if (input_init(dev->input) != 0 || dev->input->state != INPUT_INITIALIZED) { if (errno != 0) { cerr << "Failed to initialize input device " << i << ": " << strerror(errno) << " - aborting\n"; } else { cerr << "Failed to initialize input device " << i << " - aborting\n"; } error(); } if (input_start(dev->input) != 0) { cerr << "Failed to start input on device " << i << ": " << strerror(errno) << " - aborting\n"; error(); } if (dev->mode == R_SCAN) { // FIXME: set errno if (pthread_mutex_init(&dev->tag_queue_lock, NULL) != 0) { cerr << "Failed to initialize mutex - aborting\n"; error(); } // FIXME: not needed when freq_count == 1? pthread_create(&dev->controller_thread, NULL, &controller_thread, dev); } } int timeout_device = 50; // 5 seconds while ((devices_running = count_devices_running()) != device_count && timeout_device > 0) { SLEEP(100); timeout_device--; } if ((devices_running = count_devices_running()) != device_count) { log(LOG_ERR, "%d device(s) failed to initialize - aborting\n", device_count - devices_running); error(); } if (tui && 1==0) { //PMPMPM printf("\e[1;1H\e[2J"); GOTOXY(0, 0); printf(" "); for (int i = 0; i < device_count; i++) { GOTOXY(0, i * 17 + 1); for (int j = 0; j < devices[i].channel_count; j++) { printf(" %7.3f ", devices[i].channels[j].freqlist[devices[i].channels[j].freq_idx].frequency / 1000000.0); } if (i != device_count - 1) { GOTOXY(0, i * 17 + 16); printf("-------------------------------------------------------------------------------"); } } } THREAD belysning_control_check; THREAD mqtt_control_check; pthread_create(&belysning_control_check, NULL, &belysning_control_thread, NULL); pthread_create(&mqtt_control_check, NULL, &mqtt_control_thread, NULL); mqtt_msg_t mqtt_msg; mqtt_msg.type=3; mqtt_queue.push(mqtt_msg); THREAD output_check; pthread_create(&output_check, NULL, &output_check_thread, NULL); int demod_thread_count = multiple_demod_threads ? device_count : 1; demod_params_t* demod_params = (demod_params_t*)XCALLOC(demod_thread_count, sizeof(demod_params_t)); THREAD* demod_threads = (THREAD*)XCALLOC(demod_thread_count, sizeof(THREAD)); int output_thread_count = 1; if (multiple_output_threads) { output_thread_count = demod_thread_count; if (mixer_count > 0) { output_thread_count++; } } output_params_t* output_params = (output_params_t*)XCALLOC(output_thread_count, sizeof(output_params_t)); THREAD* output_threads = (THREAD*)XCALLOC(output_thread_count, sizeof(THREAD)); // Setup the output and demod threads if (multiple_output_threads == false) { init_output(&output_params[0], 0, device_count, 0, mixer_count); if (multiple_demod_threads == false) { init_demod(&demod_params[0], output_params[0].mp3_signal, 0, device_count); } else { for (int i = 0; i < demod_thread_count; i++) { init_demod(&demod_params[i], output_params[0].mp3_signal, i, i + 1); } } } else { if (multiple_demod_threads == false) { init_output(&output_params[0], 0, device_count, 0, 0); init_demod(&demod_params[0], output_params[0].mp3_signal, 0, device_count); } else { for (int i = 0; i < device_count; i++) { init_output(&output_params[i], i, i + 1, 0, 0); init_demod(&demod_params[i], output_params[i].mp3_signal, i, i + 1); } } if (mixer_count > 0) { init_output(&output_params[output_thread_count - 1], 0, 0, 0, mixer_count); } } // Startup the output threads for (int i = 0; i < output_thread_count; i++) { pthread_create(&output_threads[i], NULL, &output_thread, &output_params[i]); } // Startup the mixer thread (if there is one) using the signal for the last output thread THREAD mixer; if (mixer_count > 0) { pthread_create(&mixer, NULL, &mixer_thread, output_params[output_thread_count - 1].mp3_signal); } #ifdef WITH_PULSEAUDIO pulse_start(); #endif /* WITH_PULSEAUDIO */ sincosf_lut_init(); // Startup the demod threads for (int i = 0; i < demod_thread_count; i++) { pthread_create(&demod_threads[i], NULL, &demodulate, &demod_params[i]); } // Wait for demod threads to exit for (int i = 0; i < demod_thread_count; i++) { pthread_join(demod_threads[i], NULL); } log(LOG_INFO, "Cleaning up\n"); for (int i = 0; i < device_count; i++) { if (devices[i].mode == R_SCAN) pthread_join(devices[i].controller_thread, NULL); if (input_stop(devices[i].input) != 0 || devices[i].input->state != INPUT_STOPPED) { if (errno != 0) { log(LOG_ERR, "Failed do stop device #%d: %s\n", i, strerror(errno)); } else { log(LOG_ERR, "Failed do stop device #%d\n", i); } } } log(LOG_INFO, "Input threads closed\n"); for (int i = 0; i < device_count; i++) { device_t* dev = devices + i; disable_device_outputs(dev); } if (mixer_count > 0) { log(LOG_INFO, "Closing mixer thread\n"); pthread_join(mixer, NULL); } log(LOG_INFO, "Closing output thread(s)\n"); for (int i = 0; i < output_thread_count; i++) { output_params[i].mp3_signal->send(); pthread_join(output_threads[i], NULL); } for (int i = 0; i < device_count; i++) { device_t* dev = devices + i; for (int j = 0; j < dev->channel_count; j++) { channel_t* channel = dev->channels + j; if (channel->need_mp3 && channel->lame) { lame_close(channel->lame); } } } log(LOG_INFO, "Closing bel thread\n"); pthread_join(belysning_control_check, NULL); log(LOG_INFO, "Closing mqtt thread\n"); pthread_kill(mqtt_control_check,SIGALRM); // pthread_join(mqtt_control_check, NULL); log(LOG_INFO, "Closing xxx thread\n"); close_debug(); #ifdef WITH_PROFILING ProfilerStop(); #endif /* WITH_PROFILING */ return 0; }