diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 73 | ||||
-rw-r--r-- | src/RadioImpl.cc | 326 | ||||
-rw-r--r-- | src/RadioImpl.h | 123 | ||||
-rw-r--r-- | src/convenience/convenience.c | 309 | ||||
-rw-r--r-- | src/convenience/convenience.h | 142 | ||||
-rw-r--r-- | src/main-grpc.cc | 83 | ||||
-rw-r--r-- | src/meson.build | 81 | ||||
-rw-r--r-- | src/radio-binding.c | 752 | ||||
-rw-r--r-- | src/radio_impl.h | 128 | ||||
-rw-r--r-- | src/radio_impl_kingfisher.c | 522 | ||||
-rw-r--r-- | src/radio_impl_kingfisher.h | 25 | ||||
-rw-r--r-- | src/radio_impl_null.c | 279 | ||||
-rw-r--r-- | src/radio_impl_null.h | 25 | ||||
-rw-r--r-- | src/radio_impl_rtlsdr.c | 496 | ||||
-rw-r--r-- | src/radio_impl_rtlsdr.h | 25 | ||||
-rw-r--r-- | src/radio_impl_tef665x.c | 2401 | ||||
-rw-r--r-- | src/radio_impl_tef665x.h | 23 | ||||
-rw-r--r-- | src/radio_output.h | 31 | ||||
-rw-r--r-- | src/radio_output_gstreamer.c | 231 | ||||
-rw-r--r-- | src/rtl_fm.c | 1275 | ||||
-rw-r--r-- | src/rtl_fm.h | 70 | ||||
-rw-r--r-- | src/rtl_fm_helper.c | 243 | ||||
-rw-r--r-- | src/tef665x.h | 397 |
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 |