Q =
endif
-.PHONY: all clean distclean maintainer-clean install man tarball\
+.PHONY: dep all clean distclean maintainer-clean install man tarball\
.FORCE-GIT-VERSION-FILE
-all: @executables@ $(man_pages)
+all: dep @executables@ $(man_pages)
+dep: $(deps)
man: $(man_pages)
tarball: $(tarball)
$(audiod_objs) $(audioc_objs) $(fade_objs) $(server_objs) \
$(write_objs) $(afh_objs)
+deps := $(all_objs:.o=.d)
+
ifeq ($(findstring clean, $(MAKECMDGOALS)),)
--include $(all_objs:.o=.d)
+-include $(deps)
endif
para_recv: $(recv_objs)
0.4.6 (to be announced) "deterministic entropy"
-----------------------------------------------
+Lots of ogg/vorbis improvements, enhancements for para_gui and a fair
+amount of other bug fixes.
+
- For DCCP/OGG streams the audio file header is only sent once
at the beginning of the stream rather than periodically
every five seconds. This reduces network traffic and the
FEC group size.
- - aacdec error message cleanups
- - simplified color error handling
+ - The vorbis comment header is replaced by an empty dummy header
+ before the header is sent over the network. This also results in
+ less network traffic and smaller FEC groups.
+ - ogg timing fixes and performance improvements
- para_gui: New option --theme to select a startup theme. Several
other improvements and fixes.
+ - aacdec error message cleanups
+ - simplified color error handling
--------------------------------------------
0.4.5 (2010-12-17) "symmetric randomization"
}
}
-static int cat_file(void *audio_file_data, struct afh_info *afhi)
+static int cat_file(struct afh_info *afhi, int audio_format_id,
+ void *audio_file_data, size_t audio_file_size)
{
int ret;
struct timeval stream_start;
long unsigned i, first_chunk, last_chunk;
const char *buf;
+ char *header;
size_t size;
-
if (conf.begin_chunk_arg < 0) {
if (-conf.begin_chunk_arg > afhi->chunks_total)
return -ERRNO_TO_PARA_ERROR(EINVAL);
return -ERRNO_TO_PARA_ERROR(EINVAL);
if (!afhi->chunks_total)
return 1;
- afh_get_header(afhi, audio_file_data, &buf, &size);
- if (size && first_chunk && !conf.no_header_given) {
- PARA_INFO_LOG("writing audio file header (%zu bytes)\n", size);
- ret = write(STDOUT_FILENO, buf, size);
- if (ret < 0)
- return ret;
- if (ret != size)
- return -E_AFH_SHORT_WRITE;
+ if (first_chunk > 0 && !conf.no_header_given) {
+ afh_get_header(afhi, audio_format_id, audio_file_data, audio_file_size,
+ &header, &size);
+ if (size > 0) {
+ PARA_INFO_LOG("writing header (%zu bytes)\n", size);
+ ret = write(STDOUT_FILENO, header, size); /* FIXME */
+ afh_free_header(header, audio_format_id);
+ if (ret < 0)
+ return ret;
+ if (ret != size)
+ return -E_AFH_SHORT_WRITE;
+ }
}
PARA_NOTICE_LOG("writing chunks %lu - %lu\n", first_chunk, last_chunk);
gettimeofday(&stream_start, NULL);
fd, &afhi);
if (ret < 0)
goto out;
+
audio_format_num = ret;
if (conf.stream_given)
- ret = cat_file(audio_file_data, &afhi);
+ ret = cat_file(&afhi, audio_format_num,
+ audio_file_data, audio_file_size);
else {
printf("File %d: %s\n", i + 1, conf.inputs[i]);
print_info(audio_format_num, &afhi);
if (conf.chunk_table_given)
print_chunk_table(&afhi);
- free(afhi.techinfo);
- free(afhi.tags.artist);
- free(afhi.tags.title);
- free(afhi.tags.year);
- free(afhi.tags.album);
- free(afhi.tags.comment);
- free(afhi.chunk_table);
printf("\n");
}
+ free(afhi.techinfo);
+ free(afhi.tags.artist);
+ free(afhi.tags.title);
+ free(afhi.tags.year);
+ free(afhi.tags.album);
+ free(afhi.tags.comment);
+ free(afhi.chunk_table);
ret2 = para_munmap(audio_file_data, audio_file_size);
if (ret2 < 0 && ret >= 0)
ret = ret2;
* for all supported audio formats.
*/
struct taginfo {
- /** TPE1 (id3v2) / ARTIST (vorbis) / ©ART (aac) */
+ /** TPE1 (id3v2) / ARTIST (vorbis) / ART (aac)/ author(spx) */
char *artist;
- /** TIT2/TITLE/©nam */
+ /** TIT2/TITLE/nam */
char *title;
- /** TDRC/YEAR/©day */
+ /** TDRC/YEAR/day */
char *year;
- /** TALB/ALBUM/©alb */
+ /** TALB/ALBUM/alb */
char *album;
- /** COMM/COMMENT/©cmt */
+ /** COMM/COMMENT/cmt */
char *comment;
};
uint32_t *chunk_table;
/** Period of time between sending data chunks. */
struct timeval chunk_tv;
- /**
- * The position of the header within the audio file. Ignored if \a
- * header_len equals zero.
- */
- uint32_t header_offset;
/**
* The header is needed by senders in case a new client connects in the
* middle of the stream. The length of the header defaults to zero
*/
int (*get_file_info)(char *map, size_t numbytes, int fd,
struct afh_info *afi);
+
+ void (*get_header)(void *map, size_t mapsize, char **buf, size_t *len);
};
void afh_init(void);
const char *audio_format_name(int);
void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
void *map, const char **buf, size_t *len);
-void afh_get_header(struct afh_info *afhi, void *map, const char **buf, size_t *len);
+uint32_t afh_get_largest_chunk_size(struct afh_info *afhi);
+void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id,
+ void *map, size_t mapsize, char **buf, size_t *len);
+void afh_free_header(char *header_buf, uint8_t audio_format_id);
{
int ret, i, format;
- afhi->header_offset = 0;
afhi->header_len = 0;
afhi->techinfo = NULL;
afhi->tags.artist = NULL;
* Get the header of an audio file.
*
* \param afhi The audio file handler data describing the file.
+ * \param audio_format_id Determines the audio format handler.
* \param map The data of the audio file.
+ * \param mapsize The amount of bytes of the mmapped audio file.
* \param buf The length of the header is stored here.
* \param len Points to a buffer containing the header on return.
*
* This function sets \a buf to \p NULL and \a len to zero if \a map or \a
* afhi is \p NULL, or if the current audio format does not need special
* header treatment.
+ *
+ * Otherwise, it is checked whether the audio format handler given by
+ * \a audio_format_id defines a ->get_header() method. If it does, this
+ * method is called to obtain the header. If ->get_header() is \p NULL,
+ * a reference to the first chunk of the audio file is returned.
+ *
+ * Once the header is no longer needed, the caller must call \ref
+ * afh_free_header() to free the resources allocated by this function.
*/
-void afh_get_header(struct afh_info *afhi, void *map, const char **buf, size_t *len)
+void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id,
+ void *map, size_t mapsize, char **buf, size_t *len)
{
+ struct audio_format_handler *afh = afl + audio_format_id;
+
if (!map || !afhi || !afhi->header_len) {
*buf = NULL;
*len = 0;
return;
}
- *len = afhi->header_len;
- *buf = map + afhi->header_offset;
+ if (!afh->get_header) {
+ *len = afhi->header_len;
+ *buf = map;
+ return;
+ }
+ afh->get_header(map, mapsize, buf, len);
+}
+
+/**
+ * Deallocate any resources obtained from afh_get_header().
+ *
+ * \param header_buf Pointer obtained via afh_get_header().
+ * \param audio_format_id Determines the audio format handler.
+ */
+void afh_free_header(char *header_buf, uint8_t audio_format_id)
+{
+ struct audio_format_handler *afh = afl + audio_format_id;
+
+ if (afh->get_header)
+ free(header_buf);
}
}
ret = open_and_update_audio_file(aft_row, score, &afd);
if (ret < 0) {
- PARA_ERROR_LOG("%s\n", para_strerror(-ret));
ret = score_delete(aft_row);
if (ret < 0) {
PARA_ERROR_LOG("%s\n", para_strerror(-ret));
ct->task.pre_select = command_pre_select;
ct->task.post_select = command_post_select;
- sprintf(ct->task.status, "command task");
+ sprintf(ct->task.status, "afs command task");
register_task(&ct->task);
}
uint32_t image_id;
/** Lyrics blob associated with this file (foreign key). */
uint32_t lyrics_id;
- /** Mp3, ogg or aac. */
+ /** Mp3, ogg, aac, wma, spx. */
uint8_t audio_format_id;
/** Amplification value. */
uint8_t amp;
struct afh_info afhi;
/** Size of the largest chunk. */
uint32_t max_chunk_size;
+ /** Needed to get the audio file header. */
+ uint8_t audio_format_id;
};
/**
AFHI_BITRATE_OFFSET = 4,
/** Position of the frequency. */
AFHI_FREQUENCY_OFFSET = 8,
- /** Location of the audio file header. */
- AFHI_HEADER_OFFSET_OFFSET = 12,
+ /** Was: Location of the audio file header. */
+ AFHI_UNUSED1_OFFSET = 12,
/* Length of the audio file header. Zero means: No header. */
AFHI_HEADER_LEN_OFFSET = 16,
/** The total number of chunks (4 bytes). */
CHUNKS_TOTAL_OFFSET = 20,
/** The length of the audio file header (4 bytes). */
HEADER_LEN_OFFSET = 24,
- /** The start of the audio file header (4 bytes). */
- HEADER_OFFSET_OFFSET = 28,
+ /** Was: The start of the audio file header (4 bytes). */
+ AFHI_UNUSED2_OFFSET = 28,
/** The seconds part of the chunk time (4 bytes). */
CHUNK_TV_TV_SEC_OFFSET = 32,
/** The microseconds part of the chunk time (4 bytes). */
write_u32(buf + AFHI_SECONDS_TOTAL_OFFSET, afhi->seconds_total);
write_u32(buf + AFHI_BITRATE_OFFSET, afhi->bitrate);
write_u32(buf + AFHI_FREQUENCY_OFFSET, afhi->frequency);
- write_u32(buf + AFHI_HEADER_OFFSET_OFFSET, afhi->header_offset);
+ write_u32(buf + AFHI_UNUSED1_OFFSET, 0);
write_u32(buf + AFHI_HEADER_LEN_OFFSET, afhi->header_len);
write_u8(buf + AFHI_CHANNELS_OFFSET, afhi->channels);
write_u32(buf + CHUNKS_TOTAL_OFFSET, afhi->chunks_total);
write_u32(buf + HEADER_LEN_OFFSET, afhi->header_len);
- write_u32(buf + HEADER_OFFSET_OFFSET, afhi->header_offset);
+ write_u32(buf + AFHI_UNUSED2_OFFSET, 0);
write_u32(buf + CHUNK_TV_TV_SEC_OFFSET, afhi->chunk_tv.tv_sec);
write_u32(buf + CHUNK_TV_TV_USEC_OFFSET, afhi->chunk_tv.tv_usec);
p = buf + AFHI_INFO_STRING_OFFSET;
afhi->seconds_total = read_u32(buf + AFHI_SECONDS_TOTAL_OFFSET);
afhi->bitrate = read_u32(buf + AFHI_BITRATE_OFFSET);
afhi->frequency = read_u32(buf + AFHI_FREQUENCY_OFFSET);
- afhi->header_offset = read_u32(buf + AFHI_HEADER_OFFSET_OFFSET);
afhi->header_len = read_u32(buf + AFHI_HEADER_LEN_OFFSET);
afhi->channels = read_u8(buf + AFHI_CHANNELS_OFFSET);
afhi->chunks_total = read_u32(buf + CHUNKS_TOTAL_OFFSET);
afhi->header_len = read_u32(buf + HEADER_LEN_OFFSET);
- afhi->header_offset = read_u32(buf + HEADER_OFFSET_OFFSET);
afhi->chunk_tv.tv_sec = read_u32(buf + CHUNK_TV_TV_SEC_OFFSET);
afhi->chunk_tv.tv_usec = read_u32(buf + CHUNK_TV_TV_USEC_OFFSET);
afhi->techinfo = (char *)buf + AFHI_INFO_STRING_OFFSET;
new_afsi.last_played = time(NULL);
save_afsi(&new_afsi, &afsi_obj); /* in-place update */
+ afd->audio_format_id = old_afsi.audio_format_id;
load_chunk_table(&afd->afhi, chunk_table_obj.data);
ret = make_status_items(afd, &old_afsi, path, score, file_hash);
if (ret < 0)
err:
free(afd->afhi.chunk_table);
osl_close_disk_object(&chunk_table_obj);
+ if (ret < 0)
+ PARA_ERROR_LOG("%s: %s\n", path, para_strerror(-ret));
return ret;
}
* \return The audio format number on success, -E_UNSUPPORTED_AUDIO_FORMAT if
* \a name is not a supported audio format.
*/
-int get_audio_format_num(const char *name)
+static int get_audio_format_num(const char *name)
{
int i;
return -E_NO_MORE_SLOTS;
}
-/**
- * get the number of filters
- *
- * \param audio_format_num the number identifying the audio format
- *
- * \return the number of filters for the given audio format
- *
- * \sa struct filter;
- */
-int num_filters(int audio_format_num)
-{
- return afi[audio_format_num].num_filters;
-}
-
static void open_filters(struct slot_info *s)
{
struct audio_format_info *a = afi + s->format;
N: grab
D: grab the audio stream
L:
-U: -- grab [-m[{s|p|a}]] [-p=<parent>] [-n=<name>] [-o]
+U: -- grab [-m[{s|p|a}]] [-p=PARENT] [-n=NAME] [-o]
H:
H: grab ('splice') the audio stream at any position in the buffer
H: tree and send that data back to the client.
H: the write, pedantic mode aborts and aggressive mode tries
H: to write anyway.
H:
-H: -p Grab output of node <parent> of the buffer tree.
+H: -p Grab output of node PARENT of the buffer tree.
H:
H: -n Name of the new buffer tree node. Defaults to 'grab'.
H:
/** \file audiod.h symbols exported from audiod.c */
-int num_filters(int audio_format_num);
-int get_audio_format_num(const char *name);
-
/** enum of audio formats supported by para_audiod */
enum {AUDIOD_AUDIO_FORMATS_ENUM};
free(info);
}
ut = uptime_str();
- ret = rc4_send_va_buffer(rc4c, "up: %s\nplayed: %u\n"
+ ret = rc4_send_va_buffer(rc4c, "version: " GIT_VERSION "\n"
+ "up: %s\nplayed: %u\n"
"server_pid: %d\n"
"afs_pid: %d\n"
"connections (active/accepted/total): %u/%u/%u\n"
/* we need a blocking fd here as recv() might return EAGAIN otherwise. */
ret = mark_fd_blocking(fd);
if (ret < 0)
- goto err_out;
+ goto net_err;
/* send Welcome message */
ret = send_va_buffer(fd, "This is para_server, version "
PACKAGE_VERSION ".\n" );
if (ret < 0)
- goto err_out;
+ goto net_err;
/* recv auth request line */
ret = recv_buffer(fd, buf, sizeof(buf));
if (ret < 0)
- goto err_out;
+ goto net_err;
if (ret < 10) {
ret = -E_AUTH_REQUEST;
- goto err_out;
+ goto net_err;
}
numbytes = ret;
ret = -E_AUTH_REQUEST;
if (strncmp(buf, AUTH_REQUEST_MSG, strlen(AUTH_REQUEST_MSG)))
- goto err_out;
+ goto net_err;
p = buf + strlen(AUTH_REQUEST_MSG);
PARA_DEBUG_LOG("received auth request for user %s\n", p);
ret = -E_BAD_USER;
ret = para_encrypt_buffer(u->rsa, rand_buf, sizeof(rand_buf),
(unsigned char *)buf);
if (ret < 0)
- goto err_out;
+ goto net_err;
numbytes = ret;
} else {
/*
ipc dccp_send fd user_list chunk_queue afs aft mood score attribute
blob playlist sha1 sched acl send_common udp_send color fec
server_command_list afs_command_list wma_afh wma_common"
-server_ldflags="-losl"
+server_ldflags=""
server_audio_formats="mp3 wma"
write_cmdline_objs="add_cmdline(write file_write)"
extras="$extras server"
executables="$executables server"
AC_SUBST(osl_cppflags)
- server_ldflags="$server_ldflags -L$with_osl_libs"
+ server_ldflags="$server_ldflags $osl_libs -losl"
fi
CPPFLAGS="$OLD_CPPFLAGS"
LDFLAGS="$OLD_LDFLAGS"
return ret < 0? -E_ENCRYPT : ret;
}
+#define RC4_ALIGN 8
+
/**
* Encrypt and send a buffer.
*
{
int ret;
unsigned char *tmp;
+ static unsigned char remainder[RC4_ALIGN];
+ size_t l1 = ROUND_DOWN(len, RC4_ALIGN), l2 = ROUND_UP(len, RC4_ALIGN);
assert(len);
- tmp = para_malloc(len + 8);
- RC4(&rc4c->send_key, len, (const unsigned char *)buf, tmp);
+ tmp = para_malloc(l2);
+ RC4(&rc4c->send_key, l1, (const unsigned char *)buf, tmp);
+ if (len > l1) {
+ memcpy(remainder, buf + l1, len - l1);
+ RC4(&rc4c->send_key, len - l1, remainder, tmp + l1);
+ }
ret = write_all(rc4c->fd, (char *)tmp, &len);
free(tmp);
return ret;
#define OGG_AFH_ERRORS \
PARA_ERROR(VORBIS, "vorbis synthesis header-in error (not vorbis?)"), \
+ PARA_ERROR(OGG_PACKET_IN, "ogg_stream_packetin() failed"), \
+ PARA_ERROR(OGG_STREAM_FLUSH, "ogg_stream_flush() failed"), \
#define VSS_ERRORS \
case mixer_channel_arg_reclev: val = SOUND_MIXER_RECLEV; break;
case mixer_channel_arg_igain: val = SOUND_MIXER_IGAIN; break;
case mixer_channel_arg_ogain: val = SOUND_MIXER_OGAIN; break;
+ default: break;
}
conf.mixer_channel_arg = val;
}
}
fixup_mixer_channel_arg();
switch (conf.mode_arg) {
- case mode_arg_sleep:
- ret = sweet_dreams();
- break;
case mode_arg_fade:
ret = fade(conf.fade_vol_arg, conf.fade_time_arg);
break;
case mode_arg_snooze:
ret = snooze();
break;
+ default: /* sleep mode */
+ ret = sweet_dreams();
+ break;
}
if (ret < 0)
PARA_EMERG_LOG("%s\n", para_strerror(-ret));
# endif
#define _static_inline_ static inline
+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"Configure filters manually"
flag off
-details="
+details = "
If (and only if) this option is set, the --filter options
- (see below) take effect. Otherwise, the compiled-in default
- filters mp3dec, oggdec and aacdec are activated for mp3, ogg,
- aac streams respectively.
-
- You have to configure filters manually if you want to use
- the amp filter or the compress filter which are not activated
- by default.
-
- Playing udp streams also requires that the output of the udp
- receiver must be fed into the fecdec filter first to produce
- a stream which can be decoded by the appropriate decoder
- (mp3dec, oggdec, aacdec, wmadec). In other words, the fecdec
- filter should be specified as the first filter of the filter
- configuration for udp streaming.
+ (see below) take effect. Otherwise, the compiled-in defaults
+ apply. These defaults depend on the receiver being used as
+ described below.
+
+ For http streams, only a single filter per audio format,
+ the decoder for that format, is activated. On the other hand,
+ since udp and dccp streams are sent fec-encoded by para_server,
+ the client side must feed the output of the receiver into
+ the fecdec filter first. Therefore the default for udp and
+ dccp streams is to activate the fecdec filter, followed by
+ the appropriate decoder.
+
+ You must give this option if you want to use any other filter,
+ for example the amp or the compress filter.
+
"
option "filter" f
rbe = ringbuffer_get(bot_win_rb, fvr);
if (!rbe)
return -1;
- /* first rbe might be only partially visible */
- offset = (*lines - bot.lines) * bot.cols;
len = strlen(rbe->msg);
- if (offset < 0 || len < offset)
- return -1;
+ if (*lines > bot.lines) {
+ /* first rbe is only partially visible */
+ offset = (*lines - bot.lines) * bot.cols;
+ assert(offset <= len);
+ } else
+ offset = 0;
wattron(bot.win, COLOR_PAIR(rbe->color));
waddstr(bot.win, rbe->msg + offset);
*lines = NUM_LINES(len - offset);
};
static int vorbis_packet_callback(ogg_packet *packet, int packet_num,
- struct afh_info *afhi, void *private_data)
+ __a_unused int serial, struct afh_info *afhi, void *private_data)
{
struct private_vorbis_data *pvd = private_data;
return ret;
}
+struct vorbis_get_header_data {
+ ogg_stream_state os;
+ char *buf;
+ size_t len;
+};
+
+static void add_ogg_page(ogg_page *og, struct vorbis_get_header_data *vghd)
+{
+ size_t old_len = vghd->len;
+ size_t new_len = vghd->len + og->header_len + og->body_len;
+ char *buf = para_realloc(vghd->buf, new_len), *p = buf + old_len;
+
+ memcpy(p, og->header, og->header_len);
+ memcpy(p + og->header_len, og->body, og->body_len);
+ vghd->buf = buf;
+ vghd->len = new_len;
+ PARA_DEBUG_LOG("header/body/old/new: %lu/%lu/%zu/%zu\n",
+ og->header_len, og->body_len, old_len, new_len);
+}
+
+static int vorbis_get_header_callback(ogg_packet *packet, int packet_num,
+ int serial, __a_unused struct afh_info *afhi, void *private_data)
+{
+ int ret;
+ struct vorbis_get_header_data *vghd = private_data;
+ ogg_page og;
+ static unsigned char dummy_packet[] = {
+ 0x03,
+ 'v', 'o', 'r', 'b', 'i', 's',
+ 0x06, 0x00, 0x00, 0x00,
+ 'd', 'u', 'm', 'm', 'y', '\0',
+ 0x00, 0x00, 0x00, 0x00, /* no comment :) */
+ 0xff /* framing bit */
+ };
+
+ PARA_DEBUG_LOG("processing ogg packet #%d\n", packet_num);
+ if (packet_num > 2)
+ return 0;
+ if (packet_num == 0) {
+ ogg_stream_init(&vghd->os, serial);
+ ret = -E_OGG_PACKET_IN;
+ ret = ogg_stream_packetin(&vghd->os, packet);
+ if (ret < 0)
+ goto out;
+ ret = -E_OGG_STREAM_FLUSH;
+ if (ogg_stream_flush(&vghd->os, &og) == 0)
+ goto out;
+ add_ogg_page(&og, vghd);
+ return 1;
+ }
+ if (packet_num == 1) {
+ PARA_INFO_LOG("replacing metadata packet\n");
+ ogg_packet replacement = *packet;
+ replacement.packet = dummy_packet;
+ replacement.bytes = sizeof(dummy_packet);
+ ret = ogg_stream_packetin(&vghd->os, &replacement);
+ if (ret >= 0)
+ return 1;
+ ret = -E_OGG_PACKET_IN;
+ goto out;
+ }
+ ret = -E_OGG_PACKET_IN;
+ if (ogg_stream_packetin(&vghd->os, packet) < 0)
+ goto out;
+ while (ogg_stream_flush(&vghd->os, &og))
+ add_ogg_page(&og, vghd);
+ ret = 0;
+out:
+ ogg_stream_clear(&vghd->os);
+ return ret;
+}
+
+static void vorbis_get_header(void *map, size_t mapsize, char **buf,
+ size_t *len)
+{
+ int ret;
+ struct vorbis_get_header_data vghd = {.len = 0};
+ struct ogg_afh_callback_info cb = {
+ .packet_callback = vorbis_get_header_callback,
+ .private_data = &vghd,
+ };
+
+ ret = ogg_get_file_info(map, mapsize, NULL, &cb);
+ if (ret < 0)
+ goto fail;
+ *buf = vghd.buf;
+ *len = vghd.len;
+ PARA_INFO_LOG("created %zu byte ogg vorbis header\n", *len);
+ return;
+fail:
+ PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+}
+
static const char* ogg_suffixes[] = {"ogg", NULL};
/**
*/
void ogg_init(struct audio_format_handler *afh)
{
- afh->get_file_info = ogg_vorbis_get_file_info,
+ afh->get_file_info = ogg_vorbis_get_file_info;
+ afh->get_header = vorbis_get_header;
afh->suffixes = ogg_suffixes;
}
* apparent at packetout.
*/
ogg_stream_pagein(stream, &page);
+ PARA_INFO_LOG("ogg page serial: %d\n",
+ ogg_page_serialno(&page));
while (i < 2) {
ret = ogg_stream_packetout(stream, &packet);
if (ret == 0)
break;
if (ret < 0)
return -E_STREAM_PACKETOUT;
- ret = ci->packet_callback(&packet, i + 1, afhi,
+ ret = ci->packet_callback(&packet, i + 1,
+ ogg_page_serialno(&page), afhi,
ci->private_data);
if (ret < 0)
return ret;
ret = -E_STREAM_PACKETOUT;
if (ogg_stream_packetout(&stream, &packet) != 1)
goto out;
- ret = ci->packet_callback(&packet, 0, afhi, ci->private_data);
+ ret = ci->packet_callback(&packet, 0, ogg_page_serialno(&page),
+ afhi, ci->private_data);
if (ret < 0)
goto out;
ret = process_packets_2_and_3(oss, &stream, afhi, ci);
if (ret < 0)
goto out;
- afhi->header_offset = 0;
- afhi->header_len = oss->returned;
ret = 1;
out:
ogg_stream_clear(&stream);
* given by \a map and \a numbytes and passes each packet to the callback
* defined by \a ci.
*
- * If the packet callback indicates success, the chunk table is built. Chunk
- * zero contains the first three ogg packets while all other chunks consist of
- * exactly one ogg page.
+ * If the packet callback indicates success and \a afhi is not \p NULL, the
+ * chunk table is built. Chunk zero contains the first three ogg packets while
+ * all other chunks consist of exactly one ogg page.
*
* \param map Audio file data.
* \param numbytes The length of \a map.
ret = process_ogg_packets(&oss, afhi, ci);
if (ret < 0)
goto out;
+ if (!afhi)
+ goto out;
+ afhi->header_len = oss.returned;
oss.returned = 0;
oss.fill = numbytes;
/* count ogg pages and get duration of the file */
* reached and no further ogg packets should be processed.
*/
int (*packet_callback)(ogg_packet *packet, int packet_num,
- struct afh_info *afhi, void *private_data);
+ int serial, struct afh_info *afhi, void *private_data);
/** Vorbis/speex specific data. */
void *private_data;
};
return ((max + 0.0) * (random() / (RAND_MAX + 1.0)));
}
+/** Round up x to next multiple of y. */
+#define ROUND_UP(x, y) ({ \
+ const typeof(y) _divisor = y; \
+ ((x) + _divisor - 1) / _divisor * _divisor; })
+
+/** Round down x to multiple of y. */
+#define ROUND_DOWN(x, y) ({ \
+ const typeof(y) _divisor = y; \
+ (x) / _divisor * _divisor; })
+
/** Divide and round up to next integer. */
#define DIV_ROUND_UP(x, y) ({ \
typeof(y) _divisor = y; \
((x) + _divisor - 1) / _divisor; })
+/**
+ * Assert a build-time dependency, as an expression.
+ *
+ * \param cond The compile-time condition which must be true.
+ *
+ * Compilation will fail if the condition isn't true, or can't be evaluated by
+ * the compiler. This can be used in an expression: its value is "0".
+ *
+ * Taken from ccan.
+ */
+#define EXPR_BUILD_ASSERT(cond) (sizeof(char [1 - 2 * !(cond)]) - 1)
+
+/* &a[0] degrades to a pointer: a different type from an array */
+#define _array_size_chk(arr) EXPR_BUILD_ASSERT(\
+ !__builtin_types_compatible_p(typeof(arr), typeof(&(arr)[0])))
/** Get the size of an array */
-#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + _array_size_chk(arr))
/**
* Wrapper for isspace.
*/
static int score_compare(const struct osl_object *obj1, const struct osl_object *obj2)
{
- int d1 = *(int*)obj1->data;
- int d2 = *(int*)obj2->data;
+ long d1 = *(long *)obj1->data;
+ long d2 = *(long *)obj2->data;
int ret = NUM_COMPARE(d2, d1);
if (ret)
size = score_table_desc.column_descriptions[SCORECOL_SCORE].data_size;
score_objs[SCORECOL_SCORE].data = para_malloc(size);
score_objs[SCORECOL_SCORE].size = size;
- *(int *)(score_objs[SCORECOL_SCORE].data) = score;
+ *(long *)(score_objs[SCORECOL_SCORE].data) = score;
// PARA_DEBUG_LOG("adding %p\n", *(void **) (score_objs[SCORECOL_AFT_ROW].data));
ret = osl(osl_add_row(score_table, score_objs));
if (ret < 0)
goto err;
add_close_on_fork_list(sct->listen_fd); /* child doesn't need the listener */
+ sprintf(sct->task.status, "server command task");
register_task(&sct->task);
return;
err:
static const char* speex_suffixes[] = {"spx", "speex", NULL};
static int spx_packet_callback(ogg_packet *packet, int packet_num,
- struct afh_info *afhi, void *private_data)
+ __a_unused int serial, struct afh_info *afhi,
+ void *private_data)
{
struct private_spx_data *psd = private_data;
int ret;
struct task task;
/** Stdin is always the root of a buffer tree. */
struct btr_node *btrn;
- /* Use a buffer pool to minimize memcpy due to alignment problems. */
+ /** Use a buffer pool to minimize memcpy due to alignment problems. */
struct btr_pool *btrp;
};
/** Used by the scheduler. */
struct task task;
/** Pointer to the header of the mapped audio file. */
- const char *header_buf;
+ char *header_buf;
/** Length of the audio file header. */
size_t header_len;
/** Time between audio file headers are sent. */
struct timeval next_header_time;
/** Used for the last source pointer of an audio file. */
unsigned char *extra_src_buf;
+ /** Needed for the last slice of the audio file header. */
+ unsigned char *extra_header_buf;
/** Extra slices needed to store largest chunk + header. */
int num_extra_slices;
/** Contains the FEC-encoded data. */
write_u32(buf + 14, g->bytes);
write_u8(buf + 18, fc->current_slice_num);
+ write_u8(buf + 19, 0); /* unused */
write_u16(buf + 20, g->slice_bytes);
write_u8(buf + 22, g->first_chunk? 0 : 1);
write_u8(buf + 23, vsst->header_len? 1 : 0);
- memset(buf + 24, 0, 7);
+ memset(buf + 24, 0, 8);
}
static bool need_audio_header(struct fec_client *fc, struct vss_task *vsst)
fc->src_data = para_realloc(fc->src_data, k * sizeof(char *));
fc->enc_buf = para_realloc(fc->enc_buf, fc->mps);
fc->extra_src_buf = para_realloc(fc->extra_src_buf, fc->mps);
+ fc->extra_header_buf = para_realloc(fc->extra_header_buf, fc->mps);
fc->state = FEC_STATE_READY_TO_RUN;
fc->next_header_time.tv_sec = 0;
return 1;
}
+static void vss_get_chunk(int chunk_num, struct vss_task *vsst,
+ char **buf, size_t *sz)
+{
+ /*
+ * Chunk zero is special for header streams: It is the first portion of
+ * the audio file which consists of the audio file header. It may be
+ * arbitrary large due to embedded meta data. Audio format handlers may
+ * replace the header by a stripped one with meta data omitted which is
+ * of bounded size. We always use the stripped header for streaming
+ * rather than the unmodified header (chunk zero).
+ */
+ if (chunk_num == 0 && vsst->header_len > 0) {
+ *buf = vsst->header_buf; /* stripped header */
+ *sz = vsst->header_len;
+ return;
+ }
+ afh_get_chunk(chunk_num, &mmd->afd.afhi, vsst->map, (const char **)buf,
+ sz);
+}
+
static void compute_group_size(struct vss_task *vsst, struct fec_group *g,
int max_bytes)
{
* of exactly one chunk for these audio formats.
*/
for (i = 0;; i++) {
- const char *buf;
+ char *buf;
size_t len;
int chunk_num = g->first_chunk + i;
break;
if (chunk_num >= mmd->afd.afhi.chunks_total) /* eof */
break;
- afh_get_chunk(chunk_num, &mmd->afd.afhi, vsst->map, &buf, &len);
+ vss_get_chunk(chunk_num, vsst, &buf, &len);
if (g->bytes + len > max_bytes)
break;
/* Include this chunk */
{
int ret, i, k, n, data_slices;
size_t len;
- const char *buf;
+ char *buf;
struct fec_group *g = &fc->group;
if (fc->state == FEC_STATE_NONE) {
fc->current_slice_num = 0;
if (g->num == 0)
set_group_timing(fc, vsst);
-
/* setup header slices */
buf = vsst->header_buf;
for (i = 0; i < g->num_header_slices; i++) {
- fc->src_data[i] = (const unsigned char *)buf;
- buf += g->slice_bytes;
+ if (buf + g->slice_bytes <= vsst->header_buf + vsst->header_len) {
+ fc->src_data[i] = (const unsigned char *)buf;
+ buf += g->slice_bytes;
+ continue;
+ }
+ /*
+ * Can not use vss->header_buf for this slice as it
+ * goes beyond the buffer. This slice will not be fully
+ * used.
+ */
+ uint32_t payload_size = vsst->header_buf
+ + vsst->header_len - buf;
+ memcpy(fc->extra_header_buf, buf, payload_size);
+ if (payload_size < g->slice_bytes)
+ memset(fc->extra_header_buf + payload_size, 0,
+ g->slice_bytes - payload_size);
+ fc->src_data[i] = fc->extra_header_buf;
+ assert(i == g->num_header_slices - 1);
}
/* setup data slices */
- afh_get_chunk(g->first_chunk, &mmd->afd.afhi, vsst->map, &buf, &len);
+ vss_get_chunk(g->first_chunk, vsst, &buf, &len);
for (; i < g->num_header_slices + data_slices; i++) {
if (buf + g->slice_bytes > vsst->map + mmd->size) {
/*
* Can not use the memory mapped audio file for this
- * slice as it goes beyond the map. This slice will not
- * be fully used.
+ * slice as it goes beyond the map.
*/
uint32_t payload_size = vsst->map + mmd->size - buf;
memcpy(fc->extra_src_buf, buf, payload_size);
free(fc->src_data);
free(fc->enc_buf);
free(fc->extra_src_buf);
+ free(fc->extra_header_buf);
fec_free(fc->parms);
free(fc);
}
if (mmd->new_vss_status_flags & VSS_NOMORE)
mmd->new_vss_status_flags = VSS_NEXT;
set_eof_barrier(vsst);
+ afh_free_header(vsst->header_buf, mmd->afd.audio_format_id);
+ vsst->header_buf = NULL;
para_munmap(vsst->map, mmd->size);
vsst->map = NULL;
mmd->chunks_sent = 0;
mmd->events++;
mmd->num_played++;
mmd->new_vss_status_flags &= (~VSS_NEXT);
- afh_get_header(&mmd->afd.afhi, vsst->map, &vsst->header_buf,
- &vsst->header_len);
+ afh_get_header(&mmd->afd.afhi, mmd->afd.audio_format_id,
+ vsst->map, mmd->size, &vsst->header_buf, &vsst->header_len);
return;
err:
free(mmd->afd.afhi.chunk_table);
compute_chunk_time(mmd->chunks_sent, &mmd->afd.afhi.chunk_tv,
&mmd->stream_start, &due);
if (tv_diff(&due, now, NULL) <= 0) {
- const char *buf;
+ char *buf;
size_t len;
if (!mmd->chunks_sent) {
* they might have still some data queued which can be sent in
* this case.
*/
- afh_get_chunk(mmd->current_chunk, &mmd->afd.afhi, vsst->map,
- &buf, &len);
+ vss_get_chunk(mmd->current_chunk, vsst, &buf, &len);
for (i = 0; senders[i].name; i++) {
if (!senders[i].send)
continue;
tv_add(&vsst->autoplay_barrier, &vsst->announce_tv,
&vsst->data_send_barrier);
}
+ sprintf(vsst->task.status, "vss task");
register_task(&vsst->task);
}
afhi->frequency = ahi.sample_rate;
afhi->channels = ahi.channels;
afhi->header_len = ahi.header_len;
- afhi->header_offset = 0;
wma_make_chunk_table(map + ahi.header_len, numbytes - ahi.header_len,
ahi.block_align, afhi);
read_asf_tags(map, ahi.header_len, &afhi->tags);