summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt73
-rw-r--r--src/RadioImpl.cc326
-rw-r--r--src/RadioImpl.h123
-rw-r--r--src/convenience/convenience.c309
-rw-r--r--src/convenience/convenience.h142
-rw-r--r--src/main-grpc.cc83
-rw-r--r--src/meson.build81
-rw-r--r--src/radio-binding.c752
-rw-r--r--src/radio_impl.h128
-rw-r--r--src/radio_impl_kingfisher.c522
-rw-r--r--src/radio_impl_kingfisher.h25
-rw-r--r--src/radio_impl_null.c279
-rw-r--r--src/radio_impl_null.h25
-rw-r--r--src/radio_impl_rtlsdr.c496
-rw-r--r--src/radio_impl_rtlsdr.h25
-rw-r--r--src/radio_impl_tef665x.c2401
-rw-r--r--src/radio_impl_tef665x.h23
-rw-r--r--src/radio_output.h31
-rw-r--r--src/radio_output_gstreamer.c231
-rw-r--r--src/rtl_fm.c1275
-rw-r--r--src/rtl_fm.h70
-rw-r--r--src/rtl_fm_helper.c243
-rw-r--r--src/tef665x.h397
23 files changed, 8060 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..560dcaf
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,73 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+# Copyright (C) 2018, 2019 Konsulko Group
+#
+# author: Fulup Ar Foll <fulup@iot.bzh>
+# contrib: Romain Forlot <romain.forlot@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+# Add target to project dependency list
+PROJECT_TARGET_ADD(radio-binding)
+
+ add_definitions(-DAFB_BINDING_VERSION=3)
+
+ # Define project Targets
+ set(radio_SOURCES
+ radio-binding.c
+ radio_impl_kingfisher.c
+ radio_impl_null.c
+ radio_impl_rtlsdr.c
+ radio_impl_tef665x.c)
+
+ PKG_CHECK_MODULES(SOUND REQUIRED gstreamer-1.0)
+
+ add_library(${TARGET_NAME} MODULE ${radio_SOURCES})
+
+ # Binder exposes a unique public entry point
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ PREFIX "libafm-"
+ LABELS "BINDING"
+ LINK_FLAGS ${BINDINGS_LINK_FLAG}
+ OUTPUT_NAME ${TARGET_NAME}
+ )
+
+ # Library dependencies (include updates automatically)
+ TARGET_COMPILE_OPTIONS(${TARGET_NAME} PUBLIC ${SOUND_CFLAGS})
+ TARGET_LINK_LIBRARIES(${TARGET_NAME} ${SOUND_LIBRARIES} ${link_libraries})
+
+# Add helper program target
+PROJECT_TARGET_ADD(rtl_fm_helper)
+
+ # Define project targets
+ set(helper_SOURCES
+ ${TARGET_NAME}.c
+ radio_output_gstreamer.c
+ rtl_fm.c
+ convenience/convenience.c)
+
+ PKG_CHECK_MODULES(helper_SOUND REQUIRED gstreamer-1.0)
+ PKG_CHECK_MODULES(helper_RTLSDR REQUIRED librtlsdr)
+ PKG_CHECK_MODULES(helper_LIBUSB REQUIRED libusb-1.0)
+
+ add_executable(${TARGET_NAME} ${helper_SOURCES})
+
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ LABELS "EXECUTABLE"
+ OUTPUT_NAME ${TARGET_NAME}
+ )
+
+ TARGET_COMPILE_OPTIONS(${TARGET_NAME} PUBLIC ${helper_SOUND_CFLAGS})
+ TARGET_LINK_LIBRARIES(${TARGET_NAME}
+ ${helper_RTLSDR_LIBRARIES} ${helper_LIBUSB_LIBRARIES} ${helper_SOUND_LIBRARIES} ${link_libraries} m)
diff --git a/src/RadioImpl.cc b/src/RadioImpl.cc
new file mode 100644
index 0000000..634f066
--- /dev/null
+++ b/src/RadioImpl.cc
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: Apache-2.0
+/*
+ * Copyright (C) 2022 Konsulko Group
+ */
+
+#include "RadioImpl.h"
+
+// C backend implementations
+extern "C" {
+#include "radio_impl_null.h"
+#include "radio_impl_rtlsdr.h"
+#include "radio_impl_kingfisher.h"
+#include "radio_impl_tef665x.h"
+}
+
+using grpc::StatusCode;
+using automotivegradelinux::Band;
+using automotivegradelinux::BAND_UNSPECIFIED;
+using automotivegradelinux::BAND_AM;
+using automotivegradelinux::BAND_FM;
+using automotivegradelinux::StereoMode;
+using automotivegradelinux::STEREO_MODE_UNSPECIFIED;
+using automotivegradelinux::STEREO_MODE_MONO;
+using automotivegradelinux::STEREO_MODE_STEREO;
+using automotivegradelinux::ScanDirection;
+using automotivegradelinux::SCAN_DIRECTION_FORWARD;
+using automotivegradelinux::SCAN_DIRECTION_BACKWARD;
+
+RadioImpl::RadioImpl()
+{
+}
+
+bool RadioImpl::Detect()
+{
+ // Probe for radio backends
+ m_radio_impl_ops = &rtlsdr_impl_ops;
+ int rc = m_radio_impl_ops->probe();
+ if(rc != 0) {
+ // Look for Kingfisher Si4689
+ m_radio_impl_ops = &kf_impl_ops;
+ rc = m_radio_impl_ops->probe();
+ }
+#if 0
+ if(rc != 0) {
+ m_radio_impl_ops = &tef665x_impl_ops;
+ rc = m_radio_impl_ops->probe();
+ }
+#endif
+ if (rc != 0) {
+ m_radio_impl_ops = &null_impl_ops;
+ rc = m_radio_impl_ops->probe();
+ }
+ if (rc != 0) {
+ // We don't expect the null implementation to fail probe, but just in case...
+ std::cerr << "No radio device found, exiting" << std::endl;
+ return false;
+ }
+ // Try to initialize detected backend
+ rc = m_radio_impl_ops->init();
+ if(rc < 0) {
+ std::cerr << m_radio_impl_ops->name << " initialization failed" << std::endl;
+ return false;
+ }
+ std::cout << m_radio_impl_ops->name << "found" << std::endl;
+ m_radio_impl_ops->set_frequency_callback(FrequencyCallback, this);
+ m_radio_impl_ops->set_frequency_callback(FrequencyCallback, this);
+#if 0
+ if(m_radio_impl_ops->set_rds_callback) {
+ m_radio_impl_ops->set_rds_callback(RDSCallback);
+ }
+#endif
+ return true;
+}
+
+Status RadioImpl::GetFrequency(ServerContext* context, const GetFrequencyRequest* request, GetFrequencyResponse* response)
+{
+ response->set_frequency(m_radio_impl_ops->get_frequency());
+ return Status::OK;
+}
+
+Status RadioImpl::SetFrequency(ServerContext* context, const SetFrequencyRequest* request, SetFrequencyResponse* response)
+{
+ radio_band_t band = m_radio_impl_ops->get_band();
+ uint32_t min_frequency = m_radio_impl_ops->get_min_frequency(band);
+ uint32_t max_frequency = m_radio_impl_ops->get_max_frequency(band);
+ uint32_t step = m_radio_impl_ops->get_frequency_step(band);
+
+ uint32_t frequency = request->frequency();
+ if(frequency < min_frequency ||
+ frequency > max_frequency ||
+ (frequency - min_frequency) % step) {
+ //afb_req_reply(request, NULL, "failed", "Invalid frequency");
+ return Status::OK;
+ }
+ m_radio_impl_ops->set_frequency(frequency);
+ return Status::OK;
+}
+
+Status RadioImpl::GetBand(ServerContext* context, const GetBandRequest* request, GetBandResponse* response)
+{
+ Band band = BAND_UNSPECIFIED;
+ radio_band_t impl_band = m_radio_impl_ops->get_band();
+
+ if (impl_band == RADIO_BAND_AM)
+ band = BAND_AM;
+ else if (impl_band == RADIO_BAND_FM)
+ band = BAND_FM;
+ response->set_band(band);
+ return Status::OK;
+}
+
+Status RadioImpl::SetBand(ServerContext* context, const SetBandRequest* request, SetBandResponse* response)
+{
+ radio_band_t impl_band = RADIO_BAND_UNSPECIFIED;
+ if (request->band() == BAND_AM)
+ impl_band = RADIO_BAND_AM;
+ else if (request->band() == BAND_FM)
+ impl_band = RADIO_BAND_FM;
+
+ if (impl_band != RADIO_BAND_UNSPECIFIED) {
+ m_radio_impl_ops->set_band(impl_band);
+ } else {
+ // FIXME: Indicate error
+ return Status::OK;
+ }
+
+ return Status::OK;
+}
+
+Status RadioImpl::GetBandSupported(ServerContext* context, const GetBandSupportedRequest* request, GetBandSupportedResponse* response)
+{
+ radio_band_t impl_band = RADIO_BAND_UNSPECIFIED;
+ if (request->band() == BAND_AM)
+ impl_band = RADIO_BAND_AM;
+ else if (request->band() == BAND_FM)
+ impl_band = RADIO_BAND_FM;
+
+ if (impl_band != RADIO_BAND_UNSPECIFIED)
+ response->set_supported(m_radio_impl_ops->band_supported(impl_band));
+ else
+ response->set_supported(false);
+
+ return Status::OK;
+}
+
+Status RadioImpl::GetBandParameters(ServerContext* context, const GetBandParametersRequest* request, GetBandParametersResponse* response)
+{
+ radio_band_t impl_band = RADIO_BAND_UNSPECIFIED;
+ if (request->band() == BAND_AM)
+ impl_band = RADIO_BAND_AM;
+ else if (request->band() == BAND_FM)
+ impl_band = RADIO_BAND_FM;
+
+ if (impl_band != RADIO_BAND_UNSPECIFIED) {
+ response->set_min(m_radio_impl_ops->get_min_frequency(impl_band));
+ response->set_max(m_radio_impl_ops->get_max_frequency(impl_band));
+ response->set_step(m_radio_impl_ops->get_frequency_step(impl_band));
+ } else {
+ // FIXME: Indicate error
+ return Status::OK;
+ }
+ return Status::OK;
+}
+
+Status RadioImpl::GetStereoMode(ServerContext* context, const GetStereoModeRequest* request, GetStereoModeResponse* response)
+{
+ StereoMode mode = STEREO_MODE_UNSPECIFIED;
+ radio_stereo_mode_t impl_mode = m_radio_impl_ops->get_stereo_mode();
+
+ if (impl_mode == RADIO_MODE_MONO)
+ mode = STEREO_MODE_MONO;
+ else if (impl_mode == RADIO_MODE_STEREO)
+ mode = STEREO_MODE_STEREO;
+ response->set_mode(mode);
+ return Status::OK;
+}
+
+Status RadioImpl::SetStereoMode(ServerContext* context, const SetStereoModeRequest* request, SetStereoModeResponse* response)
+{
+ radio_stereo_mode_t impl_mode = RADIO_MODE_UNSPECIFIED;
+ if (request->mode() == STEREO_MODE_MONO)
+ impl_mode = RADIO_MODE_MONO;
+ else if (request->mode() == STEREO_MODE_STEREO)
+ impl_mode = RADIO_MODE_STEREO;
+
+ if (impl_mode != RADIO_MODE_UNSPECIFIED) {
+ m_radio_impl_ops->set_stereo_mode(impl_mode);
+ } else {
+ // FIXME: Indicate error
+ return Status::OK;
+ }
+ return Status::OK;
+}
+
+Status RadioImpl::Start(ServerContext* context, const StartRequest* request, StartResponse* response)
+{
+ m_radio_impl_ops->set_output(NULL);
+ m_radio_impl_ops->start();
+ m_playing = true;
+
+ StatusResponse status_response;
+ auto play_status = status_response.mutable_play();
+ play_status->set_playing(true);
+ SendStatusResponse(status_response);
+
+ return Status::OK;
+}
+
+Status RadioImpl::Stop(ServerContext* context, const StopRequest* request, StopResponse* response)
+{
+ m_radio_impl_ops->stop();
+ m_playing = false;
+
+ StatusResponse status_response;
+ auto play_status = status_response.mutable_play();
+ play_status->set_playing(false);
+ SendStatusResponse(status_response);
+
+ return Status::OK;
+}
+
+Status RadioImpl::ScanStart(ServerContext* context, const ScanStartRequest* request, ScanStartResponse* response)
+{
+ radio_scan_direction_t impl_direction = RADIO_SCAN_UNSPECIFIED;
+ if (request->direction() == SCAN_DIRECTION_FORWARD)
+ impl_direction = RADIO_SCAN_FORWARD;
+ else if (request->direction() == SCAN_DIRECTION_BACKWARD)
+ impl_direction = RADIO_SCAN_BACKWARD;
+
+ if (impl_direction != RADIO_SCAN_UNSPECIFIED) {
+ m_radio_impl_ops->scan_start(impl_direction, ScanCallback, this);
+ } else {
+ // FIXME: Indicate error
+ return Status::OK;
+ }
+
+ return Status::OK;
+}
+
+Status RadioImpl::ScanStop(ServerContext* context, const ScanStopRequest* request, ScanStopResponse* response)
+{
+ m_radio_impl_ops->scan_stop();
+ return Status::OK;
+}
+
+Status RadioImpl::GetRDS(ServerContext* context, const GetRDSRequest* request, GetRDSResponse* response)
+{
+ return Status::OK;
+}
+
+Status RadioImpl::GetQuality(ServerContext* context, const GetQualityRequest* request, GetQualityResponse* response)
+{
+ return Status::OK;
+}
+
+Status RadioImpl::SetAlternativeFrequency(ServerContext* context, const SetAlternativeFrequencyRequest* request, SetAlternativeFrequencyResponse* response)
+{
+ return Status::OK;
+}
+
+Status RadioImpl::GetStatusEvents(ServerContext* context, const StatusRequest* request, ServerWriter<StatusResponse>* writer)
+{
+ // Save client information
+ m_clients_mutex.lock();
+ m_clients.push_back(std::pair(context, writer));
+ m_clients_mutex.unlock();
+
+ // For now block until client disconnect / server shutdown
+ // A switch to the async or callback server APIs might be more elegant than
+ // holding the thread like this, and may be worth investigating at some point.
+ std::unique_lock lock(m_done_mutex);
+ m_done_cv.wait(lock, [context, this]{ return (context->IsCancelled() || m_done); });
+
+ return Status::OK;
+}
+
+void RadioImpl::SendStatusResponse(StatusResponse &response)
+{
+ const std::lock_guard<std::mutex> lock(m_clients_mutex);
+
+ if (m_clients.empty())
+ return;
+
+ auto it = m_clients.begin();
+ while (it != m_clients.end()) {
+ if (it->first->IsCancelled()) {
+ // Client has gone away, remove from list
+ std::cout << "Removing cancelled RPC client!" << std::endl;
+ it = m_clients.erase(it);
+
+ // We're not exiting, but wake up blocked client RPC handlers so
+ // the canceled one will clean exit.
+ // Note that in practice this means the client RPC handler thread
+ // sticks around until the next status event is sent.
+ m_done_cv.notify_all();
+
+ continue;
+ } else {
+ it->second->Write(response);
+ ++it;
+ }
+ }
+}
+
+
+void RadioImpl::HandleFrequencyEvent(uint32_t frequency)
+{
+ StatusResponse response;
+ auto frequency_status = response.mutable_frequency();
+ frequency_status->set_frequency(frequency);
+ SendStatusResponse(response);
+}
+
+void RadioImpl::HandleScanEvent(uint32_t frequency)
+{
+ StatusResponse response;
+ auto scan_status = response.mutable_scan();
+ scan_status->set_station_found(frequency);
+ SendStatusResponse(response);
+}
+
+void RadioImpl::HandleRDSEvent(void *rds_data)
+{
+ // TODO
+}
+
diff --git a/src/RadioImpl.h b/src/RadioImpl.h
new file mode 100644
index 0000000..841c72b
--- /dev/null
+++ b/src/RadioImpl.h
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: Apache-2.0
+/*
+ * Copyright (C) 2022 Konsulko Group
+ */
+
+#ifndef RADIO_IMPL_H
+#define RADIO_IMPL_H
+
+#include <mutex>
+#include <list>
+#include <condition_variable>
+
+#include <grpcpp/ext/proto_server_reflection_plugin.h>
+#include <grpcpp/grpcpp.h>
+#include <grpcpp/health_check_service_interface.h>
+
+#include "radio.grpc.pb.h"
+extern "C" {
+#include "radio_impl.h"
+}
+
+using grpc::Server;
+using grpc::ServerBuilder;
+using grpc::ServerContext;
+using grpc::ServerWriter;
+using grpc::Status;
+
+using automotivegradelinux::Radio;
+using automotivegradelinux::GetFrequencyRequest;
+using automotivegradelinux::GetFrequencyResponse;
+using automotivegradelinux::SetFrequencyRequest;
+using automotivegradelinux::SetFrequencyResponse;
+using automotivegradelinux::GetBandRequest;
+using automotivegradelinux::GetBandResponse;
+using automotivegradelinux::SetBandRequest;
+using automotivegradelinux::SetBandResponse;
+using automotivegradelinux::GetBandSupportedRequest;
+using automotivegradelinux::GetBandSupportedResponse;
+using automotivegradelinux::GetBandParametersRequest;
+using automotivegradelinux::GetBandParametersResponse;
+using automotivegradelinux::GetStereoModeRequest;
+using automotivegradelinux::GetStereoModeResponse;
+using automotivegradelinux::SetStereoModeRequest;
+using automotivegradelinux::SetStereoModeResponse;
+using automotivegradelinux::GetStereoModeRequest;
+using automotivegradelinux::StartRequest;
+using automotivegradelinux::StartResponse;
+using automotivegradelinux::StopRequest;
+using automotivegradelinux::StopResponse;
+using automotivegradelinux::ScanStartRequest;
+using automotivegradelinux::ScanStartResponse;
+using automotivegradelinux::ScanStopRequest;
+using automotivegradelinux::ScanStopResponse;
+using automotivegradelinux::GetRDSRequest;
+using automotivegradelinux::GetRDSResponse;
+using automotivegradelinux::GetQualityRequest;
+using automotivegradelinux::GetQualityResponse;
+using automotivegradelinux::SetAlternativeFrequencyRequest;
+using automotivegradelinux::SetAlternativeFrequencyResponse;
+using automotivegradelinux::StatusRequest;
+using automotivegradelinux::StatusResponse;
+
+
+class RadioImpl final : public Radio::Service
+{
+public:
+ explicit RadioImpl();
+
+ bool Detect();
+
+ Status GetFrequency(ServerContext* context, const GetFrequencyRequest* request, GetFrequencyResponse* response) override;
+ Status SetFrequency(ServerContext* context, const SetFrequencyRequest* request, SetFrequencyResponse* response) override;
+ Status GetBand(ServerContext* context, const GetBandRequest* request, GetBandResponse* response) override;
+ Status SetBand(ServerContext* context, const SetBandRequest* request, SetBandResponse* response) override;
+ Status GetBandSupported(ServerContext* context, const GetBandSupportedRequest* request, GetBandSupportedResponse* response) override;
+ Status GetBandParameters(ServerContext* context, const GetBandParametersRequest* request, GetBandParametersResponse* response) override;
+ Status GetStereoMode(ServerContext* context, const GetStereoModeRequest* request, GetStereoModeResponse* response) override;
+ Status SetStereoMode(ServerContext* context, const SetStereoModeRequest* request, SetStereoModeResponse* response) override;
+ Status Start(ServerContext* context, const StartRequest* request, StartResponse* response) override;
+ Status Stop(ServerContext* context, const StopRequest* request, StopResponse* response) override;
+ Status ScanStart(ServerContext* context, const ScanStartRequest* request, ScanStartResponse* response) override;
+ Status ScanStop(ServerContext* context, const ScanStopRequest* request, ScanStopResponse* response) override;
+ Status GetRDS(ServerContext* context, const GetRDSRequest* request, GetRDSResponse* response) override;
+ Status GetQuality(ServerContext* context, const GetQualityRequest* request, GetQualityResponse* response) override;
+ Status SetAlternativeFrequency(ServerContext* context, const SetAlternativeFrequencyRequest* request, SetAlternativeFrequencyResponse* response) override;
+ Status GetStatusEvents(ServerContext* context, const StatusRequest* request, ServerWriter<StatusResponse>* writer) override;
+
+ void Shutdown() { m_done = true; m_done_cv.notify_all(); }
+
+ void SendStatusResponse(StatusResponse &response);
+
+ static void FrequencyCallback(uint32_t frequency, void *data) {
+ if (data)
+ ((RadioImpl*) data)->HandleFrequencyEvent(frequency);
+ }
+
+ static void ScanCallback(uint32_t frequency, void *data) {
+ if (data)
+ ((RadioImpl*) data)->HandleScanEvent(frequency);
+ }
+
+ static void RDSCallback(void *rds_data, void *data) {
+ if (data)
+ ((RadioImpl*) data)->HandleRDSEvent(rds_data);
+ }
+
+private:
+ void HandleFrequencyEvent(uint32_t frequency);
+ void HandleScanEvent(uint32_t frequency);
+ void HandleRDSEvent(void *rds_data);
+
+ std::mutex m_clients_mutex;
+ std::list<std::pair<ServerContext*, ServerWriter<StatusResponse>*> > m_clients;
+
+ std::mutex m_done_mutex;
+ std::condition_variable m_done_cv;
+ bool m_done = false;
+
+ radio_impl_ops_t *m_radio_impl_ops = NULL;
+ bool m_playing = false;
+};
+
+#endif // RADIO_IMPL_H
diff --git a/src/convenience/convenience.c b/src/convenience/convenience.c
new file mode 100644
index 0000000..ae1e24b
--- /dev/null
+++ b/src/convenience/convenience.c
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2014 by Kyle Keen <keenerd@gmail.com>
+ *
+ * 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 2 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/>.
+ */
+
+/* a collection of user friendly tools
+ * todo: use strtol for more flexible int parsing
+ * */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifndef _WIN32
+#include <unistd.h>
+#else
+#include <windows.h>
+#include <fcntl.h>
+#include <io.h>
+#define _USE_MATH_DEFINES
+#endif
+
+#include <math.h>
+
+#include "rtl-sdr.h"
+
+double atofs(char *s)
+/* standard suffixes */
+{
+ char last;
+ size_t len;
+ double suff = 1.0;
+ len = strlen(s);
+ last = s[len-1];
+ s[len-1] = '\0';
+ switch (last) {
+ case 'g':
+ case 'G':
+ suff *= 1e3;
+ /*@fallthrough@*/
+ case 'm':
+ case 'M':
+ suff *= 1e3;
+ /*@fallthrough@*/
+ case 'k':
+ case 'K':
+ suff *= 1e3;
+ suff *= atof(s);
+ s[len-1] = last;
+ return suff;
+ }
+ s[len-1] = last;
+ return atof(s);
+}
+
+double atoft(char *s)
+/* time suffixes, returns seconds */
+{
+ char last;
+ size_t len;
+ double suff = 1.0;
+ len = strlen(s);
+ last = s[len-1];
+ s[len-1] = '\0';
+ switch (last) {
+ case 'h':
+ case 'H':
+ suff *= 60;
+ /*@fallthrough@*/
+ case 'm':
+ case 'M':
+ suff *= 60;
+ /*@fallthrough@*/
+ case 's':
+ case 'S':
+ suff *= atof(s);
+ s[len-1] = last;
+ return suff;
+ }
+ s[len-1] = last;
+ return atof(s);
+}
+
+double atofp(char *s)
+/* percent suffixes */
+{
+ char last;
+ size_t len;
+ double suff = 1.0;
+ len = strlen(s);
+ last = s[len-1];
+ s[len-1] = '\0';
+ switch (last) {
+ case '%':
+ suff *= 0.01;
+ suff *= atof(s);
+ s[len-1] = last;
+ return suff;
+ }
+ s[len-1] = last;
+ return atof(s);
+}
+
+int nearest_gain(rtlsdr_dev_t *dev, int target_gain)
+{
+ int i, r, err1, err2, count, nearest;
+ int* gains;
+ r = rtlsdr_set_tuner_gain_mode(dev, 1);
+ if (r < 0) {
+ fprintf(stderr, "WARNING: Failed to enable manual gain.\n");
+ return r;
+ }
+ count = rtlsdr_get_tuner_gains(dev, NULL);
+ if (count <= 0) {
+ return 0;
+ }
+ gains = malloc(sizeof(int) * count);
+ count = rtlsdr_get_tuner_gains(dev, gains);
+ nearest = gains[0];
+ for (i=0; i<count; i++) {
+ err1 = abs(target_gain - nearest);
+ err2 = abs(target_gain - gains[i]);
+ if (err2 < err1) {
+ nearest = gains[i];
+ }
+ }
+ free(gains);
+ return nearest;
+}
+
+int verbose_set_frequency(rtlsdr_dev_t *dev, uint32_t frequency)
+{
+ int r;
+ r = rtlsdr_set_center_freq(dev, frequency);
+ if (r < 0) {
+ fprintf(stderr, "WARNING: Failed to set center freq.\n");
+ } else {
+ fprintf(stderr, "Tuned to %u Hz.\n", frequency);
+ }
+ return r;
+}
+
+int verbose_set_sample_rate(rtlsdr_dev_t *dev, uint32_t samp_rate)
+{
+ int r;
+ r = rtlsdr_set_sample_rate(dev, samp_rate);
+ if (r < 0) {
+ fprintf(stderr, "WARNING: Failed to set sample rate.\n");
+ } else {
+ fprintf(stderr, "Sampling at %u S/s.\n", samp_rate);
+ }
+ return r;
+}
+
+int verbose_direct_sampling(rtlsdr_dev_t *dev, int on)
+{
+ int r;
+ r = rtlsdr_set_direct_sampling(dev, on);
+ if (r != 0) {
+ fprintf(stderr, "WARNING: Failed to set direct sampling mode.\n");
+ return r;
+ }
+ if (on == 0) {
+ fprintf(stderr, "Direct sampling mode disabled.\n");}
+ if (on == 1) {
+ fprintf(stderr, "Enabled direct sampling mode, input 1/I.\n");}
+ if (on == 2) {
+ fprintf(stderr, "Enabled direct sampling mode, input 2/Q.\n");}
+ return r;
+}
+
+int verbose_offset_tuning(rtlsdr_dev_t *dev)
+{
+ int r;
+ r = rtlsdr_set_offset_tuning(dev, 1);
+ if (r != 0) {
+ fprintf(stderr, "WARNING: Failed to set offset tuning.\n");
+ } else {
+ fprintf(stderr, "Offset tuning mode enabled.\n");
+ }
+ return r;
+}
+
+int verbose_auto_gain(rtlsdr_dev_t *dev)
+{
+ int r;
+ r = rtlsdr_set_tuner_gain_mode(dev, 0);
+ if (r != 0) {
+ fprintf(stderr, "WARNING: Failed to set tuner gain.\n");
+ } else {
+ fprintf(stderr, "Tuner gain set to automatic.\n");
+ }
+ return r;
+}
+
+int verbose_gain_set(rtlsdr_dev_t *dev, int gain)
+{
+ int r;
+ r = rtlsdr_set_tuner_gain_mode(dev, 1);
+ if (r < 0) {
+ fprintf(stderr, "WARNING: Failed to enable manual gain.\n");
+ return r;
+ }
+ r = rtlsdr_set_tuner_gain(dev, gain);
+ if (r != 0) {
+ fprintf(stderr, "WARNING: Failed to set tuner gain.\n");
+ } else {
+ fprintf(stderr, "Tuner gain set to %0.2f dB.\n", gain/10.0);
+ }
+ return r;
+}
+
+int verbose_ppm_set(rtlsdr_dev_t *dev, int ppm_error)
+{
+ int r;
+ if (ppm_error == 0) {
+ return 0;}
+ r = rtlsdr_set_freq_correction(dev, ppm_error);
+ if (r < 0) {
+ fprintf(stderr, "WARNING: Failed to set ppm error.\n");
+ } else {
+ fprintf(stderr, "Tuner error set to %i ppm.\n", ppm_error);
+ }
+ return r;
+}
+
+int verbose_reset_buffer(rtlsdr_dev_t *dev)
+{
+ int r;
+ r = rtlsdr_reset_buffer(dev);
+ if (r < 0) {
+ fprintf(stderr, "WARNING: Failed to reset buffers.\n");}
+ return r;
+}
+
+int verbose_device_search(char *s)
+{
+ int i, device_count, device;
+ ssize_t offset;
+ char *s2;
+ char vendor[256], product[256], serial[256];
+ device_count = rtlsdr_get_device_count();
+ if (!device_count) {
+ fprintf(stderr, "No supported devices found.\n");
+ return -1;
+ }
+ fprintf(stderr, "Found %d device(s):\n", device_count);
+ for (i = 0; i < device_count; i++) {
+ rtlsdr_get_device_usb_strings(i, vendor, product, serial);
+ fprintf(stderr, " %d: %s, %s, SN: %s\n", i, vendor, product, serial);
+ }
+ fprintf(stderr, "\n");
+ /* does string look like raw id number */
+ device = (int)strtol(s, &s2, 0);
+ if (s2[0] == '\0' && device >= 0 && device < device_count) {
+ fprintf(stderr, "Using device %d: %s\n",
+ device, rtlsdr_get_device_name((uint32_t)device));
+ return device;
+ }
+ /* does string exact match a serial */
+ for (i = 0; i < device_count; i++) {
+ rtlsdr_get_device_usb_strings(i, vendor, product, serial);
+ if (strcmp(s, serial) != 0) {
+ continue;}
+ device = i;
+ fprintf(stderr, "Using device %d: %s\n",
+ device, rtlsdr_get_device_name((uint32_t)device));
+ return device;
+ }
+ /* does string prefix match a serial */
+ for (i = 0; i < device_count; i++) {
+ rtlsdr_get_device_usb_strings(i, vendor, product, serial);
+ if (strncmp(s, serial, strlen(s)) != 0) {
+ continue;}
+ device = i;
+ fprintf(stderr, "Using device %d: %s\n",
+ device, rtlsdr_get_device_name((uint32_t)device));
+ return device;
+ }
+ /* does string suffix match a serial */
+ for (i = 0; i < device_count; i++) {
+ rtlsdr_get_device_usb_strings(i, vendor, product, serial);
+ offset = strlen(serial) - strlen(s);
+ if (offset < 0) {
+ continue;}
+ if (strncmp(s, serial+offset, strlen(s)) != 0) {
+ continue;}
+ device = i;
+ fprintf(stderr, "Using device %d: %s\n",
+ device, rtlsdr_get_device_name((uint32_t)device));
+ return device;
+ }
+ fprintf(stderr, "No matching devices found.\n");
+ return -1;
+}
+
+// vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab
diff --git a/src/convenience/convenience.h b/src/convenience/convenience.h
new file mode 100644
index 0000000..1faa2af
--- /dev/null
+++ b/src/convenience/convenience.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2014 by Kyle Keen <keenerd@gmail.com>
+ *
+ * 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 2 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/>.
+ */
+
+/* a collection of user friendly tools */
+
+/*!
+ * Convert standard suffixes (k, M, G) to double
+ *
+ * \param s a string to be parsed
+ * \return double
+ */
+
+double atofs(char *s);
+
+/*!
+ * Convert time suffixes (s, m, h) to double
+ *
+ * \param s a string to be parsed
+ * \return seconds as double
+ */
+
+double atoft(char *s);
+
+/*!
+ * Convert percent suffixe (%) to double
+ *
+ * \param s a string to be parsed
+ * \return double
+ */
+
+double atofp(char *s);
+
+/*!
+ * Find nearest supported gain
+ *
+ * \param dev the device handle given by rtlsdr_open()
+ * \param target_gain in tenths of a dB
+ * \return 0 on success
+ */
+
+int nearest_gain(rtlsdr_dev_t *dev, int target_gain);
+
+/*!
+ * Set device frequency and report status on stderr
+ *
+ * \param dev the device handle given by rtlsdr_open()
+ * \param frequency in Hz
+ * \return 0 on success
+ */
+
+int verbose_set_frequency(rtlsdr_dev_t *dev, uint32_t frequency);
+
+/*!
+ * Set device sample rate and report status on stderr
+ *
+ * \param dev the device handle given by rtlsdr_open()
+ * \param samp_rate in samples/second
+ * \return 0 on success
+ */
+
+int verbose_set_sample_rate(rtlsdr_dev_t *dev, uint32_t samp_rate);
+
+/*!
+ * Enable or disable the direct sampling mode and report status on stderr
+ *
+ * \param dev the device handle given by rtlsdr_open()
+ * \param on 0 means disabled, 1 I-ADC input enabled, 2 Q-ADC input enabled
+ * \return 0 on success
+ */
+
+int verbose_direct_sampling(rtlsdr_dev_t *dev, int on);
+
+/*!
+ * Enable offset tuning and report status on stderr
+ *
+ * \param dev the device handle given by rtlsdr_open()
+ * \return 0 on success
+ */
+
+int verbose_offset_tuning(rtlsdr_dev_t *dev);
+
+/*!
+ * Enable auto gain and report status on stderr
+ *
+ * \param dev the device handle given by rtlsdr_open()
+ * \return 0 on success
+ */
+
+int verbose_auto_gain(rtlsdr_dev_t *dev);
+
+/*!
+ * Set tuner gain and report status on stderr
+ *
+ * \param dev the device handle given by rtlsdr_open()
+ * \param gain in tenths of a dB
+ * \return 0 on success
+ */
+
+int verbose_gain_set(rtlsdr_dev_t *dev, int gain);
+
+/*!
+ * Set the frequency correction value for the device and report status on stderr.
+ *
+ * \param dev the device handle given by rtlsdr_open()
+ * \param ppm_error correction value in parts per million (ppm)
+ * \return 0 on success
+ */
+
+int verbose_ppm_set(rtlsdr_dev_t *dev, int ppm_error);
+
+/*!
+ * Reset buffer
+ *
+ * \param dev the device handle given by rtlsdr_open()
+ * \return 0 on success
+ */
+
+int verbose_reset_buffer(rtlsdr_dev_t *dev);
+
+/*!
+ * Find the closest matching device.
+ *
+ * \param s a string to be parsed
+ * \return dev_index int, -1 on error
+ */
+
+int verbose_device_search(char *s);
+
diff --git a/src/main-grpc.cc b/src/main-grpc.cc
new file mode 100644
index 0000000..29e01f1
--- /dev/null
+++ b/src/main-grpc.cc
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: Apache-2.0
+/*
+ * Copyright (C) 2023 Konsulko Group
+ */
+
+#include <thread>
+#include <chrono>
+#include <glib.h>
+#include <glib-unix.h>
+
+#include "RadioImpl.h"
+
+GMainLoop *main_loop = NULL;
+
+RadioImpl *g_service = NULL;
+
+static gboolean quit_cb(gpointer user_data)
+{
+ g_info("Quitting...");
+
+ if (main_loop)
+ g_idle_add(G_SOURCE_FUNC(g_main_loop_quit), main_loop);
+ else
+ exit(0);
+
+ return G_SOURCE_REMOVE;
+}
+
+void RunGrpcServer(std::shared_ptr<Server> &server)
+{
+ // Start server and wait for shutdown
+ server->Wait();
+}
+
+int main(int argc, char *argv[])
+{
+ main_loop = g_main_loop_new(NULL, FALSE);
+
+ grpc::EnableDefaultHealthCheckService(true);
+ grpc::reflection::InitProtoReflectionServerBuilderPlugin();
+ ServerBuilder builder;
+
+ // Listen on the given address without any authentication mechanism (for now)
+ std::string server_address("localhost:50053");
+ builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
+
+ // Register "service" as the instance through which we'll communicate with
+ // clients. In this case it corresponds to a *synchronous* service.
+ RadioImpl *service = new RadioImpl();
+ if (!service->Detect()) {
+ exit(1);
+ }
+ builder.RegisterService(service);
+
+ // Finally assemble the server.
+ std::shared_ptr<Server> server(builder.BuildAndStart());
+ if (!server) {
+ exit(1);
+ }
+ std::cout << "Server listening on " << server_address << std::endl;
+
+ g_unix_signal_add(SIGTERM, quit_cb, (gpointer) &server);
+ g_unix_signal_add(SIGINT, quit_cb, (gpointer) &server);
+
+ // Start gRPC API server on its own thread
+ std::thread grpc_thread(RunGrpcServer, std::ref(server));
+
+ g_main_loop_run(main_loop);
+
+ // Service implementation may have threads blocked from client streaming
+ // RPCs, make sure those exit.
+ service->Shutdown();
+
+ // Need to set a deadline to avoid blocking on clients doing streaming
+ // RPC reads
+ server->Shutdown(std::chrono::system_clock::now() + std::chrono::milliseconds(500));
+
+ grpc_thread.join();
+
+ g_main_loop_unref(main_loop);
+
+ return 0;
+}
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..6446066
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,81 @@
+#
+# Copyright (C) 2021 Collabora Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+cpp = meson.get_compiler('cpp')
+grpcpp_reflection_dep = cpp.find_library('grpc++_reflection')
+
+gstreamer_dep = dependency('gstreamer-1.0')
+
+radio_deps = [
+ dependency('gobject-2.0'),
+ dependency('gio-unix-2.0'),
+ gstreamer_dep,
+ dependency('protobuf'),
+ dependency('grpc'),
+ dependency('grpc++'),
+ grpcpp_reflection_dep
+]
+
+protoc = find_program('protoc')
+grpc_cpp = find_program('grpc_cpp_plugin')
+
+protoc_gen = generator(protoc, \
+ output : ['@BASENAME@.pb.cc', '@BASENAME@.pb.h'],
+ arguments : ['--proto_path=@CURRENT_SOURCE_DIR@/../protos',
+ '--cpp_out=@BUILD_DIR@',
+ '@INPUT@'])
+generated_protoc_sources = protoc_gen.process('../protos/radio.proto')
+
+grpc_gen = generator(protoc, \
+ output : ['@BASENAME@.grpc.pb.cc', '@BASENAME@.grpc.pb.h'],
+ arguments : ['--proto_path=@CURRENT_SOURCE_DIR@/../protos',
+ '--grpc_out=@BUILD_DIR@',
+ '--plugin=protoc-gen-grpc=' + grpc_cpp.path(),
+ '@INPUT@'])
+generated_grpc_sources = grpc_gen.process('../protos/radio.proto')
+
+# FIXME: debug radio_impl_tef665x.c compile issues and add
+executable (
+ 'agl-service-radio',
+ [
+ generated_protoc_sources,
+ generated_grpc_sources,
+ 'main-grpc.cc',
+ 'RadioImpl.cc',
+ 'radio_impl_null.c',
+ 'radio_impl_kingfisher.c',
+ 'radio_impl_rtlsdr.c'
+ ],
+ dependencies : radio_deps,
+ install : true,
+ install_dir : get_option('sbindir')
+)
+
+cc = meson.get_compiler('c')
+m_dep = cc.find_library('m', required : false)
+helper_deps = [ gstreamer_dep, m_dep, dependency('librtlsdr'), dependency('libusb-1.0') ]
+executable (
+ 'rtl_fm_helper',
+ [
+ 'rtl_fm_helper.c',
+ 'radio_output_gstreamer.c',
+ 'rtl_fm.c',
+ 'convenience/convenience.c'
+ ],
+ dependencies : helper_deps,
+ install : true,
+ install_dir : get_option('sbindir')
+)
diff --git a/src/radio-binding.c b/src/radio-binding.c
new file mode 100644
index 0000000..8b5559c
--- /dev/null
+++ b/src/radio-binding.c
@@ -0,0 +1,752 @@
+/*
+ * Copyright (C) 2017, 2019 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <json-c/json.h>
+#include <afb/afb-binding.h>
+
+#include "radio_impl.h"
+#include "radio_impl_null.h"
+#include "radio_impl_rtlsdr.h"
+#include "radio_impl_kingfisher.h"
+#include "radio_impl_tef665x.h"
+
+static radio_impl_ops_t *radio_impl_ops;
+
+static afb_event_t freq_event;
+static afb_event_t scan_event;
+static afb_event_t status_event;
+static afb_event_t rds_event;
+
+static bool playing;
+
+static const char *signalcomposer_events[] = {
+ "event.media.next",
+ "event.media.previous",
+ "event.media.mode",
+ NULL,
+};
+
+static void freq_callback(uint32_t frequency, void *data)
+{
+ json_object *jresp = json_object_new_object();
+ json_object *value = json_object_new_int((int) frequency);
+
+ json_object_object_add(jresp, "value", value);
+ afb_event_push(freq_event, json_object_get(jresp));
+}
+
+static void scan_callback(uint32_t frequency, void *data)
+{
+ json_object *jresp = json_object_new_object();
+ json_object *value = json_object_new_int((int) frequency);
+
+ json_object_object_add(jresp, "value", value);
+ afb_event_push(scan_event, json_object_get(jresp));
+}
+
+static void rds_callback(void *rds_data)
+{
+ //rds_data is a json object
+ afb_event_push(rds_event, json_object_get(rds_data));
+}
+
+/*
+ * Binding verb handlers
+ */
+
+/*
+ * @brief Get (and optionally set) frequency
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void frequency(afb_req_t request)
+{
+ json_object *ret_json;
+ const char *value = afb_req_value(request, "value");
+ uint32_t frequency;
+
+ if(value) {
+ char *p;
+ radio_band_t band;
+ uint32_t min_frequency;
+ uint32_t max_frequency;
+ uint32_t step;
+
+ frequency = (uint32_t) strtoul(value, &p, 10);
+ if(frequency && *p == '\0') {
+ band = radio_impl_ops->get_band();
+ min_frequency = radio_impl_ops->get_min_frequency(band);
+ max_frequency = radio_impl_ops->get_max_frequency(band);
+ step = radio_impl_ops->get_frequency_step(band);
+ if(frequency < min_frequency ||
+ frequency > max_frequency ||
+ (frequency - min_frequency) % step) {
+ afb_req_reply(request, NULL, "failed", "Invalid frequency");
+ return;
+ }
+ radio_impl_ops->set_frequency(frequency);
+ } else {
+ afb_req_reply(request, NULL, "failed", "Invalid frequency");
+ return;
+ }
+ }
+ ret_json = json_object_new_object();
+ frequency = radio_impl_ops->get_frequency();
+ json_object_object_add(ret_json, "frequency", json_object_new_int((int32_t) frequency));
+ afb_req_reply(request, ret_json, NULL, NULL);
+}
+
+/*
+ * @brief Get RDS information
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void rds(afb_req_t request)
+{
+ json_object *ret_json;
+ char * rds;
+
+ if (radio_impl_ops->get_rds_info == NULL) {
+ afb_req_reply(request, NULL, "failed", "not supported");
+ return;
+ }
+
+ ret_json = json_object_new_object();
+ rds = radio_impl_ops->get_rds_info();
+ json_object_object_add(ret_json, "rds", json_object_new_string(rds?rds:""));
+ free(rds);
+
+ afb_req_reply(request, ret_json, NULL, NULL);
+}
+
+/* @brief Get quality information
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void quality(afb_req_t request)
+{
+ json_object *ret_json;
+ station_quality_t *quality_data;
+
+ if (radio_impl_ops->get_quality_info == NULL) {
+ afb_req_reply(request, NULL, "failed", "Not supported");
+ return;
+ }
+
+ quality_data = radio_impl_ops->get_quality_info();
+ ret_json=json_object_new_object();
+ if(quality_data->af_update)
+ {
+ json_object_object_add(ret_json, "af_update", json_object_new_int((int) quality_data->af_update));
+ }
+ if(quality_data->time_stamp){
+ json_object_object_add(ret_json, "timestamp", json_object_new_int((int) quality_data->time_stamp));
+ }
+ if(quality_data->rssi)
+ {
+ json_object_object_add(ret_json, "rssi", json_object_new_int((int) quality_data->rssi));
+ }
+ if(quality_data->usn)
+ {
+ json_object_object_add(ret_json, "usn", json_object_new_int((int) quality_data->usn));
+ }
+ if(quality_data->bandw)
+ {
+ json_object_object_add(ret_json, "bandwidth", json_object_new_int((int) quality_data->bandw));
+ }
+ afb_req_reply(request, ret_json, NULL, NULL);
+ return;
+}
+
+/* @brief Check alternative frequency
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void alternative_frequency(afb_req_t request)
+{
+ json_object *ret_json;
+ uint32_t alternative;
+ const char *value;
+
+ if (radio_impl_ops->set_alternative_frequency == NULL) {
+ afb_req_reply(request, NULL, "failed", "Not supported");
+ return;
+ }
+
+ value = afb_req_value(request, "value");
+ if(value) {
+ char *p;
+ radio_band_t band;
+ uint32_t min_frequency;
+ uint32_t max_frequency;
+ uint32_t step;
+
+ alternative = (uint32_t) strtoul(value, &p, 10);
+ if(alternative && *p == '\0') {
+ band = radio_impl_ops->get_band();
+ min_frequency = radio_impl_ops->get_min_frequency(band);
+ max_frequency = radio_impl_ops->get_max_frequency(band);
+ step = radio_impl_ops->get_frequency_step(band);
+ if(alternative < min_frequency ||
+ alternative > max_frequency ||
+ (alternative - min_frequency) % step) {
+ afb_req_reply(request, NULL, "failed", "Invalid alternative frequency");
+ return;
+ }
+ radio_impl_ops->set_alternative_frequency(alternative);
+ ret_json = json_object_new_object();
+ json_object_object_add(ret_json, "alternative", json_object_new_int((int32_t) alternative));
+ afb_req_reply(request, ret_json, NULL, NULL);
+ } else {
+ afb_req_reply(request, NULL, "failed", "Invalid alternative frequency");
+ return;
+ }
+ }
+ else {
+ afb_req_reply(request, NULL, "failed", "Invalid alternative frequency");
+ return;
+ }
+}
+
+/*
+ * @brief Get (and optionally set) frequency band
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void band(afb_req_t request)
+{
+ json_object *ret_json;
+ const char *value = afb_req_value(request, "value");
+ int valid = 0;
+ radio_band_t band;
+ char band_name[4];
+
+ if(value) {
+ if(!strcasecmp(value, "AM")) {
+ band = BAND_AM;
+ valid = 1;
+ } else if(!strcasecmp(value, "FM")) {
+ band = BAND_FM;
+ valid = 1;
+ } else {
+ char *p;
+ band = strtoul(value, &p, 10);
+ if(p != value && *p == '\0') {
+ switch(band) {
+ case BAND_AM:
+ case BAND_FM:
+ valid = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if(valid) {
+ radio_impl_ops->set_band(band);
+ } else {
+ afb_req_reply(request, NULL, "failed", "Invalid band");
+ return;
+ }
+ }
+ ret_json = json_object_new_object();
+ band = radio_impl_ops->get_band();
+ sprintf(band_name, "%s", band == BAND_AM ? "AM" : "FM");
+ json_object_object_add(ret_json, "band", json_object_new_string(band_name));
+ afb_req_reply(request, ret_json, NULL, NULL);
+}
+
+/*
+ * @brief Check if band is supported
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void band_supported(afb_req_t request)
+{
+ json_object *ret_json;
+ const char *value = afb_req_value(request, "band");
+ int valid = 0;
+ radio_band_t band;
+
+ if(value) {
+ if(!strcasecmp(value, "AM")) {
+ band = BAND_AM;
+ valid = 1;
+ } else if(!strcasecmp(value, "FM")) {
+ band = BAND_FM;
+ valid = 1;
+ } else {
+ char *p;
+ band = strtoul(value, &p, 10);
+ if(p != value && *p == '\0') {
+ switch(band) {
+ case BAND_AM:
+ case BAND_FM:
+ valid = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ if(!valid) {
+ afb_req_reply(request, NULL, "failed", "Invalid band");
+ return;
+ }
+ ret_json = json_object_new_object();
+ json_object_object_add(ret_json,
+ "supported",
+ json_object_new_int(radio_impl_ops->band_supported(band)));
+ afb_req_reply(request, ret_json, NULL, NULL);
+}
+
+/*
+ * @brief Get frequency range for a band
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void frequency_range(afb_req_t request)
+{
+ json_object *ret_json;
+ const char *value = afb_req_value(request, "band");
+ int valid = 0;
+ radio_band_t band;
+ uint32_t min_frequency;
+ uint32_t max_frequency;
+
+ if(value) {
+ if(!strcasecmp(value, "AM")) {
+ band = BAND_AM;
+ valid = 1;
+ } else if(!strcasecmp(value, "FM")) {
+ band = BAND_FM;
+ valid = 1;
+ } else {
+ char *p;
+ band = strtoul(value, &p, 10);
+ if(p != value && *p == '\0') {
+ switch(band) {
+ case BAND_AM:
+ case BAND_FM:
+ valid = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ if(!valid) {
+ afb_req_reply(request, NULL, "failed", "Invalid band");
+ return;
+ }
+ ret_json = json_object_new_object();
+ min_frequency = radio_impl_ops->get_min_frequency(band);
+ max_frequency = radio_impl_ops->get_max_frequency(band);
+ json_object_object_add(ret_json, "min", json_object_new_int((int32_t) min_frequency));
+ json_object_object_add(ret_json, "max", json_object_new_int((int32_t) max_frequency));
+ afb_req_reply(request, ret_json, NULL, NULL);
+}
+
+/*
+ * @brief Get frequency step size (Hz) for a band
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void frequency_step(afb_req_t request)
+{
+ json_object *ret_json;
+ const char *value = afb_req_value(request, "band");
+ int valid = 0;
+ radio_band_t band;
+ uint32_t step;
+
+ if(value) {
+ if(!strcasecmp(value, "AM")) {
+ band = BAND_AM;
+ valid = 1;
+ } else if(!strcasecmp(value, "FM")) {
+ band = BAND_FM;
+ valid = 1;
+ } else {
+ char *p;
+ band = strtoul(value, &p, 10);
+ if(p != value && *p == '\0') {
+ switch(band) {
+ case BAND_AM:
+ case BAND_FM:
+ valid = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ if(!valid) {
+ afb_req_reply(request, NULL, "failed", "Invalid band");
+ return;
+ }
+ ret_json = json_object_new_object();
+ step = radio_impl_ops->get_frequency_step(band);
+ json_object_object_add(ret_json, "step", json_object_new_int((int32_t) step));
+ afb_req_reply(request, ret_json, NULL, NULL);
+}
+
+/*
+ * @brief Start radio playback
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void start(afb_req_t request)
+{
+ radio_impl_ops->set_output(NULL);
+ radio_impl_ops->start();
+ playing = true;
+ afb_req_reply(request, NULL, NULL, NULL);
+
+ json_object *jresp = json_object_new_object();
+ json_object *value = json_object_new_string("playing");
+ json_object_object_add(jresp, "value", value);
+ afb_event_push(status_event, json_object_get(jresp));
+}
+
+/*
+ * @brief Stop radio playback
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void stop(afb_req_t request)
+{
+ radio_impl_ops->stop();
+ playing = false;
+ afb_req_reply(request, NULL, NULL, NULL);
+
+ json_object *jresp = json_object_new_object();
+ json_object *value = json_object_new_string("stopped");
+ json_object_object_add(jresp, "value", value);
+ afb_event_push(status_event, json_object_get(jresp));
+}
+
+/*
+ * @brief Scan for a station in the specified direction
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void scan_start(afb_req_t request)
+{
+ const char *value = afb_req_value(request, "direction");
+ int valid = 0;
+ radio_scan_direction_t direction;
+
+ if(value) {
+ if(!strcasecmp(value, "forward")) {
+ direction = SCAN_FORWARD;
+ valid = 1;
+ } else if(!strcasecmp(value, "backward")) {
+ direction = SCAN_BACKWARD;
+ valid = 1;
+ } else {
+ char *p;
+ direction = strtoul(value, &p, 10);
+ if(p != value && *p == '\0') {
+ switch(direction) {
+ case SCAN_FORWARD:
+ case SCAN_BACKWARD:
+ valid = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ if(!valid) {
+ afb_req_reply(request, NULL, "failed", "Invalid direction");
+ return;
+ }
+ radio_impl_ops->scan_start(direction, scan_callback, NULL);
+ afb_req_reply(request, NULL, NULL, NULL);
+}
+
+/*
+ * @brief Stop station scan
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void scan_stop(afb_req_t request)
+{
+ radio_impl_ops->scan_stop();
+ afb_req_reply(request, NULL, NULL, NULL);
+}
+
+/*
+ * @brief Get (and optionally set) stereo mode
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void stereo_mode(afb_req_t request)
+{
+ json_object *ret_json;
+ const char *value = afb_req_value(request, "value");
+ int valid = 0;
+ radio_stereo_mode_t mode;
+
+ if(value) {
+ if(!strcasecmp(value, "mono")) {
+ mode = MONO;
+ valid = 1;
+ } else if(!strcasecmp(value, "stereo")) {
+ mode = STEREO;
+ valid = 1;
+ } else {
+ char *p;
+ mode = strtoul(value, &p, 10);
+ if(p != value && *p == '\0') {
+ switch(mode) {
+ case MONO:
+ case STEREO:
+ valid = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if(valid) {
+ radio_impl_ops->set_stereo_mode(mode);
+ } else {
+ afb_req_reply(request, NULL, "failed", "Invalid mode");
+ return;
+ }
+ }
+ ret_json = json_object_new_object();
+ mode = radio_impl_ops->get_stereo_mode();
+
+ json_object_object_add(ret_json, "mode", json_object_new_string(mode == MONO ? "mono" : "stereo"));
+ afb_req_reply(request, ret_json, NULL, NULL);
+}
+
+/*
+ * @brief Subscribe for an event
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void subscribe(afb_req_t request)
+{
+ const char *value = afb_req_value(request, "value");
+ if(value) {
+ if(!strcasecmp(value, "frequency")) {
+ afb_req_subscribe(request, freq_event);
+ } else if(!strcasecmp(value, "station_found")) {
+ afb_req_subscribe(request, scan_event);
+ } else if(!strcasecmp(value, "status")) {
+ afb_req_subscribe(request, status_event);
+ } else if(!strcasecmp(value, "rds")) {
+ afb_req_subscribe(request, rds_event);
+ }
+ else {
+ afb_req_reply(request, NULL, "failed", "Invalid event");
+ return;
+ }
+ }
+ afb_req_reply(request, NULL, NULL, NULL);
+}
+
+/*
+ * @brief Unsubscribe for an event
+ *
+ * @param afb_req_t : an afb request structure
+ *
+ */
+static void unsubscribe(afb_req_t request)
+{
+ const char *value = afb_req_value(request, "value");
+ if(value) {
+ if(!strcasecmp(value, "frequency")) {
+ afb_req_unsubscribe(request, freq_event);
+ } else if(!strcasecmp(value, "station_found")) {
+ afb_req_unsubscribe(request, scan_event);
+ } else if(!strcasecmp(value, "status")) {
+ afb_req_unsubscribe(request, status_event);
+
+ } else if(!strcasecmp(value, "rds")) {
+ afb_req_unsubscribe(request, rds_event);
+
+ } else {
+ afb_req_reply(request, NULL, "failed", "Invalid event");
+ return;
+ }
+ }
+ afb_req_reply(request, NULL, NULL, NULL);
+}
+
+static const afb_verb_t verbs[]= {
+ { .verb = "frequency", .session = AFB_SESSION_NONE, .callback = frequency, .info = "Get/Set frequency" },
+ { .verb = "band", .session = AFB_SESSION_NONE, .callback = band, .info = "Get/Set band" },
+ { .verb = "rds", .session = AFB_SESSION_NONE, .callback = rds, .info = "Get RDS information" },
+ { .verb = "quality", .session = AFB_SESSION_NONE, .callback = quality, .info = "Get station quality information" },
+ { .verb = "alternative_frequency", .session = AFB_SESSION_NONE, .callback = alternative_frequency, .info = "Check an alternative frequency" },
+ { .verb = "band_supported", .session = AFB_SESSION_NONE, .callback = band_supported, .info = "Check band support" },
+ { .verb = "frequency_range", .session = AFB_SESSION_NONE, .callback = frequency_range, .info = "Get frequency range" },
+ { .verb = "frequency_step", .session = AFB_SESSION_NONE, .callback = frequency_step, .info = "Get frequency step" },
+ { .verb = "start", .session = AFB_SESSION_NONE, .callback = start, .info = "Start radio playback" },
+ { .verb = "stop", .session = AFB_SESSION_NONE, .callback = stop, .info = "Stop radio playback" },
+ { .verb = "scan_start", .session = AFB_SESSION_NONE, .callback = scan_start, .info = "Start station scan" },
+ { .verb = "scan_stop", .session = AFB_SESSION_NONE, .callback = scan_stop, .info = "Stop station scan" },
+ { .verb = "stereo_mode", .session = AFB_SESSION_NONE, .callback = stereo_mode, .info = "Get/Set stereo_mode" },
+ { .verb = "subscribe", .session = AFB_SESSION_NONE, .callback = subscribe, .info = "Subscribe for an event" },
+ { .verb = "unsubscribe", .session = AFB_SESSION_NONE, .callback = unsubscribe, .info = "Unsubscribe for an event" },
+ { }
+};
+
+static void onevent(afb_api_t api, const char *event, struct json_object *object)
+{
+ json_object *tmp = NULL;
+ const char *uid;
+ const char *value;
+
+ json_object_object_get_ex(object, "uid", &tmp);
+ if (tmp == NULL)
+ return;
+
+ uid = json_object_get_string(tmp);
+ if (strncmp(uid, "event.media.", 12))
+ return;
+
+ if (!playing ||
+ (radio_impl_ops->get_corking_state &&
+ radio_impl_ops->get_corking_state())) {
+ return;
+ }
+
+ json_object_object_get_ex(object, "value", &tmp);
+ if (tmp == NULL)
+ return;
+
+ value = json_object_get_string(tmp);
+ if (strncmp(value, "true", 4))
+ return;
+
+ if (!strcmp(uid, "event.media.next")) {
+ radio_impl_ops->scan_start(SCAN_FORWARD, scan_callback, NULL);
+ } else if (!strcmp(uid, "event.media.previous")) {
+ radio_impl_ops->scan_start(SCAN_BACKWARD, scan_callback, NULL);
+ } else if (!strcmp(uid, "event.media.mode")) {
+ // Do nothing ATM
+ } else {
+ AFB_WARNING("Unhandled signal-composer uid '%s'", uid);
+ }
+}
+
+static int init(afb_api_t api)
+{
+ // Probe for radio backends
+ radio_impl_ops = &rtlsdr_impl_ops;
+ int rc = radio_impl_ops->probe();
+ if(rc != 0) {
+ // Look for Kingfisher Si4689
+ radio_impl_ops = &kf_impl_ops;
+ rc = radio_impl_ops->probe();
+ }
+ if(rc != 0) {
+ radio_impl_ops = &tef665x_impl_ops;
+ rc = radio_impl_ops->probe();
+ }
+ if (rc != 0) {
+ radio_impl_ops = &null_impl_ops;
+ rc = radio_impl_ops->probe();
+ }
+ if (rc != 0) {
+ // We don't expect the null implementation to fail probe, but just in case...
+ AFB_API_ERROR(afbBindingV3root, "No radio device found, exiting");
+ return rc;
+ }
+ // Try to initialize detected backend
+ rc = radio_impl_ops->init();
+ if(rc < 0) {
+ AFB_API_ERROR(afbBindingV3root,
+ "%s initialization failed\n",
+ radio_impl_ops->name);
+ return rc;
+ }
+ AFB_API_NOTICE(afbBindingV3root, "%s found\n", radio_impl_ops->name);
+ radio_impl_ops->set_frequency_callback(freq_callback, NULL);
+ radio_impl_ops->set_frequency_callback(freq_callback, NULL);
+ if(radio_impl_ops->set_rds_callback) {
+ radio_impl_ops->set_rds_callback(rds_callback);
+ }
+
+ rc = afb_daemon_require_api("signal-composer", 1);
+ if (rc) {
+ AFB_WARNING("unable to initialize signal-composer binding");
+ } else {
+ const char **tmp = signalcomposer_events;
+ json_object *args = json_object_new_object();
+ json_object *signals = json_object_new_array();
+
+ while (*tmp) {
+ json_object_array_add(signals, json_object_new_string(*tmp++));
+ }
+ json_object_object_add(args, "signal", signals);
+ if(json_object_array_length(signals)) {
+ afb_api_call_sync(api, "signal-composer", "subscribe",
+ args, NULL, NULL, NULL);
+ } else {
+ json_object_put(args);
+ }
+ }
+
+ // Initialize event structures
+ freq_event = afb_daemon_make_event("frequency");
+ scan_event = afb_daemon_make_event("station_found");
+ status_event = afb_daemon_make_event("status");
+ rds_event = afb_daemon_make_event("rds");
+ return 0;
+}
+
+const afb_binding_t afbBindingExport = {
+ .info = "radio service",
+ .api = "radio",
+ .specification = "Radio API",
+ .verbs = verbs,
+ .onevent = onevent,
+ .init = init,
+};
diff --git a/src/radio_impl.h b/src/radio_impl.h
new file mode 100644
index 0000000..96909f5
--- /dev/null
+++ b/src/radio_impl.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _RADIO_IMPL_H
+#define _RADIO_IMPL_H
+
+#include <stdint.h>
+
+typedef enum {
+ RADIO_BAND_UNSPECIFIED = 0,
+ RADIO_BAND_AM,
+ RADIO_BAND_FM
+} radio_band_t;
+
+typedef enum {
+ RADIO_SCAN_UNSPECIFIED = 0,
+ RADIO_SCAN_FORWARD,
+ RADIO_SCAN_BACKWARD
+} radio_scan_direction_t;
+
+typedef void (*radio_scan_callback_t)(uint32_t frequency, void *data);
+
+typedef void (*radio_freq_callback_t)(uint32_t frequency, void *data);
+
+typedef void (*radio_rds_callback_t)(void *rds_data, void *dat);
+
+typedef enum {
+ RADIO_MODE_UNSPECIFIED = 0,
+ RADIO_MODE_MONO,
+ RADIO_MODE_STEREO
+} radio_stereo_mode_t;
+
+/*
+ * AF_update
+ * true if quality belongs to an alternative frequency
+ *
+ * time_stamp
+ * if time_stamp is zero, quality data won't be valid
+ * reliability depending on time stamp
+ * it takes some time after tuning to get valid quality data
+ *
+ * rssi
+ * (signed)
+ * level detector result(RF input level)
+ *
+ * usn
+ * FM ultrasonic noise detector (relative usn detector result)
+ *
+ * bandwidth
+ * IF bandwidth
+ */
+typedef struct
+{
+ bool af_update;
+ uint16_t time_stamp;
+ int16_t rssi;
+ uint16_t usn;
+ uint16_t bandw;
+} station_quality_t;
+
+typedef struct {
+ char *name;
+
+ int (*probe)(void);
+
+ /* NOTE: init should return -1 if called before probe has been called and returned success */
+ int (*init)(void);
+
+ void (*set_output)(const char *output);
+
+ uint32_t (*get_frequency)(void);
+
+ void (*set_frequency)(uint32_t frequency);
+
+ void (*set_frequency_callback)(radio_freq_callback_t callback,
+ void *data);
+
+ void (*set_rds_callback)(radio_rds_callback_t callback);
+
+ radio_band_t (*get_band)(void);
+
+ void (*set_band)(radio_band_t band);
+
+ int (*band_supported)(radio_band_t band);
+
+ uint32_t (*get_min_frequency)(radio_band_t band);
+
+ uint32_t (*get_max_frequency)(radio_band_t band);
+
+ uint32_t (*get_frequency_step)(radio_band_t band);
+
+ bool (*get_corking_state)(void);
+
+ void (*start)(void);
+
+ void (*stop)(void);
+
+ void (*scan_start)(radio_scan_direction_t direction,
+ radio_scan_callback_t callback,
+ void *data);
+
+ void (*scan_stop)(void);
+
+ radio_stereo_mode_t (*get_stereo_mode)(void);
+
+ void (*set_stereo_mode)(radio_stereo_mode_t mode);
+
+ char * (*get_rds_info)(void);
+
+ station_quality_t * (*get_quality_info)(void);
+
+ void (*set_alternative_frequency)(uint32_t frequency);
+} radio_impl_ops_t;
+
+#endif /* _RADIO_IMPL_H */
diff --git a/src/radio_impl_kingfisher.c b/src/radio_impl_kingfisher.c
new file mode 100644
index 0000000..a0b8449
--- /dev/null
+++ b/src/radio_impl_kingfisher.c
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2017-2019 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <glib.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <gst/gst.h>
+
+#include "radio_impl.h"
+
+#define SI_NODE "/sys/firmware/devicetree/base/si468x@0/compatible"
+#define SI_CTL "/usr/bin/si_ctl"
+#define SI_CTL_CMDLINE_MAXLEN 128
+#define SI_CTL_OUTPUT_MAXLEN 128
+
+#define GST_PIPELINE_LEN 256
+
+// Flag to enable using GST_STATE_READY instead of GST_STATE_PAUSED to trigger
+// Wireplumber policy mechanism. Hopefully temporary.
+#define WIREPLUMBER_WORKAROUND
+
+// Structure to describe FM band plans, all values in Hz.
+typedef struct {
+ char *name;
+ uint32_t min;
+ uint32_t max;
+ uint32_t step;
+} fm_band_plan_t;
+
+static fm_band_plan_t known_fm_band_plans[5] = {
+ { .name = "US", .min = 87900000, .max = 107900000, .step = 200000 },
+ { .name = "JP", .min = 76000000, .max = 95000000, .step = 100000 },
+ { .name = "EU", .min = 87500000, .max = 108000000, .step = 50000 },
+ { .name = "ITU-1", .min = 87500000, .max = 108000000, .step = 50000 },
+ { .name = "ITU-2", .min = 87900000, .max = 107900000, .step = 50000 }
+};
+
+static unsigned int bandplan = 0;
+static bool corking;
+static bool present;
+static bool initialized;
+static uint32_t current_frequency;
+static int scan_valid_snr_threshold = 128;
+static int scan_valid_rssi_threshold = 128;
+static bool scanning;
+
+// stream state
+static GstElement *pipeline;
+static bool running;
+
+static void (*freq_callback)(uint32_t, void*);
+static void *freq_callback_data;
+
+static uint32_t kf_get_min_frequency(radio_band_t band);
+static void kf_scan_stop(void);
+
+static gboolean handle_message(GstBus *bus, GstMessage *msg, __attribute__((unused)) void *ptr)
+{
+ GstState state;
+
+ if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_REQUEST_STATE) {
+ gst_message_parse_request_state(msg, &state);
+
+ if (state == GST_STATE_PAUSED) {
+ corking = true;
+
+ // NOTE: Explicitly using PAUSED here, this case currently
+ // is separate from the general PAUSED/READY issue wrt
+ // Wireplumber policy.
+ gst_element_set_state(pipeline, GST_STATE_PAUSED);
+ } else if (state == GST_STATE_PLAYING) {
+ corking = false;
+
+ gst_element_set_state(pipeline, GST_STATE_PLAYING);
+ }
+ }
+
+ return TRUE;
+}
+
+static void *gstreamer_loop_thread(void *ptr)
+{
+ g_main_loop_run(g_main_loop_new(NULL, FALSE));
+ return NULL;
+}
+
+static int kf_probe(void)
+{
+ struct stat statbuf;
+
+ if(present)
+ return 0;
+
+ // Check for Kingfisher SI468x devicetree node
+ if(stat(SI_NODE, &statbuf) != 0)
+ return -1;
+
+ // Check for Cogent's si_ctl utility
+ if(stat(SI_CTL, &statbuf) != 0)
+ return -1;
+
+ present = true;
+ return 0;
+}
+
+static int kf_init(void)
+{
+ GKeyFile* conf_file;
+ bool conf_file_present = false;
+ char *value_str;
+ char cmd[SI_CTL_CMDLINE_MAXLEN];
+ int rc;
+ char gst_pipeline_str[GST_PIPELINE_LEN];
+ pthread_t thread_id;
+
+ if(!present)
+ return -1;
+
+ if(initialized)
+ return 0;
+
+ // Load settings from configuration file if it exists
+ conf_file = g_key_file_new();
+ if(conf_file &&
+ g_key_file_load_from_dirs(conf_file,
+ "AGL.conf",
+ (const gchar**) g_get_system_config_dirs(),
+ NULL,
+ G_KEY_FILE_KEEP_COMMENTS,
+ NULL) == TRUE) {
+ conf_file_present = true;
+
+ // Set band plan if it is specified
+ value_str = g_key_file_get_string(conf_file,
+ "radio",
+ "fmbandplan",
+ NULL);
+ if(value_str) {
+ unsigned int i;
+ for(i = 0;
+ i < sizeof(known_fm_band_plans) / sizeof(fm_band_plan_t);
+ i++) {
+ if(!strcasecmp(value_str, known_fm_band_plans[i].name)) {
+ bandplan = i;
+ break;
+ }
+ }
+ }
+ }
+
+ if(conf_file_present) {
+ GError *error = NULL;
+ int n;
+
+ // Allow over-riding scanning parameters just in case a demo
+ // setup needs to do so to work reliably.
+ n = g_key_file_get_integer(conf_file,
+ "radio",
+ "scan_valid_snr_threshold",
+ &error);
+ if(!error) {
+ printf("Scan valid SNR level set to %d", n);
+ scan_valid_snr_threshold = n;
+ }
+
+ error = NULL;
+ n = g_key_file_get_integer(conf_file,
+ "radio",
+ "scan_valid_rssi_threshold",
+ &error);
+ if(!error) {
+ printf("Scan valid SNR level set to %d", n);
+ scan_valid_rssi_threshold = n;
+ }
+
+ g_key_file_free(conf_file);
+ }
+
+ printf("Using FM Bandplan: %s", known_fm_band_plans[bandplan].name);
+ current_frequency = kf_get_min_frequency(RADIO_BAND_FM);
+ snprintf(cmd,
+ sizeof(cmd),
+ "%s /dev/i2c-12 0x65 -b fm -p %s -t %d -u %d -c %d",
+ SI_CTL,
+ known_fm_band_plans[bandplan].name,
+ scan_valid_snr_threshold,
+ scan_valid_rssi_threshold,
+ current_frequency / 1000);
+ rc = system(cmd);
+ if(rc != 0) {
+ fprintf(stderr, "%s failed, rc = %d", SI_CTL, rc);
+ return -1;
+ }
+
+ // Initialize GStreamer
+ gst_init(NULL, NULL);
+
+ // Use PipeWire output
+ rc = snprintf(gst_pipeline_str,
+ GST_PIPELINE_LEN,
+ "pipewiresrc stream-properties=\"p,node.target=alsa:pcm:radio:0:capture\" ! "
+ "audio/x-raw,format=F32LE,channels=2 ! "
+ "pipewiresink stream-properties=\"p,media.role=Multimedia\"");
+ if(rc >= GST_PIPELINE_LEN) {
+ fprintf(stderr, "pipeline string too long");
+ return -1;
+ }
+ pipeline = gst_parse_launch(gst_pipeline_str, NULL);
+ if(!pipeline) {
+ fprintf(stderr, "pipeline construction failed!");
+ return -1;
+ }
+
+ // Start pipeline in paused state
+#ifdef WIREPLUMBER_WORKAROUND
+ gst_element_set_state(pipeline, GST_STATE_READY);
+#else
+ gst_element_set_state(pipeline, GST_STATE_PAUSED);
+#endif
+
+ gst_bus_add_watch(gst_element_get_bus(pipeline), (GstBusFunc) handle_message, NULL);
+
+ rc = pthread_create(&thread_id, NULL, gstreamer_loop_thread, NULL);
+ if(rc != 0)
+ return rc;
+
+ initialized = true;
+ return 0;
+}
+
+static void kf_set_output(const char *output)
+{
+}
+
+static uint32_t kf_get_frequency(void)
+{
+ return current_frequency;
+}
+
+static void kf_set_frequency(uint32_t frequency)
+{
+ char cmd[SI_CTL_CMDLINE_MAXLEN];
+ int rc;
+
+ if(!initialized)
+ return;
+
+ if(scanning)
+ return;
+
+ if(frequency < known_fm_band_plans[bandplan].min ||
+ frequency > known_fm_band_plans[bandplan].max)
+ return;
+
+ kf_scan_stop();
+ snprintf(cmd, sizeof(cmd), "%s /dev/i2c-12 0x65 -c %d", SI_CTL, frequency / 1000);
+ rc = system(cmd);
+ if(rc == 0)
+ current_frequency = frequency;
+
+ if(freq_callback)
+ freq_callback(current_frequency, freq_callback_data);
+}
+
+static void kf_set_frequency_callback(radio_freq_callback_t callback,
+ void *data)
+{
+ freq_callback = callback;
+ freq_callback_data = data;
+}
+
+static char * kf_get_rds_info(void) {
+ char cmd[SI_CTL_CMDLINE_MAXLEN];
+ char line[SI_CTL_OUTPUT_MAXLEN];
+ char * rds = NULL;
+ FILE *fp;
+
+ if (scanning)
+ goto done;
+
+ snprintf(cmd, sizeof(cmd), "%s /dev/i2c-12 0x65 -m", SI_CTL);
+ fp = popen(cmd, "r");
+ if(fp == NULL) {
+ fprintf(stderr, "Could not run: %s!\n", cmd);
+ goto done;
+ }
+ /* Look for "Name:" in output */
+ while (fgets(line, sizeof(line), fp) != NULL) {
+
+ char* nS = strstr(line, "Name:");
+ char * end;
+ if (!nS)
+ continue;
+
+ end = nS+strlen("Name:");
+ /* remove the trailing '\n' */
+ end[strlen(end)-1] = '\0';
+
+ rds = strdup(end);
+ break;
+ }
+
+ /* Make sure si_ctl has finished */
+ pclose(fp);
+
+done:
+ return rds;
+}
+
+static radio_band_t kf_get_band(void)
+{
+ return RADIO_BAND_FM;
+}
+
+static void kf_set_band(radio_band_t band)
+{
+ // We only support FM, so do nothing
+}
+
+static int kf_band_supported(radio_band_t band)
+{
+ if(band == RADIO_BAND_FM)
+ return 1;
+ return 0;
+}
+
+static uint32_t kf_get_min_frequency(radio_band_t band)
+{
+ return known_fm_band_plans[bandplan].min;
+}
+
+static uint32_t kf_get_max_frequency(radio_band_t band)
+{
+ return known_fm_band_plans[bandplan].max;
+}
+
+static uint32_t kf_get_frequency_step(radio_band_t band)
+{
+ uint32_t ret = 0;
+
+ switch (band) {
+ case RADIO_BAND_AM:
+ ret = 1000; // 1 kHz
+ break;
+ case RADIO_BAND_FM:
+ ret = known_fm_band_plans[bandplan].step;
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static bool kf_get_corking_state(void)
+{
+ return corking;
+}
+
+static void kf_start(void)
+{
+ if(!initialized)
+ return;
+
+ if(!running || corking) {
+ // Start pipeline
+ gst_element_set_state(pipeline, GST_STATE_PLAYING);
+ running = true;
+ corking = false;
+ }
+}
+
+static void kf_stop(void)
+{
+ GstEvent *event;
+
+ if(initialized && running) {
+ // Stop pipeline
+ running = false;
+
+#ifdef WIREPLUMBER_WORKAROUND
+ // NOTE: Using NULL here instead of READY, as it seems to trigger
+ // some odd behavior in the pipeline; alsasrc does not seem to
+ // stop, and things get hung up on restart as there are a bunch
+ // of "old" samples that seemingly confuse pipewiresink. Going
+ // to NULL state seems to tear down things enough to avoid
+ // whatever happens.
+ gst_element_set_state(pipeline, GST_STATE_NULL);
+#else
+ gst_element_set_state(pipeline, GST_STATE_PAUSED);
+#endif
+ corking = false;
+
+#ifndef WIREPLUMBER_WORKAROUND
+ // Flush pipeline
+ // This seems required to avoid stutters on starts after a stop
+ event = gst_event_new_flush_start();
+ gst_element_send_event(GST_ELEMENT(pipeline), event);
+ event = gst_event_new_flush_stop(TRUE);
+ gst_element_send_event(GST_ELEMENT(pipeline), event);
+#endif
+ }
+}
+
+static void kf_scan_start(radio_scan_direction_t direction,
+ radio_scan_callback_t callback,
+ void *data)
+{
+ int rc;
+ char cmd[SI_CTL_CMDLINE_MAXLEN];
+ char line[SI_CTL_OUTPUT_MAXLEN];
+ uint32_t new_frequency = 0;
+ FILE *fp;
+
+ if(!initialized)
+ return;
+
+ if(!running || scanning)
+ return;
+
+ scanning = true;
+ snprintf(cmd,
+ SI_CTL_CMDLINE_MAXLEN,
+ "%s /dev/i2c-12 0x65 -l %s",
+ SI_CTL, direction == RADIO_SCAN_FORWARD ? "up" : "down");
+ fp = popen(cmd, "r");
+ if(fp == NULL) {
+ fprintf(stderr, "Could not run: %s!", cmd);
+ return;
+ }
+ // Look for "Frequency:" in output
+ while(fgets(line, SI_CTL_OUTPUT_MAXLEN, fp) != NULL) {
+ if(strncmp("Frequency:", line, 10) == 0) {
+ new_frequency = atoi(line + 10);
+ break;
+ }
+ }
+
+ // Make sure si_ctl has finished
+ rc = pclose(fp);
+ if(rc != 0) {
+ // Make sure we reset to original frequency, the Si4689 seems
+ // to auto-mute sometimes on failed scans, this hopefully works
+ // around that.
+ new_frequency = 0;
+ }
+
+ if(new_frequency) {
+ current_frequency = new_frequency * 1000;
+
+ // Push up the new frequency
+ // This is more efficient than calling kf_set_frequency and calling
+ // out to si_ctl again.
+ if(freq_callback)
+ freq_callback(current_frequency, freq_callback_data);
+ } else {
+ // Assume no station found, go back to starting frequency
+ kf_set_frequency(current_frequency);
+ }
+
+ // Push up scan state
+ if(callback)
+ callback(current_frequency, data);
+
+ scanning = false;
+}
+
+static void kf_scan_stop(void)
+{
+ // ATM, it's not straightforward to stop a scan since we're using the si_ctl utility...
+}
+
+static radio_stereo_mode_t kf_get_stereo_mode(void)
+{
+ return RADIO_MODE_STEREO;
+}
+
+static void kf_set_stereo_mode(radio_stereo_mode_t mode)
+{
+ // We only support stereo, so do nothing
+}
+
+radio_impl_ops_t kf_impl_ops = {
+ .name = "Kingfisher Si4689",
+ .probe = kf_probe,
+ .init = kf_init,
+ .set_output = kf_set_output,
+ .get_frequency = kf_get_frequency,
+ .set_frequency = kf_set_frequency,
+ .set_frequency_callback = kf_set_frequency_callback,
+ .get_band = kf_get_band,
+ .set_band = kf_set_band,
+ .band_supported = kf_band_supported,
+ .get_min_frequency = kf_get_min_frequency,
+ .get_max_frequency = kf_get_max_frequency,
+ .get_frequency_step = kf_get_frequency_step,
+ .get_corking_state = kf_get_corking_state,
+ .start = kf_start,
+ .stop = kf_stop,
+ .scan_start = kf_scan_start,
+ .scan_stop = kf_scan_stop,
+ .get_stereo_mode = kf_get_stereo_mode,
+ .set_stereo_mode = kf_set_stereo_mode,
+ .get_rds_info = kf_get_rds_info
+};
diff --git a/src/radio_impl_kingfisher.h b/src/radio_impl_kingfisher.h
new file mode 100644
index 0000000..5d4f064
--- /dev/null
+++ b/src/radio_impl_kingfisher.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _RADIO_IMPL_KINGFISHER_H
+#define _RADIO_IMPL_KINGFISHER_H
+
+#include "radio_impl.h"
+
+extern radio_impl_ops_t kf_impl_ops;
+
+#endif /* _RADIO_IMPL_KINGFISHER_H */
+
diff --git a/src/radio_impl_null.c b/src/radio_impl_null.c
new file mode 100644
index 0000000..60dbab2
--- /dev/null
+++ b/src/radio_impl_null.c
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2017,2018,2020 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * NOTE: Some locking around frequency and scanning state may be
+ * required if this null implementation ever needs to be used
+ * beyond basic functionality testing.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <glib.h>
+
+#include "radio_impl.h"
+
+// Structure to describe FM band plans, all values in Hz.
+typedef struct {
+ char *name;
+ uint32_t min;
+ uint32_t max;
+ uint32_t step;
+} fm_band_plan_t;
+
+static fm_band_plan_t known_fm_band_plans[5] = {
+ { .name = "US", .min = 87900000, .max = 107900000, .step = 200000 },
+ { .name = "JP", .min = 76000000, .max = 95000000, .step = 100000 },
+ { .name = "EU", .min = 87500000, .max = 108000000, .step = 50000 },
+ { .name = "ITU-1", .min = 87500000, .max = 108000000, .step = 50000 },
+ { .name = "ITU-2", .min = 87900000, .max = 107900000, .step = 50000 }
+};
+
+static unsigned int bandplan;
+static bool present;
+static bool initialized;
+static bool active;
+static bool scanning;
+static uint32_t current_frequency;
+static radio_freq_callback_t freq_callback;
+static void *freq_callback_data;
+
+static uint32_t null_get_min_frequency(radio_band_t band);
+static void null_set_frequency(uint32_t frequency);
+
+static int null_probe(void)
+{
+ present = true;
+ return 0;
+}
+
+static int null_init(void)
+{
+ GKeyFile *conf_file;
+ char *value_str;
+
+ if(!present)
+ return -1;
+
+ if(initialized)
+ return 0;
+
+ // Load settings from configuration file if it exists
+ conf_file = g_key_file_new();
+ if(conf_file &&
+ g_key_file_load_from_dirs(conf_file,
+ "AGL.conf",
+ (const gchar**) g_get_system_config_dirs(),
+ NULL,
+ G_KEY_FILE_KEEP_COMMENTS,
+ NULL) == TRUE) {
+
+ // Set band plan if it is specified
+ value_str = g_key_file_get_string(conf_file,
+ "radio",
+ "fmbandplan",
+ NULL);
+ if(value_str) {
+ unsigned int i;
+ for(i = 0;
+ i < sizeof(known_fm_band_plans) / sizeof(fm_band_plan_t);
+ i++) {
+ if(!strcasecmp(value_str, known_fm_band_plans[i].name)) {
+ bandplan = i;
+ break;
+ }
+ }
+ }
+ }
+
+ // Start off with minimum bandplan frequency
+ current_frequency = null_get_min_frequency(RADIO_BAND_FM);
+
+ initialized = true;
+ null_set_frequency(current_frequency);
+
+ return 0;
+}
+
+static void null_set_output(const char *output)
+{
+}
+
+static uint32_t null_get_frequency(void)
+{
+ return current_frequency;
+}
+
+static void null_set_frequency(uint32_t frequency)
+{
+ if(frequency < known_fm_band_plans[bandplan].min ||
+ frequency > known_fm_band_plans[bandplan].max)
+ return;
+
+ current_frequency = frequency;
+
+ if(freq_callback)
+ freq_callback(current_frequency, freq_callback_data);
+}
+
+static void null_set_frequency_callback(radio_freq_callback_t callback,
+ void *data)
+{
+ freq_callback = callback;
+ freq_callback_data = data;
+}
+
+static radio_band_t null_get_band(void)
+{
+ // We only support FM
+ return RADIO_BAND_FM;
+}
+
+static void null_set_band(radio_band_t band)
+{
+ // We only support FM, so do nothing
+}
+
+static int null_band_supported(radio_band_t band)
+{
+ if(band == RADIO_BAND_FM)
+ return 1;
+ return 0;
+}
+
+static uint32_t null_get_min_frequency(radio_band_t band)
+{
+ return known_fm_band_plans[bandplan].min;
+}
+
+static uint32_t null_get_max_frequency(radio_band_t band)
+{
+ return known_fm_band_plans[bandplan].max;
+}
+
+static uint32_t null_get_frequency_step(radio_band_t band)
+{
+ uint32_t ret = 0;
+
+ switch (band) {
+ case RADIO_BAND_AM:
+ ret = 1000; // 1 kHz
+ break;
+ case RADIO_BAND_FM:
+ ret = known_fm_band_plans[bandplan].step;
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static void null_start(void)
+{
+ if(!initialized)
+ return;
+
+ if(active)
+ return;
+
+ active = true;
+}
+
+static void null_stop(void)
+{
+ if(!initialized)
+ return;
+
+ if (!active)
+ return;
+
+ active = false;
+}
+
+static void null_scan_start(radio_scan_direction_t direction,
+ radio_scan_callback_t callback,
+ void *data)
+{
+ int frequency;
+
+ if(!active || scanning)
+ return;
+
+ scanning = true;
+
+ // Just go to the next frequency step up or down
+ frequency = current_frequency;
+ if(direction == RADIO_SCAN_FORWARD) {
+ frequency += known_fm_band_plans[bandplan].step;
+ } else {
+ frequency -= known_fm_band_plans[bandplan].step;
+ }
+ if(frequency < known_fm_band_plans[bandplan].min)
+ frequency = known_fm_band_plans[bandplan].max;
+ else if(frequency > known_fm_band_plans[bandplan].max)
+ frequency = known_fm_band_plans[bandplan].min;
+
+ scanning = false;
+ null_set_frequency(frequency);
+ if(callback)
+ callback(frequency, data);
+}
+
+static void null_scan_stop(void)
+{
+ scanning = false;
+}
+
+static radio_stereo_mode_t null_get_stereo_mode(void)
+{
+ // We only support stereo
+ return RADIO_MODE_STEREO;
+}
+
+static void null_set_stereo_mode(radio_stereo_mode_t mode)
+{
+ // We only support stereo, so do nothing
+}
+
+radio_impl_ops_t null_impl_ops = {
+ .name = "null/mock radio",
+ .probe = null_probe,
+ .init = null_init,
+ .set_output = null_set_output,
+ .get_frequency = null_get_frequency,
+ .set_frequency = null_set_frequency,
+ .set_frequency_callback = null_set_frequency_callback,
+ .get_band = null_get_band,
+ .set_band = null_set_band,
+ .band_supported = null_band_supported,
+ .get_min_frequency = null_get_min_frequency,
+ .get_max_frequency = null_get_max_frequency,
+ .get_frequency_step = null_get_frequency_step,
+ .start = null_start,
+ .stop = null_stop,
+ .scan_start = null_scan_start,
+ .scan_stop = null_scan_stop,
+ .get_stereo_mode = null_get_stereo_mode,
+ .set_stereo_mode = null_set_stereo_mode
+};
diff --git a/src/radio_impl_null.h b/src/radio_impl_null.h
new file mode 100644
index 0000000..16bd5e6
--- /dev/null
+++ b/src/radio_impl_null.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _RADIO_IMPL_NULL_H
+#define _RADIO_IMPL_NULL_H
+
+#include "radio_impl.h"
+
+extern radio_impl_ops_t null_impl_ops;
+
+#endif /* _RADIO_IMPL_NULL_H */
+
diff --git a/src/radio_impl_rtlsdr.c b/src/radio_impl_rtlsdr.c
new file mode 100644
index 0000000..d978605
--- /dev/null
+++ b/src/radio_impl_rtlsdr.c
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2017,2018,2020 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <glib.h>
+
+#include "radio_impl.h"
+
+#define HELPER_NAME "rtl_fm_helper"
+#define HELPER_MAX PATH_MAX + 64
+
+#define HELPER_CMD_MAXLEN 64
+#define HELPER_RSP_MAXLEN 128
+
+// Structure to describe FM band plans, all values in Hz.
+typedef struct {
+ char *name;
+ uint32_t min;
+ uint32_t max;
+ uint32_t step;
+} fm_band_plan_t;
+
+static fm_band_plan_t known_fm_band_plans[5] = {
+ { .name = "US", .min = 87900000, .max = 107900000, .step = 200000 },
+ { .name = "JP", .min = 76000000, .max = 95000000, .step = 100000 },
+ { .name = "EU", .min = 87500000, .max = 108000000, .step = 50000 },
+ { .name = "ITU-1", .min = 87500000, .max = 108000000, .step = 50000 },
+ { .name = "ITU-2", .min = 87900000, .max = 107900000, .step = 50000 }
+};
+
+static unsigned int bandplan;
+static char *helper_output;
+static pid_t helper_pid;
+static int helper_in;
+static int helper_out;
+static pthread_mutex_t helper_mutex = PTHREAD_MUTEX_INITIALIZER;
+static bool present;
+static bool initialized;
+static bool active;
+static bool scanning;
+static uint32_t current_frequency;
+static radio_freq_callback_t freq_callback;
+static void *freq_callback_data;
+
+static uint32_t rtlsdr_get_min_frequency(radio_band_t band);
+static void rtlsdr_set_frequency(uint32_t frequency);
+static void rtlsdr_scan_stop(void);
+
+/*
+ * Bi-directional popen implementation
+ * Based on one of the versions given in:
+ *
+ * https://stackoverflow.com/questions/12778672/killing-process-that-has-been-created-with-popen2
+ */
+static pid_t popen2(char *command, int *in_fd, int *out_fd)
+{
+ int pin[2], pout[2];
+ pid_t pid;
+
+ if(out_fd != NULL) {
+ if(pipe(pin) != 0)
+ return -1;
+ }
+ if(in_fd != NULL) {
+ if (pipe(pout) != 0) {
+ if(out_fd != NULL) {
+ close(pin[0]);
+ close(pin[1]);
+ }
+ return -1;
+ }
+ }
+
+ pid = fork();
+ if(pid < 0) {
+ if(out_fd != NULL) {
+ close(pin[0]);
+ close(pin[1]);
+ }
+ if(in_fd != NULL) {
+ close(pout[0]);
+ close(pout[1]);
+ }
+ return pid;
+ }
+ if(pid == 0) {
+ if(out_fd != NULL) {
+ close(pin[1]);
+ dup2(pin[0], 0);
+ }
+ if(in_fd != NULL) {
+ close(pout[0]);
+ dup2(pout[1], 1);
+ }
+ execlp(command, command, NULL);
+ perror("Error:");
+ exit(1);
+ }
+ if(in_fd != NULL) {
+ close(pout[1]);
+ *in_fd = pout[0];
+ }
+ if(out_fd != NULL) {
+ close(pin[0]);
+ *out_fd = pin[1];
+ }
+ return pid;
+}
+
+static int rtlsdr_probe(void)
+{
+ const char *bindir = "/usr/sbin";
+ char *helper_path;
+
+ // Run helper to detect adapter
+ helper_path = malloc(HELPER_MAX);
+ if(!helper_path)
+ return -ENOMEM;
+ if(snprintf(helper_path, HELPER_MAX, "%s/%s --detect", bindir, HELPER_NAME) == HELPER_MAX) {
+ fprintf(stderr, "Could not create command for \"%s --detect\"", HELPER_NAME);
+ return -EINVAL;
+ }
+ if(system(helper_path) != 0) {
+ free(helper_path);
+ return -1;
+ }
+
+ present = true;
+ return 0;
+}
+
+static int rtlsdr_start_helper(void)
+{
+ const char *bindir = "/usr/sbin";
+ char *helper_path;
+ static bool helper_started = false;
+
+ if(!present || initialized)
+ return -1;
+
+ if(helper_started)
+ return 0;
+
+ if(helper_output) {
+ // Indicate desired output to helper
+ printf("Setting RADIO_OUTPUT=%s", helper_output);
+ setenv("RADIO_OUTPUT", helper_output, 1);
+ }
+
+ // Run helper
+ helper_path = malloc(HELPER_MAX);
+ if(!helper_path)
+ return -ENOMEM;
+ if(snprintf(helper_path, PATH_MAX, "%s/%s", bindir, HELPER_NAME) == PATH_MAX) {
+ fprintf(stderr, "Could not create path to %s", HELPER_NAME);
+ return -EINVAL;
+ }
+ helper_pid = popen2(helper_path, &helper_out, &helper_in);
+ if(helper_pid < 0) {
+ fprintf(stderr, "Could not run %s!", HELPER_NAME);
+ return -1;
+ }
+ printf("%s started", HELPER_NAME);
+ helper_started = true;
+ free(helper_path);
+
+ return 0;
+}
+
+static int rtlsdr_init(void)
+{
+ GKeyFile *conf_file;
+ char *value_str;
+ int rc;
+
+ if(!present)
+ return -1;
+
+ if(initialized)
+ return 0;
+
+ // Load settings from configuration file if it exists
+ conf_file = g_key_file_new();
+ if(conf_file &&
+ g_key_file_load_from_dirs(conf_file,
+ "AGL.conf",
+ (const gchar**) g_get_system_config_dirs(),
+ NULL,
+ G_KEY_FILE_KEEP_COMMENTS,
+ NULL) == TRUE) {
+
+ // Set band plan if it is specified
+ value_str = g_key_file_get_string(conf_file,
+ "radio",
+ "fmbandplan",
+ NULL);
+ if(value_str) {
+ unsigned int i;
+ for(i = 0;
+ i < sizeof(known_fm_band_plans) / sizeof(fm_band_plan_t);
+ i++) {
+ if(!strcasecmp(value_str, known_fm_band_plans[i].name)) {
+ bandplan = i;
+ break;
+ }
+ }
+ }
+ }
+
+ // Start off with minimum bandplan frequency
+ current_frequency = rtlsdr_get_min_frequency(RADIO_BAND_FM);
+ rc = rtlsdr_start_helper();
+ if(rc != 0) {
+ return rc;
+ }
+
+ initialized = true;
+ rtlsdr_set_frequency(current_frequency);
+
+ return 0;
+}
+
+static void rtlsdr_set_output(const char *output)
+{
+ // Save output for later use
+ free(helper_output);
+ helper_output = output ? strdup(output) : NULL;
+}
+
+static uint32_t rtlsdr_get_frequency(void)
+{
+ return current_frequency;
+}
+
+static void rtlsdr_set_frequency(uint32_t frequency)
+{
+ char cmd[HELPER_CMD_MAXLEN];
+ char output[HELPER_RSP_MAXLEN];
+ bool found = false;
+ ssize_t rc;
+ uint32_t n;
+
+ if(!initialized)
+ return;
+
+ if(frequency < known_fm_band_plans[bandplan].min ||
+ frequency > known_fm_band_plans[bandplan].max)
+ return;
+
+ if(scanning)
+ rtlsdr_scan_stop();
+
+ current_frequency = frequency;
+ snprintf(cmd, sizeof(cmd), "F=%u\n", frequency);
+ pthread_mutex_lock(&helper_mutex);
+ rc = write(helper_in, cmd, strlen(cmd));
+ if(rc < 0) {
+ pthread_mutex_unlock(&helper_mutex);
+ return;
+ }
+ while(!found) {
+ rc = read(helper_out, output, sizeof(output)-1);
+ if(rc <= 0)
+ break;
+ output[rc] = '\0';
+ if(output[0] == 'F') {
+ if(sscanf(output, "F=%u\n", &n) == 1) {
+ if(freq_callback)
+ freq_callback(n, freq_callback_data);
+ found = true;
+ }
+ }
+ }
+ pthread_mutex_unlock(&helper_mutex);
+}
+
+static void rtlsdr_set_frequency_callback(radio_freq_callback_t callback,
+ void *data)
+{
+ freq_callback = callback;
+ freq_callback_data = data;
+}
+
+static radio_band_t rtlsdr_get_band(void)
+{
+ // We only support FM
+ return RADIO_BAND_FM;
+}
+
+static void rtlsdr_set_band(radio_band_t band)
+{
+ // We only support FM, so do nothing
+}
+
+static int rtlsdr_band_supported(radio_band_t band)
+{
+ if(band == RADIO_BAND_FM)
+ return 1;
+ return 0;
+}
+
+static uint32_t rtlsdr_get_min_frequency(radio_band_t band)
+{
+ return known_fm_band_plans[bandplan].min;
+}
+
+static uint32_t rtlsdr_get_max_frequency(radio_band_t band)
+{
+ return known_fm_band_plans[bandplan].max;
+}
+
+static uint32_t rtlsdr_get_frequency_step(radio_band_t band)
+{
+ uint32_t ret = 0;
+
+ switch (band) {
+ case RADIO_BAND_AM:
+ ret = 1000; // 1 kHz
+ break;
+ case RADIO_BAND_FM:
+ ret = known_fm_band_plans[bandplan].step;
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static void rtlsdr_start(void)
+{
+ ssize_t rc;
+ char cmd[HELPER_CMD_MAXLEN];
+
+ if(!initialized)
+ return;
+
+ if(active)
+ return;
+
+ snprintf(cmd, sizeof(cmd), "START\n");
+ pthread_mutex_lock(&helper_mutex);
+ rc = write(helper_in, cmd, strlen(cmd));
+ pthread_mutex_unlock(&helper_mutex);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to ask \"%s\" to start", HELPER_NAME);
+ return;
+ }
+ active = true;
+}
+
+static void rtlsdr_stop(void)
+{
+ ssize_t rc;
+ if(!initialized)
+ return;
+
+ if (!active)
+ return;
+
+ char cmd[HELPER_CMD_MAXLEN];
+
+ snprintf(cmd, sizeof(cmd), "STOP\n");
+ pthread_mutex_lock(&helper_mutex);
+ rc = write(helper_in, cmd, strlen(cmd));
+ pthread_mutex_unlock(&helper_mutex);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to ask \"%s\" to stop", HELPER_NAME);
+ return;
+ }
+ active = false;
+}
+
+static void rtlsdr_scan_start(radio_scan_direction_t direction,
+ radio_scan_callback_t callback,
+ void *data)
+{
+ ssize_t rc;
+ char cmd[HELPER_CMD_MAXLEN];
+ char output[HELPER_RSP_MAXLEN];
+ bool found = false;
+ uint32_t n;
+
+ if(!active || scanning)
+ return;
+
+ scanning = true;
+ snprintf(cmd,
+ sizeof(cmd),
+ "S=%s\n", direction == RADIO_SCAN_FORWARD ? "UP" : "DOWN");
+ pthread_mutex_lock(&helper_mutex);
+ if(!scanning) {
+ pthread_mutex_unlock(&helper_mutex);
+ return;
+ }
+ rc = write(helper_in, cmd, strlen(cmd));
+ pthread_mutex_unlock(&helper_mutex);
+ if(rc < 0)
+ return;
+ while(!found) {
+ pthread_mutex_lock(&helper_mutex);
+ if(!scanning) {
+ pthread_mutex_unlock(&helper_mutex);
+ break;
+ }
+ rc = read(helper_out, output, sizeof(output)-1);
+ pthread_mutex_unlock(&helper_mutex);
+ if(rc <= 0)
+ break;
+
+ output[rc] = '\0';
+ if(output[0] == 'F') {
+ if(sscanf(output, "F=%u\n", &n) == 1) {
+ current_frequency = n;
+ if(freq_callback)
+ freq_callback(n, freq_callback_data);
+ }
+ }
+ if(output[0] == 'S') {
+ if(sscanf(output, "S=%u\n", &n) == 1) {
+ if(callback)
+ callback(n, data);
+ found = true;
+ scanning = false;
+ }
+ }
+ }
+}
+
+static void rtlsdr_scan_stop(void)
+{
+ char cmd[HELPER_CMD_MAXLEN];
+ ssize_t rc;
+
+ snprintf(cmd, sizeof(cmd), "S=STOP\n");
+ pthread_mutex_lock(&helper_mutex);
+ scanning = false;
+ rc = write(helper_in, cmd, strlen(cmd));
+ pthread_mutex_unlock(&helper_mutex);
+ if (rc < 0)
+ fprintf(stderr, "Failed to ask \"%s\" to stop scan", HELPER_NAME);
+}
+
+static radio_stereo_mode_t rtlsdr_get_stereo_mode(void)
+{
+ // We only support stereo
+ return RADIO_MODE_STEREO;
+}
+
+static void rtlsdr_set_stereo_mode(radio_stereo_mode_t mode)
+{
+ // We only support stereo, so do nothing
+}
+
+radio_impl_ops_t rtlsdr_impl_ops = {
+ .name = "RTL-SDR USB adapter",
+ .probe = rtlsdr_probe,
+ .init = rtlsdr_init,
+ .set_output = rtlsdr_set_output,
+ .get_frequency = rtlsdr_get_frequency,
+ .set_frequency = rtlsdr_set_frequency,
+ .set_frequency_callback = rtlsdr_set_frequency_callback,
+ .get_band = rtlsdr_get_band,
+ .set_band = rtlsdr_set_band,
+ .band_supported = rtlsdr_band_supported,
+ .get_min_frequency = rtlsdr_get_min_frequency,
+ .get_max_frequency = rtlsdr_get_max_frequency,
+ .get_frequency_step = rtlsdr_get_frequency_step,
+ .start = rtlsdr_start,
+ .stop = rtlsdr_stop,
+ .scan_start = rtlsdr_scan_start,
+ .scan_stop = rtlsdr_scan_stop,
+ .get_stereo_mode = rtlsdr_get_stereo_mode,
+ .set_stereo_mode = rtlsdr_set_stereo_mode
+};
diff --git a/src/radio_impl_rtlsdr.h b/src/radio_impl_rtlsdr.h
new file mode 100644
index 0000000..6c83338
--- /dev/null
+++ b/src/radio_impl_rtlsdr.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _RADIO_IMPL_RTLSDR_H
+#define _RADIO_IMPL_RTLSDR_H
+
+#include "radio_impl.h"
+
+extern radio_impl_ops_t rtlsdr_impl_ops;
+
+#endif /* _RADIO_IMPL_RTLSDR_H */
+
diff --git a/src/radio_impl_tef665x.c b/src/radio_impl_tef665x.c
new file mode 100644
index 0000000..7cca710
--- /dev/null
+++ b/src/radio_impl_tef665x.c
@@ -0,0 +1,2401 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ TODO:
+ at this point:
+ - complete the set stereo_mode verb.
+ - find a way to tell the service which i2c chanel is used.
+ - separate the functions of driver from the verbs by creating new c file.
+ - find a way of monitoring the quality of tuning and correct it time by time.
+ - use Interupt for getting RDS data
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <linux/i2c-dev.h>
+#include <linux/i2c.h>
+#include <stdarg.h>
+#include <error.h>
+#include <gst/gst.h>
+#include <time.h>
+#include <gst/gst.h>
+#include <pthread.h>
+
+#include "radio_impl.h"
+#include "tef665x.h"
+
+#define I2C_ADDRESS 0x64
+#define I2C_DEV "/dev/i2c-3"
+#define VERSION "0.1"
+
+#define TEF665x_CMD_LEN_MAX 20
+#define SET_SUCCESS 1
+#define TEF665X_SPLIT_SIZE 24
+
+#define TEF665x_REF_CLK 9216000 //reference clock frequency
+#define TEF665x_IS_CRYSTAL_CLK 0 //crstal
+#define TEF665x_IS_EXT_CLK 1 //external clock input
+
+#define High_16bto8b(a) ((uint8_t)((a) >> 8))
+#define Low_16bto8b(a) ((uint8_t)(a))
+#define Convert8bto16b(a) ((uint16_t)(((uint16_t)(*(a))) << 8 |((uint16_t)(*(a+1)))))
+
+#define GST_PIPELINE_LEN 256
+
+const uint8_t tef665x_patch_cmdTab1[] = {3, 0x1c,0x00,0x00};
+const uint8_t tef665x_patch_cmdTab2[] = {3, 0x1c,0x00,0x74};
+const uint8_t tef665x_patch_cmdTab3[] = {3, 0x1c,0x00,0x75};
+
+
+typedef struct {
+ char *name;
+ uint32_t min;
+ uint32_t max;
+ uint32_t step;
+} band_plan_t;
+
+typedef struct{
+ radio_scan_callback_t callback;
+ radio_scan_direction_t direction;
+ void* data;
+}scan_data_t;
+typedef struct rds_data
+{
+ bool Text_Changed;
+ bool TrafficAnnouncement;
+ bool TrafficProgram;
+ bool Music_Speech;
+
+ uint32_t Alternative_Freq[25];
+ uint8_t Alternative_Freq_Counter;
+ uint8_t Num_AlterFreq;
+
+ uint16_t PICode;
+ uint8_t DI_Seg;
+ uint8_t PTY_Code;
+
+ uint8_t Year;
+ uint8_t Month;
+ uint8_t Day;
+ uint8_t Hour;
+ uint8_t Min;
+
+ uint8_t PTYN_Size;
+ uint8_t raw_data[12];
+
+ char PS_Name[16];
+ char RT[128];
+ char PTYN[16];
+} rds_data_t;
+
+//thread for handling RDS and Mutex
+pthread_t rds_thread;
+rds_data_t RDS_Message;
+pthread_mutex_t RDS_Mutex;
+
+station_quality_t quality;
+
+//Threads for handling Scan
+pthread_t scan_thread;
+
+pthread_mutex_t scan_mutex;
+
+char _Temp[64]={0};
+
+static band_plan_t known_fm_band_plans[5] = {
+ { .name = "US", .min = 87900000, .max = 107900000, .step = 200000 },
+ { .name = "JP", .min = 76000000, .max = 95000000, .step = 100000 },
+ { .name = "EU", .min = 87500000, .max = 108000000, .step = 50000 },
+ { .name = "ITU-1", .min = 87500000, .max = 108000000, .step = 50000 },
+ { .name = "ITU-2", .min = 87900000, .max = 107900000, .step = 50000 }
+};
+static band_plan_t known_am_band_plans[1] = {
+ { .name = "W-ASIA", .min = 522000, .max = 1620000, .step = 9000 }
+};
+
+static unsigned int fm_bandplan = 2;
+static unsigned int am_bandplan = 0;
+static bool corking;
+static bool present;
+static bool initialized;
+static bool scanning;
+
+// stream state
+static GstElement *pipeline;
+static bool running;
+
+
+uint32_t AlterFreqOffset=0;
+
+static void (*freq_callback)(uint32_t, void*);
+static void (*rds_callback) (void*);
+static void *freq_callback_data;
+
+int tef665x_set_rds (uint32_t i2c_file_desc);
+#define DEBUG 0
+
+#if DEBUG == 1
+#define _debug(x, y) printf("function: %s, %s : %d\n", __FUNCTION__, #x, y)
+#else
+#define _debug(x, y)
+#endif
+
+static uint32_t file_desc;
+
+static radio_band_t current_band;
+static uint32_t current_am_frequency;
+static uint32_t current_fm_frequency;
+
+static void tef665x_scan_stop (void);
+static void tef665x_set_frequency (uint32_t);
+static void tef665x_search_frequency (uint32_t);
+
+static uint32_t tef665x_get_min_frequency (radio_band_t);
+static uint32_t tef665x_get_max_frequency (radio_band_t);
+static uint32_t tef665x_get_frequency_step (radio_band_t);
+
+static station_quality_t *tef665x_get_quality_info (void);
+
+static gboolean handle_message(GstBus *bus, GstMessage *msg, __attribute__((unused)) void *ptr)
+{
+ GstState state;
+
+ if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_REQUEST_STATE) {
+
+ gst_message_parse_request_state(msg, &state);
+
+ if (state == GST_STATE_PAUSED)
+ corking = true;
+ else if (state == GST_STATE_PLAYING)
+ corking = false;
+
+ }
+
+ return TRUE;
+}
+
+static int tef665x_set_cmd(int i2c_file_desc, TEF665x_MODULE module, uint8_t cmd, int len, ...)
+{
+ int i, ret;
+ uint8_t buf[TEF665x_CMD_LEN_MAX];
+ uint16_t temp;
+ va_list vArgs;
+
+ va_start(vArgs, len);
+
+ buf[0] = module; //module, FM/AM/APP
+ buf[1] = cmd; //cmd, 1,2,10,...
+ buf[2] = 0x01; //index, always 1
+
+ for(i = 3; i < len; i++)
+ {
+ temp = va_arg(vArgs,int);
+
+ buf[i++] = High_16bto8b(temp);
+ buf[i] = Low_16bto8b(temp);
+ }
+
+ va_end(vArgs);
+
+ ret = write(i2c_file_desc, buf, len);
+
+ temp = (ret == len) ? 1 : 0;
+ _debug("return value", temp);
+ return temp;
+}
+
+static int tef665x_get_cmd(int i2c_file_desc, TEF665x_MODULE module, uint8_t cmd, uint8_t *receive, int len)
+{
+ uint8_t temp;
+ uint8_t buf[3];
+ int ret;
+
+ buf[0]= module; //module, FM/AM/APP
+ buf[1]= cmd; //cmd, 1,2,10,...
+ buf[2]= 1; //index, always 1
+
+ write(i2c_file_desc, buf, 3);
+
+ ret = read(i2c_file_desc, receive, len);
+ temp = (ret == len) ? 1 : 0;
+ _debug("return value", temp);
+ if(temp==0)
+ fprintf(stderr, "Error Number: %d: %s",errno,strerror(errno));
+ return temp;
+}
+
+/*
+module 64 APPL
+cmd 128 Get_Operation_Status | status
+index
+1 status
+ Device operation status
+ 0 = boot state; no command support
+ 1 = idle state
+ 2 = active state; radio standby
+ 3 = active state; FM
+ 4 = active state; AM
+*/
+static int appl_get_operation_status(int i2c_file_desc ,uint8_t *status)
+{
+ uint8_t buf[2];
+ int ret;
+
+ ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_APPL,
+ TEF665X_Cmd_Get_Operation_Status,
+ buf, sizeof(buf));
+
+ if(ret == SET_SUCCESS)
+ {
+ *status = Convert8bto16b(buf);
+ _debug("return value", 1);
+ return 1;
+ }
+ _debug("return value", 0);
+ return 0;
+}
+
+static int get_operation_status(int i2c_file_desc, TEF665x_STATE *status)
+{
+ TEF665x_STATE data;
+ int ret;
+ if(SET_SUCCESS ==(ret = appl_get_operation_status(i2c_file_desc, &data)))
+ {
+ //printk( "appl_get_operation_status1 data= %d \n",data);
+ _debug("got status", ret);
+ switch(data)
+ {
+ case 0:
+ _debug("status: boot", ret);
+ *status = eDevTEF665x_Boot_state;
+ break;
+ case 1:
+ _debug("status: idle", ret);
+ *status = eDevTEF665x_Idle_state;
+ break;
+ default:
+ _debug("status: active", ret);
+ *status = eDevTEF665x_Active_state;
+ break;
+ }
+ }
+ return ret;
+}
+
+static int tef665x_power_on(int i2c_file_desc)
+{
+ int ret;
+ TEF665x_STATE status;
+ usleep(5000);
+ if(SET_SUCCESS == (ret = get_operation_status(i2c_file_desc, &status))) //[ w 40 80 01 [ r 0000 ]
+ {
+ _debug("Powered ON", ret);
+ }
+ else
+ {
+ _debug("Powered ON FAILED!", ret);
+ }
+
+ return ret;
+}
+
+static int tef665x_writeTab(int i2c_file_desc,const uint8_t *tab)
+{
+ int ret;
+ ret = write(i2c_file_desc, tab + 1, tab[0]);
+ return (ret != tab[0]) ? 0 : 1;
+}
+
+static int tef665x_patch_load(int i2c_file_desc, const uint8_t *bytes, uint16_t size)
+{
+ uint8_t buf[25]; //the size which we break the data into, is 24 bytes.
+ int ret, i;
+
+ uint16_t num = size / 24;
+ uint16_t rem = size % 24;
+
+ buf[0] = 0x1b;
+
+ usleep(10000);
+
+ for(i = 0; i < num; i++)
+ {
+ memcpy(buf + 1, bytes + (24 * i), 24);
+
+ ret = write(i2c_file_desc, buf, 25);
+
+ if(ret != 25)
+ {
+ _debug("FAILED, send patch error! in pack no", i);
+ return false;
+ }
+ usleep(50);
+ }
+
+ memcpy(buf + 1, bytes + (num * 24), rem);
+
+ ret = write(i2c_file_desc, buf, rem);
+ if(ret != rem)
+ {
+ _debug("FAILED, send patch error at the end!", 0);
+ return false;
+ }
+ usleep(50);
+
+ _debug("return value", 1);
+ return true;
+}
+
+static int tef665x_patch_init(int i2c_file_desc)
+{
+ int ret = 0;
+ ret = tef665x_writeTab(i2c_file_desc, tef665x_patch_cmdTab1); //[ w 1C 0000 ]
+ if(!ret)
+ {
+ _debug("1- tab1 load FAILED", ret);
+ return ret;
+ }
+
+ ret = tef665x_writeTab(i2c_file_desc, tef665x_patch_cmdTab2); //[ w 1C 0074 ]
+ if(!ret)
+ {
+ _debug("2- tab2 load FAILED", ret);
+ return ret;
+ }
+
+ ret = tef665x_patch_load(i2c_file_desc, pPatchBytes, patchSize); //table1
+ if(!ret)
+ {
+ _debug("3- pPatchBytes load FAILED", ret);
+ return ret;
+ }
+
+ ret = tef665x_writeTab(i2c_file_desc, tef665x_patch_cmdTab1); //[ w 1C 0000 ]
+ if(!ret)
+ {
+ _debug("4- tab1 load FAILED", ret);
+ return ret;
+ }
+
+ ret = tef665x_writeTab(i2c_file_desc, tef665x_patch_cmdTab3); //[ w 1C 0075 ]
+ if(!ret)
+ {
+ _debug("5- tab3 load FAILED", ret);
+ return ret;
+ }
+
+ ret = tef665x_patch_load(i2c_file_desc, pLutBytes, lutSize); //table2
+ if(!ret)
+ {
+ _debug("6- pLutBytes load FAILED", ret);
+ return ret;
+ }
+
+ ret = tef665x_writeTab(i2c_file_desc, tef665x_patch_cmdTab1); //[ w 1C 0000 ]
+ if(!ret)
+ {
+ _debug("7- tab1 load FAILED", ret);
+ return ret;
+ }
+ _debug("patch loaded", ret);
+ return ret;
+}
+
+//Command start will bring the device into? idle state�: [ w 14 0001 ]
+static int tef665x_start_cmd(int i2c_file_desc)
+{
+
+ int ret;
+ unsigned char buf[3];
+
+ buf[0] = 0x14;
+ buf[1] = 0;
+ buf[2] = 1;
+
+ ret = write(i2c_file_desc, buf, 3);
+
+ if (ret != 3)
+ {
+ _debug("start cmd FAILED", 0);
+ return 0;
+ }
+ _debug("return true", 1);
+ return 1;
+}
+
+static int tef665x_boot_state(int i2c_file_desc)
+{
+ int ret=0;
+ if(1 == tef665x_patch_init(i2c_file_desc))
+ {
+ _debug("return true", 1);
+ }
+ else
+ {
+ _debug("return value", 0);
+ return 0;
+ }
+
+ usleep(50000);
+
+ if(1 == tef665x_start_cmd(i2c_file_desc))
+ {
+ _debug("'start cmd'return true", 1);
+ }
+ else
+ {
+ _debug("return value", 0);
+ return 0;
+ }
+
+ usleep(50000);
+
+ return ret;
+}
+
+/*
+module 64 APPL
+cmd 4 Set_ReferenceClock frequency
+
+index
+1 frequency_high
+ [ 15:0 ]
+ MSB part of the reference clock frequency
+ [ 31:16 ]
+2 frequency_low
+ [ 15:0 ]
+ LSB part of the reference clock frequency
+ [ 15:0 ]
+ frequency [*1 Hz] (default = 9216000)
+3 type
+ [ 15:0 ]
+ clock type
+ 0 = crystal oscillator operation (default)
+ 1 = external clock input operation
+*/
+static int tef665x_appl_set_referenceClock(uint32_t i2c_file_desc, uint16_t frequency_high, uint16_t frequency_low, uint16_t type)
+{
+ return tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_APPL,
+ TEF665X_Cmd_Set_ReferenceClock,
+ 9,
+ frequency_high, frequency_low, type);
+}
+
+static int appl_set_referenceClock(uint32_t i2c_file_desc, uint32_t frequency, bool is_ext_clk) //0x3d 0x900
+{
+ return tef665x_appl_set_referenceClock(i2c_file_desc,(uint16_t)(frequency >> 16), (uint16_t)frequency, is_ext_clk);
+}
+
+/*
+module 64 APPL
+cmd 5 Activate mode
+
+index
+1 mode
+ [ 15:0 ]
+ 1 = goto �active� state with operation mode of �radio standby�
+*/
+static int tef665x_appl_activate(uint32_t i2c_file_desc ,uint16_t mode)
+{
+ return tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_APPL,
+ TEF665X_Cmd_Activate,
+ 5,
+ mode);
+}
+
+static int appl_activate(uint32_t i2c_file_desc)
+{
+ return tef665x_appl_activate(i2c_file_desc, 1);
+}
+/*
+module 48 AUDIO
+cmd 22 set_dig_io signal, format, operation, samplerate
+
+index
+1 signal
+[ 15:0 ]
+ digital audio input / output
+ 32 = I²S digital audio IIS_SD_0 (input)
+ 33 = I²S digital audio IIS_SD_1 (output)
+(2) mode
+ 0 = off (default)
+ 1 = input (only available for signal = 32)
+ 2 = output (only available for signal = 33)
+(3) format
+ [ 15:0 ]
+ digital audio format select
+ 16 = I²S 16 bits (fIIS_BCK = 32 * samplerate)
+ 32 = I²S 32 bits (fIIS_BCK = 64 * samplerate) (default)
+ 272 = lsb aligned 16 bit (fIIS_BCK = 64 * samplerate)
+ 274 = lsb aligned 18 bit (fIIS_BCK = 64 * samplerate)
+ 276 = lsb aligned 20 bit (fIIS_BCK = 64 * samplerate)
+ 280 = lsb aligned 24 bit (fIIS_BCK = 64 * samplerate)
+(4) operation
+ [ 15:0 ]
+ operation mode
+ 0 = slave mode; IIS_BCK and IIS_WS input defined by source (default)
+ 256 = master mode; IIS_BCK and IIS_WS output defined by device
+(5) samplerate
+ [ 15:0 ] 3200 = 32.0 kHz
+ 4410 = 44.1 kHz (default)
+ 4800 = 48.0 kHz
+*/
+static int tef665x_audio_set_dig_io(uint8_t i2c_file_desc, uint16_t signal, uint16_t mode, uint16_t format, uint16_t operation, uint16_t samplerate)
+{
+ int ret = tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_AUDIO,
+ TEF665X_Cmd_Set_Dig_IO,
+ 13,
+ signal, mode, format, operation, samplerate);
+ if(ret)
+ {
+ _debug("Digital In/Out is set ", signal);
+ }
+ else
+ {
+ _debug("FAILED, return", 0);
+ return 0;
+ }
+ return 1;
+}
+
+/*
+module 32 / 33 FM / AM
+cmd 85 Set_Specials ana_out, dig_out
+
+index
+1 signal
+ [ 15:0 ]
+ analog audio output
+ 128 = DAC L/R output
+2 mode
+ [ 15:0 ]
+ output mode
+ 0 = off (power down)
+ 1 = output enabled (default)
+*/
+
+static int tef665x_audio_set_ana_out(uint32_t i2c_file_desc, uint16_t signal,uint16_t mode)
+{
+ int ret = tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_AUDIO,
+ TEF665X_Cmd_Set_Ana_Out,
+ 7,
+ signal, mode);
+ if(ret)
+ {
+ _debug("analog output is set to ", mode);
+ }
+ else
+ {
+ _debug("FAILED, return", 0);
+ return 0;
+ }
+ return 1;
+
+}
+
+/*
+module 48 AUDIO
+cmd 13 Set_Output_Source
+
+index
+1 signal
+ [ 15:0 ]
+ audio output
+ 33 = I2S Digital audio
+ 128 = DAC L/R output (default)
+2 source
+ [ 15:0 ]
+ source
+ 4 = analog radio
+ 32 = i2s digital audio input
+ 224 = audio processor (default)
+ 240 = sin wave generator
+*/
+static int tef665x_set_output_src(uint32_t i2c_file_desc, uint8_t signal, uint8_t src)
+{
+ int ret = tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_AUDIO,
+ TEF665X_Cmd_Set_Output_Source,
+ 7,
+ signal, src);
+ if(ret)
+ {
+ _debug("Output is set ", signal);
+ }
+ else
+ {
+ _debug("FAILED, return", 0);
+ return 0;
+ }
+ return 1;
+}
+
+static int tef665x_idle_state(int i2c_file_desc)
+{
+ TEF665x_STATE status;
+
+ //mdelay(50);
+
+ if(SET_SUCCESS == get_operation_status(i2c_file_desc, &status))
+ {
+ _debug("got operation status", 1);
+ if(status != eDevTEF665x_Boot_state)
+ {
+ _debug("not in boot status", 1);
+
+ if(SET_SUCCESS == appl_set_referenceClock(i2c_file_desc, TEF665x_REF_CLK, TEF665x_IS_CRYSTAL_CLK)) //TEF665x_IS_EXT_CLK
+ {
+ _debug("set the clock", TEF665x_REF_CLK);
+ if(SET_SUCCESS == appl_activate(i2c_file_desc))// APPL_Activate mode = 1.[ w 40 05 01 0001 ]
+ {
+ //usleep(100000); //Wait 100 ms
+ _debug("activate succeed", 1);
+ return 1;
+ }
+ else
+ {
+ _debug("activate FAILED", 1);
+ }
+ }
+ else
+ {
+ _debug("set the clock FAILED", TEF665x_REF_CLK);
+ }
+
+ }
+ else
+ {
+ _debug("did not get operation status", 0);
+ }
+
+ }
+ _debug("return value", 0);
+ return 0;
+}
+
+static int tef665x_para_load(uint32_t i2c_file_desc)
+{
+ int i;
+ int r;
+ const uint8_t *p = init_para;
+
+ for(i = 0; i < sizeof(init_para); i += (p[i]+1))
+ {
+ if(SET_SUCCESS != (r = tef665x_writeTab(i2c_file_desc, p + i)))
+ {
+ break;
+ }
+ }
+
+ //Initiate RDS
+ tef665x_set_rds(i2c_file_desc);
+
+ _debug("return value", r);
+ return r;
+}
+
+/*
+module 32 / 33 FM / AM
+cmd 1 Tune_To mode, frequency
+
+index
+1 mode
+ [ 15:0 ]
+ tuning actions
+ 0 = no action (radio mode does not change as function of module band)
+ 1 = Preset Tune to new program with short mute time
+ 2 = Search Tune to new program and stay muted
+ FM 3 = AF-Update Tune to alternative frequency, store quality
+ and tune back with inaudible mute
+ 4 = Jump Tune to alternative frequency with short
+ inaudible mute
+ 5 = Check Tune to alternative frequency and stay
+ muted
+ AM 3 � 5 = reserved
+ 6 = reserved
+ 7 = End Release the mute of a Search or Check action
+ (frequency is not required and ignored)
+2 frequency
+[ 15:0 ]
+ tuning frequency
+ FM 6500 � 10800 65.00 � 108.00 MHz / 10 kHz step size
+ AM LW 144 � 288 144 � 288 kHz / 1 kHz step size
+ MW 522 � 1710 522 � 1710 kHz / 1 kHz step size
+ SW 2300 � 27000 2.3 � 27 MHz / 1 kHz step size
+*/
+static int tef665x_radio_tune_to (uint32_t i2c_file_desc, bool fm, uint16_t mode,uint16_t frequency )
+{
+ return tef665x_set_cmd(i2c_file_desc, fm ? TEF665X_MODULE_FM: TEF665X_MODULE_AM,
+ TEF665X_Cmd_Tune_To,
+ ( mode <= 5 ) ? 7 : 5,
+ mode, frequency);
+}
+
+static int FM_tune_to(uint32_t i2c_file_desc, AR_TuningAction_t mode, uint16_t frequency)
+{
+ int ret = tef665x_radio_tune_to(i2c_file_desc, 1, (uint16_t)mode, frequency);
+ _debug("return value", ret);
+ return ret;
+}
+
+static int AM_tune_to(uint32_t i2c_file_desc, AR_TuningAction_t mode,uint16_t frequency)
+{
+ int ret = tef665x_radio_tune_to(i2c_file_desc, 0, (uint16_t)mode, frequency);
+ _debug("return value", ret);
+ return ret;
+}
+
+/*
+module 48 AUDIO
+cmd 11 Set_Mute mode
+
+index
+1 mode
+ [ 15:0 ]
+ audio mute
+ 0 = mute disabled
+ 1 = mute active (default)
+*/
+int tef665x_audio_set_mute(uint32_t i2c_file_desc, uint16_t mode)
+{
+ int ret = tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_AUDIO,
+ TEF665X_Cmd_Set_Mute,
+ 5,
+ mode);
+ if(ret)
+ {
+ _debug("mute state changed , mode", mode);
+ }
+ else
+ {
+ _debug("FAILED, return", 0);
+ return 0;
+ }
+ return 1;
+}
+
+/*
+module 48 AUDIO
+cmd 10 Set_Volume volume
+
+index
+1 volume
+ [ 15:0 ] (signed)
+ audio volume
+ -599 � +240 = -60 � +24 dB volume
+ 0 = 0 dB (default)f665x_patch_init function: "3"t,int16_t volume)
+*/
+static int tef665x_audio_set_volume(uint32_t i2c_file_desc, uint16_t volume)
+{
+ return tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_AUDIO,
+ TEF665X_Cmd_Set_Volume,
+ 5,
+ volume*10);
+}
+/*
+module 64 APPL
+cmd 130 Get_Identification
+index
+1 device
+2 hw_version
+3 sw_version
+*/
+int appl_get_identification(int i2c_file_desc)
+{
+ uint8_t buf[6];
+ int ret;
+
+ ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_APPL,
+ TEF665X_Cmd_Get_Identification,
+ buf, sizeof(buf));
+// should be completed for further use
+// extracting chip versions ...
+ if(ret == SET_SUCCESS)
+ {
+ for(int i = 0; i<6;i++)
+ printf("buf[%i] = %x\n", i, buf[i]);
+ return 1;
+ }
+ _debug("return value", 0);
+ return 0;
+}
+
+
+//mute=1, unmute=0
+int audio_set_mute(uint32_t i2c_file_desc, bool mute)
+{
+ return tef665x_audio_set_mute(i2c_file_desc, mute);//AUDIO_Set_Mute mode = 0 : disable mute
+}
+
+//-60 � +24 dB volume
+int audio_set_volume(uint32_t i2c_file_desc, int vol)
+{
+ return tef665x_audio_set_volume(i2c_file_desc, (uint16_t)vol);
+}
+
+/*
+module 64 APPL
+cmd 1 Set_OperationMode mode
+
+index
+1 mode
+ [ 15:0 ]
+ device operation mode
+ 0 = normal operation
+ 1 = radio standby mode (low-power mode without radio functionality)
+ (default)
+*/
+
+static int tef665x_audio_set_operationMode(uint32_t i2c_file_desc, uint16_t mode)
+{
+ _debug("normal: 0 standby: 1 requested", 1);
+ int ret = tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_APPL,
+ TEF665X_Cmd_Set_OperationMode,
+ 5,
+ mode);
+ if(ret)
+ {
+ _debug("was able to set the mode", ret);
+ }
+ else
+ {
+ _debug("FAILED, return", 0);
+ return 0;
+ }
+ return 1;
+}
+
+
+
+//TRUE = ON;
+//FALSE = OFF
+static void radio_powerSwitch(uint32_t i2c_file_desc, bool OnOff)
+{
+ tef665x_audio_set_operationMode(i2c_file_desc, OnOff? 0 : 1);//standby mode = 1
+}
+
+static void radio_modeSwitch(uint32_t i2c_file_desc, bool mode_switch, AR_TuningAction_t mode, uint16_t frequency)
+{
+
+ if(mode_switch) //FM
+ {
+ FM_tune_to(i2c_file_desc, mode, frequency);
+ }
+ else //AM
+ {
+ AM_tune_to(i2c_file_desc, mode, frequency);
+ }
+}
+
+/*
+module 32 FM
+cmd 81 Set_RDS
+
+index
+1 mode
+ [ 15:0 ]
+ RDS operation mode
+ 0 = OFF
+ 1 = decoder mode (default), output of RDS groupe data (Block A, B, C, D)
+ from get_rds_status, get_rds_data FM cmd 130/131
+
+2 restart
+ [ 15:0 ]
+ RDS decoder restart
+ 0 = no control
+ 1 = manual restart, starlooking for new RDS data immidiately
+ 2 = automatic restart after tuning (default)
+ 3 = flush, empty RDS output buffer.
+
+3 interface
+ [ 15:0 ]
+ 0 = no pin interface.
+ 2 = data available status output; active low (GPIO feature 'DAVN')
+ 4 = legecy 2-wire demodulator data and clock output ('RDDA' and 'RDCL')
+*/
+int tef665x_set_rds(uint32_t i2c_file_desc)
+{
+ return tef665x_set_cmd(i2c_file_desc, TEF665X_MODULE_FM,
+ TEF665X_Cmd_Set_RDS,
+ 9,//Total Bytes to be sent
+ TEF665X_Cmd_Set_RDS_mode, // default
+ TEF665X_Cmd_Set_RDS_autorestart, // restart after tune
+ 0x002 // no interface
+ );
+}
+
+
+/*
+ * @brief Adding Alternative Frequencies to RDS Data Structure
+ *
+ * @param uint8_t* : raw data of alternative frequency (Group 0A of RDS)
+ * @param rds_data_t* : Pointer to RDS Data Structure
+ * @return void
+ */
+void Extract_Alt_Freqs(uint8_t* buf,rds_data_t *Rds_STU)
+{
+ for(int Buffer_Index=6;Buffer_Index<8;Buffer_Index++)
+ {
+ if(buf[Buffer_Index]>204){
+ if(250>buf[Buffer_Index]&&buf[Buffer_Index]>224)
+ {
+ Rds_STU->Num_AlterFreq=buf[Buffer_Index]-224;
+
+ if(Rds_STU->Alternative_Freq_Counter == Rds_STU->Num_AlterFreq)
+ {
+ Rds_STU->Alternative_Freq_Counter = 0;
+ }
+ AlterFreqOffset=87500000;
+ }
+ else if(buf[Buffer_Index]==205)
+ {
+ fprintf(stderr, "Filler Code");
+ }
+ else if(buf[Buffer_Index]==224)
+ {
+ fprintf(stderr, "No AF Exists");
+ }
+ else if(buf[Buffer_Index]==250)
+ {
+ fprintf(stderr, "An LF/MF Frequency Follows");
+ AlterFreqOffset=144000;
+ }
+ else if(buf[Buffer_Index]>250)
+ {
+ printf("Alternative Frequency Not Assigned");
+ }
+ }
+ else if(buf[Buffer_Index]>0)
+ {
+ if(AlterFreqOffset == 87500000)
+ {
+ Rds_STU->Alternative_Freq[Rds_STU->Alternative_Freq_Counter]=
+ buf[Buffer_Index] * 100000 + AlterFreqOffset;
+
+ Rds_STU->Alternative_Freq_Counter++;
+
+ if(Rds_STU->Alternative_Freq_Counter == Rds_STU->Num_AlterFreq)
+ {
+ Rds_STU->Alternative_Freq_Counter = 0;
+ }
+ }
+ else if(AlterFreqOffset == 144000)
+ {
+ Rds_STU->Alternative_Freq[Rds_STU->Alternative_Freq_Counter]=
+ ((uint32_t)buf[Buffer_Index]) * 9000 + AlterFreqOffset;
+
+ Rds_STU->Alternative_Freq_Counter++;
+
+ if(Rds_STU->Alternative_Freq_Counter == Rds_STU->Num_AlterFreq)
+ {
+ Rds_STU->Alternative_Freq_Counter = 0;
+ }
+ }
+ else
+ {
+ printf("Alternative Frequency is not defined");
+ }
+ }
+ else
+ {
+ fprintf(stderr, "Alternative Frequency- Not to be used");
+ }
+ }
+}
+
+/*
+ * @brief Checking rds error code (determined by decoder)
+ *
+ * 0 : no error; block data was received with matching data and syndrome
+ * 1 : small error; possible 1 bit reception error detected; data is corrected
+ * 2 : large error; theoretical correctable error detected; data is corrected
+ * 3 : uncorrectable error; no data correction possible
+ *
+ * @param Errors : Error Code of blocks A,B,C and D of RDS
+ * @return void
+ */
+void Check_RDS_Error(uint8_t Errors[])
+{
+ for (int i=0;i<4;i++){
+ if(Errors[i]==1){
+ printf("RDS Block %d Reception Error; small error; possible 1 bit reception error detected; data is corrected",i+1);
+ }
+ else if(Errors[i]==2){
+ printf("RDS Block %d Reception Error; large error; theoretical correctable error detected; data is corrected",i+1);
+ }
+ else if(Errors[i]==3){
+ fprintf(stderr, "RDS Block %d Reception Error; uncorrectable error; no data correction possible",i+1);
+ }
+ }
+}
+
+/*
+ * @brief Getting rds_data_t and Process its raw_data
+ *
+ * @param rds_data_t * : Pointer to latest RDS Data Structure
+ */
+void *Process_RDS_Words(void* rds_words){
+ pthread_detach(pthread_self());
+
+ rds_data_t *Rds_STU = rds_words;
+ uint8_t *raw_data = Rds_STU->raw_data;
+ int8_t group_Ver = -1;
+ uint8_t GType0 = 0;
+ bool DI_Seg = 0;
+ bool M_S = 0;
+ bool TA = 0;
+
+ //Parse 1st Section
+ bool DataAvailable = (raw_data[0] >> 7) & 1;
+ bool DataLoss = (raw_data[0] >> 6) & 1 == 1;
+ bool DataAvailType = (raw_data[0] >> 5) & 1 == 0;
+ bool GroupType = (raw_data[0] >> 4) & 1;
+ bool SyncStatus = (raw_data[0] >> 1) & 1;
+
+ //Parse Last Section(Error Codes)
+ uint8_t Error_A = raw_data[10] >> 6;
+ uint8_t Error_B = raw_data[10] >> 4 & 3;
+ uint8_t Error_C = raw_data[10] >> 2 & 3;
+ uint8_t Error_D = raw_data[10] & 3;
+ uint8_t Errors[]={Error_A,Error_B,Error_C,Error_D};
+
+ //Inform user about Error Blocks Status Codes
+ Check_RDS_Error(Errors);
+
+ if(Error_A==0){
+ //Bytes 2 and 3 are inside Block A
+ //raw_data[2]and raw_data[3] Contains PI Code
+ Rds_STU->PICode=Convert8bto16b(&raw_data[2]);
+ }
+ else{
+ fprintf(stderr, "Error_A=%d",Error_A);
+ }
+
+ bool GTypeVer=GType0;
+ uint16_t GType=raw_data[4]>>4;
+ //Bytes 4 and 5 are inside Block B
+ if(Error_B==0){
+ GTypeVer=raw_data[4]>>3 & 1;
+ GType=raw_data[4]>>4;
+ Rds_STU->TrafficProgram=raw_data[4]>>2&1;
+ Rds_STU->PTY_Code= (raw_data[4] & 3) << 3 | raw_data[5] >> 5;
+ }
+
+ //Position Of Character
+ uint8_t CharPos=0;
+
+ //Extract Data based on Group Type values
+ switch (GType)
+ {
+ case 0:
+ {
+ if(Error_B==0)
+ {
+ CharPos = raw_data[5] & 3;
+
+ Rds_STU->TrafficAnnouncement = raw_data[5] >> 4 & 1;
+ Rds_STU->Music_Speech = raw_data[5] >> 3 & 1;
+ Rds_STU->DI_Seg = (raw_data[5] >> 2 & 1) * (2 ^ (3 - CharPos));
+ }
+
+ if(Error_C==0)
+ {
+ //Group Type 0A
+ if (GType==0)
+ {
+ Extract_Alt_Freqs(raw_data,Rds_STU);
+ }
+
+ //Group Type 0B
+ else
+ {
+ Rds_STU->PICode=Convert8bto16b(&raw_data[6]);
+ }
+ }
+
+ if(Error_D == 0 && Error_B == 0)
+ {
+ if(raw_data[8] != 0x7f)
+ {
+ Rds_STU->PS_Name[CharPos*2] = raw_data[8];
+ }
+ else
+ {
+ Rds_STU->PS_Name[CharPos*2] = (char)'\0';
+ }
+ if(raw_data[9] != 0x7f)
+ {
+ Rds_STU->PS_Name[CharPos*2+1] = raw_data[9];
+ }
+ else
+ {
+ Rds_STU->PS_Name[CharPos*2+1] = (char)'\0';
+ }
+ }
+ }
+ break;
+ case 1:
+ {
+ //Group Type 1A
+ if(GTypeVer == 0)
+ {
+ if(Error_D == 0)
+ {
+ Rds_STU->Day = raw_data[8] >> 3;
+ Rds_STU->Hour = raw_data[8] >> 3;
+ Rds_STU->Min = ((raw_data[8] & 7) << 2) | (raw_data[9] >> 6) ;
+ }
+ }
+ }
+ break;
+ case 2:
+ {
+ //Group Type 2A:
+ if(GTypeVer == 0)
+ {
+ uint8_t Text_pos = raw_data[5] & 15;
+
+ if(Error_B == 0 && Error_C == 0)
+ {
+
+ if(raw_data[6] !=0x7f && raw_data[6] != '\n')
+ {
+ Rds_STU->RT[Text_pos*4] = raw_data[6];
+ }
+ else{
+ Rds_STU->RT[Text_pos*4] = (char)'\0';
+ }
+ if(raw_data[7]!=0x7f&&raw_data[7]!='\n')
+ {
+ Rds_STU->RT[Text_pos*4+1] = raw_data[7];
+ }
+ else
+ {
+ Rds_STU->RT[Text_pos*4+1] = (char)'\0';
+ }
+ }
+ if(Error_B == 0 && Error_D == 0)
+ {
+ if(raw_data[8] != 0x7f && raw_data[8] != '\n')
+ {
+ Rds_STU->RT[Text_pos*4+2] = raw_data[8];
+ }
+ else{
+ Rds_STU->RT[Text_pos*4+2] = (char)'\0';
+ }
+ if(raw_data[9] != 0x7f && raw_data[9] != '\n')
+ {
+ Rds_STU->RT[Text_pos*4+3] = raw_data[9];
+ }
+ else
+ {
+ Rds_STU->RT[Text_pos*4+3] = (char)'\0';
+ }
+ }
+ }
+
+ //Group Type 2B:
+ else{
+ if(Error_B==0 && Error_D==0)
+ {
+ //Clear All Radio Text if flag was changed
+ if(raw_data[5] >> 4 & 1 != Rds_STU->Text_Changed)
+ {
+ memcpy(Rds_STU->RT, _Temp , 64);
+ }
+
+ uint8_t Text_pos = raw_data[5] & 15;
+ if(raw_data[8] != 0x7f && raw_data[8] != '\n')
+ {
+
+ Rds_STU->RT[Text_pos*2] = raw_data[8];
+ }
+ else{
+ Rds_STU->RT[Text_pos*2] = (char)'\0';
+ }
+ if(raw_data[9] != 0x7f && raw_data[9] != '\n')
+ {
+ Rds_STU->RT[Text_pos*2+1] = raw_data[9];
+ }
+ else
+ {
+ Rds_STU->RT[Text_pos*2+1] = (char)'\0';
+ }
+ }
+ }
+ }
+ break;
+ case 4:
+ {
+ //Group Type 4A
+ if(GTypeVer == 0)
+ {
+ if(Error_B == 0 && Error_C == 0 && Error_D == 0)
+ {
+ //Following caclulations are based on RDS Standard
+ uint32_t Modified_Julian_Day = ((raw_data[5] & 3) << 15) | (raw_data[6] << 7) | (raw_data[7]>>1);
+ int y2 = (int)((((double)Modified_Julian_Day)-15078.2)/365.25);
+ int m2 = (int)((((double)Modified_Julian_Day)-14956.1-((double)y2*365.25))/30.6001);
+ int d2 = (double)Modified_Julian_Day-14956-(int)(y2*365.25)-(int)(m2*30.6001);
+ int k = 0;
+
+ if(m2 == 14 || m2 == 15)
+ {
+ k = 1;
+ }
+
+ Rds_STU->Day = d2;
+ Rds_STU->Month = m2 - 1 + k * 12;
+ Rds_STU->Year = y2 + k;
+
+ uint8_t UTCHour = ((raw_data[7] & 1) << 4) | (raw_data[8] >> 4);
+ uint8_t UTCMinute = ((raw_data[8] & 15) << 2) | (raw_data[9] >> 6);
+
+ //Check Negative Offset
+ bool NegOff = raw_data[9] & 32;
+ uint8_t LocTimeOff = raw_data[9] & 31;
+
+ if(!NegOff)
+ {
+ Rds_STU->Min = UTCMinute + LocTimeOff % 2;
+ while(UTCMinute > 60)
+ {
+ UTCHour++;
+ UTCMinute = UTCMinute - 60;
+ }
+
+ Rds_STU->Hour = UTCHour + LocTimeOff / 2;
+ while(Rds_STU->Hour > 24){
+ Rds_STU->Hour = Rds_STU->Hour - 24;
+ }
+
+
+ }
+ else{
+ Rds_STU->Min = UTCMinute + LocTimeOff % 2;
+ while(UTCMinute < 0)
+ {
+ UTCHour--;
+ UTCMinute = UTCMinute + 60;
+ }
+ Rds_STU->Hour = UTCHour + LocTimeOff / 2;
+ while(Rds_STU->Hour<0)
+ {
+ Rds_STU->Hour = Rds_STU->Hour + 24;
+
+ }
+ }
+ }
+ }
+ //Group Type 4B
+ else
+ {
+ printf("Groupe Type 4B are not supported yet");
+ }
+ }
+ case 8:
+ {
+ printf("Groupe Type 8A and 8B are not supported yet");
+
+ }
+ case 10:
+ {
+ printf("Groupe Type 10A and 10B are not supported yet");
+ /*
+ if(Error_B == 0){
+ uint8_t pos = 0;
+ pos=(raw_data[5] & 1) * 4;
+
+ if(Error_C == 0){
+ Rds_STU->PTYN[pos] = raw_data[6];
+ Rds_STU->PTYN[pos+1] = raw_data[7];
+ Rds_STU->PTYN_Size = pos + 2;
+ }
+ if(Error_D == 0){
+ Rds_STU->PTYN[pos+2] = raw_data[8];
+ Rds_STU->PTYN[pos+3] = raw_data[9];
+ Rds_STU->PTYN_Size = pos + 4;
+ }
+ }
+ /**/
+ }
+ break;
+ default:
+ fprintf(stderr, "Unsupported Group %d",GType);
+ break;
+ }
+
+ if(!DataAvailable)
+ {
+ fprintf(stderr, "RDS Data is not available");
+ }
+
+ if(DataLoss)
+ {
+ fprintf(stderr, "previous data was not read, replaced by newer data");
+ }
+
+ if(GroupType == 0)
+ {
+ group_Ver = 0;
+ }
+ else
+ {
+ group_Ver = 1;
+ }
+
+ if(!SyncStatus)
+ {
+ fprintf(stderr, " RDS decoder not synchronized; no RDS data found");
+ }
+
+ if(GroupType != GTypeVer)
+ {
+ fprintf(stderr, "Version is not Correct?");
+ }
+}
+
+/*
+module 32 FM
+cmd 131 get RDS data
+
+index
+1 status
+ [ 15:0 ]
+ FM RDS reception status.
+ [15] = dta availableflag
+ 0 = no data
+ 1 = data available
+ [14] = data loss flag
+ 0 = no data loss
+ 1 = previose data not read, replaced by newer data.
+ [13] = data available type
+ 0 = group data; continuos operation.
+ 1 = first PI data;data with PI code following decoder sync.
+ [12] = groupe type.
+ 0 = type A; A-B-C-D group (with PI code in the block A)
+ 1 = type B; A-B-C'-D group (with PI code in the block A and C')
+ [ 8:0 ] reserved
+
+2 A_Block
+ [ 15:0 ] = A block data
+
+3 B_Block
+ [ 15:0 ] = B block data
+
+4 C_Block
+ [ 15:0 ] = C block data
+
+5 D_Block
+ [ 15:0 ] = D block data
+
+6 dec error
+ [ 15:0 ]
+ error code determined by decoder
+ [ 15:14 ] = A block error
+ [ 13:12 ] = B block error
+ [ 11:10 ] = C block error
+ [ 9:8 ] = D block error
+ 0 = no error found
+ 1 = small error, correctable. data is corrected.
+ 2 = larg error, correctable. data is corrected.
+ 3 = uncorrectable error.
+ [ 7:0 ] = reserved.
+*/
+/*
+ * @brief Get RDS Data fron Tef-665
+ *
+ * Getting RDS Data From I2C and Calling a thread to process raw data
+ *
+ * @param i2c_file_desc : I2C File Descriptor
+ * @param Rds_STU : RDS Data Structure
+ *
+ */
+int tef665x_get_rds_data(uint32_t i2c_file_desc, rds_data_t *Rds_STU)
+{
+
+ int ret;
+ uint8_t buf[12];
+
+ ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_FM,
+ TEF665X_Cmd_Get_RDS_Data,
+ buf, sizeof(buf));
+
+ if(ret == 1) {
+ memcpy(Rds_STU->raw_data,buf,12);
+ pthread_t t0;
+ pthread_create(&t0, NULL,Process_RDS_Words ,(void *) (Rds_STU));
+ }
+ return ret;
+}
+
+void Clear_RDS_Data(rds_data_t *Rds_STU){
+
+ Rds_STU-> Text_Changed=0;
+ Rds_STU-> TrafficAnnouncement=0;
+ Rds_STU-> TrafficProgram=0;
+ Rds_STU-> Music_Speech=0;
+
+ Rds_STU-> DI_Seg=0;
+ Rds_STU-> PTY_Code=0;
+ Rds_STU-> Num_AlterFreq=0;
+ Rds_STU->PTYN_Size=0;
+
+ Rds_STU-> Day=0;
+ Rds_STU-> Month=0;
+ Rds_STU-> Year=0;
+
+ Rds_STU-> Hour=0;
+ Rds_STU-> Min=0;
+
+ /*memcpy(Rds_STU->Alternative_Freq,_Temp,25);/**/
+ for(uint8_t i=0;i<25;i++){
+ Rds_STU->Alternative_Freq[i]=0;
+ }
+ memcpy(Rds_STU-> PS_Name,_Temp,8);
+ Rds_STU-> PS_Name[0]='\0';
+ memcpy(Rds_STU-> RT,_Temp,64);
+ Rds_STU-> RT[0]='\0';
+ memcpy(Rds_STU-> PTYN,_Temp,8);
+ Rds_STU-> PTYN[0]='\0';
+
+ Rds_STU-> PICode=0;
+ Rds_STU->Alternative_Freq_Counter=0;
+ Rds_STU->PTYN_Size=0;
+}
+
+//Check if RDS is available
+int tef665x_get_rds_status(uint32_t i2c_file_desc, uint16_t *status)
+{
+ int ret = 0;
+ uint8_t buf[2];
+
+ ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_FM,
+ TEF665X_Cmd_Get_RDS_Status,
+ buf, sizeof(buf));
+
+ if(ret == 1){
+ status[0] =buf[0];
+ status[1] =buf[1];
+ }
+
+ return ret;
+}
+
+static int tef665x_wait_active(uint32_t i2c_file_desc)
+{
+ TEF665x_STATE status;
+ //usleep(50000);
+ if(SET_SUCCESS == appl_get_operation_status(i2c_file_desc, &status))
+ {
+ printf("got status", 1);
+ if((status != eDevTEF665x_Boot_state) && (status != eDevTEF665x_Idle_state))
+ {
+ printf("active status", 1);
+
+ if(SET_SUCCESS == tef665x_para_load(i2c_file_desc))
+ {
+ _debug("parameters loaded", 1);
+ }
+ else
+ {
+ _debug("parameters not loaded", 0);
+ return 0;
+ }
+
+ if(current_band == RADIO_BAND_FM){
+ FM_tune_to(i2c_file_desc, eAR_TuningAction_Preset, current_fm_frequency / 10000);// tune to min
+ } else {
+ AM_tune_to(i2c_file_desc, eAR_TuningAction_Preset, current_am_frequency / 1000);// tune to min
+ }
+
+ if(SET_SUCCESS == audio_set_mute(i2c_file_desc, 1))//unmute=0
+ {
+ _debug("muted", 1);
+ }
+ else
+ {
+ _debug("not muted", 0);
+ return 0;
+ }
+
+ // //if(SET_SUCCESS == audio_set_volume(i2c_file_desc, 35))//set to -10db
+ // {
+ // _debug("set vol to", 25);
+ // }
+
+ // else
+ // {
+ // _debug("vol not set", 0);
+ // return 0;
+ // }
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void tef665x_chip_init(int i2c_file_desc)
+{
+ if(1 == tef665x_power_on(i2c_file_desc)) _debug("tef665x_power_on", 1);
+ usleep(50000);
+ if(1 == tef665x_boot_state(i2c_file_desc)) _debug("tef665x_boot_state", 1);
+ usleep(100000);
+ if(1 == tef665x_idle_state(i2c_file_desc)) _debug("tef665x_idle_state", 1);
+ usleep(200000);
+ if(1 == tef665x_wait_active(i2c_file_desc)) _debug("tef665x_wait_active", 1);
+ //if you want to use analog output comment below command, or pass 1 to it.
+ if(SET_SUCCESS != tef665x_audio_set_ana_out(i2c_file_desc, TEF665X_Cmd_Set_Output_signal_dac, 0))
+ {
+ _debug("Set DAC to OFF failed", 0);
+ //return 0;
+ }
+
+ if(SET_SUCCESS != tef665x_set_output_src(i2c_file_desc, TEF665X_Cmd_Set_Output_signal_i2s,
+ TEF665X_Cmd_Set_Output_source_aProcessor))
+ {
+ _debug("Set output failed", 0);
+ //return 0;
+ }
+ //this is needed to use digital output
+ if(SET_SUCCESS != tef665x_audio_set_dig_io(i2c_file_desc, TEF665X_AUDIO_CMD_22_SIGNAL_i2s1,
+ TEF665X_AUDIO_CMD_22_MODE_voltage,
+ TEF665X_AUDIO_CMD_22_FORMAT_16,
+ TEF665X_AUDIO_CMD_22_OPERATION_slave,
+ TEF665X_AUDIO_CMD_22_SAMPLERATE_48K))
+ {
+ _debug("Setup i2s failed", 0);
+ //return 0;
+ }
+
+
+}
+
+
+static int i2c_init(const char *i2c, int state, uint32_t *i2c_file_desc)
+{
+ int fd = 0, t;
+
+ if(state == _open)
+ {
+ fd = open(i2c, O_RDWR);
+
+ if(fd < 0)
+ {
+ _debug("could not open %s", i2c);
+ return fd;
+ }
+
+ t = ioctl(fd, I2C_SLAVE, I2C_ADDRESS);
+ if (t < 0)
+ {
+ _debug("could not set up slave ", 0);
+ return t;
+ }
+ *i2c_file_desc = fd;
+ }
+ else
+ {
+ close(*i2c_file_desc);
+ }
+
+ return 0;
+}
+
+static void tef665x_start(void)
+{
+ int ret;
+
+ if(!initialized)
+ return;
+
+ _debug("file_desc ", file_desc);
+
+ audio_set_mute(file_desc, 0);
+
+ if(!running) {
+
+ // Start pipeline
+ ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
+ _debug("gst_element_set_state to play", ret);
+ running = true;
+ }
+ }
+
+/*
+ * @brief Send_Rds_Result to rds subscribers
+ *
+ * @param rds_data_t : a rds message structure
+ * @return The JsonObject of rds info
+ */
+void *Send_Rds_Result(rds_data_t* RDS_Message){
+ //Kill the thread when it was over
+ pthread_detach(pthread_self());
+
+ json_object *ret_json;
+ json_object *Alternative_Freqs;
+
+
+ ret_json = json_object_new_object();
+ Alternative_Freqs=json_object_new_array();
+
+
+
+ for(uint8_t af=0 ; af<25 ; af++)
+ {
+ if(RDS_Message->Alternative_Freq[af]!=NULL&&RDS_Message->Alternative_Freq[af]!=0)
+ {
+ json_object_array_add(Alternative_Freqs,json_object_new_int(RDS_Message->Alternative_Freq[af]));
+ }
+ }
+
+ //Prepare JSon Object
+ json_object_object_add(ret_json, "name" , json_object_new_string(RDS_Message->PS_Name));
+ json_object_object_add(ret_json, "radiotext" , json_object_new_string(RDS_Message->RT));
+ json_object_object_add(ret_json, "alternatives" , (Alternative_Freqs));
+ json_object_object_add(ret_json, "minute" , json_object_new_int (RDS_Message->Min));
+ json_object_object_add(ret_json, "hour" , json_object_new_int (RDS_Message->Hour));
+ json_object_object_add(ret_json, "day" , json_object_new_int (RDS_Message->Day));
+ json_object_object_add(ret_json, "month" , json_object_new_int (RDS_Message->Month));
+ json_object_object_add(ret_json, "year" , json_object_new_int (RDS_Message->Year));
+ json_object_object_add(ret_json, "pi" , json_object_new_int (RDS_Message->PICode));
+ json_object_object_add(ret_json, "pty" , json_object_new_int (RDS_Message->PTY_Code));
+ json_object_object_add(ret_json, "ta" , json_object_new_int (RDS_Message->TrafficAnnouncement));
+ json_object_object_add(ret_json, "tp" , json_object_new_int (RDS_Message->TrafficProgram));
+ json_object_object_add(ret_json, "ms" , json_object_new_int (RDS_Message->Music_Speech));
+
+ //Send JsonObject to rds Subscribers
+ if(rds_callback){
+ rds_callback(ret_json);
+ }
+
+ return ret_json;
+}
+
+/*
+ * @brief Create an infinit Loop to get RDS Packets and Send them to subscribers
+ *
+ * RDS data will be available every 85 ms;
+ * Currently availability of RDS is checkes by tef665x_get_rds_status function
+ *
+ * @param rds_data_t : a rds message structure
+ * @return The JsonObject of latest rds info
+ */
+void *Get_RDS_Packets(rds_data_t *StuRDS){
+ pthread_detach(pthread_self());
+ uint32_t fd = 0;
+
+ int ret = i2c_init(I2C_DEV, _open, &fd);
+ uint8_t status[2];
+
+ ret=tef665x_get_rds_status(fd, status);
+
+ if(ret==1){
+ if(status[0]>7){
+ //RDS must update all the time, except the times we are scanning or changing frequency
+ //when scanning or changing frequncy, we unlock RDS_Mutex and it will end this thread
+ for (int ref_cnt=0; pthread_mutex_trylock(&RDS_Mutex) != 0;ref_cnt++){
+ //Get New RDS Data
+ tef665x_get_rds_data(fd,StuRDS);
+
+ //Send RDS Data after rexeiving 22 Packets
+ if(ref_cnt%22==0){
+ pthread_t t0;
+ pthread_create(&t0, NULL,Send_Rds_Result ,(void *) (&RDS_Message));
+ }
+
+ //Wait for 85ms before reading available rds data
+ usleep(85000);
+ }
+ pthread_mutex_unlock (&RDS_Mutex);
+ }
+
+ else{
+ fprintf(stderr, "RDS is Not Valid0");
+ }
+ }
+
+ else{
+ fprintf(stderr, "RDS is Not Valid1");
+ }
+ i2c_init(I2C_DEV, _close, &fd);
+}
+
+/*
+ * @brief Free Allocated Memory for Scan Thread and Unlock Scan Mutex
+ *
+ * @param scan_data : scan_data_t contains direction of search and callback
+ * for station_found event
+ */
+static void scan_cleanup_handler(void *scan_data)
+{
+ pthread_mutex_unlock(&scan_mutex);
+ free(scan_data);
+ scanning=false;
+}
+
+/*
+ * @brief Create a loop to scan from current frequency to find a valid frequency
+ *
+ * If found a valid frequency, send station_found to subscribers and break the loop;
+ * If the direction was forward and reach the maximum frequency, Search Must continue
+ * from minimum frequency
+ * If the direction was backward and reach the minimum frequency, Search Must continue
+ * from maximum frequency
+ * If no valid frequency found, scan will stop at the begining point
+ * If stop_scan called, scan_mutex will be unlocked and thread must be stopped
+ *
+ * @param scan_data : scan_data_t contains direction of search and callback
+ * for station_found event
+ */
+void *scan_frequencies(scan_data_t* scan_data){
+ pthread_cleanup_push(scan_cleanup_handler, (void *)scan_data);
+
+ //Kill the thread when it was over
+ pthread_detach(pthread_self());
+
+ //Set Scan Flag
+ scanning=true;
+
+ //"Unlock Mutex" Flag
+ bool unlck_mtx = false;
+ uint32_t new_freq = 0;
+ uint32_t init_freq = 0;
+
+ init_freq = current_band == RADIO_BAND_FM ? current_fm_frequency : current_am_frequency;
+
+ //First Mute Current Frequency
+ tef665x_search_frequency(init_freq);
+
+ //freq_step will be negative if direction was backward and positive if direction was forward
+ uint32_t freq_step = tef665x_get_frequency_step(current_band) * (scan_data->direction==RADIO_SCAN_FORWARD?1:-1);
+
+ //Continue loop until reaching the initial frequency
+ while(init_freq != new_freq)
+ {
+ //Check Status of scan_mutex
+ unlck_mtx = pthread_mutex_trylock(&scan_mutex)==0;
+
+ //break the loop if scan_mutex was unlocked
+ if(unlck_mtx)
+ {
+ break;
+ }
+
+ if(current_band==RADIO_BAND_FM)
+ {
+ new_freq = current_fm_frequency + freq_step;
+
+ //Searching Step is 100 KHz
+ //If frequency reached to initial point, the search must stop
+ while (((new_freq/10000)%10)!=0 && init_freq != new_freq){
+ new_freq = new_freq+freq_step;
+ }
+ }
+ else
+ {
+ new_freq = current_am_frequency + freq_step;
+ }
+
+ //Set Freq to min when it was more than Max Value
+ if(new_freq>tef665x_get_max_frequency(current_band))
+ {
+ new_freq=tef665x_get_min_frequency(current_band);
+ }
+
+ //Set Freq to max when it was less than Min Value
+ if(new_freq<tef665x_get_min_frequency(current_band))
+ {
+ new_freq=tef665x_get_max_frequency(current_band);
+ }
+
+ //Tune to new frequency
+ tef665x_search_frequency(new_freq);
+
+ //wait 30 ms to make sure quality data is available
+ for(int i=0;i<40;i++)
+ {
+ usleep(1000);
+
+ //Check scan_mutex lock for handling stop_scan
+ unlck_mtx=pthread_mutex_trylock(&scan_mutex)==0;
+ if(unlck_mtx)
+ {
+ break;
+ }
+ }
+ if(unlck_mtx)
+ {
+ break;
+ }
+
+ //Get Quality of tuned frequeency
+ tef665x_get_quality_info();//Get_quality_status();
+
+ if((quality.rssi >260 /*&& ->.usn<100/**/) || quality.bandw>1200)
+ {
+ //Send frequency value
+ if(scan_data->callback)
+ {
+ scan_data->callback(new_freq,NULL);
+ }
+
+ break;
+ }
+ usleep(100);
+ }
+
+ //Calling last pthread_cleanup_push
+ pthread_cleanup_pop(1);
+}
+
+/*
+ * @brief Get latest RDS Info and send rds jsonObject as response
+ *
+ * @return: cast rds_json(json_object) to (char *) and return result as response
+ */
+static char *tef665x_get_rds_info(void)
+{
+ //If Getting RDS Result wasn't already started, Start it now
+ if(pthread_mutex_trylock(&RDS_Mutex) == 0)
+ {
+ //AFB_DEBUG("Create the thread.");
+ pthread_create(&rds_thread, NULL,Get_RDS_Packets ,(void *) (&RDS_Message));
+ }
+
+ //Send latest available rds data
+ json_object *rds_json=(json_object *)Send_Rds_Result(&RDS_Message);
+
+ //Convert json_object to char* and send it as response
+ return (char *)json_object_to_json_string(rds_json);
+}
+
+/*
+ * @brief Get latest quality Info and send quality parameters as response
+ *
+ * module 32/33 FM/AM
+ * cmd 129 Get_Quality_Data
+ *
+ * index
+ * 1 status
+ * [ 15:0 ]
+ * quality detector status
+ * [15] = AF_update flag
+ * 0 = continuous quality data with time stamp
+ * 1 = AF_Update sampled data
+ * [14:10] = reserved
+ * 0 = no data loss
+ * 1 = previose data not read, replaced by newer data.
+ * [9:0] = quality time stamp
+ * 0 = tuning is in progress, no quality data available
+ * 1 … 320 (* 0.1 ms) = 0.1 … 32 ms after tuning,
+ * quality data available, reliability depending on time stamp
+ * 1000 = > 32 ms after tuning
+ * quality data continuously updated
+ *
+ * 2 level
+ * [ 15:0 ] (signed)
+ * level detector result
+ * -200 … 1200 (0.1 * dBuV) = -20 … 120 dBuV RF input level
+ * actual range and accuracy is limited by noise and agc
+ *
+ * 3 usn
+ * [ 15:0 ] = noise detector
+ * FM ultrasonic noise detector
+ * 0 … 1000 (*0.1 %) = 0 … 100% relative usn detector result
+ *
+ * 4 wam
+ * [ 15:0 ] = radio frequency offset
+ * FM ‘wideband-AM’ multipath detector
+ * 0 … 1000 (*0.1 %) = 0 … 100% relative wam detector result
+ *
+ * 5 offset
+ * [ 15:0 ] (signed) = radio frequency offset
+ * -1200 … 1200 (*0.1 kHz) = -120 kHz … 120 kHz radio frequency error
+ * actual range and accuracy is limited by noise and bandwidth
+ *
+ * 6 bandwidth
+ * [ 15:0 ] = IF bandwidth
+ * FM 560 … 3110 [*0.1 kHz] = IF bandwidth 56 … 311 kHz; narrow … wide
+ * AM 30 … 80 [*0.1 kHz] = IF bandwidth 3 … 8 kHz; narrow … wide
+ *
+ * 7 modulation
+ * [ 15:0 ] = modulation detector
+ * FM 0 … 1000 [*0.1 %] = 0 … 100% modulation = 0 … 75 kHz FM dev.
+ *
+ * @return: cast station_quality_t pointer as response
+ *
+ */
+
+static station_quality_t *tef665x_get_quality_info(void)
+{
+ uint32_t i2c_file_desc=0;
+ uint8_t data[14];
+
+ int ret = i2c_init(I2C_DEV, _open, &i2c_file_desc);
+ if(current_band==RADIO_BAND_FM)
+ {
+ ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_FM,
+ TEF665X_Cmd_Get_Quality_Data,
+ data, sizeof(data));
+ }
+ else
+ {
+ ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_AM,
+ TEF665X_Cmd_Get_Quality_Data,
+ data, sizeof(data));
+ }
+ i2c_init(I2C_DEV, _close, &i2c_file_desc);
+
+ quality.af_update = data[0]&0b10000000;
+ quality.time_stamp = ((data[0]&0b00000011)<<8 | data[1]);
+ quality.rssi = (data[2] << 8 | data[3] );
+ quality.usn = (data[4] << 8 | data[5] );
+ quality.bandw = (data[10]<< 8 | data[11]);
+
+ return &quality;
+}
+
+/*
+ * @brief Start Scan
+ *
+ * @param radio_scan_direction_t direction which is the scan direction and can be
+ * RADIO_SCAN_FORWARD or RADIO_SCAN_BACKWARD
+ * @param radio_scan_callback_t callback which is the callback for sending result of search to
+ * station_found ecent subscribers
+ * @return void
+ */
+static void tef665x_scan_start(radio_scan_direction_t direction,
+ radio_scan_callback_t callback,
+ void *data)
+{
+ //Stop RDS if enabled
+ pthread_mutex_unlock (&RDS_Mutex);
+
+ //Stop current scan:
+ if(scanning)
+ {
+ tef665x_scan_stop();
+ }
+
+ scan_data_t *inputs;
+
+ //Clean RDS Message since frequency will change
+ Clear_RDS_Data(&RDS_Message);
+ usleep(10000);
+
+ //AFB_DEBUG("check Mutex Condition");
+
+ //check if is there any activated search
+ if(pthread_mutex_trylock(&scan_mutex)==0&&!scanning)
+ {
+ //AFB_DEBUG("Start Scanning...");
+
+ inputs=malloc(sizeof(*inputs));
+ if(!inputs)
+ return -ENOMEM;
+
+ inputs->direction= direction;
+ inputs->callback= callback;
+ inputs->data=data;
+
+ pthread_create(&scan_thread, NULL,scan_frequencies ,(void *) inputs);
+ }
+}
+
+/*
+ * @brief Stop Scan
+ *
+ * By unlocking scan_mutex, Scan thread will be stopped safely and update scanning flag
+ *
+ * @return void
+ */
+static void tef665x_scan_stop(void)
+{
+ pthread_mutex_unlock(&scan_mutex);
+ while(scanning)
+ {
+ usleep(100);
+ //AFB_DEBUG(" Wait for unlocking scan Thread");
+ }
+}
+
+/*
+module 32 / 33 FM / AM
+cmd 133 Get_Signal_Status | status
+index
+1 status
+ [ 15:0 ] = Radio signal information
+ [15] = 0 : mono signal
+ [15] = 1 : FM stereo signal (stereo pilot detected)
+
+ [14] = 0 : analog signal
+ [14] = 1 : digital signal (blend input activated by digital processor or control)
+ (TEF6659 only)
+*/
+radio_stereo_mode_t tef665x_get_stereo_mode(void)
+{
+ uint32_t i2c_file_desc = 0;
+ int ret = i2c_init(I2C_DEV, _open, &i2c_file_desc);
+ uint8_t data[2];
+ if(current_band==RADIO_BAND_FM){
+ ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_FM,
+ TEF665X_Cmd_Get_Signal_Status,
+ data, sizeof(data));
+ }
+ else{
+ ret = tef665x_get_cmd(i2c_file_desc, TEF665X_MODULE_AM,
+ TEF665X_Cmd_Get_Signal_Status,
+ data, sizeof(data));
+ }
+ i2c_init(I2C_DEV, _close, &i2c_file_desc);
+ return data[0]>>7 ==1 ? RADIO_MODE_STEREO:RADIO_MODE_MONO;
+}
+
+static void tef665x_stop(void)
+{
+ int ret;
+ GstEvent *event;
+ audio_set_mute(file_desc, 1);
+
+ if(initialized && running) {
+ // Stop pipeline
+ running = false;
+ ret = gst_element_set_state(pipeline, GST_STATE_PAUSED);
+ _debug("gst_element_set_state to pause", ret);
+
+ // Flush pipeline
+ // This seems required to avoidstatic stutters on starts after a stop
+ event = gst_event_new_flush_start();
+ gst_element_send_event(GST_ELEMENT(pipeline), event);
+ event = gst_event_new_flush_stop(TRUE);
+ gst_element_send_event(GST_ELEMENT(pipeline), event);
+ }
+}
+
+static int tef665x_probe()
+{
+ int rc;
+
+ if(present)
+ return 0;
+
+ rc = i2c_init(I2C_DEV, _open, &file_desc);
+ if(rc < 0) {
+ fprintf(stderr, "tef665x not present");
+ return -1;
+ }
+ _debug("file_desc= ", file_desc);
+
+ rc = appl_get_identification(file_desc);
+ if(rc != 1){
+ fprintf(stderr, "no tef665x!");
+ return -1;
+ }
+
+ present = true;
+ return 0;
+}
+
+static int tef665x_init()
+{
+ char gst_pipeline_str[GST_PIPELINE_LEN];
+ int rc;
+
+ if(!present)
+ return -1;
+
+ if(initialized)
+ return 0;
+
+ current_am_frequency = known_am_band_plans[am_bandplan].min;
+ current_fm_frequency = known_fm_band_plans[fm_bandplan].min;
+
+ current_band = RADIO_BAND_AM;
+
+ radio_powerSwitch(file_desc, 1);
+
+ tef665x_chip_init(file_desc);
+
+ // Initialize GStreamer
+ gst_init(NULL, NULL);
+
+ // Use PipeWire output
+ // This pipeline is working on imx6solo, the important thing, up to now, is that it gets xrun error every few seconds.
+ // I believe it's related to wireplumber on imx6.
+ rc = snprintf(gst_pipeline_str,
+ GST_PIPELINE_LEN,
+ "alsasrc device=hw:1,0 ! audioconvert ! audioresample ! audio/x-raw, rate=48000, channels=2 \
+ ! pipewiresink stream-properties=\"p,media.role=Multimedia\"");
+
+ if(rc >= GST_PIPELINE_LEN) {
+ fprintf(stderr, "pipeline string too long");
+ return -1;
+ }
+ printf("pipeline: , %s\n", gst_pipeline_str);
+
+ pipeline = gst_parse_launch(gst_pipeline_str, NULL);
+ if(!pipeline) {
+ fprintf(stderr, "pipeline construction failed!");
+ return -1;
+ }
+
+ // Start pipeline in paused state
+ rc = gst_element_set_state(pipeline, GST_STATE_PAUSED);
+ _debug("gst_element_set_state to pause (at the begining)", rc);
+
+ rc = gst_bus_add_watch(gst_element_get_bus(pipeline), (GstBusFunc) handle_message, NULL);
+ _debug("gst_bus_add_watch rc", rc);
+
+ //Initialize Mutex Lock for Scan and RDS
+ pthread_mutex_init(&scan_mutex, NULL);
+ pthread_mutex_init (&RDS_Mutex, NULL);
+
+ initialized = true;
+
+ tef665x_start();
+ return 0;
+}
+
+static void tef665x_set_frequency_callback(radio_freq_callback_t callback,
+ void *data)
+{
+ freq_callback = callback;
+ freq_callback_data = data;
+}
+static void tef665x_set_rds_callback(radio_rds_callback_t callback)
+{
+ rds_callback = callback;
+
+}
+static void tef665x_set_output(const char *output)
+{
+}
+
+static radio_band_t tef665x_get_band(void)
+{
+ _debug("band", current_band);
+ return current_band;
+}
+
+static void tef665x_set_band(radio_band_t band)
+{
+ uint32_t fd = 0;
+ int ret = i2c_init(I2C_DEV, _open, &fd);
+
+ _debug("i2c_init ret value", ret);
+
+ if(band == RADIO_BAND_FM){
+ current_band = band;
+ FM_tune_to(fd, eAR_TuningAction_Preset, current_fm_frequency / 10000);
+ } else {
+ current_band = band;
+ AM_tune_to(fd, eAR_TuningAction_Preset, current_am_frequency / 1000);
+ }
+
+ i2c_init(I2C_DEV, _close, &fd);
+
+ _debug("band", current_band);
+}
+
+static uint32_t tef665x_get_frequency(void)
+{
+ if(current_band == RADIO_BAND_FM){
+ return current_fm_frequency;
+ } else {
+ return current_am_frequency;
+ }
+}
+
+static void tef665x_set_alternative_frequency(uint32_t frequency)
+{
+ uint32_t fd = 0;
+ int ret = i2c_init(I2C_DEV, _open, &fd);
+
+ if(current_band == RADIO_BAND_FM)
+ {
+ FM_tune_to(fd, eAR_TuningAction_AF_Update, frequency / 10000);
+ }
+
+ i2c_init(I2C_DEV, _close, &fd);
+}
+
+static void tef665x_set_frequency(uint32_t frequency)
+{
+ uint32_t fd = 0;
+
+ if(!initialized)
+ return;
+
+ if(scanning)
+ return;
+
+ if(current_band == RADIO_BAND_FM) {
+ if(frequency < known_fm_band_plans[fm_bandplan].min ||
+ frequency > known_fm_band_plans[fm_bandplan].max ) {
+ _debug("invalid FM frequency", frequency);
+ return;
+ }
+ } else {
+ if(frequency < known_am_band_plans[am_bandplan].min ||
+ frequency > known_am_band_plans[am_bandplan].max ) {
+ _debug("invalid AM frequency", frequency);
+ return;
+ }
+ }
+
+ int ret = i2c_init(I2C_DEV, _open, &fd);
+
+ if(current_band == RADIO_BAND_FM){
+ current_fm_frequency = frequency;
+ _debug("frequency set to FM :", frequency);
+ FM_tune_to(fd, eAR_TuningAction_Preset, frequency / 10000);
+ } else {
+ current_am_frequency = frequency;
+ _debug("frequency set to AM :", frequency);
+ AM_tune_to(fd, eAR_TuningAction_Preset, frequency / 1000);
+ }
+ i2c_init(I2C_DEV, _close, &fd);
+
+ //Send Frequency data to subscribers
+ if(freq_callback)
+ {
+ freq_callback(frequency, freq_callback_data);
+ }
+
+ //Start RDS if the band was FM
+ if(current_band==RADIO_BAND_FM){
+ //Unlock Mutex
+ pthread_mutex_unlock (&RDS_Mutex);
+
+ //Clean RDS Message
+ Clear_RDS_Data(&RDS_Message);
+
+ //Wait to make sure rds thread is finished
+ usleep(300000);
+
+ //Restart RDS
+ tef665x_get_rds_info();
+ }
+}
+
+/*
+ * @brief Tune to a frequency in search mode
+ *
+ * Tune to new program and stay muted
+ * Sending new frequency to subscribers
+ *
+ * @param uint32_t which is the frequecy to be tuned
+ * @return void
+ */
+static void tef665x_search_frequency(uint32_t frequency)
+{
+ uint32_t fd = 0;
+ int ret = i2c_init(I2C_DEV, _open, &fd);
+ if(current_band == RADIO_BAND_FM)
+ {
+ current_fm_frequency = frequency;
+ _debug("frequency set to FM :", frequency);
+ FM_tune_to(fd, eAR_TuningAction_Search, frequency / 10000);
+
+ }
+ else
+ {
+ current_am_frequency = frequency;
+ _debug("frequency set to AM :", frequency);
+ AM_tune_to(fd, eAR_TuningAction_Search, frequency / 1000);
+ }
+ i2c_init(I2C_DEV, _close, &fd);
+
+ //Send Frequency data to subscribers
+ if(freq_callback)
+ {
+ freq_callback(frequency, freq_callback_data);
+ }
+}
+
+static int tef665x_band_supported(radio_band_t band)
+{
+ if(band == RADIO_BAND_FM || band == RADIO_BAND_AM)
+ return 1;
+ return 0;
+}
+
+static uint32_t tef665x_get_min_frequency(radio_band_t band)
+{
+ if(band == RADIO_BAND_FM) {
+ return known_fm_band_plans[fm_bandplan].min;
+ } else {
+ return known_am_band_plans[am_bandplan].min;
+ }
+}
+
+static uint32_t tef665x_get_max_frequency(radio_band_t band)
+{
+ if(band == RADIO_BAND_FM) {
+ return known_fm_band_plans[fm_bandplan].max;
+ } else {
+ return known_am_band_plans[am_bandplan].max;
+ }
+}
+
+static uint32_t tef665x_get_frequency_step(radio_band_t band)
+{
+ uint32_t ret = 0;
+
+ switch (band) {
+ case RADIO_BAND_AM:
+ ret = known_am_band_plans[am_bandplan].step;
+ break;
+ case RADIO_BAND_FM:
+ ret = known_fm_band_plans[fm_bandplan].step;
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+radio_impl_ops_t tef665x_impl_ops = {
+ .name = "TEF665x",
+ .probe = tef665x_probe,
+ .init = tef665x_init,
+ .start = tef665x_start,
+ .stop = tef665x_stop,
+ .set_output = tef665x_set_output,
+ .get_frequency = tef665x_get_frequency,
+ .set_frequency = tef665x_set_frequency,
+ .set_frequency_callback = tef665x_set_frequency_callback,
+ .set_rds_callback=tef665x_set_rds_callback,
+ .get_band = tef665x_get_band,
+ .set_band = tef665x_set_band,
+ .band_supported = tef665x_band_supported,
+ .get_min_frequency = tef665x_get_min_frequency,
+ .get_max_frequency = tef665x_get_max_frequency,
+ .get_frequency_step = tef665x_get_frequency_step,
+ .scan_start = tef665x_scan_start,
+ .scan_stop = tef665x_scan_stop,
+ .get_stereo_mode = tef665x_get_stereo_mode,
+ //.set_stereo_mode = tef665x_set_stereo_mode,*/
+ .get_rds_info = tef665x_get_rds_info,
+ .get_quality_info = tef665x_get_quality_info,
+ .set_alternative_frequency = tef665x_set_alternative_frequency
+};
diff --git a/src/radio_impl_tef665x.h b/src/radio_impl_tef665x.h
new file mode 100644
index 0000000..a95fd99
--- /dev/null
+++ b/src/radio_impl_tef665x.h
@@ -0,0 +1,23 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef _RADIO_IMPL_TEF665X_H
+#define _RADIO_IMPL_TEF665X_H
+
+#include "radio_impl.h"
+
+extern radio_impl_ops_t tef665x_impl_ops;
+
+
+#endif /* _RADIO_IMPL_TEF665X_H */
+
diff --git a/src/radio_output.h b/src/radio_output.h
new file mode 100644
index 0000000..bfa13cd
--- /dev/null
+++ b/src/radio_output.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _RADIO_OUTPUT_H
+#define _RADIO_OUTPUT_H
+
+int radio_output_open(void);
+
+int radio_output_start(void);
+
+void radio_output_stop(void);
+
+void radio_output_close(void);
+
+ssize_t radio_output_write(void *buf, int len);
+
+#endif /* _RADIO_OUTPUT_H */
+
diff --git a/src/radio_output_gstreamer.c b/src/radio_output_gstreamer.c
new file mode 100644
index 0000000..e098d2d
--- /dev/null
+++ b/src/radio_output_gstreamer.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2018, 2019 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <gst/gst.h>
+
+#include "radio_output.h"
+#include "rtl_fm.h"
+
+// Flag to enable using GST_STATE_READY instead of GST_STATE_PAUSED to trigger
+// Wireplumber policy mechanism. Hopefully temporary.
+#define WIREPLUMBER_WORKAROUND
+
+// Output buffer
+static unsigned int extra;
+static int16_t extra_buf[1];
+static unsigned char *output_buf;
+
+// GStreamer state
+static GstElement *pipeline, *appsrc;
+static bool running;
+
+int radio_output_open()
+{
+ unsigned int rate = 24000;
+ GstElement *queue, *convert, *sink, *resample;
+ char *p;
+
+ if(pipeline)
+ return 0;
+
+ // Initialize GStreamer
+#ifdef DEBUG
+ unsigned int argc = 2;
+ char **argv = malloc(2 * sizeof(char*));
+ argv[0] = strdup("test");
+ argv[1] = strdup("--gst-debug-level=5");
+ gst_init(&argc, &argv);
+#else
+ gst_init(NULL, NULL);
+#endif
+
+ // Setup pipeline
+ // NOTE: With our use of the simple buffer pushing mode, there seems to
+ // be no need for a mainloop, so currently not instantiating one.
+ pipeline = gst_pipeline_new("pipeline");
+ appsrc = gst_element_factory_make("appsrc", "source");
+ queue = gst_element_factory_make("queue", "queue");
+ convert = gst_element_factory_make("audioconvert", "convert");
+ resample = gst_element_factory_make("audioresample", "resample");
+ sink = gst_element_factory_make("pipewiresink", "sink");
+ if(!(pipeline && appsrc && queue && convert && resample && sink)) {
+ fprintf(stderr, "pipeline element construction failed!\n");
+ }
+ g_object_set(G_OBJECT(appsrc), "caps",
+ gst_caps_new_simple("audio/x-raw",
+ "format", G_TYPE_STRING, "S16LE",
+ "rate", G_TYPE_INT, rate,
+ "channels", G_TYPE_INT, 2,
+ "layout", G_TYPE_STRING, "interleaved",
+ "channel-mask", G_TYPE_UINT64, 3,
+ NULL), NULL);
+ gst_util_set_object_arg(sink, "stream-properties", "p,media.role=Multimedia");
+
+ if((p = getenv("RADIO_OUTPUT"))) {
+ fprintf(stderr, "Using output device %s\n", p);
+ g_object_set(sink, "device", p, NULL);
+ }
+ gst_bin_add_many(GST_BIN(pipeline), appsrc, queue, convert, resample, sink, NULL);
+ gst_element_link_many(appsrc, queue, convert, resample, sink, NULL);
+ //gst_bin_add_many(GST_BIN(pipeline), appsrc, convert, resample, sink, NULL);
+ //gst_element_link_many(appsrc, convert, resample, sink, NULL);
+
+ // Set up appsrc
+ // NOTE: Radio seems like it matches the use case the "is-live" property
+ // is for, but setting it seems to require a lot more work with
+ // respect to latency settings to make the pipeline work smoothly.
+ // For now, leave it unset since the result seems to work
+ // reasonably well.
+ g_object_set(G_OBJECT(appsrc),
+ "stream-type", 0,
+ "format", GST_FORMAT_TIME,
+ NULL);
+
+ // Start pipeline in paused state
+#ifdef WIREPLUMBER_WORKAROUND
+ gst_element_set_state(pipeline, GST_STATE_READY);
+#else
+ gst_element_set_state(pipeline, GST_STATE_PAUSED);
+#endif
+
+ // Set up output buffer
+ extra = 0;
+ output_buf = malloc(sizeof(unsigned char) * RTL_FM_MAXIMUM_BUF_LENGTH);
+
+ return 0;
+}
+
+int radio_output_start(void)
+{
+ int rc = 0;
+
+ if(!pipeline) {
+ rc = radio_output_open();
+ if(rc)
+ return rc;
+ }
+
+ // Start pipeline
+ running = true;
+ gst_element_set_state(pipeline, GST_STATE_PLAYING);
+
+ return rc;
+}
+
+void radio_output_stop(void)
+{
+ GstEvent *event;
+
+ if(pipeline && running) {
+ // Stop pipeline
+ running = false;
+#ifdef WIREPLUMBER_WORKAROUND
+ gst_element_set_state(pipeline, GST_STATE_READY);
+#else
+ gst_element_set_state(pipeline, GST_STATE_PAUSED);
+#endif
+
+ // Flush pipeline
+ // This seems required to avoid stutters on starts after a stop
+ event = gst_event_new_flush_start();
+ gst_element_send_event(GST_ELEMENT(pipeline), event);
+ event = gst_event_new_flush_stop(TRUE);
+ gst_element_send_event(GST_ELEMENT(pipeline), event);
+ }
+}
+
+void radio_output_suspend(int state)
+{
+ // Placeholder
+}
+
+void radio_output_close(void)
+{
+ radio_output_stop();
+
+ if(pipeline) {
+ // Tear down pipeline
+ gst_element_set_state(pipeline, GST_STATE_NULL);
+ gst_object_unref(GST_OBJECT(pipeline));
+ pipeline = NULL;
+ running = false;
+ }
+
+ free(output_buf);
+ output_buf = NULL;
+}
+
+ssize_t radio_output_write(void *buf, int len)
+{
+ ssize_t rc = -EINVAL;
+ size_t n = len;
+ int samples = len / 2;
+ unsigned char *p;
+ GstBuffer *buffer;
+ GstFlowReturn ret;
+
+ if(!(pipeline && buf)) {
+ return rc;
+ }
+
+ // Don't bother pushing samples if output hasn't started
+ if(!running)
+ return 0;
+
+ /*
+ * Handle the rtl_fm code giving us an odd number of samples.
+ * This extra buffer copying approach is not particularly efficient,
+ * but works for now. It looks feasible to hack in something in the
+ * demod and output thread routines in rtl_fm.c to handle it there
+ * if more performance is required.
+ */
+ p = output_buf;
+ if(extra) {
+ memcpy(output_buf, extra_buf, sizeof(int16_t));
+ if((extra + samples) % 2) {
+ // We still have an extra sample, n remains the same, store the extra
+ memcpy(output_buf + sizeof(int16_t), buf, n - 2);
+ memcpy(extra_buf, ((unsigned char*) buf) + n - 2, sizeof(int16_t));
+ } else {
+ // We have an even number of samples, no extra
+ memcpy(output_buf + sizeof(int16_t), buf, n);
+ n += 2;
+ extra = 0;
+ }
+ } else if(samples % 2) {
+ // We have an extra sample, store it, and decrease n
+ n -= 2;
+ memcpy(output_buf + sizeof(int16_t), buf, n);
+ memcpy(extra_buf, ((unsigned char*) buf) + n, sizeof(int16_t));
+ extra = 1;
+ } else {
+ p = buf;
+ }
+
+ // Push buffer into pipeline
+ buffer = gst_buffer_new_allocate(NULL, n, NULL);
+ gst_buffer_fill(buffer, 0, p, n);
+ g_signal_emit_by_name(appsrc, "push-buffer", buffer, &ret);
+ gst_buffer_unref(buffer);
+ rc = n;
+
+ return rc;
+}
diff --git a/src/rtl_fm.c b/src/rtl_fm.c
new file mode 100644
index 0000000..cfbd487
--- /dev/null
+++ b/src/rtl_fm.c
@@ -0,0 +1,1275 @@
+/*
+ * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver
+ * Copyright (C) 2012 by Steve Markgraf <steve@steve-m.de>
+ * Copyright (C) 2012 by Hoernchen <la@tfc-server.de>
+ * Copyright (C) 2012 by Kyle Keen <keenerd@gmail.com>
+ * Copyright (C) 2013 by Elias Oenal <EliasOenal@gmail.com>
+ * Copyright (C) 2016, 2017 Konsulko Group
+ *
+ * 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 2 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/>.
+ */
+
+/*
+ * Note that this version replaces the standalone main() with separate
+ * init/start/stop API calls to allow building into another application.
+ * Other than removing the separate controller thread and adding an output
+ * function callback, other changes have been kept to a minimum to
+ * potentially allow using other rtl_fm features by modifying rtl_fm_init.
+ *
+ * December 2016, Scott Murray <scott.murray@konsulko.com>
+ */
+
+/*
+ * written because people could not do real time
+ * FM demod on Atom hardware with GNU radio
+ * based on rtl_sdr.c and rtl_tcp.c
+ *
+ * lots of locks, but that is okay
+ * (no many-to-many locks)
+ *
+ * todo:
+ * sanity checks
+ * scale squelch to other input parameters
+ * test all the demodulations
+ * pad output on hop
+ * frequency ranges could be stored better
+ * scaled AM demod amplification
+ * auto-hop after time limit
+ * peak detector to tune onto stronger signals
+ * fifo for active hop frequency
+ * clips
+ * noise squelch
+ * merge stereo patch
+ * merge soft agc patch
+ * merge udp patch
+ * testmode to detect overruns
+ * watchdog to reset bad dongle
+ * fix oversampling
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <math.h>
+#include <pthread.h>
+
+#include "rtl-sdr.h"
+#include "rtl_fm.h"
+#include "convenience/convenience.h"
+
+#define DEFAULT_SAMPLE_RATE 24000
+#define DEFAULT_BUF_LENGTH RTL_FM_DEFAULT_BUF_LENGTH
+#define MAXIMUM_OVERSAMPLE RTL_FM_MAXIMUM_OVERSAMPLE
+#define MAXIMUM_BUF_LENGTH RTL_FM_MAXIMUM_BUF_LENGTH
+#define AUTO_GAIN -100
+#define BUFFER_DUMP 4096
+
+#define FREQUENCIES_LIMIT 1000
+
+#define DEFAULT_SQUELCH_LEVEL 140
+#define DEFAULT_CONSEQ_SQUELCH 10
+
+static volatile int do_exit = 0;
+static int lcm_post[17] = {1,1,1,3,1,5,3,7,1,9,5,11,3,13,7,15,1};
+static int ACTUAL_BUF_LENGTH;
+
+static int *atan_lut = NULL;
+static int atan_lut_size = 131072; /* 512 KB */
+static int atan_lut_coef = 8;
+
+struct dongle_state
+{
+ int exit_flag;
+ pthread_t thread;
+ rtlsdr_dev_t *dev;
+ int dev_index;
+ uint32_t freq;
+ uint32_t rate;
+ int gain;
+ uint16_t buf16[MAXIMUM_BUF_LENGTH];
+ uint32_t buf_len;
+ int ppm_error;
+ int offset_tuning;
+ int direct_sampling;
+ int mute;
+ struct demod_state *demod_target;
+};
+
+struct demod_state
+{
+ int exit_flag;
+ pthread_t thread;
+ int16_t lowpassed[MAXIMUM_BUF_LENGTH];
+ int lp_len;
+ int16_t lp_i_hist[10][6];
+ int16_t lp_q_hist[10][6];
+ int16_t result[MAXIMUM_BUF_LENGTH];
+ int16_t droop_i_hist[9];
+ int16_t droop_q_hist[9];
+ int result_len;
+ int rate_in;
+ int rate_out;
+ int rate_out2;
+ int now_r, now_j;
+ int pre_r, pre_j;
+ int prev_index;
+ int downsample; /* min 1, max 256 */
+ int post_downsample;
+ int output_scale;
+ int squelch_level, conseq_squelch, squelch_hits, terminate_on_squelch;
+ int downsample_passes;
+ int comp_fir_size;
+ int custom_atan;
+ int deemph, deemph_a;
+ int now_lpr;
+ int prev_lpr_index;
+ int dc_block, dc_avg;
+ void (*mode_demod)(struct demod_state*);
+ pthread_rwlock_t rw;
+ pthread_cond_t ready;
+ pthread_mutex_t ready_m;
+ struct output_state *output_target;
+};
+
+struct output_state
+{
+ int exit_flag;
+ pthread_t thread;
+ rtl_fm_output_fn_t output_fn;
+ void *output_fn_data;
+ int16_t result[MAXIMUM_BUF_LENGTH];
+ int result_len;
+ int rate;
+ pthread_rwlock_t rw;
+ pthread_cond_t ready;
+ pthread_mutex_t ready_m;
+};
+
+struct controller_state
+{
+ int exit_flag;
+ pthread_t thread;
+ uint32_t freqs[FREQUENCIES_LIMIT];
+ int freq_len;
+ int freq_now;
+ int edge;
+ int wb_mode;
+ pthread_cond_t hop;
+ pthread_mutex_t hop_m;
+
+ void (*freq_callback)(uint32_t, void*);
+ void *freq_callback_data;
+
+ int scanning;
+ int scan_direction;
+ void (*scan_callback)(uint32_t, void*);
+ void *scan_callback_data;
+ uint32_t scan_step;
+ uint32_t scan_min;
+ uint32_t scan_max;
+ int scan_squelch_level;
+ int scan_squelch_count;
+};
+
+// multiple of these, eventually
+struct dongle_state dongle;
+struct demod_state demod;
+struct output_state output;
+struct controller_state controller;
+
+#if 0
+static void sighandler(int signum)
+{
+ fprintf(stderr, "Signal caught, exiting!\n");
+ do_exit = 1;
+ rtlsdr_cancel_async(dongle.dev);
+}
+#endif
+
+/* more cond dumbness */
+#define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m)
+#define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m)
+
+/* {length, coef, coef, coef} and scaled by 2^15
+ for now, only length 9, optimal way to get +85% bandwidth */
+#define CIC_TABLE_MAX 10
+int cic_9_tables[][10] = {
+ {0,},
+ {9, -156, -97, 2798, -15489, 61019, -15489, 2798, -97, -156},
+ {9, -128, -568, 5593, -24125, 74126, -24125, 5593, -568, -128},
+ {9, -129, -639, 6187, -26281, 77511, -26281, 6187, -639, -129},
+ {9, -122, -612, 6082, -26353, 77818, -26353, 6082, -612, -122},
+ {9, -120, -602, 6015, -26269, 77757, -26269, 6015, -602, -120},
+ {9, -120, -582, 5951, -26128, 77542, -26128, 5951, -582, -120},
+ {9, -119, -580, 5931, -26094, 77505, -26094, 5931, -580, -119},
+ {9, -119, -578, 5921, -26077, 77484, -26077, 5921, -578, -119},
+ {9, -119, -577, 5917, -26067, 77473, -26067, 5917, -577, -119},
+ {9, -199, -362, 5303, -25505, 77489, -25505, 5303, -362, -199},
+};
+
+void rotate_90(unsigned char *buf, uint32_t len)
+/* 90 rotation is 1+0j, 0+1j, -1+0j, 0-1j
+ or [0, 1, -3, 2, -4, -5, 7, -6] */
+{
+ uint32_t i;
+ uint8_t tmp;
+
+ for (i=0; i<len; i+=8) {
+ /* uint8_t negation = 255 - x */
+ tmp = (uint8_t)(255 - buf[i+3]);
+ buf[i+3] = buf[i+2];
+ buf[i+2] = tmp;
+
+ buf[i+4] = (uint8_t)(255 - buf[i+4]);
+ buf[i+5] = (uint8_t)(255 - buf[i+5]);
+
+ tmp = (uint8_t)(255 - buf[i+6]);
+ buf[i+6] = buf[i+7];
+ buf[i+7] = tmp;
+ }
+}
+
+void low_pass(struct demod_state *d)
+/* simple square window FIR */
+{
+ int i=0, i2=0;
+ while (i < d->lp_len) {
+ d->now_r += d->lowpassed[i];
+ d->now_j += d->lowpassed[i+1];
+ i += 2;
+ d->prev_index++;
+ if (d->prev_index < d->downsample) {
+ continue;
+ }
+ d->lowpassed[i2] = (int16_t)d->now_r; // * d->output_scale;
+ d->lowpassed[i2+1] = (int16_t)d->now_j; // * d->output_scale;
+ d->prev_index = 0;
+ d->now_r = 0;
+ d->now_j = 0;
+ i2 += 2;
+ }
+ d->lp_len = i2;
+}
+
+int low_pass_simple(int16_t *signal2, int len, int step)
+// no wrap around, length must be multiple of step
+{
+ int i, i2, sum;
+ for(i=0; i < len; i+=step) {
+ sum = 0;
+ for(i2=0; i2<step; i2++) {
+ sum += (int)signal2[i + i2];
+ }
+ //signal2[i/step] = (int16_t)(sum / step);
+ signal2[i/step] = (int16_t)(sum);
+ }
+ signal2[i/step + 1] = signal2[i/step];
+ return len / step;
+}
+
+void low_pass_real(struct demod_state *s)
+/* simple square window FIR */
+// add support for upsampling?
+{
+ int i=0, i2=0;
+ int fast = (int)s->rate_out;
+ int slow = s->rate_out2;
+ while (i < s->result_len) {
+ s->now_lpr += s->result[i];
+ i++;
+ s->prev_lpr_index += slow;
+ if (s->prev_lpr_index < fast) {
+ continue;
+ }
+ s->result[i2] = (int16_t)(s->now_lpr / (fast/slow));
+ s->prev_lpr_index -= fast;
+ s->now_lpr = 0;
+ i2 += 1;
+ }
+ s->result_len = i2;
+}
+
+void fifth_order(int16_t *data, int length, int16_t *hist)
+/* for half of interleaved data */
+{
+ int i;
+ int16_t a, b, c, d, e, f;
+ a = hist[1];
+ b = hist[2];
+ c = hist[3];
+ d = hist[4];
+ e = hist[5];
+ f = data[0];
+ /* a downsample should improve resolution, so don't fully shift */
+ data[0] = (int16_t) ((a + (b+e)*5 + (c+d)*10 + f) >> 4);
+ for (i=4; i<length; i+=4) {
+ a = c;
+ b = d;
+ c = e;
+ d = f;
+ e = data[i-2];
+ f = data[i];
+ data[i/2] = (int16_t) ((a + (b+e)*5 + (c+d)*10 + f) >> 4);
+ }
+ /* archive */
+ hist[0] = a;
+ hist[1] = b;
+ hist[2] = c;
+ hist[3] = d;
+ hist[4] = e;
+ hist[5] = f;
+}
+
+void generic_fir(int16_t *data, int length, int *fir, int16_t *hist)
+/* Okay, not at all generic. Assumes length 9, fix that eventually. */
+{
+ int16_t temp;
+ int sum;
+ int d;
+ for (d=0; d<length; d+=2) {
+ temp = data[d];
+ sum = 0;
+ sum += (hist[0] + hist[8]) * fir[1];
+ sum += (hist[1] + hist[7]) * fir[2];
+ sum += (hist[2] + hist[6]) * fir[3];
+ sum += (hist[3] + hist[5]) * fir[4];
+ sum += hist[4] * fir[5];
+ data[d] = (int16_t) (sum >> 15);
+ hist[0] = hist[1];
+ hist[1] = hist[2];
+ hist[2] = hist[3];
+ hist[3] = hist[4];
+ hist[4] = hist[5];
+ hist[5] = hist[6];
+ hist[6] = hist[7];
+ hist[7] = hist[8];
+ hist[8] = temp;
+ }
+}
+
+/* define our own complex math ops
+ because ARMv5 has no hardware float */
+
+void multiply(int ar, int aj, int br, int bj, int *cr, int *cj)
+{
+ *cr = ar*br - aj*bj;
+ *cj = aj*br + ar*bj;
+}
+
+int polar_discriminant(int ar, int aj, int br, int bj)
+{
+ int cr, cj;
+ double angle;
+ multiply(ar, aj, br, -bj, &cr, &cj);
+ angle = atan2((double)cj, (double)cr);
+ return (int)(angle / 3.14159 * (1<<14));
+}
+
+int fast_atan2(int y, int x)
+/* pre scaled for int16 */
+{
+ int yabs, angle;
+ int pi4=(1<<12), pi34=3*(1<<12); // note pi = 1<<14
+ if (x==0 && y==0) {
+ return 0;
+ }
+ yabs = y;
+ if (yabs < 0) {
+ yabs = -yabs;
+ }
+ if (x >= 0) {
+ angle = pi4 - pi4 * (x-yabs) / (x+yabs);
+ } else {
+ angle = pi34 - pi4 * (x+yabs) / (yabs-x);
+ }
+ if (y < 0) {
+ return -angle;
+ }
+ return angle;
+}
+
+int polar_disc_fast(int ar, int aj, int br, int bj)
+{
+ int cr, cj;
+ multiply(ar, aj, br, -bj, &cr, &cj);
+ return fast_atan2(cj, cr);
+}
+
+int atan_lut_init(void)
+{
+ int i = 0;
+
+ atan_lut = malloc(atan_lut_size * sizeof(int));
+
+ for (i = 0; i < atan_lut_size; i++) {
+ atan_lut[i] = (int) (atan((double) i / (1<<atan_lut_coef)) / 3.14159 * (1<<14));
+ }
+
+ return 0;
+}
+
+int polar_disc_lut(int ar, int aj, int br, int bj)
+{
+ int cr, cj, x, x_abs;
+
+ multiply(ar, aj, br, -bj, &cr, &cj);
+
+ /* special cases */
+ if (cr == 0 || cj == 0) {
+ if (cr == 0 && cj == 0)
+ {return 0;}
+ if (cr == 0 && cj > 0)
+ {return 1 << 13;}
+ if (cr == 0 && cj < 0)
+ {return -(1 << 13);}
+ if (cj == 0 && cr > 0)
+ {return 0;}
+ if (cj == 0 && cr < 0)
+ {return 1 << 14;}
+ }
+
+ /* real range -32768 - 32768 use 64x range -> absolute maximum: 2097152 */
+ x = (cj << atan_lut_coef) / cr;
+ x_abs = abs(x);
+
+ if (x_abs >= atan_lut_size) {
+ /* we can use linear range, but it is not necessary */
+ const int ret = 1<<13;
+ return (cj > 0) ? ret : -ret;
+ }
+
+ if (x > 0) {
+ return (cj > 0) ? atan_lut[x] : atan_lut[x] - (1<<14);
+ } else {
+ return (cj > 0) ? (1<<14) - atan_lut[-x] : -atan_lut[-x];
+ }
+
+ return 0;
+}
+
+void fm_demod(struct demod_state *fm)
+{
+ int i, pcm;
+ int16_t *lp = fm->lowpassed;
+ pcm = polar_discriminant(lp[0], lp[1],
+ fm->pre_r, fm->pre_j);
+ fm->result[0] = (int16_t)pcm;
+ for (i = 2; i < (fm->lp_len-1); i += 2) {
+ switch (fm->custom_atan) {
+ case 0:
+ pcm = polar_discriminant(lp[i], lp[i+1],
+ lp[i-2], lp[i-1]);
+ break;
+ case 1:
+ pcm = polar_disc_fast(lp[i], lp[i+1],
+ lp[i-2], lp[i-1]);
+ break;
+ case 2:
+ pcm = polar_disc_lut(lp[i], lp[i+1],
+ lp[i-2], lp[i-1]);
+ break;
+ }
+ fm->result[i/2] = (int16_t)pcm;
+ }
+ fm->pre_r = lp[fm->lp_len - 2];
+ fm->pre_j = lp[fm->lp_len - 1];
+ fm->result_len = fm->lp_len/2;
+}
+
+void am_demod(struct demod_state *fm)
+// todo, fix this extreme laziness
+{
+ int i, pcm;
+ int16_t *lp = fm->lowpassed;
+ int16_t *r = fm->result;
+ for (i = 0; i < fm->lp_len; i += 2) {
+ // hypot uses floats but won't overflow
+ //r[i/2] = (int16_t)hypot(lp[i], lp[i+1]);
+ pcm = lp[i] * lp[i];
+ pcm += lp[i+1] * lp[i+1];
+ r[i/2] = (int16_t) ((int16_t)sqrt(pcm) * fm->output_scale);
+ }
+ fm->result_len = fm->lp_len/2;
+ // lowpass? (3khz) highpass? (dc)
+}
+
+void usb_demod(struct demod_state *fm)
+{
+ int i, pcm;
+ int16_t *lp = fm->lowpassed;
+ int16_t *r = fm->result;
+ for (i = 0; i < fm->lp_len; i += 2) {
+ pcm = lp[i] + lp[i+1];
+ r[i/2] = (int16_t)((int16_t)pcm * fm->output_scale);
+ }
+ fm->result_len = fm->lp_len/2;
+}
+
+void lsb_demod(struct demod_state *fm)
+{
+ int i, pcm;
+ int16_t *lp = fm->lowpassed;
+ int16_t *r = fm->result;
+ for (i = 0; i < fm->lp_len; i += 2) {
+ pcm = lp[i] - lp[i+1];
+ r[i/2] = (int16_t)((int16_t)pcm * fm->output_scale);
+ }
+ fm->result_len = fm->lp_len/2;
+}
+
+void raw_demod(struct demod_state *fm)
+{
+ int i;
+ for (i = 0; i < fm->lp_len; i++) {
+ fm->result[i] = (int16_t)fm->lowpassed[i];
+ }
+ fm->result_len = fm->lp_len;
+}
+
+void deemph_filter(struct demod_state *fm)
+{
+ static int avg; // cheating...
+ int i, d;
+ // de-emph IIR
+ // avg = avg * (1 - alpha) + sample * alpha;
+ for (i = 0; i < fm->result_len; i++) {
+ d = fm->result[i] - avg;
+ if (d > 0) {
+ avg += (d + fm->deemph_a/2) / fm->deemph_a;
+ } else {
+ avg += (d - fm->deemph_a/2) / fm->deemph_a;
+ }
+ fm->result[i] = (int16_t)avg;
+ }
+}
+
+void dc_block_filter(struct demod_state *fm)
+{
+ int i, avg;
+ int64_t sum = 0;
+ for (i=0; i < fm->result_len; i++) {
+ sum += fm->result[i];
+ }
+ avg = (int)(sum / fm->result_len);
+ avg = (avg + fm->dc_avg * 9) / 10;
+ for (i=0; i < fm->result_len; i++) {
+ fm->result[i] = (int16_t)(fm->result[i] - avg);
+ }
+ fm->dc_avg = avg;
+}
+
+int mad(int16_t *samples, int len, int step)
+/* mean average deviation */
+{
+ int i=0, sum=0, ave=0;
+ if (len == 0)
+ {return 0;}
+ for (i=0; i<len; i+=step) {
+ sum += samples[i];
+ }
+ ave = sum / (len * step);
+ sum = 0;
+ for (i=0; i<len; i+=step) {
+ sum += abs(samples[i] - ave);
+ }
+ return sum / (len / step);
+}
+
+int rms(int16_t *samples, int len, int step)
+/* largely lifted from rtl_power */
+{
+ int i;
+ double p, t, s;
+ double dc, err;
+
+ p = t = 0L;
+ for (i=0; i<len; i+=step) {
+ s = (long)samples[i];
+ t += s;
+ p += s * s;
+ }
+ /* correct for dc offset in squares */
+ dc = (double)(t*step) / (double)len;
+ err = t * 2 * dc - dc * dc * len;
+
+ return (int)sqrt((p-err) / len);
+}
+
+void arbitrary_upsample(int16_t *buf1, int16_t *buf2, int len1, int len2)
+/* linear interpolation, len1 < len2 */
+{
+ int i = 1;
+ int j = 0;
+ int tick = 0;
+ double frac; // use integers...
+ while (j < len2) {
+ frac = (double)tick / (double)len2;
+ buf2[j] = (int16_t)(buf1[i-1]*(1-frac) + buf1[i]*frac);
+ j++;
+ tick += len1;
+ if (tick > len2) {
+ tick -= len2;
+ i++;
+ }
+ if (i >= len1) {
+ i = len1 - 1;
+ tick = len2;
+ }
+ }
+}
+
+void arbitrary_downsample(int16_t *buf1, int16_t *buf2, int len1, int len2)
+/* fractional boxcar lowpass, len1 > len2 */
+{
+ int i = 1;
+ int j = 0;
+ int tick = 0;
+ double remainder = 0;
+ double frac; // use integers...
+ buf2[0] = 0;
+ while (j < len2) {
+ frac = 1.0;
+ if ((tick + len2) > len1) {
+ frac = (double)(len1 - tick) / (double)len2;}
+ buf2[j] = (int16_t)(buf2[j] + (double)buf1[i] * frac + remainder);
+ remainder = (double)buf1[i] * (1.0-frac);
+ tick += len2;
+ i++;
+ if (tick > len1) {
+ j++;
+ buf2[j] = 0;
+ tick -= len1;
+ }
+ if (i >= len1) {
+ i = len1 - 1;
+ tick = len1;
+ }
+ }
+ for (j=0; j<len2; j++) {
+ buf2[j] = (int16_t) (buf2[j] * len2 / len1);
+ }
+}
+
+void arbitrary_resample(int16_t *buf1, int16_t *buf2, int len1, int len2)
+/* up to you to calculate lengths and make sure it does not go OOB
+ * okay for buffers to overlap, if you are downsampling */
+{
+ if (len1 < len2) {
+ arbitrary_upsample(buf1, buf2, len1, len2);
+ } else {
+ arbitrary_downsample(buf1, buf2, len1, len2);
+ }
+}
+
+void full_demod(struct demod_state *d)
+{
+ int i, ds_p;
+ int sr = 0;
+ ds_p = d->downsample_passes;
+ if (ds_p) {
+ for (i=0; i < ds_p; i++) {
+ fifth_order(d->lowpassed, (d->lp_len >> i), d->lp_i_hist[i]);
+ fifth_order(d->lowpassed+1, (d->lp_len >> i) - 1, d->lp_q_hist[i]);
+ }
+ d->lp_len = d->lp_len >> ds_p;
+ /* droop compensation */
+ if (d->comp_fir_size == 9 && ds_p <= CIC_TABLE_MAX) {
+ generic_fir(d->lowpassed, d->lp_len,
+ cic_9_tables[ds_p], d->droop_i_hist);
+ generic_fir(d->lowpassed+1, d->lp_len-1,
+ cic_9_tables[ds_p], d->droop_q_hist);
+ }
+ } else {
+ low_pass(d);
+ }
+ /* power squelch */
+ if (d->squelch_level) {
+ sr = rms(d->lowpassed, d->lp_len, 1);
+ if (sr < d->squelch_level) {
+ d->squelch_hits++;
+ for (i=0; i< d->lp_len; i++) {
+ d->lowpassed[i] = 0;
+ }
+ } else {
+ d->squelch_hits = 0;
+ }
+ }
+ d->mode_demod(d); /* lowpassed -> result */
+ if (d->mode_demod == &raw_demod) {
+ return;
+ }
+ /* todo, fm noise squelch */
+ // use nicer filter here too?
+ if (d->post_downsample > 1) {
+ d->result_len = low_pass_simple(d->result, d->result_len, d->post_downsample);}
+ if (d->deemph) {
+ deemph_filter(d);}
+ if (d->dc_block) {
+ dc_block_filter(d);}
+ if (d->rate_out2 > 0) {
+ low_pass_real(d);
+ //arbitrary_resample(d->result, d->result, d->result_len, d->result_len * d->rate_out2 / d->rate_out);
+ }
+}
+
+static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx)
+{
+ int i;
+ struct dongle_state *s = ctx;
+ struct demod_state *d = s->demod_target;
+
+ if (do_exit) {
+ return;}
+ if (!ctx) {
+ return;}
+ if (s->mute) {
+ for (i=0; i<s->mute; i++) {
+ buf[i] = 127;}
+ s->mute = 0;
+ }
+ if (!s->offset_tuning) {
+ rotate_90(buf, len);}
+ for (i=0; i<(int)len; i++) {
+ s->buf16[i] = (int16_t)(buf[i] - 127);
+ }
+ pthread_rwlock_wrlock(&d->rw);
+ memcpy(d->lowpassed, s->buf16, 2*len);
+ d->lp_len = len;
+ pthread_rwlock_unlock(&d->rw);
+ safe_cond_signal(&d->ready, &d->ready_m);
+}
+
+static void *dongle_thread_fn(void *arg)
+{
+ struct dongle_state *s = arg;
+ fprintf(stderr, "dongle_thread_fn running\n");
+ rtlsdr_read_async(s->dev, rtlsdr_callback, s, 0, s->buf_len);
+ fprintf(stderr, "dongle_thread_fn exited!\n");
+ return 0;
+}
+
+static void rtl_fm_scan_callback(void)
+{
+ struct controller_state *s = &controller;
+ uint32_t frequency = rtl_fm_get_freq();
+
+ if(!s->scanning)
+ return;
+
+ if(!s->scan_direction) {
+ frequency += s->scan_step;
+ if(frequency > s->scan_max)
+ frequency = s->scan_min;
+ } else {
+ frequency -= s->scan_step;
+ if(frequency < s->scan_min)
+ frequency = s->scan_max;
+ }
+
+ rtl_fm_set_freq(frequency);
+}
+
+static void rtl_fm_scan_end_callback(void)
+{
+ struct controller_state *s = &controller;
+
+ if(!s->scanning)
+ return;
+
+ rtl_fm_scan_stop();
+
+ if(s->scan_callback)
+ s->scan_callback(rtl_fm_get_freq(), s->scan_callback_data);
+}
+
+static void *demod_thread_fn(void *arg)
+{
+ struct demod_state *d = arg;
+ struct output_state *o = d->output_target;
+ fprintf(stderr, "demod_thread_fn running\n");
+ while (!do_exit) {
+ safe_cond_wait(&d->ready, &d->ready_m);
+ pthread_rwlock_wrlock(&d->rw);
+ full_demod(d);
+ pthread_rwlock_unlock(&d->rw);
+ if (d->exit_flag) {
+ do_exit = 1;
+ }
+ if (d->squelch_level) {
+ if(d->squelch_hits > d->conseq_squelch) {
+ d->squelch_hits = d->conseq_squelch + 1; /* hair trigger */
+ //safe_cond_signal(&controller.hop, &controller.hop_m);
+ rtl_fm_scan_callback();
+ continue;
+ } else if(!d->squelch_hits) {
+ rtl_fm_scan_end_callback();
+ }
+ }
+ pthread_rwlock_wrlock(&o->rw);
+ memcpy(o->result, d->result, 2*d->result_len);
+ o->result_len = d->result_len;
+ pthread_rwlock_unlock(&o->rw);
+ safe_cond_signal(&o->ready, &o->ready_m);
+ }
+ fprintf(stderr, "demod_thread_fn exited!\n");
+ return 0;
+}
+
+static void *output_thread_fn(void *arg)
+{
+ struct output_state *s = arg;
+ fprintf(stderr, "output_thread_fn running\n");
+ while (!do_exit) {
+ // use timedwait and pad out under runs
+ safe_cond_wait(&s->ready, &s->ready_m);
+ pthread_rwlock_rdlock(&s->rw);
+ if(s->output_fn) {
+ s->output_fn(s->result, s->result_len, s->output_fn_data);
+ }
+ pthread_rwlock_unlock(&s->rw);
+ }
+ fprintf(stderr, "output_thread_fn exited!\n");
+ return 0;
+}
+
+static void optimal_settings(int freq, int rate)
+{
+ // giant ball of hacks
+ // seems unable to do a single pass, 2:1
+ int capture_freq, capture_rate;
+ struct dongle_state *d = &dongle;
+ struct demod_state *dm = &demod;
+ struct controller_state *cs = &controller;
+ dm->downsample = (1000000 / dm->rate_in) + 1;
+ if (dm->downsample_passes) {
+ dm->downsample_passes = (int)log2(dm->downsample) + 1;
+ dm->downsample = 1 << dm->downsample_passes;
+ }
+ capture_freq = freq;
+ capture_rate = dm->downsample * dm->rate_in;
+ if (!d->offset_tuning) {
+ capture_freq = freq + capture_rate/4;}
+ capture_freq += cs->edge * dm->rate_in / 2;
+ dm->output_scale = (1<<15) / (128 * dm->downsample);
+ if (dm->output_scale < 1) {
+ dm->output_scale = 1;}
+ if (dm->mode_demod == &fm_demod) {
+ dm->output_scale = 1;}
+ d->freq = (uint32_t)capture_freq;
+ d->rate = (uint32_t)capture_rate;
+}
+
+
+void frequency_range(struct controller_state *s, char *arg)
+{
+ char *start, *stop, *step;
+ int i;
+ start = arg;
+ stop = strchr(start, ':') + 1;
+ stop[-1] = '\0';
+ step = strchr(stop, ':') + 1;
+ step[-1] = '\0';
+ for(i=(int)atofs(start); i<=(int)atofs(stop); i+=(int)atofs(step))
+ {
+ s->freqs[s->freq_len] = (uint32_t)i;
+ s->freq_len++;
+ if (s->freq_len >= FREQUENCIES_LIMIT) {
+ break;}
+ }
+ stop[-1] = ':';
+ step[-1] = ':';
+}
+
+void dongle_init(struct dongle_state *s)
+{
+ s->rate = DEFAULT_SAMPLE_RATE;
+ s->gain = AUTO_GAIN; // tenths of a dB
+ s->mute = 0;
+ s->direct_sampling = 0;
+ s->offset_tuning = 0;
+ s->demod_target = &demod;
+}
+
+void demod_init(struct demod_state *s)
+{
+ s->rate_in = DEFAULT_SAMPLE_RATE;
+ s->rate_out = DEFAULT_SAMPLE_RATE;
+ s->squelch_level = 0;
+ s->conseq_squelch = DEFAULT_CONSEQ_SQUELCH;
+ s->terminate_on_squelch = 0;
+ s->squelch_hits = DEFAULT_CONSEQ_SQUELCH + 1;
+ s->downsample_passes = 0;
+ s->comp_fir_size = 0;
+ s->prev_index = 0;
+ s->post_downsample = 1; // once this works, default = 4
+ s->custom_atan = 0;
+ s->deemph = 0;
+ s->rate_out2 = -1; // flag for disabled
+ s->mode_demod = &fm_demod;
+ s->pre_j = s->pre_r = s->now_r = s->now_j = 0;
+ s->prev_lpr_index = 0;
+ s->deemph_a = 0;
+ s->now_lpr = 0;
+ s->dc_block = 0;
+ s->dc_avg = 0;
+ pthread_rwlock_init(&s->rw, NULL);
+ pthread_cond_init(&s->ready, NULL);
+ pthread_mutex_init(&s->ready_m, NULL);
+ s->output_target = &output;
+}
+
+void demod_cleanup(struct demod_state *s)
+{
+ pthread_rwlock_destroy(&s->rw);
+ pthread_cond_destroy(&s->ready);
+ pthread_mutex_destroy(&s->ready_m);
+}
+
+void output_init(struct output_state *s)
+{
+ s->rate = DEFAULT_SAMPLE_RATE;
+ s->output_fn = NULL;
+ s->output_fn_data = NULL;
+ pthread_rwlock_init(&s->rw, NULL);
+ pthread_cond_init(&s->ready, NULL);
+ pthread_mutex_init(&s->ready_m, NULL);
+}
+
+void output_cleanup(struct output_state *s)
+{
+ pthread_rwlock_destroy(&s->rw);
+ pthread_cond_destroy(&s->ready);
+ pthread_mutex_destroy(&s->ready_m);
+}
+
+void controller_init(struct controller_state *s)
+{
+ s->freqs[0] = 100000000;
+ s->freq_len = 0;
+ s->edge = 0;
+ s->wb_mode = 0;
+ pthread_cond_init(&s->hop, NULL);
+ pthread_mutex_init(&s->hop_m, NULL);
+}
+
+void controller_cleanup(struct controller_state *s)
+{
+ pthread_cond_destroy(&s->hop);
+ pthread_mutex_destroy(&s->hop_m);
+}
+
+int sanity_checks(void)
+{
+ int r = 1;
+ if (controller.freq_len == 0) {
+ fprintf(stderr, "Please specify a frequency.\n");
+ r = 0;
+ }
+
+ if (controller.freq_len >= FREQUENCIES_LIMIT) {
+ fprintf(stderr, "Too many channels, maximum %i.\n", FREQUENCIES_LIMIT);
+ r = 0;
+ }
+
+ if (controller.freq_len > 1 && demod.squelch_level == 0) {
+ fprintf(stderr, "Please specify a squelch level. Required for scanning multiple frequencies.\n");
+ r = 0;
+ }
+ return r;
+}
+
+int rtl_fm_init(uint32_t freq,
+ uint32_t sample_rate,
+ uint32_t resample_rate,
+ rtl_fm_output_fn_t output_fn,
+ void *output_fn_data)
+{
+ int r = 0;
+
+ dongle_init(&dongle);
+ demod_init(&demod);
+ output_init(&output);
+ controller_init(&controller);
+
+ /*
+ * Simulate the effects of command line arguments:
+ *
+ * -W wbfm -s <sample rate> -r <resample rate>
+ */
+
+ /* Set initial frequency */
+ controller.freqs[0] = freq;
+ controller.freq_len++;
+
+ /* Set mode to wbfm */
+ controller.wb_mode = 1;
+ demod.mode_demod = &fm_demod;
+ demod.rate_in = 170000;
+ demod.rate_out = 170000;
+ demod.rate_out2 = 32000;
+ demod.custom_atan = 1;
+ //demod.post_downsample = 4;
+ demod.deemph = 1;
+ controller.scan_squelch_count = DEFAULT_CONSEQ_SQUELCH;
+ controller.scan_squelch_level = DEFAULT_SQUELCH_LEVEL;
+ demod.squelch_level = 0;
+
+ /* Adjust frequency for wb mode */
+ controller.freqs[0] += 16000;
+
+ /* Set sample rate */
+ demod.rate_in = sample_rate;
+ demod.rate_out = sample_rate;
+
+ /* Set resample rate */
+ output.rate = (int) resample_rate;
+ demod.rate_out2 = (int) resample_rate;
+
+ /* Set output function pointer */
+ if(output_fn) {
+ output.output_fn = output_fn;
+ output.output_fn_data = output_fn_data;
+ }
+
+ /* quadruple sample_rate to limit to Δθ to ±π/2 */
+ demod.rate_in *= demod.post_downsample;
+
+ if (!output.rate) {
+ output.rate = demod.rate_out;
+ }
+
+ if (!sanity_checks())
+ return -1;
+
+ if (controller.freq_len > 1) {
+ demod.terminate_on_squelch = 0;
+ }
+
+ ACTUAL_BUF_LENGTH = lcm_post[demod.post_downsample] * DEFAULT_BUF_LENGTH;
+
+ dongle.dev_index = verbose_device_search("0");
+ if (dongle.dev_index < 0) {
+ return -1;
+ }
+
+ r = rtlsdr_open(&dongle.dev, (uint32_t)dongle.dev_index);
+ if (r < 0) {
+ fprintf(stderr, "Failed to open rtlsdr device #%d.\n", dongle.dev_index);
+ return r;
+ }
+
+ if (demod.deemph) {
+ demod.deemph_a = (int)round(1.0/((1.0-exp(-1.0/(demod.rate_out * 75e-6)))));
+ }
+
+ /* Set the tuner gain */
+ if (dongle.gain == AUTO_GAIN) {
+ verbose_auto_gain(dongle.dev);
+ } else {
+ dongle.gain = nearest_gain(dongle.dev, dongle.gain);
+ verbose_gain_set(dongle.dev, dongle.gain);
+ }
+
+ verbose_ppm_set(dongle.dev, dongle.ppm_error);
+
+ //r = rtlsdr_set_testmode(dongle.dev, 1);
+
+ return r;
+}
+
+void rtl_fm_start(void)
+{
+ struct controller_state *s = &controller;
+
+ /*
+ * A bunch of the following is pulled from the controller_thread_fn,
+ * which has been removed.
+ */
+
+ /* Reset endpoint before we start reading from it (mandatory) */
+ verbose_reset_buffer(dongle.dev);
+
+ /* set up primary channel */
+ optimal_settings(s->freqs[0], demod.rate_in);
+ if (dongle.direct_sampling) {
+ verbose_direct_sampling(dongle.dev, 1);}
+ if (dongle.offset_tuning) {
+ verbose_offset_tuning(dongle.dev);}
+
+ /* Set the frequency */
+ verbose_set_frequency(dongle.dev, dongle.freq);
+ fprintf(stderr, "Oversampling input by: %ix.\n", demod.downsample);
+ fprintf(stderr, "Oversampling output by: %ix.\n", demod.post_downsample);
+ fprintf(stderr, "Buffer size: %0.2fms\n",
+ 1000 * 0.5 * (float)ACTUAL_BUF_LENGTH / (float)dongle.rate);
+
+ /* Set the sample rate */
+ verbose_set_sample_rate(dongle.dev, dongle.rate);
+ fprintf(stderr, "Output at %u Hz.\n", demod.rate_in/demod.post_downsample);
+ usleep(100000);
+
+ rtl_fm_scan_stop();
+
+ do_exit = 0;
+ pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output));
+ pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod));
+ pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle));
+}
+
+void rtl_fm_set_freq(uint32_t freq)
+{
+ struct controller_state *s = &controller;
+
+ if(s->freqs[0] == freq)
+ return;
+
+ s->freqs[0] = freq;
+ s->freq_len = 1;
+
+ if (s->wb_mode) {
+ s->freqs[0] += 16000;
+ }
+
+ optimal_settings(s->freqs[0], demod.rate_in);
+ if (dongle.offset_tuning) {
+ verbose_offset_tuning(dongle.dev);
+ }
+ rtlsdr_set_center_freq(dongle.dev, dongle.freq);
+
+ // It does not look like refreshing the sample rate is desirable
+ // (e.g. the scanning code in the removed controller thread function
+ // did not do it), and behavior seemed a bit less robust with it
+ // present. However, I am leaving this here as a reminder to revisit
+ // via some more testing.
+ //rtlsdr_set_sample_rate(dongle.dev, dongle.rate);
+
+ // This triggers a mute during the frequency change
+ dongle.mute = BUFFER_DUMP;
+
+ if(s->freq_callback)
+ s->freq_callback(freq, s->freq_callback_data);
+}
+
+void rtl_fm_set_freq_callback(void (*callback)(uint32_t, void *),
+ void *data)
+{
+ struct controller_state *s = &controller;
+
+ s->freq_callback = callback;
+ s->freq_callback_data = data;
+}
+
+uint32_t rtl_fm_get_freq(void)
+{
+ struct controller_state *s = &controller;
+ uint32_t frequency = s->freqs[0];
+
+ if (s->wb_mode)
+ frequency -= 16000;
+
+ return frequency;
+}
+
+void rtl_fm_stop(void)
+{
+ rtl_fm_scan_stop();
+
+ rtlsdr_cancel_async(dongle.dev);
+ do_exit = 1;
+ pthread_join(dongle.thread, NULL);
+ safe_cond_signal(&demod.ready, &demod.ready_m);
+ pthread_join(demod.thread, NULL);
+ safe_cond_signal(&output.ready, &output.ready_m);
+ pthread_join(output.thread, NULL);
+}
+
+void rtl_fm_scan_start(int direction,
+ void (*callback)(uint32_t, void *),
+ void *data,
+ uint32_t step,
+ uint32_t min,
+ uint32_t max)
+{
+ struct controller_state *s = &controller;
+ struct demod_state *dm = &demod;
+ uint32_t frequency = rtl_fm_get_freq();
+
+ if(s->scanning && s->scan_direction == direction)
+ return;
+
+ s->scanning = 1;
+ s->scan_direction = direction;
+ s->scan_callback = callback;
+ s->scan_callback_data = data;
+ s->scan_step = step;
+ s->scan_min = min;
+ s->scan_max = max;
+
+ /* Start scan by stepping in the desired direction */
+ if(!direction) {
+ frequency += s->scan_step;
+ if(frequency > s->scan_max)
+ frequency = s->scan_min;
+ } else {
+ frequency -= s->scan_step;
+ if(frequency < s->scan_min)
+ frequency = s->scan_max;
+ }
+
+ rtl_fm_set_freq(frequency);
+
+ dm->conseq_squelch = s->scan_squelch_count;
+ dm->squelch_hits = s->scan_squelch_count + 1;
+ dm->squelch_level = s->scan_squelch_level;
+}
+
+void rtl_fm_scan_stop(void)
+{
+ struct controller_state *s = &controller;
+ struct demod_state *dm = &demod;
+
+ s->scanning = 0;
+
+ dm->squelch_hits = s->scan_squelch_count + 1;
+ dm->squelch_level = 0;
+}
+
+void rtl_fm_scan_set_squelch_level(int level)
+{
+ struct controller_state *s = &controller;
+
+ s->scan_squelch_level = level;
+}
+
+void rtl_fm_scan_set_squelch_limit(int count)
+{
+ struct controller_state *s = &controller;
+
+ s->scan_squelch_count = count;
+}
+
+void rtl_fm_cleanup(void)
+{
+ //dongle_cleanup(&dongle);
+ demod_cleanup(&demod);
+ output_cleanup(&output);
+ controller_cleanup(&controller);
+
+ rtlsdr_close(dongle.dev);
+}
+
+// vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab
diff --git a/src/rtl_fm.h b/src/rtl_fm.h
new file mode 100644
index 0000000..f5b2a86
--- /dev/null
+++ b/src/rtl_fm.h
@@ -0,0 +1,70 @@
+/*
+ * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver
+ * Copyright (C) 2016, 2017 Konsulko Group
+ *
+ * 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 2 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 RTL_FM_H
+#define RTL_FM_H
+
+#include <stdint.h>
+
+#define RTL_FM_DEFAULT_BUF_LENGTH (1 * 16384)
+#define RTL_FM_MAXIMUM_OVERSAMPLE 16
+#define RTL_FM_MAXIMUM_BUF_LENGTH (RTL_FM_MAXIMUM_OVERSAMPLE * RTL_FM_DEFAULT_BUF_LENGTH)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void (*rtl_fm_output_fn_t)(int16_t *result, int result_len, void *data);
+
+int rtl_fm_init(uint32_t freq,
+ uint32_t sample_rate,
+ uint32_t resample_rate,
+ rtl_fm_output_fn_t output_fn,
+ void *output_fn_data);
+
+void rtl_fm_start(void);
+
+void rtl_fm_set_freq(uint32_t freq);
+
+void rtl_fm_set_freq_callback(void (*callback)(uint32_t, void *),
+ void *data);
+
+uint32_t rtl_fm_get_freq(void);
+
+void rtl_fm_stop(void);
+
+void rtl_fm_scan_start(int direction,
+ void (*callback)(uint32_t, void *),
+ void *data,
+ uint32_t step,
+ uint32_t min,
+ uint32_t max);
+
+void rtl_fm_scan_stop(void);
+
+void rtl_fm_scan_set_squelch_level(int level);
+
+void rtl_fm_scan_set_squelch_limit(int count);
+
+void rtl_fm_cleanup(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RTL_FM_H */
diff --git a/src/rtl_fm_helper.c b/src/rtl_fm_helper.c
new file mode 100644
index 0000000..c7df4b9
--- /dev/null
+++ b/src/rtl_fm_helper.c
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2018 Konsulko Group
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <glib.h>
+
+#include "rtl_fm.h"
+#include "radio_output.h"
+
+#ifdef DEBUG
+#define LOG_FILE "/tmp/helper.log"
+FILE *_log;
+#define LOG(...) \
+ do { \
+ if(!_log) _log = fopen(LOG_FILE, "w"); \
+ fprintf(_log, __VA_ARGS__); \
+ fflush(_log); \
+ } while(0)
+#else
+#define LOG(...) do { } while(0)
+#endif
+
+// Structure to describe FM band plans, all values in Hz.
+typedef struct {
+ const char *name;
+ uint32_t min;
+ uint32_t max;
+ uint32_t step;
+} fm_band_plan_t;
+
+static fm_band_plan_t known_fm_band_plans[5] = {
+ { .name = "US", .min = 87900000, .max = 107900000, .step = 200000 },
+ { .name = "JP", .min = 76000000, .max = 95000000, .step = 100000 },
+ { .name = "EU", .min = 87500000, .max = 108000000, .step = 50000 },
+ { .name = "ITU-1", .min = 87500000, .max = 108000000, .step = 50000 },
+ { .name = "ITU-2", .min = 87900000, .max = 107900000, .step = 50000 }
+};
+
+static unsigned int bandplan;
+static char line[64];
+
+static void rtl_output_callback(int16_t *result, int result_len, void *ctx)
+{
+ radio_output_write((char*) result, result_len * 2);
+}
+
+static void rtl_freq_callback(uint32_t freq, void *ctx)
+{
+ // Note, need to flush output to ensure parent sees it
+ printf("F=%u\n", freq);
+ fflush(stdout);
+ LOG("F=%u\n", freq);
+}
+
+static void rtl_scan_callback(uint32_t freq, void *ctx)
+{
+ // Note, need to flush output to ensure parent sees it
+ printf("S=%u\n", freq);
+ fflush(stdout);
+ LOG("S=%u\n", freq);
+}
+
+static void read_config(void)
+{
+ GKeyFile* conf_file;
+ int conf_file_present = 0;
+ char *value_str;
+
+ // Load settings from configuration file if it exists
+ conf_file = g_key_file_new();
+ if(conf_file &&
+ g_key_file_load_from_dirs(conf_file,
+ "AGL.conf",
+ (const gchar**) g_get_system_config_dirs(),
+ NULL,
+ G_KEY_FILE_KEEP_COMMENTS,
+ NULL) == TRUE) {
+ conf_file_present = 1;
+
+ // Set band plan if it is specified
+ value_str = g_key_file_get_string(conf_file,
+ "radio",
+ "fmbandplan",
+ NULL);
+ if(value_str) {
+ unsigned int i;
+ for(i = 0;
+ i < sizeof(known_fm_band_plans) / sizeof(fm_band_plan_t);
+ i++) {
+ if(!strcasecmp(value_str, known_fm_band_plans[i].name)) {
+ bandplan = i;
+ break;
+ }
+ }
+ }
+ }
+ fprintf(stderr, "Using FM Bandplan: %s\n", known_fm_band_plans[bandplan].name);
+
+ if(conf_file_present) {
+ GError *error = NULL;
+ int n;
+
+ // Allow over-riding scanning parameters just in case a demo
+ // setup needs to do so to work reliably.
+ n = g_key_file_get_integer(conf_file,
+ "radio",
+ "scan_squelch_level",
+ &error);
+ if(!error) {
+ fprintf(stderr, "Scanning squelch level set to %d\n", n);
+ rtl_fm_scan_set_squelch_level(n);
+ }
+
+ error = NULL;
+ n = g_key_file_get_integer(conf_file,
+ "radio",
+ "scan_squelch_limit",
+ &error);
+ if(!error) {
+ fprintf(stderr, "Scanning squelch limit set to %d\n", n);
+ rtl_fm_scan_set_squelch_limit(n);
+ }
+
+ g_key_file_free(conf_file);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int rc;
+ bool detect = false;
+ bool done = false;
+ bool started = false;
+ uint32_t frequency;
+
+ LOG("started\n");
+
+ read_config();
+ frequency = known_fm_band_plans[bandplan].min;
+
+ if(argc == 2 && strcmp(argv[1], "--detect") == 0) {
+ detect = true;
+ }
+
+ rc = rtl_fm_init(frequency, 200000, 48000, rtl_output_callback, NULL);
+ if(rc < 0) {
+ fprintf(stderr, "No RTL USB adapter?\n");
+ exit(1);
+ }
+ if(detect) {
+ rtl_fm_cleanup();
+ exit(0);
+ }
+
+ rtl_fm_set_freq_callback(rtl_freq_callback, NULL);
+
+ while(!done) {
+ LOG("Reading command\n");
+ if (fgets(line, sizeof(line), stdin) == NULL)
+ break;
+ if(line[0] == '\0' || line[0] == '\n')
+ continue;
+ if(strcmp(line, "START\n") == 0) {
+ LOG("START received\n");
+ if(!started) {
+ LOG("Starting\n");
+ radio_output_start();
+ rtl_fm_start();
+ started = true;
+ }
+ } else if(strcmp(line, "STOP\n") == 0) {
+ LOG("STOP received\n");
+ if(started) {
+ LOG("Stopping\n");
+ radio_output_stop();
+ rtl_fm_stop();
+ started = false;
+ }
+ } else if(strncmp(line, "F=", 2) == 0) {
+ uint32_t n;
+ if(sscanf(line, "F=%u\n", &n) == 1) {
+ LOG("F=%d received\n", n);
+ rtl_fm_scan_stop();
+ rtl_fm_set_freq(n);
+ }
+ } else if(strcmp(line, "S=UP\n") == 0) {
+ LOG("S=UP received\n");
+ if(!started)
+ continue;
+ LOG("Calling rtl_fm_scan_start\n");
+ rtl_fm_scan_start(0,
+ rtl_scan_callback,
+ NULL,
+ known_fm_band_plans[bandplan].step,
+ known_fm_band_plans[bandplan].min,
+ known_fm_band_plans[bandplan].max);
+ } else if(strcmp(line, "S=DOWN\n") == 0) {
+ LOG("S=DOWN received\n");
+ if(!started)
+ continue;
+ LOG("Calling rtl_fm_scan_start\n");
+ rtl_fm_scan_start(1,
+ rtl_scan_callback,
+ NULL,
+ known_fm_band_plans[bandplan].step,
+ known_fm_band_plans[bandplan].min,
+ known_fm_band_plans[bandplan].max);
+ } else if(strcmp(line, "S=STOP\n") == 0) {
+ LOG("S=STOP received\n");
+ if(started) {
+ LOG("Calling rtl_fm_scan_stop\n");
+ rtl_fm_scan_stop();
+ }
+ } else if(line[0] == 'q' || line[0] == 'Q')
+ break;
+ }
+ if(started) {
+ radio_output_stop();
+ rtl_fm_stop();
+ }
+ rtl_fm_cleanup();
+ LOG("done");
+ return 0;
+}
diff --git a/src/tef665x.h b/src/tef665x.h
new file mode 100644
index 0000000..10874e8
--- /dev/null
+++ b/src/tef665x.h
@@ -0,0 +1,397 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TEF665X_H
+#define TEF665X_H
+
+typedef uint8_t u8;
+typedef uint16_t ushort;
+typedef uint32_t uint;
+
+typedef enum
+{
+_close = 0,
+_open
+} I2C_STATE;
+
+typedef enum
+{
+ TEF665X_MODULE_FM = 0x20, //32
+ TEF665X_MODULE_AM = 0x21, //31
+ TEF665X_MODULE_AUDIO = 0x30, //48
+ TEF665X_MODULE_APPL = 0x40 //64
+} TEF665x_MODULE;
+
+enum {
+ TEF6657_CMD_tune = 0,
+ TEF6657_CMD_open = 1,
+ TEF6657_CMD_close = 2,
+ TEF6657_CMD_am = 3,
+ TEF6657_CMD_fm = 4,
+ TEF6657_CMD_GETamSTAUS = 5,
+ TEF6657_CMD_GETfmSTAUS = 6,
+ TEF6657_CMD_GEToirtSTAUS
+};
+
+enum
+{
+ RADIO_BOOT_STATE = 0,
+ RADIO_IDLE_STATE,
+ RADIO_STANBDY_STATE,
+ RADIO_FM_STATE,
+ RADIO_AM_STATE
+
+};
+
+typedef enum
+{
+ TEF665X_Cmd_Set_OperationMode = 0x01,
+ TEF665X_Cmd_Set_GPIO = 0x03,
+ TEF665X_Cmd_Set_ReferenceClock = 0x04,
+ TEF665X_Cmd_Activate = 0x05,
+
+ TEF665X_Cmd_Get_Operation_Status = 0x80, //128,
+ TEF665X_Cmd_Get_GPIO_Status = 0x81, //129,
+ TEF665X_Cmd_Get_Identification = 0x82, //130,
+ TEF665X_Cmd_Get_LastWrite = 0x83, //131
+} TEF665x_APPL_COMMAND;
+
+typedef enum
+{
+ TEF665X_Cmd_Set_RDS_mode = 0x01, // default
+ TEF665X_Cmd_Set_RDS_autorestart= 0x02, // restart after tune
+ TEF665X_Cmd_Set_RDS_interface = 0, // no interface
+} TEF665x_FM_COMMAND;
+
+typedef enum
+{
+ TEF665X_Cmd_Tune_To = 0x01,
+ TEF665X_Cmd_Set_Tune_Options =0x02,
+ TEF665X_Cmd_Set_Bandwidth =0x0A, //10,
+ TEF665X_Cmd_Set_RFAGC =0x0B, //11,
+ TEF665X_Cmd_Set_Antenna =0x0C, //12,
+
+ TEF665X_Cmd_Set_MphSuppression =0x14, //20,
+ TEF665X_Cmd_Set_NoiseBlanker =0x17, //23,
+ TEF665X_Cmd_Set_NoiseBlanker_Audio =0x18, //24,
+
+ TEF665X_Cmd_Set_DigitalRadio =0x1E, //30,
+ TEF665X_Cmd_Set_Deemphasis =0x1F, //31,
+
+ TEF665X_Cmd_Set_LevelStep = 0x26, //38,
+ TEF665X_Cmd_Set_LevelOffset = 0x27, //39,
+
+ TEF665X_Cmd_Set_Softmute_Time =0x28, //40,
+ TEF665X_Cmd_Set_Softmute_Mod = 0x29, //41,
+ TEF665X_Cmd_Set_Softmute_Level =0x2A, //42,
+ TEF665X_Cmd_Set_Softmute_Noise =0x2B, //43,
+ TEF665X_Cmd_Set_Softmute_Mph =0x2C, //44,
+ TEF665X_Cmd_Set_Softmute_Max =0x2D, //45,
+
+ TEF665X_Cmd_Set_Highcut_Time =0x32, //50,
+ TEF665X_Cmd_Set_Highcut_Mod = 0x33, //51,
+ TEF665X_Cmd_Set_Highcut_Level =0x34, //52,
+ TEF665X_Cmd_Set_Highcut_Noise = 0x35, //53,
+ TEF665X_Cmd_Set_Highcut_Mph =0x36, //54,
+ TEF665X_Cmd_Set_Highcut_Max =0x37, //55,
+ TEF665X_Cmd_Set_Highcut_Min =0x38, //56,
+ TEF665X_Cmd_Set_Lowcut_Min =0x3A, //58,
+
+ TEF665X_Cmd_Set_Stereo_Time =0x3C, //60,
+ TEF665X_Cmd_Set_Stereo_Mod =0x3D, //61,
+ TEF665X_Cmd_Set_Stereo_Level =0x3E, //62,
+ TEF665X_Cmd_Set_Stereo_Noise = 0x3F, //63,
+ TEF665X_Cmd_Set_Stereo_Mph =0x40, //64,
+ TEF665X_Cmd_Set_Stereo_Max = 0x41, //65,
+ TEF665X_Cmd_Set_Stereo_Min = 0x42, //66,
+
+ TEF665X_Cmd_Set_Scaler = 0x50, //80,
+ TEF665X_Cmd_Set_RDS = 0x51, //81,
+ TEF665X_Cmd_Set_QualityStatus = 0x52, //82,
+ TEF665X_Cmd_Set_DR_Blend = 0x53, //83,
+ TEF665X_Cmd_Set_DR_Options = 0x54, //84,
+ TEF665X_Cmd_Set_Specials = 0x55, //85,
+
+ TEF665X_Cmd_Get_Quality_Status = 0x80, //128,
+ TEF665X_Cmd_Get_Quality_Data = 0x81, //129,
+ TEF665X_Cmd_Get_RDS_Status = 0x82, //130,
+ TEF665X_Cmd_Get_RDS_Data = 0x83, //131,
+ TEF665X_Cmd_Get_AGC = 0x84, //132,
+ TEF665X_Cmd_Get_Signal_Status = 0x85, //133,
+ TEF665X_Cmd_Get_Processing_Status = 0x86, //134,
+ TEF665X_Cmd_Get_Interface_Status = 0x87, //135,
+
+ TEF665X_Cmd_Set_Output_signal_i2s = 0x21, //33,
+ TEF665X_Cmd_Set_Output_signal_dac = 0x80, //128,
+ TEF665X_Cmd_Set_Output_source_aRadio = 0x04, //4,
+ TEF665X_Cmd_Set_Output_source_dInput = 0x20, //32,
+ TEF665X_Cmd_Set_Output_source_aProcessor = 0xe0, //224,
+ TEF665X_Cmd_Set_Output_source_SinWave = 0xf0, //240,
+ } TEF665x_RADIO_COMMAND;
+
+typedef enum
+{
+ TEF665X_Cmd_Set_Volume = 0x0A, //10,
+ TEF665X_Cmd_Set_Mute = 0x0B, //11,
+ TEF665X_Cmd_Set_Input = 0x0C, //12,
+ TEF665X_Cmd_Set_Output_Source = 0x0D, //13,
+
+ TEF665X_Cmd_Set_Ana_Out = 0x15, //21,
+ TEF665X_Cmd_Set_Dig_IO = 0x16, //22,
+ TEF665X_Cmd_Set_Input_Scaler = 0x17, //23,
+ TEF665X_Cmd_Set_WaveGen = 0x18, //24
+} TEF665x_AUDIO_COMMAND;
+
+typedef enum
+{
+ TEF665X_AUDIO_CMD_22_SIGNAL_i2s1 = 0x21, //33,
+ TEF665X_AUDIO_CMD_22_MODE_voltage = 0x02, //2,
+ TEF665X_AUDIO_CMD_22_FORMAT_16 = 0x10, //16,
+ TEF665X_AUDIO_CMD_22_FORMAT_32 = 0x20, //32,
+ TEF665X_AUDIO_CMD_22_OPERATION_slave = 0, //0,
+ TEF665X_AUDIO_CMD_22_OPERATION_master = 0x0100,//256,
+ TEF665X_AUDIO_CMD_22_SAMPLERATE_48K = 0x12c0 //4800
+} TEF665x_AUDIO_CMD_22;
+
+typedef enum{
+ eDevTEF665x_Boot_state ,
+ eDevTEF665x_Idle_state,
+ eDevTEF665x_Wait_Active,
+ eDevTEF665x_Active_state,
+
+ eDevTEF665x_Power_on,
+
+ eDevTEF665x_Not_Exist,
+
+ eDevTEF665x_Last
+}TEF665x_STATE;
+
+const u8 patchByteValues[]=
+{
+ 0xF0, 0x00, 0x38, 0x16, 0xD0, 0x80, 0x43, 0xB2, 0x38, 0x1D, 0xD0, 0x80, 0xF0, 0x00, 0x70, 0x00, 0xC2, 0xF7, 0xF0, 0x00, 0x38, 0x4E, 0xD0, 0x80,
+ 0xF0, 0x00, 0x38, 0xF1, 0xD0, 0x80, 0xC4, 0xA2, 0x02, 0x0C, 0x60, 0x04, 0x90, 0x01, 0x39, 0x07, 0xD0, 0x80, 0xF0, 0x00, 0x38, 0xD3, 0xD0, 0x80,
+ 0xF0, 0x00, 0x39, 0x0E, 0xD2, 0x80, 0xF0, 0x00, 0x39, 0x12, 0xD0, 0x80, 0x40, 0x20, 0x39, 0x1E, 0xD0, 0x80, 0x9E, 0x30, 0x18, 0xF9, 0xD2, 0x80,
+ 0xF0, 0x00, 0x39, 0x20, 0xD0, 0x80, 0xF0, 0x00, 0x39, 0x23, 0xD0, 0x80, 0xF0, 0x00, 0x39, 0x38, 0xD0, 0x80, 0xF0, 0x00, 0x39, 0x3B, 0xD0, 0x80,
+ 0x00, 0x43, 0x39, 0x43, 0xD9, 0x80, 0xF0, 0x00, 0x39, 0x46, 0xD0, 0x80, 0xF0, 0x00, 0x39, 0x60, 0xD0, 0x80, 0xF0, 0x00, 0x39, 0x71, 0xD0, 0x80,
+ 0xF0, 0x00, 0x39, 0x7F, 0xD0, 0x80, 0xF0, 0x00, 0x39, 0x82, 0xD0, 0x80, 0xF0, 0x00, 0x70, 0x00, 0xA0, 0x14, 0xF0, 0x00, 0x70, 0x00, 0xA0, 0xD4,
+ 0xF0, 0x00, 0x70, 0x00, 0xA0, 0xDB, 0xF0, 0x00, 0x70, 0x00, 0xA1, 0x0C, 0xF0, 0x00, 0x70, 0x00, 0xA1, 0x12, 0xF0, 0x00, 0x70, 0x00, 0xA1, 0x2D,
+ 0xF0, 0x00, 0x20, 0x31, 0xD0, 0x80, 0x00, 0x7F, 0x60, 0x02, 0xE2, 0x00, 0xF0, 0x00, 0x0E, 0x22, 0x60, 0x0A, 0xF0, 0x00, 0x00, 0xFF, 0x60, 0x03,
+ 0xF0, 0x00, 0x01, 0x42, 0xD2, 0x80, 0x90, 0x03, 0x40, 0x02, 0xF0, 0x00, 0x90, 0x43, 0x01, 0x70, 0xD1, 0x80, 0xF0, 0x00, 0x01, 0x69, 0xD0, 0x80,
+ 0x0E, 0x69, 0x60, 0x0A, 0xA1, 0x60, 0x20, 0x23, 0x00, 0x01, 0x60, 0x01, 0xF0, 0x00, 0x70, 0x00, 0xF0, 0x00, 0xC4, 0xCB, 0x70, 0x00, 0xF0, 0x00,
+ 0xCA, 0x09, 0x30, 0x23, 0xF0, 0x00, 0xC2, 0xCB, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x30, 0x23, 0xD0, 0x08, 0x82, 0x00, 0x0D, 0x50, 0x60, 0x08,
+ 0xF0, 0x00, 0x0D, 0x51, 0x60, 0x09, 0x30, 0x00, 0x21, 0x80, 0x60, 0x01, 0xF0, 0x00, 0x40, 0x32, 0xF0, 0x00, 0x30, 0x11, 0x45, 0xF3, 0xF0, 0x00,
+ 0x30, 0x92, 0x2D, 0x30, 0x60, 0x04, 0x31, 0x13, 0x2D, 0x40, 0x60, 0x05, 0x31, 0x94, 0x7F, 0xFF, 0x60, 0x06, 0x32, 0x15, 0x0D, 0x61, 0x60, 0x0A,
+ 0x32, 0x96, 0x0D, 0x6B, 0x60, 0x0B, 0x33, 0x10, 0x0D, 0x50, 0x60, 0x01, 0x33, 0x90, 0x0D, 0x5C, 0x60, 0x02, 0x30, 0x21, 0x0D, 0x63, 0x60, 0x03,
+ 0x30, 0x31, 0x0D, 0x75, 0x60, 0x0C, 0x30, 0xA2, 0x8D, 0x00, 0x60, 0x01, 0x30, 0xB3, 0x01, 0x73, 0x60, 0x02, 0x30, 0x41, 0x00, 0x25, 0x60, 0x03,
+ 0x30, 0xC2, 0x40, 0x44, 0xF0, 0x00, 0x31, 0x43, 0x40, 0x35, 0xF0, 0x00, 0x31, 0xC4, 0x64, 0x00, 0x60, 0x06, 0x32, 0x45, 0x1F, 0x40, 0x60, 0x07,
+ 0x32, 0xC6, 0x70, 0x00, 0xF0, 0x00, 0x33, 0x47, 0x1E, 0xBC, 0x60, 0x0D, 0x33, 0xC0, 0x01, 0x22, 0x60, 0x01, 0x34, 0x40, 0xFD, 0xEE, 0x60, 0x02,
+ 0x30, 0x51, 0x7B, 0x8F, 0x60, 0x03, 0x30, 0xD2, 0xC4, 0x29, 0x60, 0x04, 0x31, 0x51, 0x1E, 0xC2, 0x60, 0x0E, 0x32, 0x53, 0xFF, 0x0D, 0x60, 0x02,
+ 0x32, 0xD4, 0x7D, 0x2E, 0x60, 0x03, 0x30, 0x61, 0xC1, 0x9A, 0x60, 0x04, 0x30, 0xE2, 0x70, 0x00, 0xF0, 0x00, 0x31, 0x61, 0x70, 0x00, 0xF0, 0x00,
+ 0x32, 0x63, 0x70, 0x00, 0xF0, 0x00, 0x32, 0xE4, 0x70, 0x00, 0xD0, 0x08, 0xF0, 0x00, 0x03, 0x70, 0xD2, 0x80, 0xF0, 0x00, 0x70, 0x00, 0xA0, 0x02,
+ 0xF0, 0x00, 0x70, 0x00, 0xA0, 0x59, 0xF0, 0x00, 0x02, 0x15, 0xD0, 0x80, 0xF0, 0x00, 0x0D, 0x51, 0x60, 0x0F, 0xF0, 0x00, 0x05, 0x17, 0x60, 0x0E,
+ 0x23, 0xF6, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x21, 0x63, 0x41, 0xF5, 0x91, 0x8F, 0x21, 0xF8, 0x40, 0x74, 0xC3, 0xEF, 0x21, 0xE0, 0xF0, 0x00,
+ 0xC3, 0xA4, 0x33, 0xF7, 0xF0, 0x00, 0xD8, 0x5B, 0x70, 0x00, 0xF0, 0x00, 0x82, 0x18, 0x70, 0x00, 0xF0, 0x00, 0x9F, 0xAF, 0x18, 0x00, 0xF0, 0x00,
+ 0x9F, 0x0F, 0x31, 0xF8, 0x90, 0x02, 0xF0, 0x00, 0x70, 0x00, 0x90, 0x28, 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x08, 0xF0, 0x00, 0x22, 0x78, 0xF0, 0x00,
+ 0x16, 0xD3, 0x60, 0x09, 0xA0, 0x7F, 0x35, 0xF0, 0x1E, 0xBC, 0x60, 0x0D, 0xF0, 0x00, 0x0D, 0x61, 0x60, 0x08, 0xF0, 0x00, 0x03, 0xA5, 0xD2, 0x80,
+ 0xF0, 0x00, 0x1E, 0xC2, 0x60, 0x0D, 0xF0, 0x00, 0x0D, 0x6B, 0x60, 0x08, 0xF0, 0x00, 0x03, 0xA5, 0xD2, 0x80, 0xF0, 0x00, 0x21, 0x00, 0xF0, 0x00,
+ 0x83, 0x6D, 0x22, 0xF1, 0xF0, 0x00, 0xF0, 0x00, 0x23, 0x77, 0xF0, 0x00, 0x90, 0x41, 0x36, 0x70, 0xF0, 0x00, 0x9E, 0x79, 0x70, 0x00, 0x90, 0x01,
+ 0xF0, 0x00, 0x32, 0xF1, 0xD0, 0x08, 0x91, 0xC7, 0x33, 0x75, 0xF0, 0x00, 0xF0, 0x00, 0x34, 0x70, 0xE6, 0x00, 0xF0, 0x00, 0x34, 0xF0, 0xE6, 0x00,
+ 0xF0, 0x00, 0x24, 0x74, 0xF0, 0x00, 0xF0, 0x00, 0x24, 0xF3, 0xF0, 0x00, 0x8C, 0x24, 0x26, 0xF2, 0x40, 0x16, 0x8A, 0x1B, 0x34, 0x74, 0x4F, 0xF5,
+ 0x82, 0xB7, 0x34, 0xF3, 0xF0, 0x00, 0xF0, 0x00, 0x20, 0x71, 0x90, 0x05, 0x83, 0x04, 0x70, 0x00, 0xF0, 0x00, 0x8E, 0x67, 0x70, 0x00, 0xF0, 0x00,
+ 0xF0, 0x00, 0x70, 0x00, 0x90, 0x02, 0xF0, 0x00, 0x36, 0xF6, 0xF0, 0x00, 0xF0, 0x00, 0x34, 0xF0, 0x80, 0x06, 0x82, 0xAF, 0x70, 0x00, 0xF0, 0x00,
+ 0x82, 0x1B, 0x70, 0x00, 0xD0, 0x09, 0x8E, 0x5F, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x09, 0xF0, 0x00, 0x36, 0xF5, 0xF0, 0x00,
+ 0xF0, 0x00, 0x34, 0x70, 0xF0, 0x00, 0x40, 0x11, 0x27, 0x72, 0xA1, 0x03, 0x90, 0x8A, 0x20, 0xF3, 0xA1, 0x02, 0x8E, 0xD7, 0x37, 0x72, 0xF0, 0x00,
+ 0xF0, 0x00, 0x37, 0xF1, 0xE6, 0x00, 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x08, 0xF0, 0x00, 0x22, 0x7A, 0xF0, 0x00, 0x16, 0xC3, 0x60, 0x09, 0xA0, 0x58,
+ 0xF0, 0x00, 0x18, 0x20, 0xF0, 0x00, 0xF0, 0x00, 0x35, 0x70, 0xF0, 0x00, 0xF0, 0x00, 0x32, 0x7A, 0xD0, 0x08, 0x82, 0x00, 0x0D, 0x51, 0x60, 0x08,
+ 0x40, 0x03, 0x70, 0x00, 0xF0, 0x00, 0x33, 0x80, 0x70, 0x00, 0xF0, 0x00, 0x21, 0x06, 0x70, 0x00, 0xF0, 0x00, 0x37, 0x00, 0x70, 0x00, 0xF0, 0x00,
+ 0x37, 0x80, 0x40, 0x15, 0xF0, 0x00, 0x36, 0x83, 0x70, 0x00, 0xF0, 0x00, 0x33, 0x05, 0x0D, 0x61, 0x60, 0x09, 0x32, 0x86, 0x0D, 0x6B, 0x60, 0x0A,
+ 0x32, 0x10, 0x70, 0x00, 0xF0, 0x00, 0x32, 0x90, 0x70, 0x00, 0xF0, 0x00, 0x33, 0x10, 0x70, 0x00, 0xF0, 0x00, 0x33, 0x90, 0x70, 0x00, 0xF0, 0x00,
+ 0x34, 0x10, 0x70, 0x00, 0xF0, 0x00, 0x34, 0x90, 0x70, 0x00, 0xF0, 0x00, 0x31, 0x10, 0x70, 0x00, 0xF0, 0x00, 0x31, 0x90, 0x70, 0x00, 0xF0, 0x00,
+ 0x32, 0x20, 0x70, 0x00, 0xF0, 0x00, 0x32, 0xA0, 0x70, 0x00, 0xF0, 0x00, 0x33, 0x20, 0x70, 0x00, 0xF0, 0x00, 0x33, 0xA0, 0x70, 0x00, 0xF0, 0x00,
+ 0x34, 0x20, 0x70, 0x00, 0xF0, 0x00, 0x34, 0xA0, 0x70, 0x00, 0xF0, 0x00, 0x31, 0x20, 0x70, 0x00, 0xF0, 0x00, 0x31, 0xA0, 0x70, 0x00, 0xF0, 0x00,
+ 0x82, 0x00, 0x0D, 0x30, 0x60, 0x0A, 0x0D, 0x40, 0x60, 0x0B, 0xC0, 0x10, 0xF0, 0x00, 0x10, 0x20, 0xF0, 0x00, 0x0D, 0x51, 0x60, 0x0C, 0xC0, 0x10,
+ 0xF0, 0x00, 0x10, 0x30, 0xF0, 0x00, 0xF0, 0x00, 0x35, 0xC0, 0xD0, 0x08, 0xF0, 0x00, 0x0D, 0x75, 0x60, 0x0F, 0xF0, 0x00, 0x05, 0x63, 0x60, 0x0E,
+ 0x24, 0xF7, 0x05, 0x1D, 0x60, 0x0D, 0x25, 0x76, 0x70, 0x00, 0xF0, 0x00, 0x91, 0xC7, 0x20, 0xE8, 0x40, 0x15, 0x91, 0x8F, 0x21, 0xE9, 0xD4, 0x09,
+ 0xC3, 0xEF, 0x20, 0x00, 0x40, 0x12, 0x9F, 0xBE, 0x20, 0x11, 0x58, 0x03, 0xA0, 0x80, 0x35, 0x77, 0x90, 0x01, 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x08,
+ 0xF0, 0x00, 0x21, 0xF5, 0xF0, 0x00, 0xA0, 0xCA, 0x22, 0x54, 0xF0, 0x00, 0xCC, 0x09, 0x05, 0x17, 0x60, 0x0C, 0x83, 0x2C, 0x70, 0x00, 0xF0, 0x00,
+ 0x8A, 0x61, 0x70, 0x00, 0xF0, 0x00, 0xAE, 0x48, 0x22, 0x45, 0xA0, 0xCB, 0xA2, 0x28, 0x20, 0x78, 0xF0, 0x00, 0xF0, 0x00, 0x35, 0xF0, 0xF0, 0x00,
+ 0xF0, 0x00, 0x18, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x30, 0x78, 0xF0, 0x00, 0x16, 0xE3, 0x60, 0x09, 0xA0, 0x27, 0x89, 0x01, 0x23, 0xF4, 0xF0, 0x00,
+ 0xF0, 0x00, 0x20, 0xF2, 0xF0, 0x00, 0x82, 0x61, 0x21, 0x73, 0xF0, 0x00, 0xA0, 0x50, 0x36, 0x70, 0xF0, 0x00, 0xA0, 0x58, 0x23, 0x72, 0xE1, 0x40,
+ 0xA8, 0x01, 0x22, 0xF3, 0xF0, 0x00, 0x90, 0x49, 0x22, 0x75, 0xE0, 0x40, 0x80, 0x61, 0x70, 0x00, 0xF0, 0x00, 0x8A, 0x51, 0x33, 0xF1, 0xF0, 0x00,
+ 0xA0, 0x58, 0x70, 0x00, 0xF0, 0x00, 0xAF, 0x48, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x34, 0x70, 0xD0, 0x08, 0x82, 0x00, 0x0D, 0x75, 0x60, 0x08,
+ 0x90, 0x09, 0x0D, 0x00, 0x60, 0x09, 0xF0, 0x00, 0x35, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x33, 0x80, 0xC0, 0x28, 0xF0, 0x00, 0x10, 0x10, 0xF0, 0x00,
+ 0xF0, 0x00, 0x34, 0x81, 0xD0, 0x08, 0x82, 0x49, 0x0D, 0x75, 0x60, 0x08, 0xF0, 0x00, 0x70, 0x00, 0x8F, 0xFD, 0x04, 0x00, 0x60, 0x00, 0xA0, 0xB1,
+ 0x8E, 0xC0, 0x40, 0x00, 0x60, 0x05, 0x60, 0x00, 0x60, 0x05, 0xE6, 0x00, 0xC8, 0x1B, 0x70, 0x00, 0xF0, 0x00, 0xD8, 0xDB, 0x0D, 0x51, 0x60, 0x08,
+ 0x83, 0x5B, 0x70, 0x00, 0xF0, 0x00, 0x9E, 0xBA, 0x30, 0x03, 0xF0, 0x00, 0xF0, 0x00, 0x30, 0x84, 0xD4, 0x09, 0xF0, 0x00, 0x70, 0x00, 0x8F, 0xAF,
+ 0xF0, 0x00, 0x0D, 0x75, 0x60, 0x08, 0xF0, 0x00, 0x0D, 0x51, 0x60, 0x09, 0xF0, 0x00, 0x24, 0x03, 0xF0, 0x00, 0xF0, 0x00, 0x27, 0x94, 0xD0, 0x08,
+ 0xA0, 0x03, 0x70, 0x00, 0xF0, 0x00, 0x00, 0x11, 0x08, 0x00, 0xF0, 0x00, 0x00, 0x11, 0x08, 0x00, 0xC0, 0x0E, 0xA0, 0x09, 0x00, 0x11, 0x08, 0x00,
+ 0xA0, 0x09, 0x70, 0x00, 0xF0, 0x00, 0xA4, 0x08, 0x70, 0x00, 0xD0, 0x08, 0xA0, 0x03, 0x70, 0x00, 0xF0, 0x00, 0x00, 0x11, 0x08, 0x00, 0xF0, 0x00,
+ 0x00, 0x11, 0x08, 0x00, 0xC0, 0x26, 0xA0, 0x09, 0x00, 0x11, 0x08, 0x00, 0xA0, 0x09, 0x70, 0x00, 0xF0, 0x00, 0xA4, 0x08, 0x70, 0x00, 0xD0, 0x08,
+ 0xF0, 0x00, 0x1D, 0x01, 0x60, 0x08, 0xF0, 0x00, 0x0A, 0x2C, 0x60, 0x00, 0xF0, 0x00, 0x01, 0x1A, 0x60, 0x01, 0x31, 0x00, 0x70, 0x00, 0xF0, 0x00,
+ 0x31, 0x81, 0x70, 0x00, 0xD0, 0x08, 0x10, 0x00, 0x60, 0x03, 0xA0, 0x93, 0x30, 0x23, 0x07, 0x73, 0xD2, 0x80, 0xF0, 0x00, 0x07, 0xC6, 0xD0, 0x80,
+ 0x40, 0xE0, 0x00, 0x1F, 0x60, 0x01, 0x13, 0xD5, 0x60, 0x07, 0xA0, 0x06, 0x13, 0xFB, 0x60, 0x06, 0xF0, 0x00, 0x90, 0x40, 0x0D, 0x28, 0xD2, 0x80,
+ 0x14, 0x05, 0x60, 0x06, 0xF0, 0x00, 0xF0, 0x00, 0x0D, 0x28, 0xD2, 0x80, 0x14, 0x0F, 0x60, 0x06, 0xF0, 0x00, 0xF0, 0x00, 0x0D, 0x28, 0xD0, 0x80,
+ 0xD7, 0xCA, 0x00, 0xFF, 0x60, 0x04, 0x81, 0xD7, 0x0C, 0xF7, 0x60, 0x09, 0xD0, 0x56, 0x70, 0x00, 0xF0, 0x00, 0x82, 0x76, 0x30, 0x17, 0xF0, 0x00,
+ 0xD0, 0xF6, 0x40, 0x83, 0xF0, 0x00, 0xC1, 0xA4, 0x20, 0x19, 0xF0, 0x00, 0x82, 0xF6, 0x70, 0x00, 0xF0, 0x00, 0xC1, 0x80, 0x20, 0x17, 0xA0, 0x81,
+ 0xC3, 0xE7, 0x70, 0x00, 0xF0, 0x00, 0xC5, 0xC7, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x10, 0xB1, 0xD0, 0x80, 0x40, 0x00, 0x70, 0x00, 0xAF, 0xF0,
+ 0x41, 0xF1, 0x40, 0x40, 0xF0, 0x00, 0xF0, 0x00, 0x10, 0xB2, 0xD0, 0x80, 0x40, 0x71, 0x10, 0xB2, 0xD2, 0x80, 0xF0, 0x00, 0x10, 0x81, 0xD0, 0x80,
+ 0xF0, 0x00, 0x0B, 0xC9, 0x60, 0x08, 0xF0, 0x00, 0x1D, 0x8D, 0xD2, 0x80, 0xF0, 0x00, 0x16, 0xD5, 0xD0, 0x80, 0xF0, 0x00, 0x0B, 0xC9, 0x60, 0x08,
+ 0xF0, 0x00, 0x1D, 0x8F, 0xD2, 0x80, 0xF0, 0x00, 0x16, 0xDA, 0xD0, 0x80, 0xF0, 0x00, 0x0B, 0x60, 0x60, 0x0E, 0xF0, 0x00, 0x00, 0x04, 0x60, 0x03,
+ 0xF0, 0x00, 0x3F, 0xFC, 0x60, 0x04, 0x32, 0x63, 0x80, 0x08, 0x60, 0x05, 0x32, 0xE4, 0x19, 0x9A, 0x60, 0x06, 0x33, 0x65, 0x70, 0x00, 0xF0, 0x00,
+ 0x31, 0xE6, 0x70, 0x00, 0xD0, 0x08, 0x83, 0x6D, 0x0C, 0x35, 0x60, 0x08, 0x40, 0x60, 0x39, 0x36, 0x60, 0x01, 0x41, 0xE2, 0x21, 0x96, 0x60, 0x03,
+ 0x33, 0x00, 0x41, 0x44, 0xF0, 0x00, 0x33, 0x81, 0x70, 0x00, 0xF0, 0x00, 0x34, 0x02, 0x70, 0x00, 0xF0, 0x00, 0x34, 0x83, 0x70, 0x00, 0xF0, 0x00,
+ 0x35, 0x04, 0x70, 0x00, 0xF0, 0x00, 0x35, 0x85, 0x70, 0x00, 0xD0, 0x08, 0xF0, 0x00, 0x70, 0x00, 0xAF, 0x54, 0xF0, 0x00, 0x70, 0x00, 0x8F, 0x99,
+ 0xF0, 0x00, 0x70, 0x00, 0xAF, 0x92, 0xF0, 0x00, 0x0C, 0x51, 0xD2, 0x80, 0xF0, 0x00, 0x21, 0xA0, 0xD0, 0x80, 0xF0, 0x00, 0x05, 0x2E, 0xD2, 0x80,
+ 0xF0, 0x00, 0x70, 0x00, 0xA0, 0x01, 0xF0, 0x00, 0x21, 0xB7, 0xD0, 0x80, 0xF0, 0x00, 0x20, 0xF0, 0xD2, 0x80, 0x90, 0x02, 0x27, 0xDF, 0xD2, 0x80,
+ 0x9E, 0x69, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x07, 0x00, 0xD1, 0x80, 0xF0, 0x00, 0x23, 0x20, 0xD0, 0x80, 0x17, 0x0B, 0x60, 0x0C, 0xA0, 0x41,
+ 0x00, 0x40, 0x40, 0x05, 0xF0, 0x00, 0x00, 0x41, 0x24, 0x3F, 0xD0, 0x80, 0xF0, 0x00, 0x70, 0x00, 0xAE, 0xDD, 0xF0, 0x00, 0x0C, 0x8D, 0x60, 0x08,
+ 0xF0, 0x00, 0x26, 0x0A, 0xD0, 0x80, 0x83, 0xFF, 0x0D, 0x83, 0x60, 0x08, 0xF0, 0x00, 0x01, 0xF4, 0x60, 0x00, 0xF0, 0x00, 0x03, 0xB1, 0x60, 0x01,
+ 0x10, 0x00, 0x03, 0xB2, 0x60, 0x02, 0x10, 0x01, 0x04, 0x0E, 0x60, 0x00, 0x10, 0x02, 0x04, 0x0F, 0x60, 0x01, 0x10, 0x00, 0x04, 0x5C, 0x60, 0x02,
+ 0x10, 0x01, 0x04, 0x5D, 0x60, 0x00, 0x10, 0x02, 0x13, 0x80, 0x60, 0x01, 0x10, 0x00, 0x0D, 0x8E, 0x60, 0x09, 0x10, 0x01, 0x02, 0xEE, 0x60, 0x00,
+ 0x10, 0x07, 0x43, 0x06, 0x60, 0x01, 0x10, 0x10, 0x04, 0x69, 0x60, 0x02, 0x10, 0x11, 0x44, 0x87, 0x60, 0x00, 0x10, 0x12, 0x05, 0xE3, 0x60, 0x01,
+ 0x10, 0x10, 0x46, 0x08, 0x60, 0x02, 0x10, 0x11, 0x06, 0xAE, 0x60, 0x00, 0x10, 0x12, 0x0D, 0x8C, 0x60, 0x08, 0x10, 0x10, 0x9E, 0x3C, 0x60, 0x00,
+ 0x10, 0x17, 0x0D, 0x82, 0x60, 0x09, 0x10, 0x00, 0x70, 0x00, 0xF0, 0x00, 0x10, 0x07, 0x70, 0x00, 0xF0, 0x00, 0x10, 0x17, 0x70, 0x00, 0xD0, 0x08,
+ 0x0D, 0x83, 0x60, 0x08, 0xA0, 0x24, 0xF0, 0x00, 0x00, 0x02, 0xA0, 0x23, 0x90, 0x82, 0x70, 0x00, 0xF0, 0x00, 0x82, 0x8A, 0x70, 0x00, 0x90, 0x03,
+ 0x90, 0x8A, 0x70, 0x00, 0x90, 0x01, 0xF0, 0x00, 0x70, 0x00, 0x8F, 0xFB, 0x82, 0xBF, 0x70, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x01, 0x99, 0xD2, 0x80,
+ 0xF0, 0x00, 0x0D, 0x82, 0x60, 0x08, 0xF0, 0x00, 0x26, 0xC1, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x02, 0xA0, 0x1A, 0x90, 0x82, 0x70, 0x00, 0xF0, 0x00,
+ 0x82, 0x8A, 0x70, 0x00, 0x90, 0x03, 0x90, 0x8A, 0x70, 0x00, 0x90, 0x01, 0xF0, 0x00, 0x70, 0x00, 0x8F, 0xFB, 0x82, 0x80, 0x70, 0x00, 0xF0, 0x00,
+ 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x08, 0xF0, 0x00, 0x0D, 0x8E, 0x60, 0x0D, 0xF0, 0x00, 0x3F, 0xFF, 0x60, 0x02, 0xF0, 0x00, 0x00, 0x51, 0xA0, 0x11,
+ 0xC2, 0x53, 0x70, 0x00, 0xF0, 0x00, 0x8E, 0xC4, 0x70, 0x00, 0x90, 0x01, 0xF0, 0x00, 0x70, 0x00, 0x97, 0xFC, 0xD4, 0x8F, 0x01, 0x99, 0xD2, 0x80,
+ 0x40, 0x05, 0x0D, 0x8C, 0x60, 0x0D, 0x40, 0x17, 0x3F, 0xFF, 0x60, 0x02, 0x9F, 0x7D, 0x00, 0x51, 0xA0, 0x0A, 0xC2, 0x53, 0x70, 0x00, 0xF0, 0x00,
+ 0x82, 0xC4, 0x27, 0x1C, 0xD1, 0x80, 0xF0, 0x00, 0x70, 0x00, 0x97, 0xFC, 0x91, 0x46, 0x27, 0x20, 0xD0, 0x80, 0xF0, 0x00, 0x29, 0x15, 0xD2, 0x80,
+ 0xF0, 0x00, 0x39, 0x86, 0xD2, 0x80, 0xF0, 0x00, 0x28, 0x88, 0xD0, 0x80, 0x01, 0x52, 0x60, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x28, 0xDC, 0xD2, 0x80,
+ 0xF0, 0x00, 0x28, 0xD5, 0xD0, 0x80, 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x08, 0xF0, 0x00, 0x16, 0xC3, 0x60, 0x08, 0xF0, 0x00, 0x00, 0x9A, 0x60, 0x00,
+ 0xF0, 0x00, 0x02, 0xE3, 0x60, 0x00, 0x10, 0x00, 0x04, 0xF4, 0x60, 0x00, 0x10, 0x00, 0x06, 0xFF, 0x60, 0x00, 0x10, 0x00, 0x09, 0x07, 0x60, 0x00,
+ 0x10, 0x00, 0x0B, 0x10, 0x60, 0x00, 0x10, 0x00, 0x0D, 0x1F, 0x60, 0x00, 0x10, 0x00, 0x0F, 0x65, 0x60, 0x00, 0x10, 0x00, 0x0F, 0x65, 0x60, 0x00,
+ 0x10, 0x00, 0x0D, 0x1F, 0x60, 0x00, 0x10, 0x00, 0x0B, 0x10, 0x60, 0x00, 0x10, 0x00, 0x09, 0x07, 0x60, 0x00, 0x10, 0x00, 0x06, 0xFF, 0x60, 0x00,
+ 0x10, 0x00, 0x04, 0xF4, 0x60, 0x00, 0x10, 0x00, 0x02, 0xE3, 0x60, 0x00, 0x10, 0x00, 0x00, 0x9A, 0x60, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00,
+ 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x16, 0xD3, 0x60, 0x08, 0xF0, 0x00, 0xFF, 0x93, 0x60, 0x00, 0xF0, 0x00, 0xFE, 0x37, 0x60, 0x00,
+ 0x10, 0x00, 0xFD, 0xD9, 0x60, 0x00, 0x10, 0x00, 0xFE, 0x9F, 0x60, 0x00, 0x10, 0x00, 0x03, 0x85, 0x60, 0x00, 0x10, 0x00, 0x0D, 0x6B, 0x60, 0x00,
+ 0x10, 0x00, 0x16, 0x84, 0x60, 0x00, 0x10, 0x00, 0x1E, 0x49, 0x60, 0x00, 0x10, 0x00, 0x1E, 0x49, 0x60, 0x00, 0x10, 0x00, 0x16, 0x84, 0x60, 0x00,
+ 0x10, 0x00, 0x0D, 0x6B, 0x60, 0x00, 0x10, 0x00, 0x03, 0x85, 0x60, 0x00, 0x10, 0x00, 0xFE, 0x9F, 0x60, 0x00, 0x10, 0x00, 0xFD, 0xD9, 0x60, 0x00,
+ 0x10, 0x00, 0xFE, 0x37, 0x60, 0x00, 0x10, 0x00, 0xFF, 0x93, 0x60, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00,
+ 0xF0, 0x00, 0x16, 0xE3, 0x60, 0x08, 0xF0, 0x00, 0x00, 0x64, 0x60, 0x00, 0xF0, 0x00, 0xFF, 0xA8, 0x60, 0x00, 0x10, 0x00, 0xFF, 0xA6, 0x60, 0x00,
+ 0x10, 0x00, 0xFF, 0xDF, 0x60, 0x00, 0x10, 0x00, 0x00, 0x01, 0x60, 0x00, 0x10, 0x00, 0x01, 0x28, 0x60, 0x00, 0x10, 0x00, 0x01, 0x2F, 0x60, 0x00,
+ 0x10, 0x00, 0xFD, 0x23, 0x60, 0x00, 0x10, 0x00, 0xFD, 0xA1, 0x60, 0x00, 0x10, 0x00, 0x03, 0x89, 0x60, 0x00, 0x10, 0x00, 0x02, 0x54, 0x60, 0x00,
+ 0x10, 0x00, 0x0E, 0xA2, 0x60, 0x00, 0x10, 0x00, 0x0C, 0x47, 0x60, 0x00, 0x10, 0x00, 0xF9, 0x42, 0x60, 0x00, 0x10, 0x00, 0xFB, 0xE1, 0x60, 0x00,
+ 0x10, 0x00, 0x00, 0x8C, 0x60, 0x00, 0x10, 0x00, 0xFE, 0x7D, 0x60, 0x00, 0x10, 0x00, 0x02, 0x54, 0x60, 0x00, 0x10, 0x00, 0x03, 0x89, 0x60, 0x00,
+ 0x10, 0x00, 0xFD, 0xA1, 0x60, 0x00, 0x10, 0x00, 0xFD, 0x23, 0x60, 0x00, 0x10, 0x00, 0x01, 0x2F, 0x60, 0x00, 0x10, 0x00, 0x01, 0x28, 0x60, 0x00,
+ 0x10, 0x00, 0x00, 0x01, 0x60, 0x00, 0x10, 0x00, 0xFF, 0xDF, 0x60, 0x00, 0x10, 0x00, 0xFF, 0xA6, 0x60, 0x00, 0x10, 0x00, 0xFF, 0xA8, 0x60, 0x00,
+ 0x10, 0x00, 0x00, 0x64, 0x60, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x17, 0x0B, 0x60, 0x08,
+ 0xF0, 0x00, 0x00, 0x03, 0x60, 0x00, 0xF0, 0x00, 0x54, 0xC0, 0x60, 0x00, 0x10, 0x00, 0x00, 0x05, 0x60, 0x00, 0x10, 0x00, 0x00, 0x05, 0x60, 0x00,
+ 0x10, 0x00, 0x00, 0x0F, 0x60, 0x00, 0x10, 0x00, 0x00, 0x0F, 0x60, 0x00, 0x10, 0x00, 0x09, 0xC0, 0x60, 0x00, 0x10, 0x00, 0x0A, 0x20, 0x60, 0x00,
+ 0x10, 0x00, 0x1D, 0x40, 0x60, 0x00, 0x10, 0x00, 0x1E, 0x60, 0x60, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x10, 0x00, 0xF0, 0x00,
+ 0xF0, 0x00, 0x70, 0x00, 0xD0, 0x08
+};
+
+unsigned char lutByteValues[]=
+{
+ /*
+ 0x40, 0x13, 0x41, 0x68, 0x41, 0xC1, 0x42, 0x14,
+ 0x47, 0xC5, 0x4D, 0x83, 0x4E, 0x49, 0x4E, 0x53,
+ 0x4F, 0x92, 0x4F, 0xEA, 0x50, 0x80, 0x56, 0x60,
+ 0x56, 0xD4, 0x56, 0xD9, 0x61, 0x9F, 0x61, 0xB6,
+ 0x64, 0x3B, 0x66, 0x09, 0x67, 0x0B, 0x67, 0x1A,
+ 0x68, 0x87, 0x68, 0xD4
+ */
+ 0x80, 0x17, 0x80, 0x45, 0x80, 0x96, 0x81, 0x5B,
+ 0x82, 0xD6, 0x88, 0x0B, 0x88, 0x10, 0x88, 0xDB,
+ 0x89, 0x48, 0x8B, 0x0A, 0x8B, 0x12, 0x8B, 0x13,
+ 0x8B, 0x21, 0x8B, 0x26, 0x8C, 0x6F, 0x94, 0xD0,
+ 0x95, 0x20, 0x95, 0x33, 0x95, 0x6F, 0x95, 0xA8,
+ 0x95, 0xAC, 0x95, 0xC3, 0x95, 0xE1, 0x97, 0xC9,
+ 0x97, 0xF6, 0x98, 0x8D, 0x99, 0x00, 0x9A, 0x00,
+ 0x9E, 0x73, 0xA0, 0x12, 0xA1, 0x03, 0xA2, 0xF0,
+ 0xA3, 0x0D, 0xA3, 0x4C, 0xA3, 0x52, 0xA3, 0xED,
+ 0xA8, 0x31, 0xAB, 0x01, 0xAB, 0x1F, 0xAB, 0x4C,
+ 0xAC, 0x76, 0xAC, 0x88, 0xAD, 0xB0, 0xAD, 0xB7,
+ 0xB0, 0xE2, 0xB1, 0x01, 0xB1, 0x0B, 0xB1, 0x35,
+ 0xB1, 0x3B, 0xB1, 0x97, 0xB3, 0x03
+};
+
+const uint32_t patchSize = sizeof(patchByteValues);
+const uint8_t *pPatchBytes = &patchByteValues[0];
+
+const uint32_t lutSize = sizeof(lutByteValues);
+const uint8_t *pLutBytes = &lutByteValues[0];
+
+static const u8 init_para[] = {
+//Set the Band related API settings...
+11, 0x20, 0x0A, 0x01, 0x00, 0x01, 0x09, 0x38, 0x05, 0xDC, 0x05, 0xDC, //FM_BandWidth Auto, FM_Set_Bandwidth (1, 1, 2360, 1500, 1200)
+5, 0x20, 0x14, 0x01, 0x00, 0x01, //FM_MphSuppression, FM_Set_MphSuppression (1, 1)
+//5, 0x20, 0x16, 0x01, 0x00, 0x01,
+7, 0x20, 0x17, 0x01, 0x00, 0x01, 0x05, 0xDC, //FM_NoiseBlanker, FM_Set_NoiseBlanker (1, 1, 1200)
+
+//Set all Weaksignal API settings (LevelOffset)...
+//Set the SoftMute API settings...
+9, 0x20, 0x2A, 0x01, 0x00, 0x03, 0x00, 0x64, 0x00, 0xFA, //FM_SmlMode, FM_Set_SoftMute_Level (1, 3, 100, 250)
+11, 0x20, 0x28, 0x01, 0x00, 0x3C, 0x00, 0x78, 0x00, 0x0A, 0x00, 0x14, //FM_SmSlowAttack,FM_Set_SoftMute_Time (1, 60, 120, 10, 20)
+9, 0x20, 0x2C, 0x01, 0x00, 0x03, 0x01, 0x90, 0x03, 0xE8, //FM_Smm,FM_Set_SoftMute_Mph (1, 3, 400, 1000)
+9, 0x20, 0x2B, 0x01, 0x00, 0x03, 0x01, 0x90, 0x03, 0xE8, //FM_Smn,FM_Set_SoftMute_Noise (1, 3, 400, 1000)
+7, 0x20, 0x2D, 0x01, 0x00, 0x01, 0x00, 0x64, //FM_SmMaximum,FM_Set_SoftMute_Max (1, 1, 100)
+
+//Set the HighCut API settings...
+9, 0x20, 0x34, 0x01, 0x00, 0x01, 0x01, 0xF4, 0x00, 0xC8, //FM_HclMode,FM_Set_HighCut_Level (1, 1, 500, 200)
+11, 0x20, 0x32, 0x01, 0x00, 0x3C, 0x00, 0x78, 0x00, 0x14, 0x00, 0x14, //FM_HcSlowAttack,FM_Set_HighCut_Time (1, 60, 120, 20, 20)
+11, 0x20, 0x33, 0x01, 0x00, 0x01, 0x01, 0x90, 0x00, 0xC8, 0x03, 0x20, //FM_Hco,FM_Set_HighCut_Mod (1, 1, 400, 200, 800)
+9, 0x20, 0x36, 0x01, 0x00, 0x01, 0x00, 0x64, 0x00, 0x64, //FM_Hcm,FM_Set_HighCut_Mph (1, 1, 100, 100)
+9, 0x20, 0x35, 0x01, 0x00, 0x01, 0x00, 0x64, 0x00, 0x64, //FM_Hcn,FM_Set_HighCut_Noise (1, 1, 100, 100)
+7, 0x20, 0x38, 0x01, 0x00, 0x01, 0x3A, 0x98, //FM_HcMinimum,FM_Set_HighCut_Min (1, 1, 15000)
+7, 0x20, 0x3A, 0x01, 0x00, 0x01, 0x00, 0x0A, //FM_HcLowCutMinimum,FM_Set_LowCut_Min (1, 1, 100)
+7, 0x20, 0x37, 0x01, 0x00, 0x01, 0x05, 0xDC, //FM_HcMaximum,FM_Set_HighCut_Max (1, 1, 1500)
+
+//Set the Stereo API settings...
+9, 0x20, 0x3E, 0x01, 0x00, 0x01, 0x01, 0xF4, 0x00, 0xFA, //FM_StlMode,FM_Set_Stereo_Level (1, 1, 500, 250)
+11, 0x20, 0x3C, 0x01, 0x00, 0x3C, 0x00, 0x78, 0x00, 0x0A, 0x00, 0x14, //FM_StSlowAttack,FM_Set_Stereo_Time (1, 60, 120, 10, 20)
+11, 0x20, 0x3D, 0x01, 0x00, 0x01, 0x00, 0xC8, 0x00, 0xC8, 0x03, 0xE8, //FM_Sto,FM_Set_Stereo_Mod (1, 1, 200, 200, 1000)
+9, 0x20, 0x40, 0x01, 0x00, 0x01, 0x00, 0x64, 0x00, 0x64, //FM_Stm,FM_Set_Stereo_Mph (1, 1, 100, 100)
+9, 0x20, 0x3F, 0x01, 0x00, 0x01, 0x00, 0x64, 0x00, 0x64, //FM_Stn,FM_Set_Stereo_Noise (1, 1, 100, 100)
+7, 0x20, 0x39, 0x01, 0x00, 0x01, 0x00, 0x50,
+9, 0x20, 0x4A, 0x01, 0x00, 0x03, 0x00, 0x50, 0x00, 0x80,
+9, 0x20, 0x49, 0x01, 0x00, 0x03, 0x00, 0x50, 0x00, 0x80,
+
+//Set_Deemphasis
+5, 0x20, 0x1F, 0x01, 0x02, 0xEE,
+
+//Set RDS
+9, 0x20, 0x51, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02,
+
+//Set the Audio and Application related API settings...
+9, 0x40, 0x03, 0x01, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, //AM_GPIO 0 Feature
+9, 0x40, 0x03, 0x01, 0x00, 0x01, 0x00, 0x21, 0x00, 0x00, //AM_GPIO 1 Feature
+9, 0x40, 0x03, 0x01, 0x00, 0x02, 0x00, 0x21, 0x00, 0x00, //AM_GPIO 2 Feature
+9, 0x40, 0x03, 0x01, 0x00, 0x00, 0x00, 0x20, 0x01, 0x01, //FM_GPIO 0 Feature set for RDS
+9, 0x40, 0x03, 0x01, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, //FM_GPIO 1 Feature
+9, 0x40, 0x03, 0x01, 0x00, 0x02, 0x00, 0x20, 0x00, 0x00, //FM_GPIO 2 Feature
+
+5, 0x20, 0x1E, 0x01, 0x00, 0x01 ,
+5, 0x20, 0x54, 0x01, 0x00, 0x00 ,
+7, 0x20, 0x0B, 0x01, 0x03, 0x7A, 0x00, 0x00
+//13, 0x30, 0x16, 0x01, 0x00, 0x21, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x11, 0x3A, //Dig_IO_IIS_SD_1 Mode
+//5, 0x30, 0x0B, 0x01, 0x00, 0x00, //Mute
+//5, 0x30, 0x0A, 0x01, 0xFF, 0xf0, //Volume
+//7, 0x30, 0x0D, 0x01, 0x00, 0x80, 0x00, 0xE0,//Audio Output Source DAC L/R
+};
+
+typedef enum
+{
+ eAR_TuningAction_Standby = 0,
+ eAR_TuningAction_Preset = 1, /*!< Tune to new program with short mute time */
+ eAR_TuningAction_Search = 2, /*!< Tune to new program and stay muted */
+ eAR_TuningAction_AF_Update = 3, /*!< Tune to alternative frequency, store quality and tune back with inaudible mute */
+ eAR_TuningAction_Jump = 4, /*!< Tune to alternative frequency with short inaudible mute */
+ eAR_TuningAction_Check = 5, /*!< Tune to alternative frequency and stay muted */
+ eAR_TuningAction_End = 7 /*!< Release the mute of a Search/Check action (frequency is ignored) */
+} AR_TuningAction_t, *pAR_TuningAction_t;
+#endif