uname_s := $(shell uname -s 2>/dev/null || echo "UNKNOWN_OS")
uname_rs := $(shell uname -rs)
cc_version := $(shell $(CC) --version | head -n 1)
-codename := symplectic separability
+codename := imaginary radiation
DEBUG_CPPFLAGS += -Wno-sign-compare -g -Wunused -Wundef -W
DEBUG_CPPFLAGS += -Wredundant-decls
NEWS
====
+-------------------------------------
+0.3.6 (2009-12-07) "cubic continuity"
+-------------------------------------
+
+Quite a few bugs have been found and fixed since 0.3.5, so here's
+another 0.3.x release. No new features.
+
+ - Always check return value of malloc().
+ - ogg vorbis/FEC: Do not write garbage after the audio file header.
+ - exit if root privileges could not be dropped.
+ - FEC: Fix computation of extra slices.
+ - oss: Fix check for empty input buffer.
+ - Avoid buffer underruns due to filter chain output buffer constraints.
+ - server: Fix assignment of afs_pid.
+ - Don't panic if the afs database contains unknown audio formats.
+ - http/dccp: Do not send the audio file header twice.
+ - FEC: Timing improvements.
+
--------------------------------------------
0.3.5 (2009-09-21) "symplectic separability"
--------------------------------------------
*/
const char *audio_format_name(int i)
{
- //PARA_NOTICE_LOG("array size: %u¸ requested: %d\n", ARRAY_SIZE(afl), i);
- assert(i < 0 || i < ARRAY_SIZE(afl) - 1);
- return i >= 0? afl[i].name : "(none)";
+ if (i < 0 || i >= ARRAY_SIZE(afl) - 1)
+ return "???";
+ return afl[i].name;
}
/**
pad->channels) < 0)
return -E_CHANNEL_COUNT;
if (snd_pcm_hw_params_set_rate_near(pad->handle, hwparams,
- &pad->samplerate, 0) < 0)
+ &pad->samplerate, NULL) < 0)
return -E_SET_RATE;
- err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &pad->buffer_time, 0);
+ err = snd_pcm_hw_params_get_buffer_time_max(hwparams,
+ &pad->buffer_time, NULL);
if (err < 0 || !pad->buffer_time)
return -E_GET_BUFFER_TIME;
PARA_INFO_LOG("buffer time: %d\n", pad->buffer_time);
if (snd_pcm_hw_params_set_buffer_time_near(pad->handle, hwparams,
- &pad->buffer_time, 0) < 0)
+ &pad->buffer_time, NULL) < 0)
return -E_SET_BUFFER_TIME;
if (snd_pcm_hw_params(pad->handle, hwparams) < 0)
return -E_HW_PARAMS;
- snd_pcm_hw_params_get_period_size(hwparams, &period_size, 0);
+ snd_pcm_hw_params_get_period_size(hwparams, &period_size, NULL);
snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
PARA_INFO_LOG("buffer size: %lu, period_size: %lu\n", buffer_size,
period_size);
a->receiver->close(s->receiver_node);
free(s->receiver_node);
s->receiver_node = NULL;
+ stat_task->current_audio_format_num = -1;
}
static void kill_all_decoders(int error)
stat_task->length_seconds = 0;
stat_task->offset_seconds = 0;
stat_task->vss_status = 0;
+ stat_task->current_audio_format_num = -1;
audiod_status_dump();
}
/** Define the \p close function for this blob type. */
#define DEFINE_BLOB_CLOSE(table_name) \
- void table_name ## _close(void) \
+ static void table_name ## _close(void) \
{ \
osl_close_table(table_name ## _table, OSL_MARK_CLEAN); \
table_name ## _table = NULL; \
/** Define the \p create function for this blob type. */
#define DEFINE_BLOB_CREATE(table_name) \
- int table_name ## _create(const char *dir) \
+ static int table_name ## _create(const char *dir) \
{ \
table_name ## _table_desc.dir = dir; \
return osl_create_table(&table_name ## _table_desc); \
}
#define DEFINE_BLOB_OPEN(table_name) \
- int table_name ## _open(const char *dir) \
+ static int table_name ## _open(const char *dir) \
{ \
return blob_open(&table_name ## _table, \
&table_name ## _table_desc, dir); \
],
[
have_oss="no"
- AC_MSG_WARN([no linux/soundcard.h $msg])
+ AC_MSG_WARN([no sys/soundcard.h $msg])
]
)
CPPFLAGS="$OLD_CPPFLAGS"
exit(EXIT_FAILURE);
}
PARA_INFO_LOG("dropping root privileges\n");
- setuid(p->pw_uid);
+ if (setuid(p->pw_uid) < 0) {
+ PARA_EMERG_LOG("failed to set effective user ID (%s)",
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
PARA_DEBUG_LOG("uid: %d, euid: %d\n", (int)getuid(), (int)geteuid());
}
FILE *fp;
struct tm *tm;
time_t t1;
- char *color, str[MAXLINE] = "";
+ char *color;
ll = PARA_MIN(ll, NUM_LOGLEVELS - 1);
ll = PARA_MAX(ll, LL_DEBUG);
color = daemon_test_flag(DF_COLOR_LOG)? me->log_colors[ll] : NULL;
if (color)
fprintf(fp, "%s", color);
- if (daemon_test_flag(DF_LOG_TIME)) {
- /* date and time */
+ if (daemon_test_flag(DF_LOG_TIME)) { /* print date and time */
+ char str[100];
time(&t1);
tm = localtime(&t1);
- strftime(str, MAXLINE, "%b %d %H:%M:%S", tm);
+ strftime(str, sizeof(str), "%b %d %H:%M:%S", tm);
fprintf(fp, "%s ", str);
}
if (daemon_test_flag(DF_LOG_HOSTNAME)) {
unsigned char **data;
};
-/**
- * The fecdec filter defers decoding of the first group until the first slice
- * of the next group was received. This avoids buffer underruns in subsequent
- * filters of the filter chain.
- */
-enum group_completion_status {
- /** No complete group received so far. */
- GCS_NO_COMPLETE_GROUP,
- /** First group received, but not yet decoded. */
- GCS_FIRST_GROUP_COMPLETE,
- /** At least one complete group decoded. */
- GCS_FIRST_GROUP_DECODED,
-};
-
/**
* Data private to the fecdec filter.
*/
struct fecdec_group groups[NUM_FEC_GROUPS];
/** Whether an audio file header was already received. */
int have_header;
- /** See \ref group_completion_status. */
- unsigned completion_status;
/** Points to the first received group. */
struct fecdec_group *first_complete_group;
};
/** Iterate over all fecdec groups. */
#define FOR_EACH_FECDEC_GROUP(g, d) for (g = (d)->groups; \
- (g) - (d)->groups < NUM_FEC_GROUPS; (g)++)
+ (g) < (d)->groups + NUM_FEC_GROUPS; (g)++)
static int group_complete(struct fecdec_group *fg)
{
fg->idx[i] = -1;
}
free(fg->data);
+ fg->data = NULL;
free(fg->idx);
+ fg->idx = NULL;
fg->num_slices = 0;
memset(&fg->h, 0, sizeof(struct fec_header));
fg->num_received_slices = 0;
* Don't clear the first complete group if it has not yet been
* decoded.
*/
- if (pfd->completion_status == GCS_FIRST_GROUP_COMPLETE
- && pfd->first_complete_group == fg)
+ if (fg == pfd->first_complete_group)
continue;
clear_group(fg);
return fg;
PARA_WARNING_LOG("Clearing incomplete group %d "
"(contains %d slices)\n", oldest->h.group_num,
oldest->num_received_slices);
- assert(pfd->completion_status != GCS_FIRST_GROUP_COMPLETE
- || oldest != pfd->first_complete_group);
+ if (oldest == pfd->first_complete_group)
+ pfd->first_complete_group = NULL;
clear_group(oldest);
return oldest;
}
FEC_GROUP_UNUSABLE,
FEC_GROUP_USABLE,
FEC_GROUP_USABLE_SKIP_HEADER,
+ FEC_GROUP_USABLE_WITH_HEADER
};
static enum fec_group_usability group_is_usable(struct fecdec_group *fg,
if (fg->h.bos)
return FEC_GROUP_USABLE;
if (fg->h.audio_header_size)
- return FEC_GROUP_USABLE;
+ return FEC_GROUP_USABLE_WITH_HEADER;
return FEC_GROUP_UNUSABLE;
}
static int decode_group(struct fecdec_group *fg, struct filter_node *fn)
{
int i, ret, sb = fg->h.slice_bytes;
- size_t written = 0, need;
+ size_t written, need;
struct private_fecdec_data *pfd = fn->private_data;
enum fec_group_usability u = group_is_usable(fg, pfd);
PARA_INFO_LOG("increasing fec buf to %zu\n", fn->bufsize);
fn->buf = para_realloc(fn->buf, fn->bufsize);
}
+ if (u == FEC_GROUP_USABLE_WITH_HEADER) {
+ PARA_INFO_LOG("writing audio file header\n");
+ written = 0;
+ for (i = 0; i < fg->h.data_slices_per_group; i++) {
+ size_t n = sb;
+ if (written >= fg->h.audio_header_size)
+ break;
+ if (sb + written > fg->h.audio_header_size)
+ n = fg->h.audio_header_size - written;
+ memcpy(fn->buf + fn->loaded, fg->data[i], n);
+ fn->loaded += n;
+ written += n;
+ }
+ }
+ written = 0;
for (; i < fg->h.data_slices_per_group; i++) {
size_t n = sb;
if (n + written > fg->h.group_bytes)
ret = get_group(h, pfd, &fg);
if (ret < 0)
return ret;
- if (!add_slice(buf, fg))
+ if (!add_slice(buf, fg)) /* group already complete */
return 1;
- if (group_complete(fg)) {
- if (pfd->completion_status == GCS_NO_COMPLETE_GROUP) {
- pfd->completion_status = GCS_FIRST_GROUP_COMPLETE;
- pfd->first_complete_group = fg;
+ if (!group_complete(fg))
+ return 1;
+ /* this slice completed the group */
+ if (pfd->fec)
+ goto decode;
+ /* it's either the first or the second complete group */
+ if (!pfd->first_complete_group) { /* it's the first group */
+ enum fec_group_usability u = group_is_usable(fg, pfd);
+ assert(u != FEC_GROUP_USABLE_SKIP_HEADER);
+ if (u == FEC_GROUP_UNUSABLE) /* forget it */
return 1;
- }
- assert(pfd->fec);
- ret = decode_group(fg, fn);
- if (ret < 0)
- return ret;
+ pfd->first_complete_group = fg; /* remember it */
return 1;
}
- if (pfd->completion_status == GCS_NO_COMPLETE_GROUP)
- return 1;
- if (pfd->completion_status == GCS_FIRST_GROUP_DECODED)
- return 1;
- if (fg == pfd->first_complete_group)
- return 1;
- assert(!pfd->fec);
+ /* we have two complete groups, let's go */
k = h->data_slices_per_group;
n = h->slices_per_group;
PARA_NOTICE_LOG("init fec (%d, %d)\n", k, n);
ret = fec_new(k, n, &pfd->fec);
if (ret < 0)
return ret;
+ /* decode and clear the first group */
ret = decode_group(pfd->first_complete_group, fn);
if (ret < 0)
return ret;
- pfd->completion_status = GCS_FIRST_GROUP_DECODED;
+ clear_group(pfd->first_complete_group);
+ pfd->first_complete_group = NULL;
+decode:
+ ret = decode_group(fg, fn);
+ if (ret < 0)
+ return ret;
return 1;
}
fn->bufsize = FECDEC_DEFAULT_OUTBUF_SIZE;
fn->buf = para_malloc(fn->bufsize);
pfd = para_calloc(sizeof(*pfd));
- pfd->completion_status = GCS_NO_COMPLETE_GROUP;
fn->private_data = pfd;
fn->loaded = 0;
}
phsd->status = HTTP_CONNECTED;
}
-static void http_pre_select(int *max_fileno, fd_set *rfds, __a_unused fd_set *wfds)
+static void http_pre_select(int *max_fileno, fd_set *rfds, fd_set *wfds)
{
struct sender_client *sc, *tmp;
struct private_http_sender_data *phsd = sc->private_data;
if (phsd->status == HTTP_CONNECTED) /* need to recv get request */
para_fd_set(sc->fd, rfds, max_fileno);
+ if (phsd->status == HTTP_GOT_GET_REQUEST ||
+ phsd->status == HTTP_INVALID_GET_REQUEST)
+ para_fd_set(sc->fd, wfds, max_fileno);
}
}
struct private_oss_write_data *powd = wn->private_data;
struct writer_node_group *wng = wn->wng;
- if (!*wng->loaded)
+ if (*wng->loaded - wn->written < powd->bytes_per_frame)
return 0;
para_fd_set(powd->fd, &s->wfds, &s->max_fileno);
return 1;
}
-static int oss_post_select(__a_unused struct sched *s,
- struct writer_node *wn)
+
+static int oss_post_select(struct sched *s, struct writer_node *wn)
{
int ret;
struct private_oss_write_data *powd = wn->private_data;
#include <sys/types.h>
#include <dirent.h>
-#include <CoreAudio/CoreAudio.h>
+
#include "para.h"
#include "fd.h"
#include "string.h"
#include "osx_write.cmdline.h"
#include "error.h"
-
-#include <CoreAudio/CoreAudio.h>
+#include <CoreServices/CoreServices.h>
#include <AudioUnit/AudioUnit.h>
-#include <AudioToolbox/DefaultAudioOutput.h>
+#include <AudioToolbox/AudioToolbox.h>
/** describes one input buffer for the osx writer */
struct osx_buffer {
if (ret < 0)
goto out;
}
- sc->header_sent = 1;
}
+ sc->header_sent = 1;
ret = send_queued_chunks(sc->fd, sc->cq, max_bytes_per_write);
if (ret < 0) {
shutdown_client(sc, ss);
static int init_afs(void)
{
int ret, afs_server_socket[2];
+ pid_t afs_pid;
ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, afs_server_socket);
if (ret < 0)
exit(EXIT_FAILURE);
afs_socket_cookie = para_random((uint32_t)-1);
- mmd->afs_pid = fork();
- if (mmd->afs_pid < 0)
+ afs_pid = fork();
+ if (afs_pid < 0)
exit(EXIT_FAILURE);
- if (!mmd->afs_pid) { /* child (afs) */
+ if (afs_pid == 0) { /* child (afs) */
close(afs_server_socket[0]);
afs_init(afs_socket_cookie, afs_server_socket[1]);
}
+ mmd->afs_pid = afs_pid;
close(afs_server_socket[1]);
ret = mark_fd_nonblocking(afs_server_socket[0]);
if (ret < 0)
}
add_close_on_fork_list(ut->fd);
ut->cq = cq_new(UDP_CQ_BYTES);
- PARA_NOTICE_LOG("sending to udp %s#%d using fec parms %d:%d:%d\n",
- ut->host, ut->port , ut->fcp.max_slice_bytes,
- ut->fcp.data_slices_per_group, ut->fcp.slices_per_group);
+ PARA_NOTICE_LOG("sending to udp %s#%d\n", ut->host, ut->port);
return 1;
}
struct user *u;
RSA *rsa;
- ret = para_fgets(line, MAXLINE, file_ptr);
+ ret = para_fgets(line, sizeof(line), file_ptr);
if (ret <= 0)
break;
if (sscanf(line,"%200s %200s %200s %200s", w, n, k, p) < 3)
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.6 (GNU/Linux)
+
+iD8DBQBLHTO5Wto1QDEAkw8RAtzpAKCMU4BmDjwy+GBXUVDTCKkh9nTX3wCfe/2N
+8tyHsmUOB6MVi2CzFzEdzmE=
+=SUFL
+-----END PGP SIGNATURE-----
uint32_t num_chunks;
/** When the first chunk was sent. */
struct timeval start;
+ /** The duration of the full group. */
+ struct timeval duration;
/** The group duration divided by the number of slices. */
struct timeval slice_duration;
/** Group contains the audio file header that occupies that many slices. */
return 1;
}
-static void set_slice_duration(struct fec_client *fc, struct fec_group *g)
+/* set group start and group duration */
+static void set_group_timing(struct fec_client *fc, struct fec_group *g)
{
- struct timeval group_duration, *chunk_tv = vss_chunk_time();
+ struct timeval *chunk_tv = vss_chunk_time();
- tv_scale(g->num_chunks, chunk_tv, &group_duration);
+ tv_scale(g->num_chunks, chunk_tv, &g->duration);
tv_divide(fc->fcp->slices_per_group + fc->num_extra_slices,
- &group_duration, &g->slice_duration);
+ &g->duration, &g->slice_duration);
PARA_DEBUG_LOG("durations (group/chunk/slice): %lu/%lu/%lu\n",
- tv2ms(&group_duration), tv2ms(chunk_tv), tv2ms(&g->slice_duration));
+ tv2ms(&g->duration), tv2ms(chunk_tv), tv2ms(&g->slice_duration));
}
static int setup_next_fec_group(struct fec_client *fc, struct vss_task *vsst)
int ret, i, k, data_slices;
size_t len;
const char *buf, *start_buf;
- struct timeval tmp, *chunk_tv = vss_chunk_time();
struct fec_group *g = &fc->group;
unsigned slice_bytes = fc->fcp->max_slice_bytes - FEC_HEADER_SIZE;
uint32_t max_data_size;
- assert(chunk_tv);
- k = fc->fcp->data_slices_per_group + fc->num_extra_slices;
if (fc->first_stream_chunk < 0) {
- uint32_t largest = afh_get_largest_chunk_size(&mmd->afd.afhi)
- + vsst->header_len;
- uint8_t needed, want;
+ uint8_t hs, ds; /* needed header/data slices */
+ uint8_t rs = fc->fcp->slices_per_group
+ - fc->fcp->data_slices_per_group; /* redundant slices */
+ int n;
- ret = num_slices(largest, fc, &needed);
+ ret = num_slices(vsst->header_len, fc, &hs);
+ if (ret < 0)
+ return ret;
+ ret = num_slices(afh_get_largest_chunk_size(&mmd->afd.afhi),
+ fc, &ds);
+ if (ret < 0)
+ return ret;
+ k = (int)hs + ds;
+ if (k > 255)
+ return -E_BAD_CT;
+ if (k < fc->fcp->data_slices_per_group)
+ k = fc->fcp->data_slices_per_group;
+ n = k + rs;
+ fc->num_extra_slices = k - fc->fcp->data_slices_per_group;
+ PARA_NOTICE_LOG("fec parms %d:%d:%d (%d extra slices)\n",
+ slice_bytes, k, n, fc->num_extra_slices);
+ fec_free(fc->parms);
+ fc->src_data = para_realloc(fc->src_data, k * sizeof(char *));
+ ret = fec_new(k, n, &fc->parms);
if (ret < 0)
return ret;
- if (needed > fc->fcp->data_slices_per_group)
- PARA_WARNING_LOG("fec parms insufficient for this audio file\n");
- want = PARA_MAX(needed, fc->fcp->data_slices_per_group);
- if (want != k) {
- fec_free(fc->parms);
- fc->src_data = para_realloc(fc->src_data, want * sizeof(char *));
- ret = fec_new(want, want + fc->fcp->slices_per_group
- - fc->fcp->data_slices_per_group, &fc->parms);
- if (ret < 0)
- return ret;
- k = want;
- fc->num_extra_slices = 0;
- if (k > fc->fcp->data_slices_per_group) {
- fc->num_extra_slices = k - fc->fcp->data_slices_per_group;
- PARA_NOTICE_LOG("using %d extra slices\n",
- fc->num_extra_slices);
- }
- }
fc->stream_start = *now;
fc->first_stream_chunk = mmd->current_chunk;
g->first_chunk = mmd->current_chunk;
g->num = 0;
+ g->start = *now;
} else {
- /* use duration of the previous group for the timing of this group */
- set_slice_duration(fc, g);
+ /*
+ * Start and duration of this group depend only on the previous
+ * group. Compute the new group start as g->start += g->duration.
+ */
+ struct timeval tmp = g->start;
+ tv_add(&tmp, &g->duration, &g->start);
+ k = fc->fcp->data_slices_per_group + fc->num_extra_slices;
+ set_group_timing(fc, g);
g->first_chunk += g->num_chunks;
g->num++;
}
assert(g->num_chunks);
fc->current_slice_num = 0;
if (g->num == 0)
- set_slice_duration(fc, g);
+ set_group_timing(fc, g);
/* setup header slices */
buf = vsst->header_buf;
g->first_chunk + g->num_chunks - 1,
g->num_header_slices, data_slices
);
- /* set group start */
- if (g->num != 0 && vsst->header_len != 0 && fc->first_stream_chunk == 0)
- /* chunk #0 is the audio file header */
- tv_scale(g->first_chunk - 1, chunk_tv, &tmp);
- else
- tv_scale(g->first_chunk - fc->first_stream_chunk,
- chunk_tv, &tmp);
- tv_add(&fc->stream_start, &tmp, &g->start);
return 1;
}
<h1>Events</h1>
<hr>
<ul>
+ <li>2009-12-07: <a href="versions/paraslash-0.3.6.tar.bz2">paraslash-0.3.6</a>
+ <a href="versions/paraslash-0.3.6.tar.bz2.asc">(sig)</a>
+ "cubic continuity"
+ </li>
<li>2009-09-21: <a href="versions/paraslash-0.3.5.tar.bz2">paraslash-0.3.5</a>
<a href="versions/paraslash-0.3.5.tar.bz2.asc">(sig)</a>
"symplectic separability"
struct task task;
/** Whether the group is open, i.e. wng_open() was called. */
int open;
+ /** Max number of bytes written in the previous post_select() call. */
+ int last_written;
};
/** Loop over each writer node in a writer group. */
/** the array of supported writers */
struct writer writers[NUM_SUPPORTED_WRITERS] = {WRITER_ARRAY};
-static void wng_pre_select(__a_unused struct sched *s, struct task *t)
+static void wng_pre_select(struct sched *s, struct task *t)
{
struct writer_node_group *g = container_of(t, struct writer_node_group, task);
int i;
if (t->error < 0)
return;
}
+ /*
+ * Force a minimal delay if something was written during the previous
+ * call to wng_post_select(). This is necessary because the filter
+ * chain might still have data for us which it couldn't convert during
+ * the previous run due to its buffer size constraints. In this case we
+ * do not want to wait until the next input data arrives as this could
+ * lead to buffer underruns.
+ */
+ if (g->last_written == 0)
+ return;
+ s->timeout.tv_sec = 0;
+ s->timeout.tv_usec = 1;
}
static void wng_post_select(struct sched *s, struct task *t)
{
struct writer_node_group *g = container_of(t, struct writer_node_group, task);
int i;
- size_t min_written = 0;
+ size_t min_written = 0, max_written = 0;
FOR_EACH_WRITER_NODE(i, g) {
struct writer_node *wn = &g->writer_nodes[i];
min_written = wn->written;
else
min_written = PARA_MIN(min_written, wn->written);
+ max_written = PARA_MAX(max_written, wn->written);
}
+ g->last_written = max_written;
//PARA_INFO_LOG("loaded: %zd, min_written: %zd bytes\n", *g->loaded, min_written);
if (min_written) {
*g->loaded -= min_written;