shithub: sox

ref: 12653efbe201211c5e192c1291de8d756b1ffd22
dir: /src/soxio.c/

View raw version
#include "sox_i.h"

#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h> /* for fstat() */
#include <sys/stat.h> /* for fstat() */

#ifdef _MSC_VER
/* __STDC__ is defined, so these symbols aren't created. */
#define S_IFMT   _S_IFMT
#define S_IFREG  _S_IFREG
#define fstat _fstat
#endif

/* Based on zlib's minigzip: */
#if defined(WIN32) || defined(__NT__) || defined(__DJGPP__)
#include <fcntl.h>
#include <io.h>
#ifndef O_BINARY
#define O_BINARY _O_BINARY
#endif
#define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY)
#else
#define SET_BINARY_MODE(file)
#endif

sox_size_t sox_bufsiz = 8192;
sox_global_info_t sox_global_info;

void set_endianness_if_not_already_set(ft_t ft)
{
  if (ft->signal.reverse_bytes == SOX_OPTION_DEFAULT) {
    if (ft->h->flags & SOX_FILE_ENDIAN)
      /* Set reverse_bytes for big-endian formats; on a big-endian
         system this cancels out with the reversal made for being on a
         big-endian system. */
      ft->signal.reverse_bytes = (ft->h->flags & SOX_FILE_ENDBIG) != 0;
    else
      ft->signal.reverse_bytes = SOX_OPTION_NO;
  }
  if (ft->signal.reverse_bits == SOX_OPTION_DEFAULT)
    ft->signal.reverse_bits = !!(ft->h->flags & SOX_FILE_BIT_REV);
  else if (ft->signal.reverse_bits != !!(ft->h->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->h->flags & SOX_FILE_NIB_REV);
  else if (ft->signal.reverse_nibbles != !!(ft->h->flags & SOX_FILE_NIB_REV))
      sox_report("'%s': Format options overriding file-type nibble-order", ft->filename);
}

static int is_seekable(ft_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(ft_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 == -1)
        {
                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 <= 0) || (ft->signal.size > SOX_INFO_SIZE_MAX))
        {
                sox_fail_errno(ft,SOX_EFMT,"data size %d 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;
}

ft_t sox_open_read(const char *path, const sox_signalinfo_t *info,
                  const char *filetype)
{
    ft_t ft = (ft_t)xcalloc(sizeof(struct sox_soundstream), 1);

    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);

    if (sox_gettype(ft, sox_false) != SOX_SUCCESS) {
        sox_fail("Unknown input file format for `%s': %s",
                ft->filename,
                ft->sox_errstr);
        goto input_error;
    }

    ft->signal.size = -1;
    ft->signal.encoding = SOX_ENCODING_UNKNOWN;
    ft->signal.channels = 0;
    if (info)
        ft->signal = *info;
    ft->mode = 'r';

    if (!(ft->h->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_global_info.stdin_in_use_by) {
            sox_fail("'-' (stdin) already in use by '%s'", sox_global_info.stdin_in_use_by);
            goto input_error;
          }
          sox_global_info.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->h->startread && (*ft->h->startread)(ft) != SOX_SUCCESS)
    {
        sox_fail("Failed reading `%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->h->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;
}

#if defined(DOS) || defined(WIN32)
#define LASTCHAR '\\'
#else
#define LASTCHAR '/'
#endif

ft_t sox_open_write(
    sox_bool (*overwrite_permitted)(const char *filename),
    const char *path,
    const sox_signalinfo_t *info,
    const char *filetype,
    const char *comment,
    sox_size_t length,
    const sox_instrinfo_t *instr,
    const sox_loopinfo_t *loops)
{
    ft_t ft = (ft_t)xcalloc(sizeof(struct sox_soundstream), 1);
    int i;
    sox_bool no_filetype_given = filetype == NULL;

    ft->filename = xstrdup(path);

    if (!filetype) {
        char *chop;
        int len;

        len = strlen(ft->filename);

        /* Use filename extension to determine audio type.
         * Search for the last '.' appearing in the filename, same
         * as for input files.
         */
        chop = ft->filename + len;
        while (chop > ft->filename && *chop != LASTCHAR && *chop != '.')
            chop--;

        if (*chop == '.') {
            chop++;
            ft->filetype = xstrdup(chop);
        }
    } else
        ft->filetype = xstrdup(filetype);

    if (!ft->filetype || sox_gettype(ft, no_filetype_given) != SOX_SUCCESS)
    {
        sox_fail("Unknown output file format for '%s':  %s",
                ft->filename,
                ft->sox_errstr);
        goto output_error;
    }

    ft->signal.size = -1;
    ft->signal.encoding = SOX_ENCODING_UNKNOWN;
    ft->signal.channels = 0;
    if (info)
        ft->signal = *info;
    ft->mode = 'w';

    if (!(ft->h->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_global_info.stdout_in_use_by) {
            sox_fail("'-' (stdout) already in use by '%s'", sox_global_info.stdout_in_use_by);
            goto output_error;
          }
          sox_global_info.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(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_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->comment = xstrdup(comment);

    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->h->startwrite && (*ft->h->startwrite)(ft) != SOX_SUCCESS)
    {
        sox_fail("Failed writing %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;
    }

    return ft;

output_error:

    free(ft->filename);
    free(ft->filetype);
    free(ft);
    return NULL;
}

sox_size_t sox_read(ft_t f, sox_ssample_t * buf, sox_size_t len)
{
  sox_size_t actual = f->h->read? (*f->h->read)(f, buf, len) : 0;
  return (actual > len? 0 : actual);
}

sox_size_t sox_write(ft_t ft, const sox_ssample_t *buf, sox_size_t len)
{
    return ft->h->write? (*ft->h->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 ^ SOX_IS_BIGENDIAN) \
    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( \
      ft_t ft, ctype *buf, sox_size_t len) \
  { \
    sox_size_t n, nread; \
    if ((nread = sox_readbuf(ft, buf, len * size)) != len * size) \
      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( \
      ft_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_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( \
      ft_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( \
      ft_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(ft_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(ft_t ft)
{
    int rc;

    if (ft->mode == 'r')
        rc = ft->h->stopread? (*ft->h->stopread)(ft) : SOX_SUCCESS;
    else
        rc = ft->h->stopwrite? (*ft->h->stopwrite)(ft) : SOX_SUCCESS;

    if (!(ft->h->flags & SOX_FILE_NOSTDIO))
        fclose(ft->fp);
    free(ft->filename);
    free(ft->filetype);
    /* Currently, since startread() mallocs comments, stopread
     * is expected to also free it. */
    if (ft->mode == 'w')
        free(ft->comment);

    return rc;
}

int sox_seek(ft_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->h->flags & SOX_FILE_SEEK))
        return ft->h->seek? (*ft->h->seek)(ft, offset) : SOX_EOF;
    else
        return SOX_EOF; /* FIXME: return SOX_EBADF */
}