ref: 22b6b7d61db4e1e79ce2e4513fa3912048bc67ff
dir: /src/soxio.c/
#include "sox_i.h" #include <ctype.h> #include <errno.h> #include <fcntl.h> #include <math.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #ifdef HAVE_IO_H #include <io.h> #endif #ifdef HAVE_LIBLTDL #include <ltdl.h> #endif static void output_message(unsigned level, const char *filename, const char *fmt, va_list ap); sox_globals_t sox_globals = {2, 8192, NULL, NULL, output_message, NULL}; static void output_message(unsigned level, const char *filename, const char *fmt, va_list ap) { if (sox_globals.verbosity >= level) { sox_output_message(stderr, filename, fmt, ap); fprintf(stderr, "\n"); } } /* Plugins */ #ifdef HAVE_LIBLTDL static sox_bool plugins_initted = sox_false; #endif /* FIXME: Use vasprintf */ #ifdef HAVE_LIBLTDL #define MAX_NAME_LEN 1024 static int init_format(const char *file, lt_ptr data) { lt_dlhandle lth = lt_dlopenext(file); const char *end = file + strlen(file); const char prefix[] = "libsox_fmt_"; char fnname[MAX_NAME_LEN]; char *start = strstr(file, prefix); (void)data; if (start && (start += sizeof(prefix) - 1) < end) { int ret = snprintf(fnname, MAX_NAME_LEN, "sox_%.*s_format_fn", end - start, start); if (ret > 0 && ret < MAX_NAME_LEN) { sox_format_fns[sox_formats].fn = (sox_format_fn_t)lt_dlsym(lth, fnname); sox_debug("opening format plugin `%s': library %p, entry point %p\n", fnname, (void *)lth, (void *)sox_format_fns[sox_formats].fn); if (sox_format_fns[sox_formats].fn) sox_formats++; } } return 0; } #endif /* * Initialize list of known format handlers. */ int sox_format_init(void) { #ifdef HAVE_LIBLTDL int ret; if ((ret = lt_dlinit()) != 0) { sox_fail("lt_dlinit failed with %d error(s): %s", ret, lt_dlerror()); return SOX_EOF; } plugins_initted = sox_true; lt_dlforeachfile(PKGLIBDIR, init_format, NULL); #endif return SOX_SUCCESS; } /* * Check that we have a known format suffix string. */ int sox_gettype(sox_format_t * ft, sox_bool is_file_extension) { if (!ft->filetype) sox_fail_errno(ft, SOX_EFMT, "unknown file type"); else { ft->handler = sox_find_format(ft->filetype, is_file_extension); if (ft->handler) return SOX_SUCCESS; sox_fail_errno(ft, SOX_EFMT, "unknown file type `%s'", ft->filetype); } return SOX_EFMT; } /* * Cleanup things. */ void sox_format_quit(void) { #ifdef HAVE_LIBLTDL { int ret; if (plugins_initted && (ret = lt_dlexit()) != 0) { sox_fail("lt_dlexit failed with %d error(s): %s", ret, lt_dlerror()); } } #endif } void set_signal_defaults(sox_signalinfo_t * signal) { if (!signal->rate ) signal->rate = SOX_DEFAULT_RATE; if (!signal->size ) signal->size = SOX_DEFAULT_SIZE; if (!signal->channels) signal->channels = SOX_DEFAULT_CHANNELS; } void set_endianness_if_not_already_set(sox_format_t * ft) { if (ft->signal.reverse_bytes == SOX_OPTION_DEFAULT) { if (ft->handler->flags & SOX_FILE_ENDIAN) { /* Set revere_bytes if we are running on opposite endian * machine compared to file format. */ if (ft->handler->flags & SOX_FILE_ENDBIG) ft->signal.reverse_bytes = SOX_IS_LITTLEENDIAN; else ft->signal.reverse_bytes = SOX_IS_BIGENDIAN; } else ft->signal.reverse_bytes = SOX_OPTION_NO; } if (ft->signal.reverse_bits == SOX_OPTION_DEFAULT) ft->signal.reverse_bits = !!(ft->handler->flags & SOX_FILE_BIT_REV); else if (ft->signal.reverse_bits != !!(ft->handler->flags & SOX_FILE_BIT_REV)) sox_report("'%s': Format options overriding file-type bit-order", ft->filename); if (ft->signal.reverse_nibbles == SOX_OPTION_DEFAULT) ft->signal.reverse_nibbles = !!(ft->handler->flags & SOX_FILE_NIB_REV); else if (ft->signal.reverse_nibbles != !!(ft->handler->flags & SOX_FILE_NIB_REV)) sox_report("'%s': Format options overriding file-type nibble-order", ft->filename); } static int is_seekable(sox_format_t * ft) { struct stat st; fstat(fileno(ft->fp), &st); return ((st.st_mode & S_IFMT) == S_IFREG); } /* check that all settings have been given */ static int sox_checkformat(sox_format_t * ft) { ft->sox_errno = SOX_SUCCESS; if (ft->signal.rate == 0) { sox_fail_errno(ft,SOX_EFMT,"sampling rate was not specified"); return SOX_EOF; } if (!ft->signal.size) { sox_fail_errno(ft,SOX_EFMT,"data size was not specified"); return SOX_EOF; } if (ft->signal.encoding == SOX_ENCODING_UNKNOWN) { sox_fail_errno(ft,SOX_EFMT,"data encoding was not specified"); return SOX_EOF; } if (ft->signal.size > SOX_INFO_SIZE_MAX) { sox_fail_errno(ft,SOX_EFMT,"data size %u is invalid", ft->signal.size); return SOX_EOF; } if (ft->signal.encoding <= 0 || ft->signal.encoding >= SOX_ENCODINGS) { sox_fail_errno(ft,SOX_EFMT,"data encoding %d is invalid", ft->signal.encoding); return SOX_EOF; } return SOX_SUCCESS; } sox_format_t * sox_open_read(const char *path, const sox_signalinfo_t *info, const char *filetype) { sox_format_t * ft = xcalloc(sizeof(*ft), 1); ft->signal.reverse_bytes = ft->signal.reverse_nibbles = ft->signal.reverse_bits = SOX_OPTION_DEFAULT; ft->filename = xstrdup(path); /* Let auto type do the work if user is not overriding. */ if (!filetype) ft->filetype = xstrdup("auto"); else ft->filetype = xstrdup(filetype); ft->mode = 'r'; if (sox_gettype(ft, sox_false) != SOX_SUCCESS) { sox_fail("Can't open input file `%s': %s", ft->filename, ft->sox_errstr); goto input_error; } ft->signal.size = 0; ft->signal.encoding = SOX_ENCODING_UNKNOWN; ft->signal.channels = 0; ft->signal.compression = HUGE_VAL; if (info) ft->signal = *info; if (!(ft->handler->flags & SOX_FILE_NOSTDIO)) { /* Open file handler based on input name. Used stdin file handler * if the filename is "-" */ if (!strcmp(ft->filename, "-")) { if (sox_globals.stdin_in_use_by) { sox_fail("'-' (stdin) already in use by '%s'", sox_globals.stdin_in_use_by); goto input_error; } sox_globals.stdin_in_use_by = "audio input"; SET_BINARY_MODE(stdin); ft->fp = stdin; } else if ((ft->fp = xfopen(ft->filename, "rb")) == NULL) { sox_fail("Can't open input file `%s': %s", ft->filename, strerror(errno)); goto input_error; } /* See if this file is seekable or not */ ft->seekable = is_seekable(ft); } if (filetype) set_endianness_if_not_already_set(ft); /* Read and write starters can change their formats. */ if (ft->handler->startread && (*ft->handler->startread)(ft) != SOX_SUCCESS) { sox_fail("Can't open input file `%s': %s", ft->filename, ft->sox_errstr); goto input_error; } /* Go ahead and assume 1 channel audio if nothing is detected. * This is because libsox usually doesn't set this for mono file * formats (for historical reasons). */ if (!(ft->handler->flags & SOX_FILE_PHONY) && !ft->signal.channels) ft->signal.channels = 1; if (sox_checkformat(ft) ) { sox_fail("bad input format for file %s: %s", ft->filename, ft->sox_errstr); goto input_error; } return ft; input_error: free(ft->filename); free(ft->filetype); free(ft); return NULL; } sox_format_t * sox_open_write( sox_bool (*overwrite_permitted)(const char *filename), const char *path, const sox_signalinfo_t *info, const char *filetype, comments_t comments, sox_size_t length, const sox_instrinfo_t *instr, const sox_loopinfo_t *loops) { sox_format_t * ft = xcalloc(sizeof(*ft), 1); int i; sox_bool no_filetype_given = filetype == NULL; ft->filename = xstrdup(path); if (!filetype) { char const * extension = find_file_extension(ft->filename); if (extension) ft->filetype = xstrdup(extension); } else ft->filetype = xstrdup(filetype); ft->mode = 'w'; if (sox_gettype(ft, no_filetype_given) != SOX_SUCCESS) { sox_fail("Can't open output file `%s': %s", ft->filename, ft->sox_errstr); goto output_error; } ft->signal.size = 0; ft->signal.encoding = SOX_ENCODING_UNKNOWN; ft->signal.channels = 0; ft->signal.compression = HUGE_VAL; if (info) ft->signal = *info; if (!(ft->handler->flags & SOX_FILE_NOSTDIO)) { /* Open file handler based on output name. Used stdout file handler * if the filename is "-" */ if (!strcmp(ft->filename, "-")) { if (sox_globals.stdout_in_use_by) { sox_fail("'-' (stdout) already in use by '%s'", sox_globals.stdout_in_use_by); goto output_error; } sox_globals.stdout_in_use_by = "audio output"; SET_BINARY_MODE(stdout); ft->fp = stdout; } else { struct stat st; if (!stat(ft->filename, &st) && (st.st_mode & S_IFMT) == S_IFREG && (overwrite_permitted && !overwrite_permitted(ft->filename))) { sox_fail("Permission to overwrite '%s' denied", ft->filename); goto output_error; } if ((ft->fp = fopen(ft->filename, "wb")) == NULL) { sox_fail("Can't open output file `%s': %s", ft->filename, strerror(errno)); goto output_error; } } /* stdout tends to be line-buffered. Override this */ /* to be Full Buffering. */ if (setvbuf (ft->fp, NULL, _IOFBF, sizeof(char) * sox_globals.bufsiz)) { sox_fail("Can't set write buffer"); goto output_error; } /* See if this file is seekable or not */ ft->seekable = is_seekable(ft); } ft->comments = copy_comments(comments); if (loops) for (i = 0; i < SOX_MAX_NLOOPS; i++) ft->loops[i] = loops[i]; /* leave SMPTE # alone since it's absolute */ if (instr) ft->instr = *instr; ft->length = length; set_endianness_if_not_already_set(ft); /* Read and write starters can change their formats. */ if (ft->handler->startwrite && (*ft->handler->startwrite)(ft) != SOX_SUCCESS) { sox_fail("Can't open output file `%s': %s", ft->filename, ft->sox_errstr); goto output_error; } if (sox_checkformat(ft) ) { sox_fail("bad output format for file %s: %s", ft->filename, ft->sox_errstr); goto output_error; } /* * Bit of a hack; doesn't cover the situation where * codec changes audio length (e.g. 8svx, gsm): */ if (info) ft->length = ft->length * ft->signal.rate / info->rate * ft->signal.channels / info->channels + .5; return ft; output_error: free(ft->filename); free(ft->filetype); free(ft); return NULL; } sox_size_t sox_read(sox_format_t * ft, sox_sample_t * buf, sox_size_t len) { sox_size_t actual = ft->handler->read? (*ft->handler->read)(ft, buf, len) : 0; return (actual > len? 0 : actual); } sox_size_t sox_write(sox_format_t * ft, const sox_sample_t *buf, sox_size_t len) { return ft->handler->write? (*ft->handler->write)(ft, buf, len) : 0; } #define TWIDDLE_BYTE(ub, type) \ do { \ if (ft->signal.reverse_bits) \ ub = cswap[ub]; \ if (ft->signal.reverse_nibbles) \ ub = ((ub & 15) << 4) | (ub >> 4); \ } while (0); #define TWIDDLE_WORD(uw, type) \ if (ft->signal.reverse_bytes) \ uw = sox_swap ## type(uw); /* N.B. This macro doesn't work for unaligned types (e.g. 3-byte types). */ #define READ_FUNC(type, size, ctype, twiddle) \ sox_size_t sox_read_ ## type ## _buf( \ sox_format_t * ft, ctype *buf, sox_size_t len) \ { \ sox_size_t n, nread; \ if ((nread = sox_readbuf(ft, buf, len * size)) != len * size && sox_error(ft)) \ sox_fail_errno(ft, errno, sox_readerr); \ nread /= size; \ for (n = 0; n < nread; n++) \ twiddle(buf[n], type); \ return nread; \ } /* Unpack a 3-byte value from a uint8_t * */ #define sox_unpack3(p) ((p)[0] | ((p)[1] << 8) | ((p)[2] << 16)) /* This (slower) macro works for unaligned types (e.g. 3-byte types) that need to be unpacked. */ #define READ_FUNC_UNPACK(type, size, ctype, twiddle) \ sox_size_t sox_read_ ## type ## _buf( \ sox_format_t * ft, ctype *buf, sox_size_t len) \ { \ sox_size_t n, nread; \ uint8_t *data = xmalloc(size * len); \ if ((nread = sox_readbuf(ft, data, len * size)) != len * size && sox_error(ft)) \ sox_fail_errno(ft, errno, sox_readerr); \ nread /= size; \ for (n = 0; n < nread; n++) { \ ctype datum = sox_unpack ## size(data + n * size); \ twiddle(datum, type); \ buf[n] = datum; \ } \ free(data); \ return n; \ } READ_FUNC(b, 1, uint8_t, TWIDDLE_BYTE) READ_FUNC(w, 2, uint16_t, TWIDDLE_WORD) READ_FUNC_UNPACK(3, 3, uint24_t, TWIDDLE_WORD) READ_FUNC(dw, 4, uint32_t, TWIDDLE_WORD) READ_FUNC(f, sizeof(float), float, TWIDDLE_WORD) READ_FUNC(df, sizeof(double), double, TWIDDLE_WORD) /* N.B. This macro doesn't work for unaligned types (e.g. 3-byte types). */ #define WRITE_FUNC(type, size, ctype, twiddle) \ sox_size_t sox_write_ ## type ## _buf( \ sox_format_t * ft, ctype *buf, sox_size_t len) \ { \ sox_size_t n, nwritten; \ for (n = 0; n < len; n++) \ twiddle(buf[n], type); \ if ((nwritten = sox_writebuf(ft, buf, len * size)) != len * size) \ sox_fail_errno(ft, errno, sox_writerr); \ return nwritten / size; \ } /* Pack a 3-byte value to a uint8_t * */ #define sox_pack3(p, v) \ (p)[0] = v & 0xff; \ (p)[1] = (v >> 8) & 0xff; \ (p)[2] = (v >> 16) & 0xff; /* This (slower) macro works for unaligned types (e.g. 3-byte types) that need to be packed. */ #define WRITE_FUNC_PACK(type, size, ctype, twiddle) \ sox_size_t sox_write_ ## type ## _buf( \ sox_format_t * ft, ctype *buf, sox_size_t len) \ { \ sox_size_t n, nwritten; \ uint8_t *data = xmalloc(size * len); \ for (n = 0; n < len; n++) { \ ctype datum = buf[n]; \ twiddle(datum, type); \ sox_pack ## size(data + n * size, datum); \ } \ if ((nwritten = sox_writebuf(ft, data, len * size)) != len * size) \ sox_fail_errno(ft, errno, sox_writerr); \ free(data); \ return nwritten / size; \ } WRITE_FUNC(b, 1, uint8_t, TWIDDLE_BYTE) WRITE_FUNC(w, 2, uint16_t, TWIDDLE_WORD) WRITE_FUNC_PACK(3, 3, uint24_t, TWIDDLE_WORD) WRITE_FUNC(dw, 4, uint32_t, TWIDDLE_WORD) WRITE_FUNC(f, sizeof(float), float, TWIDDLE_WORD) WRITE_FUNC(df, sizeof(double), double, TWIDDLE_WORD) #define WRITE1_FUNC(type, sign, ctype) \ int sox_write ## type(sox_format_t * ft, ctype datum) \ { \ return sox_write_ ## type ## _buf(ft, &datum, 1) == 1 ? SOX_SUCCESS : SOX_EOF; \ } WRITE1_FUNC(b, u, uint8_t) WRITE1_FUNC(w, u, uint16_t) WRITE1_FUNC(3, u, uint24_t) WRITE1_FUNC(dw, u, uint32_t) WRITE1_FUNC(f, su, float) WRITE1_FUNC(df, su, double) /* N.B. The file (if any) may already have been deleted. */ int sox_close(sox_format_t * ft) { int rc; if (ft->mode == 'r') rc = ft->handler->stopread? (*ft->handler->stopread)(ft) : SOX_SUCCESS; else rc = ft->handler->stopwrite? (*ft->handler->stopwrite)(ft) : SOX_SUCCESS; if (!(ft->handler->flags & SOX_FILE_NOSTDIO)) fclose(ft->fp); free(ft->filename); free(ft->filetype); delete_comments(&ft->comments); free(ft); return rc; } int sox_seek(sox_format_t * ft, sox_size_t offset, int whence) { /* FIXME: Implement SOX_SEEK_CUR and SOX_SEEK_END. */ if (whence != SOX_SEEK_SET) return SOX_EOF; /* FIXME: return SOX_EINVAL */ /* If file is a seekable file and this handler supports seeking, * the invoke handlers function. */ if (ft->seekable && (ft->handler->flags & SOX_FILE_SEEK)) return ft->handler->seek? (*ft->handler->seek)(ft, offset) : SOX_EOF; else return SOX_EOF; /* FIXME: return SOX_EBADF */ } sox_bool sox_is_playlist(char const * filename) { return strcaseends(filename, ".m3u") || strcaseends(filename, ".pls"); } int sox_parse_playlist(sox_playlist_callback_t callback, void * p, char const * const listname) { sox_bool const is_pls = strcaseends(listname, ".pls"); int const comment_char = "#;"[is_pls]; size_t text_length = 100; char * text = xmalloc(text_length + 1); char * dirname = xstrdup(listname); char * slash_pos = LAST_SLASH(dirname); FILE * file = xfopen(listname, "r"); char * filename; int c, result = SOX_SUCCESS; if (!slash_pos) *dirname = '\0'; else *slash_pos = '\0'; if (file == NULL) { sox_fail("Can't open playlist file `%s': %s", listname, strerror(errno)); result = SOX_EOF; } else do { size_t i = 0; size_t begin = 0, end = 0; while (isspace(c = getc(file))); if (c == EOF) break; while (c != EOF && !strchr("\r\n", c) && c != comment_char) { if (i == text_length) text = xrealloc(text, (text_length <<= 1) + 1); text[i++] = c; if (!strchr(" \t\f", c)) end = i; c = getc(file); } if (ferror(file)) break; if (c == comment_char) { do c = getc(file); while (c != EOF && !strchr("\r\n", c)); if (ferror(file)) break; } text[end] = '\0'; if (is_pls) { char dummy; if (!strncasecmp(text, "file", 4) && sscanf(text + 4, "%*u=%c", &dummy) == 1) begin = strchr(text + 5, '=') - text + 1; else end = 0; } if (begin != end) { char const * id = text + begin; if (!dirname[0] || is_uri(id) || IS_ABSOLUTE(id)) filename = xstrdup(id); else { filename = xmalloc(strlen(dirname) + strlen(id) + 2); sprintf(filename, "%s/%s", dirname, id); } if (sox_is_playlist(filename)) sox_parse_playlist(callback, p, filename); else if (callback(p, filename)) c = EOF; free(filename); } } while (c != EOF); if (ferror(file)) { sox_fail("Error reading playlist file `%s': %s", listname, strerror(errno)); result = SOX_EOF; } if (file) fclose(file); free(text); free(dirname); return result; }