Files
snapclient/components/lightsnapcast/snapcast.c
Carlos 7b24787e73 - major change, use lwip netconn instead of socket API to reduce RAM footprint
o flac works, opus and pcm are untested and not working
- improve on resyncing issues
- add wifi logger component submodule
- optimize stack sizes
2021-10-16 22:07:37 +02:00

377 lines
8.2 KiB
C

#include "snapcast.h"
//#ifdef ESP_PLATFORM
// The ESP-IDF changes the include directory for cJSON
#include <cJSON.h>
//#else
//#include "json/cJSON.h"
//#endif
#include <buffer.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "esp_heap_caps.h"
#include "esp_log.h"
/* Logging tag */
static const char *TAG = "libSNAPCAST";
int
base_message_serialize (base_message_t *msg, char *data, uint32_t size)
{
write_buffer_t buffer;
int result = 0;
buffer_write_init (&buffer, data, size);
result |= buffer_write_uint16 (&buffer, msg->type);
result |= buffer_write_uint16 (&buffer, msg->id);
result |= buffer_write_uint16 (&buffer, msg->refersTo);
result |= buffer_write_int32 (&buffer, msg->sent.sec);
result |= buffer_write_int32 (&buffer, msg->sent.usec);
result |= buffer_write_int32 (&buffer, msg->received.sec);
result |= buffer_write_int32 (&buffer, msg->received.usec);
result |= buffer_write_uint32 (&buffer, msg->size);
return result;
}
int
base_message_deserialize (base_message_t *msg, const char *data, uint32_t size)
{
read_buffer_t buffer;
int result = 0;
buffer_read_init (&buffer, data, size);
result |= buffer_read_uint16 (&buffer, &(msg->type));
result |= buffer_read_uint16 (&buffer, &(msg->id));
result |= buffer_read_uint16 (&buffer, &(msg->refersTo));
result |= buffer_read_int32 (&buffer, &(msg->sent.sec));
result |= buffer_read_int32 (&buffer, &(msg->sent.usec));
result |= buffer_read_int32 (&buffer, &(msg->received.sec));
result |= buffer_read_int32 (&buffer, &(msg->received.usec));
result |= buffer_read_uint32 (&buffer, &(msg->size));
return result;
}
static cJSON *
hello_message_to_json (hello_message_t *msg)
{
cJSON *mac;
cJSON *hostname;
cJSON *version;
cJSON *client_name;
cJSON *os;
cJSON *arch;
cJSON *instance;
cJSON *id;
cJSON *protocol_version;
cJSON *json = NULL;
json = cJSON_CreateObject ();
if (!json)
{
goto error;
}
mac = cJSON_CreateString (msg->mac);
if (!mac)
{
goto error;
}
cJSON_AddItemToObject (json, "MAC", mac);
hostname = cJSON_CreateString (msg->hostname);
if (!hostname)
{
goto error;
}
cJSON_AddItemToObject (json, "HostName", hostname);
version = cJSON_CreateString (msg->version);
if (!version)
{
goto error;
}
cJSON_AddItemToObject (json, "Version", version);
client_name = cJSON_CreateString (msg->client_name);
if (!client_name)
{
goto error;
}
cJSON_AddItemToObject (json, "ClientName", client_name);
os = cJSON_CreateString (msg->os);
if (!os)
{
goto error;
}
cJSON_AddItemToObject (json, "OS", os);
arch = cJSON_CreateString (msg->arch);
if (!arch)
{
goto error;
}
cJSON_AddItemToObject (json, "Arch", arch);
instance = cJSON_CreateNumber (msg->instance);
if (!instance)
{
goto error;
}
cJSON_AddItemToObject (json, "Instance", instance);
id = cJSON_CreateString (msg->id);
if (!id)
{
goto error;
}
cJSON_AddItemToObject (json, "ID", id);
protocol_version = cJSON_CreateNumber (msg->protocol_version);
if (!protocol_version)
{
goto error;
}
cJSON_AddItemToObject (json, "SnapStreamProtocolVersion", protocol_version);
goto end;
error:
cJSON_Delete (json);
end:
return json;
}
char *
hello_message_serialize (hello_message_t *msg, size_t *size)
{
int str_length, prefixed_length;
cJSON *json;
char *str = NULL;
char *prefixed_str = NULL;
json = hello_message_to_json (msg);
if (!json)
{
return NULL;
}
str = cJSON_PrintUnformatted (json);
if (!str)
{
return NULL;
}
cJSON_Delete (json);
str_length = strlen (str);
prefixed_length = str_length + 4;
prefixed_str = malloc (prefixed_length);
if (!prefixed_str)
{
return NULL;
}
prefixed_str[0] = str_length & 0xff;
prefixed_str[1] = (str_length >> 8) & 0xff;
prefixed_str[2] = (str_length >> 16) & 0xff;
prefixed_str[3] = (str_length >> 24) & 0xff;
memcpy (&(prefixed_str[4]), str, str_length);
free (str);
*size = prefixed_length;
return prefixed_str;
}
int
server_settings_message_deserialize (server_settings_message_t *msg,
const char *json_str)
{
int status = 1;
cJSON *value = NULL;
cJSON *json = cJSON_Parse (json_str);
if (!json)
{
const char *error_ptr = cJSON_GetErrorPtr ();
if (error_ptr)
{
ESP_LOGE (TAG, "Error before: %s", error_ptr);
goto end;
}
}
if (msg == NULL)
{
status = 2;
goto end;
}
value = cJSON_GetObjectItemCaseSensitive (json, "bufferMs");
if (cJSON_IsNumber (value))
{
msg->buffer_ms = value->valueint;
}
value = cJSON_GetObjectItemCaseSensitive (json, "latency");
if (cJSON_IsNumber (value))
{
msg->latency = value->valueint;
}
value = cJSON_GetObjectItemCaseSensitive (json, "volume");
if (cJSON_IsNumber (value))
{
msg->volume = value->valueint;
}
value = cJSON_GetObjectItemCaseSensitive (json, "muted");
msg->muted = cJSON_IsTrue (value);
status = 0;
end:
cJSON_Delete (json);
return status;
}
int
codec_header_message_deserialize (codec_header_message_t *msg,
const char *data, uint32_t size)
{
read_buffer_t buffer;
uint32_t string_size;
int result = 0;
buffer_read_init (&buffer, data, size);
result |= buffer_read_uint32 (&buffer, &string_size);
if (result)
{
// Can't allocate the proper size string if we didn't read the size, so
// fail early
return 1;
}
msg->codec = malloc (string_size + 1);
if (!msg->codec)
{
return 2;
}
result |= buffer_read_buffer (&buffer, msg->codec, string_size);
// Make sure the codec is a proper C string by terminating it with a null
// character
msg->codec[string_size] = '\0';
result |= buffer_read_uint32 (&buffer, &(msg->size));
if (result)
{
// Can't allocate the proper size string if we didn't read the size, so
// fail early
return 1;
}
// msg->payload = malloc(msg->size);
// if (!msg->payload) {
// return 2;
// }
msg->payload = &data[buffer.index];
// result |= buffer_read_buffer(&buffer, msg->payload, msg->size);
return result;
}
int
wire_chunk_message_deserialize (wire_chunk_message_t *msg, const char *data,
uint32_t size)
{
read_buffer_t buffer;
int result = 0;
buffer_read_init (&buffer, data, size);
result |= buffer_read_int32 (&buffer, &(msg->timestamp.sec));
result |= buffer_read_int32 (&buffer, &(msg->timestamp.usec));
result |= buffer_read_uint32 (&buffer, &(msg->size));
// If there's been an error already (especially for the size bit) return
// early
if (result)
{
return result;
}
// TODO maybe should check to see if need to free memory?
//#if CONFIG_USE_PSRAM
// msg->payload =
// (char *)heap_caps_malloc(msg->size * sizeof(char),
// MALLOC_CAP_SPIRAM);
//#else
// msg->payload = heap_caps_malloc(msg->size * sizeof(char),
// MALLOC_CAP_8BIT);
//#endif
msg->payload = &data[buffer.index];
// Failed to allocate the memory
if (!msg->payload)
{
return 2;
}
// result |= buffer_read_buffer(&buffer, msg->payload, msg->size);
return result;
}
void
codec_header_message_free (codec_header_message_t *msg)
{
free (msg->codec);
msg->codec = NULL;
// free(msg->payload);
// msg->payload = NULL;
}
void
wire_chunk_message_free (wire_chunk_message_t *msg)
{
// if (msg->payload) {
// free(msg->payload);
// msg->payload = NULL;
// }
}
int
time_message_serialize (time_message_t *msg, char *data, uint32_t size)
{
write_buffer_t buffer;
int result = 0;
buffer_write_init (&buffer, data, size);
result |= buffer_write_int32 (&buffer, msg->latency.sec);
result |= buffer_write_int32 (&buffer, msg->latency.usec);
return result;
}
int
time_message_deserialize (time_message_t *msg, const char *data, uint32_t size)
{
read_buffer_t buffer;
int result = 0;
buffer_read_init (&buffer, data, size);
result |= buffer_read_int32 (&buffer, &(msg->latency.sec));
result |= buffer_read_int32 (&buffer, &(msg->latency.usec));
return result;
}