write: Support audio formats != 16 bit little endian.
authorAndre Noll <maan@systemlinux.org>
Mon, 12 Jul 2010 15:28:58 +0000 (17:28 +0200)
committerAndre Noll <maan@systemlinux.org>
Tue, 13 Jul 2010 12:39:16 +0000 (14:39 +0200)
This adds the new --sample_format option to para_write and teaches
the check_wav_task to read the sample format from the wav header
rather than assuming 16 bit little endian.

The alsa, oss and osx writers all ask the upper buffer tree nodes
for the current audio format. For para_write the check_wav task
answers this query by using the value given at the command line,
looking at the wav header, or using 16 bit little endian as the
default answer.

For para_audiod, the current decoder answers this query. In this
case the audio format is either 16 bit little endian or 16 bit
big endian, depending on the endianness of the machine.

alsa_write.c
filter_common.c
ggo/write.m4
oss_write.c
osx_write.c
para.h
write.c
write_common.c
write_common.h

index 09cca93..dfa7a2e 100644 (file)
@@ -31,9 +31,6 @@
 #include "alsa_write.cmdline.h"
 #include "error.h"
 
-/** always use 16 bit little endian */
-#define FORMAT SND_PCM_FORMAT_S16_LE
-
 /** Data specific to the alsa writer. */
 struct private_alsa_write_data {
        /** The alsa handle */
@@ -49,6 +46,8 @@ struct private_alsa_write_data {
         * of the writer node group.
         */
        unsigned sample_rate;
+
+       snd_pcm_format_t sample_format;
        /**
         * The number of channels, given by command line option or the
         * decoder of the writer node group.
@@ -57,6 +56,19 @@ struct private_alsa_write_data {
        struct timeval drain_barrier;
 };
 
+static snd_pcm_format_t get_alsa_pcm_format(enum sample_format sf)
+{
+       switch (sf) {
+       case SF_S8: return SND_PCM_FORMAT_S8;
+       case SF_U8: return SND_PCM_FORMAT_U8;
+       case SF_S16_LE: return SND_PCM_FORMAT_S16_LE;
+       case SF_S16_BE: return SND_PCM_FORMAT_S16_BE;
+       case SF_U16_LE: return SND_PCM_FORMAT_U16_LE;
+       case SF_U16_BE: return SND_PCM_FORMAT_U16_BE;
+       default: return SND_PCM_FORMAT_S16_LE;
+       }
+}
+
 /* Install PCM software and hardware configuration. */
 static int alsa_init(struct private_alsa_write_data *pad,
                struct alsa_write_args_info *conf)
@@ -80,7 +92,8 @@ static int alsa_init(struct private_alsa_write_data *pad,
        if (snd_pcm_hw_params_set_access(pad->handle, hwparams,
                        SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
                return -E_ACCESS_TYPE;
-       if (snd_pcm_hw_params_set_format(pad->handle, hwparams, FORMAT) < 0)
+       if (snd_pcm_hw_params_set_format(pad->handle, hwparams,
+                       pad->sample_format) < 0)
                return -E_SAMPLE_FORMAT;
        if (snd_pcm_hw_params_set_channels(pad->handle, hwparams,
                        pad->channels) < 0)
@@ -120,7 +133,7 @@ static int alsa_init(struct private_alsa_write_data *pad,
                return -E_STOP_THRESHOLD;
        if (snd_pcm_sw_params(pad->handle, swparams) < 0)
                PARA_WARNING_LOG("unable to install sw params\n");
-       pad->bytes_per_frame = snd_pcm_format_physical_width(FORMAT)
+       pad->bytes_per_frame = snd_pcm_format_physical_width(pad->sample_format)
                * pad->channels / 8;
        if (pad->bytes_per_frame <= 0)
                return -E_PHYSICAL_WIDTH;
@@ -229,12 +242,16 @@ again:
        }
        if (!pad->handle) {
                int32_t val;
+
                if (bytes == 0) /* no data available */
                        return;
                get_btr_sample_rate(btrn, &val);
                pad->sample_rate = val;
                get_btr_channels(btrn, &val);
                pad->channels = val;
+               get_btr_sample_format(btrn, &val);
+               pad->sample_format = get_alsa_pcm_format(val);
+
                PARA_INFO_LOG("%d channel(s), %dHz\n", pad->channels,
                        pad->sample_rate);
                ret = alsa_init(pad, wn->conf);
index 8e657f5..cdd9f39 100644 (file)
@@ -137,12 +137,18 @@ void generic_filter_pre_select(struct sched *s, struct task *t)
                sched_min_delay(s);
 }
 
+#ifdef WORDS_BIGENDIAN
+#define DECODER_SAMPLE_FORMAT SF_S16_BE
+#else
+#define DECODER_SAMPLE_FORMAT SF_S16_LE
+#endif
+
 /**
  * Execute a btr command for a decoder.
  *
- * The buffer tree nodes of the writers ask the parent nodes about sample_rate
- * and the channels count. This function is called by all decoders to answer
- * these queries.
+ * The buffer tree nodes of the writers ask the parent nodes about sample_rate,
+ * channels count and sample format. This function is called by all decoders to
+ * answer these queries.
  *
  * \param cmd The command to be executed by the child node.
  * \param sample_rate Known to the decoder.
@@ -164,5 +170,9 @@ int decoder_execute(const char *cmd, unsigned sample_rate, unsigned channels,
                *result = make_message("%u", channels);
                return 1;
        }
+       if (!strcmp(cmd, "sample_format")) {
+               *result = make_message("%u", DECODER_SAMPLE_FORMAT);
+               return 1;
+       }
        return -ERRNO_TO_PARA_ERROR(ENOTSUP);
 }
index 847bd20..524d724 100644 (file)
@@ -32,3 +32,12 @@ option "sample-rate" s
 int typestr = "num"
 default = "44100"
 optional
+
+option "sample-format" f
+#~~~~~~~~~~~~~~~~~~~~~~~
+"specify sample format"
+# This must match the enum sample_format of para.h
+values = "S8", "U8", "S16_LE", "S16_BE", "U16_LE", "U16_BE" enum
+default = "S16_LE"
+optional
+
index ea31fc8..b82b396 100644 (file)
@@ -25,9 +25,6 @@
 #include "oss_write.cmdline.h"
 #include "error.h"
 
-/** Always use 16 bit little endian. */
-#define FORMAT AFMT_S16_LE
-
 /** Data specific to the oss writer. */
 struct private_oss_write_data {
        /** The file handle of the device. */
@@ -36,6 +33,19 @@ struct private_oss_write_data {
        int bytes_per_frame;
 };
 
+static int get_oss_format(enum sample_format sf)
+{
+       switch (sf) {
+       case SF_S8: return AFMT_S8;
+       case SF_U8: return AFMT_U8;
+       case SF_S16_LE: return AFMT_S16_LE;
+       case SF_S16_BE: return AFMT_S16_BE;
+       case SF_U16_LE: return AFMT_U16_LE;
+       case SF_U16_BE: return AFMT_U16_BE;
+       default: return AFMT_S16_LE;
+       }
+}
+
 static void oss_pre_select(struct sched *s, struct task *t)
 {
        struct writer_node *wn = container_of(t, struct writer_node, task);
@@ -69,9 +79,10 @@ static void oss_close(struct writer_node *wn)
  * incorrectly believe that the device is still in 44.1 kHz mode when actually
  * the speed is decreased to 22.05 kHz.
  */
-static int oss_init(struct writer_node *wn, unsigned sample_rate, unsigned channels)
+static int oss_init(struct writer_node *wn, unsigned sample_rate,
+               unsigned channels, int sample_format)
 {
-       int ret, format = FORMAT;
+       int ret, format;
        unsigned ch, rate;
        struct oss_write_args_info *conf = wn->conf;
        struct private_oss_write_data *powd = wn->private_data;
@@ -85,18 +96,16 @@ static int oss_init(struct writer_node *wn, unsigned sample_rate, unsigned chann
        if (ret < 0)
                goto err;
        /* set PCM format */
+       sample_format = format = get_oss_format(sample_format);
        ret = ioctl(powd->fd, SNDCTL_DSP_SETFMT, &format);
        if (ret < 0) {
                ret = -ERRNO_TO_PARA_ERROR(errno);
                goto err;
        }
        ret = -E_BAD_SAMPLE_FORMAT;
-       if (format != FORMAT)
+       if (format != sample_format)
                goto err;
        /* set number of channels */
-       ret = -E_BAD_CHANNEL_COUNT;
-       if (channels == 0)
-               goto err;
        ch = channels;
        ret = ioctl(powd->fd, SNDCTL_DSP_CHANNELS, &ch);
        if (ret < 0) {
@@ -106,7 +115,10 @@ static int oss_init(struct writer_node *wn, unsigned sample_rate, unsigned chann
        ret = -E_BAD_CHANNEL_COUNT;
        if (ch != channels)
                goto err;
-       powd->bytes_per_frame = ch * 2;
+       if (format == SF_U8 || format == SF_S8)
+               powd->bytes_per_frame = ch;
+       else
+               powd->bytes_per_frame = ch * 2;
 
        /*
         * Set sampling rate
@@ -157,10 +169,11 @@ static void oss_post_select(__a_unused struct sched *s,
        if (ret == 0)
                return;
        if (powd->fd < 0) {
-               int32_t rate, ch;
+               int32_t rate, ch, format;
                get_btr_sample_rate(btrn, &rate);
                get_btr_channels(btrn, &ch);
-               ret = oss_init(wn, rate, ch);
+               get_btr_sample_format(btrn, &format);
+               ret = oss_init(wn, rate, ch, format);
                if (ret < 0)
                        goto out;
                return;
index 65a5adc..cfd02e7 100644 (file)
@@ -58,6 +58,8 @@ struct private_osx_write_data {
        struct osx_buffer *to;
        /** sample rate of the current audio stream */
        unsigned sample_rate;
+       /** Sample format of the current audio stream */
+       unsigned sample_format;
        /** number of channels of the current audio stream */
        unsigned channels;
 };
@@ -94,18 +96,41 @@ static void init_buffers(struct writer_node *wn)
        *ptrptr = powd->from = powd->to;
 }
 
-static void fill_buffer(struct osx_buffer *b, short *source, long size)
+static void fill_buffer(struct private_osx_write_data *powd, char *data, long bytes)
 {
+       struct osx_buffer *b = powd->to;
        float *dest;
+       long samples;
+       enum sample_format sf = powd->sample_format;
 
-       assert(b->remaining == 0 || size > 0);
-       if (b->size != size) {
-               b->buffer = para_realloc(b->buffer, size * sizeof(float));
-               b->size = size;
+       samples = (sf == SF_S8 || sf == SF_U8)? bytes : bytes / 2;
+       assert(b->remaining == 0 || samples > 0);
+       if (b->size != samples) {
+               b->buffer = para_realloc(b->buffer, samples * sizeof(float));
+               b->size = samples;
        }
        dest = b->buffer;
-       while (size--)
-               *dest++ = (*source++) / 32768.0;
+       switch (powd->sample_format) {
+       case SF_U8: {
+               uint8_t *src = (uint8_t *)data;
+               while (samples--) {
+                       *dest++ = (*src++) / 256.0;
+               }
+               break;
+       }
+       case SF_S8: {
+               int8_t *src = (int8_t *)data;
+               while (samples--) {
+                       *dest++ = ((*src++) + 128) / 256.0;
+               }
+               break;
+       }
+       default: {
+               short *src = (short *)data;
+               while (samples--)
+                       *dest++ = (*src++) / 32768.0;
+       }
+       }
        b->ptr = b->buffer;
        b->remaining = b->size;
 }
@@ -164,8 +189,16 @@ static OSStatus osx_callback(void * inClientData,
 
 static int osx_write_open(struct writer_node *wn)
 {
-       struct private_osx_write_data *powd = para_calloc(
-               sizeof(struct private_osx_write_data));
+       struct private_osx_write_data *powd = para_calloc(sizeof(*powd));
+
+       wn->private_data = powd;
+       init_buffers(wn);
+       return 0;
+}
+
+static int core_audio_init(struct writer_node *wn)
+{
+       struct private_osx_write_data *powd = wn->private_data;
        ComponentDescription desc;
        Component comp;
        AURenderCallbackStruct inputCallback = {osx_callback, powd};
@@ -174,7 +207,6 @@ static int osx_write_open(struct writer_node *wn)
        struct btr_node *btrn = wn->btrn;
        int32_t val;
 
-       wn->private_data = powd;
        /* where did that default audio output go? */
        desc.componentType = kAudioUnitType_Output;
        desc.componentSubType = kAudioUnitSubType_DefaultOutput;
@@ -197,6 +229,8 @@ static int osx_write_open(struct writer_node *wn)
        powd->sample_rate = val;
        get_btr_channels(btrn, &val);
        powd->channels = val;
+       get_btr_sample_format(btrn, &val);
+       powd->sample_format = val;
        /*
         * Choose PCM format. We tell the Output Unit what format we're going
         * to supply data to it. This is necessary if you're providing data
@@ -204,31 +238,36 @@ static int osx_write_open(struct writer_node *wn)
         * any format conversions necessary from your format to the device's
         * format.
         */
-       format.mSampleRate = powd->sample_rate;
-       /* The specific encoding type of audio stream */
        format.mFormatID = kAudioFormatLinearPCM;
+       format.mFramesPerPacket = 1;
+       format.mSampleRate = powd->sample_rate;
        /* flags specific to each format */
        format.mFormatFlags = kLinearPCMFormatFlagIsFloat
                | kLinearPCMFormatFlagIsPacked
                | ENDIAN_FLAGS;
+       switch (powd->sample_format) {
+       case SF_S8:
+       case SF_U8:
+               wn->min_iqs = powd->channels;
+               break;
+       default:
+               wn->min_iqs = powd->channels * 2;
+       }
+       format.mBitsPerChannel = 8 * sizeof(float);
+       format.mBytesPerPacket = powd->channels * sizeof(float);
+       format.mBytesPerFrame = format.mBytesPerPacket;
        format.mChannelsPerFrame = powd->channels;
-       format.mFramesPerPacket = 1;
-       format.mBytesPerPacket = format.mChannelsPerFrame * sizeof(float);
-       format.mBytesPerFrame = format.mFramesPerPacket * format.mBytesPerPacket;
-       /* one of the most constant constants of the whole computer history */
-       format.mBitsPerChannel = sizeof(float) * 8;
+
        ret = -E_STREAM_FORMAT;
        if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_StreamFormat,
                        kAudioUnitScope_Input, 0, &format,
                        sizeof(AudioStreamBasicDescription)))
                goto e2;
-       init_buffers(wn);
        ret = -E_ADD_CALLBACK;
        if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_SetRenderCallback,
                        kAudioUnitScope_Input, 0, &inputCallback,
                        sizeof(inputCallback)) < 0)
                goto e3;
-       wn->min_iqs = powd->channels * 2;
        return 1;
 e3:
        destroy_buffers(powd);
@@ -285,10 +324,15 @@ static void osx_write_post_select(__a_unused struct sched *s, struct task *t)
                ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
                if (ret <= 0)
                        break;
+               if (powd->sample_rate == 0) {
+                       ret = core_audio_init(wn);
+                       if (ret < 0)
+                               break;
+               }
                btr_merge(btrn, 8192);
                bytes = btr_next_buffer(btrn, &data);
                //PARA_CRIT_LOG("have: %zu\n", bytes);
-               fill_buffer(powd->to, (short *)data, bytes / sizeof(short));
+               fill_buffer(powd, data, bytes);
                btr_consume(btrn, bytes);
                if (!powd->play) {
                        ret = -E_UNIT_START;
@@ -298,9 +342,10 @@ static void osx_write_post_select(__a_unused struct sched *s, struct task *t)
                }
                powd->to = powd->to->next;
        }
-       if (ret < 0)
+       if (ret < 0 && powd->from->remaining <= 0) {
                btr_remove_node(btrn);
-       t->error = ret;
+               t->error = ret;
+       }
 }
 
 static void osx_write_pre_select(struct sched *s, struct task *t)
diff --git a/para.h b/para.h
index ae3b5b2..287e3c7 100644 (file)
--- a/para.h
+++ b/para.h
@@ -259,3 +259,27 @@ _static_inline_ long int para_random(unsigned max)
 /** Used to avoid a shortcoming in vim's syntax highlighting. */
 #define EMBRACE(...) { __VA_ARGS__}
 
+/**
+ * The sample formats supported by paraslash.
+ *
+ * It may be determined by one of the following sources:
+ *
+ *     1. The decoding filter (para_audiod only). In this case, it is always
+ *     \t SF_S16_LE which is the canonical format used within decoders.
+ *
+ *     2. The wav header (para_write only).
+ *
+ *     3. The --format option of para_write.
+ */
+#define SAMPLE_FORMATS \
+       SAMPLE_FORMAT(SF_S8, "8 bit signed"), \
+       SAMPLE_FORMAT(SF_U8, "8 bit unsigned"), \
+       SAMPLE_FORMAT(SF_S16_LE, "16 bit signed, little endian"), \
+       SAMPLE_FORMAT(SF_S16_BE, "16 bit signed, big endian"), \
+       SAMPLE_FORMAT(SF_U16_LE, "16 bit unsigned, little endian"), \
+       SAMPLE_FORMAT(SF_U16_BE, "16 bit unsigned, big endian"), \
+
+#define SAMPLE_FORMAT(a, b) a
+enum sample_format {SAMPLE_FORMATS};
+#undef SAMPLE_FORMAT
+#define SAMPLE_FORMAT(a, b) b
diff --git a/write.c b/write.c
index d2169f2..8411003 100644 (file)
--- a/write.c
+++ b/write.c
@@ -32,12 +32,13 @@ enum check_wav_state {
        CWS_NO_HEADER,
 };
 
+/* Information extracted from the wav header. */
 struct check_wav_task {
        int state;
-       /** Number of channels specified in wav header given by \a buf. */
+       /** Number of channels. */
        unsigned channels;
-       /** Sample rate specified in wav header given by \a buf. */
        unsigned sample_rate;
+       unsigned sample_format;
        /** The task structure used by the scheduler. */
        struct task task;
        struct btr_node *btrn;
@@ -51,12 +52,6 @@ static struct stdin_task sit;
 /** Length of a standard wav header. */
 #define WAV_HEADER_LEN 44
 
-/**
- * Test if audio buffer contains a valid wave header.
- *
- * \return If not, return -E_NO_WAV_HEADER, otherwise, return zero. If
- * there is less than WAV_HEADER_LEN bytes available, return one.
- */
 static void check_wav_pre_select(struct sched *s, struct task *t)
 {
        struct check_wav_task *cwt = container_of(t, struct check_wav_task, task);
@@ -71,7 +66,7 @@ static void check_wav_pre_select(struct sched *s, struct task *t)
        if (!strcmp(cmd, #_cmd)) { \
                if (!conf._cmd ## _given && cwt->state == CWS_NEED_HEADER) \
                        return -E_BTR_NAVAIL; \
-               *result = make_message("%d", cwt->state == CWS_NO_HEADER? \
+               *result = make_message("%d", cwt->state == CWS_NO_HEADER || conf._cmd ## _given? \
                        conf._cmd ## _arg : cwt->_cmd); \
                return 1; \
        } \
@@ -83,6 +78,7 @@ static int check_wav_exec(struct btr_node *btrn, const char *cmd, char **result)
 
        HANDLE_EXEC(sample_rate);
        HANDLE_EXEC(channels);
+       HANDLE_EXEC(sample_format);
        return -ERRNO_TO_PARA_ERROR(ENOTSUP);
 }
 
@@ -93,6 +89,8 @@ static void check_wav_post_select(__a_unused struct sched *s, struct task *t)
        unsigned char *a;
        size_t sz;
        int ret;
+       uint16_t bps; /* bits per sample */
+       const char *sample_formats[] = {SAMPLE_FORMATS};
 
        t->error = 0;
        ret = btr_node_status(btrn, cwt->min_iqs, BTR_NT_INTERNAL);
@@ -105,9 +103,13 @@ static void check_wav_post_select(__a_unused struct sched *s, struct task *t)
        if (sz < cwt->min_iqs) /* file size less than WAV_HEADER_SIZE */
                goto pushdown;
        cwt->min_iqs = 0;
-       cwt->channels = 2;
-       cwt->sample_rate = 44100;
-       if (a[0] != 'R' || a[1] != 'I' || a[2] != 'F' || a[3] != 'F') {
+       /*
+        * The default byte ordering assumed for WAVE data files is
+        * little-endian. Files written using the big-endian byte ordering
+        * scheme have the identifier RIFX instead of RIFF.
+        */
+       if (a[0] != 'R' || a[1] != 'I' || a[2] != 'F' ||
+                       (a[3] != 'F' && a[3] != 'X')) {
                PARA_NOTICE_LOG("wav header not found\n");
                cwt->state = CWS_NO_HEADER;
                sprintf(t->status, "check wav: no header");
@@ -118,7 +120,23 @@ static void check_wav_post_select(__a_unused struct sched *s, struct task *t)
        sprintf(t->status, "check wav: have header");
        cwt->channels = (unsigned) a[22];
        cwt->sample_rate = a[24] + (a[25] << 8) + (a[26] << 16) + (a[27] << 24);
-       PARA_INFO_LOG("channels: %d, sample rate: %d\n", cwt->channels, cwt->sample_rate);
+       bps = a[34] + ((unsigned)a[35] << 8);
+       if (bps != 8 && bps != 16) {
+               PARA_WARNING_LOG("%u bps not supported, assuming 16\n", bps);
+               bps = 16;
+       }
+       /*
+        * 8-bit samples are stored as unsigned bytes, ranging from 0 to 255.
+        * 16-bit samples are stored as 2's-complement signed integers, ranging
+        * from -32768 to 32767.
+        */
+       if (bps == 8)
+               cwt->sample_format = SF_U8;
+       else
+               cwt->sample_format = (a[3] == 'F')? SF_S16_LE : SF_S16_BE;
+       PARA_NOTICE_LOG("%dHz, %s, %s\n", cwt->sample_rate,
+               cwt->channels == 1? "mono" : "stereo",
+               sample_formats[cwt->sample_format]);
        btr_consume(btrn, WAV_HEADER_LEN);
 pushdown:
        btr_pushdown(btrn);
index b73ba9a..6cd7ed2 100644 (file)
@@ -159,18 +159,37 @@ static void get_btr_value(struct btr_node *btrn, const char *cmd,
        free(buf);
 }
 
-/*
+/**
  * Ask parent btr nodes for the sample rate of the current stream.
+ *
+ * \param btrn Where to start the search.
+ * \param result. Filled in by this function.
+ *
+ * This function is assumed to succeed and terminates on errors.
  */
 void get_btr_sample_rate(struct btr_node *btrn, int32_t *result)
 {
        get_btr_value(btrn, "sample_rate", result);
 }
 
-/*
+/**
  * Ask parent btr nodes for the channel count of the current stream.
+ *
+ * \param btrn See \ref get_btr_sample_rate.
+ * \param result See \ref get_btr_sample_rate.
  */
 void get_btr_channels(struct btr_node *btrn, int32_t *result)
 {
        get_btr_value(btrn, "channels", result);
 }
+
+/**
+ * Ask parent btr nodes for the number of bits per sample and the byte sex.
+ *
+ * \param btrn See \ref get_btr_sample_rate.
+ * \param result Contains the sample format as an enum sample_format type.
+ */
+void get_btr_sample_format(struct btr_node *btrn, int32_t *result)
+{
+       get_btr_value(btrn, "sample_format", result);
+}
index 74f8e1c..d26d42c 100644 (file)
@@ -14,3 +14,5 @@ int setup_writer_node(const char *arg, struct btr_node *parent,
                struct writer_node *wn);
 void get_btr_sample_rate(struct btr_node *btrn, int32_t *result);
 void get_btr_channels(struct btr_node *btrn, int32_t *result);
+void get_btr_sample_format(struct btr_node *btrn, int32_t *result);
+