Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
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
8 changes: 7 additions & 1 deletion components/lightsnapcast/include/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,15 @@ typedef struct snapcastSetting_s {
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));
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
175 changes: 125 additions & 50 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);

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

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


//don't take playerStateMux here, otherwise we might block player task from stopping
//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 +425,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 +463,13 @@ 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)) {
int ret = 0;
if (set_mute_cb == NULL) {
ESP_LOGE(TAG, "set_mute_cb is NULL");
return -1;
}
audio_set_mute = set_mute_cb;

deinit_player();

Expand All @@ -459,14 +482,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 +542,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 +607,65 @@ 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;
xSemaphoreGive(playerStateMux);
if (pause && playerTaskHandle != NULL) {
xTaskNotifyGiveIndexed(playerTaskHandle, 1);
Comment thread
luar123 marked this conversation as resolved.
Outdated
}
else {
call_state_cb(); // notify state change, e.g. for http task to send pcm
}
} else {
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 +767,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 +776,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 +1430,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 @@ -1483,7 +1563,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);

// wait for early time syncs to be ready
xSemaphoreTake(latencyBufFullSemaphoreHandle, portMAX_DELAY);
Expand Down Expand Up @@ -1564,13 +1644,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);

scSet = __scSet; // store for next round

Expand Down Expand Up @@ -1702,8 +1780,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 +1795,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);

ESP_LOGI(TAG, "initial sync age: %lldus, chunk duration: %lldus", age,
chunkDuration_us);
Expand Down Expand Up @@ -2106,21 +2182,18 @@ static void player_task(void *pvParameters) {
"diff2Server: %llds, %lld.%lldms",
uxQueueMessagesWaiting(pcmChkQHdl), sec, msec, usec);
}

dir = 0;
initialSync = 0;

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

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

xSemaphoreTake(playerStateMux, portMAX_DELAY);
xSemaphoreTake(snapcastSettingsMux, portMAX_DELAY);
// delete the queue
vQueueDelete(snapcastSettingQueueHandle);
Expand All @@ -2134,8 +2207,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);
}
2 changes: 0 additions & 2 deletions components/lightsnapcast/snapcast_protocol_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,5 @@ parser_return_state_t parser_skip_typed_message(snapcast_protocol_parser_t* pars
if (!read_byte(parser, &dummy_byte)) return PARSER_RESTART_CONNECTION;
}

ESP_LOGI(TAG, "done skipping typed message %d", base_message_rx->type);

return PARSER_OK;
}
Loading