#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 */
* 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.
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)
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)
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;
}
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);
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.
*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);
}
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
+
#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. */
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);
* 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;
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) {
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
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;
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;
};
*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;
}
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};
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;
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
* 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);
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;
}
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)
/** 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
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;
/** 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);
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; \
} \
HANDLE_EXEC(sample_rate);
HANDLE_EXEC(channels);
+ HANDLE_EXEC(sample_format);
return -ERRNO_TO_PARA_ERROR(ENOTSUP);
}
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);
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");
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);
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);
+}
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);
+