sintonia/library/ecasound/ecatools/ecaplay.c
Paul Baranowski 6a39e4f5f5 Removed the Debian directory.
Renamed directory "dev" to "dev_tools".

Replaced the ecasound-2.7.2 with a new download of ecasound.  The reason is
that the previous ecasound directory had all the Makefiles checked in with
hardcoded paths from Naomi's computer.  This prevented anyone else from
being able to build.  I copied over the modified version of ecacontrol.py.
2011-03-09 18:20:34 -05:00

757 lines
19 KiB
C

/**
* ecaplay.c: A simple command-line tool for playing audio files.
*
* Copyright (C) 1999-2002,2004-2006 Kai Vehmanen
*
* 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 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/**
* TODO:
* - show playlist length during runtime
* - random start switch (both for cmdline and playlist modes)
* - write some notes about locking issues
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h> /* ANSI-C: malloc(), free() */
#include <string.h> /* ANSI-C: strlen(), strncmp() */
#include <unistd.h> /* POSIX: ... */
#include <signal.h> /* POSIX: sigaction() */
#include <sys/stat.h> /* POSIX: mkdir() */
#include <sys/types.h> /* POSIX: mkdir() */
#include <ecasoundc.h>
/**
* Function declarations
*/
int main(int argc, char *argv[]);
static void add_input_to_chainsetup(eci_handle_t eci, const char* nextrack);
static int flush_tracks(void);
static const char* get_next_track(int *tracknum, int argc, char *argv[], eci_handle_t *eci);
static char* get_playlist_path(void);
static const char* get_track_cmdline(int n, int argc, char *argv[]);
static const char* get_track_playlist(int* next_track);
static void initialize_chainsetup_for_playback(eci_handle_t* eci, const char* nexttrack, int tracknum);
static void initialize_check_output(eci_handle_t* eci);
static int list_tracks(void);
static int play_tracks(int argc, char *argv[]);
static void print_usage(FILE* stream);
static int process_option(const char* option);
static int queue_tracks(int argc, char *argv[]);
static int set_audio_format(eci_handle_t* eci, const char* fmt);
static void setup_signal_handling(void);
static void signal_handler(int signum);
/**
* Definitions and options
*/
#define ECAPLAY_AFMT_MAXLEN 64
#define ECAPLAY_EIAM_LOGLEVEL 256
#define ECAPLAY_TIMEOUT 3
#define ECAPLAY_MODE_NORMAL 0
#define ECAPLAY_MODE_PL_FLUSH 1
#define ECAPLAY_MODE_PL_LIST 2
#define ECAPLAY_MODE_PL_PLAY 3
#define ECAPLAY_MODE_PL_QUEUE 4
#define ECAPLAY_PLAYLIST_BASEDIR ".ecasound"
#define ECAPLAY_PLAYLIST_FILE "ecaplay_queue"
/**
* Global variables
*/
static const char* ecaplay_version = "20061206-45"; /* ecaplay version */
static char ecaplay_next[PATH_MAX]; /* file to play next */
static char ecaplay_audio_format[ECAPLAY_AFMT_MAXLEN]; /* audio format to use */
static const char* ecaplay_output = NULL; /* output device to use */
static int ecaplay_debuglevel = ECAPLAY_EIAM_LOGLEVEL; /* debug level to use */
static int ecaplay_skip = 0; /* how many playlist items to skip */
static int ecaplay_mode = ECAPLAY_MODE_NORMAL; /* playlist mode */
/* FIX: static int ecaplay_list_len = -1; playlist length */
static int ecaplay_initialized = 0; /* playlist mode */
static sig_atomic_t ecaplay_skip_flag = 0; /* signal flag for ctrl-c */
/**
* Function definitions
*/
int main(int argc, char *argv[])
{
int i, res = 0;
/* get the default output device */
ecaplay_output = getenv("ECAPLAY_OUTPUT_DEVICE");
/* process command-line arguments */
for(i = 1; i < argc; i++) { res += process_option(argv[i]); }
if (res == 0) {
switch(ecaplay_mode) {
case ECAPLAY_MODE_PL_FLUSH:
res = flush_tracks();
break;
case ECAPLAY_MODE_PL_LIST:
res = list_tracks();
break;
case ECAPLAY_MODE_PL_QUEUE:
res = queue_tracks(argc, argv);
break;
case ECAPLAY_MODE_NORMAL:
case ECAPLAY_MODE_PL_PLAY:
res = play_tracks(argc, argv);
break;
default:
assert(0);
}
}
if (res != 0) {
fprintf(stderr, "(ecaplay) Errors encountered, return code is %d.\n", res);
}
return res;
}
/**
* Adds input 'nexttrack' to currently selected chainsetup
* of 'eci'. Sets the global variable 'ecaplay_audio_format'.
*/
static void add_input_to_chainsetup(eci_handle_t eci, const char* nexttrack)
{
size_t len = strlen("ai-add '") + strlen(nexttrack) + strlen("'") + 1;
char* tmpbuf = malloc(len);
assert(tmpbuf != NULL);
snprintf(tmpbuf, len, "ai-add \"%s\"", nexttrack);
eci_command_r(eci, tmpbuf);
/* check that add succeeded */
eci_command_r(eci, "ai-list");
if (eci_last_string_list_count_r(eci) != 1) {
fprintf(stderr, "(ecaplay) Warning! Failed to add input '%s'.\n", nexttrack);
}
/* we must connect to get correct input format */
eci_command_r(eci, "ao-add null");
eci_command_r(eci, "cs-connect");
eci_command_r(eci, "ai-iselect 1");
eci_command_r(eci, "ai-get-format");
strncpy(ecaplay_audio_format,
eci_last_string_r(eci),
ECAPLAY_AFMT_MAXLEN);
ecaplay_audio_format[ECAPLAY_AFMT_MAXLEN - 1] = 0;
/* disconnect and remove the null output */
eci_command_r(eci, "cs-disconnect");
eci_command_r(eci, "ao-iselect 1");
eci_command_r(eci, "ao-remove");
free(tmpbuf);
}
/**
* Flushes the playlist contents.
*
* @return zero on success, non-zero otherwise
*/
static int flush_tracks(void)
{
char *path = get_playlist_path();
if (truncate(path, 0) != 0) {
printf("(ecaplay) Unable to flush playlist '%s'.\n", path);
return -1;
}
return 0;
}
/**
* Checks that current chainsetup has exactly one
* output.
*/
static void initialize_check_output(eci_handle_t* eci)
{
eci_command_r(eci, "ao-list");
if (eci_last_string_list_count_r(eci) != 1) {
fprintf(stderr, "(ecaplay) Warning! Failed to add output device.\n");
}
else {
static int once = 1;
if (once) {
eci_command_r(eci, "ao-iselect 1");
eci_command_r(eci, "ao-describe");
char *tmpstr = (char*)eci_last_string_r(eci);
/* skip the "-x:" prefix where x is one of [io] */
while(*tmpstr && *tmpstr++ != ':')
;
printf("(ecaplay) Output device: '%s'\n", tmpstr);
once = 0;
}
}
}
static void initialize_chainsetup_for_playback(eci_handle_t* eci, const char* nexttrack, int tracknum)
{
const char* ret = NULL;
*eci = eci_init_r();
ecaplay_initialized = 1;
if (ecaplay_debuglevel != -1) {
char tmpbuf[32];
snprintf(tmpbuf, 32, "debug %d", ecaplay_debuglevel);
eci_command_r(*eci, tmpbuf);
}
eci_command_r(*eci, "cs-add ecaplay_chainsetup");
/* check that add succeeded */
eci_command_r(*eci, "cs-list");
if (eci_last_string_list_count_r(*eci) != 2) {
fprintf(stderr, "(ecaplay) Warning! Failed to add a new chainsetup.\n");
}
/* as this is a new chainsetup, we can assume that
* adding chains succeeds */
eci_command_r(*eci, "c-add ecaplay_chain");
add_input_to_chainsetup(*eci, nexttrack);
set_audio_format(*eci, ecaplay_audio_format);
if (ecaplay_output == NULL) {
eci_command_r(*eci, "ao-add-default");
/* check that add succeeded */
initialize_check_output(*eci);
}
else {
int len = strlen("ao-add ") + strlen(ecaplay_output) + 1;
char* tmpbuf = (char*)malloc(len);
snprintf(tmpbuf, len, "ao-add %s", ecaplay_output);
eci_command_r(*eci, tmpbuf);
initialize_check_output(*eci);
free(tmpbuf);
}
/* FIXME: add detection of consecutive errors */
eci_command_r(*eci, "cs-connect");
if (eci_error_r(*eci)) {
fprintf(stderr, "(ecaplay) Unable to play file '%s':\n%s\n", nexttrack, eci_last_error_r(*eci));
}
else {
eci_command_r(*eci, "cs-connected");
ret = eci_last_string_r(*eci);
if (strncmp(ret, "ecaplay_chainsetup", strlen("ecaplay_chainsetup")) != 0) {
fprintf(stderr, "(ecaplay) Error while playing file '%s' . Skipping...\n", nexttrack);
}
else {
/* note: audio format set separately for each input file */
printf("(ecaplay) Playing %d: '%s' (%s).\n", tracknum, nexttrack, ecaplay_audio_format);
eci_command_r(*eci, "start");
}
}
}
static const char* get_next_track(int *tracknum, int argc, char *argv[], eci_handle_t *eci)
{
const char *nexttrack = NULL;
if (ecaplay_mode == ECAPLAY_MODE_PL_PLAY)
nexttrack = get_track_playlist(tracknum);
else
nexttrack = get_track_cmdline(*tracknum, argc, argv);
if (nexttrack != NULL) {
/* queue nexttrack for playing */
if (ecaplay_initialized) {
eci_cleanup_r(*eci);
}
initialize_chainsetup_for_playback(eci, nexttrack, *tracknum);
}
else {
/* reached end of playlist */
if (ecaplay_mode != ECAPLAY_MODE_PL_PLAY) {
/* normal mode; end processing after all files played */
/* printf("(ecaplay) No more files...\n"); */
assert(nexttrack == NULL);
}
else {
/* if in playlist mode, loop from beginning */
*tracknum = 1;
/* FIXME: if in playlist mode; query the current lenght of
* playlist and set 'tracknum = (tracknum % pllen)' */
if (ecaplay_mode == ECAPLAY_MODE_PL_PLAY)
nexttrack = get_track_playlist(tracknum);
else
nexttrack = get_track_cmdline(*tracknum, argc, argv);
/* printf("(ecaplay) Looping back to start of playlist...(%s)\n", nexttrack); */
if (nexttrack != NULL) {
/* queue nexttrack for playing */
if (ecaplay_initialized) {
eci_cleanup_r(*eci);
}
initialize_chainsetup_for_playback(eci, nexttrack, *tracknum);
}
else {
/* get_next_track() failed two times, stopping processing */
assert(nexttrack == NULL);
}
}
}
return nexttrack;
}
/**
* Returns the track number 'n' from the list
* given in argc and argv.
*
* @return track name or NULL on error
*/
static const char* get_track_cmdline(int n, int argc, char *argv[])
{
int i, c = 0;
assert(n > 0 && n <= argc);
for(i = 1; i < argc; i++) {
/* FIXME: add support for '-- -foo.wav' */
if (argv[i][0] != '-') {
if (++c == n) {
return argv[i];
}
}
}
return NULL;
}
/**
* Returns a string containing the full path to the
* playlist file. Ownership of the string is transfered
* to the caller (i.e. it must be free()'ed).
*
* @return full pathname or NULL if error has occured
*/
static char* get_playlist_path(void)
{
char *path = malloc(PATH_MAX);
struct stat statbuf;
/* create pathname based on HOME */
strncpy(path, getenv("HOME"), PATH_MAX);
strncat(path, "/" ECAPLAY_PLAYLIST_BASEDIR, PATH_MAX - strlen(path) - 1);
/* make sure basedir exists */
if (stat(path, &statbuf) != 0) {
printf("(ecaplay) Creating directory %s.\n", path);
mkdir(path, 0700);
}
else {
if (!S_ISDIR(statbuf.st_mode)) {
/* error, basedir exists but is not a directory */
free(path);
path = NULL;
}
}
if (path != NULL) {
/* add filename to basedir */
strncat(path, "/" ECAPLAY_PLAYLIST_FILE, PATH_MAX - strlen(path) - 1);
}
return path;
}
/**
* Returns the track from playlist matching number 'next_track'.
*
* In case 'next_track' is larger than the playlist length,
* track 'next_track mod playlist_len' will be selected, and
* the modified playlist item number stored to 'next_track'.
*
* Note: modifies global variable 'ecaplay_next'.
*
* @return track name or NULL on error
*/
static const char* get_track_playlist(int* next_track)
{
const char *res = NULL;
char *path;
FILE *f1;
int next = *next_track;
assert(next > 0);
path = get_playlist_path();
if (path == NULL) {
return path;
}
f1 = fopen(path, "rb");
if (f1 != NULL) {
int c, w, cur_item = 1;
/* iterate through all data octet at a time */
for(w = 0;;) {
c = fgetc(f1);
if (c == EOF) {
if (next > cur_item) {
/* next_track beyond playlist length, reset to valid track number */
next = next % cur_item;
*next_track = next;
/* seek back to start and look again */
fseek(f1, 0, SEEK_SET);
cur_item = 1;
w = 0;
continue;
}
break;
}
if (cur_item == next) {
if (c == '\n') {
ecaplay_next[w] = 0;
res = ecaplay_next;
break;
}
else {
ecaplay_next[w] = c;
}
++w;
}
if (c == '\n') {
++cur_item;
}
}
/* close the file and return results */
fclose(f1);
}
free(path);
return res;
}
/**
* Lists tracks on the playlist.
*
* @return zero on success, non-zero otherwise
*/
static int list_tracks(void)
{
FILE *f1;
char *path = get_playlist_path();
f1 = fopen(path, "rb");
if (f1 != NULL) {
int c;
while((c = fgetc(f1)) != EOF) {
printf("%c", c);
}
fclose(f1);
return 0;
}
return -1;
}
/**
* Play tracks using the Ecasound engine via the
* ECI interface.
*
* Depending on the mode, tracks are selected either
* from the command-line or from the playlist.
*/
static int play_tracks(int argc, char *argv[])
{
eci_handle_t eci = NULL;
int tracknum = 1, stop = 0;
const char* nexttrack = NULL;
assert(ecaplay_mode == ECAPLAY_MODE_NORMAL ||
ecaplay_mode == ECAPLAY_MODE_PL_PLAY);
tracknum += ecaplay_skip;
nexttrack = get_next_track(&tracknum, argc, argv, &eci);
if (nexttrack != NULL) {
setup_signal_handling();
while(nexttrack != NULL) {
unsigned int timeleft = ECAPLAY_TIMEOUT;
while(timeleft > 0) {
timeleft = sleep(timeleft);
if (timeleft > 0 && ecaplay_skip_flag > 1) {
fprintf(stderr, "\n(ecaplay) Interrupted, exiting...\n");
eci_cleanup_r(eci);
stop = 1;
break;
}
}
/* see above while() loop */
if (stop) break;
if (ecaplay_skip_flag == 0) {
eci_command_r(eci, "engine-status");
}
else {
printf("(ecaplay) Skipping...\n");
}
if (ecaplay_skip_flag != 0 || strcmp(eci_last_string_r(eci), "running") != 0) {
ecaplay_skip_flag = 0;
++tracknum;
nexttrack = get_next_track(&tracknum, argc, argv, &eci);
/* printf("Next track is %s.\n", nexttrack); */
}
}
fprintf(stderr, "exiting...\n");
/* see while() loop above */
if (stop == 0) {
eci_cleanup_r(eci);
}
}
return 0;
}
static void print_usage(FILE* stream)
{
fprintf(stream, "Ecaplay v%s (%s)\n\n", ecaplay_version, VERSION);
fprintf(stream, "Copyright (C) 1997-2005 Kai Vehmanen, released under GPL licence \n");
fprintf(stream, "Ecaplay comes with ABSOLUTELY NO WARRANTY.\n");
fprintf(stream, "You may redistribute copies of ecasound under the terms of the GNU\n");
fprintf(stream, "General Public License. For more information about these matters, see\n");
fprintf(stream, "the file named COPYING.\n");
fprintf(stream, "\nUSAGE: ecaplay [-dfhklopq] [ file1 file2 ... fileN ]\n\n");
fprintf(stream, "See ecaplay(1) man page for more details.\n");
}
static int process_option(const char* option)
{
if (option[0] == '-') {
if (strncmp("--help", option, sizeof("--help")) == 0 ||
strncmp("--version", option, sizeof("--version")) == 0) {
print_usage(stdout);
return 0;
}
switch(option[1])
{
case 'd':
{
const char* level = &option[3];
if (option[2] != 0 && option[3] != 0) {
ecaplay_debuglevel |= atoi(level);
printf("(ecaplay) Setting log level to %d.\n", ecaplay_debuglevel);
}
break;
}
case 'f':
{
ecaplay_mode = ECAPLAY_MODE_PL_FLUSH;
printf("(ecaplay) Flushing playlist.\n");
break;
}
case 'h':
{
print_usage(stdout);
return 0;
}
case 'k':
{
const char* skip = &option[3];
if (option[2] != 0 && option[3] != 0) {
ecaplay_skip = atoi(skip);
printf("(ecaplay) Skipping the first %d files..\n", ecaplay_skip);
}
break;
}
case 'l':
{
ecaplay_mode = ECAPLAY_MODE_PL_LIST;
/* printf("(ecaplay) Listing playlist contents.\n"); */
break;
}
case 'o':
{
const char* output = &option[3];
if (option[2] != 0 && option[3] != 0) {
ecaplay_output = output;
/* printf("(ecaplay) Output device: '%s'\n", ecaplay_output); */
}
break;
}
case 'p':
{
ecaplay_mode = ECAPLAY_MODE_PL_PLAY;
printf("(ecaplay) Playlist mode selected (file: %s).\n",
"~/" ECAPLAY_PLAYLIST_BASEDIR "/" ECAPLAY_PLAYLIST_FILE);
break;
}
case 'q':
{
ecaplay_mode = ECAPLAY_MODE_PL_QUEUE;
printf("(ecaplay) Queuing tracks to playlist.\n");
break;
}
default:
{
fprintf(stderr, "(ecaplay) Error! Unknown option '%s'.\n", option);
print_usage(stderr);
return 1;
}
}
}
return 0;
}
static int queue_tracks(int argc, char *argv[])
{
int i, res = 0;
char *path;
FILE *f1;
path = get_playlist_path();
/* path maybe NULL but fopen can handle it */
f1 = fopen(path, "a+b");
if (f1 != NULL) {
for(i = 1; i < argc; i++) {
char c = argv[i][0];
/* printf("(ecaplay) processing arg '%s' (%c).\n", argv[i], c); */
/* FIXME: add support for '-- -foo.wav' */
if (c != '-') {
/* printf("(ecaplay) 2:processing arg '%s' (%c).\n", argv[i], c); */
if (c != '/') {
/* reserve extra room for '/' */
char* tmp = malloc(PATH_MAX + strlen(argv[i]) + 1);
if (getcwd(tmp, PATH_MAX) != NULL) {
strcat(tmp, "/");
strcat(tmp, argv[i]);
printf("(ecaplay) Track '%s' added to playlist.\n", argv[i]);
fwrite(tmp, 1, strlen(tmp), f1);
}
free(tmp);
}
else {
printf("(ecaplay) Track '%s' added to playlist.\n", argv[i]);
fwrite(argv[i], 1, strlen(argv[i]), f1);
}
fwrite("\n", 1, 1, f1);
}
}
fclose(f1);
}
else {
res = -1;
}
free(path); /* can be NULL */
return res;
}
/**
* Sets the chainsetup audio format to 'fmt'.
*
* @return zero on success, non-zero on error
*/
int set_audio_format(eci_handle_t* eci, const char* fmt)
{
size_t len = strlen("cs-set-audio-format -f:") + strlen(fmt) + 1;
char* tmpbuf = malloc(len);
int res = 0;
strcpy(tmpbuf, "cs-set-audio-format ");
strcat(tmpbuf, fmt);
tmpbuf[len - 1] = 0;
eci_command_r(eci, tmpbuf);
if (eci_error_r(eci)) {
fprintf(stderr, "(ecaplay) Unknown audio format encountered.\n");
res = -1;
}
free(tmpbuf);
return res;
}
static void setup_signal_handling(void)
{
struct sigaction es_handler_int;
struct sigaction ign_handler;
es_handler_int.sa_handler = signal_handler;
sigemptyset(&es_handler_int.sa_mask);
es_handler_int.sa_flags = 0;
ign_handler.sa_handler = SIG_IGN;
sigemptyset(&ign_handler.sa_mask);
ign_handler.sa_flags = 0;
/* handle the follwing signals explicitly */
sigaction(SIGINT, &es_handler_int, 0);
/* ignore the following signals */
sigaction(SIGPIPE, &ign_handler, 0);
sigaction(SIGFPE, &ign_handler, 0);
}
static void signal_handler(int signum)
{
++ecaplay_skip_flag;
}