Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions components/lightsnapcast/include/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,20 @@ typedef struct snapcastSetting_s {
i2s_data_bit_width_t bits;

bool muted;
uint32_t volume;

char *pcmBuf;
uint32_t pcmBufSize;
} snapcastSetting_t;

int init_player(i2s_std_gpio_config_t pin_config0_, i2s_port_t i2sNum_);
typedef enum { IDLE = 0, PLAYING, PAUSED } player_state_e;
int init_player(i2s_std_gpio_config_t pin_config0_, i2s_port_t i2sNum_, void (*set_mute_cb)(bool, bool));
int deinit_player(void);
int start_player(snapcastSetting_t *setting);
void pause_player(bool pause);

void call_state_cb(void);
void add_player_state_cb(void (*cb)(void));
player_state_e get_player_state(void);

int32_t allocate_pcm_chunk_memory(pcm_chunk_message_t **pcmChunk, size_t bytes);
int32_t insert_pcm_chunk(pcm_chunk_message_t *pcmChunk);
Expand Down
168 changes: 122 additions & 46 deletions components/lightsnapcast/player.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,20 @@ static bool gpTimerRunning = false;

static void player_task(void *pvParameters);

//player state
bool gotSettings = false;
bool playerstarted = false;
bool playerStarted = false;
bool playerPaused = false;
static SemaphoreHandle_t playerStateMux = NULL;

extern void audio_set_mute(bool mute);
extern void audio_dac_enable(bool enabled);
typedef struct state_cb_s {
void (*cb)(void);
struct state_cb_s *next;
} state_cb_t;

static state_cb_t *state_cb_head = NULL;

static void (*audio_set_mute)(bool mute, bool set_state);

static i2s_chan_handle_t tx_chan = NULL; // I2S tx channel handler
static bool i2sEnabled = false;
Expand Down Expand Up @@ -390,17 +399,20 @@ int deinit_player(void) {

// must disable i2s before stopping player task or it will hang
my_i2s_channel_disable(tx_chan);


if (playerStateMux != NULL) {
xSemaphoreTake(playerStateMux, pdMS_TO_TICKS(10000));
}
//wait max 10s for task to stop itself
for(int i = 0; i< 100; i++) {
if (playerstarted) {
if (playerStarted) {
vTaskDelay(pdMS_TO_TICKS(100));
} else {
break;
}
}

// stop the task f still running
// stop the task if still running
if (playerTaskHandle != NULL) {
vTaskDelete(playerTaskHandle);
playerTaskHandle = NULL;
Expand All @@ -411,6 +423,10 @@ int deinit_player(void) {
tx_chan = NULL;
}

if (playerStateMux != NULL) {
vSemaphoreDelete(playerStateMux);
playerStateMux = NULL;
}
if (snapcastSettingsMux != NULL) {
vSemaphoreDelete(snapcastSettingsMux);
snapcastSettingsMux = NULL;
Expand Down Expand Up @@ -445,8 +461,9 @@ int deinit_player(void) {
/**
* call before http task creation!
*/
int init_player(i2s_std_gpio_config_t pin_config0_, i2s_port_t i2sNum_) {
int init_player(i2s_std_gpio_config_t pin_config0_, i2s_port_t i2sNum_, void (*set_mute_cb)(bool, bool)) {
int ret = 0;
audio_set_mute = set_mute_cb;

deinit_player();

Expand All @@ -459,14 +476,17 @@ int init_player(i2s_std_gpio_config_t pin_config0_, i2s_port_t i2sNum_) {
currentSnapcastSetting.sr = 0;
currentSnapcastSetting.ch = 0;
currentSnapcastSetting.bits = 0;
currentSnapcastSetting.muted = true;
currentSnapcastSetting.volume = 0;

if (snapcastSettingsMux == NULL) {
snapcastSettingsMux = xSemaphoreCreateMutex();
xSemaphoreGive(snapcastSettingsMux);
}

if (playerStateMux == NULL) {
playerStateMux = xSemaphoreCreateMutex();
xSemaphoreGive(playerStateMux);
}

/**
ret = player_setup_i2s(&currentSnapcastSetting);
if (ret < 0) {
Expand Down Expand Up @@ -516,18 +536,25 @@ int init_player(i2s_std_gpio_config_t pin_config0_, i2s_port_t i2sNum_) {
* call to start the player task
*/
int start_player(snapcastSetting_t *setting) {
if (playerstarted){
return -1;
}
playerstarted = true;
if (xSemaphoreTake(playerStateMux, 0) != pdTRUE) {
// shutdown in progress, don't start player
return -1;
}
if (playerStarted || playerPaused){
xSemaphoreGive(playerStateMux);
return -1;
}
playerStarted = true;
int ret = 0;

ret = player_setup_i2s(setting);
if (ret < 0) {
ESP_LOGE(TAG, "player_setup_i2s failed: %d", ret);
playerstarted = false;
playerStarted = false;
xSemaphoreGive(playerStateMux);
return -1;
}
xSemaphoreGive(playerStateMux);

tg0_timer_init();

Expand Down Expand Up @@ -574,12 +601,63 @@ int start_player(snapcastSetting_t *setting) {
SYNC_TASK_PRIORITY, &playerTaskHandle,
SYNC_TASK_CORE_ID);


call_state_cb();
ESP_LOGI(TAG, "start player done");

return 0;
}

void pause_player(bool pause) {
xSemaphoreTake(playerStateMux, portMAX_DELAY);
if (pause != playerPaused) {
playerPaused = pause;
if (pause && playerTaskHandle != NULL) {
xTaskNotifyGiveIndexed(playerTaskHandle, 1);
Comment thread
luar123 marked this conversation as resolved.
Outdated
}
if (!pause) {
call_state_cb(); // notify state change, e.g. for http task to send pcm
}
}
xSemaphoreGive(playerStateMux);
}

player_state_e get_player_state(void) {
xSemaphoreTake(playerStateMux, portMAX_DELAY);
player_state_e state = IDLE;
if (playerPaused) {
state = PAUSED;
} else if (playerStarted) {
state = PLAYING;
}
xSemaphoreGive(playerStateMux);
return state;
}

void call_state_cb(void) {
state_cb_t *current = state_cb_head;
while (current != NULL) {
if (current->cb != NULL) {
current->cb();
}
current = current->next;
}
}

/**
* add callback to be called when player state changes, e.g. from not started to started.
* Callbacks needs to be implemented thread safe as they will be called from player task
*/
void add_player_state_cb(void (*cb)()) {
state_cb_t *new_cb = malloc(sizeof(state_cb_t));
if (new_cb == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for state callback");
return;
}
new_cb->cb = cb;
new_cb->next = state_cb_head;
state_cb_head = new_cb;
}

/**
*
*/
Expand Down Expand Up @@ -681,8 +759,7 @@ int32_t player_send_snapcast_setting(snapcastSetting_t *setting) {
if ((curSet.bits != setting->bits) || (curSet.buf_ms != setting->buf_ms) ||
(curSet.ch != setting->ch) ||
(curSet.chkInFrames != setting->chkInFrames) ||
(curSet.codec != setting->codec) || (curSet.muted != setting->muted) ||
(curSet.sr != setting->sr) || (curSet.volume != setting->volume) ||
(curSet.codec != setting->codec) || (curSet.sr != setting->sr) ||
(curSet.cDacLat_ms != setting->cDacLat_ms)) {
ret = player_set_snapcast_settings(setting);
if (ret != pdPASS) {
Expand All @@ -691,16 +768,7 @@ int32_t player_send_snapcast_setting(snapcastSetting_t *setting) {
"snapcast setting");
}

// check if it is only volume / mute related setting, which is handled by
// http_get_task()
// if ((((curSet.muted != setting->muted) ||
//(curSet.volume != setting->volume)) &&
//((curSet.bits == setting->bits) &&
//(curSet.buf_ms == setting->buf_ms) && (curSet.ch == setting->ch) &&
//(curSet.chkInFrames == setting->chkInFrames) &&
//(curSet.codec == setting->codec) && (curSet.sr == setting->sr) &&
//(curSet.cDacLat_ms == setting->cDacLat_ms))) == false) {
// notify needed
// notify needed
if ((playerTaskHandle != NULL) && (snapcastSettingQueueHandle != NULL)) {
ret = xQueueOverwrite(snapcastSettingQueueHandle, &settingChanged);
if (ret != pdPASS) {
Expand Down Expand Up @@ -1354,14 +1422,18 @@ int32_t insert_pcm_chunk(pcm_chunk_message_t *pcmChunk) {
return -1;
}

if (playerPaused) {
free_pcm_chunk(pcmChunk);
return 0;
}
if (pcmChkQHdl == NULL) {
ESP_LOGW(TAG, "pcm chunk queue not created. Player started: %s", playerstarted ? "True": "False");
ESP_LOGW(TAG, "pcm chunk queue not created. Player started: %s", playerStarted ? "True": "False");

free_pcm_chunk(pcmChunk);

snapcastSetting_t curSet;
player_get_snapcast_settings(&curSet);
if (!curSet.muted && gotSettings) {
if (gotSettings) {
start_player(&curSet);
}

Expand Down Expand Up @@ -1457,7 +1529,7 @@ static void player_task(void *pvParameters) {

//audio_hal_ctrl_codec(audio_hal_handle_t audio_hal, audio_hal_codec_mode_t mode, audio_hal_ctrl_t audio_hal_ctrl)

audio_set_mute(true);
audio_set_mute(true, false);
Comment thread
luar123 marked this conversation as resolved.
Outdated

buf_us = (int64_t)(scSet.buf_ms) * 1000LL;
clientDacLatency_us = (int64_t)scSet.cDacLat_ms * 1000LL;
Expand All @@ -1483,7 +1555,7 @@ static void player_task(void *pvParameters) {
//
// ESP_LOGI(TAG, "created new queue with %d", entries);
// }
audio_set_mute(scSet.muted);
audio_set_mute(false, false);
Comment thread
luar123 marked this conversation as resolved.
Outdated

// wait for early time syncs to be ready
xSemaphoreTake(latencyBufFullSemaphoreHandle, portMAX_DELAY);
Expand Down Expand Up @@ -1518,7 +1590,7 @@ static void player_task(void *pvParameters) {
if ((scSet.sr != __scSet.sr) || (scSet.bits != __scSet.bits) ||
(scSet.ch != __scSet.ch)) {
my_i2s_channel_enable(tx_chan);
audio_set_mute(true);
audio_set_mute(true, false);
my_i2s_channel_disable(tx_chan);

ret = player_setup_i2s(&__scSet);
Expand Down Expand Up @@ -1564,13 +1636,11 @@ static void player_task(void *pvParameters) {
(scSet.ch != __scSet.ch) || (scSet.buf_ms != __scSet.buf_ms)) {
ESP_LOGI(TAG,
"snapserver config changed, buffer %ldms, chunk %ld frames, "
"sample rate %ld, ch %d, bits %d mute %d latency %ld",
"sample rate %ld, ch %d, bits %d latency %ld",
__scSet.buf_ms, __scSet.chkInFrames, __scSet.sr, __scSet.ch,
__scSet.bits, __scSet.muted, __scSet.cDacLat_ms);
} else {
audio_set_mute(__scSet.muted);
ESP_LOGI(TAG, "snapserver config changed, mute: %d", __scSet.muted);
__scSet.bits, __scSet.cDacLat_ms);
}
audio_set_mute(false, false);

scSet = __scSet; // store for next round

Expand Down Expand Up @@ -1702,8 +1772,6 @@ static void player_task(void *pvParameters) {

my_gptimer_stop(gptimer);

audio_dac_enable(true);

my_i2s_channel_enable(tx_chan);

playback_start_time_us = esp_timer_get_time();
Expand All @@ -1719,7 +1787,7 @@ static void player_task(void *pvParameters) {

// TODO: use a timer to un-mute non blocking
vTaskDelay(pdMS_TO_TICKS(2));
audio_set_mute(scSet.muted);
audio_set_mute(false, false);

ESP_LOGI(TAG, "initial sync age: %lldus, chunk duration: %lldus", age,
chunkDuration_us);
Expand Down Expand Up @@ -1764,7 +1832,7 @@ static void player_task(void *pvParameters) {

insertedSamplesCounter = 0;

audio_set_mute(true);
audio_set_mute(true, false);

my_i2s_channel_disable(tx_chan);

Expand Down Expand Up @@ -2020,7 +2088,7 @@ static void player_task(void *pvParameters) {

my_gptimer_stop(gptimer);

audio_set_mute(true);
audio_set_mute(true, false);

my_i2s_channel_disable(tx_chan);

Expand Down Expand Up @@ -2110,17 +2178,23 @@ static void player_task(void *pvParameters) {
dir = 0;
initialSync = 0;

audio_set_mute(true);
audio_dac_enable(false);
audio_set_mute(true, false);
my_i2s_channel_disable(tx_chan);
i2s_del_channel(tx_chan);
tx_chan = NULL;

break;
}
if (ulTaskNotifyTakeIndexed(1, pdTRUE, 0) == pdTRUE) {
audio_set_mute(true, false);
my_i2s_channel_disable(tx_chan);
i2s_del_channel(tx_chan);
tx_chan = NULL;
break;
}
}
ret = 0;

xSemaphoreTake(playerStateMux, portMAX_DELAY);
xSemaphoreTake(snapcastSettingsMux, portMAX_DELAY);
// delete the queue
vQueueDelete(snapcastSettingQueueHandle);
Expand All @@ -2134,8 +2208,10 @@ static void player_task(void *pvParameters) {
ret = destroy_pcm_queue(&pcmChkQHdl);

tg0_timer_deinit();
playerstarted = false;
playerStarted = false;
call_state_cb();
ESP_LOGI(TAG, "stop player done");
playerTaskHandle = NULL;
xSemaphoreGive(playerStateMux);
vTaskDelete(NULL);
}
Loading