943 lines
32 KiB
C++
943 lines
32 KiB
C++
// butt - broadcast using this tool
|
|
//
|
|
// Copyright 2007-2018 by Daniel Noethen.
|
|
//
|
|
// 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 2, 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.
|
|
//
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <signal.h>
|
|
#include <limits.h>
|
|
#ifdef WIN32
|
|
#include <time.h>
|
|
#define IDI_ICON 101
|
|
#endif
|
|
|
|
#ifndef BUILD_CLIENT
|
|
#include <lame/lame.h>
|
|
#include <pthread.h>
|
|
#if !defined(__APPLE__) && !defined(WIN32)
|
|
#include <FL/Fl_File_Icon.H>
|
|
#include <dlfcn.h>
|
|
#endif
|
|
|
|
#if defined(__APPLE__)
|
|
#include <libgen.h> // for dirname
|
|
#include <mach-o/dyld.h> // for _NSGetExecutablePath
|
|
#include <dlfcn.h>
|
|
#include "AskForMicPermission.h"
|
|
#endif
|
|
#endif // #ifndef BUILD_CLIENT
|
|
|
|
#include "config.h"
|
|
#include "gettext.h"
|
|
|
|
#include "butt.h"
|
|
#include "command.h"
|
|
#include "sockfuncs.h"
|
|
#ifndef BUILD_CLIENT
|
|
#include "cfg.h"
|
|
#include "port_audio.h"
|
|
#include "lame_encode.h"
|
|
#include "opus_encode.h"
|
|
#include "flac_encode.h"
|
|
#include "shoutcast.h"
|
|
#include "parseconfig.h"
|
|
#include "vu_meter.h"
|
|
#include "util.h"
|
|
#include "flgui.h"
|
|
#include "fl_funcs.h"
|
|
#include "fl_timer_funcs.h"
|
|
#include "update.h"
|
|
#include "tray_agent.h"
|
|
#ifdef WITH_RADIOCO
|
|
#include "radioco.h"
|
|
#endif
|
|
#ifdef HAVE_LIBFDK_AAC
|
|
#include "aac_encode.h"
|
|
#endif
|
|
|
|
#include "port_midi.h"
|
|
|
|
int g_print_debug_info = 0;
|
|
|
|
bool recording;
|
|
bool connected;
|
|
bool streaming;
|
|
bool disconnect;
|
|
bool try_to_connect;
|
|
|
|
int stream_socket;
|
|
double kbytes_sent;
|
|
double kbytes_written;
|
|
unsigned int record_start_hour;
|
|
|
|
timer_ms_t rec_timer;
|
|
timer_ms_t stream_timer;
|
|
|
|
lame_enc lame_stream;
|
|
lame_enc lame_rec;
|
|
vorbis_enc vorbis_stream;
|
|
vorbis_enc vorbis_rec;
|
|
opus_enc opus_stream;
|
|
opus_enc opus_rec;
|
|
flac_enc flac_stream;
|
|
flac_enc flac_rec;
|
|
|
|
#ifdef HAVE_LIBFDK_AAC
|
|
aac_enc aac_stream;
|
|
aac_enc aac_rec;
|
|
aacEncOpenPtr aacEncOpen_butt;
|
|
aacEncoder_SetParamPtr aacEncoder_SetParam_butt;
|
|
aacEncoder_GetParamPtr aacEncoder_GetParam_butt;
|
|
aacEncEncodePtr aacEncEncode_butt;
|
|
aacEncInfoPtr aacEncInfo_butt;
|
|
aacEncClosePtr aacEncClose_butt;
|
|
#endif // ifdef HAVE_LIBFDK_AAC
|
|
|
|
#endif // ifndef BUILD_CLIENT
|
|
|
|
void print_usage(void)
|
|
{
|
|
#ifndef BUILD_CLIENT
|
|
printf(
|
|
"Usage: butt [-h | -v | -s [name] | -u <song name> | -M <streaming signal threshold> | -m <streaming silence threshold> | -O <recording signal threshold> | -o <recording silence threshold> | -SLdrtqn | -c <config_path>] [-A | -U | -x] [-a <addr>] [-p <port>]\n");
|
|
#else
|
|
printf(
|
|
"Usage: butt-client [-h | -v | -s [name] | -u <song name> | -M <streaming signal threshold> | -m <streaming silence threshold> | -O <recording signal threshold> | -o <recording silence threshold> | -USdrtqn ] [-a <addr>] [-p <port>]\n");
|
|
#endif
|
|
fflush(stdout);
|
|
}
|
|
|
|
#ifndef BUILD_CLIENT
|
|
void load_AAC_lib(void)
|
|
{
|
|
#ifdef WIN32
|
|
// Load aac library
|
|
HMODULE hModule = LoadLibrary(TEXT("libfdk-aac-2.dll"));
|
|
if (hModule != NULL) {
|
|
aacEncOpen_butt = (aacEncOpenPtr)GetProcAddress(hModule, "aacEncOpen");
|
|
aacEncoder_SetParam_butt = (aacEncoder_SetParamPtr)GetProcAddress(hModule, "aacEncoder_SetParam");
|
|
aacEncoder_GetParam_butt = (aacEncoder_GetParamPtr)GetProcAddress(hModule, "aacEncoder_GetParam");
|
|
aacEncEncode_butt = (aacEncEncodePtr)GetProcAddress(hModule, "aacEncEncode");
|
|
aacEncInfo_butt = (aacEncInfoPtr)GetProcAddress(hModule, "aacEncInfo");
|
|
aacEncClose_butt = (aacEncClosePtr)GetProcAddress(hModule, "aacEncClose");
|
|
g_aac_lib_available = 1;
|
|
}
|
|
#endif
|
|
|
|
#if defined(__APPLE__)
|
|
#ifdef HAVE_LIBFDK_AAC
|
|
void *dylib = dlopen("libfdk-aac.2.dylib", RTLD_LAZY);
|
|
|
|
if (dylib == NULL) {
|
|
dylib = dlopen("/Library/Application Support/butt/libfdk-aac.2.dylib", RTLD_LAZY); // New path since 0.1.34
|
|
}
|
|
|
|
if (dylib != NULL) {
|
|
// typedef AACENC_ERROR(WINAPI *aacEncOpenPtr)(HANDLE_AACENCODER*, UINT, UINT);
|
|
|
|
aacEncOpen_butt = (aacEncOpenPtr)dlsym(dylib, "aacEncOpen");
|
|
aacEncoder_SetParam_butt = (aacEncoder_SetParamPtr)dlsym(dylib, "aacEncoder_SetParam");
|
|
aacEncoder_GetParam_butt = (aacEncoder_GetParamPtr)dlsym(dylib, "aacEncoder_GetParam");
|
|
aacEncEncode_butt = (aacEncEncodePtr)dlsym(dylib, "aacEncEncode");
|
|
aacEncInfo_butt = (aacEncInfoPtr)dlsym(dylib, "aacEncInfo");
|
|
aacEncClose_butt = (aacEncClosePtr)dlsym(dylib, "aacEncClose");
|
|
|
|
g_aac_lib_available = 1;
|
|
}
|
|
|
|
// askForMicPermission();
|
|
#endif
|
|
#endif
|
|
|
|
#if !defined(__APPLE__) && !defined(WIN32) // LINUX
|
|
#ifdef HAVE_LIBFDK_AAC
|
|
void *dylib = dlopen("libfdk-aac.so", RTLD_LAZY);
|
|
|
|
if (dylib == NULL) { // Try other lib name
|
|
dylib = dlopen("libfdk-aac.so.2", RTLD_LAZY);
|
|
}
|
|
|
|
if (dylib != NULL) {
|
|
// typedef AACENC_ERROR(WINAPI *aacEncOpenPtr)(HANDLE_AACENCODER*, UINT, UINT);
|
|
|
|
aacEncOpen_butt = (aacEncOpenPtr)dlsym(dylib, "aacEncOpen");
|
|
aacEncoder_SetParam_butt = (aacEncoder_SetParamPtr)dlsym(dylib, "aacEncoder_SetParam");
|
|
aacEncoder_GetParam_butt = (aacEncoder_GetParamPtr)dlsym(dylib, "aacEncoder_GetParam");
|
|
aacEncEncode_butt = (aacEncEncodePtr)dlsym(dylib, "aacEncEncode");
|
|
aacEncInfo_butt = (aacEncInfoPtr)dlsym(dylib, "aacEncInfo");
|
|
aacEncClose_butt = (aacEncClosePtr)dlsym(dylib, "aacEncClose");
|
|
|
|
g_aac_lib_available = 1;
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
int read_cfg(void)
|
|
{
|
|
char *p;
|
|
int shift_pressed;
|
|
|
|
if (Fl::get_key(FL_Shift_L) || Fl::get_key(FL_Shift_R)) {
|
|
shift_pressed = 1;
|
|
}
|
|
else {
|
|
shift_pressed = 0;
|
|
}
|
|
|
|
// Prepare configuration loading
|
|
#ifdef WIN32
|
|
if ((cfg_path == NULL) || (shift_pressed == 1)) {
|
|
if ((p = fl_getenv("APPDATA")) == NULL) {
|
|
// If there is no "%APPDATA% we are probably in none-NT Windows
|
|
// So we save the config file to the programm directory
|
|
cfg_path = (char *)malloc(strlen(CONFIG_FILE) + 1);
|
|
strcpy(cfg_path, CONFIG_FILE);
|
|
}
|
|
else {
|
|
cfg_path = (char *)malloc((PATH_MAX + strlen(CONFIG_FILE) + 1) * sizeof(char));
|
|
snprintf(cfg_path, PATH_MAX + strlen(CONFIG_FILE), "%s\\%s", p, CONFIG_FILE);
|
|
}
|
|
}
|
|
#else // UNIX/MacOS
|
|
if ((cfg_path == NULL) || (shift_pressed == 1)) {
|
|
if ((p = fl_getenv("HOME")) == NULL) {
|
|
fl_alert(_("No home-directory found"));
|
|
return 1;
|
|
}
|
|
else {
|
|
cfg_path = (char *)malloc((PATH_MAX + strlen(CONFIG_FILE) + 1) * sizeof(char));
|
|
snprintf(cfg_path, PATH_MAX + strlen(CONFIG_FILE), "%s/%s", p, CONFIG_FILE);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (shift_pressed) {
|
|
int rc = fl_choice(_("The shift key was held down during startup.\n"
|
|
"Do you want to start butt with a new configuration file?\n"
|
|
"This will overwrite your existing configuration file at\n%s"),
|
|
0, _("Start with old"), _("Start with new"), cfg_path);
|
|
if (rc == 2) {
|
|
if (cfg_create_default()) {
|
|
fl_alert(_("Could not create config %s\nbutt is going to close now"), cfg_path);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
printf(_("Reading config %s\n"), cfg_path);
|
|
|
|
fflush(stdout);
|
|
DEBUG_LOG("Reading config from ");
|
|
DEBUG_LOG(cfg_path);
|
|
|
|
if (cfg_set_values(NULL) != 0) // read config file and initialize config struct
|
|
{
|
|
printf(_("Could not find config %s\n"), cfg_path);
|
|
fflush(stdout);
|
|
if (cfg_create_default()) {
|
|
fl_alert(_("Could not create config %s\nbutt is going to close now"), cfg_path);
|
|
DEBUG_LOG("Could not create default config file");
|
|
return 1;
|
|
}
|
|
printf(_("butt created a default config at\n%s\n"), cfg_path);
|
|
fflush(stdout);
|
|
cfg_set_values(NULL);
|
|
}
|
|
|
|
if (cfg.audio.dev_count == 0) {
|
|
DEBUG_LOG("Could not find input audio devices");
|
|
fl_alert(_(
|
|
"Could not find any audio device with input channels.\nbutt requires at least one audio device with input channels in order to work.\nThis can either be a built-in audio device, an external audio device or a virtual audio device.\n\nbutt is going to close now."));
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void set_locale_from_system(void)
|
|
{
|
|
setlocale(LC_CTYPE, "");
|
|
setlocale(LC_MESSAGES, "");
|
|
|
|
#if defined(__APPLE__)
|
|
|
|
char path_to_executeable[PATH_MAX];
|
|
char path_to_locale_dir[PATH_MAX];
|
|
|
|
uint32_t path_len = (uint32_t)sizeof(path_to_executeable);
|
|
if (_NSGetExecutablePath(path_to_executeable, &path_len) == 0) {
|
|
char *folder_of_executable = strdup(dirname(path_to_executeable));
|
|
snprintf(path_to_locale_dir, PATH_MAX, "%s%s", folder_of_executable, "/../Resources/locale");
|
|
free(folder_of_executable);
|
|
}
|
|
|
|
bindtextdomain(PACKAGE, path_to_locale_dir);
|
|
|
|
#elif defined(WIN32)
|
|
bindtextdomain(PACKAGE, "locale");
|
|
#else // Linux
|
|
char *p;
|
|
if ((p = fl_getenv("APPDIR")) != NULL) { // Set locale dir when launched from .AppImage
|
|
char path_to_locale_dir[PATH_MAX];
|
|
snprintf(path_to_locale_dir, sizeof(path_to_locale_dir), "%s/usr/share/locale", p);
|
|
bindtextdomain(PACKAGE, path_to_locale_dir);
|
|
}
|
|
else { // Set locale dir to dir found by ./configure
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
}
|
|
#endif
|
|
|
|
textdomain(PACKAGE);
|
|
bind_textdomain_codeset(PACKAGE, "UTF-8");
|
|
}
|
|
|
|
void set_locale_from_config(void)
|
|
{
|
|
int lang;
|
|
static char env_str[32];
|
|
|
|
setlocale(LC_CTYPE, "");
|
|
setlocale(LC_MESSAGES, "");
|
|
snprintf(env_str, sizeof(env_str), "LANG=%s", cfg.gui.lang_str);
|
|
putenv(env_str);
|
|
|
|
snprintf(env_str, sizeof(env_str), "LANGUAGE=%s", cfg.gui.lang_str);
|
|
putenv(env_str);
|
|
|
|
#if defined(__APPLE__)
|
|
char path_to_executeable[PATH_MAX];
|
|
char path_to_locale_dir[PATH_MAX];
|
|
|
|
uint32_t path_len = (uint32_t)sizeof(path_to_executeable);
|
|
if (_NSGetExecutablePath(path_to_executeable, &path_len) == 0) {
|
|
char *folder_of_executable = strdup(dirname(path_to_executeable));
|
|
snprintf(path_to_locale_dir, PATH_MAX, "%s%s", folder_of_executable, "/../Resources/locale");
|
|
printf("%s\n", path_to_locale_dir);
|
|
free(folder_of_executable);
|
|
}
|
|
|
|
bindtextdomain(PACKAGE, path_to_locale_dir);
|
|
#elif defined(WIN32)
|
|
bindtextdomain(PACKAGE, "locale");
|
|
#else // Linux
|
|
char *p;
|
|
if ((p = fl_getenv("APPDIR")) != NULL) { // Set locale dir when launched from .AppImage
|
|
char path_to_locale_dir[PATH_MAX];
|
|
snprintf(path_to_locale_dir, sizeof(path_to_locale_dir), "%s/usr/share/locale", p);
|
|
bindtextdomain(PACKAGE, path_to_locale_dir);
|
|
}
|
|
else { // Set locale dir to dir found by ./configure
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
}
|
|
#endif
|
|
|
|
textdomain(PACKAGE);
|
|
bind_textdomain_codeset(PACKAGE, "UTF-8");
|
|
}
|
|
#endif // BUILD_CLIENT
|
|
|
|
int get_threshold_from_args(char opt, char *optarg, command_t *command)
|
|
{
|
|
float threshold = atof(optarg);
|
|
if (threshold < 0) {
|
|
printf(_("Illegal argument: Threshold must be a non-negative number\n"));
|
|
fflush(stdout);
|
|
}
|
|
else {
|
|
switch (opt) {
|
|
case 'M':
|
|
command->cmd = CMD_SET_STREAM_SIGNAL_THRESHOLD;
|
|
break;
|
|
case 'm':
|
|
command->cmd = CMD_SET_STREAM_SILENCE_THRESHOLD;
|
|
break;
|
|
case 'O':
|
|
command->cmd = CMD_SET_RECORD_SIGNAL_THRESHOLD;
|
|
break;
|
|
case 'o':
|
|
command->cmd = CMD_SET_RECORD_SILENCE_THRESHOLD;
|
|
break;
|
|
default:
|
|
printf("Internal error: Illegal value for \"opt\"\n");
|
|
return -1;
|
|
}
|
|
command->param_size = sizeof(float);
|
|
command->param = (float *)malloc(command->param_size);
|
|
*((float *)command->param) = threshold;
|
|
printf(_("%c threshold set to %0.1f\n"), opt, threshold);
|
|
}
|
|
return threshold;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
DEBUG_LOG("Starting BUTT");
|
|
char lcd_buf[33];
|
|
char info_buf[256];
|
|
int opt;
|
|
int port = 1256;
|
|
int search_port = 1;
|
|
int skip_opening_audio_device = 0;
|
|
int server_mode = SERVER_MODE_LOCAL;
|
|
sock_proto_t command_proto = SOCK_PROTO_TCP;
|
|
char server_addr[128];
|
|
uint32_t song_len;
|
|
|
|
srand(time(NULL));
|
|
|
|
#ifndef BUILD_CLIENT
|
|
|
|
cfg_path = NULL;
|
|
// Activate support for multi threading
|
|
Fl::lock();
|
|
#endif
|
|
|
|
command_t command;
|
|
command.cmd = CMD_EMPTY;
|
|
command.param_size = 0;
|
|
command.param = NULL;
|
|
|
|
snprintf(server_addr, sizeof(server_addr), "%s", "127.0.0.1");
|
|
|
|
#ifndef WIN32
|
|
// ignore the SIGPIPE signal. (In case the server closes the connection unexpected)
|
|
signal(SIGPIPE, SIG_IGN);
|
|
#endif
|
|
|
|
#ifndef BUILD_CLIENT
|
|
#ifdef ENABLE_NLS
|
|
DEBUG_LOG("Setting up locale");
|
|
set_locale_from_system();
|
|
#endif
|
|
#endif
|
|
|
|
DEBUG_LOG("Init sockets");
|
|
sock_init();
|
|
|
|
#ifdef BUILD_CLIENT
|
|
if (argc < 2) {
|
|
print_usage();
|
|
}
|
|
#endif
|
|
|
|
// Parse command line parameters
|
|
DEBUG_LOG("Parsing command line parameters");
|
|
#ifndef WIN32
|
|
while ((opt = getopt(argc, argv, ":vhc:AULxs:drtnqu:a:p:SM:m:O:o:j:")) != -1) {
|
|
#else
|
|
while ((opt = getopt(argc, argv, ":vhc:AULxs:drtnqu:a:p:SM:m:O:o:")) != -1) {
|
|
#endif
|
|
switch (opt) {
|
|
#ifndef BUILD_CLIENT
|
|
case 'A':
|
|
server_mode = SERVER_MODE_ALL;
|
|
break;
|
|
case 'x':
|
|
server_mode = SERVER_MODE_OFF;
|
|
break;
|
|
case 'c':
|
|
cfg_path = (char *)malloc((strlen(optarg) + 1) * sizeof(char));
|
|
strcpy(cfg_path, optarg);
|
|
break;
|
|
case 'L':
|
|
snd_print_devices();
|
|
return 0;
|
|
break;
|
|
#endif
|
|
case 'U':
|
|
command_proto = SOCK_PROTO_UDP;
|
|
break;
|
|
case 'p':
|
|
port = atoi(optarg);
|
|
if (port < 1024 || port > 65535) {
|
|
printf(_("Illegal argument: Port must be a number between 1023 and 65535\n"));
|
|
fflush(stdout);
|
|
return 1;
|
|
}
|
|
search_port = 0;
|
|
break;
|
|
case 's':
|
|
if (command.cmd != CMD_EMPTY) {
|
|
printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt);
|
|
break;
|
|
}
|
|
command.cmd = CMD_CONNECT;
|
|
command.param_size = (uint32_t)strlen(optarg) + 1;
|
|
command.param = (char *)malloc(command.param_size);
|
|
sprintf((char *)command.param, "%s", optarg);
|
|
break;
|
|
case 'd':
|
|
if (command.cmd != CMD_EMPTY) {
|
|
printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt);
|
|
break;
|
|
}
|
|
command.cmd = CMD_DISCONNECT;
|
|
break;
|
|
case 'r':
|
|
if (command.cmd != CMD_EMPTY) {
|
|
printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt);
|
|
break;
|
|
}
|
|
command.cmd = CMD_START_RECORDING;
|
|
break;
|
|
case 't':
|
|
if (command.cmd != CMD_EMPTY) {
|
|
printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt);
|
|
break;
|
|
}
|
|
command.cmd = CMD_STOP_RECORDING;
|
|
break;
|
|
case 'n':
|
|
if (command.cmd != CMD_EMPTY) {
|
|
printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt);
|
|
break;
|
|
}
|
|
command.cmd = CMD_SPLIT_RECORDING;
|
|
break;
|
|
case 'q':
|
|
if (command.cmd != CMD_EMPTY) {
|
|
printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt);
|
|
break;
|
|
}
|
|
command.cmd = CMD_QUIT;
|
|
break;
|
|
case 'v':
|
|
printf("%s %s\n", argv[0], VERSION);
|
|
fflush(stdout);
|
|
return 0;
|
|
case 'u':
|
|
if (command.cmd != CMD_EMPTY) {
|
|
printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt);
|
|
break;
|
|
}
|
|
song_len = strlen(optarg) + 1;
|
|
if (song_len > 1000) {
|
|
song_len = 1000;
|
|
}
|
|
|
|
command.cmd = CMD_UPDATE_SONGNAME;
|
|
command.param_size = song_len;
|
|
command.param = (char *)malloc(command.param_size);
|
|
sprintf((char *)command.param, "%s", optarg);
|
|
break;
|
|
case 'a':
|
|
memset(server_addr, 0, sizeof(server_addr));
|
|
snprintf(server_addr, sizeof(server_addr), "%s", optarg);
|
|
break;
|
|
case 'S':
|
|
if (command.cmd != CMD_EMPTY) {
|
|
printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt);
|
|
break;
|
|
}
|
|
command.cmd = CMD_GET_STATUS;
|
|
break;
|
|
case 'M':
|
|
case 'm':
|
|
case 'O':
|
|
case 'o':
|
|
if (command.cmd != CMD_EMPTY) {
|
|
printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), opt);
|
|
break;
|
|
}
|
|
if (get_threshold_from_args(opt, optarg, &command) < 0) {
|
|
return 1;
|
|
}
|
|
break;
|
|
case 'h':
|
|
print_usage();
|
|
printf(_("\nOptions:\n"
|
|
"-h\tPrint this help text\n"
|
|
"-v\tPrint version information\n"));
|
|
#ifndef BUILD_CLIENT
|
|
printf(_("\nOptions for operating mode:\n"
|
|
"-c\tPath to configuration file\n"
|
|
"-L\tPrint available audio devices\n"
|
|
"-A\tCommand server will be accessible from your network/internet (default: localhost only)\n"
|
|
"-U\tCommand server will use UDP instead of TCP\n"
|
|
"-x\tDo not start a command server\n"
|
|
"-p\tPort where the command server shall listen to (default: 1256)\n"));
|
|
#endif
|
|
|
|
printf(_("\nOptions for control mode:\n"
|
|
"-s\tConnect to streaming server\n"
|
|
"-d\tDisconnect from streaming server\n"
|
|
"-r\tStart recording\n"
|
|
"-t\tStop recording\n"
|
|
"-n\tSplit recording\n"
|
|
"-q\tQuit butt\n"
|
|
"-u\tupdate song name\n"
|
|
"-S\tRequest status\n"
|
|
"-M\tSet streaming signal threshold (seconds)\n"
|
|
"-m\tSet streaming silence threshold (seconds)\n"
|
|
"-O\tSet recording signal threshold (seconds)\n"
|
|
"-o\tSet recording silence threshold (seconds)\n"
|
|
"-U\tConnect via UDP instead of TCP\n"
|
|
"-a\tAddress of the butt instance to be controlled (default: 127.0.0.1)\n"
|
|
"-p\tPort of the butt instance to be controlled (default: 1256)\n"));
|
|
fflush(stdout);
|
|
return 0;
|
|
case '?':
|
|
printf(_("Illegal option -%c.\nType butt -h to get a list of supported options.\n"), optopt);
|
|
print_usage();
|
|
return 1;
|
|
case ':':
|
|
if (optopt == 's') { // Handle connect option without argument
|
|
if (command.cmd != CMD_EMPTY) {
|
|
printf(_("Warning: You may only pass one control option. Option -%c has been ignored.\n"), optopt);
|
|
break;
|
|
}
|
|
command.cmd = CMD_CONNECT;
|
|
break;
|
|
}
|
|
printf(_("Option -%c requires an argument\n"), optopt);
|
|
print_usage();
|
|
return 1;
|
|
#ifndef WIN32
|
|
case 'j':
|
|
jack_client_name = (char *)malloc((strlen(optarg) + 1) * sizeof(char));
|
|
strcpy(jack_client_name, optarg);
|
|
break;
|
|
#endif
|
|
default:
|
|
printf(_("Command line parsing failed\n"));
|
|
print_usage();
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Handle commands
|
|
if (command.cmd != CMD_EMPTY) {
|
|
int ret;
|
|
#ifndef BUILD_CLIENT
|
|
if (cfg_path != NULL) {
|
|
free(cfg_path);
|
|
}
|
|
#endif
|
|
|
|
ret = command_send_cmd(command, server_addr, port, command_proto);
|
|
|
|
if (command.param != NULL) {
|
|
free(command.param);
|
|
}
|
|
|
|
switch (ret) {
|
|
case CMD_ERR_CONNECT:
|
|
printf(_("No butt instance running on %s at port %d\n"), server_addr, port);
|
|
fflush(stdout);
|
|
return 1;
|
|
break;
|
|
case CMD_ERR_SEND_CMD:
|
|
printf(_("Error while sending command\n"));
|
|
fflush(stdout);
|
|
return 1;
|
|
break;
|
|
case CMD_ERR_RECV_RESPONSE:
|
|
printf(_("Error: Did not receive response packet\n"));
|
|
fflush(stdout);
|
|
return 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (command.cmd == CMD_GET_STATUS) {
|
|
status_packet_t status_packet;
|
|
|
|
ret = command_recv_status_reply(&status_packet, command_proto);
|
|
if (ret == SOCK_ERR_RECV) {
|
|
printf(_("Error: You may only request one status packet per second\n"));
|
|
return 1;
|
|
}
|
|
if (ret == CMD_ERR_RECV_STATUS) {
|
|
printf(_("Error: Did not receive status packet (UDP server not running?)\n"));
|
|
return 1;
|
|
}
|
|
if (ret < 0) {
|
|
printf(_("Network error while receiving status packet: %d\n"), ret);
|
|
return 1;
|
|
}
|
|
else if (ret == 0) {
|
|
printf(_("Error: Client and server versions do not match\n"));
|
|
return 1;
|
|
}
|
|
else if (ret > 0) {
|
|
int connected = (status_packet.status & (1 << STATUS_CONNECTED)) > 0;
|
|
int connecting = (status_packet.status & (1 << STATUS_CONNECTING)) > 0;
|
|
int recording = (status_packet.status & (1 << STATUS_RECORDING)) > 0;
|
|
int signal = (status_packet.status & (1 << STATUS_SIGNAL_DETECTED)) > 0;
|
|
int silence = (status_packet.status & (1 << STATUS_SILENCE_DETECTED)) > 0;
|
|
int is_extended_packet = (status_packet.status & (1 << STATUS_EXTENDED_PACKET)) > 0;
|
|
|
|
printf(_("connected: %d\nconnecting: %d\nrecording: %d\nsignal present: %d\nsignal absent: %d\n"), connected, connecting, recording, signal,
|
|
silence);
|
|
fflush(stdout);
|
|
|
|
if (is_extended_packet == 1) {
|
|
printf(_("stream seconds: %lu\n"), (unsigned long)status_packet.stream_seconds);
|
|
printf(_("stream kBytes: %lu\n"), (unsigned long)status_packet.stream_kByte);
|
|
printf(_("record seconds: %lu\n"), (unsigned long)status_packet.record_seconds);
|
|
printf(_("record kBytes: %lu\n"), (unsigned long)status_packet.record_kByte);
|
|
printf(_("volume left: %0.1f\n"), status_packet.volume_left / 10.0);
|
|
printf(_("volume right: %0.1f\n"), status_packet.volume_right / 10.0);
|
|
printf(_("song: %s\n"), status_packet.song);
|
|
printf(_("record path: %s\n"), status_packet.rec_path);
|
|
printf(_("listeners: %d\n"), status_packet.listener_count);
|
|
fflush(stdout);
|
|
free(status_packet.song);
|
|
free(status_packet.rec_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
DEBUG_LOG("Parsing done");
|
|
|
|
#ifndef BUILD_CLIENT
|
|
if (Fl::get_key(FL_Control_L) || Fl::get_key(FL_Control_R)) {
|
|
fl_alert(_("The control key was held down during startup.\n"
|
|
"butt will start without opening an audio device.\n"
|
|
"Please select your preferred audio device in settings->audio"));
|
|
|
|
skip_opening_audio_device = 1;
|
|
}
|
|
|
|
DEBUG_LOG("Init audio subsystem");
|
|
if (snd_init() != 0) {
|
|
fl_alert(_("PortAudio init failed\nbutt is going to close now"));
|
|
return 1;
|
|
}
|
|
|
|
DEBUG_LOG("Trying to load AAC ");
|
|
load_AAC_lib();
|
|
|
|
DEBUG_LOG("Reading config");
|
|
if (read_cfg() != 0) {
|
|
DEBUG_LOG("Failed to read config");
|
|
return 1;
|
|
}
|
|
|
|
DEBUG_LOG("Init DSP modules");
|
|
snd_init_dsp();
|
|
|
|
#ifdef ENABLE_NLS
|
|
if (strcmp(cfg.gui.lang_str, "system") != 0) {
|
|
set_locale_from_config();
|
|
}
|
|
#endif
|
|
|
|
#if !defined(__APPLE__) && !defined(WIN32) // LINUX
|
|
Fl_File_Icon::load_system_icons();
|
|
FL_NORMAL_SIZE = 12;
|
|
#endif
|
|
|
|
DEBUG_LOG("Setting up GUI");
|
|
fl_g = new flgui();
|
|
fl_g->window_main->xclass("butt_FLTK");
|
|
|
|
DEBUG_LOG("Showing GUI");
|
|
fl_g->window_main->show();
|
|
fl_font(fl_font(), 10);
|
|
|
|
strcpy(lcd_buf, _("idle"));
|
|
print_lcd(lcd_buf, strlen(lcd_buf), -2, 1);
|
|
fl_g->label_current_listeners->hide();
|
|
|
|
#ifdef WIN32
|
|
DEBUG_LOG("Loading icons");
|
|
fl_g->window_main->icon((char *)LoadIcon(fl_display, MAKEINTRESOURCE(IDI_ICON)));
|
|
// The fltk icon code above only loads the default icon.
|
|
// Here, once the window is shown, we can assign
|
|
// additional icons, just to make things look a bit nicer.
|
|
HANDLE bigicon = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 32, 32, 0);
|
|
SendMessage(fl_xid(fl_g->window_main), WM_SETICON, ICON_BIG, (LPARAM)bigicon);
|
|
HANDLE smallicon = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
|
|
SendMessage(fl_xid(fl_g->window_main), WM_SETICON, ICON_SMALL, (LPARAM)smallicon);
|
|
#endif
|
|
|
|
#ifndef BUILD_CLIENT
|
|
#if defined(__APPLE__)
|
|
DEBUG_LOG("Asking for mic permission");
|
|
askForMicPermission();
|
|
#endif
|
|
#endif
|
|
|
|
lame_stream.gfp = NULL;
|
|
lame_rec.gfp = NULL;
|
|
flac_rec.encoder = NULL;
|
|
flac_stream.encoder = NULL;
|
|
#ifdef HAVE_LIBFDK_AAC
|
|
aac_stream.handle = NULL;
|
|
aac_rec.handle = NULL;
|
|
#endif
|
|
|
|
snprintf(info_buf, sizeof(info_buf),
|
|
_("Starting %s\nWritten by Daniel Nöthen\n"
|
|
"iPhone/iPad client: https://izicast.de\n"
|
|
"Donate: paypal@danielnoethen.de\n"),
|
|
PACKAGE_STRING);
|
|
print_info(info_buf, 0);
|
|
|
|
DEBUG_LOG("Init GUI and audio");
|
|
init_main_gui_and_audio();
|
|
if ((skip_opening_audio_device == 0) && (snd_open_streams() != 0)) {
|
|
DEBUG_LOG("Error opening audio stream");
|
|
DEBUG_LOG("Trying system default audio device");
|
|
cfg.audio.dev_num = 0;
|
|
cfg.audio.dev_name = (char *)realloc(cfg.audio.dev_name, strlen(_("Default PCM device (default)")) + 1);
|
|
strcpy(cfg.audio.dev_name, _("Default PCM device (default)"));
|
|
|
|
cfg.audio.dev2_num = -1;
|
|
cfg.audio.dev2_name = (char *)realloc(cfg.audio.dev2_name, strlen(_("None")) + 1);
|
|
strcpy(cfg.audio.dev2_name, _("None"));
|
|
|
|
if (snd_open_streams() != 0) {
|
|
fl_alert(_("Could not open audio device.\nPlease select your preferred audio device in settings->audio"));
|
|
}
|
|
else {
|
|
fl_alert(_("butt could not open previously used audio device.\nThe system default audio device will be used.\n"));
|
|
}
|
|
}
|
|
vu_init();
|
|
|
|
DEBUG_LOG("Initializing timers");
|
|
Fl::add_timeout(5, &display_rotate_timer);
|
|
Fl::add_timeout(0.25, &cmd_timer);
|
|
Fl::add_timeout(0, &rotate_sponsor_logo_timer);
|
|
|
|
if (cfg.main.connect_at_startup) {
|
|
button_connect_cb();
|
|
}
|
|
else if (cfg.main.signal_detection == 1 && cfg.main.signal_threshold > 0) {
|
|
Fl::add_timeout(1, &stream_signal_timer);
|
|
}
|
|
|
|
if (cfg.rec.rec_after_launch && !recording) {
|
|
button_record_cb(false);
|
|
}
|
|
else if (cfg.rec.signal_detection == 1 && cfg.rec.signal_threshold > 0) {
|
|
Fl::add_timeout(1, &record_signal_timer);
|
|
}
|
|
|
|
if (server_mode != SERVER_MODE_OFF) {
|
|
int command_port = command_start_server(port, search_port, server_mode, command_proto);
|
|
if (command_port > 0) {
|
|
snprintf(info_buf, sizeof(info_buf), _("Command server listening on port %d\n"), command_port);
|
|
printf("%s", info_buf);
|
|
fflush(stdout);
|
|
// print_info(info_buf, 0);
|
|
}
|
|
else {
|
|
printf("ret command_start_server: %d\n", command_port);
|
|
snprintf(info_buf, sizeof(info_buf), _("Warning: could not start command server on port %d\n"), port);
|
|
printf("%s", info_buf);
|
|
fflush(stdout);
|
|
print_info(info_buf, 0);
|
|
}
|
|
}
|
|
|
|
DEBUG_LOG("Checking for available updates");
|
|
if (cfg.main.check_for_update) {
|
|
int rc;
|
|
char uri[100];
|
|
char *new_version;
|
|
int ret = update_check_for_new_version();
|
|
|
|
if (ret == UPDATE_NEW_VERSION) {
|
|
new_version = update_get_version();
|
|
rc = fl_choice(_("New version available: %s\nYou have version %s"), _("Don't ask again"), _("Cancel"), _("Get new version"), new_version, VERSION);
|
|
if (rc == 0) {
|
|
cfg.main.check_for_update = 0;
|
|
}
|
|
if (rc == 2) {
|
|
snprintf(uri, sizeof(uri) - 1, "https://danielnoethen.de/butt/index.html#_download");
|
|
fl_open_uri(uri);
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG_LOG("Init MIDI module");
|
|
PmError midi_result = midi_init();
|
|
if (midi_result == 0) {
|
|
if (strcmp(cfg.midi.dev_name, "Disabled") != 0) {
|
|
if (midi_open_device(cfg.midi.dev_name) == 0) {
|
|
midi_start_polling();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
snprintf(info_buf, sizeof(info_buf), _("Could not initialize PortMidi: %s"), Pm_GetErrorText(midi_result));
|
|
print_info(info_buf, 1);
|
|
}
|
|
|
|
DEBUG_LOG("Fill widgets with config data");
|
|
fill_cfg_widgets();
|
|
|
|
#ifdef WIN32
|
|
if (cfg.main.minimize_to_tray == 1) {
|
|
fl_g->window_main->minimize_to_tray = true;
|
|
}
|
|
|
|
if (cfg.main.start_agent == 1) {
|
|
if ((tray_agent_start() == 0) && (cfg.gui.start_minimized == 0)) {
|
|
tray_agent_send_cmd(TA_START);
|
|
}
|
|
}
|
|
|
|
#else
|
|
fl_g->group_agent->deactivate();
|
|
#endif
|
|
|
|
#ifndef WITH_RADIOCO
|
|
fl_g->radio_add_srv_radioco->hide();
|
|
#endif
|
|
|
|
#ifndef HAVE_LIBDATACHANNEL
|
|
fl_g->radio_add_srv_webrtc->deactivate();
|
|
fl_g->radio_add_srv_webrtc->tooltip(_("butt was built without WebRTC support"));
|
|
#endif
|
|
|
|
if (cfg.gui.start_minimized == 1) {
|
|
DEBUG_LOG("Starting minimized");
|
|
fl_g->window_main->iconize();
|
|
}
|
|
|
|
// Make sure input field callbacks are called when the Enter key is pressed or when the field is released.
|
|
// Unfortunately this can not be configured in fluid, so we have to do it here.
|
|
fl_g->input_gui_vu_mid_range_start->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_gui_vu_high_range_start->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_cfg_silence->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_cfg_signal->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_rec_signal->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_rec_silence->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_gui_listeners_update_rate->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_rec_filename->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_stream_mp3_lowpass_freq->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_stream_mp3_lowpass_width->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_stream_mp3_highpass_freq->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_stream_mp3_highpass_width->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_rec_mp3_lowpass_freq->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_rec_mp3_lowpass_width->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_rec_mp3_highpass_freq->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
fl_g->input_rec_mp3_highpass_width->when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE);
|
|
|
|
DEBUG_LOG("Launching final event handler");
|
|
return GUI_LOOP();
|
|
|
|
#endif // BUILD_CLIENT
|
|
}
|