refactoring step 3 - components

squeezelite is a component
platform_esp32 is the main
This commit is contained in:
philippe44
2019-06-29 13:16:46 -07:00
parent 4b54f1733b
commit 53b0ab2390
49 changed files with 98 additions and 42 deletions

View File

@@ -1,6 +1,6 @@
set(COMPONENT_ADD_INCLUDEDIRS . )
set(COMPONENT_SRCS "esp_app_main.c" "platform_esp32.c" "bt_app_core.c" "cmd_wifi.c" "console.c" "nvs_utilities.c" "cmd_squeezelite.c")
set(COMPONENT_SRCS "bt_app_core.c" )
set(REQUIRES esp_common)
set(REQUIRES_COMPONENTS freertos nvs_flash esp32 spi_flash newlib log console )

View File

@@ -17,8 +17,7 @@
#include "argtable3/argtable3.h"
#include "bt_app_core.h"
#include "platform_esp32.h"
#include "trace.h"
static const char * TAG = "platform";

View File

@@ -6,4 +6,6 @@
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
# please read the SDK documents if you need to do this.
#
CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG
CFLAGS += -I$(COMPONENT_PATH)/../tools
#CFLAGS += -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG

View File

@@ -1,22 +0,0 @@
/* Console example — declarations of command registration functions.
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "cmd_system.h"
#include "cmd_wifi.h"
#include "cmd_nvs.h"
#include "cmd_i2ctools.h"
#ifdef __cplusplus
}
#endif

View File

@@ -1,129 +0,0 @@
//size_t esp_console_split_argv(char *line, char **argv, size_t argv_size);
#include "cmd_squeezelite.h"
#include <stdio.h>
#include <string.h>
#include "cmd_decl.h"
#include "esp_log.h"
#include "esp_console.h"
#include "esp_pthread.h"
#include "argtable3/argtable3.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "pthread.h"
#include "platform_esp32.h"
#include "nvs.h"
#include "nvs_flash.h"
//extern char current_namespace[];
static const char * TAG = "squeezelite_cmd";
#define SQUEEZELITE_THREAD_STACK_SIZE 8192
extern int main(int argc, char **argv);
static int launchsqueezelite(int argc, char **argv);
pthread_t thread_squeezelite;
pthread_t thread_squeezelite_runner;
/** Arguments used by 'squeezelite' function */
static struct {
struct arg_str *parameters;
struct arg_end *end;
} squeezelite_args;
static struct {
int argc;
char ** argv;
} thread_parms ;
static void * squeezelite_runner_thread(){
ESP_LOGI(TAG ,"Calling squeezelite");
main(thread_parms.argc,thread_parms.argv);
return NULL;
}
#define ADDITIONAL_SQUEEZELILTE_ARGS 5
static void * squeezelite_thread(){
int * exit_code;
static bool isRunning=false;
if(isRunning) {
ESP_LOGE(TAG,"Squeezelite already running. Exiting!");
return NULL;
}
isRunning=true;
ESP_LOGI(TAG,"Waiting for WiFi.");
while(!wait_for_wifi()){usleep(100000);};
ESP_LOGD(TAG ,"Number of args received: %u",thread_parms.argc );
ESP_LOGD(TAG ,"Values:");
for(int i = 0;i<thread_parms.argc; i++){
ESP_LOGD(TAG ," %s",thread_parms.argv[i]);
}
ESP_LOGD(TAG,"Starting Squeezelite runner Thread");
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name= "squeezelite-run";
cfg.inherit_cfg = true;
cfg.stack_size = SQUEEZELITE_THREAD_STACK_SIZE ;
esp_pthread_set_cfg(&cfg);
// no attribute if we want esp stack stack to prevail
pthread_create(&thread_squeezelite_runner, NULL, squeezelite_runner_thread,NULL);
// Wait for thread completion so we can free up memory.
pthread_join(thread_squeezelite_runner,(void **)&exit_code);
ESP_LOGV(TAG ,"Exited from squeezelite's main(). Freeing argv structure.");
for(int i=0;i<thread_parms.argc;i++){
ESP_LOGV(TAG ,"Freeing char buffer for parameter %u", i+1);
free(thread_parms.argv[i]);
}
ESP_LOGV(TAG ,"Freeing argv pointer");
free(thread_parms.argv);
isRunning=false;
return NULL;
}
static int launchsqueezelite(int argc, char **argv)
{
ESP_LOGV(TAG ,"Begin");
ESP_LOGD(TAG, "Parameters:");
for(int i = 0;i<argc; i++){
ESP_LOGD(TAG, " %s",argv[i]);
}
ESP_LOGV(TAG,"Saving args in thread structure");
thread_parms.argc=0;
thread_parms.argv = malloc(sizeof(char**)*(argc+ADDITIONAL_SQUEEZELILTE_ARGS));
memset(thread_parms.argv,'\0',sizeof(char**)*(argc+ADDITIONAL_SQUEEZELILTE_ARGS));
for(int i=0;i<argc;i++){
ESP_LOGD(TAG ,"assigning parm %u : %s",i,argv[i]);
thread_parms.argv[thread_parms.argc++]=strdup(argv[i]);
}
if(argc==1){
// There isn't a default configuration that would actually work
// if no parameter is passed.
ESP_LOGV(TAG ,"Adding argv value at %u. Prev value: %s",thread_parms.argc,thread_parms.argv[thread_parms.argc-1]);
thread_parms.argv[thread_parms.argc++]=strdup("-?");
}
ESP_LOGD(TAG,"Starting Squeezelite Thread");
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name= "squeezelite";
cfg.inherit_cfg = true;
esp_pthread_set_cfg(&cfg);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&thread_squeezelite, &attr, squeezelite_thread,NULL);
pthread_attr_destroy(&attr);
ESP_LOGD(TAG ,"Back to console thread!");
return 0;
}
void register_squeezelite(){
squeezelite_args.parameters = arg_str0(NULL, NULL, "<parms>", "command line for squeezelite. -h for help, --defaults to launch with default values.");
squeezelite_args.end = arg_end(1);
const esp_console_cmd_t launch_squeezelite = {
.command = "squeezelite",
.help = "Starts squeezelite",
.hint = NULL,
.func = &launchsqueezelite,
.argtable = &squeezelite_args
};
ESP_ERROR_CHECK( esp_console_cmd_register(&launch_squeezelite) );
}

View File

@@ -1,13 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// Register WiFi functions
void register_squeezelite();
#ifdef __cplusplus
}
#endif

View File

@@ -1,185 +0,0 @@
/* Console example — WiFi commands
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "cmd_wifi.h"
#include <stdio.h>
#include <string.h>
#include "cmd_decl.h"
#include "esp_log.h"
#include "esp_console.h"
#include "argtable3/argtable3.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "tcpip_adapter.h"
#include "esp_event.h"
#define JOIN_TIMEOUT_MS (10000)
static EventGroupHandle_t wifi_event_group;
const int CONNECTED_BIT = BIT0;
static const char * TAG = "cmd_wifi";
/** Arguments used by 'join' function */
static struct {
struct arg_int *timeout;
struct arg_str *ssid;
struct arg_str *password;
struct arg_end *end;
} join_args;
///** Arguments used by 'join' function */
//static struct {
// struct arg_int *autoconnect;
// struct arg_end *end;
//} auto_connect_args;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
esp_wifi_connect();
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
}
}
bool wait_for_wifi(){
bool connected=(xEventGroupGetBits(wifi_event_group) & CONNECTED_BIT)!=0;
if(!connected){
ESP_LOGD(TAG,"Waiting for WiFi...");
connected = (xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
pdFALSE, pdTRUE, JOIN_TIMEOUT_MS / portTICK_PERIOD_MS)& CONNECTED_BIT)!=0;
if(!connected){
ESP_LOGD(TAG,"wifi timeout.");
}
else
{
ESP_LOGI(TAG,"WiFi Connected!");
}
}
return connected;
}
static void initialise_wifi(void)
{
static bool initialized = false;
if (initialized) {
return;
}
tcpip_adapter_init();
wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &event_handler, NULL) );
ESP_ERROR_CHECK( esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL) );
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_NULL) );
ESP_ERROR_CHECK( esp_wifi_start() );
initialized = true;
}
static bool wifi_join(const char *ssid, const char *pass, int timeout_ms)
{
initialise_wifi();
wifi_config_t wifi_config = { 0 };
strncpy((char *) wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid));
if (pass) {
strncpy((char *) wifi_config.sta.password, pass, sizeof(wifi_config.sta.password));
}
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
ESP_ERROR_CHECK( esp_wifi_connect() );
int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
pdFALSE, pdTRUE, timeout_ms / portTICK_PERIOD_MS);
return (bits & CONNECTED_BIT) != 0;
}
static int set_auto_connect(int argc, char **argv)
{
// int nerrors = arg_parse(argc, argv, (void **) &join_args);
// if (nerrors != 0) {
// arg_print_errors(stderr, join_args.end, argv[0]);
// return 1;
// }
// ESP_LOGI(__func__, "Connecting to '%s'",
// join_args.ssid->sval[0]);
//
// /* set default value*/
// if (join_args.timeout->count == 0) {
// join_args.timeout->ival[0] = JOIN_TIMEOUT_MS;
// }
//
// bool connected = wifi_join(join_args.ssid->sval[0],
// join_args.password->sval[0],
// join_args.timeout->ival[0]);
// if (!connected) {
// ESP_LOGW(__func__, "Connection timed out");
// return 1;
// }
// ESP_LOGI(__func__, "Connected");
return 0;
}
static int connect(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **) &join_args);
if (nerrors != 0) {
arg_print_errors(stderr, join_args.end, argv[0]);
return 1;
}
ESP_LOGI(__func__, "Connecting to '%s'",
join_args.ssid->sval[0]);
/* set default value*/
if (join_args.timeout->count == 0) {
join_args.timeout->ival[0] = JOIN_TIMEOUT_MS;
}
bool connected = wifi_join(join_args.ssid->sval[0],
join_args.password->sval[0],
join_args.timeout->ival[0]);
if (!connected) {
ESP_LOGW(__func__, "Connection timed out");
return 1;
}
ESP_LOGI(__func__, "Connected");
return 0;
}
void register_wifi_join()
{
join_args.timeout = arg_int0(NULL, "timeout", "<t>", "Connection timeout, ms");
join_args.ssid = arg_str1(NULL, NULL, "<ssid>", "SSID of AP");
join_args.password = arg_str0(NULL, NULL, "<pass>", "PSK of AP");
join_args.end = arg_end(2);
const esp_console_cmd_t join_cmd = {
.command = "join",
.help = "Join WiFi AP as a station",
.hint = NULL,
.func = &connect,
.argtable = &join_args
};
ESP_ERROR_CHECK( esp_console_cmd_register(&join_cmd) );
}
void register_wifi()
{
register_wifi_join();
initialise_wifi();
}

View File

@@ -1,21 +0,0 @@
/* Console example — declarations of command registration functions.
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// Register WiFi functions
void register_wifi();
#ifdef __cplusplus
}
#endif

View File

@@ -1,318 +0,0 @@
/* Console example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_console.h"
#include "esp_vfs_dev.h"
#include "driver/uart.h"
#include "linenoise/linenoise.h"
#include "argtable3/argtable3.h"
#include "esp_vfs_fat.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "pthread.h"
#include "platform_esp32.h"
#include "cmd_decl.h"
#include "console.h"
#include "cmd_squeezelite.h"
#include "nvs_utilities.h"
pthread_t thread_console;
static void * console_thread();
void console_start();
static const char * TAG = "console";
/* Prompt to be printed before each line.
* This can be customized, made dynamic, etc.
*/
const char* prompt = LOG_COLOR_I "squeezelite-esp32> " LOG_RESET_COLOR;
/* Console command history can be stored to and loaded from a file.
* The easiest way to do this is to use FATFS filesystem on top of
* wear_levelling library.
*/
#define MOUNT_PATH "/data"
#define HISTORY_PATH MOUNT_PATH "/history.txt"
void run_command(char * line);
//optListStruct * getOptionByName(char * option){
// optListStruct * curOpt=&optList[0];
// while(curOpt->optName !=NULL){
// if(!strcmp(curOpt->optName, option)){
// return curOpt;
// }
// curOpt++;
// }
// return NULL;
//}
//
//static int list_options(int argc, char **argv)
//{
// nvs_handle nvs;
// esp_err_t err;
//
// err = nvs_open(current_namespace, NVS_READONLY, &nvs);
// if (err != ESP_OK) {
// return err;
// }
// //
// optListStruct * curOpt=&optList[0];
// printf("System Configuration Options.\n");
// while(curOpt->optName!=NULL){
// printf("Option: %s\n"
// " Description: %20s\n"
// " Default Value: %20s\n"
// " Current Value: ",curOpt->optName, curOpt->description, curOpt->defaultValue);
// size_t len;
// if ( (nvs_get_str(nvs, curOpt->optName, NULL, &len)) == ESP_OK) {
// char *str = (char *)malloc(len);
// if ( (nvs_get_str(nvs, curOpt->optName, str, &len)) == ESP_OK) {
// printf("%20s\n", str);
// }
// free(str);
// }
// else
// {
// if(store_nvs_value(NVS_TYPE_STR, curOpt->optName,curOpt->defaultValue, strlen(curOpt->defaultValue))==ESP_OK)
// {
// printf("%20s\n", curOpt->defaultValue);
// }
// else
// {
// printf("Error. Invalid key\n");
// }
// }
// curOpt++;
// }
// printf("\n");
// nvs_close(nvs);
// return 0;
//}
//void register_list_options(){
// const esp_console_cmd_t config_list = {
// .command = "config-list",
// .help = "Lists available configuration options.",
// .hint = NULL,
// .func = &list_options,
// .argtable = NULL
// };
//
// ESP_ERROR_CHECK( esp_console_cmd_register(&config_list) );
//
//}
void process_autoexec(){
int i=1;
char autoexec_name[21]={0};
char * autoexec_value=NULL;
uint8_t * autoexec_flag=NULL;
autoexec_flag = get_nvs_value_alloc(NVS_TYPE_U8, "autoexec");
if(autoexec_flag!=NULL ){
ESP_LOGI(TAG,"autoexec flag value found with value %u", *autoexec_flag);
if(*autoexec_flag == 1) {
do {
snprintf(autoexec_name,sizeof(autoexec_name)-1,"autoexec%u",i++);
ESP_LOGD(TAG,"Getting command name %s", autoexec_name);
autoexec_value= get_nvs_value_alloc(NVS_TYPE_STR, autoexec_name);
if(autoexec_value!=NULL ){
ESP_LOGI(TAG,"Running command %s = %s", autoexec_name, autoexec_value);
run_command(autoexec_value);
ESP_LOGD(TAG,"Freeing memory for command %s name", autoexec_name);
free(autoexec_value);
}
else {
ESP_LOGD(TAG,"No matching command found for name %s", autoexec_name);
break;
}
} while(1);
}
free(autoexec_flag);
}
else
{
ESP_LOGD(TAG,"No matching command found for name autoexec. Adding default entries");
uint8_t autoexec_dft=0;
char autoexec1_dft[]="join MySSID MyPASSWORD";
char autoexec2_dft[]="squeezelite -o \"DAC\" -b 500:2000 -d all=debug -M esp32 -r \"44100,4800\" -N \"playername.txt\"";
store_nvs_value(NVS_TYPE_U8,"autoexec",&autoexec_dft);
store_nvs_value(NVS_TYPE_STR,"autoexec1",autoexec1_dft);
store_nvs_value(NVS_TYPE_STR,"autoexec2",autoexec2_dft);
}
}
static void initialize_filesystem() {
static wl_handle_t wl_handle;
const esp_vfs_fat_mount_config_t mount_config = {
.max_files = 10,
.format_if_mount_failed = true,
.allocation_unit_size = 4096
};
esp_err_t err = esp_vfs_fat_spiflash_mount(MOUNT_PATH, "storage",
&mount_config, &wl_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err));
return;
}
}
static void initialize_nvs() {
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
}
void initialize_console() {
/* Disable buffering on stdin */
setvbuf(stdin, NULL, _IONBF, 0);
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
/* Move the caret to the beginning of the next line on '\n' */
esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
/* Configure UART. Note that REF_TICK is used so that the baud rate remains
* correct while APB frequency is changing in light sleep mode.
*/
const uart_config_t uart_config = { .baud_rate =
CONFIG_CONSOLE_UART_BAUDRATE, .data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1,
.use_ref_tick = true };
ESP_ERROR_CHECK(uart_param_config(CONFIG_CONSOLE_UART_NUM, &uart_config));
/* Install UART driver for interrupt-driven reads and writes */
ESP_ERROR_CHECK(
uart_driver_install(CONFIG_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0));
/* Tell VFS to use UART driver */
esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM);
/* Initialize the console */
esp_console_config_t console_config = { .max_cmdline_args = 22,
.max_cmdline_length = 256,
#if CONFIG_LOG_COLORS
.hint_color = atoi(LOG_COLOR_CYAN)
#endif
};
ESP_ERROR_CHECK(esp_console_init(&console_config));
/* Configure linenoise line completion library */
/* Enable multiline editing. If not set, long commands will scroll within
* single line.
*/
linenoiseSetMultiLine(1);
/* Tell linenoise where to get command completions and hints */
linenoiseSetCompletionCallback(&esp_console_get_completion);
linenoiseSetHintsCallback((linenoiseHintsCallback*) &esp_console_get_hint);
/* Set command history size */
linenoiseHistorySetMaxLen(100);
/* Load command history from filesystem */
linenoiseHistoryLoad(HISTORY_PATH);
}
void console_start() {
initialize_nvs();
initialize_filesystem();
initialize_console();
/* Register commands */
esp_console_register_help_command();
register_system();
register_wifi();
register_nvs();
register_squeezelite();
register_i2ctools();
printf("\n"
"Type 'help' to get the list of commands.\n"
"Use UP/DOWN arrows to navigate through command history.\n"
"Press TAB when typing command name to auto-complete.\n"
"\n"
"To automatically execute lines at startup:\n"
"\tSet NVS variable autoexec (U8) = 1 to enable, 0 to disable automatic execution.\n"
"\tSet NVS variable autoexec[1~9] (string)to a command that should be executed automatically\n"
"\n"
"\n");
/* Figure out if the terminal supports escape sequences */
int probe_status = linenoiseProbe();
if (probe_status) { /* zero indicates success */
printf("\n****************************\n"
"Your terminal application does not support escape sequences.\n"
"Line editing and history features are disabled.\n"
"On Windows, try using Putty instead.\n"
"****************************\n");
linenoiseSetDumbMode(1);
#if CONFIG_LOG_COLORS
/* Since the terminal doesn't support escape sequences,
* don't use color codes in the prompt.
*/
prompt = "squeezelite-esp32> ";
#endif //CONFIG_LOG_COLORS
}
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name= "console";
cfg.inherit_cfg = true;
esp_pthread_set_cfg(&cfg);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&thread_console, &attr, console_thread, NULL);
pthread_attr_destroy(&attr);
}
void run_command(char * line){
/* Try to run the command */
int ret;
esp_err_t err = esp_console_run(line, &ret);
if (err == ESP_ERR_NOT_FOUND) {
printf("Unrecognized command\n");
} else if (err == ESP_ERR_INVALID_ARG) {
// command was empty
} else if (err == ESP_OK && ret != ESP_OK) {
printf("Command returned non-zero error code: 0x%x (%s)\n", ret,
esp_err_to_name(err));
} else if (err != ESP_OK) {
printf("Internal error: %s\n", esp_err_to_name(err));
}
}
static void * console_thread() {
process_autoexec();
/* Main loop */
while (1) {
/* Get a line using linenoise.
* The line is returned when ENTER is pressed.
*/
char* line = linenoise(prompt);
if (line == NULL) { /* Ignore empty lines */
continue;
}
/* Add the command to the history */
linenoiseHistoryAdd(line);
/* Save command history to filesystem */
linenoiseHistorySave(HISTORY_PATH);
printf("\n");
run_command(line);
/* linenoise allocates line buffer on the heap, so need to free it */
linenoiseFree(line);
}
return NULL;
}

View File

@@ -1,18 +0,0 @@
/* Console example — declarations of command registration functions.
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -1,213 +0,0 @@
#include "nvs_utilities.h"
#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_console.h"
#include "esp_vfs_dev.h"
#include "driver/uart.h"
#include "linenoise/linenoise.h"
#include "argtable3/argtable3.h"
#include "cmd_decl.h"
#include "esp_vfs_fat.h"
#include "nvs.h"
#include "nvs_flash.h"
extern char current_namespace[];
static const char * TAG = "platform_esp32";
bool isNameValid(char * key){
bool bFound=false;
nvs_handle nvs;
esp_err_t err;
int8_t val=0;
err = nvs_open(current_namespace, NVS_READONLY, &nvs);
if (err != ESP_OK) {
ESP_LOGE(TAG,"Error opening nvs storage for namespace %s",current_namespace);
return false;
}
err = nvs_get_i8(nvs, key, &val);
if(err==ESP_OK || err== ESP_ERR_NVS_INVALID_LENGTH){
bFound=true;
}
else {
ESP_LOGD(TAG,"Search for key %s in namespace %s returned %#08X",key,current_namespace,err);
}
// nvs_iterator_t it = nvs_entry_find(NVS_DEFAULT_PART_NAME, current_namespace, NVS_TYPE_ANY);
// while (it != NULL) {
// nvs_entry_info_t info;
// nvs_entry_info(it, &info);
// it = nvs_entry_next(it);
// if(!strcmp(info.key,key)){
// bFound=true;
// }
// printf("key '%s', type '%d' \n", info.key, info.type);
// };
// // Note: no need to release iterator obtained from nvs_entry_find function when
// // nvs_entry_find or nvs_entry_next function return NULL, indicating no other
// // element for specified criteria was found.
nvs_close(nvs);
return bFound;
}
esp_err_t store_nvs_value(nvs_type_t type, const char *key, void * data) {
if (type == NVS_TYPE_BLOB)
return ESP_ERR_NVS_TYPE_MISMATCH;
return store_nvs_value_len(type, key, data,0);
}
esp_err_t store_nvs_value_len(nvs_type_t type, const char *key, void * data,
size_t data_len) {
esp_err_t err;
nvs_handle nvs;
if (type == NVS_TYPE_ANY) {
return ESP_ERR_NVS_TYPE_MISMATCH;
}
err = nvs_open(current_namespace, NVS_READWRITE, &nvs);
if (err != ESP_OK) {
return err;
}
if (type == NVS_TYPE_I8) {
err = nvs_set_i8(nvs, key, *(int8_t *) data);
} else if (type == NVS_TYPE_U8) {
err = nvs_set_u8(nvs, key, *(uint8_t *) data);
} else if (type == NVS_TYPE_I16) {
err = nvs_set_i16(nvs, key, *(int16_t *) data);
} else if (type == NVS_TYPE_U16) {
err = nvs_set_u16(nvs, key, *(uint16_t *) data);
} else if (type == NVS_TYPE_I32) {
err = nvs_set_i32(nvs, key, *(int32_t *) data);
} else if (type == NVS_TYPE_U32) {
err = nvs_set_u32(nvs, key, *(uint32_t *) data);
} else if (type == NVS_TYPE_I64) {
err = nvs_set_i64(nvs, key, *(int64_t *) data);
} else if (type == NVS_TYPE_U64) {
err = nvs_set_u64(nvs, key, *(uint64_t *) data);
} else if (type == NVS_TYPE_STR) {
err = nvs_set_str(nvs, key, data);
} else if (type == NVS_TYPE_BLOB) {
err = nvs_set_blob(nvs, key, (void *) data, data_len);
}
if (err == ESP_OK) {
err = nvs_commit(nvs);
if (err == ESP_OK) {
ESP_LOGI(TAG, "Value stored under key '%s'", key);
}
}
nvs_close(nvs);
return err;
}
void * get_nvs_value_alloc(nvs_type_t type, const char *key) {
nvs_handle nvs;
esp_err_t err;
void * value=NULL;
err = nvs_open(current_namespace, NVS_READONLY, &nvs);
if (err != ESP_OK) {
ESP_LOGE(TAG,"Could not open the nvs storage.");
return NULL;
}
if (type == NVS_TYPE_I8) {
value=malloc(sizeof(int8_t));
err = nvs_get_i8(nvs, key, (int8_t *) value);
} else if (type == NVS_TYPE_U8) {
value=malloc(sizeof(uint8_t));
err = nvs_get_u8(nvs, key, (uint8_t *) value);
} else if (type == NVS_TYPE_I16) {
value=malloc(sizeof(int16_t));
err = nvs_get_i16(nvs, key, (int16_t *) value);
} else if (type == NVS_TYPE_U16) {
value=malloc(sizeof(uint16_t));
err = nvs_get_u16(nvs, key, (uint16_t *) value);
} else if (type == NVS_TYPE_I32) {
value=malloc(sizeof(int32_t));
err = nvs_get_i32(nvs, key, (int32_t *) value);
} else if (type == NVS_TYPE_U32) {
value=malloc(sizeof(uint32_t));
err = nvs_get_u32(nvs, key, (uint32_t *) value);
} else if (type == NVS_TYPE_I64) {
value=malloc(sizeof(int64_t));
err = nvs_get_i64(nvs, key, (int64_t *) value);
} else if (type == NVS_TYPE_U64) {
value=malloc(sizeof(uint64_t));
err = nvs_get_u64(nvs, key, (uint64_t *) value);
} else if (type == NVS_TYPE_STR) {
size_t len=0;
err = nvs_get_str(nvs, key, NULL, &len);
if (err == ESP_OK) {
value=malloc(len);
err = nvs_get_str(nvs, key, value, &len);
}
} else if (type == NVS_TYPE_BLOB) {
size_t len;
err = nvs_get_blob(nvs, key, NULL, &len);
if (err == ESP_OK) {
value=malloc(len+1);
err = nvs_get_blob(nvs, key, value, &len);
}
}
if(err!=ESP_OK){
ESP_LOGD(TAG,"Value not found for key %s",key);
if(value!=NULL)
free(value);
value=NULL;
}
nvs_close(nvs);
return value;
}
esp_err_t get_nvs_value(nvs_type_t type, const char *key, void*value, const uint8_t buf_size) {
nvs_handle nvs;
esp_err_t err;
err = nvs_open(current_namespace, NVS_READONLY, &nvs);
if (err != ESP_OK) {
return err;
}
if (type == NVS_TYPE_I8) {
err = nvs_get_i8(nvs, key, (int8_t *) value);
} else if (type == NVS_TYPE_U8) {
err = nvs_get_u8(nvs, key, (uint8_t *) value);
} else if (type == NVS_TYPE_I16) {
err = nvs_get_i16(nvs, key, (int16_t *) value);
} else if (type == NVS_TYPE_U16) {
err = nvs_get_u16(nvs, key, (uint16_t *) value);
} else if (type == NVS_TYPE_I32) {
err = nvs_get_i32(nvs, key, (int32_t *) value);
} else if (type == NVS_TYPE_U32) {
err = nvs_get_u32(nvs, key, (uint32_t *) value);
} else if (type == NVS_TYPE_I64) {
err = nvs_get_i64(nvs, key, (int64_t *) value);
} else if (type == NVS_TYPE_U64) {
err = nvs_get_u64(nvs, key, (uint64_t *) value);
} else if (type == NVS_TYPE_STR) {
size_t len;
if ((err = nvs_get_str(nvs, key, NULL, &len)) == ESP_OK) {
if (len > buf_size) {
//ESP_LOGE("Error reading value for %s. Buffer size: %d, Value Length: %d", key, buf_size, len);
err = ESP_FAIL;
} else {
err = nvs_get_str(nvs, key, value, &len);
}
}
} else if (type == NVS_TYPE_BLOB) {
size_t len;
if ((err = nvs_get_blob(nvs, key, NULL, &len)) == ESP_OK) {
if (len > buf_size) {
//ESP_LOGE("Error reading value for %s. Buffer size: %d, Value Length: %d",
// key, buf_size, len);
err = ESP_FAIL;
} else {
err = nvs_get_blob(nvs, key, value, &len);
}
}
}
nvs_close(nvs);
return err;
}

View File

@@ -1,14 +0,0 @@
#pragma once
#include "esp_err.h"
#include "nvs.h"
#ifdef __cplusplus
extern "C" {
#endif
bool isNameValid(char * key);
esp_err_t store_nvs_value_len(nvs_type_t type, const char *key, void * data, size_t data_len);
esp_err_t store_nvs_value(nvs_type_t type, const char *key, void * data);
esp_err_t get_nvs_value(nvs_type_t type, const char *key, void*value, const uint8_t buf_size);
void * get_nvs_value_alloc(nvs_type_t type, const char *key);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,549 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* (c) Philippe, 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 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/>.
*
*/
#include "squeezelite.h"
#include <alac_wrapper.h>
#if BYTES_PER_FRAME == 4
#define ALIGN8(n) (n << 8)
#define ALIGN16(n) (n)
#define ALIGN24(n) (n >> 8)
#define ALIGN32(n) (n >> 16)
#else
#define ALIGN8(n) (n << 24)
#define ALIGN16(n) (n << 16)
#define ALIGN24(n) (n << 8)
#define ALIGN32(n) (n)
#endif
#define BLOCK_SIZE (4096 * BYTES_PER_FRAME)
#define MIN_READ BLOCK_SIZE
#define MIN_SPACE (MIN_READ * 4)
struct chunk_table {
u32_t sample, offset;
};
struct alac {
void *decoder;
u8_t *writebuf;
// following used for mp4 only
u32_t consume;
u32_t pos;
u32_t sample;
u32_t nextchunk;
void *stsc;
u32_t skip;
u64_t samples;
u64_t sttssamples;
bool empty;
struct chunk_table *chunkinfo;
u32_t *block_size, default_block_size, block_index;
unsigned sample_rate;
unsigned char channels, sample_size;
unsigned trak, play;
};
static struct alac *l;
extern log_level loglevel;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct decodestate decode;
extern struct processstate process;
#define LOCK_S mutex_lock(streambuf->mutex)
#define UNLOCK_S mutex_unlock(streambuf->mutex)
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
#if PROCESS
#define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex)
#define LOCK_O_not_direct if (!decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x) if (decode.direct) { x }
#define IF_PROCESS(x) if (!decode.direct) { x }
#else
#define LOCK_O_direct mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex)
#define LOCK_O_not_direct
#define UNLOCK_O_not_direct
#define IF_DIRECT(x) { x }
#define IF_PROCESS(x)
#endif
// read mp4 header to extract config data
static int read_mp4_header(void) {
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
char type[5];
u32_t len;
while (bytes >= 8) {
// count trak to find the first playable one
u32_t consume;
len = unpackN((u32_t *)streambuf->readp);
memcpy(type, streambuf->readp + 4, 4);
type[4] = '\0';
if (!strcmp(type, "moov")) {
l->trak = 0;
l->play = 0;
}
if (!strcmp(type, "trak")) {
l->trak++;
}
// extract audio config from within alac
if (!strcmp(type, "alac") && bytes > len) {
u8_t *ptr = streambuf->readp + 36;
l->decoder = alac_create_decoder(len - 36, ptr, &l->sample_size, &l->sample_rate, &l->channels);
l->play = l->trak;
}
// extract the total number of samples from stts
if (!strcmp(type, "stsz") && bytes > len) {
u32_t i;
u8_t *ptr = streambuf->readp + 12;
l->default_block_size = unpackN((u32_t *) ptr); ptr += 4;
if (!l->default_block_size) {
u32_t entries = unpackN((u32_t *)ptr); ptr += 4;
l->block_size = malloc((entries + 1)* 4);
for (i = 0; i < entries; i++) {
l->block_size[i] = unpackN((u32_t *)ptr); ptr += 4;
}
l->block_size[entries] = 0;
LOG_DEBUG("total blocksize contained in stsz %u", entries);
} else {
LOG_DEBUG("fixed blocksize in stsz %u", l->default_block_size);
}
}
// extract the total number of samples from stts
if (!strcmp(type, "stts") && bytes > len) {
u32_t i;
u8_t *ptr = streambuf->readp + 12;
u32_t entries = unpackN((u32_t *)ptr);
ptr += 4;
for (i = 0; i < entries; ++i) {
u32_t count = unpackN((u32_t *)ptr);
u32_t size = unpackN((u32_t *)(ptr + 4));
l->sttssamples += count * size;
ptr += 8;
}
LOG_DEBUG("total number of samples contained in stts: " FMT_u64, l->sttssamples);
}
// stash sample to chunk info, assume it comes before stco
if (!strcmp(type, "stsc") && bytes > len && !l->chunkinfo) {
l->stsc = malloc(len - 12);
if (l->stsc == NULL) {
LOG_WARN("malloc fail");
return -1;
}
memcpy(l->stsc, streambuf->readp + 12, len - 12);
}
// build offsets table from stco and stored stsc
if (!strcmp(type, "stco") && bytes > len && l->play == l->trak) {
u32_t i;
// extract chunk offsets
u8_t *ptr = streambuf->readp + 12;
u32_t entries = unpackN((u32_t *)ptr);
ptr += 4;
l->chunkinfo = malloc(sizeof(struct chunk_table) * (entries + 1));
if (l->chunkinfo == NULL) {
LOG_WARN("malloc fail");
return -1;
}
for (i = 0; i < entries; ++i) {
l->chunkinfo[i].offset = unpackN((u32_t *)ptr);
l->chunkinfo[i].sample = 0;
ptr += 4;
}
l->chunkinfo[i].sample = 0;
l->chunkinfo[i].offset = 0;
// fill in first sample id for each chunk from stored stsc
if (l->stsc) {
u32_t stsc_entries = unpackN((u32_t *)l->stsc);
u32_t sample = 0;
u32_t last = 0, last_samples = 0;
u8_t *ptr = (u8_t *)l->stsc + 4;
while (stsc_entries--) {
u32_t first = unpackN((u32_t *)ptr);
u32_t samples = unpackN((u32_t *)(ptr + 4));
if (last) {
for (i = last - 1; i < first - 1; ++i) {
l->chunkinfo[i].sample = sample;
sample += last_samples;
}
}
if (stsc_entries == 0) {
for (i = first - 1; i < entries; ++i) {
l->chunkinfo[i].sample = sample;
sample += samples;
}
}
last = first;
last_samples = samples;
ptr += 12;
}
free(l->stsc);
l->stsc = NULL;
}
}
// found media data, advance to start of first chunk and return
if (!strcmp(type, "mdat")) {
_buf_inc_readp(streambuf, 8);
l->pos += 8;
bytes -= 8;
if (l->play) {
LOG_DEBUG("type: mdat len: %u pos: %u", len, l->pos);
if (l->chunkinfo && l->chunkinfo[0].offset > l->pos) {
u32_t skip = l->chunkinfo[0].offset - l->pos;
LOG_DEBUG("skipping: %u", skip);
if (skip <= bytes) {
_buf_inc_readp(streambuf, skip);
l->pos += skip;
} else {
l->consume = skip;
}
}
l->sample = l->nextchunk = 1;
l->block_index = 0;
return 1;
} else {
LOG_DEBUG("type: mdat len: %u, no playable track found", len);
return -1;
}
}
// parse key-value atoms within ilst ---- entries to get encoder padding within iTunSMPB entry for gapless
if (!strcmp(type, "----") && bytes > len) {
u8_t *ptr = streambuf->readp + 8;
u32_t remain = len - 8, size;
if (!memcmp(ptr + 4, "mean", 4) && (size = unpackN((u32_t *)ptr)) < remain) {
ptr += size; remain -= size;
}
if (!memcmp(ptr + 4, "name", 4) && (size = unpackN((u32_t *)ptr)) < remain && !memcmp(ptr + 12, "iTunSMPB", 8)) {
ptr += size; remain -= size;
}
if (!memcmp(ptr + 4, "data", 4) && remain > 16 + 48) {
// data is stored as hex strings: 0 start end samples
u32_t b, c; u64_t d;
if (sscanf((const char *)(ptr + 16), "%x %x %x " FMT_x64, &b, &b, &c, &d) == 4) {
LOG_DEBUG("iTunSMPB start: %u end: %u samples: " FMT_u64, b, c, d);
if (l->sttssamples && l->sttssamples < b + c + d) {
LOG_DEBUG("reducing samples as stts count is less");
d = l->sttssamples - (b + c);
}
l->skip = b;
l->samples = d;
}
}
}
// default to consuming entire box
consume = len;
// read into these boxes so reduce consume
if (!strcmp(type, "moov") || !strcmp(type, "trak") || !strcmp(type, "mdia") || !strcmp(type, "minf") || !strcmp(type, "stbl") ||
!strcmp(type, "udta") || !strcmp(type, "ilst")) {
consume = 8;
}
// special cases which mix mix data in the enclosing box which we want to read into
if (!strcmp(type, "stsd")) consume = 16;
if (!strcmp(type, "mp4a")) consume = 36;
if (!strcmp(type, "meta")) consume = 12;
// consume rest of box if it has been parsed (all in the buffer) or is not one we want to parse
if (bytes >= consume) {
LOG_DEBUG("type: %s len: %u consume: %u", type, len, consume);
_buf_inc_readp(streambuf, consume);
l->pos += consume;
bytes -= consume;
} else if ( !(!strcmp(type, "esds") || !strcmp(type, "stts") || !strcmp(type, "stsc") ||
!strcmp(type, "stsz") || !strcmp(type, "stco") || !strcmp(type, "----")) ) {
LOG_DEBUG("type: %s len: %u consume: %u - partial consume: %u", type, len, consume, bytes);
_buf_inc_readp(streambuf, bytes);
l->pos += bytes;
l->consume = consume - bytes;
break;
} else {
break;
}
}
return 0;
}
static decode_state alac_decode(void) {
size_t bytes;
bool endstream;
u8_t *iptr;
u32_t frames, block_size;
LOCK_S;
// data not reached yet
if (l->consume) {
u32_t consume = min(l->consume, _buf_used(streambuf));
LOG_DEBUG("consume: %u of %u", consume, l->consume);
_buf_inc_readp(streambuf, consume);
l->pos += consume;
l->consume -= consume;
UNLOCK_S;
return DECODE_RUNNING;
}
if (decode.new_stream) {
int found = 0;
// mp4 - read header
found = read_mp4_header();
if (found == 1) {
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
LOG_INFO("setting track_start");
LOCK_O;
output.next_sample_rate = decode_newstream(l->sample_rate, output.supported_rates);
output.track_start = outputbuf->writep;
if (output.fade_mode) _checkfade(true);
decode.new_stream = false;
UNLOCK_O;
} else if (found == -1) {
LOG_WARN("[%p]: error reading stream header");
UNLOCK_S;
return DECODE_ERROR;
} else {
// not finished header parsing come back next time
UNLOCK_S;
return DECODE_RUNNING;
}
}
bytes = _buf_used(streambuf);
block_size = l->default_block_size ? l->default_block_size : l->block_size[l->block_index];
// stream terminated
if (stream.state <= DISCONNECT && (bytes == 0 || block_size == 0)) {
UNLOCK_S;
LOG_DEBUG("end of stream");
return DECODE_COMPLETE;
}
// enough data for coding
if (bytes < block_size) {
UNLOCK_S;
return DECODE_RUNNING;
} else if (block_size != l->default_block_size) l->block_index++;
bytes = min(bytes, _buf_cont_read(streambuf));
// need to create a buffer with contiguous data
if (bytes < block_size) {
u8_t *buffer = malloc(block_size);
memcpy(buffer, streambuf->readp, bytes);
memcpy(buffer + bytes, streambuf->buf, block_size - bytes);
iptr = buffer;
} else iptr = streambuf->readp;
if (!alac_to_pcm(l->decoder, iptr, l->writebuf, 2, &frames)) {
LOG_ERROR("decode error");
UNLOCK_S;
return DECODE_ERROR;
}
// and free it
if (bytes < block_size) free(iptr);
LOG_SDEBUG("block of %u bytes (%u frames)", block_size, frames);
endstream = false;
// mp4 end of chunk - skip to next offset
if (l->chunkinfo && l->chunkinfo[l->nextchunk].offset && l->sample++ == l->chunkinfo[l->nextchunk].sample) {
if (l->chunkinfo[l->nextchunk].offset > l->pos) {
u32_t skip = l->chunkinfo[l->nextchunk].offset - l->pos;
if (_buf_used(streambuf) >= skip) {
_buf_inc_readp(streambuf, skip);
l->pos += skip;
} else {
l->consume = skip;
}
l->nextchunk++;
} else {
LOG_ERROR("error: need to skip backwards!");
endstream = true;
}
// mp4 when not at end of chunk
} else if (frames) {
_buf_inc_readp(streambuf, block_size);
l->pos += block_size;
} else {
endstream = true;
}
UNLOCK_S;
if (endstream) {
LOG_WARN("unable to decode further");
return DECODE_ERROR;
}
// now point at the beginning of decoded samples
iptr = l->writebuf;
if (l->skip) {
u32_t skip;
if (l->empty) {
l->empty = false;
l->skip -= frames;
LOG_DEBUG("gapless: first frame empty, skipped %u frames at start", frames);
}
skip = min(frames, l->skip);
LOG_DEBUG("gapless: skipping %u frames at start", skip);
frames -= skip;
l->skip -= skip;
iptr += skip * l->channels * l->sample_size;
}
if (l->samples) {
if (l->samples < frames) {
LOG_DEBUG("gapless: trimming %u frames from end", frames - l->samples);
frames = (u32_t) l->samples;
}
l->samples -= frames;
}
LOCK_O_direct;
while (frames > 0) {
size_t f, count;
ISAMPLE_T *optr;
IF_DIRECT(
f = min(frames, _buf_cont_write(outputbuf) / BYTES_PER_FRAME);
optr = (ISAMPLE_T *)outputbuf->writep;
);
IF_PROCESS(
f = min(frames, process.max_in_frames - process.in_frames);
optr = (ISAMPLE_T *)((u8_t *) process.inbuf + process.in_frames * BYTES_PER_FRAME);
);
f = min(f, frames);
count = f;
if (l->sample_size == 8) {
while (count--) {
*optr++ = ALIGN8(*iptr++);
*optr++ = ALIGN8(*iptr++);
}
} else if (l->sample_size == 16) {
u16_t *_iptr = (u16_t*) iptr;
while (count--) {
*optr++ = ALIGN16(*_iptr++);
*optr++ = ALIGN16(*_iptr++);
}
} else if (l->sample_size == 24) {
while (count--) {
*optr++ = ALIGN24(*(u32_t*) iptr);
*optr++ = ALIGN24(*(u32_t*) (iptr + 3));
iptr += 6;
}
} else if (l->sample_size == 32) {
u32_t *_iptr = (u32_t*) iptr;
while (count--) {
*optr++ = ALIGN32(*_iptr++);
*optr++ = ALIGN32(*_iptr++);
}
} else {
LOG_ERROR("unsupported bits per sample: %u", l->sample_size);
}
frames -= f;
IF_DIRECT(
_buf_inc_writep(outputbuf, f * BYTES_PER_FRAME);
);
IF_PROCESS(
process.in_frames = f;
// called only if there is enough space in process buffer
if (frames) LOG_ERROR("unhandled case");
);
}
UNLOCK_O_direct;
return DECODE_RUNNING;
}
static void alac_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
if (l->decoder) alac_delete_decoder(l->decoder);
else l->writebuf = malloc(BLOCK_SIZE * 2);
if (l->chunkinfo) free(l->chunkinfo);
if (l->block_size) free(l->block_size);
if (l->stsc) free(l->stsc);
l->decoder = l->chunkinfo = l->stsc = l->block_size = NULL;
l->skip = 0;
l->samples = l->sttssamples = 0;
l->empty = false;
l->pos = l->consume = l->sample = l->nextchunk = 0;
}
static void alac_close(void) {
if (l->decoder) alac_delete_decoder(l->decoder);
if (l->chunkinfo) free(l->chunkinfo);
if (l->block_size) free(l->block_size);
if (l->stsc) free(l->stsc);
l->decoder = l->chunkinfo = l->stsc = l->block_size = NULL;
free(l->writebuf);
}
struct codec *register_alac(void) {
static struct codec ret = {
'l', // id
"alc", // types
MIN_READ, // min read
MIN_SPACE, // min space assuming a ratio of 2
alac_open, // open
alac_close, // close
alac_decode, // decode
};
l = malloc(sizeof(struct alac));
if (!l) {
return NULL;
}
l->decoder = l->chunkinfo = l->stsc = l->block_size = NULL;
LOG_INFO("using alac to decode alc");
return &ret;
}

View File

@@ -0,0 +1,115 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
// fifo bufffers
#define _GNU_SOURCE
#include "squeezelite.h"
// _* called with muxtex locked
inline unsigned _buf_used(struct buffer *buf) {
return buf->writep >= buf->readp ? buf->writep - buf->readp : buf->size - (buf->readp - buf->writep);
}
unsigned _buf_space(struct buffer *buf) {
return buf->size - _buf_used(buf) - 1; // reduce by one as full same as empty otherwise
}
unsigned _buf_cont_read(struct buffer *buf) {
return buf->writep >= buf->readp ? buf->writep - buf->readp : buf->wrap - buf->readp;
}
unsigned _buf_cont_write(struct buffer *buf) {
return buf->writep >= buf->readp ? buf->wrap - buf->writep : buf->readp - buf->writep;
}
void _buf_inc_readp(struct buffer *buf, unsigned by) {
buf->readp += by;
if (buf->readp >= buf->wrap) {
buf->readp -= buf->size;
}
}
void _buf_inc_writep(struct buffer *buf, unsigned by) {
buf->writep += by;
if (buf->writep >= buf->wrap) {
buf->writep -= buf->size;
}
}
void buf_flush(struct buffer *buf) {
mutex_lock(buf->mutex);
buf->readp = buf->buf;
buf->writep = buf->buf;
mutex_unlock(buf->mutex);
}
// adjust buffer to multiple of mod bytes so reading in multiple always wraps on frame boundary
void buf_adjust(struct buffer *buf, size_t mod) {
size_t size;
mutex_lock(buf->mutex);
size = ((unsigned)(buf->base_size / mod)) * mod;
buf->readp = buf->buf;
buf->writep = buf->buf;
buf->wrap = buf->buf + size;
buf->size = size;
mutex_unlock(buf->mutex);
}
// called with mutex locked to resize, does not retain contents, reverts to original size if fails
void _buf_resize(struct buffer *buf, size_t size) {
free(buf->buf);
buf->buf = malloc(size);
if (!buf->buf) {
size = buf->size;
buf->buf= malloc(size);
if (!buf->buf) {
size = 0;
}
}
buf->readp = buf->buf;
buf->writep = buf->buf;
buf->wrap = buf->buf + size;
buf->size = size;
buf->base_size = size;
}
void buf_init(struct buffer *buf, size_t size) {
buf->buf = malloc(size);
buf->readp = buf->buf;
buf->writep = buf->buf;
buf->wrap = buf->buf + size;
buf->size = size;
buf->base_size = size;
mutex_create_p(buf->mutex);
}
void buf_destroy(struct buffer *buf) {
if (buf->buf) {
free(buf->buf);
buf->buf = NULL;
buf->size = 0;
buf->base_size = 0;
mutex_destroy(buf->mutex);
}
}

View File

@@ -0,0 +1,18 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ONLY -DBYTES_PER_FRAME=4 \
-I$(COMPONENT_PATH)/../codecs/inc \
-I$(COMPONENT_PATH)/../codecs/inc/mad \
-I$(COMPONENT_PATH)/../codecs/inc/alac \
-I$(COMPONENT_PATH)/../codecs/inc/helix-aac \
-I$(COMPONENT_PATH)/../codecs/inc/vorbis \
-I$(COMPONENT_PATH)/../codecs/inc/soxr \
-I$(COMPONENT_PATH)/../codecs/inc/resample16 \
-I$(COMPONENT_PATH)/../tools
# -I$(COMPONENT_PATH)/../codecs/inc/faad2

View File

@@ -0,0 +1,308 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
// decode thread
#include "squeezelite.h"
log_level loglevel;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct processstate process;
struct decodestate decode;
struct codec *codecs[MAX_CODECS];
struct codec *codec;
static bool running = true;
#define LOCK_S mutex_lock(streambuf->mutex)
#define UNLOCK_S mutex_unlock(streambuf->mutex)
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
#define LOCK_D mutex_lock(decode.mutex);
#define UNLOCK_D mutex_unlock(decode.mutex);
#if PROCESS
#define IF_DIRECT(x) if (decode.direct) { x }
#define IF_PROCESS(x) if (!decode.direct) { x }
#define MAY_PROCESS(x) { x }
#else
#define IF_DIRECT(x) { x }
#define IF_PROCESS(x)
#define MAY_PROCESS(x)
#endif
static void *decode_thread() {
while (running) {
size_t bytes, space, min_space;
bool toend;
bool ran = false;
LOCK_S;
bytes = _buf_used(streambuf);
toend = (stream.state <= DISCONNECT);
UNLOCK_S;
LOCK_O;
space = _buf_space(outputbuf);
UNLOCK_O;
LOCK_D;
if (decode.state == DECODE_RUNNING && codec) {
LOG_SDEBUG("streambuf bytes: %u outputbuf space: %u", bytes, space);
IF_DIRECT(
min_space = codec->min_space;
);
IF_PROCESS(
min_space = process.max_out_frames * BYTES_PER_FRAME;
);
if (space > min_space && (bytes > codec->min_read_bytes || toend)) {
decode.state = codec->decode();
IF_PROCESS(
if (process.in_frames) {
process_samples();
}
if (decode.state == DECODE_COMPLETE) {
process_drain();
}
);
if (decode.state != DECODE_RUNNING) {
LOG_INFO("decode %s", decode.state == DECODE_COMPLETE ? "complete" : "error");
LOCK_O;
if (output.fade_mode) _checkfade(false);
UNLOCK_O;
wake_controller();
}
ran = true;
}
}
UNLOCK_D;
if (!ran) {
usleep(100000);
}
}
return 0;
}
static void sort_codecs(int pry, struct codec* ptr) {
static int priority[MAX_CODECS];
int i, tpry;
struct codec* tptr;
for (i = 0; i < MAX_CODECS; i++) {
if (!codecs[i]) {
codecs[i] = ptr;
priority[i] = pry;
return;
}
if (pry < priority[i]) {
tptr = codecs[i];
codecs[i] = ptr;
ptr = tptr;
tpry = priority[i];
priority[i] = pry;
pry = tpry;
}
}
}
static thread_type thread;
void decode_init(log_level level, const char *include_codecs, const char *exclude_codecs) {
int i;
char* order_codecs = NULL;
loglevel = level;
LOG_INFO("init decode");
// register codecs
// dsf,dff,alc,wma,wmap,wmal,aac,spt,ogg,ogf,flc,aif,pcm,mp3
i = 0;
#if DSD
if (!strstr(exclude_codecs, "dsd") && (!include_codecs || (order_codecs = strstr(include_codecs, "dsd"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_dsd());
#endif
#if FFMPEG
if (!strstr(exclude_codecs, "alac") && (!include_codecs || (order_codecs = strstr(include_codecs, "alac"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_ff("alc"));
if (!strstr(exclude_codecs, "wma") && (!include_codecs || (order_codecs = strstr(include_codecs, "wma"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_ff("wma"));
#else
if (!strstr(exclude_codecs, "alac") && (!include_codecs || (order_codecs = strstr(include_codecs, "alac"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_alac());
#endif
#ifndef NO_FAAD
if (!strstr(exclude_codecs, "aac") && (!include_codecs || (order_codecs = strstr(include_codecs, "aac"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_faad());
#endif
if (!strstr(exclude_codecs, "aac") && (!include_codecs || (order_codecs = strstr(include_codecs, "aac"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_helixaac());
if (!strstr(exclude_codecs, "ogg") && (!include_codecs || (order_codecs = strstr(include_codecs, "ogg"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_vorbis());
if (!strstr(exclude_codecs, "flac") && (!include_codecs || (order_codecs = strstr(include_codecs, "flac"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_flac());
if (!strstr(exclude_codecs, "pcm") && (!include_codecs || (order_codecs = strstr(include_codecs, "pcm"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_pcm());
// try mad then mpg for mp3 unless command line option passed
if (!(strstr(exclude_codecs, "mp3") || strstr(exclude_codecs, "mad")) &&
(!include_codecs || (order_codecs = strstr(include_codecs, "mp3")) || (order_codecs = strstr(include_codecs, "mad"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_mad());
else if (!(strstr(exclude_codecs, "mp3") || strstr(exclude_codecs, "mpg")) &&
(!include_codecs || (order_codecs = strstr(include_codecs, "mp3")) || (order_codecs = strstr(include_codecs, "mpg"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_mpg());
LOG_DEBUG("include codecs: %s exclude codecs: %s", include_codecs ? include_codecs : "", exclude_codecs);
mutex_create(decode.mutex);
#if LINUX || OSX || FREEBSD || EMBEDDED
pthread_attr_t attr;
pthread_attr_init(&attr);
#ifdef PTHREAD_STACK_MIN
pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + DECODE_THREAD_STACK_SIZE);
#endif
pthread_create(&thread, &attr, decode_thread, NULL);
pthread_attr_destroy(&attr);
#if HAS_PTHREAD_SETNAME_NP
pthread_setname_np(thread, "decode");
#endif
#endif
#if WIN
thread = CreateThread(NULL, DECODE_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&decode_thread, NULL, 0, NULL);
#endif
decode.new_stream = true;
decode.state = DECODE_STOPPED;
MAY_PROCESS(
decode.direct = true;
decode.process = false;
);
}
void decode_close(void) {
LOG_INFO("close decode");
LOCK_D;
if (codec) {
codec->close();
codec = NULL;
}
running = false;
UNLOCK_D;
#if LINUX || OSX || FREEBSD
pthread_join(thread, NULL);
#endif
mutex_destroy(decode.mutex);
}
void decode_flush(void) {
LOG_INFO("decode flush");
LOCK_D;
decode.state = DECODE_STOPPED;
IF_PROCESS(
process_flush();
);
UNLOCK_D;
}
unsigned decode_newstream(unsigned sample_rate, unsigned supported_rates[]) {
// called with O locked to get sample rate for potentially processed output stream
// release O mutex during process_newstream as it can take some time
MAY_PROCESS(
if (decode.process) {
UNLOCK_O;
sample_rate = process_newstream(&decode.direct, sample_rate, supported_rates);
LOCK_O;
}
);
return sample_rate;
}
void codec_open(u8_t format, u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness) {
int i;
LOG_INFO("codec open: '%c'", format);
LOCK_D;
decode.new_stream = true;
decode.state = DECODE_STOPPED;
MAY_PROCESS(
decode.direct = true; // potentially changed within codec when processing enabled
);
// find the required codec
for (i = 0; i < MAX_CODECS; ++i) {
if (codecs[i] && codecs[i]->id == format) {
if (codec && codec != codecs[i]) {
LOG_INFO("closing codec: '%c'", codec->id);
codec->close();
}
codec = codecs[i];
codec->open(sample_size, sample_rate, channels, endianness);
decode.state = DECODE_READY;
UNLOCK_D;
return;
}
}
UNLOCK_D;
LOG_ERROR("codec not found");
}

View File

@@ -18,27 +18,27 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "squeezelite.h"
#include "esp_pthread.h"
#include "esp_system.h"
#ifndef QUOTE
#define QUOTE(name) #name
#endif
#define ESP_LOG_DEBUG_EVENT(tag,e) ESP_LOGD(tag,"evt: " e)
extern void run_command(char * line);
extern bool wait_for_wifi();
extern void console_start();
extern pthread_cond_t wifi_connect_suspend_cond;
extern pthread_t wifi_connect_suspend_mutex;
#ifdef __cplusplus
void get_mac(u8_t mac[]) {
esp_read_mac(mac, ESP_MAC_WIFI_STA);
}
#endif
_sig_func_ptr signal(int sig, _sig_func_ptr func) {
return NULL;
}
void *audio_calloc(size_t nmemb, size_t size) {
return calloc(nmemb, size);
}
int pthread_setname_np(pthread_t thread, const char *name) {
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name= name;
cfg.inherit_cfg = true;
return esp_pthread_set_cfg(&cfg);
}

View File

@@ -0,0 +1,22 @@
#ifndef EMBEDDED_H
#define EMBEDDED_H
#include <inttypes.h>
#define HAS_MUTEX_CREATE_P 0
#define HAS_PTHREAD_SETNAME_NP 1
#ifndef PTHREAD_STACK_MIN
#define PTHREAD_STACK_MIN 256
#endif
typedef int16_t s16_t;
typedef int32_t s32_t;
typedef int64_t s64_t;
typedef unsigned long long u64_t;
#define exit(code) { int ret = code; pthread_exit(&ret); }
int pthread_setname_np(pthread_t thread, const char *name);
#endif // EMBEDDED_H

View File

@@ -0,0 +1,659 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
#include "squeezelite.h"
#include <neaacdec.h>
#if BYTES_PER_FRAME == 4
#define ALIGN(n) (n)
#else
#define ALIGN(n) (n << 8)
#endif
#define WRAPBUF_LEN 2048
struct chunk_table {
u32_t sample, offset;
};
struct faad {
NeAACDecHandle hAac;
u8_t type;
// following used for mp4 only
u32_t consume;
u32_t pos;
u32_t sample;
u32_t nextchunk;
void *stsc;
u32_t skip;
u64_t samples;
u64_t sttssamples;
bool empty;
struct chunk_table *chunkinfo;
// faad symbols to be dynamically loaded
#if !LINKALL
NeAACDecConfigurationPtr (* NeAACDecGetCurrentConfiguration)(NeAACDecHandle);
unsigned char (* NeAACDecSetConfiguration)(NeAACDecHandle, NeAACDecConfigurationPtr);
NeAACDecHandle (* NeAACDecOpen)(void);
void (* NeAACDecClose)(NeAACDecHandle);
long (* NeAACDecInit)(NeAACDecHandle, unsigned char *, unsigned long, unsigned long *, unsigned char *);
char (* NeAACDecInit2)(NeAACDecHandle, unsigned char *pBuffer, unsigned long, unsigned long *, unsigned char *);
void *(* NeAACDecDecode)(NeAACDecHandle, NeAACDecFrameInfo *, unsigned char *, unsigned long);
char *(* NeAACDecGetErrorMessage)(unsigned char);
#endif
};
static struct faad *a;
extern log_level loglevel;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct decodestate decode;
extern struct processstate process;
#define LOCK_S mutex_lock(streambuf->mutex)
#define UNLOCK_S mutex_unlock(streambuf->mutex)
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
#if PROCESS
#define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x) if (decode.direct) { x }
#define IF_PROCESS(x) if (!decode.direct) { x }
#else
#define LOCK_O_direct mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x) { x }
#define IF_PROCESS(x)
#endif
#if LINKALL
#define NEAAC(h, fn, ...) (NeAACDec ## fn)(__VA_ARGS__)
#else
#define NEAAC(h, fn, ...) (h)->NeAACDec##fn(__VA_ARGS__)
#endif
// minimal code for mp4 file parsing to extract audio config and find media data
// adapted from faad2/common/mp4ff
u32_t mp4_desc_length(u8_t **buf) {
u8_t b;
u8_t num_bytes = 0;
u32_t length = 0;
do {
b = **buf;
*buf += 1;
num_bytes++;
length = (length << 7) | (b & 0x7f);
} while ((b & 0x80) && num_bytes < 4);
return length;
}
// read mp4 header to extract config data
static int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_p) {
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
char type[5];
u32_t len;
while (bytes >= 8) {
// count trak to find the first playable one
static unsigned trak, play;
u32_t consume;
len = unpackN((u32_t *)streambuf->readp);
memcpy(type, streambuf->readp + 4, 4);
type[4] = '\0';
if (!strcmp(type, "moov")) {
trak = 0;
play = 0;
}
if (!strcmp(type, "trak")) {
trak++;
}
// extract audio config from within esds and pass to DecInit2
if (!strcmp(type, "esds") && bytes > len) {
unsigned config_len;
u8_t *ptr = streambuf->readp + 12;
if (*ptr++ == 0x03) {
mp4_desc_length(&ptr);
ptr += 4;
} else {
ptr += 3;
}
mp4_desc_length(&ptr);
ptr += 13;
if (*ptr++ != 0x05) {
LOG_WARN("error parsing esds");
return -1;
}
config_len = mp4_desc_length(&ptr);
if (NEAAC(a, Init2, a->hAac, ptr, config_len, samplerate_p, channels_p) == 0) {
LOG_DEBUG("playable aac track: %u", trak);
play = trak;
}
}
// extract the total number of samples from stts
if (!strcmp(type, "stts") && bytes > len) {
u32_t i;
u8_t *ptr = streambuf->readp + 12;
u32_t entries = unpackN((u32_t *)ptr);
ptr += 4;
for (i = 0; i < entries; ++i) {
u32_t count = unpackN((u32_t *)ptr);
u32_t size = unpackN((u32_t *)(ptr + 4));
a->sttssamples += count * size;
ptr += 8;
}
LOG_DEBUG("total number of samples contained in stts: " FMT_u64, a->sttssamples);
}
// stash sample to chunk info, assume it comes before stco
if (!strcmp(type, "stsc") && bytes > len && !a->chunkinfo) {
a->stsc = malloc(len - 12);
if (a->stsc == NULL) {
LOG_WARN("malloc fail");
return -1;
}
memcpy(a->stsc, streambuf->readp + 12, len - 12);
}
// build offsets table from stco and stored stsc
if (!strcmp(type, "stco") && bytes > len && play == trak) {
u32_t i;
// extract chunk offsets
u8_t *ptr = streambuf->readp + 12;
u32_t entries = unpackN((u32_t *)ptr);
ptr += 4;
a->chunkinfo = malloc(sizeof(struct chunk_table) * (entries + 1));
if (a->chunkinfo == NULL) {
LOG_WARN("malloc fail");
return -1;
}
for (i = 0; i < entries; ++i) {
a->chunkinfo[i].offset = unpackN((u32_t *)ptr);
a->chunkinfo[i].sample = 0;
ptr += 4;
}
a->chunkinfo[i].sample = 0;
a->chunkinfo[i].offset = 0;
// fill in first sample id for each chunk from stored stsc
if (a->stsc) {
u32_t stsc_entries = unpackN((u32_t *)a->stsc);
u32_t sample = 0;
u32_t last = 0, last_samples = 0;
u8_t *ptr = (u8_t *)a->stsc + 4;
while (stsc_entries--) {
u32_t first = unpackN((u32_t *)ptr);
u32_t samples = unpackN((u32_t *)(ptr + 4));
if (last) {
for (i = last - 1; i < first - 1; ++i) {
a->chunkinfo[i].sample = sample;
sample += last_samples;
}
}
if (stsc_entries == 0) {
for (i = first - 1; i < entries; ++i) {
a->chunkinfo[i].sample = sample;
sample += samples;
}
}
last = first;
last_samples = samples;
ptr += 12;
}
free(a->stsc);
a->stsc = NULL;
}
}
// found media data, advance to start of first chunk and return
if (!strcmp(type, "mdat")) {
_buf_inc_readp(streambuf, 8);
a->pos += 8;
bytes -= 8;
if (play) {
LOG_DEBUG("type: mdat len: %u pos: %u", len, a->pos);
if (a->chunkinfo && a->chunkinfo[0].offset > a->pos) {
u32_t skip = a->chunkinfo[0].offset - a->pos;
LOG_DEBUG("skipping: %u", skip);
if (skip <= bytes) {
_buf_inc_readp(streambuf, skip);
a->pos += skip;
} else {
a->consume = skip;
}
}
a->sample = a->nextchunk = 1;
return 1;
} else {
LOG_DEBUG("type: mdat len: %u, no playable track found", len);
return -1;
}
}
// parse key-value atoms within ilst ---- entries to get encoder padding within iTunSMPB entry for gapless
if (!strcmp(type, "----") && bytes > len) {
u8_t *ptr = streambuf->readp + 8;
u32_t remain = len - 8, size;
if (!memcmp(ptr + 4, "mean", 4) && (size = unpackN((u32_t *)ptr)) < remain) {
ptr += size; remain -= size;
}
if (!memcmp(ptr + 4, "name", 4) && (size = unpackN((u32_t *)ptr)) < remain && !memcmp(ptr + 12, "iTunSMPB", 8)) {
ptr += size; remain -= size;
}
if (!memcmp(ptr + 4, "data", 4) && remain > 16 + 48) {
// data is stored as hex strings: 0 start end samples
u32_t b, c; u64_t d;
if (sscanf((const char *)(ptr + 16), "%x %x %x " FMT_x64, &b, &b, &c, &d) == 4) {
LOG_DEBUG("iTunSMPB start: %u end: %u samples: " FMT_u64, b, c, d);
if (a->sttssamples && a->sttssamples < b + c + d) {
LOG_DEBUG("reducing samples as stts count is less");
d = a->sttssamples - (b + c);
}
a->skip = b;
a->samples = d;
}
}
}
// default to consuming entire box
consume = len;
// read into these boxes so reduce consume
if (!strcmp(type, "moov") || !strcmp(type, "trak") || !strcmp(type, "mdia") || !strcmp(type, "minf") || !strcmp(type, "stbl") ||
!strcmp(type, "udta") || !strcmp(type, "ilst")) {
consume = 8;
}
// special cases which mix mix data in the enclosing box which we want to read into
if (!strcmp(type, "stsd")) consume = 16;
if (!strcmp(type, "mp4a")) consume = 36;
if (!strcmp(type, "meta")) consume = 12;
// consume rest of box if it has been parsed (all in the buffer) or is not one we want to parse
if (bytes >= consume) {
LOG_DEBUG("type: %s len: %u consume: %u", type, len, consume);
_buf_inc_readp(streambuf, consume);
a->pos += consume;
bytes -= consume;
} else if ( !(!strcmp(type, "esds") || !strcmp(type, "stts") || !strcmp(type, "stsc") ||
!strcmp(type, "stco") || !strcmp(type, "----")) ) {
LOG_DEBUG("type: %s len: %u consume: %u - partial consume: %u", type, len, consume, bytes);
_buf_inc_readp(streambuf, bytes);
a->pos += bytes;
a->consume = consume - bytes;
break;
} else {
break;
}
}
return 0;
}
static decode_state faad_decode(void) {
size_t bytes_total;
size_t bytes_wrap;
static NeAACDecFrameInfo info;
ISAMPLE_T *iptr;
bool endstream;
frames_t frames;
LOCK_S;
bytes_total = _buf_used(streambuf);
bytes_wrap = min(bytes_total, _buf_cont_read(streambuf));
if (stream.state <= DISCONNECT && !bytes_total) {
UNLOCK_S;
return DECODE_COMPLETE;
}
if (a->consume) {
u32_t consume = min(a->consume, bytes_wrap);
LOG_DEBUG("consume: %u of %u", consume, a->consume);
_buf_inc_readp(streambuf, consume);
a->pos += consume;
a->consume -= consume;
UNLOCK_S;
return DECODE_RUNNING;
}
if (decode.new_stream) {
int found = 0;
static unsigned char channels;
static unsigned long samplerate;
if (a->type == '2') {
// adts stream - seek for header
while (bytes_wrap >= 2 && (*(streambuf->readp) != 0xFF || (*(streambuf->readp + 1) & 0xF6) != 0xF0)) {
_buf_inc_readp(streambuf, 1);
bytes_total--;
bytes_wrap--;
}
if (bytes_wrap >= 2) {
long n = NEAAC(a, Init, a->hAac, streambuf->readp, bytes_wrap, &samplerate, &channels);
if (n < 0) {
found = -1;
} else {
_buf_inc_readp(streambuf, n);
found = 1;
}
}
} else {
// mp4 - read header
found = read_mp4_header(&samplerate, &channels);
}
if (found == 1) {
LOG_INFO("samplerate: %u channels: %u", samplerate, channels);
bytes_total = _buf_used(streambuf);
bytes_wrap = min(bytes_total, _buf_cont_read(streambuf));
LOCK_O;
LOG_INFO("setting track_start");
output.next_sample_rate = decode_newstream(samplerate, output.supported_rates);
IF_DSD( output.next_fmt = PCM; )
output.track_start = outputbuf->writep;
if (output.fade_mode) _checkfade(true);
decode.new_stream = false;
UNLOCK_O;
} else if (found == -1) {
LOG_WARN("error reading stream header");
UNLOCK_S;
return DECODE_ERROR;
} else {
// not finished header parsing come back next time
UNLOCK_S;
return DECODE_RUNNING;
}
}
if (bytes_wrap < WRAPBUF_LEN && bytes_total > WRAPBUF_LEN) {
// make a local copy of frames which may have wrapped round the end of streambuf
static u8_t buf[WRAPBUF_LEN];
memcpy(buf, streambuf->readp, bytes_wrap);
memcpy(buf + bytes_wrap, streambuf->buf, WRAPBUF_LEN - bytes_wrap);
iptr = NEAAC(a, Decode, a->hAac, &info, buf, WRAPBUF_LEN);
} else {
iptr = NEAAC(a, Decode, a->hAac, &info, streambuf->readp, bytes_wrap);
}
if (info.error) {
LOG_WARN("error: %u %s", info.error, NEAAC(a, GetErrorMessage, info.error));
}
endstream = false;
// mp4 end of chunk - skip to next offset
if (a->chunkinfo && a->chunkinfo[a->nextchunk].offset && a->sample++ == a->chunkinfo[a->nextchunk].sample) {
if (a->chunkinfo[a->nextchunk].offset > a->pos) {
u32_t skip = a->chunkinfo[a->nextchunk].offset - a->pos;
if (skip != info.bytesconsumed) {
LOG_DEBUG("skipping to next chunk pos: %u consumed: %u != skip: %u", a->pos, info.bytesconsumed, skip);
}
if (bytes_total >= skip) {
_buf_inc_readp(streambuf, skip);
a->pos += skip;
} else {
a->consume = skip;
}
a->nextchunk++;
} else {
LOG_ERROR("error: need to skip backwards!");
endstream = true;
}
// adts and mp4 when not at end of chunk
} else if (info.bytesconsumed != 0) {
_buf_inc_readp(streambuf, info.bytesconsumed);
a->pos += info.bytesconsumed;
// error which doesn't advance streambuf - end
} else {
endstream = true;
}
UNLOCK_S;
if (endstream) {
LOG_WARN("unable to decode further");
return DECODE_ERROR;
}
if (!info.samples) {
a->empty = true;
return DECODE_RUNNING;
}
frames = info.samples / info.channels;
if (a->skip) {
u32_t skip;
if (a->empty) {
a->empty = false;
a->skip -= frames;
LOG_DEBUG("gapless: first frame empty, skipped %u frames at start", frames);
}
skip = min(frames, a->skip);
LOG_DEBUG("gapless: skipping %u frames at start", skip);
frames -= skip;
a->skip -= skip;
iptr += skip * info.channels;
}
if (a->samples) {
if (a->samples < frames) {
LOG_DEBUG("gapless: trimming %u frames from end", frames - a->samples);
frames = (frames_t)a->samples;
}
a->samples -= frames;
}
LOG_SDEBUG("write %u frames", frames);
LOCK_O_direct;
while (frames > 0) {
frames_t f;
frames_t count;
ISAMPLE_T *optr;
IF_DIRECT(
f = _buf_cont_write(outputbuf) / BYTES_PER_FRAME;
optr = (ISAMPLE_T *)outputbuf->writep;
);
IF_PROCESS(
f = process.max_in_frames;
optr = (ISAMPLE_T *)process.inbuf;
);
f = min(f, frames);
count = f;
if (info.channels == 2) {
#if BYTES_PER_FRAME == 4
memcpy(optr, iptr, count * BYTES_PER_FRAME);
iptr += count * 2;
#else
while (count--) {
*optr++ = *iptr++ << 8;
*optr++ = *iptr++ << 8;
}
#endif
} else if (info.channels == 1) {
while (count--) {
*optr++ = ALIGN(*iptr);
*optr++ = ALIGN(*iptr++);
}
} else {
LOG_WARN("unsupported number of channels");
}
frames -= f;
IF_DIRECT(
_buf_inc_writep(outputbuf, f * BYTES_PER_FRAME);
);
IF_PROCESS(
process.in_frames = f;
if (frames) LOG_ERROR("unhandled case");
);
}
UNLOCK_O_direct;
return DECODE_RUNNING;
}
static void faad_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
NeAACDecConfigurationPtr conf;
LOG_INFO("opening %s stream", size == '2' ? "adts" : "mp4");
a->type = size;
a->pos = a->consume = a->sample = a->nextchunk = 0;
if (a->chunkinfo) {
free(a->chunkinfo);
}
if (a->stsc) {
free(a->stsc);
}
a->chunkinfo = NULL;
a->stsc = NULL;
a->skip = 0;
a->samples = 0;
a->sttssamples = 0;
a->empty = false;
if (a->hAac) {
NEAAC(a, Close, a->hAac);
}
a->hAac = NEAAC(a, Open);
conf = NEAAC(a, GetCurrentConfiguration, a->hAac);
#if BYTES_PER_FRAME == 4
conf->outputFormat = FAAD_FMT_16BIT;
#else
conf->outputFormat = FAAD_FMT_24BIT;
#endif
conf->defSampleRate = 44100;
conf->downMatrix = 1;
if (!NEAAC(a, SetConfiguration, a->hAac, conf)) {
LOG_WARN("error setting config");
};
}
static void faad_close(void) {
NEAAC(a, Close, a->hAac);
a->hAac = NULL;
if (a->chunkinfo) {
free(a->chunkinfo);
a->chunkinfo = NULL;
}
if (a->stsc) {
free(a->stsc);
a->stsc = NULL;
}
}
static bool load_faad() {
#if !LINKALL
void *handle = dlopen(LIBFAAD, RTLD_NOW);
char *err;
if (!handle) {
LOG_INFO("dlerror: %s", dlerror());
return false;
}
a->NeAACDecGetCurrentConfiguration = dlsym(handle, "NeAACDecGetCurrentConfiguration");
a->NeAACDecSetConfiguration = dlsym(handle, "NeAACDecSetConfiguration");
a->NeAACDecOpen = dlsym(handle, "NeAACDecOpen");
a->NeAACDecClose = dlsym(handle, "NeAACDecClose");
a->NeAACDecInit = dlsym(handle, "NeAACDecInit");
a->NeAACDecInit2 = dlsym(handle, "NeAACDecInit2");
a->NeAACDecDecode = dlsym(handle, "NeAACDecDecode");
a->NeAACDecGetErrorMessage = dlsym(handle, "NeAACDecGetErrorMessage");
if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err);
return false;
}
LOG_INFO("loaded "LIBFAAD"");
#endif
return true;
}
struct codec *register_faad(void) {
static struct codec ret = {
'a', // id
"aac", // types
WRAPBUF_LEN, // min read
20480, // min space
faad_open, // open
faad_close, // close
faad_decode, // decode
};
a = malloc(sizeof(struct faad));
if (!a) {
return NULL;
}
a->hAac = NULL;
a->chunkinfo = NULL;
a->stsc = NULL;
if (!load_faad()) {
return NULL;
}
LOG_INFO("using faad to decode aac");
return &ret;
}

View File

@@ -0,0 +1,308 @@
/*
* Squeezelite - lightweight headless squeezeplay emulator for linux
*
* (c) Adrian Smith 2012, triode1@btinternet.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 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/>.
*
*/
#include "squeezelite.h"
#include <FLAC/stream_decoder.h>
#if BYTES_PER_FRAME == 4
#define ALIGN8(n) (n << 8)
#define ALIGN16(n) (n)
#define ALIGN24(n) (n >> 8)
#define ALIGN32(n) (n >> 16)
#else
#define ALIGN8(n) (n << 24)
#define ALIGN16(n) (n << 16)
#define ALIGN24(n) (n << 8)
#define ALIGN32(n) (n)
#endif
struct flac {
FLAC__StreamDecoder *decoder;
#if !LINKALL
// FLAC symbols to be dynamically loaded
const char **FLAC__StreamDecoderErrorStatusString;
const char **FLAC__StreamDecoderStateString;
FLAC__StreamDecoder * (* FLAC__stream_decoder_new)(void);
FLAC__bool (* FLAC__stream_decoder_reset)(FLAC__StreamDecoder *decoder);
void (* FLAC__stream_decoder_delete)(FLAC__StreamDecoder *decoder);
FLAC__StreamDecoderInitStatus (* FLAC__stream_decoder_init_stream)(
FLAC__StreamDecoder *decoder,
FLAC__StreamDecoderReadCallback read_callback,
FLAC__StreamDecoderSeekCallback seek_callback,
FLAC__StreamDecoderTellCallback tell_callback,
FLAC__StreamDecoderLengthCallback length_callback,
FLAC__StreamDecoderEofCallback eof_callback,
FLAC__StreamDecoderWriteCallback write_callback,
FLAC__StreamDecoderMetadataCallback metadata_callback,
FLAC__StreamDecoderErrorCallback error_callback,
void *client_data
);
FLAC__bool (* FLAC__stream_decoder_process_single)(FLAC__StreamDecoder *decoder);
FLAC__StreamDecoderState (* FLAC__stream_decoder_get_state)(const FLAC__StreamDecoder *decoder);
#endif
};
static struct flac *f;
extern log_level loglevel;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct decodestate decode;
extern struct processstate process;
#define LOCK_S mutex_lock(streambuf->mutex)
#define UNLOCK_S mutex_unlock(streambuf->mutex)
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
#if PROCESS
#define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x) if (decode.direct) { x }
#define IF_PROCESS(x) if (!decode.direct) { x }
#else
#define LOCK_O_direct mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x) { x }
#define IF_PROCESS(x)
#endif
#if LINKALL
#define FLAC(h, fn, ...) (FLAC__ ## fn)(__VA_ARGS__)
#define FLAC_A(h, a) (FLAC__ ## a)
#else
#define FLAC(h, fn, ...) (h)->FLAC__##fn(__VA_ARGS__)
#define FLAC_A(h, a) (h)->FLAC__ ## a
#endif
static FLAC__StreamDecoderReadStatus read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *want, void *client_data) {
size_t bytes;
bool end;
LOCK_S;
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
bytes = min(bytes, *want);
end = (stream.state <= DISCONNECT && bytes == 0);
memcpy(buffer, streambuf->readp, bytes);
_buf_inc_readp(streambuf, bytes);
UNLOCK_S;
*want = bytes;
return end ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
}
static FLAC__StreamDecoderWriteStatus write_cb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame,
const FLAC__int32 *const buffer[], void *client_data) {
size_t frames = frame->header.blocksize;
unsigned bits_per_sample = frame->header.bits_per_sample;
unsigned channels = frame->header.channels;
FLAC__int32 *lptr = (FLAC__int32 *)buffer[0];
FLAC__int32 *rptr = (FLAC__int32 *)buffer[channels > 1 ? 1 : 0];
if (decode.new_stream) {
LOCK_O;
LOG_INFO("setting track_start");
output.track_start = outputbuf->writep;
decode.new_stream = false;
#if DSD
#if SL_LITTLE_ENDIAN
#define MARKER_OFFSET 2
#else
#define MARKER_OFFSET 1
#endif
if (bits_per_sample == 24 && is_stream_dop(((u8_t *)lptr) + MARKER_OFFSET, ((u8_t *)rptr) + MARKER_OFFSET, 4, frames)) {
LOG_INFO("file contains DOP");
if (output.dsdfmt == DOP_S24_LE || output.dsdfmt == DOP_S24_3LE)
output.next_fmt = output.dsdfmt;
else
output.next_fmt = DOP;
output.next_sample_rate = frame->header.sample_rate;
output.fade = FADE_INACTIVE;
} else {
output.next_sample_rate = decode_newstream(frame->header.sample_rate, output.supported_rates);
output.next_fmt = PCM;
if (output.fade_mode) _checkfade(true);
}
#else
output.next_sample_rate = decode_newstream(frame->header.sample_rate, output.supported_rates);
if (output.fade_mode) _checkfade(true);
#endif
UNLOCK_O;
}
LOCK_O_direct;
while (frames > 0) {
frames_t f;
frames_t count;
ISAMPLE_T *optr;
IF_DIRECT(
optr = (ISAMPLE_T *)outputbuf->writep;
f = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
);
IF_PROCESS(
optr = (ISAMPLE_T *)process.inbuf;
f = process.max_in_frames;
);
f = min(f, frames);
count = f;
if (bits_per_sample == 8) {
while (count--) {
*optr++ = ALIGN8(*lptr++);
*optr++ = ALIGN8(*rptr++);
}
} else if (bits_per_sample == 16) {
while (count--) {
*optr++ = ALIGN16(*lptr++);
*optr++ = ALIGN16(*rptr++);
}
} else if (bits_per_sample == 24) {
while (count--) {
*optr++ = ALIGN24(*lptr++);
*optr++ = ALIGN24(*rptr++);
}
} else if (bits_per_sample == 32) {
while (count--) {
*optr++ = ALIGN32(*lptr++);
*optr++ = ALIGN32(*rptr++);
}
} else {
LOG_ERROR("unsupported bits per sample: %u", bits_per_sample);
}
frames -= f;
IF_DIRECT(
_buf_inc_writep(outputbuf, f * BYTES_PER_FRAME);
);
IF_PROCESS(
process.in_frames = f;
if (frames) LOG_ERROR("unhandled case");
);
}
UNLOCK_O_direct;
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
static void error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) {
LOG_INFO("flac error: %s", FLAC_A(f, StreamDecoderErrorStatusString)[status]);
}
static void flac_open(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness) {
if (f->decoder) {
FLAC(f, stream_decoder_reset, f->decoder);
} else {
f->decoder = FLAC(f, stream_decoder_new);
}
FLAC(f, stream_decoder_init_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL);
}
static void flac_close(void) {
FLAC(f, stream_decoder_delete, f->decoder);
f->decoder = NULL;
}
static decode_state flac_decode(void) {
bool ok = FLAC(f, stream_decoder_process_single, f->decoder);
FLAC__StreamDecoderState state = FLAC(f, stream_decoder_get_state, f->decoder);
if (!ok && state != FLAC__STREAM_DECODER_END_OF_STREAM) {
LOG_INFO("flac error: %s", FLAC_A(f, StreamDecoderStateString)[state]);
};
if (state == FLAC__STREAM_DECODER_END_OF_STREAM) {
return DECODE_COMPLETE;
} else if (state > FLAC__STREAM_DECODER_END_OF_STREAM) {
return DECODE_ERROR;
} else {
return DECODE_RUNNING;
}
}
static bool load_flac() {
#if !LINKALL
void *handle = dlopen(LIBFLAC, RTLD_NOW);
char *err;
if (!handle) {
LOG_INFO("dlerror: %s", dlerror());
return false;
}
f->FLAC__StreamDecoderErrorStatusString = dlsym(handle, "FLAC__StreamDecoderErrorStatusString");
f->FLAC__StreamDecoderStateString = dlsym(handle, "FLAC__StreamDecoderStateString");
f->FLAC__stream_decoder_new = dlsym(handle, "FLAC__stream_decoder_new");
f->FLAC__stream_decoder_reset = dlsym(handle, "FLAC__stream_decoder_reset");
f->FLAC__stream_decoder_delete = dlsym(handle, "FLAC__stream_decoder_delete");
f->FLAC__stream_decoder_init_stream = dlsym(handle, "FLAC__stream_decoder_init_stream");
f->FLAC__stream_decoder_process_single = dlsym(handle, "FLAC__stream_decoder_process_single");
f->FLAC__stream_decoder_get_state = dlsym(handle, "FLAC__stream_decoder_get_state");
if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err);
return false;
}
LOG_INFO("loaded "LIBFLAC);
#endif
return true;
}
struct codec *register_flac(void) {
static struct codec ret = {
'f', // id
"flc", // types
16384, // min read
204800, // min space
flac_open, // open
flac_close, // close
flac_decode, // decode
};
f = malloc(sizeof(struct flac));
if (!f) {
return NULL;
}
f->decoder = NULL;
if (!load_flac()) {
return NULL;
}
LOG_INFO("using flac to decode flc");
return &ret;
}

View File

@@ -0,0 +1,658 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
* Philippe, 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 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/>.
*
*/
#include "squeezelite.h"
#include <aacdec.h>
// AAC_MAX_SAMPLES is the number of samples for one channel
#define FRAME_BUF (AAC_MAX_NSAMPS*2)
#if BYTES_PER_FRAME == 4
#define ALIGN(n) (n)
#else
#define ALIGN(n) (n << 8)
#endif
#define WRAPBUF_LEN 2048
static unsigned rates[] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 };
struct chunk_table {
u32_t sample, offset;
};
struct helixaac {
HAACDecoder hAac;
u8_t type;
u8_t *write_buf;
// following used for mp4 only
u32_t consume;
u32_t pos;
u32_t sample;
u32_t nextchunk;
void *stsc;
u32_t skip;
u64_t samples;
u64_t sttssamples;
bool empty;
struct chunk_table *chunkinfo;
#if !LINKALL
#endif
};
static struct helixaac *a;
extern log_level loglevel;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct decodestate decode;
extern struct processstate process;
#define LOCK_S mutex_lock(streambuf->mutex)
#define UNLOCK_S mutex_unlock(streambuf->mutex)
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
#if PROCESS
#define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x) if (decode.direct) { x }
#define IF_PROCESS(x) if (!decode.direct) { x }
#else
#define LOCK_O_direct mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x) { x }
#define IF_PROCESS(x)
#endif
#if LINKALL
#define HAAC(h, fn, ...) (AAC ## fn)(__VA_ARGS__)
#else
#define HAAC(h, fn, ...) (h)->AAC##fn(__VA_ARGS__)
#endif
// minimal code for mp4 file parsing to extract audio config and find media data
// adapted from faad2/common/mp4ff
u32_t mp4_desc_length(u8_t **buf) {
u8_t b;
u8_t num_bytes = 0;
u32_t length = 0;
do {
b = **buf;
*buf += 1;
num_bytes++;
length = (length << 7) | (b & 0x7f);
} while ((b & 0x80) && num_bytes < 4);
return length;
}
// read mp4 header to extract config data
static int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_p) {
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
char type[5];
u32_t len;
while (bytes >= 8) {
// count trak to find the first playable one
static unsigned trak, play;
u32_t consume;
len = unpackN((u32_t *)streambuf->readp);
memcpy(type, streambuf->readp + 4, 4);
type[4] = '\0';
if (!strcmp(type, "moov")) {
trak = 0;
play = 0;
}
if (!strcmp(type, "trak")) {
trak++;
}
// extract audio config from within esds and pass to DecInit2
if (!strcmp(type, "esds") && bytes > len) {
u8_t *ptr = streambuf->readp + 12;
AACFrameInfo info;
if (*ptr++ == 0x03) {
mp4_desc_length(&ptr);
ptr += 4;
} else {
ptr += 3;
}
mp4_desc_length(&ptr);
ptr += 13;
if (*ptr++ != 0x05) {
LOG_WARN("error parsing esds");
return -1;
}
mp4_desc_length(&ptr);
info.profile = *ptr >> 3;
info.sampRateCore = (*ptr++ & 0x07) << 1;
info.sampRateCore |= (*ptr >> 7) & 0x01;
info.sampRateCore = rates[info.sampRateCore];
info.nChans = *ptr >> 3;
*channels_p = info.nChans;
*samplerate_p = info.sampRateCore;
HAAC(a, SetRawBlockParams, a->hAac, 0, &info);
LOG_DEBUG("playable aac track: %u (p:%x, r:%d, c:%d)", trak, info.profile, info.sampRateCore, info.nChans);
play = trak;
}
// extract the total number of samples from stts
if (!strcmp(type, "stts") && bytes > len) {
u32_t i;
u8_t *ptr = streambuf->readp + 12;
u32_t entries = unpackN((u32_t *)ptr);
ptr += 4;
for (i = 0; i < entries; ++i) {
u32_t count = unpackN((u32_t *)ptr);
u32_t size = unpackN((u32_t *)(ptr + 4));
a->sttssamples += count * size;
ptr += 8;
}
LOG_DEBUG("total number of samples contained in stts: " FMT_u64, a->sttssamples);
}
// stash sample to chunk info, assume it comes before stco
if (!strcmp(type, "stsc") && bytes > len && !a->chunkinfo) {
a->stsc = malloc(len - 12);
if (a->stsc == NULL) {
LOG_WARN("malloc fail");
return -1;
}
memcpy(a->stsc, streambuf->readp + 12, len - 12);
}
// build offsets table from stco and stored stsc
if (!strcmp(type, "stco") && bytes > len && play == trak) {
u32_t i;
// extract chunk offsets
u8_t *ptr = streambuf->readp + 12;
u32_t entries = unpackN((u32_t *)ptr);
ptr += 4;
a->chunkinfo = malloc(sizeof(struct chunk_table) * (entries + 1));
if (a->chunkinfo == NULL) {
LOG_WARN("malloc fail");
return -1;
}
for (i = 0; i < entries; ++i) {
a->chunkinfo[i].offset = unpackN((u32_t *)ptr);
a->chunkinfo[i].sample = 0;
ptr += 4;
}
a->chunkinfo[i].sample = 0;
a->chunkinfo[i].offset = 0;
// fill in first sample id for each chunk from stored stsc
if (a->stsc) {
u32_t stsc_entries = unpackN((u32_t *)a->stsc);
u32_t sample = 0;
u32_t last = 0, last_samples = 0;
u8_t *ptr = (u8_t *)a->stsc + 4;
while (stsc_entries--) {
u32_t first = unpackN((u32_t *)ptr);
u32_t samples = unpackN((u32_t *)(ptr + 4));
if (last) {
for (i = last - 1; i < first - 1; ++i) {
a->chunkinfo[i].sample = sample;
sample += last_samples;
}
}
if (stsc_entries == 0) {
for (i = first - 1; i < entries; ++i) {
a->chunkinfo[i].sample = sample;
sample += samples;
}
}
last = first;
last_samples = samples;
ptr += 12;
}
free(a->stsc);
a->stsc = NULL;
}
}
// found media data, advance to start of first chunk and return
if (!strcmp(type, "mdat")) {
_buf_inc_readp(streambuf, 8);
a->pos += 8;
bytes -= 8;
if (play) {
LOG_DEBUG("type: mdat len: %u pos: %u", len, a->pos);
if (a->chunkinfo && a->chunkinfo[0].offset > a->pos) {
u32_t skip = a->chunkinfo[0].offset - a->pos;
LOG_DEBUG("skipping: %u", skip);
if (skip <= bytes) {
_buf_inc_readp(streambuf, skip);
a->pos += skip;
} else {
a->consume = skip;
}
}
a->sample = a->nextchunk = 1;
return 1;
} else {
LOG_DEBUG("type: mdat len: %u, no playable track found", len);
return -1;
}
}
// parse key-value atoms within ilst ---- entries to get encoder padding within iTunSMPB entry for gapless
if (!strcmp(type, "----") && bytes > len) {
u8_t *ptr = streambuf->readp + 8;
u32_t remain = len - 8, size;
if (!memcmp(ptr + 4, "mean", 4) && (size = unpackN((u32_t *)ptr)) < remain) {
ptr += size; remain -= size;
}
if (!memcmp(ptr + 4, "name", 4) && (size = unpackN((u32_t *)ptr)) < remain && !memcmp(ptr + 12, "iTunSMPB", 8)) {
ptr += size; remain -= size;
}
if (!memcmp(ptr + 4, "data", 4) && remain > 16 + 48) {
// data is stored as hex strings: 0 start end samples
u32_t b, c; u64_t d;
if (sscanf((const char *)(ptr + 16), "%x %x %x " FMT_x64, &b, &b, &c, &d) == 4) {
LOG_DEBUG("iTunSMPB start: %u end: %u samples: " FMT_u64, b, c, d);
if (a->sttssamples && a->sttssamples < b + c + d) {
LOG_DEBUG("reducing samples as stts count is less");
d = a->sttssamples - (b + c);
}
a->skip = b;
a->samples = d;
}
}
}
// default to consuming entire box
consume = len;
// read into these boxes so reduce consume
if (!strcmp(type, "moov") || !strcmp(type, "trak") || !strcmp(type, "mdia") || !strcmp(type, "minf") || !strcmp(type, "stbl") ||
!strcmp(type, "udta") || !strcmp(type, "ilst")) {
consume = 8;
}
// special cases which mix mix data in the enclosing box which we want to read into
if (!strcmp(type, "stsd")) consume = 16;
if (!strcmp(type, "mp4a")) consume = 36;
if (!strcmp(type, "meta")) consume = 12;
// consume rest of box if it has been parsed (all in the buffer) or is not one we want to parse
if (bytes >= consume) {
LOG_DEBUG("type: %s len: %u consume: %u", type, len, consume);
_buf_inc_readp(streambuf, consume);
a->pos += consume;
bytes -= consume;
} else if ( !(!strcmp(type, "esds") || !strcmp(type, "stts") || !strcmp(type, "stsc") ||
!strcmp(type, "stco") || !strcmp(type, "----")) ) {
LOG_DEBUG("type: %s len: %u consume: %u - partial consume: %u", type, len, consume, bytes);
_buf_inc_readp(streambuf, bytes);
a->pos += bytes;
a->consume = consume - bytes;
break;
} else {
break;
}
}
return 0;
}
static decode_state helixaac_decode(void) {
size_t bytes_total, bytes_wrap;
int res, bytes;
static AACFrameInfo info;
ISAMPLE_T *iptr;
u8_t *sptr;
bool endstream;
frames_t frames;
LOCK_S;
bytes_total = _buf_used(streambuf);
bytes_wrap = min(bytes_total, _buf_cont_read(streambuf));
if (stream.state <= DISCONNECT && !bytes_total) {
UNLOCK_S;
return DECODE_COMPLETE;
}
if (a->consume) {
u32_t consume = min(a->consume, bytes_wrap);
LOG_DEBUG("consume: %u of %u", consume, a->consume);
_buf_inc_readp(streambuf, consume);
a->pos += consume;
a->consume -= consume;
UNLOCK_S;
return DECODE_RUNNING;
}
if (decode.new_stream) {
int found = 0;
static unsigned char channels;
static unsigned long samplerate;
if (a->type == '2') {
// adts stream - seek for header
long n = AACFindSyncWord(streambuf->readp, bytes_wrap);
LOG_DEBUG("Sync search in %d bytes %d", bytes_wrap, n);
if (n >= 0) {
u8_t *p = streambuf->readp + n;
int bytes = bytes_wrap - n;
if (!HAAC(a, Decode, a->hAac, &p, &bytes, (short*) a->write_buf)) {
HAAC(a, GetLastFrameInfo, a->hAac, &info);
channels = info.nChans;
samplerate = info.sampRateOut;
found = 1;
}
HAAC(a, FlushCodec, a->hAac);
bytes_total -= n;
bytes_wrap -= n;
_buf_inc_readp(streambuf, n);
} else {
found = -1;
}
} else {
// mp4 - read header
found = read_mp4_header(&samplerate, &channels);
}
if (found == 1) {
LOG_INFO("samplerate: %u channels: %u", samplerate, channels);
bytes_total = _buf_used(streambuf);
bytes_wrap = min(bytes_total, _buf_cont_read(streambuf));
LOCK_O;
LOG_INFO("setting track_start");
output.next_sample_rate = decode_newstream(samplerate, output.supported_rates);
IF_DSD( output.next_fmt = PCM; )
output.track_start = outputbuf->writep;
if (output.fade_mode) _checkfade(true);
decode.new_stream = false;
UNLOCK_O;
} else if (found == -1) {
LOG_WARN("error reading stream header");
UNLOCK_S;
return DECODE_ERROR;
} else {
// not finished header parsing come back next time
UNLOCK_S;
return DECODE_RUNNING;
}
}
if (bytes_wrap < WRAPBUF_LEN && bytes_total > WRAPBUF_LEN) {
// make a local copy of frames which may have wrapped round the end of streambuf
static u8_t buf[WRAPBUF_LEN];
memcpy(buf, streambuf->readp, bytes_wrap);
memcpy(buf + bytes_wrap, streambuf->buf, WRAPBUF_LEN - bytes_wrap);
sptr = buf;
bytes = bytes_wrap = WRAPBUF_LEN;
} else {
sptr = streambuf->readp;
bytes = bytes_wrap;
}
// decode function changes iptr, so can't use streambuf->readp (same for bytes)
res = HAAC(a, Decode, a->hAac, &sptr, &bytes, (short*) a->write_buf);
if (res < 0) {
LOG_WARN("AAC decode error %d", res);
}
HAAC(a, GetLastFrameInfo, a->hAac, &info);
iptr = (ISAMPLE_T *) a->write_buf;
bytes = bytes_wrap - bytes;
endstream = false;
// mp4 end of chunk - skip to next offset
if (a->chunkinfo && a->chunkinfo[a->nextchunk].offset && a->sample++ == a->chunkinfo[a->nextchunk].sample) {
if (a->chunkinfo[a->nextchunk].offset > a->pos) {
u32_t skip = a->chunkinfo[a->nextchunk].offset - a->pos;
if (skip != bytes) {
LOG_DEBUG("skipping to next chunk pos: %u consumed: %u != skip: %u", a->pos, bytes, skip);
}
if (bytes_total >= skip) {
_buf_inc_readp(streambuf, skip);
a->pos += skip;
} else {
a->consume = skip;
}
a->nextchunk++;
} else {
LOG_ERROR("error: need to skip backwards!");
endstream = true;
}
// adts and mp4 when not at end of chunk
} else if (bytes > 0) {
_buf_inc_readp(streambuf, bytes);
a->pos += bytes;
// error which doesn't advance streambuf - end
} else {
endstream = true;
}
UNLOCK_S;
if (endstream) {
LOG_WARN("unable to decode further");
return DECODE_ERROR;
}
if (!info.outputSamps) {
a->empty = true;
return DECODE_RUNNING;
}
frames = info.outputSamps / info.nChans;
if (a->skip) {
u32_t skip;
if (a->empty) {
a->empty = false;
a->skip -= frames;
LOG_DEBUG("gapless: first frame empty, skipped %u frames at start", frames);
}
skip = min(frames, a->skip);
LOG_DEBUG("gapless: skipping %u frames at start", skip);
frames -= skip;
a->skip -= skip;
iptr += skip * info.nChans;
}
if (a->samples) {
if (a->samples < frames) {
LOG_DEBUG("gapless: trimming %u frames from end", frames - a->samples);
frames = (frames_t)a->samples;
}
a->samples -= frames;
}
LOG_SDEBUG("write %u frames", frames);
LOCK_O_direct;
while (frames > 0) {
frames_t f;
frames_t count;
ISAMPLE_T *optr;
IF_DIRECT(
f = _buf_cont_write(outputbuf) / BYTES_PER_FRAME;
optr = (ISAMPLE_T *)outputbuf->writep;
);
IF_PROCESS(
f = process.max_in_frames;
optr = (ISAMPLE_T *)process.inbuf;
);
f = min(f, frames);
count = f;
if (info.nChans == 2) {
#if BYTES_PER_FRAME == 4
memcpy(optr, iptr, count * BYTES_PER_FRAME);
iptr += count * 2;
#else
while (count--) {
*optr++ = *iptr++ << 8;
*optr++ = *iptr++ << 8;
}
#endif
} else if (info.nChans == 1) {
while (count--) {
*optr++ = ALIGN(*iptr);
*optr++ = ALIGN(*iptr++);
}
} else {
LOG_WARN("unsupported number of channels");
}
frames -= f;
IF_DIRECT(
_buf_inc_writep(outputbuf, f * BYTES_PER_FRAME);
);
IF_PROCESS(
process.in_frames = f;
if (frames) LOG_ERROR("unhandled case");
);
}
UNLOCK_O_direct;
return DECODE_RUNNING;
}
static void helixaac_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
LOG_INFO("opening %s stream", size == '2' ? "adts" : "mp4");
a->type = size;
a->pos = a->consume = a->sample = a->nextchunk = 0;
if (a->chunkinfo) {
free(a->chunkinfo);
}
if (a->stsc) {
free(a->stsc);
}
a->chunkinfo = NULL;
a->stsc = NULL;
a->skip = 0;
a->samples = 0;
a->sttssamples = 0;
a->empty = false;
if (a->hAac) {
HAAC(a, FlushCodec, a->hAac);
} else {
a->hAac = HAAC(a, InitDecoder);
a->write_buf = malloc(FRAME_BUF * BYTES_PER_FRAME);
}
}
static void helixaac_close(void) {
HAAC(a, FreeDecoder, a->hAac);
a->hAac = NULL;
if (a->chunkinfo) {
free(a->chunkinfo);
a->chunkinfo = NULL;
}
if (a->stsc) {
free(a->stsc);
a->stsc = NULL;
}
free(a->write_buf);
}
static bool load_helixaac() {
#if !LINKALL
void *handle = dlopen(LIBHELIX-AAC, RTLD_NOW);
char *err;
if (!handle) {
LOG_INFO("dlerror: %s", dlerror());
return false;
}
// load symbols here
if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err);
return false;
}
LOG_INFO("loaded "LIBHELIX-AAC"");
#endif
return true;
}
struct codec *register_helixaac(void) {
static struct codec ret = {
'a', // id
"aac", // types
WRAPBUF_LEN, // min read
20480, // min space
helixaac_open, // open
helixaac_close, // close
helixaac_decode, // decode
};
a = malloc(sizeof(struct helixaac));
if (!a) {
return NULL;
}
a->hAac = NULL;
a->chunkinfo = NULL;
a->stsc = NULL;
if (!load_helixaac()) {
return NULL;
}
LOG_INFO("using helix-aac to decode aac");
return &ret;
}

View File

@@ -0,0 +1,419 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
#include "squeezelite.h"
#include <mad.h>
#define MAD_DELAY 529
#define READBUF_SIZE 2048 // local buffer used by decoder: FIXME merge with any other decoders needing one?
struct mad {
u8_t *readbuf;
unsigned readbuf_len;
struct mad_stream stream;
struct mad_frame frame;
struct mad_synth synth;
enum mad_error last_error;
// for lame gapless processing
int checktags;
u32_t consume;
u32_t skip;
u64_t samples;
u32_t padding;
#if !LINKALL
// mad symbols to be dynamically loaded
void (* mad_stream_init)(struct mad_stream *);
void (* mad_frame_init)(struct mad_frame *);
void (* mad_synth_init)(struct mad_synth *);
void (* mad_frame_finish)(struct mad_frame *);
void (* mad_stream_finish)(struct mad_stream *);
void (* mad_stream_buffer)(struct mad_stream *, unsigned char const *, unsigned long);
int (* mad_frame_decode)(struct mad_frame *, struct mad_stream *);
void (* mad_synth_frame)(struct mad_synth *, struct mad_frame const *);
char const *(* mad_stream_errorstr)(struct mad_stream const *);
#endif
};
static struct mad *m;
extern log_level loglevel;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct decodestate decode;
extern struct processstate process;
#define LOCK_S mutex_lock(streambuf->mutex)
#define UNLOCK_S mutex_unlock(streambuf->mutex)
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
#if PROCESS
#define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x) if (decode.direct) { x }
#define IF_PROCESS(x) if (!decode.direct) { x }
#else
#define LOCK_O_direct mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x) { x }
#define IF_PROCESS(x)
#endif
#if LINKALL
#define MAD(h, fn, ...) (mad_ ## fn)(__VA_ARGS__)
#else
#define MAD(h, fn, ...) (h)->mad_##fn(__VA_ARGS__)
#endif
// based on libmad minimad.c scale
static inline ISAMPLE_T scale(mad_fixed_t sample) {
sample += (1L << (MAD_F_FRACBITS - 24));
if (sample >= MAD_F_ONE)
sample = MAD_F_ONE - 1;
else if (sample < -MAD_F_ONE)
sample = -MAD_F_ONE;
#if BYTES_PER_FRAME == 4
return (ISAMPLE_T)((sample >> (MAD_F_FRACBITS + 1 - 24)) >> 8);
#else
return (ISAMPLE_T)((sample >> (MAD_F_FRACBITS + 1 - 24)) << 8);
#endif
}
// check for id3.2 tag at start of file - http://id3.org/id3v2.4.0-structure, return length
static unsigned _check_id3_tag(size_t bytes) {
u8_t *ptr = streambuf->readp;
u32_t size = 0;
if (bytes > 10 && *ptr == 'I' && *(ptr+1) == 'D' && *(ptr+2) == '3') {
// size is encoded as syncsafe integer, add 10 if footer present
if (*(ptr+6) < 0x80 && *(ptr+7) < 0x80 && *(ptr+8) < 0x80 && *(ptr+9) < 0x80) {
size = 10 + (*(ptr+6) << 21) + (*(ptr+7) << 14) + (*(ptr+8) << 7) + *(ptr+9) + ((*(ptr+5) & 0x10) ? 10 : 0);
LOG_DEBUG("id3.2 tag len: %u", size);
}
}
return size;
}
// check for lame gapless params, don't advance streambuf
static void _check_lame_header(size_t bytes) {
u8_t *ptr = streambuf->readp;
if (*ptr == 0xff && (*(ptr+1) & 0xf0) == 0xf0 && bytes > 180) {
u32_t frame_count = 0, enc_delay = 0, enc_padding = 0;
u8_t flags;
// 2 channels
if (!memcmp(ptr + 36, "Xing", 4) || !memcmp(ptr + 36, "Info", 4)) {
ptr += 36 + 7;
// mono
} else if (!memcmp(ptr + 21, "Xing", 4) || !memcmp(ptr + 21, "Info", 4)) {
ptr += 21 + 7;
}
flags = *ptr;
if (flags & 0x01) {
frame_count = unpackN((u32_t *)(ptr + 1));
ptr += 4;
}
if (flags & 0x02) ptr += 4;
if (flags & 0x04) ptr += 100;
if (flags & 0x08) ptr += 4;
if (!!memcmp(ptr+1, "LAME", 4)) {
return;
}
ptr += 22;
enc_delay = (*ptr << 4 | *(ptr + 1) >> 4) + MAD_DELAY;
enc_padding = (*(ptr + 1) & 0xF) << 8 | *(ptr + 2);
enc_padding = enc_padding > MAD_DELAY ? enc_padding - MAD_DELAY : 0;
// add one frame to initial skip for this (empty) frame
m->skip = enc_delay + 1152;
m->samples = frame_count * 1152 - enc_delay - enc_padding;
m->padding = enc_padding;
LOG_INFO("gapless: skip: %u samples: " FMT_u64 " delay: %u padding: %u", m->skip, m->samples, enc_delay, enc_padding);
}
}
static decode_state mad_decode(void) {
size_t bytes;
bool eos = false;
LOCK_S;
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
if (m->checktags) {
if (m->checktags == 1) {
m->consume = _check_id3_tag(bytes);
m->checktags = 2;
}
if (m->consume) {
u32_t consume = min(m->consume, bytes);
LOG_DEBUG("consume: %u of %u", consume, m->consume);
_buf_inc_readp(streambuf, consume);
m->consume -= consume;
UNLOCK_S;
return DECODE_RUNNING;
}
if (m->checktags == 2) {
if (!stream.meta_interval) {
_check_lame_header(bytes);
}
m->checktags = 0;
}
}
if (m->stream.next_frame && m->readbuf_len) {
m->readbuf_len -= m->stream.next_frame - m->readbuf;
memmove(m->readbuf, m->stream.next_frame, m->readbuf_len);
}
bytes = min(bytes, READBUF_SIZE - m->readbuf_len);
memcpy(m->readbuf + m->readbuf_len, streambuf->readp, bytes);
m->readbuf_len += bytes;
_buf_inc_readp(streambuf, bytes);
if (stream.state <= DISCONNECT && _buf_used(streambuf) == 0) {
eos = true;
LOG_DEBUG("end of stream");
memset(m->readbuf + m->readbuf_len, 0, MAD_BUFFER_GUARD);
m->readbuf_len += MAD_BUFFER_GUARD;
}
UNLOCK_S;
MAD(m, stream_buffer, &m->stream, m->readbuf, m->readbuf_len);
while (true) {
size_t frames;
s32_t *iptrl;
s32_t *iptrr;
unsigned max_frames;
if (MAD(m, frame_decode, &m->frame, &m->stream) == -1) {
decode_state ret;
if (!eos && m->stream.error == MAD_ERROR_BUFLEN) {
ret = DECODE_RUNNING;
} else if (eos && (m->stream.error == MAD_ERROR_BUFLEN || m->stream.error == MAD_ERROR_LOSTSYNC
|| m->stream.error == MAD_ERROR_BADBITRATE)) {
ret = DECODE_COMPLETE;
} else if (!MAD_RECOVERABLE(m->stream.error)) {
LOG_INFO("mad_frame_decode error: %s - stopping decoder", MAD(m, stream_errorstr, &m->stream));
ret = DECODE_COMPLETE;
} else {
if (m->stream.error != m->last_error) {
// suppress repeat error messages
LOG_DEBUG("mad_frame_decode error: %s", MAD(m, stream_errorstr, &m->stream));
}
ret = DECODE_RUNNING;
}
m->last_error = m->stream.error;
return ret;
};
MAD(m, synth_frame, &m->synth, &m->frame);
if (decode.new_stream) {
LOCK_O;
LOG_INFO("setting track_start");
output.next_sample_rate = decode_newstream(m->synth.pcm.samplerate, output.supported_rates);
IF_DSD( output.next_fmt = PCM; )
output.track_start = outputbuf->writep;
if (output.fade_mode) _checkfade(true);
decode.new_stream = false;
UNLOCK_O;
}
LOCK_O_direct;
IF_DIRECT(
max_frames = _buf_space(outputbuf) / BYTES_PER_FRAME;
);
IF_PROCESS(
max_frames = process.max_in_frames - process.in_frames;
);
if (m->synth.pcm.length > max_frames) {
LOG_WARN("too many samples - dropping samples");
m->synth.pcm.length = max_frames;
}
frames = m->synth.pcm.length;
iptrl = m->synth.pcm.samples[0];
iptrr = m->synth.pcm.samples[ m->synth.pcm.channels - 1 ];
if (m->skip) {
u32_t skip = min(m->skip, frames);
LOG_DEBUG("gapless: skipping %u frames at start", skip);
frames -= skip;
m->skip -= skip;
iptrl += skip;
iptrr += skip;
}
if (m->samples) {
if (m->samples < frames) {
LOG_DEBUG("gapless: trimming %u frames from end", frames - m->samples);
frames = (size_t)m->samples;
}
m->samples -= frames;
if (m->samples > 0 && eos && !(m->stream.next_frame[0] == 0xff && (m->stream.next_frame[1] & 0xf0) == 0xf0)) {
// this is the last frame to be decoded, but more samples expected so we must have skipped, remove padding
// note this only works if the padding is less than one frame of 1152 bytes otherswise some gap will remain
LOG_DEBUG("gapless: early end - trimming padding from end");
if (frames >= m->padding) {
frames -= m->padding;
} else {
frames = 0;
}
m->samples = 0;
}
}
LOG_SDEBUG("write %u frames", frames);
while (frames > 0) {
size_t f, count;
ISAMPLE_T *optr;
IF_DIRECT(
f = min(frames, _buf_cont_write(outputbuf) / BYTES_PER_FRAME);
optr = (ISAMPLE_T *)outputbuf->writep;
);
IF_PROCESS(
f = min(frames, process.max_in_frames - process.in_frames);
optr = (ISAMPLE_T *)((u8_t *)process.inbuf + process.in_frames * BYTES_PER_FRAME);
);
count = f;
while (count--) {
*optr++ = scale(*iptrl++);
*optr++ = scale(*iptrr++);
}
frames -= f;
IF_DIRECT(
_buf_inc_writep(outputbuf, f * BYTES_PER_FRAME);
);
IF_PROCESS(
process.in_frames += f;
);
}
UNLOCK_O_direct;
}
return eos ? DECODE_COMPLETE : DECODE_RUNNING;
}
static void mad_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
if (!m->readbuf) {
m->readbuf = malloc(READBUF_SIZE + MAD_BUFFER_GUARD);
}
m->checktags = 1;
m->consume = 0;
m->skip = MAD_DELAY;
m->samples = 0;
m->readbuf_len = 0;
m->last_error = MAD_ERROR_NONE;
MAD(m, stream_init, &m->stream);
MAD(m, frame_init, &m->frame);
MAD(m, synth_init, &m->synth);
}
static void mad_close(void) {
mad_synth_finish(&m->synth); // macro only in current version
MAD(m, frame_finish, &m->frame);
MAD(m, stream_finish, &m->stream);
free(m->readbuf);
m->readbuf = NULL;
}
static bool load_mad() {
#if !LINKALL
void *handle = dlopen(LIBMAD, RTLD_NOW);
char *err;
if (!handle) {
LOG_INFO("dlerror: %s", dlerror());
return false;
}
m->mad_stream_init = dlsym(handle, "mad_stream_init");
m->mad_frame_init = dlsym(handle, "mad_frame_init");
m->mad_synth_init = dlsym(handle, "mad_synth_init");
m->mad_frame_finish = dlsym(handle, "mad_frame_finish");
m->mad_stream_finish = dlsym(handle, "mad_stream_finish");
m->mad_stream_buffer = dlsym(handle, "mad_stream_buffer");
m->mad_frame_decode = dlsym(handle, "mad_frame_decode");
m->mad_synth_frame = dlsym(handle, "mad_synth_frame");
m->mad_stream_errorstr = dlsym(handle, "mad_stream_errorstr");
if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err);
return false;
}
LOG_INFO("loaded "LIBMAD);
#endif
return true;
}
struct codec *register_mad(void) {
static struct codec ret = {
'm', // id
"mp3", // types
READBUF_SIZE, // min read
206800, // min space
mad_open, // open
mad_close, // close
mad_decode, // decode
};
m = malloc(sizeof(struct mad));
if (!m) {
return NULL;
}
m->readbuf = NULL;
m->readbuf_len = 0;
if (!load_mad()) {
return NULL;
}
LOG_INFO("using mad to decode mp3");
return &ret;
}

View File

@@ -0,0 +1,848 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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 "squeezelite.h"
#include <signal.h>
#define TITLE "Squeezelite " VERSION ", Copyright 2012-2015 Adrian Smith, 2015-2019 Ralph Irving."
#define CODECS_BASE "flac,pcm,mp3,ogg"
#if NO_FAAD
#define CODECS_AAC ""
#else
#define CODECS_AAC ",aac"
#endif
#if FFMPEG
#define CODECS_FF ",wma,alac"
#else
#define CODECS_FF ""
#endif
#if DSD
#define CODECS_DSD ",dsd"
#else
#define CODECS_DSD ""
#endif
#define CODECS_MP3 " (mad,mpg for specific mp3 codec)"
#define CODECS CODECS_BASE CODECS_AAC CODECS_FF CODECS_DSD CODECS_MP3
static void usage(const char *argv0) {
printf(TITLE " See -t for license terms\n"
"Usage: %s [options]\n"
" -s <server>[:<port>]\tConnect to specified server, otherwise uses autodiscovery to find server\n"
" -o <output device>\tSpecify output device, default \"default\", - = output to stdout\n"
" -l \t\t\tList output devices\n"
#if ALSA
" -a <b>:<p>:<f>:<m>\tSpecify ALSA params to open output device, b = buffer time in ms or size in bytes, p = period count or size in bytes, f sample format (16|24|24_3|32), m = use mmap (0|1)\n"
#endif
#if PORTAUDIO
#if PA18API
" -a <frames>:<buffers>\tSpecify output target 4 byte frames per buffer, number of buffers\n"
#elif OSX && !defined(OSXPPC)
" -a <l>:<r>\t\tSpecify Portaudio params to open output device, l = target latency in ms, r = allow OSX to resample (0|1)\n"
#elif WIN
" -a <l>:<e>\t\tSpecify Portaudio params to open output device, l = target latency in ms, e = use exclusive mode for WASAPI (0|1)\n"
#else
" -a <l>\t\tSpecify Portaudio params to open output device, l = target latency in ms\n"
#endif
#endif
" -a <f>\t\tSpecify sample format (16|24|32) of output file when using -o - to output samples to stdout (interleaved little endian only)\n"
" -b <stream>:<output>\tSpecify internal Stream and Output buffer sizes in Kbytes\n"
" -c <codec1>,<codec2>\tRestrict codecs to those specified, otherwise load all available codecs; known codecs: " CODECS "\n"
" \t\t\tCodecs reported to LMS in order listed, allowing codec priority refinement.\n"
" -C <timeout>\t\tClose output device when idle after timeout seconds, default is to keep it open while player is 'on'\n"
#if !IR
" -d <log>=<level>\tSet logging level, logs: all|slimproto|stream|decode|output, level: info|debug|sdebug\n"
#else
" -d <log>=<level>\tSet logging level, logs: all|slimproto|stream|decode|output|ir, level: info|debug|sdebug\n"
#endif
#if defined(GPIO) && defined(RPI)
" -G <Rpi GPIO#>:<H/L>\tSpecify the BCM GPIO# to use for Amp Power Relay and if the output should be Active High or Low\n"
#endif
" -e <codec1>,<codec2>\tExplicitly exclude native support of one or more codecs; known codecs: " CODECS "\n"
" -f <logfile>\t\tWrite debug to logfile\n"
#if IR
" -i [<filename>]\tEnable lirc remote control support (lirc config file ~/.lircrc used if filename not specified)\n"
#endif
" -m <mac addr>\t\tSet mac address, format: ab:cd:ef:12:34:56\n"
" -M <modelname>\tSet the squeezelite player model name sent to the server (default: " MODEL_NAME_STRING ")\n"
" -n <name>\t\tSet the player name\n"
" -N <filename>\t\tStore player name in filename to allow server defined name changes to be shared between servers (not supported with -n)\n"
" -W\t\t\tRead wave and aiff format from header, ignore server parameters\n"
#if ALSA
" -p <priority>\t\tSet real time priority of output thread (1-99)\n"
#endif
#if LINUX || FREEBSD || SUN
" -P <filename>\t\tStore the process id (PID) in filename\n"
#endif
" -r <rates>[:<delay>]\tSample rates supported, allows output to be off when squeezelite is started; rates = <maxrate>|<minrate>-<maxrate>|<rate1>,<rate2>,<rate3>; delay = optional delay switching rates in ms\n"
#if GPIO
" -S <Power Script>\tAbsolute path to script to launch on power commands from LMS\n"
#endif
#if RESAMPLE
" -R -u [params]\tResample, params = <recipe>:<flags>:<attenuation>:<precision>:<passband_end>:<stopband_start>:<phase_response>,\n"
" \t\t\t recipe = (v|h|m|l|q)(L|I|M)(s) [E|X], E = exception - resample only if native rate not supported, X = async - resample to max rate for device, otherwise to max sync rate\n"
" \t\t\t flags = num in hex,\n"
" \t\t\t attenuation = attenuation in dB to apply (default is -1db if not explicitly set),\n"
" \t\t\t precision = number of bits precision (NB. HQ = 20. VHQ = 28),\n"
" \t\t\t passband_end = number in percent (0dB pt. bandwidth to preserve. nyquist = 100%%),\n"
" \t\t\t stopband_start = number in percent (Aliasing/imaging control. > passband_end),\n"
" \t\t\t phase_response = 0-100 (0 = minimum / 50 = linear / 100 = maximum)\n"
#endif
#if RESAMPLE16
" -R -u [params]\tResample, params = (b|l|m)[:i],\n"
" \t\t\t b = basic linear interpolation, l = 13 taps, m = 21 taps, i = interpolate filter coefficients\n"
#endif
#if DSD
#if ALSA
" -D [delay][:format]\tOutput device supports DSD, delay = optional delay switching between PCM and DSD in ms\n"
" \t\t\t format = dop (default if not specified), u8, u16le, u16be, u32le or u32be.\n"
#else
" -D [delay]\t\tOutput device supports DSD over PCM (DoP), delay = optional delay switching between PCM and DoP in ms\n"
#endif
#endif
#if VISEXPORT
" -v \t\t\tVisualiser support\n"
#endif
# if ALSA
" -O <mixer device>\tSpecify mixer device, defaults to 'output device'\n"
" -L \t\t\tList volume controls for output device\n"
" -U <control>\t\tUnmute ALSA control and set to full volume (not supported with -V)\n"
" -V <control>\t\tUse ALSA control for volume adjustment, otherwise use software volume adjustment\n"
" -X \t\t\tUse linear volume adjustments instead of in terms of dB (only for hardware volume control)\n"
#endif
#if LINUX || FREEBSD || SUN
" -z \t\t\tDaemonize\n"
#endif
#if RESAMPLE || RESAMPLE16
" -Z <rate>\t\tReport rate to server in helo as the maximum sample rate we can support\n"
#endif
" -t \t\t\tLicense terms\n"
" -? \t\t\tDisplay this help text\n"
"\n"
"Build options:"
#if SUN
" SOLARIS"
#elif LINUX
" LINUX"
#endif
#if WIN
" WIN"
#endif
#if OSX
" OSX"
#endif
#if OSXPPC
"PPC"
#endif
#if FREEBSD
" FREEBSD"
#endif
#if ALSA
" ALSA"
#endif
#if PORTAUDIO
" PORTAUDIO"
#if PA18API
"18"
#endif
#endif
#if EMBEDDED
" EMBEDDED"
#endif
#if EVENTFD
" EVENTFD"
#endif
#if SELFPIPE
" SELFPIPE"
#endif
#if LOOPBACK
" LOOPBACK"
#endif
#if WINEVENT
" WINEVENT"
#endif
#if RESAMPLE_MP
" RESAMPLE_MP"
#else
#if RESAMPLE
" RESAMPLE"
#endif
#if RESAMPLE16
" RESAMPLE16"
#endif
#endif
#if FFMPEG
" FFMPEG"
#endif
#if NO_FAAD
" NO_FAAD"
#endif
#if VISEXPORT
" VISEXPORT"
#endif
#if IR
" IR"
#endif
#if GPIO
" GPIO"
#endif
#if RPI
" RPI"
#endif
#if DSD
" DSD"
#endif
#if USE_SSL
" SSL"
#endif
#if LINKALL
" LINKALL"
#endif
#if STATUSHACK
" STATUSHACK"
#endif
"\n\n",
argv0);
}
static void license(void) {
printf(TITLE "\n\n"
"This program is free software: you can redistribute it and/or modify\n"
"it under the terms of the GNU General Public License as published by\n"
"the Free Software Foundation, either version 3 of the License, or\n"
"(at your option) any later version.\n\n"
"This program is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
"GNU General Public License for more details.\n\n"
"You should have received a copy of the GNU General Public License\n"
"along with this program. If not, see <http://www.gnu.org/licenses/>.\n"
#if DSD
"\nContains dsd2pcm library Copyright 2009, 2011 Sebastian Gesemann which\n"
"is subject to its own license.\n"
"\nContains the Daphile Project full dsd patch Copyright 2013-2017 Daphile,\n"
"which is subject to its own license.\n"
#endif
"\nOption to allow server side upsampling for PCM streams (-W) from\n"
"squeezelite-R2 (c) Marco Curti 2015, marcoc1712@gmail.com.\n"
#if GPIO
"\nAdditions (c) Paul Hermann, 2015, 2017 under the same license terms\n"
"- Launch a script on power status change\n"
"- Control of Raspberry pi GPIO for amplifier power\n"
#endif
#if RPI
"\nContains wiringpi GPIO Interface library Copyright (c) 2012-2017\n"
"Gordon Henderson, which is subject to its own license.\n"
#endif
#if FFMPEG
"\nThis software uses libraries from the FFmpeg project under\n"
"the LGPLv2.1 and its source can be downloaded from\n"
"<https://sourceforge.net/projects/lmsclients/files/source/>\n"
#endif
"\n"
);
}
static void sighandler(int signum) {
slimproto_stop();
// remove ourselves in case above does not work, second SIGINT will cause non gracefull shutdown
signal(signum, SIG_DFL);
}
int main(int argc, char **argv) {
char *server = NULL;
char *output_device = "default";
char *include_codecs = NULL;
char *exclude_codecs = "";
char *name = NULL;
char *namefile = NULL;
char *modelname = NULL;
extern bool pcm_check_header;
extern bool user_rates;
char *logfile = NULL;
u8_t mac[6];
unsigned stream_buf_size = STREAMBUF_SIZE;
unsigned output_buf_size = 0; // set later
unsigned rates[MAX_SUPPORTED_SAMPLERATES] = { 0 };
unsigned rate_delay = 0;
char *resample = NULL;
char *output_params = NULL;
unsigned idle = 0;
#if LINUX || FREEBSD || SUN
bool daemonize = false;
char *pidfile = NULL;
FILE *pidfp = NULL;
#endif
#if ALSA
unsigned rt_priority = OUTPUT_RT_PRIORITY;
char *mixer_device = output_device;
char *output_mixer = NULL;
bool output_mixer_unmute = false;
bool linear_volume = false;
#endif
#if DSD
unsigned dsd_delay = 0;
dsd_format dsd_outfmt = PCM;
#endif
#if VISEXPORT
bool visexport = false;
#endif
#if IR
char *lircrc = NULL;
#endif
log_level log_output = lWARN;
log_level log_stream = lWARN;
log_level log_decode = lWARN;
log_level log_slimproto = lWARN;
#if IR
log_level log_ir = lWARN;
#endif
int maxSampleRate = 0;
char *optarg = NULL;
int optind = 1;
int i;
#define MAXCMDLINE 512
char cmdline[MAXCMDLINE] = "";
get_mac(mac);
for (i = 0; i < argc && (strlen(argv[i]) + strlen(cmdline) + 2 < MAXCMDLINE); i++) {
strcat(cmdline, argv[i]);
strcat(cmdline, " ");
}
while (optind < argc && strlen(argv[optind]) >= 2 && argv[optind][0] == '-') {
char *opt = argv[optind] + 1;
if (strstr("oabcCdefmMnNpPrs"
#if ALSA
"UVO"
#endif
/*
* only allow '-Z <rate>' override of maxSampleRate
* reported by client if built with the capability to resample!
*/
#if RESAMPLE || RESAMPLE16
"Z"
#endif
, opt) && optind < argc - 1) {
optarg = argv[optind + 1];
optind += 2;
} else if (strstr("ltz?W"
#if ALSA
"LX"
#endif
#if RESAMPLE || RESAMPLE16
"uR"
#endif
#if DSD
"D"
#endif
#if VISEXPORT
"v"
#endif
#if IR
"i"
#endif
#if defined(GPIO) && defined(RPI)
"G"
#endif
#if GPIO
"S"
#endif
, opt)) {
optarg = NULL;
optind += 1;
} else {
fprintf(stderr, "\nOption error: -%s\n\n", opt);
usage(argv[0]);
exit(1);
}
switch (opt[0]) {
case 'o':
output_device = optarg;
#if ALSA
mixer_device = optarg;
#endif
break;
case 'a':
output_params = optarg;
break;
case 'b':
{
char *s = next_param(optarg, ':');
char *o = next_param(NULL, ':');
if (s) stream_buf_size = atoi(s) * 1024;
if (o) output_buf_size = atoi(o) * 1024;
}
break;
case 'c':
include_codecs = optarg;
break;
case 'C':
if (atoi(optarg) > 0) {
idle = atoi(optarg) * 1000;
}
break;
case 'e':
exclude_codecs = optarg;
break;
case 'd':
{
char *l = strtok(optarg, "=");
char *v = strtok(NULL, "=");
log_level new = lWARN;
if (l && v) {
if (!strcmp(v, "info")) new = lINFO;
if (!strcmp(v, "debug")) new = lDEBUG;
if (!strcmp(v, "sdebug")) new = lSDEBUG;
if (!strcmp(l, "all") || !strcmp(l, "slimproto")) log_slimproto = new;
if (!strcmp(l, "all") || !strcmp(l, "stream")) log_stream = new;
if (!strcmp(l, "all") || !strcmp(l, "decode")) log_decode = new;
if (!strcmp(l, "all") || !strcmp(l, "output")) log_output = new;
#if IR
if (!strcmp(l, "all") || !strcmp(l, "ir")) log_ir = new;
#endif
} else {
fprintf(stderr, "\nDebug settings error: -d %s\n\n", optarg);
usage(argv[0]);
exit(1);
}
}
break;
case 'f':
logfile = optarg;
break;
case 'm':
{
int byte = 0;
char *tmp;
if (!strncmp(optarg, "00:04:20", 8)) {
LOG_ERROR("ignoring mac address from hardware player range 00:04:20:**:**:**");
} else {
char *t = strtok(optarg, ":");
while (t && byte < 6) {
mac[byte++] = (u8_t)strtoul(t, &tmp, 16);
t = strtok(NULL, ":");
}
}
}
break;
case 'M':
modelname = optarg;
break;
case 'r':
{
char *rstr = next_param(optarg, ':');
char *dstr = next_param(NULL, ':');
if (rstr && strstr(rstr, ",")) {
// parse sample rates and sort them
char *r = next_param(rstr, ',');
unsigned tmp[MAX_SUPPORTED_SAMPLERATES] = { 0 };
int i, j;
int last = 999999;
for (i = 0; r && i < MAX_SUPPORTED_SAMPLERATES; ++i) {
tmp[i] = atoi(r);
r = next_param(NULL, ',');
}
for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
int largest = 0;
for (j = 0; j < MAX_SUPPORTED_SAMPLERATES; ++j) {
if (tmp[j] > largest && tmp[j] < last) {
largest = tmp[j];
}
}
rates[i] = last = largest;
}
} else if (rstr) {
// optstr is <min>-<max> or <max>, extract rates from test rates within this range
unsigned ref[] TEST_RATES;
char *str1 = next_param(rstr, '-');
char *str2 = next_param(NULL, '-');
unsigned max = str2 ? atoi(str2) : (str1 ? atoi(str1) : ref[0]);
unsigned min = str1 && str2 ? atoi(str1) : 0;
unsigned tmp;
int i, j;
if (max < min) { tmp = max; max = min; min = tmp; }
rates[0] = max;
for (i = 0, j = 1; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
if (ref[i] < rates[j-1] && ref[i] >= min) {
rates[j++] = ref[i];
}
}
}
if (dstr) {
rate_delay = atoi(dstr);
}
if (rates[0]) {
user_rates = true;
}
}
break;
case 's':
server = optarg;
break;
case 'n':
name = optarg;
break;
case 'N':
namefile = optarg;
break;
case 'W':
pcm_check_header = true;
break;
#if ALSA
case 'p':
rt_priority = atoi(optarg);
if (rt_priority > 99 || rt_priority < 1) {
fprintf(stderr, "\nError: invalid priority: %s\n\n", optarg);
usage(argv[0]);
exit(1);
}
break;
#endif
#if LINUX || FREEBSD || SUN
case 'P':
pidfile = optarg;
break;
#endif
#ifndef EMBEDDED
case 'l':
list_devices();
exit(0);
break;
#endif
#if RESAMPLE || RESAMPLE16
case 'u':
case 'R':
if (optind < argc && argv[optind] && argv[optind][0] != '-') {
resample = argv[optind++];
} else {
resample = "";
}
break;
case 'Z':
maxSampleRate = atoi(optarg);
break;
#endif
#if DSD
case 'D':
dsd_outfmt = DOP;
if (optind < argc && argv[optind] && argv[optind][0] != '-') {
char *dstr = next_param(argv[optind++], ':');
char *fstr = next_param(NULL, ':');
dsd_delay = dstr ? atoi(dstr) : 0;
if (fstr) {
if (!strcmp(fstr, "dop")) dsd_outfmt = DOP;
if (!strcmp(fstr, "u8")) dsd_outfmt = DSD_U8;
if (!strcmp(fstr, "u16le")) dsd_outfmt = DSD_U16_LE;
if (!strcmp(fstr, "u32le")) dsd_outfmt = DSD_U32_LE;
if (!strcmp(fstr, "u16be")) dsd_outfmt = DSD_U16_BE;
if (!strcmp(fstr, "u32be")) dsd_outfmt = DSD_U32_BE;
if (!strcmp(fstr, "dop24")) dsd_outfmt = DOP_S24_LE;
if (!strcmp(fstr, "dop24_3")) dsd_outfmt = DOP_S24_3LE;
}
}
break;
#endif
#if VISEXPORT
case 'v':
visexport = true;
break;
#endif
#if ALSA
case 'O':
mixer_device = optarg;
break;
case 'L':
list_mixers(mixer_device);
exit(0);
break;
case 'X':
linear_volume = true;
break;
case 'U':
output_mixer_unmute = true;
case 'V':
if (output_mixer) {
fprintf(stderr, "-U and -V option should not be used at same time\n");
exit(1);
}
output_mixer = optarg;
break;
#endif
#if IR
case 'i':
if (optind < argc && argv[optind] && argv[optind][0] != '-') {
lircrc = argv[optind++];
} else {
lircrc = "~/.lircrc"; // liblirc_client will expand ~/
}
break;
#endif
#if defined(GPIO) && defined(RPI)
case 'G':
if (power_script != NULL){
fprintf(stderr, "-G and -S options cannot be used together \n\n" );
usage(argv[0]);
exit(1);
}
if (optind < argc && argv[optind] && argv[optind][0] != '-') {
char *gp = next_param(argv[optind++], ':');
char *go = next_param (NULL, ':');
gpio_pin = atoi(gp);
if (go != NULL){
if ((strcmp(go, "H")==0)|(strcmp(go, "h")==0)){
gpio_active_low=false;
}else if((strcmp(go, "L")==0)|(strcmp(go, "l")==0)){
gpio_active_low=true;
}else{
fprintf(stderr,"Must set output to be active High or Low i.e. -G18:H or -G18:L\n");
usage(argv[0]);
exit(1);
}
}else{
fprintf(stderr,"-G Option Error\n");
usage(argv[0]);
exit(1);
}
gpio_active = true;
relay(0);
} else {
fprintf(stderr, "Error in GPIO Pin assignment.\n");
usage(argv[0]);
exit(1);
}
break;
#endif
#if GPIO
case 'S':
if (gpio_active){
fprintf(stderr, "-G and -S options cannot be used together \n\n" );
usage(argv[0]);
exit(1);
}
if (optind < argc && argv[optind] && argv[optind][0] != '-') {
power_script = argv[optind++];
if( access( power_script, R_OK|X_OK ) == -1 ) {
// file doesn't exist
fprintf(stderr, "Script %s, not found\n\n", argv[optind-1]);
usage(argv[0]);
exit(1);
}
} else {
fprintf(stderr, "No Script Name Given.\n\n");
usage(argv[0]);
exit(1);
}
relay_script(0);
break;
#endif
#if LINUX || FREEBSD || SUN
case 'z':
daemonize = true;
#if SUN
init_daemonize();
#endif /* SUN */
break;
#endif
case 't':
license();
exit(0);
case '?':
usage(argv[0]);
exit(0);
break;
default:
fprintf(stderr, "Arg error: %s\n", argv[optind]);
break;
}
}
// warn if command line includes something which isn't parsed
if (optind < argc) {
fprintf(stderr, "\nError: command line argument error\n\n");
usage(argv[0]);
exit(1);
}
signal(SIGINT, sighandler);
signal(SIGTERM, sighandler);
#if defined(SIGQUIT)
signal(SIGQUIT, sighandler);
#endif
#if defined(SIGHUP)
signal(SIGHUP, sighandler);
#endif
#if USE_SSL && !LINKALL
ssl_loaded = load_ssl_symbols();
#endif
// set the output buffer size if not specified on the command line, take account of resampling
if (!output_buf_size) {
output_buf_size = OUTPUTBUF_SIZE;
if (resample) {
unsigned scale = 8;
if (rates[0]) {
scale = rates[0] / 44100;
if (scale > 8) scale = 8;
if (scale < 1) scale = 1;
}
output_buf_size *= scale;
}
}
if (logfile) {
if (!freopen(logfile, "a", stderr)) {
fprintf(stderr, "error opening logfile %s: %s\n", logfile, strerror(errno));
} else {
if (log_output >= lINFO || log_stream >= lINFO || log_decode >= lINFO || log_slimproto >= lINFO) {
fprintf(stderr, "\n%s\n", cmdline);
}
}
}
#if LINUX || FREEBSD || SUN
if (pidfile) {
if (!(pidfp = fopen(pidfile, "w")) ) {
fprintf(stderr, "Error opening pidfile %s: %s\n", pidfile, strerror(errno));
exit(1);
}
pidfile = realpath(pidfile, NULL); // daemonize will change cwd
}
if (daemonize) {
if (daemon(0, logfile ? 1 : 0)) {
fprintf(stderr, "error daemonizing: %s\n", strerror(errno));
}
}
if (pidfp) {
fprintf(pidfp, "%d\n", (int) getpid());
fclose(pidfp);
}
#endif
#if WIN
winsock_init();
#endif
stream_init(log_stream, stream_buf_size);
#if EMBEDDED
output_init_embedded(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle);
#else
if (!strcmp(output_device, "-")) {
output_init_stdout(log_output, output_buf_size, output_params, rates, rate_delay);
} else {
#if ALSA
output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority, idle, mixer_device, output_mixer,
output_mixer_unmute, linear_volume);
#endif
#if PORTAUDIO
output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle);
#endif
}
#endif
#if DSD
dsd_init(dsd_outfmt, dsd_delay);
#endif
#if VISEXPORT
if (visexport) {
output_vis_init(log_output, mac);
}
#endif
decode_init(log_decode, include_codecs, exclude_codecs);
#if RESAMPLE || RESAMPLE16
if (resample) {
process_init(resample);
}
#endif
#if IR
if (lircrc) {
ir_init(log_ir, lircrc);
}
#endif
if (name && namefile) {
fprintf(stderr, "-n and -N option should not be used at same time\n");
exit(1);
}
slimproto(log_slimproto, server, mac, name, namefile, modelname, maxSampleRate);
decode_close();
stream_close();
#if EMBEDDED
output_close_embedded();
#else
if (!strcmp(output_device, "-")) {
output_close_stdout();
} else {
#if ALSA
output_close_alsa();
#endif
#if PORTAUDIO
output_close_pa();
#endif
}
#endif
#if IR
ir_close();
#endif
#if WIN
winsock_close();
#endif
#if LINUX || FREEBSD || SUN
if (pidfile) {
unlink(pidfile);
free(pidfile);
}
#endif
#if USE_SSL && !LINKALL
free_ssl_symbols();
#endif
exit(0);
}

View File

@@ -0,0 +1,8 @@
#include "squeezelite.h"
extern log_level loglevel;
struct codec *register_mpg(void) {
LOG_INFO("mpg unavailable");
return NULL;
}

View File

@@ -0,0 +1,448 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
// Common output function
#include "squeezelite.h"
static log_level loglevel;
struct outputstate output;
static struct buffer buf;
struct buffer *outputbuf = &buf;
u8_t *silencebuf;
#if DSD
u8_t *silencebuf_dsd;
#endif
bool user_rates = false;
#define LOCK mutex_lock(outputbuf->mutex)
#define UNLOCK mutex_unlock(outputbuf->mutex)
// functions starting _* are called with mutex locked
frames_t _output_frames(frames_t avail) {
frames_t frames, size;
bool silence;
s32_t cross_gain_in = 0, cross_gain_out = 0; ISAMPLE_T *cross_ptr = NULL;
s32_t gainL = output.current_replay_gain ? gain(output.gainL, output.current_replay_gain) : output.gainL;
s32_t gainR = output.current_replay_gain ? gain(output.gainR, output.current_replay_gain) : output.gainR;
if (output.invert) { gainL = -gainL; gainR = -gainR; }
frames = _buf_used(outputbuf) / BYTES_PER_FRAME;
silence = false;
// start when threshold met
if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) {
output.state = OUTPUT_RUNNING;
LOG_INFO("start buffer frames: %u", frames);
wake_controller();
}
// skip ahead - consume outputbuf but play nothing
if (output.state == OUTPUT_SKIP_FRAMES) {
if (frames > 0) {
frames_t skip = min(frames, output.skip_frames);
LOG_INFO("skip %u of %u frames", skip, output.skip_frames);
frames -= skip;
output.frames_played += skip;
while (skip > 0) {
frames_t cont_frames = min(skip, _buf_cont_read(outputbuf) / BYTES_PER_FRAME);
skip -= cont_frames;
_buf_inc_readp(outputbuf, cont_frames * BYTES_PER_FRAME);
}
}
output.state = OUTPUT_RUNNING;
}
// pause frames - play silence for required frames
if (output.state == OUTPUT_PAUSE_FRAMES) {
LOG_INFO("pause %u frames", output.pause_frames);
if (output.pause_frames == 0) {
output.state = OUTPUT_RUNNING;
} else {
silence = true;
frames = min(avail, output.pause_frames);
frames = min(frames, MAX_SILENCE_FRAMES);
output.pause_frames -= frames;
}
}
// start at - play silence until jiffies reached
if (output.state == OUTPUT_START_AT) {
u32_t now = gettime_ms();
if (now >= output.start_at || output.start_at > now + 10000) {
output.state = OUTPUT_RUNNING;
} else {
u32_t delta_frames = (output.start_at - now) * output.current_sample_rate / 1000;
silence = true;
frames = min(avail, delta_frames);
frames = min(frames, MAX_SILENCE_FRAMES);
}
}
// play silence if buffering or no frames
if (output.state <= OUTPUT_BUFFER || frames == 0) {
silence = true;
frames = min(avail, MAX_SILENCE_FRAMES);
}
LOG_SDEBUG("avail: %d frames: %d silence: %d", avail, frames, silence);
frames = min(frames, avail);
size = frames;
while (size > 0) {
frames_t out_frames;
frames_t cont_frames = _buf_cont_read(outputbuf) / BYTES_PER_FRAME;
int wrote;
if (output.track_start && !silence) {
if (output.track_start == outputbuf->readp) {
unsigned delay = 0;
if (output.current_sample_rate != output.next_sample_rate) {
delay = output.rate_delay;
}
IF_DSD(
if (output.outfmt != output.next_fmt) {
delay = output.dsd_delay;
}
)
frames -= size;
// add silence delay in two halves, before and after track start on rate or pcm-dop change
if (delay) {
output.state = OUTPUT_PAUSE_FRAMES;
if (!output.delay_active) {
output.pause_frames = output.current_sample_rate * delay / 2000;
output.delay_active = true; // first delay - don't process track start
break;
} else {
output.pause_frames = output.next_sample_rate * delay / 2000;
output.delay_active = false; // second delay - process track start
}
}
LOG_INFO("track start sample rate: %u replay_gain: %u", output.next_sample_rate, output.next_replay_gain);
output.frames_played = 0;
output.track_started = true;
output.track_start_time = gettime_ms();
output.current_sample_rate = output.next_sample_rate;
IF_DSD(
output.outfmt = output.next_fmt;
)
if (output.fade == FADE_INACTIVE || output.fade_mode != FADE_CROSSFADE) {
output.current_replay_gain = output.next_replay_gain;
}
output.track_start = NULL;
break;
} else if (output.track_start > outputbuf->readp) {
// reduce cont_frames so we find the next track start at beginning of next chunk
cont_frames = min(cont_frames, (output.track_start - outputbuf->readp) / BYTES_PER_FRAME);
}
}
IF_DSD(
if (output.outfmt != PCM) {
gainL = gainR = FIXED_ONE;
}
)
if (output.fade && !silence) {
if (output.fade == FADE_DUE) {
if (output.fade_start == outputbuf->readp) {
LOG_INFO("fade start reached");
output.fade = FADE_ACTIVE;
} else if (output.fade_start > outputbuf->readp) {
cont_frames = min(cont_frames, (output.fade_start - outputbuf->readp) / BYTES_PER_FRAME);
}
}
if (output.fade == FADE_ACTIVE) {
// find position within fade
frames_t cur_f = outputbuf->readp >= output.fade_start ? (outputbuf->readp - output.fade_start) / BYTES_PER_FRAME :
(outputbuf->readp + outputbuf->size - output.fade_start) / BYTES_PER_FRAME;
frames_t dur_f = output.fade_end >= output.fade_start ? (output.fade_end - output.fade_start) / BYTES_PER_FRAME :
(output.fade_end + outputbuf->size - output.fade_start) / BYTES_PER_FRAME;
if (cur_f >= dur_f) {
if (output.fade_mode == FADE_INOUT && output.fade_dir == FADE_DOWN) {
LOG_INFO("fade down complete, starting fade up");
output.fade_dir = FADE_UP;
output.fade_start = outputbuf->readp;
output.fade_end = outputbuf->readp + dur_f * BYTES_PER_FRAME;
if (output.fade_end >= outputbuf->wrap) {
output.fade_end -= outputbuf->size;
}
cur_f = 0;
} else if (output.fade_mode == FADE_CROSSFADE) {
LOG_INFO("crossfade complete");
if (_buf_used(outputbuf) >= dur_f * BYTES_PER_FRAME) {
_buf_inc_readp(outputbuf, dur_f * BYTES_PER_FRAME);
LOG_INFO("skipped crossfaded start");
} else {
LOG_WARN("unable to skip crossfaded start");
}
output.fade = FADE_INACTIVE;
output.current_replay_gain = output.next_replay_gain;
} else {
LOG_INFO("fade complete");
output.fade = FADE_INACTIVE;
}
}
// if fade in progress set fade gain, ensure cont_frames reduced so we get to end of fade at start of chunk
if (output.fade) {
if (output.fade_end > outputbuf->readp) {
cont_frames = min(cont_frames, (output.fade_end - outputbuf->readp) / BYTES_PER_FRAME);
}
if (output.fade_dir == FADE_UP || output.fade_dir == FADE_DOWN) {
// fade in, in-out, out handled via altering standard gain
s32_t fade_gain;
if (output.fade_dir == FADE_DOWN) {
cur_f = dur_f - cur_f;
}
fade_gain = to_gain((float)cur_f / (float)dur_f);
gainL = gain(gainL, fade_gain);
gainR = gain(gainR, fade_gain);
if (output.invert) { gainL = -gainL; gainR = -gainR; }
}
if (output.fade_dir == FADE_CROSS) {
// cross fade requires special treatment - performed later based on these values
// support different replay gain for old and new track by retaining old value until crossfade completes
if (_buf_used(outputbuf) / BYTES_PER_FRAME > dur_f + size) {
cross_gain_in = to_gain((float)cur_f / (float)dur_f);
cross_gain_out = FIXED_ONE - cross_gain_in;
if (output.current_replay_gain) {
cross_gain_out = gain(cross_gain_out, output.current_replay_gain);
}
if (output.next_replay_gain) {
cross_gain_in = gain(cross_gain_in, output.next_replay_gain);
}
gainL = output.gainL;
gainR = output.gainR;
if (output.invert) { gainL = -gainL; gainR = -gainR; }
cross_ptr = (ISAMPLE_T *)(output.fade_end + cur_f * BYTES_PER_FRAME);
} else {
LOG_INFO("unable to continue crossfade - too few samples");
output.fade = FADE_INACTIVE;
}
}
}
}
}
out_frames = !silence ? min(size, cont_frames) : size;
wrote = output.write_cb(out_frames, silence, gainL, gainR, cross_gain_in, cross_gain_out, &cross_ptr);
if (wrote <= 0) {
frames -= size;
break;
} else {
out_frames = (frames_t)wrote;
}
size -= out_frames;
_vis_export(outputbuf, &output, out_frames, silence);
if (!silence) {
_buf_inc_readp(outputbuf, out_frames * BYTES_PER_FRAME);
output.frames_played += out_frames;
}
}
LOG_SDEBUG("wrote %u frames", frames);
return frames;
}
void _checkfade(bool start) {
frames_t bytes;
LOG_INFO("fade mode: %u duration: %u %s", output.fade_mode, output.fade_secs, start ? "track-start" : "track-end");
bytes = output.next_sample_rate * BYTES_PER_FRAME * output.fade_secs;
if (output.fade_mode == FADE_INOUT) {
/* align on a frame boundary */
bytes = ((bytes / 2) / BYTES_PER_FRAME) * BYTES_PER_FRAME;
}
if (start && (output.fade_mode == FADE_IN || (output.fade_mode == FADE_INOUT && _buf_used(outputbuf) == 0))) {
bytes = min(bytes, outputbuf->size - BYTES_PER_FRAME); // shorter than full buffer otherwise start and end align
LOG_INFO("fade IN: %u frames", bytes / BYTES_PER_FRAME);
output.fade = FADE_DUE;
output.fade_dir = FADE_UP;
output.fade_start = outputbuf->writep;
output.fade_end = output.fade_start + bytes;
if (output.fade_end >= outputbuf->wrap) {
output.fade_end -= outputbuf->size;
}
}
if (!start && (output.fade_mode == FADE_OUT || output.fade_mode == FADE_INOUT)) {
bytes = min(_buf_used(outputbuf), bytes);
LOG_INFO("fade %s: %u frames", output.fade_mode == FADE_INOUT ? "IN-OUT" : "OUT", bytes / BYTES_PER_FRAME);
output.fade = FADE_DUE;
output.fade_dir = FADE_DOWN;
output.fade_start = outputbuf->writep - bytes;
if (output.fade_start < outputbuf->buf) {
output.fade_start += outputbuf->size;
}
output.fade_end = outputbuf->writep;
}
if (start && output.fade_mode == FADE_CROSSFADE) {
if (_buf_used(outputbuf) != 0) {
if (output.next_sample_rate != output.current_sample_rate) {
LOG_INFO("crossfade disabled as sample rates differ");
return;
}
bytes = min(bytes, _buf_used(outputbuf)); // max of current remaining samples from previous track
bytes = min(bytes, (frames_t)(outputbuf->size * 0.9)); // max of 90% of outputbuf as we consume additional buffer during crossfade
LOG_INFO("CROSSFADE: %u frames", bytes / BYTES_PER_FRAME);
output.fade = FADE_DUE;
output.fade_dir = FADE_CROSS;
output.fade_start = outputbuf->writep - bytes;
if (output.fade_start < outputbuf->buf) {
output.fade_start += outputbuf->size;
}
output.fade_end = outputbuf->writep;
output.track_start = output.fade_start;
} else if (outputbuf->size == OUTPUTBUF_SIZE && outputbuf->readp == outputbuf->buf) {
// if default setting used and nothing in buffer attempt to resize to provide full crossfade support
LOG_INFO("resize outputbuf for crossfade");
_buf_resize(outputbuf, OUTPUTBUF_SIZE_CROSSFADE);
#if LINUX || FREEBSD
touch_memory(outputbuf->buf, outputbuf->size);
#endif
}
}
}
void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle) {
unsigned i;
loglevel = level;
output_buf_size = output_buf_size - (output_buf_size % BYTES_PER_FRAME);
LOG_DEBUG("outputbuf size: %u", output_buf_size);
buf_init(outputbuf, output_buf_size);
if (!outputbuf->buf) {
LOG_ERROR("unable to malloc output buffer");
exit(0);
}
silencebuf = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
if (!silencebuf) {
LOG_ERROR("unable to malloc silence buffer");
exit(0);
}
memset(silencebuf, 0, MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
IF_DSD(
silencebuf_dsd = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
if (!silencebuf_dsd) {
LOG_ERROR("unable to malloc silence dsd buffer");
exit(0);
}
dsd_silence_frames((u32_t *)silencebuf_dsd, MAX_SILENCE_FRAMES);
)
LOG_DEBUG("idle timeout: %u", idle);
output.state = idle ? OUTPUT_OFF: OUTPUT_STOPPED;
output.device = device;
output.fade = FADE_INACTIVE;
output.invert = false;
output.error_opening = false;
output.idle_to = (u32_t) idle;
/* Skip test_open for stdout, set default sample rates */
if ( output.device[0] == '-' ) {
for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
output.supported_rates[i] = rates[i];
}
}
else {
if (!test_open(output.device, output.supported_rates, user_rates)) {
LOG_ERROR("unable to open output device: %s", output.device);
exit(0);
}
}
if (user_rates) {
for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
output.supported_rates[i] = rates[i];
}
}
// set initial sample rate, preferring 44100
for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
if (output.supported_rates[i] == 44100) {
output.default_sample_rate = 44100;
break;
}
}
if (!output.default_sample_rate) {
output.default_sample_rate = output.supported_rates[0];
}
output.current_sample_rate = output.default_sample_rate;
if (loglevel >= lINFO) {
char rates_buf[10 * MAX_SUPPORTED_SAMPLERATES] = "";
for (i = 0; output.supported_rates[i]; ++i) {
char s[10];
sprintf(s, "%d ", output.supported_rates[i]);
strcat(rates_buf, s);
}
LOG_INFO("supported rates: %s", rates_buf);
}
}
void output_close_common(void) {
buf_destroy(outputbuf);
free(silencebuf);
IF_DSD(
free(silencebuf_dsd);
)
}
void output_flush(void) {
LOG_INFO("flush output buffer");
buf_flush(outputbuf);
LOCK;
output.fade = FADE_INACTIVE;
if (output.state != OUTPUT_OFF) {
output.state = OUTPUT_STOPPED;
if (output.error_opening) {
output.current_sample_rate = output.default_sample_rate;
}
output.delay_active = false;
}
output.frames_played = 0;
UNLOCK;
}

View File

@@ -0,0 +1,175 @@
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
* Philippe G. 2019, 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 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/>.
*
*/
#include "squeezelite.h"
#include "perf_trace.h"
extern struct outputstate output;
extern struct buffer *outputbuf;
extern struct buffer *streambuf;
extern u8_t *silencebuf;
#define LOCK mutex_lock(outputbuf->mutex)
#define UNLOCK mutex_unlock(outputbuf->mutex)
#define LOCK_S mutex_lock(streambuf->mutex)
#define UNLOCK_S mutex_unlock(streambuf->mutex)
#define FRAME_BLOCK MAX_SILENCE_FRAMES
#define STATS_REPORT_DELAY_MS 15000
extern void hal_bluetooth_init(const char * options);
static log_level loglevel;
uint8_t * btout;
static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
#define DECLARE_ALL_MIN_MAX \
DECLARE_MIN_MAX(req);\
DECLARE_MIN_MAX(rec);\
DECLARE_MIN_MAX(bt);\
DECLARE_MIN_MAX(under);\
DECLARE_MIN_MAX(stream_buf);\
DECLARE_MIN_MAX_DURATION(lock_out_time)
#define RESET_ALL_MIN_MAX \
RESET_MIN_MAX(bt); \
RESET_MIN_MAX(req); \
RESET_MIN_MAX(rec); \
RESET_MIN_MAX(under); \
RESET_MIN_MAX(stream_buf); \
RESET_MIN_MAX_DURATION(lock_out_time)
DECLARE_ALL_MIN_MAX;
void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
loglevel = level;
hal_bluetooth_init(device);
output.write_cb = &_write_frames;
}
static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr) {
assert(btout != NULL);
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);
}
if (gainL != FIXED_ONE || gainR!= FIXED_ONE) {
_apply_gain(outputbuf, out_frames, gainL, gainR);
}
#if BYTES_PER_FRAME == 4
memcpy(btout, outputbuf->readp, out_frames * BYTES_PER_FRAME);
#else
{
frames_t count = out_frames;
s32_t *_iptr = (s32_t*) outputbuf->readp;
s16_t *_optr = (s16_t*) bt_optr;
while (count--) {
*_optr++ = *_iptr++ >> 16;
*_optr++ = *_iptr++ >> 16;
}
}
#endif
} else {
u8_t *buf = silencebuf;
memcpy(btout, buf, out_frames * BYTES_PER_FRAME);
}
return (int)out_frames;
}
int32_t output_bt_data(uint8_t *data, int32_t len) {
int32_t avail_data = 0, wanted_len = 0, start_timer = 0;
if (len < 0 || data == NULL ) {
return 0;
}
btout = data;
// This is how the BTC layer calculates the number of bytes to
// for us to send. (BTC_SBC_DEC_PCM_DATA_LEN * sizeof(OI_INT16) - availPcmBytes
wanted_len=len;
SET_MIN_MAX(len,req);
TIME_MEASUREMENT_START(start_timer);
LOCK;
output.device_frames = 0; // todo: check if this is the right way do to this.
output.updated = gettime_ms();
output.frames_played_dmp = output.frames_played;
SET_MIN_MAX_SIZED(_buf_used(outputbuf),bt,outputbuf->size);
do {
avail_data = _output_frames( wanted_len/BYTES_PER_FRAME )*BYTES_PER_FRAME; // Keep the transfer buffer full
wanted_len-=avail_data;
} while (wanted_len > 0 && avail_data != 0);
if (wanted_len > 0) {
SET_MIN_MAX(wanted_len, under);
}
UNLOCK;
SET_MIN_MAX(TIME_MEASUREMENT_GET(start_timer),lock_out_time);
SET_MIN_MAX((len-wanted_len), rec);
TIME_MEASUREMENT_START(start_timer);
return len-wanted_len;
}
void output_bt_tick(void) {
static time_t lastTime=0;
LOCK_S;
SET_MIN_MAX_SIZED(_buf_used(streambuf), stream_buf, streambuf->size);
UNLOCK_S;
if (lastTime <= gettime_ms() )
{
lastTime = gettime_ms() + STATS_REPORT_DELAY_MS;
LOG_INFO("Statistics over %u secs. " , STATS_REPORT_DELAY_MS/1000);
LOG_INFO(" +==========+==========+================+=====+================+");
LOG_INFO(" | max | min | average | avg | count |");
LOG_INFO(" | (bytes) | (bytes) | (bytes) | pct | |");
LOG_INFO(" +==========+==========+================+=====+================+");
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("stream avl",stream_buf));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("output avl",bt));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("requested",req));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("received",rec));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("underrun",under));
LOG_INFO( " +==========+==========+================+=====+================+");
LOG_INFO("\n");
LOG_INFO(" ==========+==========+===========+===========+ ");
LOG_INFO(" max (us) | min (us) | avg(us) | count | ");
LOG_INFO(" ==========+==========+===========+===========+ ");
LOG_INFO(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("Out Buf Lock",lock_out_time));
LOG_INFO(" ==========+==========+===========+===========+");
RESET_ALL_MIN_MAX;
}
}

View File

@@ -0,0 +1,118 @@
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
* Philippe G. 2019, 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 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/>.
*
*/
#include "squeezelite.h"
extern struct outputstate output;
extern struct buffer *outputbuf;
#define FRAME_BLOCK MAX_SILENCE_FRAMES
#define LOCK mutex_lock(outputbuf->mutex)
#define UNLOCK mutex_unlock(outputbuf->mutex)
extern void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params,
unsigned rates[], unsigned rate_delay, unsigned idle);
extern void output_init_i2s(log_level level, char *device, unsigned output_buf_size, char *params,
unsigned rates[], unsigned rate_delay, unsigned idle);
extern void output_close_i2s(void);
static log_level loglevel;
static void (*volume_cb)(unsigned left, unsigned right);
static void (*close_cb)(void);
void output_init_embedded(log_level level, char *device, unsigned output_buf_size, char *params,
unsigned rates[], unsigned rate_delay, unsigned idle) {
loglevel = level;
LOG_INFO("init device: %s", device);
memset(&output, 0, sizeof(output));
output_init_common(level, device, output_buf_size, rates, idle);
output.start_frames = FRAME_BLOCK;
output.rate_delay = rate_delay;
if (strstr(device, "BT ")) {
LOG_INFO("init Bluetooth");
output_init_bt(level, device, output_buf_size, params, rates, rate_delay, idle);
} else {
LOG_INFO("init I2S");
close_cb = output_close_i2s;
output_init_i2s(level, device, output_buf_size, params, rates, rate_delay, idle);
}
LOG_INFO("init completed.");
}
void output_close_embedded(void) {
LOG_INFO("close output");
output_close_common();
if (close_cb) (*close_cb)();
}
void set_volume(unsigned left, unsigned right) {
LOG_DEBUG("setting internal gain left: %u right: %u", left, right);
if (!volume_cb) {
LOCK;
output.gainL = left;
output.gainR = right;
UNLOCK;
} else (*volume_cb)(left, right);
}
bool test_open(const char *device, unsigned rates[], bool userdef_rates) {
memset(rates, 0, MAX_SUPPORTED_SAMPLERATES * sizeof(unsigned));
if (!strcmp(device, "BT")) {
rates[0] = 44100;
} else {
unsigned _rates[] = { 96000, 88200, 48000, 44100, 32000, 0 };
memcpy(rates, _rates, sizeof(_rates));
}
return true;
}
char* output_state_str(void){
output_state state;
LOCK;
state = output.state;
UNLOCK;
switch (state) {
case OUTPUT_OFF: return STR(OUTPUT_OFF);
case OUTPUT_STOPPED: return STR(OUTPUT_STOPPED);
case OUTPUT_BUFFER: return STR(OUTPUT_BUFFER);
case OUTPUT_RUNNING: return STR(OUTPUT_RUNNING);
case OUTPUT_PAUSE_FRAMES: return STR(OUTPUT_PAUSE_FRAMES);
case OUTPUT_SKIP_FRAMES: return STR(OUTPUT_SKIP_FRAMES);
case OUTPUT_START_AT: return STR(OUTPUT_START_AT);
default: return "OUTPUT_UNKNOWN_STATE";
}
}
bool output_stopped(void) {
output_state state;
LOCK;
state = output.state;
UNLOCK;
return state <= OUTPUT_STOPPED;
}

View File

@@ -0,0 +1,380 @@
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
* Philippe G. 2019, 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 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/>.
*
*/
#include "squeezelite.h"
#include "driver/i2s.h"
#include "perf_trace.h"
#include <signal.h>
#include "time.h"
#define DECLARE_ALL_MIN_MAX \
DECLARE_MIN_MAX(o); \
DECLARE_MIN_MAX(s); \
DECLARE_MIN_MAX(loci2sbuf); \
DECLARE_MIN_MAX(req); \
DECLARE_MIN_MAX(rec); \
DECLARE_MIN_MAX(over); \
DECLARE_MIN_MAX(i2savailable);\
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(loci2sbuf); \
RESET_MIN_MAX(req); \
RESET_MIN_MAX(rec); \
RESET_MIN_MAX(over); \
RESET_MIN_MAX(i2savailable);\
RESET_MIN_MAX(i2s_time);\
RESET_MIN_MAX(buffering);
#define STATS_PERIOD_MS 5000
// Prevent compile errors if dac output is
// included in the build and not actually activated in menuconfig
#ifndef CONFIG_I2S_BCK_IO
#define CONFIG_I2S_BCK_IO -1
#endif
#ifndef CONFIG_I2S_WS_IO
#define CONFIG_I2S_WS_IO -1
#endif
#ifndef CONFIG_I2S_DO_IO
#define CONFIG_I2S_DO_IO -1
#endif
#ifndef CONFIG_I2S_NUM
#define CONFIG_I2S_NUM -1
#endif
#if REPACK && BYTES_PER_FRAMES == 4
#error "REPACK is not compatible with BYTES_PER_FRAME=4"
#endif
#define LOCK mutex_lock(outputbuf->mutex)
#define UNLOCK mutex_unlock(outputbuf->mutex)
#define FRAME_BLOCK MAX_SILENCE_FRAMES
extern struct outputstate output;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern u8_t *silencebuf;
static log_level loglevel;
static size_t i2s_buffer_size = 0;
static bool running = true;
static bool isI2SStarted=false;
static struct buffer _i2s_buffer_structure;
static struct buffer *i2sbuffer=&_i2s_buffer_structure;
static i2s_config_t i2s_config;
static int bytes_per_frame;
static thread_type thread;
static pthread_t stats_thread;
DECLARE_ALL_MIN_MAX;
static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
static void *output_thread_i2s();
static void *output_thread_i2s_stats();
/****************************************************************************************
* Initialize the DAC output
*/
void output_init_i2s(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
loglevel = level;
#ifdef CONFIG_I2S_BITS_PER_CHANNEL
switch (CONFIG_I2S_BITS_PER_CHANNEL) {
case 24:
output.format = S24_BE;
bytes_per_frame = 2*3;
break;
case 16:
output.format = S16_BE;
bytes_per_frame = 2*2;
break;
case 8:
output.format = S8_BE;
bytes_per_frame = 2*4;
break;
default:
LOG_ERROR("Unsupported bit depth %d",CONFIG_I2S_BITS_PER_CHANNEL);
break;
}
#else
output.format = S16_LE;
bytes_per_frame = 2*2;
#endif
output.write_cb = &_i2s_write_frames;
running=true;
// todo: move this to a hardware abstraction layer
//hal_dac_init(device);
i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX; // Only TX
i2s_config.sample_rate = output.current_sample_rate;
i2s_config.bits_per_sample = bytes_per_frame * 8 / 2;
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT; //2-channels
i2s_config.communication_format = I2S_COMM_FORMAT_I2S| I2S_COMM_FORMAT_I2S_MSB;
// todo: tune this parameter. Expressed in number of samples. Byte size depends on bit depth.
i2s_config.dma_buf_count = 10;
// From the I2S driver source, the DMA buffer size is 4092 bytes.
// so buf_len * 2 channels * 2 bytes/sample should be < 4092 or else it will be resized.
i2s_config.dma_buf_len = FRAME_BLOCK/2;
i2s_config.use_apll = false;
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
i2s_pin_config_t pin_config = { .bck_io_num = CONFIG_I2S_BCK_IO, .ws_io_num =
CONFIG_I2S_WS_IO, .data_out_num = CONFIG_I2S_DO_IO, .data_in_num = -1 //Not used
};
LOG_INFO("Initializing I2S with rate: %d, bits per sample: %d, buffer len: %d, number of buffers: %d ",
i2s_config.sample_rate, i2s_config.bits_per_sample, i2s_config.dma_buf_len, i2s_config.dma_buf_count);
i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
i2s_set_pin(CONFIG_I2S_NUM, &pin_config);
i2s_set_clk(CONFIG_I2S_NUM, output.current_sample_rate, i2s_config.bits_per_sample, 2);
isI2SStarted=false;
i2s_stop(CONFIG_I2S_NUM);
i2s_buffer_size = 5*FRAME_BLOCK*bytes_per_frame;
LOG_INFO("Allocating local DAC transfer buffer of %u bytes.",i2s_buffer_size);
buf_init(i2sbuffer,i2s_buffer_size);
if (!i2sbuffer->buf) {
LOG_ERROR("unable to malloc i2s buffer");
exit(0);
}
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE);
pthread_create(&thread, &attr, output_thread_i2s, NULL);
pthread_attr_destroy(&attr);
#if HAS_PTHREAD_SETNAME_NP
pthread_setname_np(thread, "output_i2s");
#endif
// leave stack size to default
pthread_create(&stats_thread, NULL, output_thread_i2s_stats, NULL);
#if HAS_PTHREAD_SETNAME_NP
pthread_setname_np(stats_thread, "output_i2s_sts");
#endif
}
/****************************************************************************************
* Terminate DAC output
*/
void output_close_i2s(void) {
i2s_driver_uninstall(CONFIG_I2S_NUM);
buf_destroy(i2sbuffer);
}
/****************************************************************************************
* Write frames to the output buffer
*/
static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr) {
size_t bytes = out_frames * bytes_per_frame;
assert(bytes > 0);
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);
}
#if !REPACK
if (gainL != FIXED_ONE || gainR!= FIXED_ONE) {
_apply_gain(outputbuf, out_frames, gainL, gainR);
}
memcpy(i2sbuffer->writep, outputbuf->readp, bytes);
#else
obuf = outputbuf->readp;
#endif
} else {
#if !REPACK
memcpy(i2sbuffer->writep, silencebuf, bytes);
#endif
}
#if REPACK
_scale_and_pack_frames(optr, (s32_t *)(void *)obuf, out_frames, gainL, gainR, output.format);
#endif
_buf_inc_writep(i2sbuffer, bytes);
return bytes / bytes_per_frame;
}
/****************************************************************************************
* Main output thread
*/
static void *output_thread_i2s() {
frames_t frames=0;
frames_t available_frames_space=0;
size_t bytes_to_send_i2s=0, // Contiguous buffer which can be addressed
i2s_bytes_written = 0,
i2s_total_bytes_written=0; //actual size that the i2s port was able to write
uint32_t timer_start=0;
static int count = 0;
output_state state;
while (running) {
i2s_bytes_written=0;
frames=0;
available_frames_space=0;
bytes_to_send_i2s=0, // Contiguous buffer which can be addressed
i2s_bytes_written = 0; //actual size that the i2s port was able to write
TIME_MEASUREMENT_START(timer_start);
LOCK;
state =output.state;
if (output.state == OUTPUT_OFF) {
UNLOCK;
LOG_INFO("Output state is off.");
LOG_SDEBUG("Current buffer free: %10d, cont read: %10d",_buf_space(i2sbuffer),_buf_cont_read(i2sbuffer));
if(isI2SStarted) {
isI2SStarted=false;
i2s_stop(CONFIG_I2S_NUM);
}
usleep(200000);
continue;
}
LOG_SDEBUG("Current buffer free: %10d, cont read: %10d",_buf_space(i2sbuffer),_buf_cont_read(i2sbuffer));
output.device_frames =0;
output.updated = gettime_ms();
output.frames_played_dmp = output.frames_played;
do{
// fill our buffer
available_frames_space = min(_buf_space(i2sbuffer), _buf_cont_write(i2sbuffer)) / bytes_per_frame;
if(available_frames_space)
{
frames = _output_frames( available_frames_space ); // Keep the transfer buffer full
SET_MIN_MAX( available_frames_space,req);
SET_MIN_MAX(frames,rec);
}
}while(available_frames_space>0 && frames>0);
SET_MIN_MAX_SIZED(_buf_used(outputbuf),o,outputbuf->size);
SET_MIN_MAX_SIZED(_buf_used(streambuf),s,streambuf->size);
UNLOCK;
LOG_SDEBUG("Current buffer free: %10d, cont read: %10d",_buf_space(i2sbuffer),_buf_cont_read(i2sbuffer));
SET_MIN_MAX( TIME_MEASUREMENT_GET(timer_start),buffering);
SET_MIN_MAX_SIZED(_buf_used(i2sbuffer),loci2sbuf,i2sbuffer->size);
bytes_to_send_i2s = _buf_cont_read(i2sbuffer);
SET_MIN_MAX(bytes_to_send_i2s,i2savailable);
i2s_total_bytes_written=0;
while (bytes_to_send_i2s>0 )
{
// now send all the data
TIME_MEASUREMENT_START(timer_start);
if(!isI2SStarted)
{
isI2SStarted=true;
LOG_INFO("Restarting I2S.");
i2s_start(CONFIG_I2S_NUM);
if( i2s_config.sample_rate != output.current_sample_rate)
{
i2s_config.sample_rate = output.current_sample_rate;
i2s_set_sample_rates(CONFIG_I2S_NUM, i2s_config.sample_rate);
}
}
count++;
LOG_SDEBUG("Outputting to I2S");
LOG_SDEBUG("Current buffer free: %10d, cont read: %10d",_buf_space(i2sbuffer),_buf_cont_read(i2sbuffer));
i2s_write(CONFIG_I2S_NUM, i2sbuffer->readp,bytes_to_send_i2s, &i2s_bytes_written, portMAX_DELAY);
_buf_inc_readp(i2sbuffer,i2s_bytes_written);
if(i2s_bytes_written!=bytes_to_send_i2s)
{
LOG_WARN("I2S DMA Overflow! available bytes: %d, I2S wrote %d bytes", bytes_to_send_i2s,i2s_bytes_written);
}
LOG_SDEBUG("DONE Outputting to I2S. Wrote: %d bytes out of %d", i2s_bytes_written,bytes_to_send_i2s);
LOG_SDEBUG("Current buffer free: %10d, cont read: %10d",_buf_space(i2sbuffer),_buf_cont_read(i2sbuffer));
i2s_total_bytes_written+=i2s_bytes_written;
SET_MIN_MAX( TIME_MEASUREMENT_GET(timer_start),i2s_time);
if(bytes_to_send_i2s>0) {
SET_MIN_MAX(bytes_to_send_i2s-i2s_bytes_written,over);
}
bytes_to_send_i2s = _buf_cont_read(i2sbuffer);
SET_MIN_MAX(bytes_to_send_i2s,i2savailable);
}
}
return 0;
}
/****************************************************************************************
* Stats output thread
*/
static void *output_thread_i2s_stats() {
while (running) {
LOCK;
output_state state = output.state;
UNLOCK;
if(state>OUTPUT_STOPPED){
LOG_INFO( "Output State: %d, current sample rate: %d, bytes per frame: %d",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);
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD4);
LOG_INFO(LINE_MIN_MAX_FORMAT_STREAM, LINE_MIN_MAX_STREAM("stream",s));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("output",o));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("dac buf used",loci2sbuf));
LOG_INFO(LINE_MIN_MAX_FORMAT_FOOTER);
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("i2swrite",i2savailable));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("requested",req));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("received",rec));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("overflow",over));
LOG_INFO(LINE_MIN_MAX_FORMAT_FOOTER);
LOG_INFO("");
LOG_INFO(" ----------+----------+-----------+-----------+ ");
LOG_INFO(" max (us) | min (us) | avg(us) | count | ");
LOG_INFO(" ----------+----------+-----------+-----------+ ");
LOG_INFO(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("Buffering(us)",buffering));
LOG_INFO(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("i2s tfr(us)",i2s_time));
LOG_INFO(" ----------+----------+-----------+-----------+");
RESET_ALL_MIN_MAX;
}
usleep(STATS_PERIOD_MS *1000);
}
return NULL;
}

View File

@@ -0,0 +1,372 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
// Scale and pack functions
#include "squeezelite.h"
#if BYTES_PER_FRAM == 4
#define MAX_VAL16 0x7fffffffLL
#define MAX_SCALESAMPLE 0x7fffffffffffLL
#define MIN_SCALESAMPLE -MAX_SCALESAMPLE
#else
#define MAX_SCALESAMPLE 0x7fffffffffffLL
#define MIN_SCALESAMPLE -MAX_SCALESAMPLE
#endif
// inlining these on windows prevents them being linkable...
#if !WIN
inline
#endif
s32_t gain(s32_t gain, s32_t sample) {
s64_t res = (s64_t)gain * (s64_t)sample;
if (res > MAX_SCALESAMPLE) res = MAX_SCALESAMPLE;
if (res < MIN_SCALESAMPLE) res = MIN_SCALESAMPLE;
return (s32_t) (res >> 16);
}
#if !WIN
inline
#endif
s32_t to_gain(float f) {
return (s32_t)(f * 65536.0F);
}
void _scale_and_pack_frames(void *outputptr, s32_t *inputptr, frames_t cnt, s32_t gainL, s32_t gainR, output_format format) {
switch(format) {
#if DSD
case U32_LE:
{
#if SL_LITTLE_ENDIAN
memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME);
#else
u32_t *optr = (u32_t *)(void *)outputptr;
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
(lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24;
*(optr++) =
(rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 |
(rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24;
}
#endif
}
break;
case U32_BE:
{
#if SL_LITTLE_ENDIAN
u32_t *optr = (u32_t *)(void *)outputptr;
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
(lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24;
*(optr++) =
(rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 |
(rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24;
}
#else
memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME);
#endif
}
break;
case U16_LE:
{
u32_t *optr = (u32_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
while (cnt--) {
*(optr++) = (*(inputptr) >> 16 & 0x0000ffff) | (*(inputptr + 1) & 0xffff0000);
inputptr += 2;
}
#else
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 |
(rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24;
}
#endif
}
break;
case U16_BE:
{
u32_t *optr = (u32_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
(rsample & 0xff000000) >> 8 | (rsample & 0x00ff0000) << 8;
}
#else
while (cnt--) {
*(optr++) = (*(inputptr) & 0xffff0000) | (*(inputptr + 1) >> 16 & 0x0000ffff);
inputptr += 2;
}
#endif
}
break;
case U8:
{
u16_t *optr = (u16_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
while (cnt--) {
*(optr++) = (u16_t)((*(inputptr) >> 24 & 0x000000ff) | (*(inputptr + 1) >> 16 & 0x0000ff00));
inputptr += 2;
}
#else
while (cnt--) {
*(optr++) = (u16_t)((*(inputptr) >> 16 & 0x0000ff00) | (*(inputptr + 1) >> 24 & 0x000000ff));
inputptr += 2;
}
#endif
}
break;
#endif
case S16_LE:
{
u32_t *optr = (u32_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
*(optr++) = (*(inputptr) >> 16 & 0x0000ffff) | (*(inputptr + 1) & 0xffff0000);
inputptr += 2;
}
} else {
while (cnt--) {
*(optr++) = (gain(gainL, *(inputptr)) >> 16 & 0x0000ffff) | (gain(gainR, *(inputptr+1)) & 0xffff0000);
inputptr += 2;
}
}
#else
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 |
(rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24;
}
} else {
while (cnt--) {
s32_t lsample = gain(gainL, *(inputptr++));
s32_t rsample = gain(gainR, *(inputptr++));
*(optr++) =
(lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 |
(rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24;
}
}
#endif
}
break;
case S24_LE:
{
u32_t *optr = (u32_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
*(optr++) = *(inputptr++) >> 8;
*(optr++) = *(inputptr++) >> 8;
}
} else {
while (cnt--) {
*(optr++) = gain(gainL, *(inputptr++)) >> 8;
*(optr++) = gain(gainR, *(inputptr++)) >> 8;
}
}
#else
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0xff000000) >> 16 | (lsample & 0x00ff0000) | (lsample & 0x0000ff00 << 16);
*(optr++) =
(rsample & 0xff000000) >> 16 | (rsample & 0x00ff0000) | (rsample & 0x0000ff00 << 16);
}
} else {
while (cnt--) {
s32_t lsample = gain(gainL, *(inputptr++));
s32_t rsample = gain(gainR, *(inputptr++));
*(optr++) =
(lsample & 0xff000000) >> 16 | (lsample & 0x00ff0000) | (lsample & 0x0000ff00 << 16);
*(optr++) =
(rsample & 0xff000000) >> 16 | (rsample & 0x00ff0000) | (rsample & 0x0000ff00 << 16);
}
}
#endif
}
break;
case S24_3LE:
{
u8_t *optr = (u8_t *)(void *)outputptr;
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt) {
// attempt to do 32 bit memory accesses - move 2 frames at once: 16 bytes -> 12 bytes
// falls through to exception case when not aligned or if less than 2 frames to move
if (((uintptr_t)optr & 0x3) == 0 && cnt >= 2) {
u32_t *o_ptr = (u32_t *)(void *)optr;
while (cnt >= 2) {
s32_t l1 = *(inputptr++); s32_t r1 = *(inputptr++);
s32_t l2 = *(inputptr++); s32_t r2 = *(inputptr++);
#if SL_LITTLE_ENDIAN
*(o_ptr++) = (l1 & 0xffffff00) >> 8 | (r1 & 0x0000ff00) << 16;
*(o_ptr++) = (r1 & 0xffff0000) >> 16 | (l2 & 0x00ffff00) << 8;
*(o_ptr++) = (l2 & 0xff000000) >> 24 | (r2 & 0xffffff00);
#else
*(o_ptr++) = (l1 & 0x0000ff00) << 16 | (l1 & 0x00ff0000) | (l1 & 0xff000000) >> 16 |
(r1 & 0x0000ff00) >> 8;
*(o_ptr++) = (r1 & 0x00ff0000) << 8 | (r1 & 0xff000000) >> 8 | (l2 & 0x0000ff00) |
(l2 & 0x00ff0000) >> 16;
*(o_ptr++) = (l2 & 0xff000000) | (r2 & 0x0000ff00) << 8 | (r2 & 0x00ff0000) >> 8 |
(r2 & 0xff000000) >> 24;
#endif
optr += 12;
cnt -= 2;
}
} else {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) = (lsample & 0x0000ff00) >> 8;
*(optr++) = (lsample & 0x00ff0000) >> 16;
*(optr++) = (lsample & 0xff000000) >> 24;
*(optr++) = (rsample & 0x0000ff00) >> 8;
*(optr++) = (rsample & 0x00ff0000) >> 16;
*(optr++) = (rsample & 0xff000000) >> 24;
cnt--;
}
}
} else {
while (cnt) {
// attempt to do 32 bit memory accesses - move 2 frames at once: 16 bytes -> 12 bytes
// falls through to exception case when not aligned or if less than 2 frames to move
if (((uintptr_t)optr & 0x3) == 0 && cnt >= 2) {
u32_t *o_ptr = (u32_t *)(void *)optr;
while (cnt >= 2) {
s32_t l1 = gain(gainL, *(inputptr++)); s32_t r1 = gain(gainR, *(inputptr++));
s32_t l2 = gain(gainL, *(inputptr++)); s32_t r2 = gain(gainR, *(inputptr++));
#if SL_LITTLE_ENDIAN
*(o_ptr++) = (l1 & 0xffffff00) >> 8 | (r1 & 0x0000ff00) << 16;
*(o_ptr++) = (r1 & 0xffff0000) >> 16 | (l2 & 0x00ffff00) << 8;
*(o_ptr++) = (l2 & 0xff000000) >> 24 | (r2 & 0xffffff00);
#else
*(o_ptr++) = (l1 & 0x0000ff00) << 16 | (l1 & 0x00ff0000) | (l1 & 0xff000000) >> 16 |
(r1 & 0x0000ff00) >> 8;
*(o_ptr++) = (r1 & 0x00ff0000) << 8 | (r1 & 0xff000000) >> 8 | (l2 & 0x0000ff00) |
(l2 & 0x00ff0000) >> 16;
*(o_ptr++) = (l2 & 0xff000000) | (r2 & 0x0000ff00) << 8 | (r2 & 0x00ff0000) >> 8 |
(r2 & 0xff000000) >> 24;
#endif
optr += 12;
cnt -= 2;
}
} else {
s32_t lsample = gain(gainL, *(inputptr++));
s32_t rsample = gain(gainR, *(inputptr++));
*(optr++) = (lsample & 0x0000ff00) >> 8;
*(optr++) = (lsample & 0x00ff0000) >> 16;
*(optr++) = (lsample & 0xff000000) >> 24;
*(optr++) = (rsample & 0x0000ff00) >> 8;
*(optr++) = (rsample & 0x00ff0000) >> 16;
*(optr++) = (rsample & 0xff000000) >> 24;
cnt--;
}
}
}
}
break;
case S32_LE:
{
u32_t *optr = (u32_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME);
} else {
while (cnt--) {
*(optr++) = gain(gainL, *(inputptr++));
*(optr++) = gain(gainR, *(inputptr++));
}
}
#else
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
(lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24;
*(optr++) =
(rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 |
(rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24;
}
} else {
while (cnt--) {
s32_t lsample = gain(gainL, *(inputptr++));
s32_t rsample = gain(gainR, *(inputptr++));
*(optr++) =
(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
(lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24;
*(optr++) =
(rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 |
(rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24;
}
}
#endif
}
break;
default:
break;
}
}
#if !WIN
inline
#endif
void _apply_cross(struct buffer *outputbuf, frames_t out_frames, s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr) {
ISAMPLE_T *ptr = (ISAMPLE_T *)(void *)outputbuf->readp;
frames_t count = out_frames * 2;
while (count--) {
if (*cross_ptr > (ISAMPLE_T *)outputbuf->wrap) {
*cross_ptr -= outputbuf->size / BYTES_PER_FRAME * 2;
}
*ptr = gain(cross_gain_out, *ptr) + gain(cross_gain_in, **cross_ptr);
ptr++; (*cross_ptr)++;
}
}
#if !WIN
inline
#endif
void _apply_gain(struct buffer *outputbuf, frames_t count, s32_t gainL, s32_t gainR) {
ISAMPLE_T *ptrL = (ISAMPLE_T *)(void *)outputbuf->readp;
ISAMPLE_T *ptrR = (ISAMPLE_T *)(void *)outputbuf->readp + 1;
while (count--) {
*ptrL = gain(gainL, *ptrL);
*ptrR = gain(gainR, *ptrR);
ptrL += 2;
ptrR += 2;
}
}

View File

@@ -0,0 +1,488 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
#include "squeezelite.h"
#if BYTES_PER_FRAME == 4
#define SHIFT 16
#define OPTR_T u16_t
#else
#define OPTR_T u32_t
#define SHIFT 0
#endif
extern log_level loglevel;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct decodestate decode;
extern struct processstate process;
bool pcm_check_header = false;
#define LOCK_S mutex_lock(streambuf->mutex)
#define UNLOCK_S mutex_unlock(streambuf->mutex)
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
#if PROCESS
#define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex)
#define LOCK_O_not_direct if (!decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x) if (decode.direct) { x }
#define IF_PROCESS(x) if (!decode.direct) { x }
#else
#define LOCK_O_direct mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex)
#define LOCK_O_not_direct
#define UNLOCK_O_not_direct
#define IF_DIRECT(x) { x }
#define IF_PROCESS(x)
#endif
#define MAX_DECODE_FRAMES 4096
static u32_t sample_rates[] = {
11025, 22050, 32000, 44100, 48000, 8000, 12000, 16000, 24000, 96000, 88200, 176400, 192000, 352800, 384000, 705600, 768000
};
static u32_t sample_rate;
static u32_t sample_size;
static u32_t channels;
static bool bigendian;
static bool limit;
static u32_t audio_left;
static u32_t bytes_per_frame;
typedef enum { UNKNOWN = 0, WAVE, AIFF } header_format;
static void _check_header(void) {
u8_t *ptr = streambuf->readp;
unsigned bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
header_format format = UNKNOWN;
// simple parsing of wav and aiff headers and get to samples
if (bytes > 12) {
if (!memcmp(ptr, "RIFF", 4) && !memcmp(ptr+8, "WAVE", 4)) {
LOG_INFO("WAVE");
format = WAVE;
} else if (!memcmp(ptr, "FORM", 4) && (!memcmp(ptr+8, "AIFF", 4) || !memcmp(ptr+8, "AIFC", 4))) {
LOG_INFO("AIFF");
format = AIFF;
}
}
if (format != UNKNOWN) {
ptr += 12;
bytes -= 12;
while (bytes >= 8) {
char id[5];
unsigned len;
memcpy(id, ptr, 4);
id[4] = '\0';
if (format == WAVE) {
len = *(ptr+4) | *(ptr+5) << 8 | *(ptr+6) << 16| *(ptr+7) << 24;
} else {
len = *(ptr+4) << 24 | *(ptr+5) << 16 | *(ptr+6) << 8 | *(ptr+7);
}
LOG_INFO("header: %s len: %d", id, len);
if (format == WAVE && !memcmp(ptr, "data", 4)) {
ptr += 8;
_buf_inc_readp(streambuf, ptr - streambuf->readp);
audio_left = len;
if ((audio_left == 0xFFFFFFFF) || (audio_left == 0x7FFFEFFC)) {
LOG_INFO("wav audio size unknown: %u", audio_left);
limit = false;
} else {
LOG_INFO("wav audio size: %u", audio_left);
limit = true;
}
return;
}
if (format == AIFF && !memcmp(ptr, "SSND", 4) && bytes >= 16) {
unsigned offset = *(ptr+8) << 24 | *(ptr+9) << 16 | *(ptr+10) << 8 | *(ptr+11);
// following 4 bytes is blocksize - ignored
ptr += 8 + 8;
_buf_inc_readp(streambuf, ptr + offset - streambuf->readp);
// Reading from an upsampled stream, length could be wrong.
// Only use length in header for files.
if (stream.state == STREAMING_FILE) {
audio_left = len - 8 - offset;
LOG_INFO("aif audio size: %u", audio_left);
limit = true;
}
return;
}
if (format == WAVE && !memcmp(ptr, "fmt ", 4) && bytes >= 24) {
// override the server parsed values with our own
channels = *(ptr+10) | *(ptr+11) << 8;
sample_rate = *(ptr+12) | *(ptr+13) << 8 | *(ptr+14) << 16 | *(ptr+15) << 24;
sample_size = (*(ptr+22) | *(ptr+23) << 8) / 8;
bigendian = 0;
LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian);
}
if (format == AIFF && !memcmp(ptr, "COMM", 4) && bytes >= 26) {
int exponent;
// override the server parsed values with our own
channels = *(ptr+8) << 8 | *(ptr+9);
sample_size = (*(ptr+14) << 8 | *(ptr+15)) / 8;
bigendian = 1;
// sample rate is encoded as IEEE 80 bit extended format
// make some assumptions to simplify processing - only use first 32 bits of mantissa
exponent = ((*(ptr+16) & 0x7f) << 8 | *(ptr+17)) - 16383 - 31;
sample_rate = *(ptr+18) << 24 | *(ptr+19) << 16 | *(ptr+20) << 8 | *(ptr+21);
while (exponent < 0) { sample_rate >>= 1; ++exponent; }
while (exponent > 0) { sample_rate <<= 1; --exponent; }
LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian);
}
if (bytes >= len + 8) {
ptr += len + 8;
bytes -= (len + 8);
} else {
LOG_WARN("run out of data");
return;
}
}
} else {
LOG_WARN("unknown format - can't parse header");
}
}
static decode_state pcm_decode(void) {
unsigned bytes, in, out;
frames_t frames, count;
OPTR_T *optr;
u8_t *iptr;
u8_t tmp[3*8];
LOCK_S;
if ( decode.new_stream && ( ( stream.state == STREAMING_FILE ) || pcm_check_header ) ) {
_check_header();
}
LOCK_O_direct;
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
IF_DIRECT(
out = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
);
IF_PROCESS(
out = process.max_in_frames;
);
if ((stream.state <= DISCONNECT && bytes == 0) || (limit && audio_left == 0)) {
UNLOCK_O_direct;
UNLOCK_S;
return DECODE_COMPLETE;
}
if (decode.new_stream) {
LOG_INFO("setting track_start");
LOCK_O_not_direct;
output.track_start = outputbuf->writep;
decode.new_stream = false;
#if DSD
if (sample_size == 3 &&
is_stream_dop(((u8_t *)streambuf->readp) + (bigendian?0:2),
((u8_t *)streambuf->readp) + (bigendian?0:2) + sample_size,
sample_size * channels, bytes / (sample_size * channels))) {
LOG_INFO("file contains DOP");
if (output.dsdfmt == DOP_S24_LE || output.dsdfmt == DOP_S24_3LE)
output.next_fmt = output.dsdfmt;
else
output.next_fmt = DOP;
output.next_sample_rate = sample_rate;
output.fade = FADE_INACTIVE;
} else {
output.next_sample_rate = decode_newstream(sample_rate, output.supported_rates);
output.next_fmt = PCM;
if (output.fade_mode) _checkfade(true);
}
#else
output.next_sample_rate = decode_newstream(sample_rate, output.supported_rates);
if (output.fade_mode) _checkfade(true);
#endif
UNLOCK_O_not_direct;
IF_PROCESS(
out = process.max_in_frames;
);
bytes_per_frame = channels * sample_size;
}
IF_DIRECT(
optr = (OPTR_T *)outputbuf->writep;
);
IF_PROCESS(
optr = (OPTR_T *)process.inbuf;
);
iptr = (u8_t *)streambuf->readp;
in = bytes / bytes_per_frame;
// handle frame wrapping round end of streambuf
// - only need if resizing of streambuf does not avoid this, could occur in localfile case
if (in == 0 && bytes > 0 && _buf_used(streambuf) >= bytes_per_frame) {
memcpy(tmp, iptr, bytes);
memcpy(tmp + bytes, streambuf->buf, bytes_per_frame - bytes);
iptr = tmp;
in = 1;
}
frames = min(in, out);
frames = min(frames, MAX_DECODE_FRAMES);
if (limit && frames * bytes_per_frame > audio_left) {
LOG_INFO("reached end of audio");
frames = audio_left / bytes_per_frame;
}
count = frames * channels;
if (channels == 2) {
if (sample_size == 1) {
while (count--) {
*optr++ = *iptr++ << (24-SHIFT);
}
} else if (sample_size == 2) {
if (bigendian) {
#if BYTES_PER_FRAME == 4 && !SL_LITTLE_ENDIAN
// while loop below works as is, but memcpy is a win for that 16/16 typical case
memcpy(optr, iptr, count * BYTES_PER_FRAME / 2);
#else
while (count--) {
*optr++ = *(iptr) << (24-SHIFT) | *(iptr+1) << (16-SHIFT);
iptr += 2;
}
#endif
} else {
#if BYTES_PER_FRAME == 4 && SL_LITTLE_ENDIAN
// while loop below works as is, but memcpy is a win for that 16/16 typical case
memcpy(optr, iptr, count * BYTES_PER_FRAME / 2);
#else
while (count--) {
*optr++ = *(iptr) << (16-SHIFT) | *(iptr+1) << (24-SHIFT);
iptr += 2;
}
#endif
}
} else if (sample_size == 3) {
if (bigendian) {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr) << 8 | *(iptr+1);
#else
*optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8;
#endif
iptr += 3;
}
} else {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr+1) | *(iptr+2) << 8;
#else
*optr++ = *(iptr) << 8 | *(iptr+1) << 16 | *(iptr+2) << 24;
#endif
iptr += 3;
}
}
} else if (sample_size == 4) {
if (bigendian) {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr) << 8 | *(iptr+1);
#else
*optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3);
#endif
iptr += 4;
}
} else {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr+2) | *(iptr+3) << 8;
#else
*optr++ = *(iptr) | *(iptr+1) << 8 | *(iptr+2) << 16 | *(iptr+3) << 24;
#endif
iptr += 4;
}
}
}
} else if (channels == 1) {
if (sample_size == 1) {
while (count--) {
*optr = *iptr++ << (24-SHIFT);
*(optr+1) = *optr;
optr += 2;
}
} else if (sample_size == 2) {
if (bigendian) {
while (count--) {
*optr = *(iptr) << (24-SHIFT) | *(iptr+1) << (16-SHIFT);
*(optr+1) = *optr;
iptr += 2;
optr += 2;
}
} else {
while (count--) {
*optr = *(iptr) << (16-SHIFT) | *(iptr+1) << (24-SHIFT);
*(optr+1) = *optr;
iptr += 2;
optr += 2;
}
}
} else if (sample_size == 3) {
if (bigendian) {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr) << 8 | *(iptr+1);
#else
*optr = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8;
#endif
*(optr+1) = *optr;
iptr += 3;
optr += 2;
}
} else {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr+1) | *(iptr+2) << 8;
#else
*optr = *(iptr) << 8 | *(iptr+1) << 16 | *(iptr+2) << 24;
#endif
*(optr+1) = *optr;
iptr += 3;
optr += 2;
}
}
} else if (sample_size == 4) {
if (bigendian) {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr) << 8 | *(iptr+1);
#else
*optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3);
#endif
*(optr+1) = *optr;
iptr += 4;
optr += 2;
}
} else {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr+2) | *(iptr+3) << 8;
#else
*optr++ = *(iptr) | *(iptr+1) << 8 | *(iptr+2) << 16 | *(iptr+3) << 24;
#endif
*(optr+1) = *optr;
iptr += 4;
optr += 2;
}
}
}
} else {
LOG_ERROR("unsupported channels");
}
LOG_SDEBUG("decoded %u frames", frames);
_buf_inc_readp(streambuf, frames * bytes_per_frame);
if (limit) {
audio_left -= frames * bytes_per_frame;
}
IF_DIRECT(
_buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME);
);
IF_PROCESS(
process.in_frames = frames;
);
UNLOCK_O_direct;
UNLOCK_S;
return DECODE_RUNNING;
}
static void pcm_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
sample_size = size - '0' + 1;
sample_rate = sample_rates[rate - '0'];
channels = chan - '0';
bigendian = (endianness == '0');
limit = false;
LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian);
buf_adjust(streambuf, sample_size * channels);
}
static void pcm_close(void) {
buf_adjust(streambuf, 1);
}
struct codec *register_pcm(void) {
if ( pcm_check_header )
{
static struct codec ret = {
'p', // id
"wav,aif,pcm", // types
4096, // min read
102400, // min space
pcm_open, // open
pcm_close, // close
pcm_decode, // decode
};
LOG_INFO("using pcm to decode wav,aif,pcm");
return &ret;
}
else
{
static struct codec ret = {
'p', // id
"aif,pcm", // types
4096, // min read
102400, // min space
pcm_open, // open
pcm_close, // close
pcm_decode, // decode
};
LOG_INFO("using pcm to decode aif,pcm");
return &ret;
}
return NULL;
}

View File

@@ -0,0 +1,194 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
// sample processing - only included when building with PROCESS set
#include "squeezelite.h"
#if PROCESS
extern log_level loglevel;
extern struct buffer *outputbuf;
extern struct decodestate decode;
struct processstate process;
extern struct codec *codec;
#define LOCK_D mutex_lock(decode.mutex);
#define UNLOCK_D mutex_unlock(decode.mutex);
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
// macros to map to processing functions - currently only resample.c
// this can be made more generic when multiple processing mechanisms get added
#if RESAMPLE || RESAMPLE16
#define SAMPLES_FUNC resample_samples
#define DRAIN_FUNC resample_drain
#define NEWSTREAM_FUNC resample_newstream
#define FLUSH_FUNC resample_flush
#define INIT_FUNC resample_init
#endif
// transfer all processed frames to the output buf
static void _write_samples(void) {
frames_t frames = process.out_frames;
ISAMPLE_T *iptr = (ISAMPLE_T *) process.outbuf;
unsigned cnt = 10;
LOCK_O;
while (frames > 0) {
frames_t f = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
ISAMPLE_T *optr = (ISAMPLE_T*) outputbuf->writep;
if (f > 0) {
f = min(f, frames);
memcpy(optr, iptr, f * BYTES_PER_FRAME);
frames -= f;
_buf_inc_writep(outputbuf, f * BYTES_PER_FRAME);
iptr += f * BYTES_PER_FRAME / sizeof(*iptr);
} else if (cnt--) {
// there should normally be space in the output buffer, but may need to wait during drain phase
UNLOCK_O;
usleep(10000);
LOCK_O;
} else {
// bail out if no space found after 100ms to avoid locking
LOG_ERROR("unable to get space in output buffer");
UNLOCK_O;
return;
}
}
UNLOCK_O;
}
// process samples - called with decode mutex set
void process_samples(void) {
SAMPLES_FUNC(&process);
_write_samples();
process.in_frames = 0;
}
// drain at end of track - called with decode mutex set
void process_drain(void) {
bool done;
do {
done = DRAIN_FUNC(&process);
_write_samples();
} while (!done);
LOG_DEBUG("processing track complete - frames in: %lu out: %lu", process.total_in, process.total_out);
}
// new stream - called with decode mutex set
unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned supported_rates[]) {
bool active = NEWSTREAM_FUNC(&process, raw_sample_rate, supported_rates);
LOG_INFO("processing: %s", active ? "active" : "inactive");
*direct = !active;
if (active) {
unsigned max_in_frames, max_out_frames;
process.in_frames = process.out_frames = 0;
process.total_in = process.total_out = 0;
max_in_frames = codec->min_space / BYTES_PER_FRAME ;
// increase size of output buffer by 10% as output rate is not an exact multiple of input rate
if (process.out_sample_rate % process.in_sample_rate == 0) {
max_out_frames = max_in_frames * (process.out_sample_rate / process.in_sample_rate);
} else {
max_out_frames = (int)(1.1 * (float)max_in_frames * (float)process.out_sample_rate / (float)process.in_sample_rate);
}
if (process.max_in_frames != max_in_frames) {
LOG_DEBUG("creating process buf in frames: %u", max_in_frames);
if (process.inbuf) free(process.inbuf);
process.inbuf = malloc(max_in_frames * BYTES_PER_FRAME);
process.max_in_frames = max_in_frames;
}
if (process.max_out_frames != max_out_frames) {
LOG_DEBUG("creating process buf out frames: %u", max_out_frames);
if (process.outbuf) free(process.outbuf);
process.outbuf = malloc(max_out_frames * BYTES_PER_FRAME);
process.max_out_frames = max_out_frames;
}
if (!process.inbuf || !process.outbuf) {
LOG_ERROR("malloc fail creating process buffers");
*direct = true;
return raw_sample_rate;
}
return process.out_sample_rate;
}
return raw_sample_rate;
}
// process flush - called with decode mutex set
void process_flush(void) {
LOG_INFO("process flush");
FLUSH_FUNC();
process.in_frames = 0;
}
// init - called with no mutex
void process_init(char *opt) {
bool enabled = INIT_FUNC(opt);
memset(&process, 0, sizeof(process));
if (enabled) {
LOCK_D;
decode.process = true;
UNLOCK_D;
}
}
#endif // #if PROCESS

View File

@@ -0,0 +1,379 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
// upsampling using libsoxr - only included if RESAMPLE set
#include "squeezelite.h"
#if RESAMPLE
#include <math.h>
#include <soxr.h>
extern log_level loglevel;
struct soxr {
soxr_t resampler;
size_t old_clips;
unsigned long q_recipe;
unsigned long q_flags;
double q_precision; /* Conversion precision (in bits). 20 */
double q_phase_response; /* 0=minimum, ... 50=linear, ... 100=maximum 50 */
double q_passband_end; /* 0dB pt. bandwidth to preserve; nyquist=1 0.913 */
double q_stopband_begin; /* Aliasing/imaging control; > passband_end 1 */
double scale;
bool max_rate;
bool exception;
#if !LINKALL
// soxr symbols to be dynamically loaded
soxr_io_spec_t (* soxr_io_spec)(soxr_datatype_t itype, soxr_datatype_t otype);
soxr_quality_spec_t (* soxr_quality_spec)(unsigned long recipe, unsigned long flags);
soxr_t (* soxr_create)(double, double, unsigned, soxr_error_t *,
soxr_io_spec_t const *, soxr_quality_spec_t const *, soxr_runtime_spec_t const *);
void (* soxr_delete)(soxr_t);
soxr_error_t (* soxr_process)(soxr_t, soxr_in_t, size_t, size_t *, soxr_out_t, size_t olen, size_t *);
size_t *(* soxr_num_clips)(soxr_t);
#if RESAMPLE_MP
soxr_runtime_spec_t (* soxr_runtime_spec)(unsigned num_threads);
#endif
// soxr_strerror is a macro so not included here
#endif
};
static struct soxr *r;
#if LINKALL
#define SOXR(h, fn, ...) (soxr_ ## fn)(__VA_ARGS__)
#else
#define SOXR(h, fn, ...) (h)->soxr_##fn(__VA_ARGS__)
#endif
void resample_samples(struct processstate *process) {
size_t idone, odone;
size_t clip_cnt;
soxr_error_t error =
SOXR(r, process, r->resampler, process->inbuf, process->in_frames, &idone, process->outbuf, process->max_out_frames, &odone);
if (error) {
LOG_INFO("soxr_process error: %s", soxr_strerror(error));
return;
}
if (idone != process->in_frames) {
// should not get here if buffers are big enough...
LOG_ERROR("should not get here - partial sox process: %u of %u processed %u of %u out",
(unsigned)idone, process->in_frames, (unsigned)odone, process->max_out_frames);
}
process->out_frames = odone;
process->total_in += idone;
process->total_out += odone;
clip_cnt = *(SOXR(r, num_clips, r->resampler));
if (clip_cnt - r->old_clips) {
LOG_SDEBUG("resampling clips: %u", (unsigned)(clip_cnt - r->old_clips));
r->old_clips = clip_cnt;
}
}
bool resample_drain(struct processstate *process) {
size_t odone;
size_t clip_cnt;
soxr_error_t error = SOXR(r, process, r->resampler, NULL, 0, NULL, process->outbuf, process->max_out_frames, &odone);
if (error) {
LOG_INFO("soxr_process error: %s", soxr_strerror(error));
return true;
}
process->out_frames = odone;
process->total_out += odone;
clip_cnt = *(SOXR(r, num_clips, r->resampler));
if (clip_cnt - r->old_clips) {
LOG_DEBUG("resampling clips: %u", (unsigned)(clip_cnt - r->old_clips));
r->old_clips = clip_cnt;
}
if (odone == 0) {
LOG_INFO("resample track complete - total track clips: %u", r->old_clips);
SOXR(r, delete, r->resampler);
r->resampler = NULL;
return true;
} else {
return false;
}
}
bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned supported_rates[]) {
unsigned outrate = 0;
int i;
if (r->exception) {
// find direct match - avoid resampling
for (i = 0; supported_rates[i]; i++) {
if (raw_sample_rate == supported_rates[i]) {
outrate = raw_sample_rate;
break;
}
}
// else find next highest sync sample rate
while (!outrate && i >= 0) {
if (supported_rates[i] > raw_sample_rate && supported_rates[i] % raw_sample_rate == 0) {
outrate = supported_rates[i];
break;
}
i--;
}
}
if (!outrate) {
if (r->max_rate) {
// resample to max rate for device
outrate = supported_rates[0];
} else {
// resample to max sync sample rate
for (i = 0; supported_rates[i]; i++) {
if (supported_rates[i] % raw_sample_rate == 0 || raw_sample_rate % supported_rates[i] == 0) {
outrate = supported_rates[i];
break;
}
}
}
if (!outrate) {
outrate = supported_rates[0];
}
}
process->in_sample_rate = raw_sample_rate;
process->out_sample_rate = outrate;
if (r->resampler) {
SOXR(r, delete, r->resampler);
r->resampler = NULL;
}
if (raw_sample_rate != outrate) {
soxr_io_spec_t io_spec;
soxr_quality_spec_t q_spec;
soxr_error_t error;
#if RESAMPLE_MP
soxr_runtime_spec_t r_spec;
#endif
LOG_INFO("resampling from %u -> %u", raw_sample_rate, outrate);
#if BYTES_PER_FRAME == 4
io_spec = SOXR(r, io_spec, SOXR_INT16_I, SOXR_INT16_I);
io_spec.flags = SOXR_NO_DITHER;
#else
io_spec = SOXR(r, io_spec, SOXR_INT32_I, SOXR_INT32_I);
#endif
io_spec.scale = r->scale;
q_spec = SOXR(r, quality_spec, r->q_recipe, r->q_flags);
if (r->q_precision > 0) {
q_spec.precision = r->q_precision;
}
if (r->q_passband_end > 0) {
q_spec.passband_end = r->q_passband_end;
}
if (r->q_stopband_begin > 0) {
q_spec.stopband_begin = r->q_stopband_begin;
}
if (r->q_phase_response > -1) {
q_spec.phase_response = r->q_phase_response;
}
#if RESAMPLE_MP
r_spec = SOXR(r, runtime_spec, 0); // make use of libsoxr OpenMP support allowing parallel execution if multiple cores
#endif
LOG_DEBUG("resampling with soxr_quality_spec_t[precision: %03.1f, passband_end: %03.6f, stopband_begin: %03.6f, "
"phase_response: %03.1f, flags: 0x%02x], soxr_io_spec_t[scale: %03.2f]", q_spec.precision,
q_spec.passband_end, q_spec.stopband_begin, q_spec.phase_response, q_spec.flags, io_spec.scale);
#if RESAMPLE_MP
r->resampler = SOXR(r, create, raw_sample_rate, outrate, 2, &error, &io_spec, &q_spec, &r_spec);
#else
r->resampler = SOXR(r, create, raw_sample_rate, outrate, 2, &error, &io_spec, &q_spec, NULL);
#endif
if (error) {
LOG_INFO("soxr_create error: %s", soxr_strerror(error));
return false;
}
r->old_clips = 0;
return true;
} else {
LOG_INFO("disable resampling - rates match");
return false;
}
}
void resample_flush(void) {
if (r->resampler) {
SOXR(r, delete, r->resampler);
r->resampler = NULL;
}
}
static bool load_soxr(void) {
#if !LINKALL
void *handle = dlopen(LIBSOXR, RTLD_NOW);
char *err;
if (!handle) {
LOG_INFO("dlerror: %s", dlerror());
return false;
}
r->soxr_io_spec = dlsym(handle, "soxr_io_spec");
r->soxr_quality_spec = dlsym(handle, "soxr_quality_spec");
r->soxr_create = dlsym(handle, "soxr_create");
r->soxr_delete = dlsym(handle, "soxr_delete");
r->soxr_process = dlsym(handle, "soxr_process");
r->soxr_num_clips = dlsym(handle, "soxr_num_clips");
#if RESAMPLE_MP
r->soxr_runtime_spec = dlsym(handle, "soxr_runtime_spec");
#endif
if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err);
return false;
}
LOG_INFO("loaded "LIBSOXR);
#endif
return true;
}
bool resample_init(char *opt) {
char *recipe = NULL, *flags = NULL;
char *atten = NULL;
char *precision = NULL, *passband_end = NULL, *stopband_begin = NULL, *phase_response = NULL;
r = malloc(sizeof(struct soxr));
if (!r) {
LOG_WARN("resampling disabled");
return false;
}
r->resampler = NULL;
r->old_clips = 0;
r->max_rate = false;
r->exception = false;
if (!load_soxr()) {
LOG_WARN("resampling disabled");
return false;
}
if (opt) {
recipe = next_param(opt, ':');
flags = next_param(NULL, ':');
atten = next_param(NULL, ':');
precision = next_param(NULL, ':');
passband_end = next_param(NULL, ':');
stopband_begin = next_param(NULL, ':');
phase_response = next_param(NULL, ':');
}
#if BYTES_PER_FRAME == 4
// default to LQ (16 bits) if not user specified
r->q_recipe = SOXR_LQ | SOXR_MINIMUM_PHASE;
r->q_flags = SOXR_ROLLOFF_NONE;
r->q_phase_response = 0;
#else
// default to HQ (20 bits) if not user specified
r->q_recipe = SOXR_HQ;
r->q_flags = 0;
r->q_phase_response = -1;
#endif
// default to 1db of attenuation if not user specified
r->scale = pow(10, -1.0 / 20);
// override recipe derived values with user specified values
r->q_precision = 0;
r->q_passband_end = 0.75;
r->q_stopband_begin = 1.25;
if (recipe && recipe[0] != '\0') {
if (strchr(recipe, 'v')) r->q_recipe = SOXR_VHQ;
if (strchr(recipe, 'h')) r->q_recipe = SOXR_HQ;
if (strchr(recipe, 'm')) r->q_recipe = SOXR_MQ;
if (strchr(recipe, 'l')) r->q_recipe = SOXR_LQ;
if (strchr(recipe, 'q')) r->q_recipe = SOXR_QQ;
if (strchr(recipe, 'L')) r->q_recipe |= SOXR_LINEAR_PHASE;
if (strchr(recipe, 'I')) r->q_recipe |= SOXR_INTERMEDIATE_PHASE;
if (strchr(recipe, 'M')) r->q_recipe |= SOXR_MINIMUM_PHASE;
if (strchr(recipe, 's')) r->q_recipe |= SOXR_STEEP_FILTER;
// X = async resampling to max_rate
if (strchr(recipe, 'X')) r->max_rate = true;
// E = exception, only resample if native rate is not supported
if (strchr(recipe, 'E')) r->exception = true;
}
if (flags) {
r->q_flags = strtoul(flags, 0, 16);
}
if (atten) {
double scale = pow(10, -atof(atten) / 20);
if (scale > 0 && scale <= 1.0) {
r->scale = scale;
}
}
if (precision) {
r->q_precision = atof(precision);
}
if (passband_end) {
r->q_passband_end = atof(passband_end) / 100;
}
if (stopband_begin) {
r->q_stopband_begin = atof(stopband_begin) / 100;
}
if (phase_response) {
r->q_phase_response = atof(phase_response);
}
LOG_INFO("resampling %s recipe: 0x%02x, flags: 0x%02x, scale: %03.2f, precision: %03.1f, passband_end: %03.5f, stopband_begin: %03.5f, phase_response: %03.1f",
r->max_rate ? "async" : "sync",
r->q_recipe, r->q_flags, r->scale, r->q_precision, r->q_passband_end, r->q_stopband_begin, r->q_phase_response);
return true;
}
#endif // #if RESAMPLE

View File

@@ -0,0 +1,164 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
// upsampling using libsoxr - only included if RESAMPLE set
#include "squeezelite.h"
#if RESAMPLE16
#include <resample16.h>
extern log_level loglevel;
struct resample16 {
struct resample16_s *resampler;
bool max_rate;
bool exception;
bool interp;
resample16_filter_e filter;
};
static struct resample16 r;
void resample_samples(struct processstate *process) {
ssize_t odone;
odone = resample16(r.resampler, (HWORD*) process->inbuf, process->in_frames, (HWORD*) process->outbuf);
if (odone < 0) {
LOG_INFO("resample16 error");
return;
}
process->out_frames = odone;
process->total_in += process->in_frames;
process->total_out += odone;
}
bool resample_drain(struct processstate *process) {
process->out_frames = 0;
LOG_INFO("resample track complete");
resample16_delete(r.resampler);
r.resampler = NULL;
return true;
}
bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned supported_rates[]) {
unsigned outrate = 0;
int i;
if (r.exception) {
// find direct match - avoid resampling
for (i = 0; supported_rates[i]; i++) {
if (raw_sample_rate == supported_rates[i]) {
outrate = raw_sample_rate;
break;
}
}
// else find next highest sync sample rate
while (!outrate && i >= 0) {
if (supported_rates[i] > raw_sample_rate && supported_rates[i] % raw_sample_rate == 0) {
outrate = supported_rates[i];
break;
}
i--;
}
}
if (!outrate) {
if (r.max_rate) {
// resample to max rate for device
outrate = supported_rates[0];
} else {
// resample to max sync sample rate
for (i = 0; supported_rates[i]; i++) {
if (supported_rates[i] % raw_sample_rate == 0 || raw_sample_rate % supported_rates[i] == 0) {
outrate = supported_rates[i];
break;
}
}
}
if (!outrate) {
outrate = supported_rates[0];
}
}
process->in_sample_rate = raw_sample_rate;
process->out_sample_rate = outrate;
if (r.resampler) {
resample16_delete(r.resampler);
r.resampler = NULL;
}
if (raw_sample_rate != outrate) {
LOG_INFO("resampling from %u -> %u", raw_sample_rate, outrate);
r.resampler = resample16_create((float) outrate / raw_sample_rate, r.filter, NULL, false);
return true;
} else {
LOG_INFO("disable resampling - rates match");
return false;
}
}
void resample_flush(void) {
if (r.resampler) {
resample16_delete(r.resampler);
r.resampler = NULL;
}
}
bool resample_init(char *opt) {
char *filter = NULL, *interp = NULL;
r.resampler = NULL;
r.max_rate = false;
r.exception = false;
if (opt) {
filter = next_param(opt, ':');
interp = next_param(NULL, ':');
}
if (filter) {
if (*filter == 'm') r.filter = RESAMPLE16_MED;
else if (*filter == 'l') r.filter = RESAMPLE16_LOW;
else r.filter = RESAMPLE16_BASIC;
}
if (interp && *interp == 'i') {
r.interp = true;
}
LOG_INFO("Resampling with filter %d %s", r.filter, r.interp ? "(interpolated)" : "");
return true;
}
#endif // #if RESAMPLE16

View File

@@ -0,0 +1,976 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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 "squeezelite.h"
#include "slimproto.h"
static log_level loglevel;
#define SQUEEZENETWORK "mysqueezebox.com:3483"
#define PORT 3483
#define MAXBUF 4096
#if SL_LITTLE_ENDIAN
#define LOCAL_PLAYER_IP 0x0100007f // 127.0.0.1
#define LOCAL_PLAYER_PORT 0x9b0d // 3483
#else
#define LOCAL_PLAYER_IP 0x7f000001 // 127.0.0.1
#define LOCAL_PLAYER_PORT 0x0d9b // 3483
#endif
static sockfd sock = -1;
static in_addr_t slimproto_ip = 0;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct decodestate decode;
extern struct codec *codecs[];
#if IR
extern struct irstate ir;
#endif
event_event wake_e;
#define LOCK_S mutex_lock(streambuf->mutex)
#define UNLOCK_S mutex_unlock(streambuf->mutex)
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
#define LOCK_D mutex_lock(decode.mutex)
#define UNLOCK_D mutex_unlock(decode.mutex)
#if IR
#define LOCK_I mutex_lock(ir.mutex)
#define UNLOCK_I mutex_unlock(ir.mutex)
#endif
static struct {
u32_t updated;
u32_t stream_start;
u32_t stream_full;
u32_t stream_size;
u64_t stream_bytes;
u32_t output_full;
u32_t output_size;
u32_t frames_played;
u32_t device_frames;
u32_t current_sample_rate;
u32_t last;
stream_state stream_state;
} status;
int autostart;
bool sentSTMu, sentSTMo, sentSTMl;
u32_t new_server;
char *new_server_cap;
#define PLAYER_NAME_LEN 64
char player_name[PLAYER_NAME_LEN + 1] = "";
const char *name_file = NULL;
void send_packet(u8_t *packet, size_t len) {
u8_t *ptr = packet;
unsigned try = 0;
ssize_t n;
while (len) {
n = send(sock, ptr, len, MSG_NOSIGNAL);
if (n <= 0) {
if (n < 0 && last_error() == ERROR_WOULDBLOCK && try < 10) {
LOG_DEBUG("retrying (%d) writing to socket", ++try);
usleep(1000);
continue;
}
LOG_INFO("failed writing to socket: %s", strerror(last_error()));
return;
}
ptr += n;
len -= n;
}
}
static void sendHELO(bool reconnect, const char *fixed_cap, const char *var_cap, u8_t mac[6]) {
#define BASE_CAP "Model=squeezelite,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION
#define SSL_CAP "CanHTTPS=1"
const char *base_cap;
struct HELO_packet pkt;
#if USE_SSL
#if !LINKALL
if (ssl_loaded) base_cap = SSL_CAP "," BASE_CAP;
else base_cap = BASE_CAP;
#endif
base_cap = SSL_CAP "," BASE_CAP;
#else
base_cap = BASE_CAP;
#endif
memset(&pkt, 0, sizeof(pkt));
memcpy(&pkt.opcode, "HELO", 4);
pkt.length = htonl(sizeof(struct HELO_packet) - 8 + strlen(base_cap) + strlen(fixed_cap) + strlen(var_cap));
pkt.deviceid = 12; // squeezeplay
pkt.revision = 0;
packn(&pkt.wlan_channellist, reconnect ? 0x4000 : 0x0000);
packN(&pkt.bytes_received_H, (u64_t)status.stream_bytes >> 32);
packN(&pkt.bytes_received_L, (u64_t)status.stream_bytes & 0xffffffff);
memcpy(pkt.mac, mac, 6);
LOG_INFO("mac: %02x:%02x:%02x:%02x:%02x:%02x", pkt.mac[0], pkt.mac[1], pkt.mac[2], pkt.mac[3], pkt.mac[4], pkt.mac[5]);
LOG_INFO("cap: %s%s%s", base_cap, fixed_cap, var_cap);
send_packet((u8_t *)&pkt, sizeof(pkt));
send_packet((u8_t *)base_cap, strlen(base_cap));
send_packet((u8_t *)fixed_cap, strlen(fixed_cap));
send_packet((u8_t *)var_cap, strlen(var_cap));
}
static void sendSTAT(const char *event, u32_t server_timestamp) {
struct STAT_packet pkt;
u32_t now = gettime_ms();
u32_t ms_played;
if (status.current_sample_rate && status.frames_played && status.frames_played > status.device_frames) {
ms_played = (u32_t)(((u64_t)(status.frames_played - status.device_frames) * (u64_t)1000) / (u64_t)status.current_sample_rate);
#ifndef STATUSHACK
if (now > status.updated) ms_played += (now - status.updated);
#endif
LOG_SDEBUG("ms_played: %u (frames_played: %u device_frames: %u)", ms_played, status.frames_played, status.device_frames);
} else if (status.frames_played && now > status.stream_start) {
ms_played = now - status.stream_start;
LOG_SDEBUG("ms_played: %u using elapsed time (frames_played: %u device_frames: %u)", ms_played, status.frames_played, status.device_frames);
} else {
LOG_SDEBUG("ms_played: 0");
ms_played = 0;
}
memset(&pkt, 0, sizeof(struct STAT_packet));
memcpy(&pkt.opcode, "STAT", 4);
pkt.length = htonl(sizeof(struct STAT_packet) - 8);
memcpy(&pkt.event, event, 4);
// num_crlf
// mas_initialized; mas_mode;
packN(&pkt.stream_buffer_fullness, status.stream_full);
packN(&pkt.stream_buffer_size, status.stream_size);
packN(&pkt.bytes_received_H, (u64_t)status.stream_bytes >> 32);
packN(&pkt.bytes_received_L, (u64_t)status.stream_bytes & 0xffffffff);
pkt.signal_strength = 0xffff;
packN(&pkt.jiffies, now);
packN(&pkt.output_buffer_size, status.output_size);
packN(&pkt.output_buffer_fullness, status.output_full);
packN(&pkt.elapsed_seconds, ms_played / 1000);
// voltage;
packN(&pkt.elapsed_milliseconds, ms_played);
pkt.server_timestamp = server_timestamp; // keep this is server format - don't unpack/pack
// error_code;
LOG_DEBUG("STAT: %s", event);
if (loglevel == lSDEBUG) {
LOG_SDEBUG("received bytesL: %u streambuf: %u outputbuf: %u calc elapsed: %u real elapsed: %u (diff: %d) device: %u delay: %d",
(u32_t)status.stream_bytes, status.stream_full, status.output_full, ms_played, now - status.stream_start,
ms_played - now + status.stream_start, status.device_frames * 1000 / status.current_sample_rate, now - status.updated);
}
send_packet((u8_t *)&pkt, sizeof(pkt));
}
static void sendDSCO(disconnect_code disconnect) {
struct DSCO_packet pkt;
memset(&pkt, 0, sizeof(pkt));
memcpy(&pkt.opcode, "DSCO", 4);
pkt.length = htonl(sizeof(pkt) - 8);
pkt.reason = disconnect & 0xFF;
LOG_DEBUG("DSCO: %d", disconnect);
send_packet((u8_t *)&pkt, sizeof(pkt));
}
static void sendRESP(const char *header, size_t len) {
struct RESP_header pkt_header;
memset(&pkt_header, 0, sizeof(pkt_header));
memcpy(&pkt_header.opcode, "RESP", 4);
pkt_header.length = htonl(sizeof(pkt_header) + len - 8);
LOG_DEBUG("RESP");
send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
send_packet((u8_t *)header, len);
}
static void sendMETA(const char *meta, size_t len) {
struct META_header pkt_header;
memset(&pkt_header, 0, sizeof(pkt_header));
memcpy(&pkt_header.opcode, "META", 4);
pkt_header.length = htonl(sizeof(pkt_header) + len - 8);
LOG_DEBUG("META");
send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
send_packet((u8_t *)meta, len);
}
static void sendSETDName(const char *name) {
struct SETD_header pkt_header;
memset(&pkt_header, 0, sizeof(pkt_header));
memcpy(&pkt_header.opcode, "SETD", 4);
pkt_header.id = 0; // id 0 is playername S:P:Squeezebox2
pkt_header.length = htonl(sizeof(pkt_header) + strlen(name) + 1 - 8);
LOG_DEBUG("set playername: %s", name);
send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
send_packet((u8_t *)name, strlen(name) + 1);
}
#if IR
void sendIR(u32_t code, u32_t ts) {
struct IR_packet pkt;
memset(&pkt, 0, sizeof(pkt));
memcpy(&pkt.opcode, "IR ", 4);
pkt.length = htonl(sizeof(pkt) - 8);
packN(&pkt.jiffies, ts);
pkt.ir_code = htonl(code);
LOG_DEBUG("IR: ir code: 0x%x ts: %u", code, ts);
send_packet((u8_t *)&pkt, sizeof(pkt));
}
#endif
static void process_strm(u8_t *pkt, int len) {
struct strm_packet *strm = (struct strm_packet *)pkt;
LOG_DEBUG("strm command %c", strm->command);
switch(strm->command) {
case 't':
sendSTAT("STMt", strm->replay_gain); // STMt replay_gain is no longer used to track latency, but support it
break;
case 'q':
decode_flush();
output_flush();
status.frames_played = 0;
stream_disconnect();
sendSTAT("STMf", 0);
buf_flush(streambuf);
break;
case 'f':
decode_flush();
output_flush();
status.frames_played = 0;
if (stream_disconnect()) {
sendSTAT("STMf", 0);
}
buf_flush(streambuf);
break;
case 'p':
{
unsigned interval = unpackN(&strm->replay_gain);
LOCK_O;
output.pause_frames = interval * status.current_sample_rate / 1000;
if (interval) {
output.state = OUTPUT_PAUSE_FRAMES;
} else {
output.state = OUTPUT_STOPPED;
output.stop_time = gettime_ms();
}
UNLOCK_O;
if (!interval) sendSTAT("STMp", 0);
LOG_DEBUG("pause interval: %u", interval);
}
break;
case 'a':
{
unsigned interval = unpackN(&strm->replay_gain);
LOCK_O;
output.skip_frames = interval * status.current_sample_rate / 1000;
output.state = OUTPUT_SKIP_FRAMES;
UNLOCK_O;
LOG_DEBUG("skip ahead interval: %u", interval);
}
break;
case 'u':
{
unsigned jiffies = unpackN(&strm->replay_gain);
LOCK_O;
output.state = jiffies ? OUTPUT_START_AT : OUTPUT_RUNNING;
output.start_at = jiffies;
#ifdef STATUSHACK
status.frames_played = output.frames_played;
#endif
UNLOCK_O;
LOG_DEBUG("unpause at: %u now: %u", jiffies, gettime_ms());
sendSTAT("STMr", 0);
}
break;
case 's':
{
unsigned header_len = len - sizeof(struct strm_packet);
char *header = (char *)(pkt + sizeof(struct strm_packet));
in_addr_t ip = (in_addr_t)strm->server_ip; // keep in network byte order
u16_t port = strm->server_port; // keep in network byte order
if (ip == 0) ip = slimproto_ip;
LOG_DEBUG("strm s autostart: %c transition period: %u transition type: %u codec: %c",
strm->autostart, strm->transition_period, strm->transition_type - '0', strm->format);
autostart = strm->autostart - '0';
sendSTAT("STMf", 0);
if (header_len > MAX_HEADER -1) {
LOG_WARN("header too long: %u", header_len);
break;
}
if (strm->format != '?') {
codec_open(strm->format, strm->pcm_sample_size, strm->pcm_sample_rate, strm->pcm_channels, strm->pcm_endianness);
} else if (autostart >= 2) {
// extension to slimproto to allow server to detect codec from response header and send back in codc message
LOG_DEBUG("streaming unknown codec");
} else {
LOG_WARN("unknown codec requires autostart >= 2");
break;
}
if (ip == LOCAL_PLAYER_IP && port == LOCAL_PLAYER_PORT) {
// extension to slimproto for LocalPlayer - header is filename not http header, don't expect cont
stream_file(header, header_len, strm->threshold * 1024);
autostart -= 2;
} else {
stream_sock(ip, port, header, header_len, strm->threshold * 1024, autostart >= 2);
}
sendSTAT("STMc", 0);
sentSTMu = sentSTMo = sentSTMl = false;
LOCK_O;
output.threshold = strm->output_threshold;
output.next_replay_gain = unpackN(&strm->replay_gain);
output.fade_mode = strm->transition_type - '0';
output.fade_secs = strm->transition_period;
output.invert = (strm->flags & 0x03) == 0x03;
LOG_DEBUG("set fade mode: %u", output.fade_mode);
UNLOCK_O;
}
break;
default:
LOG_WARN("unhandled strm %c", strm->command);
break;
}
}
static void process_cont(u8_t *pkt, int len) {
struct cont_packet *cont = (struct cont_packet *)pkt;
cont->metaint = unpackN(&cont->metaint);
LOG_DEBUG("cont metaint: %u loop: %u", cont->metaint, cont->loop);
if (autostart > 1) {
autostart -= 2;
LOCK_S;
if (stream.state == STREAMING_WAIT) {
stream.state = STREAMING_BUFFERING;
stream.meta_interval = stream.meta_next = cont->metaint;
}
UNLOCK_S;
wake_controller();
}
}
static void process_codc(u8_t *pkt, int len) {
struct codc_packet *codc = (struct codc_packet *)pkt;
LOG_DEBUG("codc: %c", codc->format);
codec_open(codc->format, codc->pcm_sample_size, codc->pcm_sample_rate, codc->pcm_channels, codc->pcm_endianness);
}
static void process_aude(u8_t *pkt, int len) {
struct aude_packet *aude = (struct aude_packet *)pkt;
LOG_DEBUG("enable spdif: %d dac: %d", aude->enable_spdif, aude->enable_dac);
LOCK_O;
if (!aude->enable_spdif && output.state != OUTPUT_OFF) {
output.state = OUTPUT_OFF;
}
if (aude->enable_spdif && output.state == OUTPUT_OFF && !output.idle_to) {
output.state = OUTPUT_STOPPED;
output.stop_time = gettime_ms();
}
UNLOCK_O;
}
static void process_audg(u8_t *pkt, int len) {
struct audg_packet *audg = (struct audg_packet *)pkt;
audg->gainL = unpackN(&audg->gainL);
audg->gainR = unpackN(&audg->gainR);
LOG_DEBUG("audg gainL: %u gainR: %u adjust: %u", audg->gainL, audg->gainR, audg->adjust);
set_volume(audg->adjust ? audg->gainL : FIXED_ONE, audg->adjust ? audg->gainR : FIXED_ONE);
}
static void process_setd(u8_t *pkt, int len) {
struct setd_packet *setd = (struct setd_packet *)pkt;
// handle player name query and change
if (setd->id == 0) {
if (len == 5) {
if (strlen(player_name)) {
sendSETDName(player_name);
}
} else if (len > 5) {
strncpy(player_name, setd->data, PLAYER_NAME_LEN);
player_name[PLAYER_NAME_LEN] = '\0';
LOG_INFO("set name: %s", setd->data);
// confirm change to server
sendSETDName(setd->data);
// write name to name_file if -N option set
if (name_file) {
FILE *fp = fopen(name_file, "w");
if (fp) {
LOG_INFO("storing name in %s", name_file);
fputs(player_name, fp);
fclose(fp);
} else {
LOG_WARN("unable to store new name in %s", name_file);
}
}
}
}
}
#define SYNC_CAP ",SyncgroupID="
#define SYNC_CAP_LEN 13
static void process_serv(u8_t *pkt, int len) {
struct serv_packet *serv = (struct serv_packet *)pkt;
unsigned slimproto_port = 0;
char squeezeserver[] = SQUEEZENETWORK;
if(pkt[4] == 0 && pkt[5] == 0 && pkt[6] == 0 && pkt[7] == 1) {
server_addr(squeezeserver, &new_server, &slimproto_port);
} else {
new_server = serv->server_ip;
}
LOG_INFO("switch server");
if (len - sizeof(struct serv_packet) == 10) {
if (!new_server_cap) {
new_server_cap = malloc(SYNC_CAP_LEN + 10 + 1);
}
new_server_cap[0] = '\0';
strcat(new_server_cap, SYNC_CAP);
strncat(new_server_cap, (const char *)(pkt + sizeof(struct serv_packet)), 10);
} else {
if (new_server_cap) {
free(new_server_cap);
new_server_cap = NULL;
}
}
}
struct handler {
char opcode[5];
void (*handler)(u8_t *, int);
};
static struct handler handlers[] = {
{ "strm", process_strm },
{ "cont", process_cont },
{ "codc", process_codc },
{ "aude", process_aude },
{ "audg", process_audg },
{ "setd", process_setd },
{ "serv", process_serv },
{ "", NULL },
};
static void process(u8_t *pack, int len) {
struct handler *h = handlers;
while (h->handler && strncmp((char *)pack, h->opcode, 4)) { h++; }
if (h->handler) {
LOG_DEBUG("%s", h->opcode);
h->handler(pack, len);
} else {
pack[4] = '\0';
LOG_WARN("unhandled %s", (char *)pack);
}
}
static bool running;
static void slimproto_run() {
static u8_t buffer[MAXBUF];
int expect = 0;
int got = 0;
u32_t now;
static u32_t last = 0;
event_handle ehandles[2];
int timeouts = 0;
set_readwake_handles(ehandles, sock, wake_e);
while (running && !new_server) {
bool wake = false;
event_type ev;
if ((ev = wait_readwake(ehandles, 1000)) != EVENT_TIMEOUT) {
if (ev == EVENT_READ) {
if (expect > 0) {
int n = recv(sock, buffer + got, expect, 0);
if (n <= 0) {
if (n < 0 && last_error() == ERROR_WOULDBLOCK) {
continue;
}
LOG_INFO("error reading from socket: %s", n ? strerror(last_error()) : "closed");
return;
}
expect -= n;
got += n;
if (expect == 0) {
process(buffer, got);
got = 0;
}
} else if (expect == 0) {
int n = recv(sock, buffer + got, 2 - got, 0);
if (n <= 0) {
if (n < 0 && last_error() == ERROR_WOULDBLOCK) {
continue;
}
LOG_INFO("error reading from socket: %s", n ? strerror(last_error()) : "closed");
return;
}
got += n;
if (got == 2) {
expect = buffer[0] << 8 | buffer[1]; // length pack 'n'
got = 0;
if (expect > MAXBUF) {
LOG_ERROR("FATAL: slimproto packet too big: %d > %d", expect, MAXBUF);
return;
}
}
} else {
LOG_ERROR("FATAL: negative expect");
return;
}
}
if (ev == EVENT_WAKE) {
wake = true;
}
timeouts = 0;
} else if (++timeouts > 35) {
// expect message from server every 5 seconds, but 30 seconds on mysb.com so timeout after 35 seconds
LOG_INFO("No messages from server - connection dead");
return;
}
// update playback state when woken or every 100ms
now = gettime_ms();
if (wake || now - last > 100 || last > now) {
bool _sendSTMs = false;
bool _sendDSCO = false;
bool _sendRESP = false;
bool _sendMETA = false;
bool _sendSTMd = false;
bool _sendSTMt = false;
bool _sendSTMl = false;
bool _sendSTMu = false;
bool _sendSTMo = false;
bool _sendSTMn = false;
bool _stream_disconnect = false;
bool _start_output = false;
decode_state _decode_state;
disconnect_code disconnect_code;
static char header[MAX_HEADER];
size_t header_len = 0;
#if IR
bool _sendIR = false;
u32_t ir_code, ir_ts;
#endif
last = now;
LOCK_S;
status.stream_full = _buf_used(streambuf);
status.stream_size = streambuf->size;
status.stream_bytes = stream.bytes;
status.stream_state = stream.state;
if (stream.state == DISCONNECT) {
disconnect_code = stream.disconnect;
stream.state = STOPPED;
_sendDSCO = true;
}
if (!stream.sent_headers &&
(stream.state == STREAMING_HTTP || stream.state == STREAMING_WAIT || stream.state == STREAMING_BUFFERING)) {
header_len = stream.header_len;
memcpy(header, stream.header, header_len);
_sendRESP = true;
stream.sent_headers = true;
}
if (stream.meta_send) {
header_len = stream.header_len;
memcpy(header, stream.header, header_len);
_sendMETA = true;
stream.meta_send = false;
}
UNLOCK_S;
LOCK_D;
if ((status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE ||
(status.stream_state == DISCONNECT && stream.disconnect == DISCONNECT_OK)) &&
!sentSTMl && decode.state == DECODE_READY) {
if (autostart == 0) {
decode.state = DECODE_RUNNING;
_sendSTMl = true;
sentSTMl = true;
} else if (autostart == 1) {
decode.state = DECODE_RUNNING;
_start_output = true;
}
// autostart 2 and 3 require cont to be received first
}
if (decode.state == DECODE_COMPLETE || decode.state == DECODE_ERROR) {
if (decode.state == DECODE_COMPLETE) _sendSTMd = true;
if (decode.state == DECODE_ERROR) _sendSTMn = true;
decode.state = DECODE_STOPPED;
if (status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) {
_stream_disconnect = true;
}
}
_decode_state = decode.state;
UNLOCK_D;
LOCK_O;
status.output_full = _buf_used(outputbuf);
status.output_size = outputbuf->size;
status.frames_played = output.frames_played_dmp;
status.current_sample_rate = output.current_sample_rate;
status.updated = output.updated;
status.device_frames = output.device_frames;
if (output.track_started) {
_sendSTMs = true;
output.track_started = false;
status.stream_start = output.track_start_time;
#ifdef STATUSHACK
status.frames_played = output.frames_played;
#endif
}
#if PORTAUDIO
if (output.pa_reopen) {
_pa_open();
output.pa_reopen = false;
}
#endif
if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) {
output.state = OUTPUT_BUFFER;
}
if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT &&
_decode_state == DECODE_STOPPED) {
_sendSTMu = true;
sentSTMu = true;
LOG_DEBUG("output underrun");
output.state = OUTPUT_STOPPED;
output.stop_time = now;
}
if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) {
_sendSTMo = true;
sentSTMo = true;
}
if (output.state == OUTPUT_STOPPED && output.idle_to && (now - output.stop_time > output.idle_to)) {
output.state = OUTPUT_OFF;
LOG_DEBUG("output timeout");
}
if (output.state == OUTPUT_RUNNING && now - status.last > 1000) {
_sendSTMt = true;
status.last = now;
}
UNLOCK_O;
#if IR
LOCK_I;
if (ir.code) {
_sendIR = true;
ir_code = ir.code;
ir_ts = ir.ts;
ir.code = 0;
}
UNLOCK_I;
#endif
if (_stream_disconnect) stream_disconnect();
// send packets once locks released as packet sending can block
if (_sendDSCO) sendDSCO(disconnect_code);
if (_sendSTMs) sendSTAT("STMs", 0);
if (_sendSTMd) sendSTAT("STMd", 0);
if (_sendSTMt) sendSTAT("STMt", 0);
if (_sendSTMl) sendSTAT("STMl", 0);
if (_sendSTMu) sendSTAT("STMu", 0);
if (_sendSTMo) sendSTAT("STMo", 0);
if (_sendSTMn) sendSTAT("STMn", 0);
if (_sendRESP) sendRESP(header, header_len);
if (_sendMETA) sendMETA(header, header_len);
#if IR
if (_sendIR) sendIR(ir_code, ir_ts);
#endif
}
}
}
// called from other threads to wake state machine above
void wake_controller(void) {
wake_signal(wake_e);
}
in_addr_t discover_server(char *default_server) {
struct sockaddr_in d;
struct sockaddr_in s;
char *buf;
struct pollfd pollinfo;
unsigned port;
int disc_sock = socket(AF_INET, SOCK_DGRAM, 0);
socklen_t enable = 1;
setsockopt(disc_sock, SOL_SOCKET, SO_BROADCAST, (const void *)&enable, sizeof(enable));
buf = "e";
memset(&d, 0, sizeof(d));
d.sin_family = AF_INET;
d.sin_port = htons(PORT);
d.sin_addr.s_addr = htonl(INADDR_BROADCAST);
pollinfo.fd = disc_sock;
pollinfo.events = POLLIN;
do {
LOG_INFO("sending discovery");
memset(&s, 0, sizeof(s));
if (sendto(disc_sock, buf, 1, 0, (struct sockaddr *)&d, sizeof(d)) < 0) {
LOG_INFO("error sending disovery");
}
if (poll(&pollinfo, 1, 5000) == 1) {
char readbuf[10];
socklen_t slen = sizeof(s);
recvfrom(disc_sock, readbuf, 10, 0, (struct sockaddr *)&s, &slen);
LOG_INFO("got response from: %s:%d", inet_ntoa(s.sin_addr), ntohs(s.sin_port));
}
if (default_server) {
server_addr(default_server, &s.sin_addr.s_addr, &port);
}
} while (s.sin_addr.s_addr == 0 && running);
closesocket(disc_sock);
return s.sin_addr.s_addr;
}
#define FIXED_CAP_LEN 256
#define VAR_CAP_LEN 128
void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname, int maxSampleRate) {
struct sockaddr_in serv_addr;
static char fixed_cap[FIXED_CAP_LEN], var_cap[VAR_CAP_LEN] = "";
bool reconnect = false;
unsigned failed_connect = 0;
unsigned slimproto_port = 0;
in_addr_t previous_server = 0;
int i;
memset(&status, 0, sizeof(status));
wake_create(wake_e);
loglevel = level;
running = true;
if (server) {
server_addr(server, &slimproto_ip, &slimproto_port);
}
if (!slimproto_ip) {
slimproto_ip = discover_server(server);
}
if (!slimproto_port) {
slimproto_port = PORT;
}
if (name) {
strncpy(player_name, name, PLAYER_NAME_LEN);
player_name[PLAYER_NAME_LEN] = '\0';
}
if (namefile) {
FILE *fp;
name_file = namefile;
fp = fopen(namefile, "r");
if (fp) {
if (!fgets(player_name, PLAYER_NAME_LEN, fp)) {
player_name[PLAYER_NAME_LEN] = '\0';
} else {
// strip any \n from fgets response
int len = strlen(player_name);
if (len > 0 && player_name[len - 1] == '\n') {
player_name[len - 1] = '\0';
}
LOG_INFO("retrieved name %s from %s", player_name, name_file);
}
fclose(fp);
}
}
if (!running) return;
LOCK_O;
snprintf(fixed_cap, FIXED_CAP_LEN, ",ModelName=%s,MaxSampleRate=%u", modelname ? modelname : MODEL_NAME_STRING,
((maxSampleRate > 0) ? maxSampleRate : output.supported_rates[0]));
for (i = 0; i < MAX_CODECS; i++) {
if (codecs[i] && codecs[i]->id && strlen(fixed_cap) < FIXED_CAP_LEN - 10) {
strcat(fixed_cap, ",");
strcat(fixed_cap, codecs[i]->types);
}
}
UNLOCK_O;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = slimproto_ip;
serv_addr.sin_port = htons(slimproto_port);
LOG_INFO("connecting to %s:%d", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
new_server = 0;
while (running) {
if (new_server) {
previous_server = slimproto_ip;
slimproto_ip = serv_addr.sin_addr.s_addr = new_server;
LOG_INFO("switching server to %s:%d", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
new_server = 0;
reconnect = false;
}
sock = socket(AF_INET, SOCK_STREAM, 0);
set_nonblock(sock);
set_nosigpipe(sock);
if (connect_timeout(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr), 5) != 0) {
if (previous_server) {
slimproto_ip = serv_addr.sin_addr.s_addr = previous_server;
LOG_INFO("new server not reachable, reverting to previous server %s:%d", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
} else {
LOG_INFO("unable to connect to server %u", failed_connect);
sleep(5);
}
// rediscover server if it was not set at startup
if (!server && ++failed_connect > 5) {
slimproto_ip = serv_addr.sin_addr.s_addr = discover_server(NULL);
}
} else {
struct sockaddr_in our_addr;
socklen_t len;
LOG_INFO("connected");
var_cap[0] = '\0';
failed_connect = 0;
// check if this is a local player now we are connected & signal to server via 'loc' format
// this requires LocalPlayer server plugin to enable direct file access
len = sizeof(our_addr);
getsockname(sock, (struct sockaddr *) &our_addr, &len);
if (our_addr.sin_addr.s_addr == serv_addr.sin_addr.s_addr) {
LOG_INFO("local player");
strcat(var_cap, ",loc");
}
// add on any capablity to be sent to the new server
if (new_server_cap) {
strcat(var_cap, new_server_cap);
free(new_server_cap);
new_server_cap = NULL;
}
sendHELO(reconnect, fixed_cap, var_cap, mac);
slimproto_run();
if (!reconnect) {
reconnect = true;
}
usleep(100000);
}
previous_server = 0;
closesocket(sock);
}
}
void slimproto_stop(void) {
LOG_INFO("slimproto stop");
running = false;
}

View File

@@ -0,0 +1,185 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
// packet formats for slimproto
#ifndef SUN
#pragma pack(push, 1)
#else
#pragma pack(1)
#endif
// from S:N:Slimproto _hello_handler
struct HELO_packet {
char opcode[4];
u32_t length;
u8_t deviceid;
u8_t revision;
u8_t mac[6];
u8_t uuid[16];
u16_t wlan_channellist;
u32_t bytes_received_H, bytes_received_L;
char lang[2];
// u8_t capabilities[];
};
// S:N:Slimproto _stat_handler
struct STAT_packet {
char opcode[4];
u32_t length;
u32_t event;
u8_t num_crlf;
u8_t mas_initialized;
u8_t mas_mode;
u32_t stream_buffer_size;
u32_t stream_buffer_fullness;
u32_t bytes_received_H;
u32_t bytes_received_L;
u16_t signal_strength;
u32_t jiffies;
u32_t output_buffer_size;
u32_t output_buffer_fullness;
u32_t elapsed_seconds;
u16_t voltage;
u32_t elapsed_milliseconds;
u32_t server_timestamp;
u16_t error_code;
};
// S:N:Slimproto _disco_handler
struct DSCO_packet {
char opcode[4];
u32_t length;
u8_t reason;
};
// S:N:Slimproto _http_response_handler
struct RESP_header {
char opcode[4];
u32_t length;
// char header[] - added in sendRESP
};
// S:N:Slimproto _http_metadata_handler
struct META_header {
char opcode[4];
u32_t length;
// char metadata[]
};
// S:N:Slimproto _http_setting_handler
struct SETD_header {
char opcode[4];
u32_t length;
u8_t id;
// data
};
#if IR
struct IR_packet {
char opcode[4];
u32_t length;
u32_t jiffies;
u8_t format; // ignored by server
u8_t bits; // ignored by server
u32_t ir_code;
};
#endif
// from S:P:Squeezebox stream_s
struct strm_packet {
char opcode[4];
char command;
u8_t autostart;
u8_t format;
u8_t pcm_sample_size;
u8_t pcm_sample_rate;
u8_t pcm_channels;
u8_t pcm_endianness;
u8_t threshold;
u8_t spdif_enable;
u8_t transition_period;
u8_t transition_type;
u8_t flags;
u8_t output_threshold;
u8_t slaves;
u32_t replay_gain;
u16_t server_port;
u32_t server_ip;
//char request_string[];
};
// S:P:Squeezebox2
struct aude_packet {
char opcode[4];
u8_t enable_spdif;
u8_t enable_dac;
};
// S:P:Squeezebox2
struct audg_packet {
char opcode[4];
u32_t old_gainL; // unused
u32_t old_gainR; // unused
u8_t adjust;
u8_t preamp; // unused
u32_t gainL;
u32_t gainR;
// squence ids - unused
};
// S:P:Squeezebox2
struct cont_packet {
char opcode[4];
u32_t metaint;
u8_t loop;
// guids we don't use
};
// S:C:Commands
struct serv_packet {
char opcode[4];
u32_t server_ip;
// possible sync group
};
// S:P:Squeezebox2
struct setd_packet {
char opcode[4];
u8_t id;
char data[];
};
// codec open - this is an extension to slimproto to allow the server to read the header and then return decode params
struct codc_packet {
char opcode[4];
u8_t format;
u8_t pcm_sample_size;
u8_t pcm_sample_rate;
u8_t pcm_channels;
u8_t pcm_endianness;
};
#ifndef SUN
#pragma pack(pop)
#else
#pragma pack()
#endif

View File

@@ -0,0 +1,803 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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
*/
// make may define: PORTAUDIO, SELFPIPE, RESAMPLE, RESAMPLE_MP, VISEXPORT, GPIO, IR, DSD, LINKALL to influence build
#define MAJOR_VERSION "1.9"
#define MINOR_VERSION "2"
#define MICRO_VERSION "1145"
#if defined(CUSTOM_VERSION)
#define VERSION "v" MAJOR_VERSION "." MINOR_VERSION "-" MICRO_VERSION STR(CUSTOM_VERSION)
#else
#define VERSION "v" MAJOR_VERSION "." MINOR_VERSION "-" MICRO_VERSION
#endif
#if !defined(MODEL_NAME)
#define MODEL_NAME SqueezeLite
#endif
#define QUOTE(name) #name
#define STR(macro) QUOTE(macro)
#define MODEL_NAME_STRING STR(MODEL_NAME)
// build detection
#if defined (EMBEDDED)
#undef EMBEDDED
#define EMBEDDED 1
#elif defined(linux)
#define LINUX 1
#define OSX 0
#define WIN 0
#define FREEBSD 0
#elif defined (__APPLE__)
#define LINUX 0
#define OSX 1
#define WIN 0
#define FREEBSD 0
#elif defined (_MSC_VER)
#define LINUX 0
#define OSX 0
#define WIN 1
#define FREEBSD 0
#elif defined(__FreeBSD__)
#define LINUX 0
#define OSX 0
#define WIN 0
#define FREEBSD 1
#elif defined (__sun)
#define SUN 1
#define LINUX 1
#define PORTAUDIO 1
#define PA18API 1
#define OSX 0
#define WIN 0
#else
#error unknown target
#endif
#if !EMBEDDED
#if LINUX && !defined(PORTAUDIO)
#define ALSA 1
#define PORTAUDIO 0
#else
#define ALSA 0
#define PORTAUDIO 1
#endif
#endif
#if !defined(LOOPBACK)
#if SUN
#define EVENTFD 0
#define WINEVENT 0
#define SELFPIPE 1
#elif LINUX && !defined(SELFPIPE)
#define EVENTFD 1
#define SELFPIPE 0
#define WINEVENT 0
#endif
#if (LINUX && !EVENTFD) || OSX || FREEBSD
#define EVENTFD 0
#define SELFPIPE 1
#define WINEVENT 0
#endif
#if WIN
#define EVENTFD 0
#define SELFPIPE 0
#define WINEVENT 1
#endif
#else
#define EVENTFD 0
#define SELFPIPE 0
#define WINEVENT 0
#undef LOOPBACK
#define LOOPBACK 1
#endif
#if defined(RESAMPLE) || defined(RESAMPLE_MP)
#undef RESAMPLE
#define RESAMPLE 1 // resampling
#define PROCESS 1 // any sample processing (only resampling at present)
#elif defined(RESAMPLE16)
#undef RESAMPLE16
#define RESAMPLE16 1
#define PROCESS 1
#else
#define RESAMPLE 0
#define PROCESS 0
#endif
#if defined(RESAMPLE_MP)
#undef RESAMPLE_MP
#define RESAMPLE_MP 1
#else
#define RESAMPLE_MP 0
#endif
#if defined(FFMPEG)
#undef FFMPEG
#define FFMPEG 1
#else
#define FFMPEG 0
#endif
#if (LINUX || OSX) && defined(VISEXPORT)
#undef VISEXPORT
#define VISEXPORT 1 // visulizer export support uses linux shared memory
#else
#define VISEXPORT 0
#endif
#if LINUX && defined(IR)
#undef IR
#define IR 1
#else
#define IR 0
#endif
#if defined(DSD)
#undef DSD
#define DSD 1
#define IF_DSD(x) { x }
#else
#undef DSD
#define DSD 0
#define IF_DSD(x)
#endif
#if defined(LINKALL)
#undef LINKALL
#define LINKALL 1 // link all libraries at build time - requires all to be available at run time
#else
#define LINKALL 0
#endif
#if defined (USE_SSL)
#undef USE_SSL
#define USE_SSL 1
#else
#define USE_SSL 0
#endif
#if !LINKALL
// dynamically loaded libraries at run time
#if LINUX
#define LIBFLAC "libFLAC.so.8"
#define LIBMAD "libmad.so.0"
#define LIBMPG "libmpg123.so.0"
#define LIBVORBIS "libvorbisfile.so.3"
#define LIBTREMOR "libvorbisidec.so.1"
#define LIBFAAD "libfaad.so.2"
#define LIBAVUTIL "libavutil.so.%d"
#define LIBAVCODEC "libavcodec.so.%d"
#define LIBAVFORMAT "libavformat.so.%d"
#define LIBSOXR "libsoxr.so.0"
#define LIBLIRC "liblirc_client.so.0"
#endif
#if OSX
#define LIBFLAC "libFLAC.8.dylib"
#define LIBMAD "libmad.0.dylib"
#define LIBMPG "libmpg123.0.dylib"
#define LIBVORBIS "libvorbisfile.3.dylib"
#define LIBTREMOR "libvorbisidec.1.dylib"
#define LIBFAAD "libfaad.2.dylib"
#define LIBAVUTIL "libavutil.%d.dylib"
#define LIBAVCODEC "libavcodec.%d.dylib"
#define LIBAVFORMAT "libavformat.%d.dylib"
#define LIBSOXR "libsoxr.0.dylib"
#endif
#if WIN
#define LIBFLAC "libFLAC.dll"
#define LIBMAD "libmad-0.dll"
#define LIBMPG "libmpg123-0.dll"
#define LIBVORBIS "libvorbisfile.dll"
#define LIBTREMOR "libvorbisidec.dll"
#define LIBFAAD "libfaad2.dll"
#define LIBAVUTIL "avutil-%d.dll"
#define LIBAVCODEC "avcodec-%d.dll"
#define LIBAVFORMAT "avformat-%d.dll"
#define LIBSOXR "libsoxr.dll"
#endif
#if FREEBSD
#define LIBFLAC "libFLAC.so.11"
#define LIBMAD "libmad.so.2"
#define LIBMPG "libmpg123.so.0"
#define LIBVORBIS "libvorbisfile.so.6"
#define LIBTREMOR "libvorbisidec.so.1"
#define LIBFAAD "libfaad.so.2"
#define LIBAVUTIL "libavutil.so.%d"
#define LIBAVCODEC "libavcodec.so.%d"
#define LIBAVFORMAT "libavformat.so.%d"
#endif
#endif // !LINKALL
// config options
#define STREAMBUF_SIZE (2 * 1024 * 1024)
#define OUTPUTBUF_SIZE (44100 * 8 * 10)
#define OUTPUTBUF_SIZE_CROSSFADE (OUTPUTBUF_SIZE * 12 / 10)
#define MAX_HEADER 4096 // do not reduce as icy-meta max is 4080
#if ALSA
#define ALSA_BUFFER_TIME 40
#define ALSA_PERIOD_COUNT 4
#define OUTPUT_RT_PRIORITY 45
#endif
#define SL_LITTLE_ENDIAN (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
#if SUN || OSXPPC
#undef SL_LITTLE_ENDIAN
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#if EMBEDDED
#include "embedded.h"
#endif
#if LINUX || OSX || FREEBSD || EMBEDDED
#include <unistd.h>
#include <stdbool.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/poll.h>
#if !LINKALL
#include <dlfcn.h>
#endif
#include <pthread.h>
#include <signal.h>
#if SUN
#include <sys/types.h>
#endif /* SUN */
#define STREAM_THREAD_STACK_SIZE 8 * 1024
#define DECODE_THREAD_STACK_SIZE 20 * 1024
#define OUTPUT_THREAD_STACK_SIZE 8 * 1024
#define IR_THREAD_STACK_SIZE 8 * 1024
#if !OSX
#define thread_t pthread_t;
#endif
#define closesocket(s) close(s)
#define last_error() errno
#define ERROR_WOULDBLOCK EWOULDBLOCK
#if !EMBEDDED
#ifdef SUN
typedef uint8_t u8_t;
typedef uint16_t u16_t;
typedef uint32_t u32_t;
typedef uint64_t u64_t;
#else
typedef u_int8_t u8_t;
typedef u_int16_t u16_t;
typedef u_int32_t u32_t;
typedef u_int64_t u64_t;
#endif /* SUN */
typedef int16_t s16_t;
typedef int32_t s32_t;
typedef int64_t s64_t;
#endif
#define mutex_type pthread_mutex_t
#define mutex_create(m) pthread_mutex_init(&m, NULL)
#if HAS_MUTEX_CREATE_P
#define mutex_create_p(m) pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); pthread_mutex_init(&m, &attr); pthread_mutexattr_destroy(&attr)
#else
#define mutex_create_p(m) mutex_create(m)
#endif
#define mutex_lock(m) pthread_mutex_lock(&m)
#define mutex_unlock(m) pthread_mutex_unlock(&m)
#define mutex_destroy(m) pthread_mutex_destroy(&m)
#define thread_type pthread_t
#endif
#if WIN
#include <winsock2.h>
#include <ws2tcpip.h>
#include <io.h>
#define STREAM_THREAD_STACK_SIZE (1024 * 64)
#define DECODE_THREAD_STACK_SIZE (1024 * 128)
#define OUTPUT_THREAD_STACK_SIZE (1024 * 64)
typedef unsigned __int8 u8_t;
typedef unsigned __int16 u16_t;
typedef unsigned __int32 u32_t;
typedef unsigned __int64 u64_t;
typedef __int16 s16_t;
typedef __int32 s32_t;
typedef __int64 s64_t;
typedef BOOL bool;
#define true TRUE
#define false FALSE
#define inline __inline
#define mutex_type HANDLE
#define mutex_create(m) m = CreateMutex(NULL, FALSE, NULL)
#define mutex_create_p mutex_create
#define mutex_lock(m) WaitForSingleObject(m, INFINITE)
#define mutex_unlock(m) ReleaseMutex(m)
#define mutex_destroy(m) CloseHandle(m)
#define thread_type HANDLE
#define usleep(x) Sleep(x/1000)
#define sleep(x) Sleep(x*1000)
#define last_error() WSAGetLastError()
#define ERROR_WOULDBLOCK WSAEWOULDBLOCK
#define open _open
#define read _read
#define snprintf _snprintf
#define in_addr_t u32_t
#define socklen_t int
#define ssize_t int
#define RTLD_NOW 0
#endif
#if !defined(MSG_NOSIGNAL)
#define MSG_NOSIGNAL 0
#endif
typedef u32_t frames_t;
typedef int sockfd;
#if EVENTFD
#include <sys/eventfd.h>
#define event_event int
#define event_handle struct pollfd
#define wake_create(e) e = eventfd(0, 0)
#define wake_signal(e) eventfd_write(e, 1)
#define wake_clear(e) eventfd_t val; eventfd_read(e, &val)
#define wake_close(e) close(e)
#endif
#if SELFPIPE
#define event_handle struct pollfd
#define event_event struct wake
#define wake_create(e) pipe(e.fds); set_nonblock(e.fds[0]); set_nonblock(e.fds[1])
#define wake_signal(e) write(e.fds[1], ".", 1)
#define wake_clear(e) char c[10]; read(e, &c, 10)
#define wake_close(e) close(e.fds[0]); close(e.fds[1])
struct wake {
int fds[2];
};
#endif
#if LOOPBACK
#define event_handle struct pollfd
#define event_event struct wake
#define wake_create(e) _wake_create(&e)
#define wake_signal(e) send(e.fds[1], ".", 1, 0)
#define wake_clear(e) char c; recv(e, &c, 1, 0)
#define wake_close(e) closesocket(e.mfds); closesocket(e.fds[0]); closesocket(e.fds[1])
struct wake {
int mfds;
int fds[2];
};
void _wake_create(event_event*);
#endif
#if WINEVENT
#define event_event HANDLE
#define event_handle HANDLE
#define wake_create(e) e = CreateEvent(NULL, FALSE, FALSE, NULL)
#define wake_signal(e) SetEvent(e)
#define wake_close(e) CloseHandle(e)
#endif
// printf/scanf formats for u64_t
#if (LINUX && __WORDSIZE == 64) || (FREEBSD && __LP64__)
#define FMT_u64 "%lu"
#define FMT_x64 "%lx"
#elif __GLIBC_HAVE_LONG_LONG || defined __GNUC__ || WIN || SUN
#define FMT_u64 "%llu"
#define FMT_x64 "%llx"
#else
#error can not support u64_t
#endif
#define MAX_SILENCE_FRAMES 2048
#define FIXED_ONE 0x10000
#ifndef BYTES_PER_FRAME
#define BYTES_PER_FRAME 8
#endif
#if BYTES_PER_FRAME == 8
#define ISAMPLE_T s32_t
#else
#define ISAMPLE_T s16_t
#endif
#define min(a,b) (((a) < (b)) ? (a) : (b))
// logging
typedef enum { lERROR = 0, lWARN, lINFO, lDEBUG, lSDEBUG } log_level;
const char *logtime(void);
void logprint(const char *fmt, ...);
#define LOG_ERROR(fmt, ...) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) if (loglevel >= lWARN) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) if (loglevel >= lINFO) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) if (loglevel >= lDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_SDEBUG(fmt, ...) if (loglevel >= lSDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
// utils.c (non logging)
typedef enum { EVENT_TIMEOUT = 0, EVENT_READ, EVENT_WAKE } event_type;
#if WIN && USE_SSL
char* strcasestr(const char *haystack, const char *needle);
#endif
char *next_param(char *src, char c);
u32_t gettime_ms(void);
void get_mac(u8_t *mac);
void set_nonblock(sockfd s);
int connect_timeout(sockfd sock, const struct sockaddr *addr, socklen_t addrlen, int timeout);
void server_addr(char *server, in_addr_t *ip_ptr, unsigned *port_ptr);
void set_readwake_handles(event_handle handles[], sockfd s, event_event e);
event_type wait_readwake(event_handle handles[], int timeout);
void packN(u32_t *dest, u32_t val);
void packn(u16_t *dest, u16_t val);
u32_t unpackN(u32_t *src);
u16_t unpackn(u16_t *src);
#if OSX
void set_nosigpipe(sockfd s);
#else
#define set_nosigpipe(s)
#endif
#if SUN
void init_daemonize(void);
int daemon(int,int);
#endif
#if WIN
void winsock_init(void);
void winsock_close(void);
void *dlopen(const char *filename, int flag);
void *dlsym(void *handle, const char *symbol);
char *dlerror(void);
int poll(struct pollfd *fds, unsigned long numfds, int timeout);
#endif
#if LINUX || FREEBSD
void touch_memory(u8_t *buf, size_t size);
#endif
// buffer.c
struct buffer {
u8_t *buf;
u8_t *readp;
u8_t *writep;
u8_t *wrap;
size_t size;
size_t base_size;
mutex_type mutex;
};
// _* called with mutex locked
unsigned _buf_used(struct buffer *buf);
unsigned _buf_space(struct buffer *buf);
unsigned _buf_cont_read(struct buffer *buf);
unsigned _buf_cont_write(struct buffer *buf);
void _buf_inc_readp(struct buffer *buf, unsigned by);
void _buf_inc_writep(struct buffer *buf, unsigned by);
void buf_flush(struct buffer *buf);
void buf_adjust(struct buffer *buf, size_t mod);
void _buf_resize(struct buffer *buf, size_t size);
void buf_init(struct buffer *buf, size_t size);
void buf_destroy(struct buffer *buf);
// slimproto.c
void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname, int maxSampleRate);
void slimproto_stop(void);
void wake_controller(void);
// stream.c
typedef enum { STOPPED = 0, DISCONNECT, STREAMING_WAIT,
STREAMING_BUFFERING, STREAMING_FILE, STREAMING_HTTP, SEND_HEADERS, RECV_HEADERS } stream_state;
typedef enum { DISCONNECT_OK = 0, LOCAL_DISCONNECT = 1, REMOTE_DISCONNECT = 2, UNREACHABLE = 3, TIMEOUT = 4 } disconnect_code;
struct streamstate {
stream_state state;
disconnect_code disconnect;
char *header;
size_t header_len;
bool sent_headers;
bool cont_wait;
u64_t bytes;
unsigned threshold;
u32_t meta_interval;
u32_t meta_next;
u32_t meta_left;
bool meta_send;
};
void stream_init(log_level level, unsigned stream_buf_size);
void stream_close(void);
void stream_file(const char *header, size_t header_len, unsigned threshold);
void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait);
bool stream_disconnect(void);
// decode.c
typedef enum { DECODE_STOPPED = 0, DECODE_READY, DECODE_RUNNING, DECODE_COMPLETE, DECODE_ERROR } decode_state;
struct decodestate {
decode_state state;
bool new_stream;
mutex_type mutex;
#if PROCESS
bool direct;
bool process;
#endif
};
#if PROCESS
struct processstate {
u8_t *inbuf, *outbuf;
unsigned max_in_frames, max_out_frames;
unsigned in_frames, out_frames;
unsigned in_sample_rate, out_sample_rate;
unsigned long total_in, total_out;
};
#endif
struct codec {
char id;
char *types;
unsigned min_read_bytes;
unsigned min_space;
void (*open)(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness);
void (*close)(void);
decode_state (*decode)(void);
};
void decode_init(log_level level, const char *include_codecs, const char *exclude_codecs);
void decode_close(void);
void decode_flush(void);
unsigned decode_newstream(unsigned sample_rate, unsigned supported_rates[]);
void codec_open(u8_t format, u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness);
#if PROCESS
// process.c
void process_samples(void);
void process_drain(void);
void process_flush(void);
unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned supported_rates[]);
void process_init(char *opt);
#endif
#if RESAMPLE || RESAMPLE16
// resample.c
void resample_samples(struct processstate *process);
bool resample_drain(struct processstate *process);
bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned supported_rates[]);
void resample_flush(void);
bool resample_init(char *opt);
#endif
// output.c output_alsa.c output_pa.c output_pack.c
typedef enum { OUTPUT_OFF = -1, OUTPUT_STOPPED = 0, OUTPUT_BUFFER, OUTPUT_RUNNING,
OUTPUT_PAUSE_FRAMES, OUTPUT_SKIP_FRAMES, OUTPUT_START_AT } output_state;
#if DSD
typedef enum { PCM, DOP, DSD_U8, DSD_U16_LE, DSD_U32_LE, DSD_U16_BE, DSD_U32_BE, DOP_S24_LE, DOP_S24_3LE } dsd_format;
typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE, U8, U16_LE, U16_BE, U32_LE, U32_BE } output_format;
#else
typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE, S24_BE, S24_3BE, S16_BE, S8_BE } output_format;
#endif
typedef enum { FADE_INACTIVE = 0, FADE_DUE, FADE_ACTIVE } fade_state;
typedef enum { FADE_UP = 1, FADE_DOWN, FADE_CROSS } fade_dir;
typedef enum { FADE_NONE = 0, FADE_CROSSFADE, FADE_IN, FADE_OUT, FADE_INOUT } fade_mode;
#define MAX_SUPPORTED_SAMPLERATES 18
#define TEST_RATES = { 768000, 705600, 384000, 352800, 192000, 176400, 96000, 88200, 48000, 44100, 32000, 24000, 22500, 16000, 12000, 11025, 8000, 0 }
struct outputstate {
output_state state;
output_format format;
const char *device;
#if ALSA
unsigned buffer;
unsigned period;
#endif
bool track_started;
#if PORTAUDIO
bool pa_reopen;
unsigned latency;
int pa_hostapi_option;
#endif
int (* write_cb)(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
unsigned start_frames;
unsigned frames_played;
unsigned frames_played_dmp;// frames played at the point delay is measured
unsigned current_sample_rate;
unsigned supported_rates[MAX_SUPPORTED_SAMPLERATES]; // ordered largest first so [0] is max_rate
unsigned default_sample_rate;
bool error_opening;
unsigned device_frames;
u32_t updated;
u32_t track_start_time;
u32_t current_replay_gain;
union {
u32_t pause_frames;
u32_t skip_frames;
u32_t start_at;
};
unsigned next_sample_rate; // set in decode thread
u8_t *track_start; // set in decode thread
u32_t gainL; // set by slimproto
u32_t gainR; // set by slimproto
bool invert; // set by slimproto
u32_t next_replay_gain; // set by slimproto
unsigned threshold; // set by slimproto
fade_state fade;
u8_t *fade_start;
u8_t *fade_end;
fade_dir fade_dir;
fade_mode fade_mode; // set by slimproto
unsigned fade_secs; // set by slimproto
unsigned rate_delay;
bool delay_active;
u32_t stop_time;
u32_t idle_to;
#if DSD
dsd_format next_fmt; // set in decode thread
dsd_format outfmt;
dsd_format dsdfmt; // set in dsd_init - output for DSD: DOP, DSD_U8, ...
unsigned dsd_delay; // set in dsd_init - delay in ms switching to/from dop
#endif
};
void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle);
void output_close_common(void);
void output_flush(void);
// _* called with mutex locked
frames_t _output_frames(frames_t avail);
void _checkfade(bool);
// output_alsa.c
#if ALSA
void list_devices(void);
void list_mixers(const char *output_device);
void set_volume(unsigned left, unsigned right);
bool test_open(const char *device, unsigned rates[], bool userdef_rates);
void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned rt_priority, unsigned idle, char *mixer_device, char *volume_mixer, bool mixer_unmute, bool mixer_linear);
void output_close_alsa(void);
#endif
// output_pa.c
#if PORTAUDIO
void list_devices(void);
void set_volume(unsigned left, unsigned right);
bool test_open(const char *device, unsigned rates[], bool userdef_rates);
void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle);
void output_close_pa(void);
void _pa_open(void);
#endif
// output_embedded.c
#if EMBEDDED
void set_volume(unsigned left, unsigned right);
bool test_open(const char *device, unsigned rates[], bool userdef_rates);
void output_init_embedded(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle);
void output_close_embedded(void);
#else
// output_stdout.c
void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay);
void output_close_stdout(void);
#endif
// output_pack.c
void _scale_and_pack_frames(void *outputptr, s32_t *inputptr, frames_t cnt, s32_t gainL, s32_t gainR, output_format format);
void _apply_cross(struct buffer *outputbuf, frames_t out_frames, s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
void _apply_gain(struct buffer *outputbuf, frames_t count, s32_t gainL, s32_t gainR);
s32_t gain(s32_t gain, s32_t sample);
s32_t to_gain(float f);
// output_vis.c
#if VISEXPORT
void _vis_export(struct buffer *outputbuf, struct outputstate *output, frames_t out_frames, bool silence);
void output_vis_init(log_level level, u8_t *mac);
void vis_stop(void);
#else
#define _vis_export(...)
#define vis_stop()
#endif
// dop.c
#if DSD
bool is_stream_dop(u8_t *lptr, u8_t *rptr, int step, frames_t frames);
void update_dop(u32_t *ptr, frames_t frames, bool invert);
void dsd_silence_frames(u32_t *ptr, frames_t frames);
void dsd_invert(u32_t *ptr, frames_t frames);
void dsd_init(dsd_format format, unsigned delay);
#endif
// codecs
#define MAX_CODECS 9
struct codec *register_flac(void);
struct codec *register_pcm(void);
struct codec *register_mad(void);
struct codec *register_mpg(void);
struct codec *register_vorbis(void);
struct codec *register_faad(void);
struct codec *register_helixaac(void);
struct codec *register_dsd(void);
struct codec *register_alac(void);
struct codec *register_ff(const char *codec);
//gpio.c
#if GPIO
void relay( int state);
void relay_script(int state);
int gpio_pin;
bool gpio_active_low;
bool gpio_active;
char *power_script;
// my amp state
int ampstate;
#endif
// ir.c
#if IR
struct irstate {
mutex_type mutex;
u32_t code;
u32_t ts;
};
void ir_init(log_level level, char *lircrc);
void ir_close(void);
#endif
// sslsym.c
#if USE_SSL && !LINKALL
bool load_ssl_symbols(void);
void free_ssl_symbols(void);
bool ssl_loaded;
#endif

View File

@@ -0,0 +1,593 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
// stream thread
#define _GNU_SOURCE
#include "squeezelite.h"
#include <fcntl.h>
#if USE_SSL
#include "openssl/ssl.h"
#include "openssl/err.h"
#endif
#if SUN
#include <signal.h>
#endif
static log_level loglevel;
static struct buffer buf;
struct buffer *streambuf = &buf;
#define LOCK mutex_lock(streambuf->mutex)
#define UNLOCK mutex_unlock(streambuf->mutex)
static sockfd fd;
struct streamstate stream;
#if USE_SSL
static SSL_CTX *SSLctx;
SSL *ssl;
#endif
#if !USE_SSL
#define _recv(ssl, fc, buf, n, opt) recv(fd, buf, n, opt)
#define _send(ssl, fd, buf, n, opt) send(fd, buf, n, opt)
#define _poll(ssl, pollinfo, timeout) poll(pollinfo, 1, timeout)
#define _last_error() last_error()
#else
#define _last_error() ERROR_WOULDBLOCK
static int _recv(SSL *ssl, int fd, void *buffer, size_t bytes, int options) {
int n;
if (!ssl) return recv(fd, buffer, bytes, options);
n = SSL_read(ssl, (u8_t*) buffer, bytes);
if (n <= 0 && SSL_get_error(ssl, n) == SSL_ERROR_ZERO_RETURN) return 0;
return n;
}
static int _send(SSL *ssl, int fd, void *buffer, size_t bytes, int options) {
int n;
if (!ssl) return send(fd, buffer, bytes, options);
while (1) {
int err;
ERR_clear_error();
if ((n = SSL_write(ssl, (u8_t*) buffer, bytes)) >= 0) return n;
err = SSL_get_error(ssl, n);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) continue;
LOG_INFO("SSL write error %d", err );
return n;
}
}
/*
can't mimic exactly poll as SSL is a real pain. Even if SSL_pending returns
0, there might be bytes to read but when select (poll) return > 0, there might
be no frame available. As well select (poll) < 0 does not mean that there is
no data pending
*/
static int _poll(SSL *ssl, struct pollfd *pollinfo, int timeout) {
if (!ssl) return poll(pollinfo, 1, timeout);
if (pollinfo->events & POLLIN && SSL_pending(ssl)) {
if (pollinfo->events & POLLOUT) poll(pollinfo, 1, 0);
pollinfo->revents = POLLIN;
return 1;
}
return poll(pollinfo, 1, timeout);
}
#endif
static bool send_header(void) {
char *ptr = stream.header;
int len = stream.header_len;
unsigned try = 0;
ssize_t n;
while (len) {
n = _send(ssl, fd, ptr, len, MSG_NOSIGNAL);
if (n <= 0) {
if (n < 0 && _last_error() == ERROR_WOULDBLOCK && try < 10) {
LOG_SDEBUG("retrying (%d) writing to socket", ++try);
usleep(1000);
continue;
}
LOG_INFO("failed writing to socket: %s", strerror(last_error()));
stream.disconnect = LOCAL_DISCONNECT;
stream.state = DISCONNECT;
wake_controller();
return false;
}
LOG_SDEBUG("wrote %d bytes to socket", n);
ptr += n;
len -= n;
}
LOG_SDEBUG("wrote header");
return true;
}
static bool running = true;
static void _disconnect(stream_state state, disconnect_code disconnect) {
stream.state = state;
stream.disconnect = disconnect;
#if USE_SSL
if (ssl) {
SSL_shutdown(ssl);
SSL_free(ssl);
ssl = NULL;
}
#endif
closesocket(fd);
fd = -1;
wake_controller();
}
static void *stream_thread() {
while (running) {
struct pollfd pollinfo;
size_t space;
LOCK;
space = min(_buf_space(streambuf), _buf_cont_write(streambuf));
if (fd < 0 || !space || stream.state <= STREAMING_WAIT) {
UNLOCK;
usleep(space ? 100000 : 25000);
continue;
}
if (stream.state == STREAMING_FILE) {
int n = read(fd, streambuf->writep, space);
if (n == 0) {
LOG_INFO("end of stream");
_disconnect(DISCONNECT, DISCONNECT_OK);
}
if (n > 0) {
_buf_inc_writep(streambuf, n);
stream.bytes += n;
LOG_SDEBUG("streambuf read %d bytes", n);
}
if (n < 0) {
LOG_WARN("error reading: %s", strerror(last_error()));
_disconnect(DISCONNECT, REMOTE_DISCONNECT);
}
UNLOCK;
continue;
} else {
pollinfo.fd = fd;
pollinfo.events = POLLIN;
if (stream.state == SEND_HEADERS) {
pollinfo.events |= POLLOUT;
}
}
UNLOCK;
if (_poll(ssl, &pollinfo, 100)) {
LOCK;
// check socket has not been closed while in poll
if (fd < 0) {
UNLOCK;
continue;
}
if ((pollinfo.revents & POLLOUT) && stream.state == SEND_HEADERS) {
if (send_header()) stream.state = RECV_HEADERS;
stream.header_len = 0;
UNLOCK;
continue;
}
if (pollinfo.revents & (POLLIN | POLLHUP)) {
// get response headers
if (stream.state == RECV_HEADERS) {
// read one byte at a time to catch end of header
char c;
static int endtok;
int n = _recv(ssl, fd, &c, 1, 0);
if (n <= 0) {
if (n < 0 && _last_error() == ERROR_WOULDBLOCK) {
UNLOCK;
continue;
}
LOG_INFO("error reading headers: %s", n ? strerror(last_error()) : "closed");
_disconnect(STOPPED, LOCAL_DISCONNECT);
UNLOCK;
continue;
}
*(stream.header + stream.header_len) = c;
stream.header_len++;
if (stream.header_len > MAX_HEADER - 1) {
LOG_ERROR("received headers too long: %u", stream.header_len);
_disconnect(DISCONNECT, LOCAL_DISCONNECT);
}
if (stream.header_len > 1 && (c == '\r' || c == '\n')) {
endtok++;
if (endtok == 4) {
*(stream.header + stream.header_len) = '\0';
LOG_INFO("headers: len: %d\n%s", stream.header_len, stream.header);
stream.state = stream.cont_wait ? STREAMING_WAIT : STREAMING_BUFFERING;
wake_controller();
}
} else {
endtok = 0;
}
UNLOCK;
continue;
}
// receive icy meta data
if (stream.meta_interval && stream.meta_next == 0) {
if (stream.meta_left == 0) {
// read meta length
u8_t c;
int n = _recv(ssl, fd, &c, 1, 0);
if (n <= 0) {
if (n < 0 && _last_error() == ERROR_WOULDBLOCK) {
UNLOCK;
continue;
}
LOG_INFO("error reading icy meta: %s", n ? strerror(last_error()) : "closed");
_disconnect(STOPPED, LOCAL_DISCONNECT);
UNLOCK;
continue;
}
stream.meta_left = 16 * c;
stream.header_len = 0; // amount of received meta data
// MAX_HEADER must be more than meta max of 16 * 255
}
if (stream.meta_left) {
int n = _recv(ssl, fd, stream.header + stream.header_len, stream.meta_left, 0);
if (n <= 0) {
if (n < 0 && _last_error() == ERROR_WOULDBLOCK) {
UNLOCK;
continue;
}
LOG_INFO("error reading icy meta: %s", n ? strerror(last_error()) : "closed");
_disconnect(STOPPED, LOCAL_DISCONNECT);
UNLOCK;
continue;
}
stream.meta_left -= n;
stream.header_len += n;
}
if (stream.meta_left == 0) {
if (stream.header_len) {
*(stream.header + stream.header_len) = '\0';
LOG_INFO("icy meta: len: %u\n%s", stream.header_len, stream.header);
stream.meta_send = true;
wake_controller();
}
stream.meta_next = stream.meta_interval;
UNLOCK;
continue;
}
// stream body into streambuf
} else {
int n;
space = min(_buf_space(streambuf), _buf_cont_write(streambuf));
if (stream.meta_interval) {
space = min(space, stream.meta_next);
}
n = _recv(ssl, fd, streambuf->writep, space, 0);
if (n == 0) {
LOG_INFO("end of stream");
_disconnect(DISCONNECT, DISCONNECT_OK);
}
if (n < 0 && _last_error() != ERROR_WOULDBLOCK) {
LOG_INFO("error reading: %s", strerror(last_error()));
_disconnect(DISCONNECT, REMOTE_DISCONNECT);
}
if (n > 0) {
_buf_inc_writep(streambuf, n);
stream.bytes += n;
if (stream.meta_interval) {
stream.meta_next -= n;
}
} else {
UNLOCK;
continue;
}
if (stream.state == STREAMING_BUFFERING && stream.bytes > stream.threshold) {
stream.state = STREAMING_HTTP;
wake_controller();
}
LOG_SDEBUG("streambuf read %d bytes", n);
}
}
UNLOCK;
} else {
LOG_SDEBUG("poll timeout");
}
}
#if USE_SSL
if (SSLctx) {
SSL_CTX_free(SSLctx);
}
#endif
return 0;
}
static thread_type thread;
void stream_init(log_level level, unsigned stream_buf_size) {
loglevel = level;
LOG_INFO("init stream");
LOG_DEBUG("streambuf size: %u", stream_buf_size);
buf_init(streambuf, stream_buf_size);
if (streambuf->buf == NULL) {
LOG_ERROR("unable to malloc buffer");
exit(0);
}
#if USE_SSL
#if !LINKALL
if (ssl_loaded) {
#endif
SSL_library_init();
SSLctx = SSL_CTX_new(SSLv23_client_method());
if (SSLctx == NULL) {
LOG_ERROR("unable to allocate SSL context");
exit(0);
}
SSL_CTX_set_options(SSLctx, SSL_OP_NO_SSLv2);
#if !LINKALL
}
#endif
ssl = NULL;
#endif
#if SUN
signal(SIGPIPE, SIG_IGN); /* Force sockets to return -1 with EPIPE on pipe signal */
#endif
stream.state = STOPPED;
stream.header = malloc(MAX_HEADER);
*stream.header = '\0';
fd = -1;
#if LINUX || FREEBSD
touch_memory(streambuf->buf, streambuf->size);
#endif
#if LINUX || OSX || FREEBSD || EMBEDDED
pthread_attr_t attr;
pthread_attr_init(&attr);
#ifdef PTHREAD_STACK_MIN
pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + STREAM_THREAD_STACK_SIZE);
#endif
pthread_create(&thread, &attr, stream_thread, NULL);
pthread_attr_destroy(&attr);
#if HAS_PTHREAD_SETNAME_NP
pthread_setname_np(thread, "stream");
#endif
#endif
#if WIN
thread = CreateThread(NULL, STREAM_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&stream_thread, NULL, 0, NULL);
#endif
}
void stream_close(void) {
LOG_INFO("close stream");
LOCK;
running = false;
UNLOCK;
#if LINUX || OSX || FREEBSD || POSIX
pthread_join(thread, NULL);
#endif
free(stream.header);
buf_destroy(streambuf);
}
void stream_file(const char *header, size_t header_len, unsigned threshold) {
buf_flush(streambuf);
LOCK;
stream.header_len = header_len;
memcpy(stream.header, header, header_len);
*(stream.header+header_len) = '\0';
LOG_INFO("opening local file: %s", stream.header);
#if WIN
fd = open(stream.header, O_RDONLY | O_BINARY);
#else
fd = open(stream.header, O_RDONLY);
#endif
stream.state = STREAMING_FILE;
if (fd < 0) {
LOG_INFO("can't open file: %s", stream.header);
stream.state = DISCONNECT;
}
wake_controller();
stream.cont_wait = false;
stream.meta_interval = 0;
stream.meta_next = 0;
stream.meta_left = 0;
stream.meta_send = false;
stream.sent_headers = false;
stream.bytes = 0;
stream.threshold = threshold;
UNLOCK;
}
void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
struct sockaddr_in addr;
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
LOG_ERROR("failed to create socket");
return;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = ip;
addr.sin_port = port;
LOG_INFO("connecting to %s:%d", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
set_nonblock(sock);
set_nosigpipe(sock);
if (connect_timeout(sock, (struct sockaddr *) &addr, sizeof(addr), 10) < 0) {
LOG_INFO("unable to connect to server");
LOCK;
stream.state = DISCONNECT;
stream.disconnect = UNREACHABLE;
UNLOCK;
return;
}
#if USE_SSL
if (ntohs(port) == 443) {
char *server = strcasestr(header, "Host:");
ssl = SSL_new(SSLctx);
SSL_set_fd(ssl, sock);
// add SNI
if (server) {
char *p, *servername = malloc(1024);
sscanf(server, "Host:%255[^:]s", servername);
for (p = servername; *p == ' '; p++);
SSL_set_tlsext_host_name(ssl, p);
free(servername);
}
while (1) {
int status, err = 0;
ERR_clear_error();
status = SSL_connect(ssl);
// successful negotiation
if (status == 1) break;
// error or non-blocking requires more time
if (status < 0) {
err = SSL_get_error(ssl, status);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) continue;
}
LOG_WARN("unable to open SSL socket %d (%d)", status, err);
closesocket(sock);
SSL_free(ssl);
ssl = NULL;
LOCK;
stream.state = DISCONNECT;
stream.disconnect = UNREACHABLE;
UNLOCK;
return;
}
} else {
ssl = NULL;
}
#endif
buf_flush(streambuf);
LOCK;
fd = sock;
stream.state = SEND_HEADERS;
stream.cont_wait = cont_wait;
stream.meta_interval = 0;
stream.meta_next = 0;
stream.meta_left = 0;
stream.meta_send = false;
stream.header_len = header_len;
memcpy(stream.header, header, header_len);
*(stream.header+header_len) = '\0';
LOG_INFO("header: %s", stream.header);
stream.sent_headers = false;
stream.bytes = 0;
stream.threshold = threshold;
UNLOCK;
}
bool stream_disconnect(void) {
bool disc = false;
LOCK;
#if USE_SSL
if (ssl) {
SSL_shutdown(ssl);
SSL_free(ssl);
ssl = NULL;
}
#endif
if (fd != -1) {
closesocket(fd);
fd = -1;
disc = true;
}
stream.state = STOPPED;
UNLOCK;
return disc;
}

View File

@@ -0,0 +1,563 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
#include "squeezelite.h"
#if LINUX || OSX || FREEBSD || EMBEDDED
#include <sys/ioctl.h>
#include <net/if.h>
#include <netdb.h>
#if FREEBSD
#include <ifaddrs.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#endif
#endif
#if SUN
#include <sys/socket.h>
#include <sys/sockio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#endif
#if WIN
#include <iphlpapi.h>
#if USE_SSL
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#endif
#endif
#if OSX
#include <net/if_dl.h>
#include <net/if_types.h>
#include <ifaddrs.h>
#include <netdb.h>
#endif
#include <fcntl.h>
// logging functions
const char *logtime(void) {
static char buf[100];
#if WIN
SYSTEMTIME lt;
GetLocalTime(&lt);
sprintf(buf, "[%02d:%02d:%02d.%03d]", lt.wHour, lt.wMinute, lt.wSecond, lt.wMilliseconds);
#else
struct timeval tv;
gettimeofday(&tv, NULL);
strftime(buf, sizeof(buf), "[%T.", localtime(&tv.tv_sec));
sprintf(buf+strlen(buf), "%06ld]", (long)tv.tv_usec);
#endif
return buf;
}
void logprint(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
fflush(stderr);
}
// cmdline parsing
char *next_param(char *src, char c) {
static char *str = NULL;
char *ptr, *ret;
if (src) str = src;
if (str && (ptr = strchr(str, c))) {
ret = str;
*ptr = '\0';
str = ptr + 1;
} else {
ret = str;
str = NULL;
}
return ret && ret[0] ? ret : NULL;
}
// clock
u32_t gettime_ms(void) {
#if WIN
return GetTickCount();
#else
#if LINUX || FREEBSD || EMBEDDED
struct timespec ts;
#ifdef CLOCK_MONOTONIC
if (!clock_gettime(CLOCK_MONOTONIC, &ts)) {
#else
if (!clock_gettime(CLOCK_REALTIME, &ts)) {
#endif
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
#endif
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
#endif
}
// mac address
#if LINUX && !defined(SUN)
// search first 4 interfaces returned by IFCONF
void get_mac(u8_t mac[]) {
char *utmac;
struct ifconf ifc;
struct ifreq *ifr, *ifend;
struct ifreq ifreq;
struct ifreq ifs[4];
utmac = getenv("UTMAC");
if (utmac)
{
if ( strlen(utmac) == 17 )
{
if (sscanf(utmac,"%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx",
&mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5]) == 6)
{
return;
}
}
}
mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
int s = socket(AF_INET, SOCK_DGRAM, 0);
ifc.ifc_len = sizeof(ifs);
ifc.ifc_req = ifs;
if (ioctl(s, SIOCGIFCONF, &ifc) == 0) {
ifend = ifs + (ifc.ifc_len / sizeof(struct ifreq));
for (ifr = ifc.ifc_req; ifr < ifend; ifr++) {
if (ifr->ifr_addr.sa_family == AF_INET) {
strncpy(ifreq.ifr_name, ifr->ifr_name, sizeof(ifreq.ifr_name));
if (ioctl (s, SIOCGIFHWADDR, &ifreq) == 0) {
memcpy(mac, ifreq.ifr_hwaddr.sa_data, 6);
if (mac[0]+mac[1]+mac[2] != 0) {
break;
}
}
}
}
}
close(s);
}
#endif
#if SUN
void get_mac(u8_t mac[]) {
struct arpreq parpreq;
struct sockaddr_in *psa;
struct in_addr inaddr;
struct hostent *phost;
char hostname[MAXHOSTNAMELEN];
char **paddrs;
char *utmac;
int sock;
int status=0;
utmac = getenv("UTMAC");
if (utmac)
{
if ( strlen(utmac) == 17 )
{
if (sscanf(utmac,"%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx",
&mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5]) == 6)
{
return;
}
}
}
mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
gethostname(hostname, MAXHOSTNAMELEN);
phost = gethostbyname(hostname);
paddrs = phost->h_addr_list;
memcpy(&inaddr.s_addr, *paddrs, sizeof(inaddr.s_addr));
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sock == -1)
{
mac[5] = 1;
return;
}
memset(&parpreq, 0, sizeof(struct arpreq));
psa = (struct sockaddr_in *) &parpreq.arp_pa;
memset(psa, 0, sizeof(struct sockaddr_in));
psa->sin_family = AF_INET;
memcpy(&psa->sin_addr, *paddrs, sizeof(struct in_addr));
status = ioctl(sock, SIOCGARP, &parpreq);
if(status == -1)
{
mac[5] = 2;
return;
}
mac[0] = (unsigned char) parpreq.arp_ha.sa_data[0];
mac[1] = (unsigned char) parpreq.arp_ha.sa_data[1];
mac[2] = (unsigned char) parpreq.arp_ha.sa_data[2];
mac[3] = (unsigned char) parpreq.arp_ha.sa_data[3];
mac[4] = (unsigned char) parpreq.arp_ha.sa_data[4];
mac[5] = (unsigned char) parpreq.arp_ha.sa_data[5];
}
#endif
#if OSX || FREEBSD
void get_mac(u8_t mac[]) {
struct ifaddrs *addrs, *ptr;
const struct sockaddr_dl *dlAddr;
const unsigned char *base;
mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
if (getifaddrs(&addrs) == 0) {
ptr = addrs;
while (ptr) {
if (ptr->ifa_addr->sa_family == AF_LINK && ((const struct sockaddr_dl *) ptr->ifa_addr)->sdl_type == IFT_ETHER) {
dlAddr = (const struct sockaddr_dl *)ptr->ifa_addr;
base = (const unsigned char*) &dlAddr->sdl_data[dlAddr->sdl_nlen];
memcpy(mac, base, min(dlAddr->sdl_alen, 6));
break;
}
ptr = ptr->ifa_next;
}
freeifaddrs(addrs);
}
}
#endif
#if WIN
#pragma comment(lib, "IPHLPAPI.lib")
void get_mac(u8_t mac[]) {
IP_ADAPTER_INFO AdapterInfo[16];
DWORD dwBufLen = sizeof(AdapterInfo);
DWORD dwStatus = GetAdaptersInfo(AdapterInfo, &dwBufLen);
mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == ERROR_SUCCESS) {
memcpy(mac, AdapterInfo[0].Address, 6);
}
}
#endif
void set_nonblock(sockfd s) {
#if WIN
u_long iMode = 1;
ioctlsocket(s, FIONBIO, &iMode);
#else
int flags = fcntl(s, F_GETFL,0);
fcntl(s, F_SETFL, flags | O_NONBLOCK);
#endif
}
// connect for socket already set to non blocking with timeout in seconds
int connect_timeout(sockfd sock, const struct sockaddr *addr, socklen_t addrlen, int timeout) {
fd_set w, e;
struct timeval tval;
if (connect(sock, addr, addrlen) < 0) {
#if !WIN
if (last_error() != EINPROGRESS) {
#else
if (last_error() != WSAEWOULDBLOCK) {
#endif
return -1;
}
}
FD_ZERO(&w);
FD_SET(sock, &w);
e = w;
tval.tv_sec = timeout;
tval.tv_usec = 0;
// only return 0 if w set and sock error is zero, otherwise return error code
if (select(sock + 1, NULL, &w, &e, timeout ? &tval : NULL) == 1 && FD_ISSET(sock, &w)) {
int error = 0;
socklen_t len = sizeof(error);
getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *)&error, &len);
return error;
}
return -1;
}
void server_addr(char *server, in_addr_t *ip_ptr, unsigned *port_ptr) {
struct addrinfo *res = NULL;
struct addrinfo hints;
const char *port = NULL;
if (strtok(server, ":")) {
port = strtok(NULL, ":");
if (port) {
*port_ptr = atoi(port);
}
}
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
getaddrinfo(server, NULL, &hints, &res);
if (res && res->ai_addr) {
*ip_ptr = ((struct sockaddr_in*)res->ai_addr)->sin_addr.s_addr;
}
if (res) {
freeaddrinfo(res);
}
}
void set_readwake_handles(event_handle handles[], sockfd s, event_event e) {
#if WINEVENT
handles[0] = WSACreateEvent();
handles[1] = e;
WSAEventSelect(s, handles[0], FD_READ | FD_CLOSE);
#elif SELFPIPE || LOOPBACK
handles[0].fd = s;
handles[1].fd = e.fds[0];
handles[0].events = POLLIN;
handles[1].events = POLLIN;
#else
handles[0].fd = s;
handles[1].fd = e;
handles[0].events = POLLIN;
handles[1].events = POLLIN;
#endif
}
event_type wait_readwake(event_handle handles[], int timeout) {
#if WINEVENT
int wait = WSAWaitForMultipleEvents(2, handles, FALSE, timeout, FALSE);
if (wait == WSA_WAIT_EVENT_0) {
WSAResetEvent(handles[0]);
return EVENT_READ;
} else if (wait == WSA_WAIT_EVENT_0 + 1) {
return EVENT_WAKE;
} else {
return EVENT_TIMEOUT;
}
#else
if (poll(handles, 2, timeout) > 0) {
if (handles[0].revents) {
return EVENT_READ;
}
if (handles[1].revents) {
wake_clear(handles[1].fd);
return EVENT_WAKE;
}
}
return EVENT_TIMEOUT;
#endif
}
#if LOOPBACK
void _wake_create(event_event* e) {
struct sockaddr_in addr;
short port;
socklen_t len;
e->mfds = e->fds[0] = e->fds[1] = -1;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
// create sending socket - will wait for connections
addr.sin_port = 0;
e->mfds = socket(AF_INET, SOCK_STREAM, 0);
bind(e->mfds, (struct sockaddr*) &addr, sizeof(addr));
len = sizeof(struct sockaddr);
// get assigned port & listen
getsockname(e->mfds, (struct sockaddr *) &addr, &len);
port = addr.sin_port;
listen(e->mfds, 1);
// create receiving socket
addr.sin_port = 0;
e->fds[0] = socket(AF_INET, SOCK_STREAM, 0);
bind(e->fds[0], (struct sockaddr*) &addr, sizeof(addr));
// connect to sender (we listen so it can be blocking)
addr.sin_port = port;
connect(e->fds[0], (struct sockaddr*) &addr, sizeof(addr));
// this one will work or fail, but not block
len = sizeof(struct sockaddr);
e->fds[1] = accept(e->mfds, (struct sockaddr*) &addr, &len);
}
#endif
// pack/unpack to network byte order
void packN(u32_t *dest, u32_t val) {
u8_t *ptr = (u8_t *)dest;
*(ptr) = (val >> 24) & 0xFF; *(ptr+1) = (val >> 16) & 0xFF; *(ptr+2) = (val >> 8) & 0xFF; *(ptr+3) = val & 0xFF;
}
void packn(u16_t *dest, u16_t val) {
u8_t *ptr = (u8_t *)dest;
*(ptr) = (val >> 8) & 0xFF; *(ptr+1) = val & 0xFF;
}
u32_t unpackN(u32_t *src) {
u8_t *ptr = (u8_t *)src;
return *(ptr) << 24 | *(ptr+1) << 16 | *(ptr+2) << 8 | *(ptr+3);
}
u16_t unpackn(u16_t *src) {
u8_t *ptr = (u8_t *)src;
return *(ptr) << 8 | *(ptr+1);
}
#if OSX
void set_nosigpipe(sockfd s) {
int set = 1;
setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
}
#endif
#if WIN
void winsock_init(void) {
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
int WSerr = WSAStartup(wVersionRequested, &wsaData);
if (WSerr != 0) {
LOG_ERROR("Bad winsock version");
exit(1);
}
}
void winsock_close(void) {
WSACleanup();
}
void *dlopen(const char *filename, int flag) {
SetLastError(0);
return LoadLibrary((LPCTSTR)filename);
}
void *dlsym(void *handle, const char *symbol) {
SetLastError(0);
return (void *)GetProcAddress(handle, symbol);
}
char *dlerror(void) {
static char ret[32];
int last = GetLastError();
if (last) {
sprintf(ret, "code: %i", last);
SetLastError(0);
return ret;
}
return NULL;
}
int poll(struct pollfd *fds, unsigned long numfds, int timeout) {
fd_set r, w;
struct timeval tv;
int ret, i, max_fds = fds[0].fd;
FD_ZERO(&r);
FD_ZERO(&w);
for (i = 0; i < numfds; i++) {
if (fds[i].events & POLLIN) FD_SET(fds[i].fd, &r);
if (fds[i].events & POLLOUT) FD_SET(fds[i].fd, &w);
if (max_fds < fds[i].fd) max_fds = fds[i].fd;
}
tv.tv_sec = timeout / 1000;
tv.tv_usec = 1000 * (timeout % 1000);
ret = select(max_fds + 1, &r, &w, NULL, &tv);
if (ret < 0) return ret;
for (i = 0; i < numfds; i++) {
fds[i].revents = 0;
if (FD_ISSET(fds[i].fd, &r)) fds[i].revents |= POLLIN;
if (FD_ISSET(fds[i].fd, &w)) fds[i].revents |= POLLOUT;
}
return ret;
}
#endif
#if LINUX || FREEBSD
void touch_memory(u8_t *buf, size_t size) {
u8_t *ptr;
for (ptr = buf; ptr < buf + size; ptr += sysconf(_SC_PAGESIZE)) {
*ptr = 0;
}
}
#endif
#if WIN && USE_SSL
char *strcasestr(const char *haystack, const char *needle) {
size_t length_needle;
size_t length_haystack;
size_t i;
if (!haystack || !needle)
return NULL;
length_needle = strlen(needle);
length_haystack = strlen(haystack) - length_needle + 1;
for (i = 0; i < length_haystack; i++)
{
size_t j;
for (j = 0; j < length_needle; j++)
{
unsigned char c1;
unsigned char c2;
c1 = haystack[i+j];
c2 = needle[j];
if (toupper(c1) != toupper(c2))
goto next;
}
return (char *) haystack + i;
next:
;
}
return NULL;
}
#endif

View File

@@ -0,0 +1,372 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.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 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/>.
*
*/
#include "squeezelite.h"
/*
* with some low-end CPU, the decode call takes a fair bit of time and if the outputbuf is locked during that
* period, the output_thread (or equivalent) will be locked although there is plenty of samples available.
* Normally, with PRIO_INHERIT, that thread should increase decoder priority and get the lock quickly but it
* seems that when the streambuf has plenty of data, the decode thread grabs the CPU to much, even it the output
* thread has a higher priority. Using an interim buffer where vorbis decoder writes the output is not great from
* an efficiency (one extra memory copy) point of view, but it allows the lock to not be kept for too long
*/
#define FRAME_BUF 2048
#if BYTES_PER_FRAME == 4
#define ALIGN(n) (n)
#else
#define ALIGN(n) (n << 16)
#endif
// automatically select between floating point (preferred) and fixed point libraries:
// NOTE: works with Tremor version here: http://svn.xiph.org/trunk/Tremor, not vorbisidec.1.0.2 currently in ubuntu
// we take common definations from <vorbis/vorbisfile.h> even though we can use tremor at run time
// tremor's OggVorbis_File struct is normally smaller so this is ok, but padding added to malloc in case it is bigger
#define OV_EXCLUDE_STATIC_CALLBACKS
#ifdef TREMOR_ONLY
#include <ivorbisfile.h>
#else
#include <vorbis/vorbisfile.h>
#endif
struct vorbis {
OggVorbis_File *vf;
bool opened;
#if FRAME_BUF
u8_t *write_buf;
#endif
#if !LINKALL
// vorbis symbols to be dynamically loaded - from either vorbisfile or vorbisidec (tremor) version of library
vorbis_info *(* ov_info)(OggVorbis_File *vf, int link);
int (* ov_clear)(OggVorbis_File *vf);
long (* ov_read)(OggVorbis_File *vf, char *buffer, int length, int bigendianp, int word, int sgned, int *bitstream);
long (* ov_read_tremor)(OggVorbis_File *vf, char *buffer, int length, int *bitstream);
int (* ov_open_callbacks)(void *datasource, OggVorbis_File *vf, const char *initial, long ibytes, ov_callbacks callbacks);
#endif
};
static struct vorbis *v;
extern log_level loglevel;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct decodestate decode;
extern struct processstate process;
#define LOCK_S mutex_lock(streambuf->mutex)
#define UNLOCK_S mutex_unlock(streambuf->mutex)
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
#if PROCESS
#define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex)
#define LOCK_O_not_direct if (!decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x) if (decode.direct) { x }
#define IF_PROCESS(x) if (!decode.direct) { x }
#else
#define LOCK_O_direct mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex)
#define LOCK_O_not_direct
#define UNLOCK_O_not_direct
#define IF_DIRECT(x) { x }
#define IF_PROCESS(x)
#endif
#if LINKALL
#define OV(h, fn, ...) (ov_ ## fn)(__VA_ARGS__)
#define TREMOR(h) 0
#if !WIN
extern int ov_read_tremor(); // needed to enable compilation, not linked
#endif
#else
#define OV(h, fn, ...) (h)->ov_##fn(__VA_ARGS__)
#define TREMOR(h) (h)->ov_read_tremor
#endif
// called with mutex locked within vorbis_decode to avoid locking O before S
static size_t _read_cb(void *ptr, size_t size, size_t nmemb, void *datasource) {
size_t bytes;
LOCK_S;
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
bytes = min(bytes, size * nmemb);
memcpy(ptr, streambuf->readp, bytes);
_buf_inc_readp(streambuf, bytes);
UNLOCK_S;
return bytes / size;
}
// these are needed for older versions of tremor, later versions and libvorbis allow NULL to be used
static int _seek_cb(void *datasource, ogg_int64_t offset, int whence) { return -1; }
static int _close_cb(void *datasource) { return 0; }
static long _tell_cb(void *datasource) { return 0; }
static decode_state vorbis_decode(void) {
static int channels;
frames_t frames;
int bytes, s, n;
u8_t *write_buf;
LOCK_S;
if (stream.state <= DISCONNECT && !_buf_used(streambuf)) {
UNLOCK_S;
return DECODE_COMPLETE;
}
UNLOCK_S;
if (decode.new_stream) {
ov_callbacks cbs;
int err;
struct vorbis_info *info;
cbs.read_func = _read_cb;
if (TREMOR(v)) {
cbs.seek_func = _seek_cb; cbs.close_func = _close_cb; cbs.tell_func = _tell_cb;
} else {
cbs.seek_func = NULL; cbs.close_func = NULL; cbs.tell_func = NULL;
}
if ((err = OV(v, open_callbacks, streambuf, v->vf, NULL, 0, cbs)) < 0) {
LOG_WARN("open_callbacks error: %d", err);
return DECODE_COMPLETE;
}
v->opened = true;
info = OV(v, info, v->vf, -1);
LOG_INFO("setting track_start");
LOCK_O;
output.next_sample_rate = decode_newstream(info->rate, output.supported_rates);
IF_DSD( output.next_fmt = PCM; )
output.track_start = outputbuf->writep;
if (output.fade_mode) _checkfade(true);
decode.new_stream = false;
UNLOCK_O;
channels = info->channels;
if (channels > 2) {
LOG_WARN("too many channels: %d", channels);
return DECODE_ERROR;
}
}
#if !FRAME_BUF
LOCK_O_direct;
#endif
IF_DIRECT(
frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
#if FRAME_BUF
write_buf = v->write_buf;
#else
write_buf = outputbuf->writep;
#endif
);
IF_PROCESS(
frames = process.max_in_frames;
write_buf = process.inbuf;
);
#if FRAME_BUF
frames = min(frames, FRAME_BUF);
#endif
bytes = frames * 2 * channels; // samples returned are 16 bits
// write the decoded frames into outputbuf even though they are 16 bits per sample, then unpack them
#ifdef TREMOR_ONLY
n = OV(v, read, v->vf, (char *)write_buf, bytes, &s);
#else
if (!TREMOR(v)) {
#if SL_LITTLE_ENDIAN
n = OV(v, read, v->vf, (char *)write_buf, bytes, 0, 2, 1, &s);
#else
n = OV(v, read, v->vf, (char *)write_buf, bytes, 1, 2, 1, &s);
#endif
#if !WIN
} else {
n = OV(v, read_tremor, v->vf, (char *)write_buf, bytes, &s);
#endif
}
#endif
#if FRAME_BUF
LOCK_O_direct;
#endif
if (n > 0) {
frames_t count;
s16_t *iptr;
ISAMPLE_T *optr;
frames = n / 2 / channels;
count = frames * channels;
iptr = (s16_t *)write_buf + count;
optr = (ISAMPLE_T *)write_buf + frames * 2;
if (channels == 2) {
#if BYTES_PER_FRAME == 4
memcpy(outputbuf->writep, write_buf, frames * BYTES_PER_FRAME);
#else
while (count--) {
*--optr = *--iptr << 16;
}
#endif
} else if (channels == 1) {
while (count--) {
*--optr = ALIGN(*--iptr);
*--optr = ALIGN(*iptr);
}
}
IF_DIRECT(
_buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME);
);
IF_PROCESS(
process.in_frames = frames;
);
LOG_SDEBUG("wrote %u frames", frames);
} else if (n == 0) {
LOG_INFO("end of stream");
UNLOCK_O_direct;
return DECODE_COMPLETE;
} else if (n == OV_HOLE) {
// recoverable hole in stream, seen when skipping
LOG_DEBUG("hole in stream");
} else {
LOG_INFO("ov_read error: %d", n);
UNLOCK_O_direct;
return DECODE_COMPLETE;
}
UNLOCK_O_direct;
return DECODE_RUNNING;
}
static void vorbis_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
if (!v->vf) {
v->vf = malloc(sizeof(OggVorbis_File) + 128); // add some padding as struct size may be larger
memset(v->vf, 0, sizeof(OggVorbis_File) + 128);
#if FRAME_BUF
v->write_buf = malloc(FRAME_BUF * BYTES_PER_FRAME);
#endif
} else {
if (v->opened) {
OV(v, clear, v->vf);
v->opened = false;
}
}
}
static void vorbis_close(void) {
if (v->opened) {
OV(v, clear, v->vf);
v->opened = false;
}
free(v->vf);
#if FRAME_BUF
free(v->write_buf);
v->write_buf = NULL;
#endif
v->vf = NULL;
}
static bool load_vorbis() {
#if !LINKALL
void *handle = dlopen(LIBVORBIS, RTLD_NOW);
char *err;
bool tremor = false;
if (!handle) {
handle = dlopen(LIBTREMOR, RTLD_NOW);
if (handle) {
tremor = true;
} else {
LOG_INFO("dlerror: %s", dlerror());
return false;
}
}
v->ov_read = tremor ? NULL : dlsym(handle, "ov_read");
v->ov_read_tremor = tremor ? dlsym(handle, "ov_read") : NULL;
v->ov_info = dlsym(handle, "ov_info");
v->ov_clear = dlsym(handle, "ov_clear");
v->ov_open_callbacks = dlsym(handle, "ov_open_callbacks");
if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err);
return false;
}
LOG_INFO("loaded %s", tremor ? LIBTREMOR : LIBVORBIS);
#endif
return true;
}
struct codec *register_vorbis(void) {
static struct codec ret = {
'o', // id
"ogg", // types
4096, // min read
20480, // min space
vorbis_open, // open
vorbis_close, // close
vorbis_decode,// decode
};
v = malloc(sizeof(struct vorbis));
if (!v) {
return NULL;
}
v->vf = NULL;
v->opened = false;
if (!load_vorbis()) {
return NULL;
}
LOG_INFO("using vorbis to decode ogg");
return &ret;
}

View File

@@ -19,9 +19,12 @@
*
*/
#include "platform_esp32.h"
#pragma once
#ifndef QUOTE
#define QUOTE(name) #name
#endif
#define ESP_LOG_DEBUG_EVENT(tag,e) ESP_LOGD(tag,"evt: " e)
void app_main()
{
console_start();
}