mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2026-01-27 21:00:57 +03:00
Start of 5.X work
This commit is contained in:
@@ -3,19 +3,19 @@ if(IDF_TARGET STREQUAL "esp32")
|
||||
set(target_requires "driver_bt" "platform_config")
|
||||
endif()
|
||||
|
||||
idf_component_register( SRC_DIRS . external ac101 tas57xx wm8978
|
||||
idf_component_register( SRC_DIRS . external ac101 tas57xx wm8978 cs4265
|
||||
INCLUDE_DIRS . ac101
|
||||
PRIV_REQUIRES
|
||||
codecs
|
||||
newlib
|
||||
esp_common
|
||||
esp-dsp
|
||||
tools
|
||||
platform_config
|
||||
services
|
||||
spotify
|
||||
raop
|
||||
display
|
||||
tools
|
||||
audio
|
||||
led_strip
|
||||
_override
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include <driver/i2s.h>
|
||||
#include "adac.h"
|
||||
#include "ac101.h"
|
||||
#include "Configurator.h"
|
||||
#include "Config.h"
|
||||
static const char TAG[] = "AC101";
|
||||
|
||||
#define SPKOUT_EN ((1 << 9) | (1 << 11) | (1 << 7) | (1 << 5))
|
||||
@@ -48,13 +48,13 @@ static const char TAG[] = "AC101";
|
||||
return b;\
|
||||
}
|
||||
|
||||
static bool init(char *config, int i2c_port, i2s_config_t *i2s_config, bool *mck);
|
||||
static bool init(sys_dac_config *config, i2s_config_t *i2s_config, bool *mck);
|
||||
static void speaker(bool active);
|
||||
static void headset(bool active);
|
||||
static bool volume(unsigned left, unsigned right);
|
||||
static void power(adac_power_e mode);
|
||||
|
||||
const struct adac_s dac_ac101 = { sys_DACModelEnum_AC101, init, adac_deinit, power, speaker, headset, volume };
|
||||
const struct adac_s dac_ac101 = { sys_dac_models_AC101, init, adac_deinit, power, speaker, headset, volume };
|
||||
|
||||
static void ac101_start(ac_module_t mode);
|
||||
static void ac101_stop(void);
|
||||
@@ -64,11 +64,11 @@ static void ac101_set_spk_volume(uint8_t volume);
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static bool init(char *config, int i2c_port, i2s_config_t *i2s_config, bool *mck) {
|
||||
adac_init(config, i2c_port);
|
||||
static bool init(sys_dac_config *config, i2s_config_t *i2s_config, bool *mck) {
|
||||
adac_init(config);
|
||||
if (adac_read_word(AC101_ADDR, CHIP_AUDIO_RS) == 0xffff) {
|
||||
ESP_LOGW(TAG, "No AC101 detected");
|
||||
i2c_driver_delete(i2c_port);
|
||||
i2c_driver_delete(config->i2c.port-sys_i2c_port_PORT0);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "Configurator.h"
|
||||
#include "Config.h"
|
||||
typedef enum { ADAC_ON = 0, ADAC_STANDBY, ADAC_OFF } adac_power_e;
|
||||
|
||||
struct adac_s {
|
||||
sys_DACModelEnum model;
|
||||
bool (*init)(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck);
|
||||
sys_dac_models model;
|
||||
bool (*init)(sys_dac_config *config,i2s_config_t *i2s_config, bool *mck);
|
||||
void (*deinit)(void);
|
||||
void (*power)(adac_power_e mode);
|
||||
void (*speaker)(bool active);
|
||||
@@ -29,9 +29,10 @@ extern const struct adac_s dac_tas57xx;
|
||||
extern const struct adac_s dac_tas5713;
|
||||
extern const struct adac_s dac_ac101;
|
||||
extern const struct adac_s dac_wm8978;
|
||||
extern const struct adac_s dac_cs4265;
|
||||
extern const struct adac_s dac_external;
|
||||
|
||||
int adac_init(char *config, int i2c_port);
|
||||
int adac_init(sys_dac_config *config);
|
||||
void adac_deinit(void);
|
||||
esp_err_t adac_write(int i2c_addr, uint8_t reg, uint8_t *data, size_t count);
|
||||
esp_err_t adac_write_byte(int i2c_addr, uint8_t reg, uint8_t val);
|
||||
|
||||
@@ -23,16 +23,13 @@
|
||||
} while (0)
|
||||
|
||||
static const char TAG[] = "DAC core";
|
||||
static int i2c_port = -1;
|
||||
|
||||
i2c_port_t i2c_port = -1;
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
int adac_init(char *config, int i2c_port_num) {
|
||||
int adac_init(sys_dac_config *config) {
|
||||
int i2c_addr = 0;
|
||||
|
||||
i2c_port = i2c_port_num;
|
||||
|
||||
// configure i2c
|
||||
i2c_config_t i2c_config = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
@@ -42,17 +39,19 @@ int adac_init(char *config, int i2c_port_num) {
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = 250000,
|
||||
};
|
||||
|
||||
PARSE_PARAM(config, "i2c", '=', i2c_addr);
|
||||
PARSE_PARAM(config, "sda", '=', i2c_config.sda_io_num);
|
||||
PARSE_PARAM(config, "scl", '=', i2c_config.scl_io_num);
|
||||
i2c_addr = config->addr;
|
||||
if(config->has_i2c){
|
||||
i2c_config.sda_io_num = config->i2c.sda;
|
||||
i2c_config.scl_io_num = config->i2c.scl;
|
||||
i2c_config.master.clk_speed = config->i2c.speed>0?config->i2c.speed:i2c_config.master.clk_speed;
|
||||
}
|
||||
|
||||
if (i2c_config.sda_io_num == -1 || i2c_config.scl_io_num == -1) {
|
||||
ESP_LOGW(TAG, "DAC does not use i2c");
|
||||
return i2c_addr;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "DAC uses I2C port:%d, sda:%d, scl:%d", i2c_port, i2c_config.sda_io_num, i2c_config.scl_io_num);
|
||||
i2c_port = config->i2c.port-sys_i2c_port_PORT0;
|
||||
ESP_LOGI(TAG, "DAC uses I2C port:%s, sda:%d, scl:%d", sys_i2c_port_name(config->i2c.port), i2c_config.sda_io_num, i2c_config.scl_io_num);
|
||||
|
||||
// we have an I2C configured
|
||||
i2c_param_config(i2c_port, &i2c_config);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
#include "squeezelite.h"
|
||||
// #include "Configurator.h"
|
||||
// #include "Config.h"
|
||||
#pragma message("fixme: look for TODO below")
|
||||
#include "audio_controls.h"
|
||||
|
||||
@@ -54,7 +54,7 @@ static u16_t server_cport;
|
||||
static int cli_sock = -1;
|
||||
static u8_t mac[6];
|
||||
static void (*chained_notify)(in_addr_t, u16_t, u16_t);
|
||||
static bool raw_mode;
|
||||
static bool raw_mode=false;
|
||||
|
||||
static void cli_send_cmd(char *cmd);
|
||||
|
||||
@@ -244,15 +244,16 @@ static bool ir_handler(u16_t addr, u16_t cmd) {
|
||||
*/
|
||||
void sb_controls_init(void) {
|
||||
// TODO: Add support for the commented code
|
||||
#pragma message("sb_controls_init needs to be implemented")
|
||||
// char *p = config_alloc_get_default(NVS_TYPE_STR, "lms_ctrls_raw", "n", 0);
|
||||
// raw_mode = p && (*p == '1' || *p == 'Y' || *p == 'y');
|
||||
// free(p);
|
||||
|
||||
// LOG_INFO("initializing audio (buttons/rotary/ir) controls (raw:%u)", raw_mode);
|
||||
LOG_INFO("initializing audio (buttons/rotary/ir) controls (raw:%u)", raw_mode);
|
||||
|
||||
// get_mac(mac);
|
||||
// actrls_set_default(LMS_controls, raw_mode, NULL, ir_handler);
|
||||
get_mac(mac);
|
||||
actrls_set_default(LMS_controls, raw_mode, NULL, ir_handler);
|
||||
|
||||
// chained_notify = server_notify;
|
||||
// server_notify = notify;
|
||||
chained_notify = server_notify;
|
||||
server_notify = notify;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Squeezelite for esp32
|
||||
*
|
||||
* (c) Sebastien 2019
|
||||
@@ -8,21 +8,21 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <string.h>
|
||||
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_INFO
|
||||
#include "Config.h"
|
||||
#include "adac.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "adac.h"
|
||||
#include "stdio.h"
|
||||
#include "math.h"
|
||||
#include "Configurator.h"
|
||||
#define CS4265_PULL_UP (0x4F )
|
||||
#define CS4265_PULL_DOWN (0x4E )
|
||||
#include "stdio.h"
|
||||
#define CS4265_PULL_UP (0x4F)
|
||||
#define CS4265_PULL_DOWN (0x4E)
|
||||
|
||||
#ifndef ARRAY_SIZE
|
||||
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
||||
@@ -30,188 +30,181 @@
|
||||
|
||||
static const char TAG[] = "CS4265";
|
||||
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config);
|
||||
static bool init(sys_dac_config * config, i2s_config_t* i2s_config, bool* mck);
|
||||
static void speaker(bool active);
|
||||
static void headset(bool active);
|
||||
static bool volume(unsigned left, unsigned right);
|
||||
static void power(adac_power_e mode);
|
||||
static esp_err_t cs4265_update_bit(uint8_t reg_no,uint8_t mask,uint8_t val );
|
||||
static esp_err_t cs4265_update_bit(uint8_t reg_no, uint8_t mask, uint8_t val);
|
||||
static esp_err_t set_clock();
|
||||
const struct adac_s dac_cs4265 = { sys_DACModelEnum_CS4265, init, adac_deinit, power, speaker, headset, volume };
|
||||
const struct adac_s dac_cs4265 = {
|
||||
sys_dac_models_CS4265, init, adac_deinit, power, speaker, headset, volume};
|
||||
|
||||
struct cs4265_cmd_s {
|
||||
uint8_t reg;
|
||||
uint8_t value;
|
||||
uint8_t reg;
|
||||
uint8_t value;
|
||||
};
|
||||
struct cs4265_private {
|
||||
uint8_t format;
|
||||
uint32_t sysclk;
|
||||
i2s_config_t *i2s_config;
|
||||
int i2c_port;
|
||||
};
|
||||
uint8_t format;
|
||||
uint32_t sysclk;
|
||||
i2s_config_t* i2s_config;
|
||||
i2c_port_t port;
|
||||
};
|
||||
struct cs4265_private cs4265;
|
||||
|
||||
#define CS4265_CHIP_ID 0x1
|
||||
#define CS4265_CHIP_ID_VAL 0xD0
|
||||
#define CS4265_CHIP_ID_MASK 0xF0
|
||||
#define CS4265_REV_ID_MASK 0x0F
|
||||
#define CS4265_CHIP_ID 0x1
|
||||
#define CS4265_CHIP_ID_VAL 0xD0
|
||||
#define CS4265_CHIP_ID_MASK 0xF0
|
||||
#define CS4265_REV_ID_MASK 0x0F
|
||||
|
||||
#define CS4265_PWRCTL 0x02
|
||||
#define CS4265_PWRCTL_PDN (1 << 0)
|
||||
#define CS4265_PWRCTL_PDN_DAC (1 << 1)
|
||||
#define CS4265_PWRCTL_PDN_ADC (1 << 2)
|
||||
#define CS4265_PWRCTL_PDN_MIC (1 << 3)
|
||||
#define CS4265_PWRCTL_FREEZE (1 << 7)
|
||||
#define CS4265_PWRCTL_PDN_ALL CS4265_PWRCTL_PDN | CS4265_PWRCTL_PDN_ADC | CS4265_PWRCTL_PDN_DAC | CS4265_PWRCTL_PDN_MIC
|
||||
#define CS4265_PWRCTL 0x02
|
||||
#define CS4265_PWRCTL_PDN (1 << 0)
|
||||
#define CS4265_PWRCTL_PDN_DAC (1 << 1)
|
||||
#define CS4265_PWRCTL_PDN_ADC (1 << 2)
|
||||
#define CS4265_PWRCTL_PDN_MIC (1 << 3)
|
||||
#define CS4265_PWRCTL_FREEZE (1 << 7)
|
||||
#define CS4265_PWRCTL_PDN_ALL \
|
||||
CS4265_PWRCTL_PDN | CS4265_PWRCTL_PDN_ADC | CS4265_PWRCTL_PDN_DAC | CS4265_PWRCTL_PDN_MIC
|
||||
|
||||
|
||||
|
||||
#define CS4265_DAC_CTL 0x3
|
||||
#define CS4265_DAC_CTL 0x3
|
||||
// De-Emphasis Control (Bit 1)
|
||||
// The standard 50/15 i2s digital de-emphasis filter response may be implemented for a sample
|
||||
// rate of 44.1 kHz when the DeEmph bit is set. NOTE: De-emphasis is available only in Single-Speed Mode.
|
||||
#define CS4265_DAC_CTL_DEEMPH (1 << 1)
|
||||
// rate of 44.1 kHz when the DeEmph bit is set. NOTE: De-emphasis is available only in Single-Speed
|
||||
// Mode.
|
||||
#define CS4265_DAC_CTL_DEEMPH (1 << 1)
|
||||
// MUTE DAC
|
||||
// The DAC outputs will mute and the MUTEC pin will become active when this bit is set. Though this bit is
|
||||
// active high, it should be noted that the MUTEC pin is active low. The common mode voltage on the outputs
|
||||
// will be retained when this bit is set. The muting function is effected, similar to attenuation changes, by the
|
||||
// DACSoft and DACZero bits in the DAC Control 2 register.
|
||||
#define CS4265_DAC_CTL_MUTE (1 << 2)
|
||||
// The required relationship between LRCK, SCLK and SDIN for the DAC is defined by the DAC Digital Interface
|
||||
// DAC_DIF1 DAC_DIF0 Description Format Figure
|
||||
// 0 0 Left Justified, up to 24-bit data (default) 0 5
|
||||
// 0 1 I²S, up to 24-bit data 1 6
|
||||
// The DAC outputs will mute and the MUTEC pin will become active when this bit is set. Though this
|
||||
// bit is active high, it should be noted that the MUTEC pin is active low. The common mode voltage
|
||||
// on the outputs will be retained when this bit is set. The muting function is effected, similar to
|
||||
// attenuation changes, by the DACSoft and DACZero bits in the DAC Control 2 register.
|
||||
#define CS4265_DAC_CTL_MUTE (1 << 2)
|
||||
// The required relationship between LRCK, SCLK and SDIN for the DAC is defined by the DAC Digital
|
||||
// Interface DAC_DIF1 DAC_DIF0 Description Format Figure 0 0 Left
|
||||
// Justified, up to 24-bit data (default) 0 5 0 1 I²S, up to 24-bit data 1 6
|
||||
// 1 0 Right-Justified, 16-bit Data 2 7
|
||||
// 1 1 Right-Justified, 24-bit Data 3 7
|
||||
#define CS4265_DAC_CTL_DIF0 (1 << 4)
|
||||
// The required relationship between LRCK, SCLK and SDIN for the DAC is defined by the DAC Digital Interface
|
||||
// DAC_DIF1 DAC_DIF0 Description Format Figure
|
||||
// 0 0 Left Justified, up to 24-bit data (default) 0 5
|
||||
// 0 1 I²S, up to 24-bit data 1 6
|
||||
#define CS4265_DAC_CTL_DIF0 (1 << 4)
|
||||
// The required relationship between LRCK, SCLK and SDIN for the DAC is defined by the DAC Digital
|
||||
// Interface DAC_DIF1 DAC_DIF0 Description Format Figure 0 0 Left
|
||||
// Justified, up to 24-bit data (default) 0 5 0 1 I²S, up to 24-bit data 1 6
|
||||
// 1 0 Right-Justified, 16-bit Data 2 7
|
||||
// 1 1 Right-Justified, 24-bit Data 3 7
|
||||
#define CS4265_DAC_CTL_DIF1 (1 << 5)
|
||||
#define CS4265_DAC_CTL_DIF1 (1 << 5)
|
||||
|
||||
#define CS4265_ADC_CTL 0x4
|
||||
#define CS4265_ADC_MASTER 1
|
||||
|
||||
#define CS4265_ADC_CTL_MUTE (1 << 2)
|
||||
#define CS4265_ADC_DIF (1 << 4)
|
||||
#define CS4265_ADC_FM (3 << 6)
|
||||
|
||||
#define CS4265_ADC_CTL 0x4
|
||||
#define CS4265_ADC_MASTER 1
|
||||
|
||||
#define CS4265_ADC_CTL_MUTE (1 << 2)
|
||||
#define CS4265_ADC_DIF (1 << 4)
|
||||
#define CS4265_ADC_FM (3 << 6)
|
||||
|
||||
//Master Clock Dividers (Bits 6:4)
|
||||
//Sets the frequency of the supplied MCLK signal.
|
||||
// Master Clock Dividers (Bits 6:4)
|
||||
// Sets the frequency of the supplied MCLK signal.
|
||||
//
|
||||
//MCLK Divider MCLK Freq2 MCLK Freq1 MCLK Freq0
|
||||
// ÷ 1 0 0 0
|
||||
// ÷ 1.5 0 0 1
|
||||
// ÷ 2 0 1 0
|
||||
// ÷ 3 0 1 1
|
||||
// ÷ 4 1 0 0
|
||||
// NA 1 0 1
|
||||
// NA 1 1 x
|
||||
#define CS4265_MCLK_FREQ 0x5
|
||||
#define CS4265_MCLK_FREQ_1_0X (0b000<<4 )
|
||||
#define CS4265_MCLK_FREQ_1_5X (0b001<<4 )
|
||||
#define CS4265_MCLK_FREQ_2_0X (0b010<<4 )
|
||||
#define CS4265_MCLK_FREQ_3_0X (0b011<<4 )
|
||||
#define CS4265_MCLK_FREQ_4_0X (0b100<<4 )
|
||||
// MCLK Divider MCLK Freq2 MCLK Freq1 MCLK Freq0
|
||||
// ÷ 1 0 0 0
|
||||
// ÷ 1.5 0 0 1
|
||||
// ÷ 2 0 1 0
|
||||
// ÷ 3 0 1 1
|
||||
// ÷ 4 1 0 0
|
||||
// NA 1 0 1
|
||||
// NA 1 1 x
|
||||
#define CS4265_MCLK_FREQ 0x5
|
||||
#define CS4265_MCLK_FREQ_1_0X (0b000 << 4)
|
||||
#define CS4265_MCLK_FREQ_1_5X (0b001 << 4)
|
||||
#define CS4265_MCLK_FREQ_2_0X (0b010 << 4)
|
||||
#define CS4265_MCLK_FREQ_3_0X (0b011 << 4)
|
||||
#define CS4265_MCLK_FREQ_4_0X (0b100 << 4)
|
||||
|
||||
#define CS4265_MCLK_FREQ_MASK (7 << 4)
|
||||
|
||||
#define CS4265_MCLK_FREQ_MASK (7 << 4)
|
||||
|
||||
#define CS4265_SIG_SEL 0x6
|
||||
#define CS4265_SIG_SEL_LOOP (1 << 1)
|
||||
#define CS4265_SIG_SEL_SDIN2 (1 << 7)
|
||||
#define CS4265_SIG_SEL_SDIN1 (0 << 7)
|
||||
#define CS4265_SIG_SEL 0x6
|
||||
#define CS4265_SIG_SEL_LOOP (1 << 1)
|
||||
#define CS4265_SIG_SEL_SDIN2 (1 << 7)
|
||||
#define CS4265_SIG_SEL_SDIN1 (0 << 7)
|
||||
|
||||
// Sets the gain or attenuation for the ADC input PGA stage. The gain may be adjusted from -12 dB to
|
||||
// +12 dB in 0.5 dB steps. The gain bits are in two’s complement with the Gain0 bit set for a 0.5 dB step.
|
||||
// Register settings outside of the ±12 dB range are reserved and must not be used. See Table 13 for example settings
|
||||
#define CS4265_CHB_PGA_CTL 0x7
|
||||
// +12 dB in 0.5 dB steps. The gain bits are in two’s complement with the Gain0 bit set for a 0.5 dB
|
||||
// step. Register settings outside of the ±12 dB range are reserved and must not be used. See Table
|
||||
// 13 for example settings
|
||||
#define CS4265_CHB_PGA_CTL 0x7
|
||||
// Sets the gain or attenuation for the ADC input PGA stage. The gain may be adjusted from -12 dB to
|
||||
// +12 dB in 0.5 dB steps. The gain bits are in two’s complement with the Gain0 bit set for a 0.5 dB step.
|
||||
// Register settings outside of the ±12 dB range are reserved and must not be used. See Table 13 for example settings
|
||||
#define CS4265_CHA_PGA_CTL 0x8
|
||||
// +12 dB in 0.5 dB steps. The gain bits are in two’s complement with the Gain0 bit set for a 0.5 dB
|
||||
// step. Register settings outside of the ±12 dB range are reserved and must not be used. See Table
|
||||
// 13 for example settings
|
||||
#define CS4265_CHA_PGA_CTL 0x8
|
||||
// Gain[5:0] Setting
|
||||
// 101000 -12 dB
|
||||
// 000000 0 dB
|
||||
// 011000 +12 dB
|
||||
|
||||
#define CS4265_ADC_CTL2 0x9
|
||||
|
||||
#define CS4265_ADC_CTL2 0x9
|
||||
|
||||
// The digital volume control allows the user to attenuate the signal in 0.5 dB increments from 0 to -127 dB.
|
||||
// The Vol0 bit activates a 0.5 dB attenuation when set, and no attenuation when cleared. The Vol[7:1] bits
|
||||
// activate attenuation equal to their decimal equivalent (in dB).
|
||||
//Binary Code Volume Setting
|
||||
//00000000 0 dB
|
||||
//00000001 -0.5 dB
|
||||
//00101000 -20 dB
|
||||
//00101001 -20.5 dB
|
||||
//11111110 -127 dB
|
||||
//11111111 -127.5 dB
|
||||
#define CS4265_DAC_CHA_VOL 0xA
|
||||
// The digital volume control allows the user to attenuate the signal in 0.5 dB increments from 0 to -127 dB.
|
||||
// The Vol0 bit activates a 0.5 dB attenuation when set, and no attenuation when cleared. The Vol[7:1] bits
|
||||
// activate attenuation equal to their decimal equivalent (in dB).
|
||||
//Binary Code Volume Setting
|
||||
//00000000 0 dB
|
||||
//00000001 -0.5 dB
|
||||
//00101000 -20 dB
|
||||
//00101001 -20.5 dB
|
||||
//11111110 -127 dB
|
||||
//11111111 -127.5 dB
|
||||
#define CS4265_DAC_CHB_VOL 0xB
|
||||
#define CS4265_DAC_VOL_ATT_000_0 0b00000000
|
||||
#define CS4265_DAC_VOL_ATT_000_5 0b00000001
|
||||
#define CS4265_DAC_VOL_ATT_020_0 0b00101000
|
||||
#define CS4265_DAC_VOL_ATT_020_5 0b00101001
|
||||
#define CS4265_DAC_VOL_ATT_127_0 0b11111110
|
||||
#define CS4265_DAC_VOL_ATT_127_5 0b11111111
|
||||
// The digital volume control allows the user to attenuate the signal in 0.5 dB increments from 0 to
|
||||
// -127 dB. The Vol0 bit activates a 0.5 dB attenuation when set, and no attenuation when cleared.
|
||||
// The Vol[7:1] bits activate attenuation equal to their decimal equivalent (in dB).
|
||||
// Binary Code Volume Setting
|
||||
// 00000000 0 dB
|
||||
// 00000001 -0.5 dB
|
||||
// 00101000 -20 dB
|
||||
// 00101001 -20.5 dB
|
||||
// 11111110 -127 dB
|
||||
// 11111111 -127.5 dB
|
||||
#define CS4265_DAC_CHA_VOL 0xA
|
||||
// The digital volume control allows the user to attenuate the signal in 0.5 dB increments from 0 to
|
||||
// -127 dB. The Vol0 bit activates a 0.5 dB attenuation when set, and no attenuation when cleared.
|
||||
// The Vol[7:1] bits activate attenuation equal to their decimal equivalent (in dB).
|
||||
// Binary Code Volume Setting
|
||||
// 00000000 0 dB
|
||||
// 00000001 -0.5 dB
|
||||
// 00101000 -20 dB
|
||||
// 00101001 -20.5 dB
|
||||
// 11111110 -127 dB
|
||||
// 11111111 -127.5 dB
|
||||
#define CS4265_DAC_CHB_VOL 0xB
|
||||
#define CS4265_DAC_VOL_ATT_000_0 0b00000000
|
||||
#define CS4265_DAC_VOL_ATT_000_5 0b00000001
|
||||
#define CS4265_DAC_VOL_ATT_020_0 0b00101000
|
||||
#define CS4265_DAC_VOL_ATT_020_5 0b00101001
|
||||
#define CS4265_DAC_VOL_ATT_127_0 0b11111110
|
||||
#define CS4265_DAC_VOL_ATT_127_5 0b11111111
|
||||
|
||||
// DAC Soft Ramp or Zero Cross Enable (Bits 7:6)
|
||||
//
|
||||
// Soft Ramp Enable
|
||||
// Soft Ramp allows level changes, both muting and attenuation, to be implemented by incrementally ramping, in 1/8 dB steps, from the current level to the new level at a rate of 1 dB per 8 left/right clock periods.
|
||||
// See Table 17.
|
||||
// Zero Cross Enable
|
||||
// Zero Cross Enable dictates that signal-level changes, either by attenuation changes or muting, will occur
|
||||
// on a signal zero crossing to minimize audible artifacts. The requested level change will occur after a timeout period between 512 and 1024 sample periods (10.7 ms to 21.3 ms at 48 kHz sample rate) if the signal
|
||||
// does not encounter a zero crossing. The zero cross function is independently monitored and implemented
|
||||
// for each channel. See Table 17.
|
||||
// Soft Ramp and Zero Cross Enable
|
||||
// Soft Ramp and Zero Cross Enable dictate that signal-level changes, either by attenuation changes or muting, will occur in 1/8 dB steps and be implemented on a signal zero crossing. The 1/8 dB level change will
|
||||
// occur after a time-out period between 512 and 1024 sample periods (10.7 ms to 21.3 ms at 48 kHz sample rate) if the signal does not encounter a zero crossing. The zero cross function is independently monitored and implemented for each channel
|
||||
// DACSoft DACZeroCross Mode
|
||||
// 0 0 Changes to affect immediately
|
||||
// 0 1 Zero Cross enabled
|
||||
// 1 0 Soft Ramp enabled
|
||||
// 1 1 Soft Ramp and Zero Cross enabled (default)
|
||||
#define CS4265_DAC_CTL2 0xC
|
||||
#define CS4265_DAC_CTL2_ZERO_CROSS_EN (uint8_t)(0b01 <<7)
|
||||
#define CS4265_DAC_CTL2_SOFT_RAMP_EN (uint8_t)(0b10 <<7)
|
||||
#define CS4265_DAC_CTL2_SOFT_RAMP_ZERO_CROSS_EN (uint8_t)(0b11 <<7)
|
||||
// Soft Ramp allows level changes, both muting and attenuation, to be implemented by incrementally
|
||||
// ramping, in 1/8 dB steps, from the current level to the new level at a rate of 1 dB per 8
|
||||
// left/right clock periods. See Table 17. Zero Cross Enable Zero Cross Enable dictates that
|
||||
// signal-level changes, either by attenuation changes or muting, will occur on a signal zero
|
||||
// crossing to minimize audible artifacts. The requested level change will occur after a timeout
|
||||
// period between 512 and 1024 sample periods (10.7 ms to 21.3 ms at 48 kHz sample rate) if the
|
||||
// signal does not encounter a zero crossing. The zero cross function is independently monitored and
|
||||
// implemented for each channel. See Table 17. Soft Ramp and Zero Cross Enable Soft Ramp and Zero
|
||||
// Cross Enable dictate that signal-level changes, either by attenuation changes or muting, will
|
||||
// occur in 1/8 dB steps and be implemented on a signal zero crossing. The 1/8 dB level change will
|
||||
// occur after a time-out period between 512 and 1024 sample periods (10.7 ms to 21.3 ms at 48 kHz
|
||||
// sample rate) if the signal does not encounter a zero crossing. The zero cross function is
|
||||
// independently monitored and implemented for each channel DACSoft DACZeroCross Mode 0 0 Changes to
|
||||
// affect immediately 0 1 Zero Cross enabled 1 0 Soft Ramp enabled 1 1 Soft Ramp and Zero Cross
|
||||
// enabled (default)
|
||||
#define CS4265_DAC_CTL2 0xC
|
||||
#define CS4265_DAC_CTL2_ZERO_CROSS_EN (uint8_t)(0b01 << 7)
|
||||
#define CS4265_DAC_CTL2_SOFT_RAMP_EN (uint8_t)(0b10 << 7)
|
||||
#define CS4265_DAC_CTL2_SOFT_RAMP_ZERO_CROSS_EN (uint8_t)(0b11 << 7)
|
||||
|
||||
#define CS4265_INT_STATUS 0xD
|
||||
#define CS4265_INT_STATUS_ADC_UNDF (1 << 0)
|
||||
#define CS4265_INT_STATUS_ADC_OVF (1 << 1)
|
||||
#define CS4265_INT_STATUS_CLKERR (1 << 3)
|
||||
|
||||
#define CS4265_INT_STATUS 0xD
|
||||
#define CS4265_INT_STATUS_ADC_UNDF (1<<0)
|
||||
#define CS4265_INT_STATUS_ADC_OVF (1<<1)
|
||||
#define CS4265_INT_STATUS_CLKERR (1<<3)
|
||||
#define CS4265_INT_MASK 0xE
|
||||
#define CS4265_STATUS_MODE_MSB 0xF
|
||||
#define CS4265_STATUS_MODE_LSB 0x10
|
||||
|
||||
// Transmitter Control 1 - Address 11h
|
||||
#define CS4265_SPDIF_CTL1 0x11
|
||||
|
||||
#define CS4265_INT_MASK 0xE
|
||||
#define CS4265_STATUS_MODE_MSB 0xF
|
||||
#define CS4265_STATUS_MODE_LSB 0x10
|
||||
|
||||
//Transmitter Control 1 - Address 11h
|
||||
#define CS4265_SPDIF_CTL1 0x11
|
||||
|
||||
|
||||
|
||||
#define CS4265_SPDIF_CTL2 0x12
|
||||
#define CS4265_SPDIF_CTL2 0x12
|
||||
// Transmitter Digital Interface Format (Bits 7:6)
|
||||
// Function:
|
||||
// The required relationship between LRCK, SCLK and SDIN for the transmitter is defined
|
||||
@@ -220,343 +213,348 @@ struct cs4265_private cs4265;
|
||||
// 0 1 I²S, up to 24-bit data 1 6
|
||||
// 1 0 Right-Justified, 16-bit Data 2 7
|
||||
// 1 1 Right-Justified, 24-bit Data 3 7
|
||||
#define CS4265_SPDIF_CTL2_MMTLR (1<<0)
|
||||
#define CS4265_SPDIF_CTL2_MMTCS (1<<1)
|
||||
#define CS4265_SPDIF_CTL2_MMT (1<<2)
|
||||
#define CS4265_SPDIF_CTL2_V (1<<3)
|
||||
#define CS4265_SPDIF_CTL2_TXMUTE (1<<4)
|
||||
#define CS4265_SPDIF_CTL2_TXOFF (1<<5)
|
||||
#define CS4265_SPDIF_CTL2_MUTE (1 << 4)
|
||||
#define CS4265_SPDIF_CTL2_DIF (3 << 6)
|
||||
#define CS4265_SPDIF_CTL2_DIF0 (1 << 6)
|
||||
#define CS4265_SPDIF_CTL2_DIF1 (1 << 7)
|
||||
#define CS4265_SPDIF_CTL2_MMTLR (1 << 0)
|
||||
#define CS4265_SPDIF_CTL2_MMTCS (1 << 1)
|
||||
#define CS4265_SPDIF_CTL2_MMT (1 << 2)
|
||||
#define CS4265_SPDIF_CTL2_V (1 << 3)
|
||||
#define CS4265_SPDIF_CTL2_TXMUTE (1 << 4)
|
||||
#define CS4265_SPDIF_CTL2_TXOFF (1 << 5)
|
||||
#define CS4265_SPDIF_CTL2_MUTE (1 << 4)
|
||||
#define CS4265_SPDIF_CTL2_DIF (3 << 6)
|
||||
#define CS4265_SPDIF_CTL2_DIF0 (1 << 6)
|
||||
#define CS4265_SPDIF_CTL2_DIF1 (1 << 7)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#define CS4265_C_DATA_BUFF 0x13
|
||||
#define CS4265_MAX_REGISTER 0x2A
|
||||
#define CS4265_C_DATA_BUFF 0x13
|
||||
#define CS4265_MAX_REGISTER 0x2A
|
||||
struct cs4265_clk_para {
|
||||
uint32_t mclk;
|
||||
uint32_t rate;
|
||||
uint8_t fm_mode; /* values 1, 2, or 4 */
|
||||
uint8_t mclkdiv;
|
||||
uint32_t mclk;
|
||||
uint32_t rate;
|
||||
uint8_t fm_mode; /* values 1, 2, or 4 */
|
||||
uint8_t mclkdiv;
|
||||
};
|
||||
static const struct cs4265_clk_para clk_map_table[] = {
|
||||
/*32k*/
|
||||
{8192000, 32000, 0, 0},
|
||||
{12288000, 32000, 0, 1},
|
||||
{16384000, 32000, 0, 2},
|
||||
{24576000, 32000, 0, 3},
|
||||
{32768000, 32000, 0, 4},
|
||||
/*32k*/
|
||||
{8192000, 32000, 0, 0},
|
||||
{12288000, 32000, 0, 1},
|
||||
{16384000, 32000, 0, 2},
|
||||
{24576000, 32000, 0, 3},
|
||||
{32768000, 32000, 0, 4},
|
||||
|
||||
/*44.1k*/
|
||||
{11289600, 44100, 0, 0},
|
||||
{16934400, 44100, 0, 1},
|
||||
{22579200, 44100, 0, 2},
|
||||
{33868000, 44100, 0, 3},
|
||||
{45158400, 44100, 0, 4},
|
||||
/*44.1k*/
|
||||
{11289600, 44100, 0, 0},
|
||||
{16934400, 44100, 0, 1},
|
||||
{22579200, 44100, 0, 2},
|
||||
{33868000, 44100, 0, 3},
|
||||
{45158400, 44100, 0, 4},
|
||||
|
||||
/*48k*/
|
||||
{12288000, 48000, 0, 0},
|
||||
{18432000, 48000, 0, 1},
|
||||
{24576000, 48000, 0, 2},
|
||||
{36864000, 48000, 0, 3},
|
||||
{49152000, 48000, 0, 4},
|
||||
/*48k*/
|
||||
{12288000, 48000, 0, 0},
|
||||
{18432000, 48000, 0, 1},
|
||||
{24576000, 48000, 0, 2},
|
||||
{36864000, 48000, 0, 3},
|
||||
{49152000, 48000, 0, 4},
|
||||
|
||||
/*64k*/
|
||||
{8192000, 64000, 1, 0},
|
||||
{12288000, 64000, 1, 1},
|
||||
{16934400, 64000, 1, 2},
|
||||
{24576000, 64000, 1, 3},
|
||||
{32768000, 64000, 1, 4},
|
||||
/*64k*/
|
||||
{8192000, 64000, 1, 0},
|
||||
{12288000, 64000, 1, 1},
|
||||
{16934400, 64000, 1, 2},
|
||||
{24576000, 64000, 1, 3},
|
||||
{32768000, 64000, 1, 4},
|
||||
|
||||
/* 88.2k */
|
||||
{11289600, 88200, 1, 0},
|
||||
{16934400, 88200, 1, 1},
|
||||
{22579200, 88200, 1, 2},
|
||||
{33868000, 88200, 1, 3},
|
||||
{45158400, 88200, 1, 4},
|
||||
/* 88.2k */
|
||||
{11289600, 88200, 1, 0},
|
||||
{16934400, 88200, 1, 1},
|
||||
{22579200, 88200, 1, 2},
|
||||
{33868000, 88200, 1, 3},
|
||||
{45158400, 88200, 1, 4},
|
||||
|
||||
/* 96k */
|
||||
{12288000, 96000, 1, 0},
|
||||
{18432000, 96000, 1, 1},
|
||||
{24576000, 96000, 1, 2},
|
||||
{36864000, 96000, 1, 3},
|
||||
{49152000, 96000, 1, 4},
|
||||
/* 96k */
|
||||
{12288000, 96000, 1, 0},
|
||||
{18432000, 96000, 1, 1},
|
||||
{24576000, 96000, 1, 2},
|
||||
{36864000, 96000, 1, 3},
|
||||
{49152000, 96000, 1, 4},
|
||||
|
||||
/* 128k */
|
||||
{8192000, 128000, 2, 0},
|
||||
{12288000, 128000, 2, 1},
|
||||
{16934400, 128000, 2, 2},
|
||||
{24576000, 128000, 2, 3},
|
||||
{32768000, 128000, 2, 4},
|
||||
/* 128k */
|
||||
{8192000, 128000, 2, 0},
|
||||
{12288000, 128000, 2, 1},
|
||||
{16934400, 128000, 2, 2},
|
||||
{24576000, 128000, 2, 3},
|
||||
{32768000, 128000, 2, 4},
|
||||
|
||||
/* 176.4k */
|
||||
{11289600, 176400, 2, 0},
|
||||
{16934400, 176400, 2, 1},
|
||||
{22579200, 176400, 2, 2},
|
||||
{33868000, 176400, 2, 3},
|
||||
{49152000, 176400, 2, 4},
|
||||
/* 176.4k */
|
||||
{11289600, 176400, 2, 0},
|
||||
{16934400, 176400, 2, 1},
|
||||
{22579200, 176400, 2, 2},
|
||||
{33868000, 176400, 2, 3},
|
||||
{49152000, 176400, 2, 4},
|
||||
|
||||
/* 192k */
|
||||
{12288000, 192000, 2, 0},
|
||||
{18432000, 192000, 2, 1},
|
||||
{24576000, 192000, 2, 2},
|
||||
{36864000, 192000, 2, 3},
|
||||
{49152000, 192000, 2, 4},
|
||||
/* 192k */
|
||||
{12288000, 192000, 2, 0},
|
||||
{18432000, 192000, 2, 1},
|
||||
{24576000, 192000, 2, 2},
|
||||
{36864000, 192000, 2, 3},
|
||||
{49152000, 192000, 2, 4},
|
||||
};
|
||||
static const struct cs4265_cmd_s cs4265_init_sequence[] = {
|
||||
{CS4265_PWRCTL, CS4265_PWRCTL_PDN_ADC | CS4265_PWRCTL_FREEZE | CS4265_PWRCTL_PDN_DAC | CS4265_PWRCTL_PDN_MIC},
|
||||
{CS4265_DAC_CTL, CS4265_DAC_CTL_DIF0 | CS4265_DAC_CTL_MUTE},
|
||||
{CS4265_SIG_SEL, CS4265_SIG_SEL_SDIN1},/// SDIN1
|
||||
{CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_DIF0 },//
|
||||
{CS4265_ADC_CTL, 0x00 },// // Set the serial audio port in slave mode
|
||||
{CS4265_MCLK_FREQ, CS4265_MCLK_FREQ_1_0X },// // no divider
|
||||
{CS4265_CHB_PGA_CTL, 0x00 },// // sets the gain to 0db on channel B
|
||||
{CS4265_CHA_PGA_CTL, 0x00 },// // sets the gain to 0db on channel A
|
||||
{CS4265_ADC_CTL2, 0x19 },//
|
||||
{CS4265_DAC_CHA_VOL,CS4265_DAC_VOL_ATT_000_0 },// Full volume out
|
||||
{CS4265_DAC_CHB_VOL, CS4265_DAC_VOL_ATT_000_0 },// // Full volume out
|
||||
{CS4265_DAC_CTL2, CS4265_DAC_CTL2_SOFT_RAMP_ZERO_CROSS_EN },//
|
||||
{CS4265_SPDIF_CTL1, 0x00 },//
|
||||
{CS4265_INT_MASK, 0x00 },//
|
||||
{CS4265_STATUS_MODE_MSB, 0x00 },//
|
||||
{CS4265_STATUS_MODE_LSB, 0x00 },//
|
||||
{0xff,0xff}
|
||||
};
|
||||
|
||||
{CS4265_PWRCTL, CS4265_PWRCTL_PDN_ADC | CS4265_PWRCTL_FREEZE | CS4265_PWRCTL_PDN_DAC |
|
||||
CS4265_PWRCTL_PDN_MIC},
|
||||
{CS4265_DAC_CTL, CS4265_DAC_CTL_DIF0 | CS4265_DAC_CTL_MUTE},
|
||||
{CS4265_SIG_SEL, CS4265_SIG_SEL_SDIN1}, /// SDIN1
|
||||
{CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_DIF0}, //
|
||||
{CS4265_ADC_CTL, 0x00}, // // Set the serial audio port in slave mode
|
||||
{CS4265_MCLK_FREQ, CS4265_MCLK_FREQ_1_0X}, // // no divider
|
||||
{CS4265_CHB_PGA_CTL, 0x00}, // // sets the gain to 0db on channel B
|
||||
{CS4265_CHA_PGA_CTL, 0x00}, // // sets the gain to 0db on channel A
|
||||
{CS4265_ADC_CTL2, 0x19}, //
|
||||
{CS4265_DAC_CHA_VOL, CS4265_DAC_VOL_ATT_000_0}, // Full volume out
|
||||
{CS4265_DAC_CHB_VOL, CS4265_DAC_VOL_ATT_000_0}, // // Full volume out
|
||||
{CS4265_DAC_CTL2, CS4265_DAC_CTL2_SOFT_RAMP_ZERO_CROSS_EN}, //
|
||||
{CS4265_SPDIF_CTL1, 0x00}, //
|
||||
{CS4265_INT_MASK, 0x00}, //
|
||||
{CS4265_STATUS_MODE_MSB, 0x00}, //
|
||||
{CS4265_STATUS_MODE_LSB, 0x00}, //
|
||||
{0xff, 0xff}};
|
||||
|
||||
// matching orders
|
||||
typedef enum { cs4265_ACTIVE = 0, cs4265_STANDBY, cs4265_DOWN, cs4265_ANALOGUE_OFF, cs4265_ANALOGUE_ON, cs4265_VOLUME } dac_cmd_e;
|
||||
|
||||
|
||||
typedef enum {
|
||||
cs4265_ACTIVE = 0,
|
||||
cs4265_STANDBY,
|
||||
cs4265_DOWN,
|
||||
cs4265_ANALOGUE_OFF,
|
||||
cs4265_ANALOGUE_ON,
|
||||
cs4265_VOLUME
|
||||
} dac_cmd_e;
|
||||
|
||||
static int cs4265_addr;
|
||||
|
||||
static void dac_cmd(dac_cmd_e cmd, ...);
|
||||
static int cs4265_detect(void);
|
||||
static uint32_t calc_rnd_mclk_freq(){
|
||||
float m_scale = (cs4265.i2s_config->sample_rate > 96000 && cs4265.i2s_config->bits_per_sample > 16) ? 4 : 8;
|
||||
float num_channels = cs4265.i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 2 : 1;
|
||||
return (uint32_t) round(cs4265.i2s_config->bits_per_sample*i2s_get_clk(cs4265.i2c_port)* m_scale*num_channels/100)*100;
|
||||
static uint32_t calc_rnd_mclk_freq() {
|
||||
float m_scale =
|
||||
(cs4265.i2s_config->sample_rate > 96000 && cs4265.i2s_config->bits_per_sample > 16) ? 4 : 8;
|
||||
float num_channels = cs4265.i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 2 : 1;
|
||||
return (uint32_t)round(cs4265.i2s_config->bits_per_sample * i2s_get_clk(cs4265.port) *
|
||||
m_scale * num_channels / 100) *
|
||||
100;
|
||||
}
|
||||
static int cs4265_get_clk_index(int mclk, int rate)
|
||||
{
|
||||
for (int i = 0; i < ARRAY_SIZE(clk_map_table); i++) {
|
||||
if (clk_map_table[i].rate == rate &&
|
||||
clk_map_table[i].mclk == mclk)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
static int cs4265_get_clk_index(int mclk, int rate) {
|
||||
for (int i = 0; i < ARRAY_SIZE(clk_map_table); i++) {
|
||||
if (clk_map_table[i].rate == rate && clk_map_table[i].mclk == mclk) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static esp_err_t set_clock(){
|
||||
static esp_err_t set_clock() {
|
||||
esp_err_t err = ESP_OK;
|
||||
uint32_t mclk = calc_rnd_mclk_freq();
|
||||
int index = cs4265_get_clk_index(mclk,cs4265.i2s_config->sample_rate );
|
||||
if (index >= 0) {
|
||||
ESP_LOGD(TAG, "Setting clock for mclk %u, rate %u (fm mode:%u, clk div:%u))", mclk,cs4265.i2s_config->sample_rate,clk_map_table[index].fm_mode,clk_map_table[index].mclkdiv);
|
||||
err=cs4265_update_bit(CS4265_ADC_CTL,CS4265_ADC_FM, clk_map_table[index].fm_mode << 6);
|
||||
err|=cs4265_update_bit( CS4265_MCLK_FREQ,CS4265_MCLK_FREQ_MASK,clk_map_table[index].mclkdiv << 4);
|
||||
} else {
|
||||
ESP_LOGE(TAG,"can't get correct mclk for ");
|
||||
return -1;
|
||||
}
|
||||
uint32_t mclk = calc_rnd_mclk_freq();
|
||||
int index = cs4265_get_clk_index(mclk, cs4265.i2s_config->sample_rate);
|
||||
if (index >= 0) {
|
||||
ESP_LOGD(TAG, "Setting clock for mclk %u, rate %u (fm mode:%u, clk div:%u))", mclk,
|
||||
cs4265.i2s_config->sample_rate, clk_map_table[index].fm_mode,
|
||||
clk_map_table[index].mclkdiv);
|
||||
err = cs4265_update_bit(CS4265_ADC_CTL, CS4265_ADC_FM, clk_map_table[index].fm_mode << 6);
|
||||
err |= cs4265_update_bit(
|
||||
CS4265_MCLK_FREQ, CS4265_MCLK_FREQ_MASK, clk_map_table[index].mclkdiv << 4);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "can't get correct mclk for ");
|
||||
return -1;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static void get_status(){
|
||||
uint8_t sts1= adac_read_byte(cs4265_addr, CS4265_INT_STATUS);
|
||||
ESP_LOGD(TAG,"Status: %s",sts1&CS4265_INT_STATUS_CLKERR?"CLK Error":"CLK OK");
|
||||
static void get_status() {
|
||||
uint8_t sts1 = adac_read_byte(cs4265_addr, CS4265_INT_STATUS);
|
||||
ESP_LOGD(TAG, "Status: %s", sts1 & CS4265_INT_STATUS_CLKERR ? "CLK Error" : "CLK OK");
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) {
|
||||
// find which TAS we are using (if any)
|
||||
cs4265_addr = adac_init(config, i2c_port);
|
||||
cs4265.i2s_config = i2s_config;
|
||||
cs4265.i2c_port=i2c_port;
|
||||
if (!cs4265_addr) cs4265_addr = cs4265_detect();
|
||||
if (!cs4265_addr) {
|
||||
ESP_LOGE(TAG, "No cs4265 detected");
|
||||
adac_deinit();
|
||||
return false;
|
||||
}
|
||||
#if BYTES_PER_FRAME == 8
|
||||
ESP_LOGE(TAG,"The CS4265 does not support 32 bits mode. ");
|
||||
adac_deinit();
|
||||
return false;
|
||||
#endif
|
||||
// configure MLK
|
||||
ESP_LOGD(TAG, "Configuring MCLK on GPIO0");
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
|
||||
REG_WRITE(PIN_CTRL, 0xFFFFFFF0);
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
for (int i = 0; cs4265_init_sequence[i].reg != 0xff; i++) {
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, (cs4265_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, cs4265_init_sequence[i].reg, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, cs4265_init_sequence[i].value, I2C_MASTER_NACK);
|
||||
ESP_LOGD(TAG, "i2c write %x at %u", cs4265_init_sequence[i].reg, cs4265_init_sequence[i].value);
|
||||
}
|
||||
static bool init(sys_dac_config * config, i2s_config_t* i2s_config, bool* mck) {
|
||||
|
||||
i2c_master_stop(i2c_cmd);
|
||||
esp_err_t res = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_RATE_MS);
|
||||
// find which CS4265 we are using (if any)
|
||||
cs4265_addr = adac_init(config);
|
||||
cs4265.i2s_config = i2s_config;
|
||||
if (!cs4265_addr) cs4265_addr = cs4265_detect();
|
||||
if (!cs4265_addr) {
|
||||
ESP_LOGE(TAG, "No cs4265 detected");
|
||||
adac_deinit();
|
||||
return false;
|
||||
}
|
||||
cs4265.port = config->i2c.port-sys_i2c_port_PORT0;
|
||||
#if BYTES_PER_FRAME == 8
|
||||
ESP_LOGE(TAG, "The CS4265 does not support 32 bits mode. ");
|
||||
adac_deinit();
|
||||
return false;
|
||||
#endif
|
||||
// we need mclk for this DAC
|
||||
*mck = true;
|
||||
|
||||
// Initialize the chip
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
for (int i = 0; cs4265_init_sequence[i].reg != 0xff; i++) {
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, (cs4265_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, cs4265_init_sequence[i].reg, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, cs4265_init_sequence[i].value, I2C_MASTER_NACK);
|
||||
ESP_LOGD(
|
||||
TAG, "i2c write %x at %u", cs4265_init_sequence[i].reg, cs4265_init_sequence[i].value);
|
||||
}
|
||||
|
||||
i2c_master_stop(i2c_cmd);
|
||||
esp_err_t res = i2c_master_cmd_begin(cs4265.port, i2c_cmd, 500 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "could not intialize cs4265 %d", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "could not intialize cs4265 %d", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static esp_err_t cs4265_update_bit(uint8_t reg_no,uint8_t mask,uint8_t val ){
|
||||
esp_err_t ret=ESP_OK;
|
||||
uint8_t old= adac_read_byte(cs4265_addr, reg_no);
|
||||
static esp_err_t cs4265_update_bit(uint8_t reg_no, uint8_t mask, uint8_t val) {
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint8_t old = adac_read_byte(cs4265_addr, reg_no);
|
||||
uint8_t newval = (old & ~mask) | (val & mask);
|
||||
bool change = old != newval;
|
||||
if (change){
|
||||
ret = adac_write_byte(cs4265_addr, reg_no, newval);
|
||||
if(ret != ESP_OK){
|
||||
ESP_LOGE(TAG,"Unable to change dac register 0x%02x [0x%02x->0x%02x] from value 0x%02x, mask 0x%02x ",reg_no,old,newval,val,mask);
|
||||
}
|
||||
else {
|
||||
ESP_LOGD(TAG,"Changed dac register 0x%02x [0x%02x->0x%02x] from value 0x%02x, mask 0x%02x ",reg_no,old,newval,val,mask);
|
||||
}
|
||||
}
|
||||
|
||||
bool change = old != newval;
|
||||
if (change) {
|
||||
ret = adac_write_byte(cs4265_addr, reg_no, newval);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG,
|
||||
"Unable to change dac register 0x%02x [0x%02x->0x%02x] from value 0x%02x, mask "
|
||||
"0x%02x ",
|
||||
reg_no, old, newval, val, mask);
|
||||
} else {
|
||||
ESP_LOGD(TAG,
|
||||
"Changed dac register 0x%02x [0x%02x->0x%02x] from value 0x%02x, mask 0x%02x ",
|
||||
reg_no, old, newval, val, mask);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* change volume
|
||||
*/
|
||||
static bool volume(unsigned left, unsigned right) {
|
||||
return false;
|
||||
}
|
||||
static bool volume(unsigned left, unsigned right) { return false; }
|
||||
|
||||
/****************************************************************************************
|
||||
* power
|
||||
*/
|
||||
static void power(adac_power_e mode) {
|
||||
switch(mode) {
|
||||
case ADAC_STANDBY:
|
||||
dac_cmd(cs4265_STANDBY);
|
||||
break;
|
||||
case ADAC_ON:
|
||||
dac_cmd(cs4265_ACTIVE);
|
||||
break;
|
||||
case ADAC_OFF:
|
||||
dac_cmd(cs4265_DOWN);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "unknown DAC command");
|
||||
break;
|
||||
}
|
||||
switch (mode) {
|
||||
case ADAC_STANDBY:
|
||||
dac_cmd(cs4265_STANDBY);
|
||||
break;
|
||||
case ADAC_ON:
|
||||
dac_cmd(cs4265_ACTIVE);
|
||||
break;
|
||||
case ADAC_OFF:
|
||||
dac_cmd(cs4265_DOWN);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "unknown DAC command");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* speaker
|
||||
*/
|
||||
static void speaker(bool active) {
|
||||
if (active) dac_cmd(cs4265_ANALOGUE_ON);
|
||||
else dac_cmd(cs4265_ANALOGUE_OFF);
|
||||
}
|
||||
if (active)
|
||||
dac_cmd(cs4265_ANALOGUE_ON);
|
||||
else
|
||||
dac_cmd(cs4265_ANALOGUE_OFF);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* headset
|
||||
*/
|
||||
static void headset(bool active) { }
|
||||
|
||||
static void headset(bool active) {}
|
||||
|
||||
/****************************************************************************************
|
||||
* DAC specific commands
|
||||
*/
|
||||
void dac_cmd(dac_cmd_e cmd, ...) {
|
||||
va_list args;
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
va_start(args, cmd);
|
||||
va_list args;
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
switch(cmd) {
|
||||
case cs4265_VOLUME:
|
||||
ESP_LOGE(TAG, "DAC volume not handled yet");
|
||||
break;
|
||||
va_start(args, cmd);
|
||||
|
||||
switch (cmd) {
|
||||
case cs4265_VOLUME:
|
||||
ESP_LOGE(TAG, "DAC volume not handled yet");
|
||||
break;
|
||||
case cs4265_ACTIVE:
|
||||
ESP_LOGD(TAG, "Activating DAC");
|
||||
adac_write_byte(cs4265_addr, CS4265_PWRCTL,0);
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,0);
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,0);
|
||||
cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,0);
|
||||
break;
|
||||
ESP_LOGD(TAG, "Activating DAC");
|
||||
adac_write_byte(cs4265_addr, CS4265_PWRCTL, 0);
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXOFF, 0);
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXMUTE, 0);
|
||||
cs4265_update_bit(CS4265_DAC_CTL, CS4265_DAC_CTL_MUTE, 0);
|
||||
break;
|
||||
case cs4265_STANDBY:
|
||||
ESP_LOGD(TAG, "DAC Stand-by");
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,CS4265_SPDIF_CTL2_TXOFF);
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,CS4265_SPDIF_CTL2_TXMUTE);
|
||||
cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,CS4265_DAC_CTL_MUTE);
|
||||
break;
|
||||
ESP_LOGD(TAG, "DAC Stand-by");
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXOFF, CS4265_SPDIF_CTL2_TXOFF);
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXMUTE, CS4265_SPDIF_CTL2_TXMUTE);
|
||||
cs4265_update_bit(CS4265_DAC_CTL, CS4265_DAC_CTL_MUTE, CS4265_DAC_CTL_MUTE);
|
||||
break;
|
||||
case cs4265_DOWN:
|
||||
ESP_LOGD(TAG, "DAC Power Down");
|
||||
adac_write_byte(cs4265_addr, CS4265_PWRCTL,CS4265_PWRCTL_PDN_ALL);
|
||||
break;
|
||||
ESP_LOGD(TAG, "DAC Power Down");
|
||||
adac_write_byte(cs4265_addr, CS4265_PWRCTL, CS4265_PWRCTL_PDN_ALL);
|
||||
break;
|
||||
case cs4265_ANALOGUE_OFF:
|
||||
ESP_LOGD(TAG, "DAC Analog off");
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,CS4265_SPDIF_CTL2_TXOFF);
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,CS4265_SPDIF_CTL2_TXMUTE);
|
||||
cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,CS4265_DAC_CTL_MUTE);
|
||||
break;
|
||||
ESP_LOGD(TAG, "DAC Analog off");
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXOFF, CS4265_SPDIF_CTL2_TXOFF);
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXMUTE, CS4265_SPDIF_CTL2_TXMUTE);
|
||||
cs4265_update_bit(CS4265_DAC_CTL, CS4265_DAC_CTL_MUTE, CS4265_DAC_CTL_MUTE);
|
||||
break;
|
||||
case cs4265_ANALOGUE_ON:
|
||||
ESP_LOGD(TAG, "DAC Analog on");
|
||||
adac_write_byte(cs4265_addr, CS4265_PWRCTL,0);
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,0);
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,0);
|
||||
cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "could not use cs4265 %d", ret);
|
||||
}
|
||||
get_status();
|
||||
// now set the clock
|
||||
ret=set_clock(cs4265.i2s_config,cs4265.i2c_port);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "could not set the cs4265's clock %d", ret);
|
||||
}
|
||||
ESP_LOGD(TAG, "DAC Analog on");
|
||||
adac_write_byte(cs4265_addr, CS4265_PWRCTL, 0);
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXOFF, 0);
|
||||
cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXMUTE, 0);
|
||||
cs4265_update_bit(CS4265_DAC_CTL, CS4265_DAC_CTL_MUTE, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "could not use cs4265 %d", ret);
|
||||
}
|
||||
get_status();
|
||||
// now set the clock
|
||||
ret = set_clock();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "could not set the cs4265's clock %d", ret);
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* TAS57 detection
|
||||
*/
|
||||
static int cs4265_detect(void) {
|
||||
uint8_t addr[] = {CS4265_PULL_DOWN,CS4265_PULL_UP};
|
||||
|
||||
for (int i = 0; i < sizeof(addr); i++) {
|
||||
ESP_LOGI(TAG,"Looking for CS4265 @0x%x",addr[i]);
|
||||
uint8_t reg=adac_read_byte(addr[i], CS4265_CHIP_ID);
|
||||
if(reg==255){
|
||||
continue;
|
||||
}
|
||||
// found a device at that address
|
||||
uint8_t devid = reg & CS4265_CHIP_ID_MASK;
|
||||
if (devid != CS4265_CHIP_ID_VAL) {
|
||||
ESP_LOGE(TAG,"CS4265 Device ID (%X). Expected %X",devid, CS4265_CHIP_ID);
|
||||
return 0;
|
||||
}
|
||||
ESP_LOGI(TAG,"Found DAC @0x%x, Version %x",addr[i], reg & CS4265_REV_ID_MASK);
|
||||
return addr[i];
|
||||
}
|
||||
return 0;
|
||||
uint8_t addr[] = {CS4265_PULL_DOWN, CS4265_PULL_UP};
|
||||
ESP_LOGD(TAG, "Detecting chip connection type/address");
|
||||
for (int i = 0; i < sizeof(addr); i++) {
|
||||
ESP_LOGI(TAG, "Looking for CS4265 @0x%x", addr[i]);
|
||||
uint8_t reg = adac_read_byte(addr[i], CS4265_CHIP_ID);
|
||||
if (reg == 255) {
|
||||
ESP_LOGD(TAG, "Nothing there");
|
||||
continue;
|
||||
}
|
||||
// found a device at that address
|
||||
ESP_LOGD(TAG, "Found a device. Check signature");
|
||||
uint8_t devid = reg & CS4265_CHIP_ID_MASK;
|
||||
if (devid != CS4265_CHIP_ID_VAL) {
|
||||
ESP_LOGE(TAG, "CS4265 Device ID (%X). Expected %X", devid, CS4265_CHIP_ID);
|
||||
return 0;
|
||||
}
|
||||
ESP_LOGI(TAG, "Found DAC @0x%x, Version %x", addr[i], reg & CS4265_REV_ID_MASK);
|
||||
return addr[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Squeezelite for esp32
|
||||
*
|
||||
* (c) Sebastien 2019
|
||||
@@ -11,14 +11,16 @@
|
||||
|
||||
#include <math.h>
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "Services.pb.h"
|
||||
#include "Squeezelite.pb.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/timers.h"
|
||||
extern sys_squeezelite_config* get_profile(const char* name);
|
||||
#endif
|
||||
#include "Configurator.h"
|
||||
#include "Config.h"
|
||||
#include "accessors.h"
|
||||
#include "squeezelite.h"
|
||||
|
||||
|
||||
#if CONFIG_BT_SINK
|
||||
#include "bt_app_sink.h"
|
||||
static bool enable_bt_sink;
|
||||
@@ -34,91 +36,93 @@ static bool enable_cspot;
|
||||
static bool enable_airplay;
|
||||
|
||||
#define RAOP_OUTPUT_SIZE (((RAOP_SAMPLE_RATE * BYTES_PER_FRAME * 2 * 120) / 100) & ~BYTES_PER_FRAME)
|
||||
#define SYNC_WIN_SLOW 32
|
||||
#define SYNC_WIN_CHECK 8
|
||||
#define SYNC_WIN_FAST 2
|
||||
#define SYNC_WIN_SLOW 32
|
||||
#define SYNC_WIN_CHECK 8
|
||||
#define SYNC_WIN_FAST 2
|
||||
|
||||
static raop_event_t raop_state;
|
||||
static sys_Squeezelite * squeezelite;
|
||||
static raop_event_t raop_state;
|
||||
static sys_squeezelite_config* squeezelite;
|
||||
|
||||
static EXT_RAM_ATTR struct {
|
||||
bool enabled;
|
||||
int sum, count, win, errors[SYNC_WIN_SLOW];
|
||||
s32_t len;
|
||||
u32_t start_time, playtime;
|
||||
bool enabled;
|
||||
int sum, count, win, errors[SYNC_WIN_SLOW];
|
||||
s32_t len;
|
||||
u32_t start_time, playtime;
|
||||
} raop_sync;
|
||||
#endif
|
||||
|
||||
static enum { SINK_RUNNING, SINK_ABORT, SINK_DISCARD } sink_state;
|
||||
|
||||
#define LOCK_O mutex_lock(outputbuf->mutex)
|
||||
#define LOCK_O mutex_lock(outputbuf->mutex)
|
||||
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
|
||||
#define LOCK_D mutex_lock(decode.mutex);
|
||||
#define LOCK_D mutex_lock(decode.mutex);
|
||||
#define UNLOCK_D mutex_unlock(decode.mutex);
|
||||
|
||||
enum { DECODE_BT = 1, DECODE_RAOP, DECODE_CSPOT };
|
||||
|
||||
extern struct outputstate output;
|
||||
extern struct decodestate decode;
|
||||
extern struct buffer *outputbuf;
|
||||
extern struct buffer* outputbuf;
|
||||
// this is the only system-wide loglevel variable
|
||||
extern log_level loglevel;
|
||||
|
||||
/****************************************************************************************
|
||||
* Common sink data handler
|
||||
*/
|
||||
static uint32_t sink_data_handler(const uint8_t *data, uint32_t len, int retries)
|
||||
{
|
||||
static uint32_t sink_data_handler(const uint8_t* data, uint32_t len, int retries) {
|
||||
size_t bytes, space;
|
||||
uint32_t written = 0;
|
||||
int wait = retries + 1;
|
||||
|
||||
// would be better to lock output, but really, it does not matter
|
||||
if (!output.external) {
|
||||
LOG_SDEBUG("Cannot use external sink while LMS is controlling player");
|
||||
return 0;
|
||||
}
|
||||
uint32_t written = 0;
|
||||
int wait = retries + 1;
|
||||
|
||||
LOCK_O;
|
||||
if (sink_state == SINK_ABORT) sink_state = SINK_RUNNING;
|
||||
// would be better to lock output, but really, it does not matter
|
||||
if (!output.external) {
|
||||
LOG_SDEBUG("Cannot use external sink while LMS is controlling player");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// there will always be room at some point
|
||||
while (len && wait && sink_state == SINK_RUNNING) {
|
||||
bytes = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / (BYTES_PER_FRAME / 4);
|
||||
bytes = min(len, bytes);
|
||||
LOCK_O;
|
||||
if (sink_state == SINK_ABORT) sink_state = SINK_RUNNING;
|
||||
|
||||
// there will always be room at some point
|
||||
while (len && wait && sink_state == SINK_RUNNING) {
|
||||
bytes = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / (BYTES_PER_FRAME / 4);
|
||||
bytes = min(len, bytes);
|
||||
#if BYTES_PER_FRAME == 4
|
||||
memcpy(outputbuf->writep, data, bytes);
|
||||
memcpy(outputbuf->writep, data, bytes);
|
||||
#else
|
||||
{
|
||||
s16_t *iptr = (s16_t*) data;
|
||||
ISAMPLE_T *optr = (ISAMPLE_T *) outputbuf->writep;
|
||||
size_t n = bytes / 2;
|
||||
while (n--) *optr++ = *iptr++ << 16;
|
||||
}
|
||||
#endif
|
||||
_buf_inc_writep(outputbuf, bytes * BYTES_PER_FRAME / 4);
|
||||
space = _buf_space(outputbuf);
|
||||
{
|
||||
s16_t* iptr = (s16_t*)data;
|
||||
ISAMPLE_T* optr = (ISAMPLE_T*)outputbuf->writep;
|
||||
size_t n = bytes / 2;
|
||||
while (n--)
|
||||
*optr++ = *iptr++ << 16;
|
||||
}
|
||||
#endif
|
||||
_buf_inc_writep(outputbuf, bytes * BYTES_PER_FRAME / 4);
|
||||
space = _buf_space(outputbuf);
|
||||
|
||||
len -= bytes;
|
||||
data += bytes;
|
||||
len -= bytes;
|
||||
data += bytes;
|
||||
written += bytes;
|
||||
|
||||
// allow i2s to empty the buffer if needed
|
||||
if (len && !space) {
|
||||
if (!retries) break;
|
||||
wait--;
|
||||
UNLOCK_O; usleep(50000); LOCK_O;
|
||||
}
|
||||
}
|
||||
|
||||
if (!wait) {
|
||||
// allow i2s to empty the buffer if needed
|
||||
if (len && !space) {
|
||||
if (!retries) break;
|
||||
wait--;
|
||||
UNLOCK_O;
|
||||
usleep(50000);
|
||||
LOCK_O;
|
||||
}
|
||||
}
|
||||
|
||||
if (!wait) {
|
||||
// re-align the buffer according to what we threw away
|
||||
_buf_inc_writep(outputbuf, outputbuf->size - (BYTES_PER_FRAME - (len % BYTES_PER_FRAME)));
|
||||
LOG_WARN("Waited too long, dropping frames %d", len);
|
||||
}
|
||||
|
||||
LOG_WARN("Waited too long, dropping frames %d", len);
|
||||
}
|
||||
|
||||
UNLOCK_O;
|
||||
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
@@ -126,77 +130,77 @@ static uint32_t sink_data_handler(const uint8_t *data, uint32_t len, int retries
|
||||
* BT sink data handler
|
||||
*/
|
||||
#if CONFIG_BT_SINK
|
||||
static void bt_sink_data_handler(const uint8_t *data, uint32_t len) {
|
||||
static void bt_sink_data_handler(const uint8_t* data, uint32_t len) {
|
||||
sink_data_handler(data, len, 10);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* BT sink command handler
|
||||
*/
|
||||
static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
|
||||
{
|
||||
// don't LOCK_O as there is always a chance that LMS takes control later anyway
|
||||
if (output.external != DECODE_BT && output.state > OUTPUT_STOPPED) {
|
||||
LOG_WARN("Cannot use BT sink while LMS/AirPlay/CSpot are controlling player %d", output.external);
|
||||
return false;
|
||||
}
|
||||
static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args) {
|
||||
// don't LOCK_O as there is always a chance that LMS takes control later anyway
|
||||
if (output.external != DECODE_BT && output.state > OUTPUT_STOPPED) {
|
||||
LOG_WARN("Cannot use BT sink while LMS/AirPlay/CSpot are controlling player %d",
|
||||
output.external);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOCK_D;
|
||||
LOCK_D;
|
||||
|
||||
if (cmd != BT_SINK_VOLUME) LOCK_O;
|
||||
|
||||
switch(cmd) {
|
||||
case BT_SINK_AUDIO_STARTED:
|
||||
_buf_flush(outputbuf);
|
||||
_buf_limit(outputbuf, 0);
|
||||
output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
|
||||
output.external = DECODE_BT;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
output.frames_played = 0;
|
||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||
LOG_INFO("BT sink started");
|
||||
break;
|
||||
case BT_SINK_AUDIO_STOPPED:
|
||||
if (output.external == DECODE_BT) {
|
||||
if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
|
||||
output.external = 0;
|
||||
output.stop_time = gettime_ms();
|
||||
LOG_INFO("BT sink stopped");
|
||||
}
|
||||
break;
|
||||
case BT_SINK_PLAY:
|
||||
output.state = OUTPUT_RUNNING;
|
||||
LOG_INFO("BT play");
|
||||
break;
|
||||
case BT_SINK_STOP:
|
||||
_buf_flush(outputbuf);
|
||||
output.state = OUTPUT_STOPPED;
|
||||
output.stop_time = gettime_ms();
|
||||
sink_state = SINK_ABORT;
|
||||
LOG_INFO("BT stop");
|
||||
break;
|
||||
case BT_SINK_PAUSE:
|
||||
output.stop_time = gettime_ms();
|
||||
LOG_INFO("BT pause, just silence");
|
||||
break;
|
||||
case BT_SINK_RATE:
|
||||
output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
|
||||
LOG_INFO("Setting BT sample rate %u", output.next_sample_rate);
|
||||
break;
|
||||
case BT_SINK_VOLUME: {
|
||||
u32_t volume = va_arg(args, u32_t);
|
||||
volume = 65536 * powf(volume / 128.0f, 3);
|
||||
set_volume(volume, volume);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd != BT_SINK_VOLUME) UNLOCK_O;
|
||||
UNLOCK_D;
|
||||
if (cmd != BT_SINK_VOLUME) LOCK_O;
|
||||
|
||||
return true;
|
||||
switch (cmd) {
|
||||
case BT_SINK_AUDIO_STARTED:
|
||||
_buf_flush(outputbuf);
|
||||
_buf_limit(outputbuf, 0);
|
||||
output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
|
||||
output.external = DECODE_BT;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
output.frames_played = 0;
|
||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||
LOG_INFO("BT sink started");
|
||||
break;
|
||||
case BT_SINK_AUDIO_STOPPED:
|
||||
if (output.external == DECODE_BT) {
|
||||
if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
|
||||
output.external = 0;
|
||||
output.stop_time = gettime_ms();
|
||||
LOG_INFO("BT sink stopped");
|
||||
}
|
||||
break;
|
||||
case BT_SINK_PLAY:
|
||||
output.state = OUTPUT_RUNNING;
|
||||
LOG_INFO("BT play");
|
||||
break;
|
||||
case BT_SINK_STOP:
|
||||
_buf_flush(outputbuf);
|
||||
output.state = OUTPUT_STOPPED;
|
||||
output.stop_time = gettime_ms();
|
||||
sink_state = SINK_ABORT;
|
||||
LOG_INFO("BT stop");
|
||||
break;
|
||||
case BT_SINK_PAUSE:
|
||||
output.stop_time = gettime_ms();
|
||||
LOG_INFO("BT pause, just silence");
|
||||
break;
|
||||
case BT_SINK_RATE:
|
||||
output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
|
||||
LOG_INFO("Setting BT sample rate %u", output.next_sample_rate);
|
||||
break;
|
||||
case BT_SINK_VOLUME: {
|
||||
u32_t volume = va_arg(args, u32_t);
|
||||
volume = 65536 * powf(volume / 128.0f, 3);
|
||||
set_volume(volume, volume);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd != BT_SINK_VOLUME) UNLOCK_O;
|
||||
UNLOCK_D;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -204,144 +208,154 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
|
||||
* raop sink data handler
|
||||
*/
|
||||
#if CONFIG_AIRPLAY_SINK
|
||||
static void raop_sink_data_handler(const uint8_t *data, uint32_t len, u32_t playtime) {
|
||||
|
||||
raop_sync.playtime = playtime;
|
||||
raop_sync.len = len;
|
||||
static void raop_sink_data_handler(const uint8_t* data, uint32_t len, u32_t playtime) {
|
||||
|
||||
sink_data_handler(data, len, 10);
|
||||
}
|
||||
raop_sync.playtime = playtime;
|
||||
raop_sync.len = len;
|
||||
|
||||
sink_data_handler(data, len, 10);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* AirPlay sink command handler
|
||||
*/
|
||||
static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
{
|
||||
// don't LOCK_O as there is always a chance that LMS takes control later anyway
|
||||
if (output.external != DECODE_RAOP && output.state > OUTPUT_STOPPED) {
|
||||
LOG_WARN("Cannot use Airplay sink while LMS/BT/CSpot are controlling player %d", output.external);
|
||||
return false;
|
||||
}
|
||||
static bool raop_sink_cmd_handler(raop_event_t event, va_list args) {
|
||||
// don't LOCK_O as there is always a chance that LMS takes control later anyway
|
||||
if (output.external != DECODE_RAOP && output.state > OUTPUT_STOPPED) {
|
||||
LOG_WARN("Cannot use Airplay sink while LMS/BT/CSpot are controlling player %d",
|
||||
output.external);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOCK_D;
|
||||
|
||||
if (event != RAOP_VOLUME) LOCK_O;
|
||||
|
||||
// this is async, so player might have been deleted
|
||||
switch (event) {
|
||||
case RAOP_TIMING: {
|
||||
if (!raop_sync.enabled || output.state != OUTPUT_RUNNING || output.frames_played_dmp < output.device_frames) break;
|
||||
LOCK_D;
|
||||
|
||||
u32_t ms, now = gettime_ms();
|
||||
u32_t level = _buf_used(outputbuf);
|
||||
int error;
|
||||
|
||||
// in how many ms will the most recent block play
|
||||
ms = (((s32_t)(level - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 10) / (RAOP_SAMPLE_RATE / 100) - (s32_t) (now - output.updated);
|
||||
|
||||
// when outputbuf is empty, it means we have a network black-out or something
|
||||
error = level ? (raop_sync.playtime - now) - ms : 0;
|
||||
|
||||
if (loglevel == lDEBUG || !level) {
|
||||
LOG_INFO("head local:%d, remote:%d (delta:%d)", ms, raop_sync.playtime - now, error);
|
||||
LOG_INFO("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process);
|
||||
}
|
||||
|
||||
// calculate sum, error and update sliding window
|
||||
raop_sync.errors[raop_sync.count++ % raop_sync.win] = error;
|
||||
raop_sync.sum += error;
|
||||
error = raop_sync.sum / min(raop_sync.count, raop_sync.win);
|
||||
if (event != RAOP_VOLUME) LOCK_O;
|
||||
|
||||
// wait till we have enough data or there is a strong deviation
|
||||
if ((raop_sync.count >= raop_sync.win && abs(error) > 10) || (raop_sync.count >= SYNC_WIN_CHECK && abs(error) > 100)) {
|
||||
if (error < 0) {
|
||||
output.skip_frames = -(error * RAOP_SAMPLE_RATE) / 1000;
|
||||
output.state = OUTPUT_SKIP_FRAMES;
|
||||
LOG_INFO("skipping %u frames (count:%d)", output.skip_frames, raop_sync.count);
|
||||
} else {
|
||||
output.pause_frames = (error * RAOP_SAMPLE_RATE) / 1000;
|
||||
output.state = OUTPUT_PAUSE_FRAMES;
|
||||
LOG_INFO("pausing for %u frames (count: %d)", output.pause_frames, raop_sync.count);
|
||||
}
|
||||
|
||||
raop_sync.sum = raop_sync.count = 0;
|
||||
memset(raop_sync.errors, 0, sizeof(raop_sync.errors));
|
||||
}
|
||||
|
||||
// move to normal mode if possible
|
||||
if (raop_sync.win == 1) {
|
||||
raop_sync.win = SYNC_WIN_FAST;
|
||||
LOG_INFO("backend played %u, desired %u, (delta:%d)", ms, raop_sync.playtime - now, error);
|
||||
} else if (raop_sync.win == SYNC_WIN_FAST && raop_sync.count >= SYNC_WIN_FAST && abs(error) < 10) {
|
||||
raop_sync.win = SYNC_WIN_SLOW;
|
||||
LOG_INFO("switching to slow sync mode %u", raop_sync.win);
|
||||
}
|
||||
// this is async, so player might have been deleted
|
||||
switch (event) {
|
||||
case RAOP_TIMING: {
|
||||
if (!raop_sync.enabled || output.state != OUTPUT_RUNNING ||
|
||||
output.frames_played_dmp < output.device_frames)
|
||||
break;
|
||||
|
||||
break;
|
||||
}
|
||||
case RAOP_SETUP: {
|
||||
uint8_t **buffer = va_arg(args, uint8_t**);
|
||||
size_t *size = va_arg(args, size_t*);
|
||||
u32_t ms, now = gettime_ms();
|
||||
u32_t level = _buf_used(outputbuf);
|
||||
int error;
|
||||
|
||||
// steal buffer tail from outputbuf but do not reallocate
|
||||
*size = _buf_limit(outputbuf, RAOP_OUTPUT_SIZE);
|
||||
*buffer = outputbuf->writep + RAOP_OUTPUT_SIZE;
|
||||
// in how many ms will the most recent block play
|
||||
ms = (((s32_t)(level - raop_sync.len) / BYTES_PER_FRAME + output.device_frames +
|
||||
output.frames_in_process) *
|
||||
10) /
|
||||
(RAOP_SAMPLE_RATE / 100) -
|
||||
(s32_t)(now - output.updated);
|
||||
|
||||
output.frames_played = 0;
|
||||
output.external = DECODE_RAOP;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||
LOG_INFO("resizing buffer %u", outputbuf->size);
|
||||
break;
|
||||
}
|
||||
case RAOP_STREAM:
|
||||
LOG_INFO("Stream", NULL);
|
||||
raop_state = event;
|
||||
raop_sync.win = 1;
|
||||
raop_sync.sum = raop_sync.count = 0;
|
||||
memset(raop_sync.errors, 0, sizeof(raop_sync.errors));
|
||||
raop_sync.enabled = !strcasestr(output.device, "BT");
|
||||
output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE;
|
||||
break;
|
||||
case RAOP_STALLED:
|
||||
case RAOP_STOP:
|
||||
output.external = 0;
|
||||
__attribute__ ((fallthrough));
|
||||
case RAOP_FLUSH:
|
||||
LOG_INFO("%s", event == RAOP_FLUSH ? "Flush" : "Stop");
|
||||
_buf_flush(outputbuf);
|
||||
raop_state = event;
|
||||
if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
|
||||
sink_state = SINK_ABORT;
|
||||
output.frames_played = 0;
|
||||
output.stop_time = gettime_ms();
|
||||
break;
|
||||
case RAOP_PLAY: {
|
||||
LOG_INFO("Play", NULL);
|
||||
if (raop_state != RAOP_PLAY) {
|
||||
output.state = OUTPUT_START_AT;
|
||||
output.start_at = va_arg(args, u32_t);
|
||||
raop_sync.start_time = output.start_at;
|
||||
LOG_INFO("Starting at %u (in %d ms)", output.start_at, output.start_at - gettime_ms());
|
||||
}
|
||||
raop_state = event;
|
||||
break;
|
||||
}
|
||||
case RAOP_VOLUME: {
|
||||
float volume = va_arg(args, double);
|
||||
LOG_INFO("Volume[0..1] %0.4f", volume);
|
||||
volume = 65536 * powf(volume, 3);
|
||||
set_volume(volume, volume);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (event != RAOP_VOLUME) UNLOCK_O;
|
||||
|
||||
UNLOCK_D;
|
||||
return true;
|
||||
// when outputbuf is empty, it means we have a network black-out or something
|
||||
error = level ? (raop_sync.playtime - now) - ms : 0;
|
||||
|
||||
if (loglevel == lDEBUG || !level) {
|
||||
LOG_INFO("head local:%d, remote:%d (delta:%d)", ms, raop_sync.playtime - now, error);
|
||||
LOG_INFO("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf),
|
||||
raop_sync.len, output.device_frames, output.frames_in_process);
|
||||
}
|
||||
|
||||
// calculate sum, error and update sliding window
|
||||
raop_sync.errors[raop_sync.count++ % raop_sync.win] = error;
|
||||
raop_sync.sum += error;
|
||||
error = raop_sync.sum / min(raop_sync.count, raop_sync.win);
|
||||
|
||||
// wait till we have enough data or there is a strong deviation
|
||||
if ((raop_sync.count >= raop_sync.win && abs(error) > 10) ||
|
||||
(raop_sync.count >= SYNC_WIN_CHECK && abs(error) > 100)) {
|
||||
if (error < 0) {
|
||||
output.skip_frames = -(error * RAOP_SAMPLE_RATE) / 1000;
|
||||
output.state = OUTPUT_SKIP_FRAMES;
|
||||
LOG_INFO("skipping %u frames (count:%d)", output.skip_frames, raop_sync.count);
|
||||
} else {
|
||||
output.pause_frames = (error * RAOP_SAMPLE_RATE) / 1000;
|
||||
output.state = OUTPUT_PAUSE_FRAMES;
|
||||
LOG_INFO("pausing for %u frames (count: %d)", output.pause_frames, raop_sync.count);
|
||||
}
|
||||
|
||||
raop_sync.sum = raop_sync.count = 0;
|
||||
memset(raop_sync.errors, 0, sizeof(raop_sync.errors));
|
||||
}
|
||||
|
||||
// move to normal mode if possible
|
||||
if (raop_sync.win == 1) {
|
||||
raop_sync.win = SYNC_WIN_FAST;
|
||||
LOG_INFO(
|
||||
"backend played %u, desired %u, (delta:%d)", ms, raop_sync.playtime - now, error);
|
||||
} else if (raop_sync.win == SYNC_WIN_FAST && raop_sync.count >= SYNC_WIN_FAST &&
|
||||
abs(error) < 10) {
|
||||
raop_sync.win = SYNC_WIN_SLOW;
|
||||
LOG_INFO("switching to slow sync mode %u", raop_sync.win);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case RAOP_SETUP: {
|
||||
uint8_t** buffer = va_arg(args, uint8_t**);
|
||||
size_t* size = va_arg(args, size_t*);
|
||||
|
||||
// steal buffer tail from outputbuf but do not reallocate
|
||||
*size = _buf_limit(outputbuf, RAOP_OUTPUT_SIZE);
|
||||
*buffer = outputbuf->writep + RAOP_OUTPUT_SIZE;
|
||||
|
||||
output.frames_played = 0;
|
||||
output.external = DECODE_RAOP;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||
LOG_INFO("resizing buffer %u", outputbuf->size);
|
||||
break;
|
||||
}
|
||||
case RAOP_STREAM:
|
||||
LOG_INFO("Stream", NULL);
|
||||
raop_state = event;
|
||||
raop_sync.win = 1;
|
||||
raop_sync.sum = raop_sync.count = 0;
|
||||
memset(raop_sync.errors, 0, sizeof(raop_sync.errors));
|
||||
raop_sync.enabled = !strcasestr(output.device, "BT");
|
||||
output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE;
|
||||
break;
|
||||
case RAOP_STALLED:
|
||||
case RAOP_STOP:
|
||||
output.external = 0;
|
||||
__attribute__((fallthrough));
|
||||
case RAOP_FLUSH:
|
||||
LOG_INFO("%s", event == RAOP_FLUSH ? "Flush" : "Stop");
|
||||
_buf_flush(outputbuf);
|
||||
raop_state = event;
|
||||
if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
|
||||
sink_state = SINK_ABORT;
|
||||
output.frames_played = 0;
|
||||
output.stop_time = gettime_ms();
|
||||
break;
|
||||
case RAOP_PLAY: {
|
||||
LOG_INFO("Play", NULL);
|
||||
if (raop_state != RAOP_PLAY) {
|
||||
output.state = OUTPUT_START_AT;
|
||||
output.start_at = va_arg(args, u32_t);
|
||||
raop_sync.start_time = output.start_at;
|
||||
LOG_INFO("Starting at %u (in %d ms)", output.start_at, output.start_at - gettime_ms());
|
||||
}
|
||||
raop_state = event;
|
||||
break;
|
||||
}
|
||||
case RAOP_VOLUME: {
|
||||
float volume = va_arg(args, double);
|
||||
LOG_INFO("Volume[0..1] %0.4f", volume);
|
||||
volume = 65536 * powf(volume, 3);
|
||||
set_volume(volume, volume);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (event != RAOP_VOLUME) UNLOCK_O;
|
||||
|
||||
UNLOCK_D;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -349,98 +363,98 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
* cspot sink data handler
|
||||
*/
|
||||
#if CONFIG_CSPOT_SINK
|
||||
static uint32_t cspot_sink_data_handler(const uint8_t *data, uint32_t len) {
|
||||
static uint32_t cspot_sink_data_handler(const uint8_t* data, uint32_t len) {
|
||||
return sink_data_handler(data, len, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* cspot sink command handler
|
||||
*/
|
||||
|
||||
static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
|
||||
{
|
||||
// don't LOCK_O as there is always a chance that LMS takes control later anyway
|
||||
if (output.external != DECODE_CSPOT && output.state > OUTPUT_STOPPED) {
|
||||
LOG_WARN("Cannot use CSpot sink while LMS/BT/Airplay are controlling player %d", output.external);
|
||||
return false;
|
||||
}
|
||||
static bool cspot_cmd_handler(cspot_event_t cmd, va_list args) {
|
||||
// don't LOCK_O as there is always a chance that LMS takes control later anyway
|
||||
if (output.external != DECODE_CSPOT && output.state > OUTPUT_STOPPED) {
|
||||
LOG_WARN("Cannot use CSpot sink while LMS/BT/Airplay are controlling player %d",
|
||||
output.external);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOCK_D;
|
||||
|
||||
if (cmd != CSPOT_VOLUME) LOCK_O;
|
||||
LOCK_D;
|
||||
|
||||
switch(cmd) {
|
||||
case CSPOT_START:
|
||||
output.current_sample_rate = output.next_sample_rate = va_arg(args, u32_t);
|
||||
output.external = DECODE_CSPOT;
|
||||
output.frames_played = 0;
|
||||
if (cmd != CSPOT_VOLUME) LOCK_O;
|
||||
|
||||
switch (cmd) {
|
||||
case CSPOT_START:
|
||||
output.current_sample_rate = output.next_sample_rate = va_arg(args, u32_t);
|
||||
output.external = DECODE_CSPOT;
|
||||
output.frames_played = 0;
|
||||
// in 1/10 of seconds
|
||||
output.threshold = 25;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
sink_state = SINK_ABORT;
|
||||
_buf_flush(outputbuf);
|
||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||
LOG_INFO("CSpot start track");
|
||||
break;
|
||||
case CSPOT_DISC:
|
||||
_buf_flush(outputbuf);
|
||||
sink_state = SINK_ABORT;
|
||||
output.external = 0;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
output.stop_time = gettime_ms();
|
||||
LOG_INFO("CSpot disconnected");
|
||||
break;
|
||||
case CSPOT_PLAY:
|
||||
sink_state = SINK_RUNNING;
|
||||
output.state = OUTPUT_RUNNING;
|
||||
LOG_INFO("CSpot play");
|
||||
break;
|
||||
case CSPOT_SEEK:
|
||||
_buf_flush(outputbuf);
|
||||
sink_state = SINK_ABORT;
|
||||
LOG_INFO("CSpot seek by %d", va_arg(args, uint32_t));
|
||||
break;
|
||||
case CSPOT_FLUSH:
|
||||
_buf_flush(outputbuf);
|
||||
sink_state = SINK_DISCARD;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
LOG_INFO("CSpot flush");
|
||||
break;
|
||||
case CSPOT_PAUSE:
|
||||
output.state = OUTPUT_STOPPED;
|
||||
output.stop_time = gettime_ms();
|
||||
LOG_INFO("CSpot pause");
|
||||
break;
|
||||
_buf_flush(outputbuf);
|
||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||
LOG_INFO("CSpot start track");
|
||||
break;
|
||||
case CSPOT_DISC:
|
||||
_buf_flush(outputbuf);
|
||||
sink_state = SINK_ABORT;
|
||||
output.external = 0;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
output.stop_time = gettime_ms();
|
||||
LOG_INFO("CSpot disconnected");
|
||||
break;
|
||||
case CSPOT_PLAY:
|
||||
sink_state = SINK_RUNNING;
|
||||
output.state = OUTPUT_RUNNING;
|
||||
LOG_INFO("CSpot play");
|
||||
break;
|
||||
case CSPOT_SEEK:
|
||||
_buf_flush(outputbuf);
|
||||
sink_state = SINK_ABORT;
|
||||
LOG_INFO("CSpot seek by %d", va_arg(args, uint32_t));
|
||||
break;
|
||||
case CSPOT_FLUSH:
|
||||
_buf_flush(outputbuf);
|
||||
sink_state = SINK_DISCARD;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
LOG_INFO("CSpot flush");
|
||||
break;
|
||||
case CSPOT_PAUSE:
|
||||
output.state = OUTPUT_STOPPED;
|
||||
output.stop_time = gettime_ms();
|
||||
LOG_INFO("CSpot pause");
|
||||
break;
|
||||
case CSPOT_TRACK_MARK:
|
||||
output.track_start = outputbuf->writep;
|
||||
break;
|
||||
case CSPOT_QUERY_REMAINING: {
|
||||
uint32_t *remaining = va_arg(args, uint32_t*);
|
||||
uint32_t* remaining = va_arg(args, uint32_t*);
|
||||
*remaining = (_buf_used(outputbuf) * 1000) / (output.current_sample_rate * BYTES_PER_FRAME);
|
||||
break;
|
||||
break;
|
||||
}
|
||||
case CSPOT_QUERY_STARTED: {
|
||||
uint32_t *started = va_arg(args, uint32_t*);
|
||||
uint32_t* started = va_arg(args, uint32_t*);
|
||||
*started = output.track_started;
|
||||
// this is a read_and_clear event
|
||||
output.track_started = false;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
case CSPOT_VOLUME: {
|
||||
u32_t volume = va_arg(args, u32_t);
|
||||
LOG_INFO("CSpot volume %u", volume);
|
||||
volume = 65536 * powf(volume / 65536.0f, 4);
|
||||
set_volume(volume, volume);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd != CSPOT_VOLUME) UNLOCK_O;
|
||||
UNLOCK_D;
|
||||
|
||||
return true;
|
||||
case CSPOT_VOLUME: {
|
||||
u32_t volume = va_arg(args, u32_t);
|
||||
LOG_INFO("CSpot volume %u", volume);
|
||||
volume = 65536 * powf(volume / 65536.0f, 4);
|
||||
set_volume(volume, volume);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd != CSPOT_VOLUME) UNLOCK_O;
|
||||
UNLOCK_D;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -448,80 +462,79 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
|
||||
* We provide the generic codec register option
|
||||
*/
|
||||
void register_external(void) {
|
||||
sys_BluetoothSink * bt_sink;
|
||||
sys_AirPlay * airplay;
|
||||
sys_Spotify * spotify;
|
||||
squeezelite = get_profile(NULL); // get the active profile
|
||||
#if CONFIG_BT_SINK
|
||||
enable_bt_sink= (SYS_SERVICES_BTSINK(bt_sink) && bt_sink->enabled);
|
||||
if ( enable_bt_sink) {
|
||||
#pragma message ("Is the BT sink logic correct?")
|
||||
if(SYS_SERVICES_SQUEEZELITE(squeezelite) && squeezelite->output_type == sys_OutputTypeEnum_OUTPUT_Bluetooth ){
|
||||
LOG_ERROR("BT Sink cannot be enabled with Bluetooth output");
|
||||
}
|
||||
else {
|
||||
bt_sink_init(bt_sink_cmd_handler, bt_sink_data_handler);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
sys_services_bt_sink* bt_sink;
|
||||
enable_bt_sink = (sys_services_config_BTSINK(bt_sink) && bt_sink->enabled);
|
||||
if (enable_bt_sink && squeezelite) {
|
||||
#pragma message("Is the BT sink logic correct?")
|
||||
if (squeezelite->output_type == sys_squeezelite_outputs_BT) {
|
||||
LOG_ERROR("BT Sink cannot be enabled with Bluetooth output");
|
||||
} else {
|
||||
bt_sink_init(bt_sink_cmd_handler, bt_sink_data_handler);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_AIRPLAY_SINK
|
||||
enable_airplay = SYS_SERVICES_AIRPLAY(airplay) && airplay->enabled;
|
||||
if (enable_airplay){
|
||||
raop_sink_init(raop_sink_cmd_handler, raop_sink_data_handler);
|
||||
LOG_INFO("Initializing AirPlay sink");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if CONFIG_CSPOT_SINK
|
||||
enable_cspot = SYS_SERVICES_SPOTIFY(spotify) && spotify->enabled;
|
||||
if (enable_cspot){
|
||||
cspot_sink_init(cspot_cmd_handler, cspot_sink_data_handler);
|
||||
LOG_INFO("Initializing CSpot sink");
|
||||
}
|
||||
#endif
|
||||
sys_airplay_config* airplay;
|
||||
enable_airplay = sys_services_config_AIRPLAY(airplay) && airplay->enabled;
|
||||
if (enable_airplay) {
|
||||
raop_sink_init(raop_sink_cmd_handler, raop_sink_data_handler);
|
||||
LOG_INFO("Initializing AirPlay sink");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if CONFIG_CSPOT_SINK
|
||||
sys_spotify_config* spotify;
|
||||
enable_cspot = sys_services_config_SPOTIFY(spotify) && spotify->enabled;
|
||||
if (enable_cspot) {
|
||||
cspot_sink_init(cspot_cmd_handler, cspot_sink_data_handler);
|
||||
LOG_INFO("Initializing CSpot sink");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void deregister_external(void) {
|
||||
#if CONFIG_BT_SINK
|
||||
sys_Squeezelite * squeezelite;
|
||||
if(SYS_SERVICES_SQUEEZELITE(squeezelite) && squeezelite->output_type != sys_OutputTypeEnum_OUTPUT_Bluetooth && enable_bt_sink ){
|
||||
bt_sink_deinit();
|
||||
}
|
||||
squeezelite = get_profile(NULL); // get the active profile
|
||||
if (squeezelite && squeezelite->output_type == sys_squeezelite_outputs_BT && enable_bt_sink) {
|
||||
bt_sink_deinit();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_AIRPLAY_SINK
|
||||
if (enable_airplay){
|
||||
LOG_INFO("Stopping AirPlay sink");
|
||||
raop_sink_deinit();
|
||||
}
|
||||
if (enable_airplay) {
|
||||
LOG_INFO("Stopping AirPlay sink");
|
||||
raop_sink_deinit();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_CSPOT_SINK
|
||||
if (enable_cspot){
|
||||
LOG_INFO("Stopping CSpot sink");
|
||||
cspot_sink_deinit();
|
||||
}
|
||||
if (enable_cspot) {
|
||||
LOG_INFO("Stopping CSpot sink");
|
||||
cspot_sink_deinit();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void decode_restore(int external) {
|
||||
switch (external) {
|
||||
#if CONFIG_BT_SINK
|
||||
case DECODE_BT:
|
||||
bt_disconnect();
|
||||
break;
|
||||
switch (external) {
|
||||
#if CONFIG_BT_SINK
|
||||
case DECODE_BT:
|
||||
bt_disconnect();
|
||||
break;
|
||||
#endif
|
||||
#if CONFIG_AIRPLAY_SINK
|
||||
case DECODE_RAOP:
|
||||
raop_disconnect();
|
||||
break;
|
||||
case DECODE_RAOP:
|
||||
raop_disconnect();
|
||||
break;
|
||||
#endif
|
||||
#if CONFIG_CSPOT_SINK
|
||||
case DECODE_CSPOT:
|
||||
cspot_disconnect();
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
case DECODE_CSPOT:
|
||||
cspot_disconnect();
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_log.h"
|
||||
#include "monitor.h"
|
||||
#include "Configurator.h"
|
||||
#include "Config.h"
|
||||
#include "messaging.h"
|
||||
#include "gpio_exp.h"
|
||||
#include "accessors.h"
|
||||
|
||||
static const char TAG[] = "embedded";
|
||||
static sys_GPIO * power=NULL;
|
||||
static sys_gpio_config * power=NULL;
|
||||
|
||||
extern void sb_controls_init(void);
|
||||
extern bool sb_displayer_init(void);
|
||||
@@ -69,7 +69,9 @@ uint32_t _gettime_ms_(void) {
|
||||
|
||||
int embedded_init(void) {
|
||||
mutex_create(slimp_mutex);
|
||||
ESP_LOGI(TAG,"Initializing controls");
|
||||
sb_controls_init();
|
||||
ESP_LOGI(TAG,"Initializing displayer");
|
||||
custom_player_id = sb_displayer_init() ? 100 : 101;
|
||||
|
||||
|
||||
@@ -88,10 +90,11 @@ void embedded_exit(int code) {
|
||||
}
|
||||
|
||||
void powering(bool on) {
|
||||
if (power->pin != -1) {
|
||||
if (SYS_GPIOS_NAME(power,power) && power->pin != -1) {
|
||||
ESP_LOGI(TAG, "powering player %s", on ? "ON" : "OFF");
|
||||
gpio_set_level_x(power->pin, on ? power->level : !power->level);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
u16_t get_RSSI(void) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
#include "math.h"
|
||||
#include "Configurator.h"
|
||||
#include "Config.h"
|
||||
#include "squeezelite.h"
|
||||
#include "equalizer.h"
|
||||
#include "esp_equalizer.h"
|
||||
@@ -24,7 +24,7 @@ static EXT_RAM_ATTR struct {
|
||||
float volume;
|
||||
float loudness_gain[EQ_BANDS];
|
||||
bool update;
|
||||
sys_Equalizer *state;
|
||||
sys_equalizer_config *state;
|
||||
} equalizer;
|
||||
|
||||
|
||||
@@ -83,19 +83,18 @@ static void calculate_loudness(void) {
|
||||
* initialize equalizer
|
||||
*/
|
||||
void equalizer_init(void) {
|
||||
sys_Services * services;
|
||||
sys_Equalizer blank_eq = sys_Equalizer_init_default;
|
||||
|
||||
sys_services_config * services;
|
||||
sys_equalizer_config blank_eq = sys_equalizer_config_init_default;
|
||||
equalizer.state = &sys_state->equalizer;
|
||||
if(!sys_state->has_equalizer ){
|
||||
sys_state->has_equalizer = true;
|
||||
if(SYS_SERVICES(services) && services->has_equalizer){
|
||||
memcpy(equalizer.state,&services->equalizer,sizeof(sys_Equalizer));
|
||||
if(sys_services_config(services) && services->has_equalizer){
|
||||
memcpy(equalizer.state,&services->equalizer,sizeof(sys_equalizer_config));
|
||||
}
|
||||
else {
|
||||
memcpy(equalizer.state,&blank_eq,sizeof(sys_Equalizer));
|
||||
memcpy(equalizer.state,&blank_eq,sizeof(sys_equalizer_config));
|
||||
}
|
||||
configurator_raise_state_changed();
|
||||
config_raise_state_changed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +159,7 @@ void equalizer_set_gain(int8_t *gain) {
|
||||
// update only if something changed
|
||||
if (!memcmp(&equalizer.state->gains, gain, EQ_BANDS)) {
|
||||
equalizer.update = true;
|
||||
configurator_raise_state_changed();
|
||||
config_raise_state_changed();
|
||||
}
|
||||
|
||||
LOG_INFO("equalizer gain %s", config);
|
||||
@@ -178,7 +177,7 @@ void equalizer_set_loudness(uint8_t loudness) {
|
||||
// update loudness gains as a factor of loudness and volume
|
||||
if (equalizer.state->loudness != loudness / 10.0) {
|
||||
equalizer.state->loudness = loudness / 10.0;
|
||||
configurator_raise_state_changed();
|
||||
config_raise_state_changed();
|
||||
calculate_loudness();
|
||||
equalizer.update = true;
|
||||
}
|
||||
|
||||
@@ -1,34 +1,20 @@
|
||||
/*
|
||||
* Squeezelite - lightweight headless squeezebox emulator
|
||||
* Squeezelite for esp32
|
||||
*
|
||||
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
|
||||
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
|
||||
* (c) Sebastien 2024
|
||||
* Philippe G. 2024, philippe_44@outlook.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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Additions (c) Paul Hermann, 2015-2017 under the same license terms
|
||||
* -Control of Raspberry pi GPIO for amplifier power
|
||||
* -Launch script on power status change from LMS
|
||||
*/
|
||||
|
||||
#include "Config.h"
|
||||
#include "squeezelite.h"
|
||||
#include <signal.h>
|
||||
#include "Configurator.h"
|
||||
|
||||
extern bool user_rates;
|
||||
static unsigned int rates[MAX_SUPPORTED_SAMPLERATES] = {0};
|
||||
sys_Squeezelite* config;
|
||||
sys_squeezelite_config* config;
|
||||
log_level loglevel = lDEBUG;
|
||||
static void sighandler(int signum) {
|
||||
slimproto_stop();
|
||||
@@ -38,8 +24,8 @@ static void sighandler(int signum) {
|
||||
}
|
||||
|
||||
unsigned int* get_rates() {
|
||||
unsigned int ref[] TEST_RATES;
|
||||
sys_RatesOption* ratescfg = &config->rates;
|
||||
unsigned int ref[] TEST_RATES;
|
||||
sys_squeezelite_rates_opt* ratescfg = &config->rates;
|
||||
if (!config->has_rates || ((ratescfg->list_count == 0 || ratescfg->list[0] == 0) &&
|
||||
ratescfg->min == 0 && ratescfg->max == 0)) {
|
||||
user_rates = false;
|
||||
@@ -71,73 +57,105 @@ unsigned int* get_rates() {
|
||||
user_rates = true;
|
||||
return rates;
|
||||
}
|
||||
log_level log_level_from_sys_level(sys_DebugLevelEnum level) {
|
||||
log_level log_level_from_sys_level(sys_squeezelite_debug_levels level) {
|
||||
switch (level) {
|
||||
case sys_DebugLevelEnum_DEFAULT:
|
||||
case sys_squeezelite_debug_levels_DEFAULT:
|
||||
return lWARN;
|
||||
break;
|
||||
case sys_DebugLevelEnum_INFO:
|
||||
case sys_squeezelite_debug_levels_INFO:
|
||||
return lINFO;
|
||||
break;
|
||||
case sys_DebugLevelEnum_ERROR:
|
||||
case sys_squeezelite_debug_levels_ERROR:
|
||||
return lERROR;
|
||||
break;
|
||||
case sys_DebugLevelEnum_WARN:
|
||||
case sys_squeezelite_debug_levels_WARN:
|
||||
return lWARN;
|
||||
break;
|
||||
case sys_DebugLevelEnum_DEBUG:
|
||||
case sys_squeezelite_debug_levels_DEBUG:
|
||||
return lDEBUG;
|
||||
break;
|
||||
case sys_DebugLevelEnum_SDEBUG:
|
||||
case sys_squeezelite_debug_levels_SDEBUG:
|
||||
return lSDEBUG;
|
||||
break;
|
||||
default:
|
||||
return lWARN;
|
||||
}
|
||||
}
|
||||
void build_codec_string(sys_CodexEnum* list, size_t count, char* buffer, size_t buf_size) {
|
||||
const char* prefix = STR(sys_CodexEnum) "_c_";
|
||||
void build_codec_string(sys_squeezelite_codecs* list, size_t count, char* buffer, size_t buf_size) {
|
||||
const char* prefix = STR(sys_squeezelite_codecs) "_c_";
|
||||
const char* name = NULL;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (i > 0) {
|
||||
strncat(buffer, ", ", buf_size);
|
||||
}
|
||||
name = sys_CodexEnum_name(list[i]) + strlen(prefix);
|
||||
name = sys_squeezelite_codecs_name(list[i]) + strlen(prefix);
|
||||
LOG_INFO("Found codec: %s ", name);
|
||||
strncat(buffer, name, buf_size);
|
||||
}
|
||||
LOG_INFO("Codec list: %s ", buffer);
|
||||
}
|
||||
|
||||
sys_squeezelite_config* get_profile(const char* name) {
|
||||
const char* resolved_name = name && strlen(name) > 0 ? name : platform->services.current_profile;
|
||||
LOG_DEBUG("get_profile called with name: %s, resolved to: %s",
|
||||
name ? name : "NULL",
|
||||
resolved_name ? resolved_name : "NULL or EMPTY");
|
||||
|
||||
if (!platform->has_services || platform->services.squeezelite_profiles_count <= 0) {
|
||||
LOG_ERROR("No squeezelite profiles available");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!resolved_name || strlen(resolved_name) == 0) {
|
||||
LOG_WARN("No specific profile requested, using the first available profile");
|
||||
return &platform->services.squeezelite_profiles[0].profile;
|
||||
}
|
||||
|
||||
for (int i = 0; i < platform->services.squeezelite_profiles_count; i++) {
|
||||
LOG_DEBUG("Checking profile: %s", platform->services.squeezelite_profiles[i].name);
|
||||
if (strcmp(platform->services.squeezelite_profiles[i].name, resolved_name) == 0) {
|
||||
LOG_DEBUG("Profile matched: %s", platform->services.squeezelite_profiles[i].name);
|
||||
return &platform->services.squeezelite_profiles[i].profile;
|
||||
}
|
||||
}
|
||||
if(platform->services.squeezelite_profiles[0].name && strlen(platform->services.squeezelite_profiles[0].name)>0){
|
||||
LOG_WARN("Could not find profile %s. Falling back to %s", resolved_name,platform->services.squeezelite_profiles[0].name);
|
||||
return &platform->services.squeezelite_profiles[0].profile;
|
||||
}
|
||||
|
||||
LOG_ERROR("Profile '%s' not found", resolved_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int squeezelite_main_start() {
|
||||
u8_t mac[6];
|
||||
unsigned output_buf_size = 0;
|
||||
LOG_INFO("Starting squeezelite");
|
||||
char include_codecs[101] = {0};
|
||||
char exclude_codecs[101] = {0};
|
||||
config = platform->has_services && platform->services.has_squeezelite
|
||||
? &platform->services.squeezelite
|
||||
: NULL;
|
||||
config = get_profile(NULL); // get the active profile
|
||||
if (!config) {
|
||||
LOG_ERROR("Squeezelite not configured");
|
||||
return -1;
|
||||
return -2;
|
||||
}
|
||||
|
||||
LOG_INFO("Running embedded init");
|
||||
int err = embedded_init();
|
||||
if (err) return err;
|
||||
get_mac(mac);
|
||||
unsigned int * rates = get_rates();
|
||||
unsigned int* rates = get_rates();
|
||||
|
||||
signal(SIGINT, sighandler);
|
||||
signal(SIGTERM, sighandler);
|
||||
#if defined(SIGQUIT)
|
||||
signal(SIGQUIT, sighandler);
|
||||
#endif
|
||||
#if defined(SIGHUP)
|
||||
signal(SIGHUP, sighandler);
|
||||
#endif
|
||||
output_buf_size = config->buffers.output;
|
||||
signal(SIGINT, sighandler);
|
||||
signal(SIGTERM, sighandler);
|
||||
#if defined(SIGQUIT)
|
||||
signal(SIGQUIT, sighandler);
|
||||
#endif
|
||||
#if defined(SIGHUP)
|
||||
signal(SIGHUP, sighandler);
|
||||
#endif
|
||||
output_buf_size = config->buffers.output*1024;
|
||||
|
||||
// set the output buffer size if not specified on the command line, take account of resampling
|
||||
if (!output_buf_size) {
|
||||
// set the output buffer size if not specified in the parameters, take account of resampling
|
||||
if (output_buf_size == 0) {
|
||||
output_buf_size = OUTPUTBUF_SIZE;
|
||||
if (strlen(config->resample) > 0) {
|
||||
unsigned scale = 8;
|
||||
@@ -151,32 +169,37 @@ int squeezelite_main_start() {
|
||||
}
|
||||
build_codec_string(config->excluded_codex, config->excluded_codex_count, exclude_codecs,
|
||||
sizeof(exclude_codecs));
|
||||
build_codec_string(
|
||||
config->included_codex, config->included_codex, include_codecs, sizeof(include_codecs));
|
||||
build_codec_string(config->included_codex, config->included_codex_count, include_codecs,
|
||||
sizeof(include_codecs));
|
||||
|
||||
unsigned int stream_buf_size =
|
||||
config->buffers.stream > 0 ? config->buffers.stream : STREAMBUF_SIZE;
|
||||
stream_init(
|
||||
log_level_from_sys_level(platform->services.squeezelite.log.stream), stream_buf_size);
|
||||
unsigned int stream_buf_size =STREAMBUF_SIZE;
|
||||
if(config->buffers.stream > 0){
|
||||
stream_buf_size = config->buffers.stream *1024;
|
||||
LOG_DEBUG("Found stream buffer configuration: %d (%d)",config->buffers.stream,stream_buf_size);
|
||||
}
|
||||
else {
|
||||
LOG_DEBUG("Using default stream buffer: %d",stream_buf_size);
|
||||
}
|
||||
stream_init(log_level_from_sys_level(config->log.stream), stream_buf_size);
|
||||
output_init_embedded();
|
||||
decode_init(log_level_from_sys_level(platform->services.squeezelite.log.decode), include_codecs,
|
||||
exclude_codecs);
|
||||
decode_init(log_level_from_sys_level(config->log.decode),
|
||||
strlen(include_codecs) > 0 ? include_codecs : NULL, exclude_codecs);
|
||||
|
||||
#if RESAMPLE || RESAMPLE16
|
||||
if (strlen(config->resample) > 0) {
|
||||
if (config->resample && strlen(config->resample) > 0) {
|
||||
process_init(config->resample);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!config->enabled) {
|
||||
LOG_ERROR("LMS is disabled");
|
||||
LOG_WARN("LMS is disabled");
|
||||
while (1)
|
||||
sleep(3600);
|
||||
}
|
||||
char* name = strlen(platform->names.squeezelite) > 0 ? platform->names.squeezelite
|
||||
: platform->names.device;
|
||||
slimproto(log_level_from_sys_level(platform->services.squeezelite.log.slimproto),
|
||||
config->server_name_ip, mac, name, NULL, NULL, config->max_rate);
|
||||
slimproto(log_level_from_sys_level(config->log.slimproto), config->server_name_ip, mac, name,
|
||||
NULL, NULL, config->max_rate);
|
||||
|
||||
decode_close();
|
||||
stream_close();
|
||||
|
||||
273
components/squeezelite/external/dac_external.c
vendored
273
components/squeezelite/external/dac_external.c
vendored
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Squeezelite for esp32
|
||||
*
|
||||
* (c) Sebastien 2019
|
||||
@@ -8,18 +8,19 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <driver/i2s.h>
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_INFO
|
||||
#include "Config.h"
|
||||
#include "adac.h"
|
||||
#include "cJSON.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "gpio_exp.h"
|
||||
#include "cJSON.h"
|
||||
#include "inttypes.h"
|
||||
#include "string.h"
|
||||
// #include "Configurator.h"
|
||||
#pragma message("fixme: look for TODO below")
|
||||
#include "adac.h"
|
||||
#include <driver/i2s.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
static const char TAG[] = "DAC external";
|
||||
|
||||
@@ -27,159 +28,167 @@ static void speaker(bool active);
|
||||
static void headset(bool active);
|
||||
static bool volume(unsigned left, unsigned right) { return false; }
|
||||
static void power(adac_power_e mode);
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck);
|
||||
static bool init(sys_dac_config* config, i2s_config_t* i2s_config, bool* mck);
|
||||
|
||||
static bool i2c_json_execute(char *set);
|
||||
// static bool i2c_json_execute(char *set);
|
||||
static bool i2c_execute_cmd(sys_dac_control_type cmd_type);
|
||||
|
||||
const struct adac_s dac_external = { sys_DACModelEnum_I2S, init, adac_deinit, power, speaker, headset, volume };
|
||||
static cJSON *i2c_json;
|
||||
const struct adac_s dac_external = {sys_dac_models_I2S, init, adac_deinit, power, speaker, headset, volume};
|
||||
static int i2c_addr;
|
||||
|
||||
static const struct {
|
||||
char *model;
|
||||
bool mclk;
|
||||
char *controlset;
|
||||
} codecs[] = {
|
||||
{ "es8388", true,
|
||||
"{\"init\":[ \
|
||||
{\"reg\":8,\"val\":0}, {\"reg\":2,\"val\":243}, {\"reg\":43,\"val\":128}, {\"reg\":0,\"val\":5}, \
|
||||
{\"reg\":1,\"val\":64}, {\"reg\":4,\"val\":60},"
|
||||
#if BYTES_PER_FRAME == 8
|
||||
"{\"reg\":23,\"val\":32},"
|
||||
#else
|
||||
"{\"reg\":23,\"val\":24},"
|
||||
#endif
|
||||
"{\"reg\":24,\"val\":2}, \
|
||||
{\"reg\":26,\"val\":0}, {\"reg\":27,\"val\":0}, {\"reg\":25,\"val\":50}, {\"reg\":38,\"val\":0}, \
|
||||
{\"reg\":39,\"val\":184}, {\"reg\":42,\"val\":184}, {\"reg\":46,\"val\":30}, {\"reg\":47,\"val\":30}, \
|
||||
{\"reg\":48,\"val\":30}, {\"reg\":49,\"val\":30}, {\"reg\":2,\"val\":170}]}" },
|
||||
{ NULL, false, NULL }
|
||||
};
|
||||
extern sys_dac_default_sets* default_dac_sets;
|
||||
static EXT_RAM_ATTR sys_dac_control_set* i2c_default_controlset = NULL;
|
||||
static EXT_RAM_ATTR sys_dac_control_set* i2c_controlset = NULL;
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck) {
|
||||
char *p=NULL;
|
||||
void * dummy = &codecs;
|
||||
// i2c_addr = adac_init(config, i2c_port_num);
|
||||
// if (!i2c_addr) return true;
|
||||
|
||||
// ESP_LOGI(TAG, "DAC on I2C @%d", i2c_addr);
|
||||
|
||||
// p = config_alloc_get_str("dac_controlset", CONFIG_DAC_CONTROLSET, NULL);
|
||||
static bool init(sys_dac_config* config, i2s_config_t* i2s_config, bool* mck) {
|
||||
|
||||
// if ((!p || !*p) && (p = strcasestr(config, "model")) != NULL) {
|
||||
// char model[32] = "";
|
||||
// int i;
|
||||
// sscanf(p, "%*[^=]=%31[^,]", model);
|
||||
// for (i = 0; *model && ((p = codecs[i].controlset) != NULL) && strcasecmp(codecs[i].model, model); i++);
|
||||
// if (p) *mck = codecs[i].mclk;
|
||||
// }
|
||||
#ifdef BYTES_PER_FRAME
|
||||
uint32_t bytes_per_frame = BYTES_PER_FRAME;
|
||||
#else
|
||||
uint32_t bytes_per_frame = 4;
|
||||
#endif
|
||||
i2c_addr = adac_init(config);
|
||||
if (!i2c_addr) return true;
|
||||
ESP_LOGI(TAG, "DAC on I2C @%d", i2c_addr);
|
||||
ESP_LOGD(TAG, "Checkinf if there's a default control set for DAC %s in the list of %d presets", sys_dac_models_name(config->model),
|
||||
default_dac_sets->sets_count);
|
||||
|
||||
// i2c_json = cJSON_Parse(p);
|
||||
|
||||
// if (!i2c_json) {
|
||||
// ESP_LOGW(TAG, "no i2c controlset found");
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// if (!i2c_json_execute("init")) {
|
||||
// ESP_LOGE(TAG, "could not intialize DAC");
|
||||
// return false;
|
||||
// }
|
||||
// TODO: Add support for the commented code
|
||||
for (int i = 0; i < default_dac_sets->sets_count; i++) {
|
||||
ESP_LOGD(TAG, "Checkinf if preset #%d (%s[%d]) matches %s[%d]", i + 1, sys_dac_models_name(default_dac_sets->sets[i].model),
|
||||
default_dac_sets->sets[i].bytes_per_frame, sys_dac_models_name(config->model), bytes_per_frame);
|
||||
if (default_dac_sets->sets[i].bytes_per_frame == bytes_per_frame && default_dac_sets->sets[i].model == config->model) {
|
||||
if (!default_dac_sets->sets[i].valid) {
|
||||
ESP_LOGE(TAG, "DAC %s is unsupported with %d bytes per frame", sys_dac_models_name(config->model), bytes_per_frame);
|
||||
return false;
|
||||
}
|
||||
i2c_default_controlset = &default_dac_sets->sets[i].set;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
if (config->has_daccontrolset && config->daccontrolset.commands_count > 0) {
|
||||
i2c_controlset = &config->daccontrolset;
|
||||
}
|
||||
if (!i2c_controlset && !i2c_default_controlset) {
|
||||
ESP_LOGE(TAG, "DAC configuration invalid for %s", sys_dac_models_name(config->model));
|
||||
return false;
|
||||
}
|
||||
if (mck) {
|
||||
*mck = i2c_controlset != NULL ? i2c_controlset->mclk_needed : i2c_default_controlset->mclk_needed;
|
||||
ESP_LOGD(TAG, "Master clock is %s", (*mck) ? "NEEDED" : "NOT NEEDED");
|
||||
}
|
||||
if (!i2c_execute_cmd(sys_dac_control_type_INIT)) {
|
||||
ESP_LOGE(TAG, "could not intialize DAC");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* power
|
||||
*/
|
||||
static void power(adac_power_e mode) {
|
||||
if (mode == ADAC_STANDBY || mode == ADAC_OFF) i2c_json_execute("poweroff");
|
||||
else i2c_json_execute("poweron");
|
||||
if (mode == ADAC_STANDBY || mode == ADAC_OFF)
|
||||
i2c_execute_cmd(sys_dac_control_type_POWER_OFF);
|
||||
else
|
||||
i2c_execute_cmd(sys_dac_control_type_POWER_ON);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* speaker
|
||||
*/
|
||||
static void speaker(bool active) {
|
||||
if (active) i2c_json_execute("speakeron");
|
||||
else i2c_json_execute("speakeroff");
|
||||
}
|
||||
if (active)
|
||||
i2c_execute_cmd(sys_dac_control_type_SPEAKER_ON);
|
||||
else
|
||||
i2c_execute_cmd(sys_dac_control_type_SPEAKER_OFF);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* headset
|
||||
*/
|
||||
static void headset(bool active) {
|
||||
if (active) i2c_json_execute("headseton");
|
||||
else i2c_json_execute("headsetoff");
|
||||
if (active)
|
||||
i2c_execute_cmd(sys_dac_control_type_HEADSET_ON);
|
||||
else
|
||||
i2c_execute_cmd(sys_dac_control_type_HEADSET_OFF);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
bool i2c_json_execute(char *set) {
|
||||
cJSON *json_set = cJSON_GetObjectItemCaseSensitive(i2c_json, set);
|
||||
cJSON *item;
|
||||
|
||||
if (!json_set) return true;
|
||||
|
||||
cJSON_ArrayForEach(item, json_set) {
|
||||
cJSON *action;
|
||||
|
||||
// is this a delay
|
||||
if ((action = cJSON_GetObjectItemCaseSensitive(item, "delay")) != NULL) {
|
||||
vTaskDelay(pdMS_TO_TICKS(action->valueint));
|
||||
ESP_LOGI(TAG, "DAC waiting %d ms", action->valueint);
|
||||
continue;
|
||||
static const sys_dac_control_itm* i2c_get_controlset_cmd(sys_dac_control_set* set, sys_dac_control_type cmd_type, pb_size_t* items_count) {
|
||||
ESP_RETURN_ON_FALSE(set!=NULL,NULL,TAG,"Invalid set");
|
||||
ESP_RETURN_ON_FALSE(items_count!=NULL,NULL,TAG,"Invalid items count");
|
||||
ESP_LOGD(TAG,"Looking for command %s in control from a list of %d commands",sys_dac_control_type_name(cmd_type),set->commands_count);
|
||||
*items_count = 0;
|
||||
for (int i = 0; i < set->commands_count; i++) {
|
||||
if (set->commands[i].type == cmd_type) {
|
||||
*items_count = set->commands[i].items_count;
|
||||
return set->commands[i].items;
|
||||
}
|
||||
|
||||
// is this a gpio toggle
|
||||
if ((action = cJSON_GetObjectItemCaseSensitive(item, "gpio")) != NULL) {
|
||||
cJSON *level = cJSON_GetObjectItemCaseSensitive(item, "level");
|
||||
ESP_LOGI(TAG, "set GPIO %d at %d", action->valueint, level->valueint);
|
||||
gpio_set_direction_x(action->valueint, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level_x(action->valueint, level->valueint);
|
||||
continue;
|
||||
}
|
||||
ESP_LOGD(TAG,"No control set found for command %s",sys_dac_control_type_name(cmd_type));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
bool i2c_execute_cmd(sys_dac_control_type cmd_type) {
|
||||
pb_size_t items_count;
|
||||
const sys_dac_control_itm* cmd = NULL;
|
||||
esp_err_t err = ESP_OK;
|
||||
ESP_LOGD(TAG, "Getting control set for command %s", sys_dac_control_type_name(cmd_type));
|
||||
if(i2c_controlset){
|
||||
cmd = i2c_get_controlset_cmd(i2c_controlset, cmd_type, &items_count);
|
||||
}
|
||||
if (!cmd || items_count == 0) {
|
||||
ESP_LOGD(TAG, "Not found. Checking if defaults are known %s", sys_dac_control_type_name(cmd_type));
|
||||
cmd = i2c_get_controlset_cmd(i2c_default_controlset, cmd_type, &items_count);
|
||||
}
|
||||
if (!cmd || items_count == 0) {
|
||||
ESP_LOGD(TAG, "No commands for %s", sys_dac_control_type_name(cmd_type));
|
||||
return true;
|
||||
}
|
||||
for (pb_size_t i = 0; i < items_count && err==ESP_OK; i++) {
|
||||
switch (cmd->which_item_type) {
|
||||
case sys_dac_control_itm_reg_action_tag:
|
||||
ESP_LOGD(TAG, "Setting reg %d mode %s value %d ", cmd->item_type.reg_action.reg,
|
||||
sys_dac_control_mode_name(cmd->item_type.reg_action.mode), cmd->item_type.reg_action.val);
|
||||
if (cmd->item_type.reg_action.mode == sys_dac_control_mode_NOTHING) {
|
||||
err = adac_write_byte(i2c_addr, cmd->item_type.reg_action.reg, cmd->item_type.reg_action.val);
|
||||
} else if (cmd->item_type.reg_action.mode == sys_dac_control_mode_OR) {
|
||||
uint8_t data = adac_read_byte(i2c_addr, cmd->item_type.reg_action.reg);
|
||||
data |= (uint8_t)cmd->item_type.reg_action.val;
|
||||
err = adac_write_byte(i2c_addr, cmd->item_type.reg_action.reg, data);
|
||||
} else if (cmd->item_type.reg_action.mode == sys_dac_control_mode_AND) {
|
||||
uint8_t data = adac_read_byte(i2c_addr, cmd->item_type.reg_action.reg);
|
||||
data &= (uint8_t)cmd->item_type.reg_action.val;
|
||||
err = adac_write_byte(i2c_addr, cmd->item_type.reg_action.reg, data);
|
||||
}
|
||||
|
||||
break;
|
||||
case sys_dac_control_itm_regs_action_tag:
|
||||
ESP_LOGD(TAG, "Setting reg %d with %d byte(s)", cmd->item_type.regs_action.reg, cmd->item_type.regs_action.vals_count);
|
||||
err = adac_write(i2c_addr, cmd->item_type.regs_action.reg, cmd->item_type.regs_action.vals, cmd->item_type.regs_action.vals_count);
|
||||
break;
|
||||
case sys_dac_control_itm_gpio_action_tag:
|
||||
ESP_LOGI(TAG, "set GPIO %d at %s", cmd->item_type.gpio_action.gpio, sys_dac_control_lvl_name(cmd->item_type.gpio_action.level));
|
||||
err = gpio_set_direction_x(cmd->item_type.gpio_action.gpio, GPIO_MODE_OUTPUT);
|
||||
if(err == ESP_OK) err = gpio_set_level_x(cmd->item_type.gpio_action.gpio, cmd->item_type.gpio_action.level - sys_dac_control_lvl_LV_0);
|
||||
break;
|
||||
case sys_dac_control_itm_delay_action_tag:
|
||||
vTaskDelay(pdMS_TO_TICKS(cmd->item_type.delay_action.delay));
|
||||
ESP_LOGI(TAG, "DAC waiting %" PRIu64 " ms", cmd->item_type.delay_action.delay);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
action= cJSON_GetObjectItemCaseSensitive(item, "reg");
|
||||
cJSON *val = cJSON_GetObjectItemCaseSensitive(item, "val");
|
||||
|
||||
// this is gpio register setting or crap
|
||||
if (cJSON_IsArray(val)) {
|
||||
cJSON *value;
|
||||
uint8_t *data = malloc(cJSON_GetArraySize(val));
|
||||
int count = 0;
|
||||
|
||||
if (!data) continue;
|
||||
|
||||
cJSON_ArrayForEach(value, val) {
|
||||
data[count++] = value->valueint;
|
||||
}
|
||||
|
||||
adac_write(i2c_addr, action->valueint, data, count);
|
||||
free(data);
|
||||
} else {
|
||||
cJSON *mode = cJSON_GetObjectItemCaseSensitive(item, "mode");
|
||||
}
|
||||
|
||||
if (!action || !val) continue;
|
||||
|
||||
if (!mode) {
|
||||
adac_write_byte(i2c_addr, action->valueint, val->valueint);
|
||||
} else if (!strcasecmp(mode->valuestring, "or")) {
|
||||
uint8_t data = adac_read_byte(i2c_addr, action->valueint);
|
||||
data |= (uint8_t) val->valueint;
|
||||
adac_write_byte(i2c_addr, action->valueint, data);
|
||||
} else if (!strcasecmp(mode->valuestring, "and")) {
|
||||
uint8_t data = adac_read_byte(i2c_addr, action->valueint);
|
||||
data &= (uint8_t) val->valueint;
|
||||
adac_write_byte(i2c_addr, action->valueint, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return (err==ESP_OK);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
#include "perf_trace.h"
|
||||
#include "services.h"
|
||||
#include "led.h"
|
||||
#include "Configurator.h"
|
||||
extern log_level log_level_from_sys_level(sys_DebugLevelEnum level);
|
||||
static sys_Squeezelite * config = NULL;
|
||||
#include "Config.h"
|
||||
extern log_level log_level_from_sys_level(sys_squeezelite_debug_levels level);
|
||||
extern sys_squeezelite_config* get_profile(const char* name);
|
||||
static sys_squeezelite_config * config = NULL;
|
||||
extern struct outputstate output;
|
||||
extern struct buffer *outputbuf;
|
||||
extern struct buffer *streambuf;
|
||||
@@ -76,7 +77,8 @@ static uint32_t bt_idle_callback(void) {
|
||||
* Init BT sink
|
||||
*/
|
||||
void output_init_bt() {
|
||||
config = &platform->services.squeezelite;
|
||||
config = get_profile(NULL); // get the active profile
|
||||
if(!config) return;
|
||||
loglevel = log_level_from_sys_level(config->log.output);
|
||||
bt_idle_since = pdTICKS_TO_MS(xTaskGetTickCount());
|
||||
services_sleep_setsleeper(bt_idle_callback);
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
*/
|
||||
#include "squeezelite.h"
|
||||
#include "equalizer.h"
|
||||
#include "Configurator.h"
|
||||
extern log_level log_level_from_sys_level(sys_DebugLevelEnum level);
|
||||
static sys_Squeezelite* config = NULL;
|
||||
#include "Config.h"
|
||||
extern log_level log_level_from_sys_level(sys_squeezelite_debug_levels level);
|
||||
extern sys_squeezelite_config* get_profile(const char* name);
|
||||
static sys_squeezelite_config* config = NULL;
|
||||
|
||||
|
||||
extern unsigned int* get_rates() ;
|
||||
@@ -76,9 +77,9 @@ static bool handler(u8_t* data, int len) {
|
||||
}
|
||||
|
||||
void output_init_embedded() {
|
||||
config = &platform->services.squeezelite;
|
||||
config = get_profile(NULL); // get the active profile
|
||||
loglevel = log_level_from_sys_level(config->log.output);
|
||||
LOG_INFO("init device: %s", sys_OutputTypeEnum_name(config->output_type));
|
||||
LOG_INFO("init device: %s", sys_squeezelite_outputs_name(config->output_type));
|
||||
|
||||
// chain handlers
|
||||
slimp_handler_chain = slimp_handler;
|
||||
@@ -87,15 +88,22 @@ void output_init_embedded() {
|
||||
// init equalizer before backends
|
||||
equalizer_init();
|
||||
memset(&output, 0, sizeof(output));
|
||||
|
||||
output_init_common(loglevel, sys_OutputTypeEnum_name(config->output_type),
|
||||
config->buffers.output, get_rates(), config->amp_gpio_timeout);
|
||||
unsigned int output_buffer = OUTPUTBUF_SIZE;
|
||||
if(config->buffers.output > 0){
|
||||
output_buffer = config->buffers.output *1024;
|
||||
LOG_DEBUG("Found output buffer configuration: %d (%d)",config->buffers.output,output_buffer);
|
||||
}
|
||||
else {
|
||||
LOG_DEBUG("Using default output buffer: %d",output_buffer);
|
||||
}
|
||||
output_init_common(loglevel, sys_squeezelite_outputs_name(config->output_type),
|
||||
output_buffer, get_rates(), config->amp_gpio_timeout);
|
||||
output.start_frames = FRAME_BLOCK;
|
||||
#pragma message("Rate delay logic incomplete")
|
||||
output.rate_delay = 0;
|
||||
|
||||
#if CONFIG_BT_SINK
|
||||
if (config->output_type == sys_OutputTypeEnum_OUTPUT_Bluetooth) {
|
||||
if (config->output_type == sys_squeezelite_outputs_BT) {
|
||||
LOG_INFO("init Bluetooth");
|
||||
close_cb = &output_close_bt;
|
||||
output_init_bt(get_rates());
|
||||
@@ -132,7 +140,7 @@ void set_volume(unsigned left, unsigned right) {
|
||||
|
||||
bool test_open(const char* device, unsigned rates[], bool userdef_rates) {
|
||||
memset(rates, 0, MAX_SUPPORTED_SAMPLERATES * sizeof(unsigned));
|
||||
if (config->output_type == sys_OutputTypeEnum_OUTPUT_I2S) {
|
||||
if (config->output_type == sys_squeezelite_outputs_I2S) {
|
||||
unsigned _rates[] = {
|
||||
#if BYTES_PER_FRAME == 4
|
||||
192000,
|
||||
@@ -152,7 +160,7 @@ bool test_open(const char* device, unsigned rates[], bool userdef_rates) {
|
||||
0
|
||||
};
|
||||
memcpy(rates, _rates, sizeof(_rates));
|
||||
} else if (config->output_type == sys_OutputTypeEnum_OUTPUT_SPDIF) {
|
||||
} else if (config->output_type == sys_squeezelite_outputs_SPDIF) {
|
||||
unsigned _rates[] = {
|
||||
96000, 88200, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 0};
|
||||
memcpy(rates, _rates, sizeof(_rates));
|
||||
|
||||
@@ -30,27 +30,29 @@ there might be a pop and a de-sync when sampling rate change happens. Not
|
||||
sure that using rate_delay would fix that
|
||||
*/
|
||||
|
||||
#include "squeezelite.h"
|
||||
#include "slimproto.h"
|
||||
#include "esp_pthread.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "perf_trace.h"
|
||||
#include <signal.h>
|
||||
#include "adac.h"
|
||||
#include "time.h"
|
||||
#include "led.h"
|
||||
#include "services.h"
|
||||
#include "monitor.h"
|
||||
#include "gpio_exp.h"
|
||||
#include "Config.h"
|
||||
#include "accessors.h"
|
||||
#include "adac.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "equalizer.h"
|
||||
#include "esp_pthread.h"
|
||||
#include "globdefs.h"
|
||||
#include "Configurator.h"
|
||||
#include "gpio_exp.h"
|
||||
#include "hal/gpio_hal.h"
|
||||
#include "led.h"
|
||||
#include "monitor.h"
|
||||
#include "perf_trace.h"
|
||||
#include "services.h"
|
||||
#include "slimproto.h"
|
||||
#include "squeezelite.h"
|
||||
#include "time.h"
|
||||
#include <signal.h>
|
||||
|
||||
extern log_level log_level_from_sys_level(sys_DebugLevelEnum level);
|
||||
static sys_Squeezelite* config = NULL;
|
||||
extern sys_squeezelite_config* get_profile(const char* name);
|
||||
extern log_level log_level_from_sys_level(sys_squeezelite_debug_levels level);
|
||||
static sys_squeezelite_config* config = NULL;
|
||||
|
||||
#define LOCK mutex_lock(outputbuf->mutex)
|
||||
#define UNLOCK mutex_unlock(outputbuf->mutex)
|
||||
@@ -75,18 +77,18 @@ static sys_Squeezelite* config = NULL;
|
||||
#define DMA_BUF_FRAMES_SPDIF 450
|
||||
#define DMA_BUF_COUNT_SPDIF 7
|
||||
|
||||
#define DECLARE_ALL_MIN_MAX \
|
||||
DECLARE_MIN_MAX(o); \
|
||||
DECLARE_MIN_MAX(s); \
|
||||
DECLARE_MIN_MAX(rec); \
|
||||
DECLARE_MIN_MAX(i2s_time); \
|
||||
#define DECLARE_ALL_MIN_MAX \
|
||||
DECLARE_MIN_MAX(o); \
|
||||
DECLARE_MIN_MAX(s); \
|
||||
DECLARE_MIN_MAX(rec); \
|
||||
DECLARE_MIN_MAX(i2s_time); \
|
||||
DECLARE_MIN_MAX(buffering);
|
||||
|
||||
#define RESET_ALL_MIN_MAX \
|
||||
RESET_MIN_MAX(o); \
|
||||
RESET_MIN_MAX(s); \
|
||||
RESET_MIN_MAX(rec); \
|
||||
RESET_MIN_MAX(i2s_time); \
|
||||
#define RESET_ALL_MIN_MAX \
|
||||
RESET_MIN_MAX(o); \
|
||||
RESET_MIN_MAX(s); \
|
||||
RESET_MIN_MAX(rec); \
|
||||
RESET_MIN_MAX(i2s_time); \
|
||||
RESET_MIN_MAX(buffering);
|
||||
|
||||
#define STATS_PERIOD_MS 5000
|
||||
@@ -101,7 +103,7 @@ extern struct buffer* streambuf;
|
||||
extern struct buffer* outputbuf;
|
||||
extern u8_t* silencebuf;
|
||||
|
||||
const struct adac_s* dac_set[] = {&dac_tas57xx, &dac_tas5713, &dac_ac101, &dac_wm8978, NULL};
|
||||
const struct adac_s* dac_set[] = {&dac_tas57xx, &dac_tas5713, &dac_ac101, &dac_wm8978, &dac_cs4265, NULL};
|
||||
const struct adac_s* adac = &dac_external;
|
||||
|
||||
static log_level loglevel;
|
||||
@@ -122,21 +124,18 @@ static size_t dma_buf_frames;
|
||||
static TaskHandle_t output_i2s_task;
|
||||
static struct {
|
||||
int gpio, active;
|
||||
} amp_control = {CONFIG_AMP_GPIO, CONFIG_AMP_GPIO_LEVEL},
|
||||
mute_control = {CONFIG_MUTE_GPIO, CONFIG_MUTE_GPIO_LEVEL};
|
||||
} amp_control = {-1, 0}, mute_control = {-1, 0};
|
||||
|
||||
DECLARE_ALL_MIN_MAX;
|
||||
|
||||
static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
|
||||
u8_t flags, s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T** cross_ptr);
|
||||
static int _i2s_write_frames(
|
||||
frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, u8_t flags, s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T** cross_ptr);
|
||||
static void output_thread_i2s(void* arg);
|
||||
static void i2s_stats(uint32_t now);
|
||||
|
||||
static void spdif_convert(ISAMPLE_T* src, size_t frames, u32_t* dst);
|
||||
static void (*jack_handler_chain)(bool inserted);
|
||||
|
||||
#define I2C_PORT 0
|
||||
|
||||
/****************************************************************************************
|
||||
* AUDO packet handler
|
||||
*/
|
||||
@@ -151,7 +150,7 @@ static bool handler(u8_t* data, int len) {
|
||||
if (jack_mutes_amp != (pkt->config == 0)) {
|
||||
jack_mutes_amp = pkt->config == 0;
|
||||
platform->dev.dac.jack_mutes_amp = jack_mutes_amp;
|
||||
configurator_raise_changed();
|
||||
config_raise_changed(false);
|
||||
|
||||
if (jack_mutes_amp && jack_inserted_svc()) {
|
||||
adac->speaker(false);
|
||||
@@ -181,8 +180,7 @@ static void jack_handler(bool inserted) {
|
||||
if (jack_mutes_amp) {
|
||||
LOG_INFO("switching amplifier %s", inserted ? "OFF" : "ON");
|
||||
adac->speaker(!inserted);
|
||||
if (amp_control.gpio != -1)
|
||||
gpio_set_level_x(amp_control.gpio, inserted ? !amp_control.active : amp_control.active);
|
||||
if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, inserted ? !amp_control.active : amp_control.active);
|
||||
}
|
||||
|
||||
// activate headset
|
||||
@@ -195,19 +193,17 @@ static void jack_handler(bool inserted) {
|
||||
/****************************************************************************************
|
||||
* Get inactivity callback
|
||||
*/
|
||||
static uint32_t i2s_idle_callback(void) {
|
||||
return output.state <= OUTPUT_STOPPED ? pdTICKS_TO_MS(xTaskGetTickCount()) - i2s_idle_since : 0;
|
||||
}
|
||||
static uint32_t i2s_idle_callback(void) { return output.state <= OUTPUT_STOPPED ? pdTICKS_TO_MS(xTaskGetTickCount()) - i2s_idle_since : 0; }
|
||||
|
||||
/****************************************************************************************
|
||||
* Initialize the DAC output
|
||||
*/
|
||||
void output_init_i2s() {
|
||||
int silent_do = -1;
|
||||
config = &platform->services.squeezelite;
|
||||
sys_DAC* dac_config = platform->has_dev && platform->dev.has_dac ? &platform->dev.dac : NULL;
|
||||
sys_SPDIF* spdif_config =
|
||||
platform->has_dev && platform->dev.has_spdif ? &platform->dev.spdif : NULL;
|
||||
LOG_DEBUG("Initializing I2S output");
|
||||
config = get_profile(NULL);
|
||||
sys_dac_config* dac_config = platform->has_dev && platform->dev.has_dac ? &platform->dev.dac : NULL;
|
||||
sys_dev_spdif* spdif_config = platform->has_dev && platform->dev.has_spdif ? &platform->dev.spdif : NULL;
|
||||
loglevel = log_level_from_sys_level(config->log.output);
|
||||
esp_err_t res;
|
||||
|
||||
@@ -216,8 +212,8 @@ void output_init_i2s() {
|
||||
slimp_handler = handler;
|
||||
if (dac_config) jack_mutes_amp = dac_config->jack_mutes_amp;
|
||||
if (platform->has_gpios) {
|
||||
amp_control.gpio = platform->has_gpios && platform->gpios.has_amp ? platform->gpios.amp.pin:-1;
|
||||
amp_control.active = platform->has_gpios && platform->gpios.has_amp ? platform->gpios.amp.level:0;
|
||||
amp_control.gpio = platform->has_gpios && platform->gpios.has_amp ? platform->gpios.amp.pin : -1;
|
||||
amp_control.active = platform->has_gpios && platform->gpios.has_amp ? platform->gpios.amp.level : 0;
|
||||
}
|
||||
|
||||
#if BYTES_PER_FRAME == 8
|
||||
@@ -236,16 +232,25 @@ void output_init_i2s() {
|
||||
|
||||
running = true;
|
||||
|
||||
i2s_pin_config_t i2s_dac_pin = {-1, -1, -1, -1}, i2s_spdif_pin = {-1, -1, -1, -1};
|
||||
i2s_pin_config_t i2s_dac_pin = {-1, -1, -1, -1, -1}, i2s_spdif_pin = {-1, -1, -1, -1, -1};
|
||||
if (dac_config) {
|
||||
i2s_dac_pin.bck_io_num = dac_config->has_bck ? dac_config->bck.pin : -1;
|
||||
i2s_dac_pin.data_out_num = dac_config->has_dout ? dac_config->dout.pin : -1;
|
||||
i2s_dac_pin.ws_io_num = dac_config->has_ws ? dac_config->ws.pin : -1;
|
||||
i2s_dac_pin.bck_io_num = dac_config->bck;
|
||||
i2s_dac_pin.data_out_num = dac_config->dout;
|
||||
i2s_dac_pin.ws_io_num = dac_config->ws;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||
if (dac_config->mck != sys_dac_mck_NONE && dac_config->mck != sys_dac_mck_INVALID1) {
|
||||
LOG_INFO("Using I2S Master clock %s", sys_dac_mck_name(dac_config->mck));
|
||||
i2s_dac_pin.mck_io_num = dac_config->mck - sys_dac_mck_GPIO0;
|
||||
} else {
|
||||
LOG_INFO("Configuration does not specify an I2S Master Clock");
|
||||
i2s_dac_pin.mck_io_num = -1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (spdif_config) {
|
||||
i2s_spdif_pin.bck_io_num = spdif_config->has_clk ? spdif_config->clk.pin : -1;
|
||||
i2s_spdif_pin.data_out_num = spdif_config->has_data ? spdif_config->data.pin : -1;
|
||||
i2s_spdif_pin.ws_io_num = spdif_config->has_dc ? spdif_config->dc.pin : -1;
|
||||
i2s_spdif_pin.bck_io_num = spdif_config->clk;
|
||||
i2s_spdif_pin.data_out_num = spdif_config->data;
|
||||
i2s_spdif_pin.ws_io_num = spdif_config->ws;
|
||||
}
|
||||
|
||||
if (i2s_dac_pin.data_out_num == -1 && i2s_spdif_pin.data_out_num == -1) {
|
||||
@@ -266,16 +271,16 @@ void output_init_i2s() {
|
||||
i2s_config.dma_buf_len = DMA_BUF_FRAMES;
|
||||
i2s_config.dma_buf_count = DMA_BUF_COUNT;
|
||||
|
||||
if (config->output_type == sys_OutputTypeEnum_OUTPUT_SPDIF) {
|
||||
if (config->output_type == sys_squeezelite_outputs_SPDIF) {
|
||||
spdif.enabled = true;
|
||||
LOG_DEBUG("Configuring SPDIF");
|
||||
if ((spdif.buf = heap_caps_malloc(SPDIF_BLOCK * 16, MALLOC_CAP_INTERNAL)) == NULL) {
|
||||
LOG_ERROR("Cannot allocate SPDIF buffer");
|
||||
}
|
||||
|
||||
if (i2s_spdif_pin.bck_io_num == -1 || i2s_spdif_pin.ws_io_num == -1 ||
|
||||
i2s_spdif_pin.data_out_num == -1) {
|
||||
LOG_WARN("Cannot initialize I2S for SPDIF bck:%d ws:%d do:%d", i2s_spdif_pin.bck_io_num,
|
||||
i2s_spdif_pin.ws_io_num, i2s_spdif_pin.data_out_num);
|
||||
if (i2s_spdif_pin.bck_io_num == -1 || i2s_spdif_pin.ws_io_num == -1 || i2s_spdif_pin.data_out_num == -1) {
|
||||
LOG_WARN(
|
||||
"Cannot initialize I2S for SPDIF bck:%d ws:%d do:%d", i2s_spdif_pin.bck_io_num, i2s_spdif_pin.ws_io_num, i2s_spdif_pin.data_out_num);
|
||||
}
|
||||
|
||||
i2s_config.sample_rate = output.current_sample_rate * 2;
|
||||
@@ -291,15 +296,14 @@ void output_init_i2s() {
|
||||
dma_buf_frames = i2s_config.dma_buf_len * i2s_config.dma_buf_count / 2;
|
||||
|
||||
// silence DAC output if sharing the same ws/bck
|
||||
if (i2s_dac_pin.ws_io_num == i2s_spdif_pin.ws_io_num &&
|
||||
i2s_dac_pin.bck_io_num == i2s_spdif_pin.bck_io_num)
|
||||
if (i2s_dac_pin.ws_io_num == i2s_spdif_pin.ws_io_num && i2s_dac_pin.bck_io_num == i2s_spdif_pin.bck_io_num)
|
||||
silent_do = i2s_dac_pin.data_out_num;
|
||||
|
||||
res = i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
|
||||
res |= i2s_set_pin(CONFIG_I2S_NUM, &i2s_spdif_pin);
|
||||
LOG_INFO("SPDIF using I2S bck:%d, ws:%d, do:%d", i2s_spdif_pin.bck_io_num,
|
||||
i2s_spdif_pin.ws_io_num, i2s_spdif_pin.data_out_num);
|
||||
LOG_INFO("SPDIF using I2S bck:%d, ws:%d, do:%d", i2s_spdif_pin.bck_io_num, i2s_spdif_pin.ws_io_num, i2s_spdif_pin.data_out_num);
|
||||
} else {
|
||||
LOG_DEBUG("Configuring DAC");
|
||||
i2s_config.sample_rate = output.current_sample_rate;
|
||||
i2s_config.bits_per_sample = BYTES_PER_FRAME * 8 / 2;
|
||||
// Counted in frames (but i2s allocates a buffer <= 4092 bytes)
|
||||
@@ -312,17 +316,30 @@ void output_init_i2s() {
|
||||
mute_control.gpio = dac_config && dac_config->has_mute ? dac_config->mute.pin : -1;
|
||||
mute_control.active = dac_config && dac_config->has_mute ? dac_config->mute.level : 0;
|
||||
bool mck_required = false;
|
||||
for (int i = 0; adac == &dac_external && dac_set[i]; i++)
|
||||
if (dac_set[i]->model == dac_config->model) adac = dac_set[i];
|
||||
res = adac->init(NULL, I2C_PORT, &i2s_config, &mck_required) ? ESP_OK : ESP_FAIL;
|
||||
|
||||
LOG_DEBUG("Looking for %s DAC Driver", sys_dac_models_name(dac_config->model));
|
||||
for (int i = 0; adac == &dac_external && dac_set[i]; i++) {
|
||||
if (dac_set[i]->model == dac_config->model) {
|
||||
LOG_DEBUG("Found Driver");
|
||||
adac = dac_set[i];
|
||||
} else {
|
||||
LOG_DEBUG("Skipping %s", sys_dac_models_name(dac_set[i]->model));
|
||||
}
|
||||
}
|
||||
if (!adac) {
|
||||
LOG_ERROR("Invalid adac structure");
|
||||
return;
|
||||
}
|
||||
if (!adac->init) {
|
||||
LOG_ERROR("adac doesn't have a valid init");
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("Calling DAC init for %s", sys_dac_models_name(adac->model));
|
||||
res = adac->init(&platform->dev.dac, &i2s_config, &mck_required) ? ESP_OK : ESP_FAIL;
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||
int mck_io_num = (dac_config->mck - sys_MCKEnum_MCK0) < 0 && mck_required
|
||||
? 0
|
||||
: dac_config->mck - sys_MCKEnum_MCK0;
|
||||
|
||||
int mck_io_num = (((int)dac_config->mck) - ((int)sys_MCKEnum_MCK0)) < 0 && mck_required ? 0
|
||||
: mck_required ? dac_config->mck - sys_MCKEnum_MCK0
|
||||
: -1;
|
||||
LOG_INFO("configuring MCLK on GPIO %d", mck_io_num);
|
||||
|
||||
if (mck_io_num == GPIO_NUM_0) {
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
|
||||
WRITE_PERI_REG(PIN_CTRL, CONFIG_I2S_NUM == I2S_NUM_0 ? 0xFFF0 : 0xFFFF);
|
||||
@@ -336,22 +353,28 @@ void output_init_i2s() {
|
||||
LOG_WARN("invalid MCK gpio %d", mck_io_num);
|
||||
}
|
||||
#else
|
||||
if (mck_required && i2s_dac_pin.mck_io_num == -1) i2s_dac_pin.mck_io_num = 0;
|
||||
LOG_INFO("configuring MCLK on GPIO %d", i2s_dac_pin.mck_io_num);
|
||||
#endif
|
||||
|
||||
res |= i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
|
||||
res |= i2s_set_pin(CONFIG_I2S_NUM, &i2s_dac_pin);
|
||||
|
||||
if (res == ESP_OK && mute_control.gpio >= 0) {
|
||||
gpio_pad_select_gpio(mute_control.gpio);
|
||||
gpio_set_direction(mute_control.gpio, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(mute_control.gpio, mute_control.active);
|
||||
if (res == ESP_OK) {
|
||||
if (mck_required && i2s_dac_pin.mck_io_num == -1) {
|
||||
LOG_DEBUG("Defaulting MCLK on GPIO %d", i2s_dac_pin.mck_io_num);
|
||||
i2s_dac_pin.mck_io_num = 0;
|
||||
} else {
|
||||
LOG_DEBUG("MCLK configured on GPIO %d", i2s_dac_pin.mck_io_num);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (res == ESP_OK) {
|
||||
res |= i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
|
||||
res |= i2s_set_pin(CONFIG_I2S_NUM, &i2s_dac_pin);
|
||||
|
||||
LOG_INFO("%s DAC using I2S bck:%d, ws:%d, do:%d, mute:%d:%d (res:%d)",
|
||||
sys_EthModelEnum_name(dac_config->model), i2s_dac_pin.bck_io_num, i2s_dac_pin.ws_io_num,
|
||||
i2s_dac_pin.data_out_num, mute_control.gpio, mute_control.active, res);
|
||||
if (res == ESP_OK && mute_control.gpio >= 0) {
|
||||
gpio_pad_select_gpio(mute_control.gpio);
|
||||
gpio_set_direction(mute_control.gpio, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(mute_control.gpio, mute_control.active);
|
||||
}
|
||||
|
||||
LOG_INFO("%s DAC using I2S bck:%d, ws:%d, do:%d, mute:%d:%d (res:%d)", sys_dac_models_name(dac_config->model), i2s_dac_pin.bck_io_num,
|
||||
i2s_dac_pin.ws_io_num, i2s_dac_pin.data_out_num, mute_control.gpio, mute_control.active, res);
|
||||
}
|
||||
}
|
||||
|
||||
if (res != ESP_OK) {
|
||||
@@ -368,8 +391,7 @@ void output_init_i2s() {
|
||||
|
||||
LOG_INFO("Initializing I2S mode %s with rate: %d, bits per sample: %d, buffer frames: %d, "
|
||||
"number of buffers: %d ",
|
||||
spdif.enabled ? "S/PDIF" : "normal", i2s_config.sample_rate, i2s_config.bits_per_sample,
|
||||
i2s_config.dma_buf_len, i2s_config.dma_buf_count);
|
||||
spdif.enabled ? "S/PDIF" : "normal", i2s_config.sample_rate, i2s_config.bits_per_sample, i2s_config.dma_buf_len, i2s_config.dma_buf_count);
|
||||
|
||||
i2s_stop(CONFIG_I2S_NUM);
|
||||
i2s_zero_dma_buffer(CONFIG_I2S_NUM);
|
||||
@@ -408,10 +430,8 @@ void output_init_i2s() {
|
||||
// create task as a FreeRTOS task but uses stack in internal RAM
|
||||
{
|
||||
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__((aligned(4)));
|
||||
static EXT_RAM_ATTR StackType_t xStack[OUTPUT_THREAD_STACK_SIZE]
|
||||
__attribute__((aligned(4)));
|
||||
output_i2s_task = xTaskCreateStaticPinnedToCore((TaskFunction_t)output_thread_i2s,
|
||||
"output_i2s", OUTPUT_THREAD_STACK_SIZE, NULL,
|
||||
static EXT_RAM_ATTR StackType_t xStack[OUTPUT_THREAD_STACK_SIZE] __attribute__((aligned(4)));
|
||||
output_i2s_task = xTaskCreateStaticPinnedToCore((TaskFunction_t)output_thread_i2s, "output_i2s", OUTPUT_THREAD_STACK_SIZE, NULL,
|
||||
CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, xStack, &xTaskBuffer, 0);
|
||||
}
|
||||
}
|
||||
@@ -439,17 +459,15 @@ void output_close_i2s(void) {
|
||||
* change volume
|
||||
*/
|
||||
bool output_volume_i2s(unsigned left, unsigned right) {
|
||||
if (mute_control.gpio >= 0)
|
||||
gpio_set_level(
|
||||
mute_control.gpio, (left | right) ? !mute_control.active : mute_control.active);
|
||||
if (mute_control.gpio >= 0) gpio_set_level(mute_control.gpio, (left | right) ? !mute_control.active : mute_control.active);
|
||||
return adac->volume(left, right);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Write frames to the output buffer
|
||||
*/
|
||||
static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
|
||||
u8_t flags, s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T** cross_ptr) {
|
||||
static int _i2s_write_frames(
|
||||
frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, u8_t flags, s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T** cross_ptr) {
|
||||
if (!silence) {
|
||||
if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && *cross_ptr) {
|
||||
_apply_cross(outputbuf, out_frames, cross_gain_in, cross_gain_out, cross_ptr);
|
||||
@@ -463,8 +481,7 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
|
||||
|
||||
// don't update visu if we don't have enough data in buffer
|
||||
if (silence || output.external || _buf_used(outputbuf) > outputbuf->size >> 2) {
|
||||
output_visu_export(obuf + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate,
|
||||
silence, (gainL + gainR) / 2);
|
||||
output_visu_export(obuf + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
|
||||
}
|
||||
|
||||
oframes += out_frames;
|
||||
@@ -503,8 +520,7 @@ static void output_thread_i2s(void* arg) {
|
||||
led_blink(LED_GREEN, 200, 1000);
|
||||
} else if (output.state == OUTPUT_RUNNING) {
|
||||
if (!jack_mutes_amp || !jack_inserted_svc()) {
|
||||
if (amp_control.gpio != -1)
|
||||
gpio_set_level_x(amp_control.gpio, amp_control.active);
|
||||
if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, amp_control.active);
|
||||
adac->speaker(true);
|
||||
}
|
||||
led_on(LED_GREEN);
|
||||
@@ -530,8 +546,7 @@ static void output_thread_i2s(void* arg) {
|
||||
output.frames_played_dmp = output.frames_played;
|
||||
// try to estimate how much we have consumed from the DMA buffer (calculation is incorrect
|
||||
// at the very beginning ...)
|
||||
output.device_frames =
|
||||
dma_buf_frames - ((output.updated - fullness) * output.current_sample_rate) / 1000;
|
||||
output.device_frames = dma_buf_frames - ((output.updated - fullness) * output.current_sample_rate) / 1000;
|
||||
// we'll try to produce iframes if we have any, but we might return less if outpuf does not
|
||||
// have enough
|
||||
_output_frames(iframes);
|
||||
@@ -572,8 +587,7 @@ static void output_thread_i2s(void* arg) {
|
||||
|
||||
// this does not work well as set_sample_rates resets the fifos (and it's too early)
|
||||
if (i2s_config.sample_rate != output.current_sample_rate) {
|
||||
LOG_INFO("changing sampling rate %u to %u", i2s_config.sample_rate,
|
||||
output.current_sample_rate);
|
||||
LOG_INFO("changing sampling rate %u to %u", i2s_config.sample_rate, output.current_sample_rate);
|
||||
if (synced) {
|
||||
/*
|
||||
// can sleep for a buffer_queue - 1 and then eat a buffer (discard) if we are
|
||||
@@ -582,8 +596,7 @@ static void output_thread_i2s(void* arg) {
|
||||
*/
|
||||
}
|
||||
i2s_config.sample_rate = output.current_sample_rate;
|
||||
i2s_set_sample_rates(CONFIG_I2S_NUM,
|
||||
spdif.enabled ? i2s_config.sample_rate * 2 : i2s_config.sample_rate);
|
||||
i2s_set_sample_rates(CONFIG_I2S_NUM, spdif.enabled ? i2s_config.sample_rate * 2 : i2s_config.sample_rate);
|
||||
i2s_zero_dma_buffer(CONFIG_I2S_NUM);
|
||||
|
||||
equalizer_set_samplerate(output.current_sample_rate);
|
||||
@@ -607,8 +620,7 @@ static void output_thread_i2s(void* arg) {
|
||||
}
|
||||
#if BYTES_PER_FRAME == 4
|
||||
} else if (i2s_config.bits_per_sample == 32) {
|
||||
i2s_write_expand(
|
||||
CONFIG_I2S_NUM, obuf, oframes * BYTES_PER_FRAME, 16, 32, &bytes, portMAX_DELAY);
|
||||
i2s_write_expand(CONFIG_I2S_NUM, obuf, oframes * BYTES_PER_FRAME, 16, 32, &bytes, portMAX_DELAY);
|
||||
#endif
|
||||
} else {
|
||||
i2s_write(CONFIG_I2S_NUM, obuf, oframes * BYTES_PER_FRAME, &bytes, portMAX_DELAY);
|
||||
@@ -617,8 +629,7 @@ static void output_thread_i2s(void* arg) {
|
||||
fullness = gettime_ms();
|
||||
|
||||
if (bytes != oframes * BYTES_PER_FRAME) {
|
||||
LOG_WARN("I2S DMA Overflow! available bytes: %d, I2S wrote %d bytes",
|
||||
oframes * BYTES_PER_FRAME, bytes);
|
||||
LOG_WARN("I2S DMA Overflow! available bytes: %d, I2S wrote %d bytes", oframes * BYTES_PER_FRAME, bytes);
|
||||
}
|
||||
|
||||
SET_MIN_MAX(TIME_MEASUREMENT_GET(timer_start), i2s_time);
|
||||
@@ -643,8 +654,7 @@ static void i2s_stats(uint32_t now) {
|
||||
if (output.state <= OUTPUT_STOPPED || now < last + STATS_PERIOD_MS) return;
|
||||
last = now;
|
||||
|
||||
LOG_INFO("Output State: %d, current sample rate: %d, bytes per frame: %d", output.state,
|
||||
output.current_sample_rate, BYTES_PER_FRAME);
|
||||
LOG_INFO("Output State: %d, current sample rate: %d, bytes per frame: %d", output.state, output.current_sample_rate, BYTES_PER_FRAME);
|
||||
LOG_INFO(LINE_MIN_MAX_FORMAT_HEAD1);
|
||||
LOG_INFO(LINE_MIN_MAX_FORMAT_HEAD2);
|
||||
LOG_INFO(LINE_MIN_MAX_FORMAT_HEAD3);
|
||||
@@ -673,27 +683,20 @@ static void i2s_stats(uint32_t now) {
|
||||
|
||||
static const u8_t VUCP24[2] = {0xCC, 0x32};
|
||||
|
||||
static const u16_t spdif_bmclookup[256] = {0xcccc, 0xb333, 0xd333, 0xaccc, 0xcb33, 0xb4cc, 0xd4cc,
|
||||
0xab33, 0xcd33, 0xb2cc, 0xd2cc, 0xad33, 0xcacc, 0xb533, 0xd533, 0xaacc, 0xccb3, 0xb34c, 0xd34c,
|
||||
0xacb3, 0xcb4c, 0xb4b3, 0xd4b3, 0xab4c, 0xcd4c, 0xb2b3, 0xd2b3, 0xad4c, 0xcab3, 0xb54c, 0xd54c,
|
||||
0xaab3, 0xccd3, 0xb32c, 0xd32c, 0xacd3, 0xcb2c, 0xb4d3, 0xd4d3, 0xab2c, 0xcd2c, 0xb2d3, 0xd2d3,
|
||||
0xad2c, 0xcad3, 0xb52c, 0xd52c, 0xaad3, 0xccac, 0xb353, 0xd353, 0xacac, 0xcb53, 0xb4ac, 0xd4ac,
|
||||
0xab53, 0xcd53, 0xb2ac, 0xd2ac, 0xad53, 0xcaac, 0xb553, 0xd553, 0xaaac, 0xcccb, 0xb334, 0xd334,
|
||||
0xaccb, 0xcb34, 0xb4cb, 0xd4cb, 0xab34, 0xcd34, 0xb2cb, 0xd2cb, 0xad34, 0xcacb, 0xb534, 0xd534,
|
||||
0xaacb, 0xccb4, 0xb34b, 0xd34b, 0xacb4, 0xcb4b, 0xb4b4, 0xd4b4, 0xab4b, 0xcd4b, 0xb2b4, 0xd2b4,
|
||||
0xad4b, 0xcab4, 0xb54b, 0xd54b, 0xaab4, 0xccd4, 0xb32b, 0xd32b, 0xacd4, 0xcb2b, 0xb4d4, 0xd4d4,
|
||||
0xab2b, 0xcd2b, 0xb2d4, 0xd2d4, 0xad2b, 0xcad4, 0xb52b, 0xd52b, 0xaad4, 0xccab, 0xb354, 0xd354,
|
||||
0xacab, 0xcb54, 0xb4ab, 0xd4ab, 0xab54, 0xcd54, 0xb2ab, 0xd2ab, 0xad54, 0xcaab, 0xb554, 0xd554,
|
||||
0xaaab, 0xcccd, 0xb332, 0xd332, 0xaccd, 0xcb32, 0xb4cd, 0xd4cd, 0xab32, 0xcd32, 0xb2cd, 0xd2cd,
|
||||
0xad32, 0xcacd, 0xb532, 0xd532, 0xaacd, 0xccb2, 0xb34d, 0xd34d, 0xacb2, 0xcb4d, 0xb4b2, 0xd4b2,
|
||||
0xab4d, 0xcd4d, 0xb2b2, 0xd2b2, 0xad4d, 0xcab2, 0xb54d, 0xd54d, 0xaab2, 0xccd2, 0xb32d, 0xd32d,
|
||||
0xacd2, 0xcb2d, 0xb4d2, 0xd4d2, 0xab2d, 0xcd2d, 0xb2d2, 0xd2d2, 0xad2d, 0xcad2, 0xb52d, 0xd52d,
|
||||
0xaad2, 0xccad, 0xb352, 0xd352, 0xacad, 0xcb52, 0xb4ad, 0xd4ad, 0xab52, 0xcd52, 0xb2ad, 0xd2ad,
|
||||
0xad52, 0xcaad, 0xb552, 0xd552, 0xaaad, 0xccca, 0xb335, 0xd335, 0xacca, 0xcb35, 0xb4ca, 0xd4ca,
|
||||
0xab35, 0xcd35, 0xb2ca, 0xd2ca, 0xad35, 0xcaca, 0xb535, 0xd535, 0xaaca, 0xccb5, 0xb34a, 0xd34a,
|
||||
0xacb5, 0xcb4a, 0xb4b5, 0xd4b5, 0xab4a, 0xcd4a, 0xb2b5, 0xd2b5, 0xad4a, 0xcab5, 0xb54a, 0xd54a,
|
||||
0xaab5, 0xccd5, 0xb32a, 0xd32a, 0xacd5, 0xcb2a, 0xb4d5, 0xd4d5, 0xab2a, 0xcd2a, 0xb2d5, 0xd2d5,
|
||||
0xad2a, 0xcad5, 0xb52a, 0xd52a, 0xaad5, 0xccaa, 0xb355, 0xd355, 0xacaa, 0xcb55, 0xb4aa, 0xd4aa,
|
||||
static const u16_t spdif_bmclookup[256] = {0xcccc, 0xb333, 0xd333, 0xaccc, 0xcb33, 0xb4cc, 0xd4cc, 0xab33, 0xcd33, 0xb2cc, 0xd2cc, 0xad33, 0xcacc,
|
||||
0xb533, 0xd533, 0xaacc, 0xccb3, 0xb34c, 0xd34c, 0xacb3, 0xcb4c, 0xb4b3, 0xd4b3, 0xab4c, 0xcd4c, 0xb2b3, 0xd2b3, 0xad4c, 0xcab3, 0xb54c, 0xd54c,
|
||||
0xaab3, 0xccd3, 0xb32c, 0xd32c, 0xacd3, 0xcb2c, 0xb4d3, 0xd4d3, 0xab2c, 0xcd2c, 0xb2d3, 0xd2d3, 0xad2c, 0xcad3, 0xb52c, 0xd52c, 0xaad3, 0xccac,
|
||||
0xb353, 0xd353, 0xacac, 0xcb53, 0xb4ac, 0xd4ac, 0xab53, 0xcd53, 0xb2ac, 0xd2ac, 0xad53, 0xcaac, 0xb553, 0xd553, 0xaaac, 0xcccb, 0xb334, 0xd334,
|
||||
0xaccb, 0xcb34, 0xb4cb, 0xd4cb, 0xab34, 0xcd34, 0xb2cb, 0xd2cb, 0xad34, 0xcacb, 0xb534, 0xd534, 0xaacb, 0xccb4, 0xb34b, 0xd34b, 0xacb4, 0xcb4b,
|
||||
0xb4b4, 0xd4b4, 0xab4b, 0xcd4b, 0xb2b4, 0xd2b4, 0xad4b, 0xcab4, 0xb54b, 0xd54b, 0xaab4, 0xccd4, 0xb32b, 0xd32b, 0xacd4, 0xcb2b, 0xb4d4, 0xd4d4,
|
||||
0xab2b, 0xcd2b, 0xb2d4, 0xd2d4, 0xad2b, 0xcad4, 0xb52b, 0xd52b, 0xaad4, 0xccab, 0xb354, 0xd354, 0xacab, 0xcb54, 0xb4ab, 0xd4ab, 0xab54, 0xcd54,
|
||||
0xb2ab, 0xd2ab, 0xad54, 0xcaab, 0xb554, 0xd554, 0xaaab, 0xcccd, 0xb332, 0xd332, 0xaccd, 0xcb32, 0xb4cd, 0xd4cd, 0xab32, 0xcd32, 0xb2cd, 0xd2cd,
|
||||
0xad32, 0xcacd, 0xb532, 0xd532, 0xaacd, 0xccb2, 0xb34d, 0xd34d, 0xacb2, 0xcb4d, 0xb4b2, 0xd4b2, 0xab4d, 0xcd4d, 0xb2b2, 0xd2b2, 0xad4d, 0xcab2,
|
||||
0xb54d, 0xd54d, 0xaab2, 0xccd2, 0xb32d, 0xd32d, 0xacd2, 0xcb2d, 0xb4d2, 0xd4d2, 0xab2d, 0xcd2d, 0xb2d2, 0xd2d2, 0xad2d, 0xcad2, 0xb52d, 0xd52d,
|
||||
0xaad2, 0xccad, 0xb352, 0xd352, 0xacad, 0xcb52, 0xb4ad, 0xd4ad, 0xab52, 0xcd52, 0xb2ad, 0xd2ad, 0xad52, 0xcaad, 0xb552, 0xd552, 0xaaad, 0xccca,
|
||||
0xb335, 0xd335, 0xacca, 0xcb35, 0xb4ca, 0xd4ca, 0xab35, 0xcd35, 0xb2ca, 0xd2ca, 0xad35, 0xcaca, 0xb535, 0xd535, 0xaaca, 0xccb5, 0xb34a, 0xd34a,
|
||||
0xacb5, 0xcb4a, 0xb4b5, 0xd4b5, 0xab4a, 0xcd4a, 0xb2b5, 0xd2b5, 0xad4a, 0xcab5, 0xb54a, 0xd54a, 0xaab5, 0xccd5, 0xb32a, 0xd32a, 0xacd5, 0xcb2a,
|
||||
0xb4d5, 0xd4d5, 0xab2a, 0xcd2a, 0xb2d5, 0xd2d5, 0xad2a, 0xcad5, 0xb52a, 0xd52a, 0xaad5, 0xccaa, 0xb355, 0xd355, 0xacaa, 0xcb55, 0xb4aa, 0xd4aa,
|
||||
0xab55, 0xcd55, 0xb2aa, 0xd2aa, 0xad55, 0xcaaa, 0xb555, 0xd555, 0xaaaa};
|
||||
|
||||
/*
|
||||
|
||||
@@ -39,13 +39,13 @@
|
||||
|
||||
static const char TAG[] = "TAS5713";
|
||||
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck);
|
||||
static bool init(sys_dac_config *config, i2s_config_t *i2s_config, bool *mck);
|
||||
static void speaker(bool active) { };
|
||||
static void headset(bool active) { } ;
|
||||
static bool volume(unsigned left, unsigned right);
|
||||
static void power(adac_power_e mode) { };
|
||||
|
||||
const struct adac_s dac_tas5713 = {sys_DACModelEnum_TAS5713, init, adac_deinit, power, speaker, headset, volume};
|
||||
const struct adac_s dac_tas5713 = {sys_dac_models_TAS5713, init, adac_deinit, power, speaker, headset, volume};
|
||||
|
||||
struct tas5713_cmd_s {
|
||||
uint8_t reg;
|
||||
@@ -65,9 +65,9 @@ typedef enum {
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static bool init(char *config, int i2c_port, i2s_config_t *i2s_config, bool *mck) {
|
||||
static bool init(sys_dac_config *config, i2s_config_t *i2s_config, bool *mck) {
|
||||
/* find if there is a tas5713 attached. Reg 0 should read non-zero but not 255 if so */
|
||||
adac_init(config, i2c_port);
|
||||
adac_init(config);
|
||||
if (adac_read_byte(TAS5713, 0x00) == 255) {
|
||||
ESP_LOGW(TAG, "No TAS5713 detected");
|
||||
adac_deinit();
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
|
||||
static const char TAG[] = "TAS575x/8x";
|
||||
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck);
|
||||
static bool init(sys_dac_config *config, i2s_config_t *i2s_config, bool *mck);
|
||||
static void speaker(bool active);
|
||||
static void headset(bool active);
|
||||
static bool volume(unsigned left, unsigned right);
|
||||
static void power(adac_power_e mode);
|
||||
|
||||
const struct adac_s dac_tas57xx = { sys_DACModelEnum_TAS57xx, init, adac_deinit, power, speaker, headset, volume };
|
||||
const struct adac_s dac_tas57xx = { sys_dac_models_TAS57xx, init, adac_deinit, power, speaker, headset, volume };
|
||||
|
||||
struct tas57xx_cmd_s {
|
||||
uint8_t reg;
|
||||
@@ -71,9 +71,9 @@ static int tas57_detect(void);
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static bool init(char *config, int i2c_port, i2s_config_t *i2s_config, bool *mck) {
|
||||
static bool init(sys_dac_config *config, i2s_config_t *i2s_config, bool *mck) {
|
||||
// find which TAS we are using (if any)
|
||||
tas57_addr = adac_init(config, i2c_port);
|
||||
tas57_addr = adac_init(config);
|
||||
if (!tas57_addr) tas57_addr = tas57_detect();
|
||||
|
||||
if (!tas57_addr) {
|
||||
@@ -93,7 +93,7 @@ static bool init(char *config, int i2c_port, i2s_config_t *i2s_config, bool *mck
|
||||
}
|
||||
|
||||
i2c_master_stop(i2c_cmd);
|
||||
esp_err_t res = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_RATE_MS);
|
||||
esp_err_t res = i2c_master_cmd_begin(config->i2c.port-sys_i2c_port_PORT0, i2c_cmd, 500 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
|
||||
if (res != ESP_OK) {
|
||||
|
||||
@@ -23,14 +23,14 @@ static void speaker(bool active) { }
|
||||
static void headset(bool active) { }
|
||||
static bool volume(unsigned left, unsigned right) { return false; }
|
||||
static void power(adac_power_e mode);
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck);
|
||||
static bool init(sys_dac_config *config, i2s_config_t *i2s_config, bool *mck);
|
||||
|
||||
static esp_err_t i2c_write_shadow(uint8_t reg, uint16_t val);
|
||||
static uint16_t i2c_read_shadow(uint8_t reg);
|
||||
|
||||
static int WM8978;
|
||||
|
||||
const struct adac_s dac_wm8978 = { sys_DACModelEnum_WM8978, init, adac_deinit, power, speaker, headset, volume };
|
||||
const struct adac_s dac_wm8978 = { sys_dac_models_WM8978, init, adac_deinit, power, speaker, headset, volume };
|
||||
|
||||
// initiation table for non-readbale 9-bit i2c registers
|
||||
static uint16_t WM8978_REGVAL_TBL[58] = {
|
||||
@@ -47,8 +47,8 @@ static uint16_t WM8978_REGVAL_TBL[58] = {
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static bool init(char *config, int i2c_port, i2s_config_t *i2s_config, bool *mck) {
|
||||
WM8978 = adac_init(config, i2c_port);
|
||||
static bool init(sys_dac_config *config, i2s_config_t *i2s_config, bool *mck) {
|
||||
WM8978 = adac_init(config);
|
||||
|
||||
if (!WM8978) WM8978 = 0x1a;
|
||||
ESP_LOGI(TAG, "WM8978 detected @%d", WM8978);
|
||||
|
||||
Reference in New Issue
Block a user