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)
at the beginning of the stream rather than periodically
every five seconds. This reduces network traffic and the
FEC group size.
+ - ogg timing fixes and performance improvements
- aacdec error message cleanups
- simplified color error handling
- para_gui: New option --theme to select a startup theme. Several
}
}
-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);
}
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)
/* 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;
LC_ALL=C gcc -MM -MG "$@" \
| sed -e "s@^\(.*\)\.o:@$object_dir/\1.d $object_dir/\1.o:@" \
- -e "s@[ ^]\([a-zA-Z0-9_]\+\.cmdline.h\)@ $cmdline_dir/\1@g"
+ -e "s@[ ^]\([a-zA-Z0-9_]\{1,\}\.cmdline.h\)@ $cmdline_dir/\1@g"
#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 \
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"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);
return ret;
}
-static void set_chunk_tv(int num_frames, int num_chunks, int frequency,
+static void set_chunk_tv(int frames_per_chunk, int frequency,
struct timeval *result)
{
- uint64_t x = (uint64_t)num_frames * 1000 * 1000
- / frequency / num_chunks;
+ uint64_t x = (uint64_t)frames_per_chunk * 1000 * 1000 / frequency;
result->tv_sec = x / 1000 / 1000;
result->tv_usec = x % (1000 * 1000);
- PARA_INFO_LOG("%d chunks, chunk time: %lums\n", num_chunks,
- tv2ms(result));
+ PARA_INFO_LOG("%d frames per chunk, chunk time: %lums\n",
+ frames_per_chunk, tv2ms(result));
}
/**
* 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 packages and get duration of the file */
+ /* count ogg pages and get duration of the file */
for (i = 0; ogg_sync_pageseek(&oss, &op) > 0; i++)
num_frames = ogg_page_granulepos(&op);
PARA_INFO_LOG("%d pages, %llu frames\n", i, num_frames);
}
}
afhi->chunks_total = j;
- set_chunk_tv(num_frames, j, afhi->frequency, &afhi->chunk_tv);
+ set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv);
ret = 0;
out:
ogg_sync_clear(&oss);
* 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;
};
/** The number of channels of the current stream. */
unsigned int channels;
/** Current sample rate in Hz. */
- struct timeval stream_start;
unsigned int sample_rate;
};
fn->private_data = NULL;
}
-#define OGGDEC_OUTPUT_CHUNK_SIZE (64 * 1024)
+#define OGGDEC_OUTPUT_CHUNK_SIZE (640 * 1024)
static int oggdec_execute(struct btr_node *btrn, const char *cmd, char **result)
{
struct btr_node *btrn = fn->btrn;
int ret, oret;
size_t iqs;
+ struct OggVorbis_File *vf = para_malloc(sizeof(*vf));
- pod->vf = para_malloc(sizeof(struct OggVorbis_File));
PARA_NOTICE_LOG("iqs: %zu, min_iqs: %zu, opening ov callbacks\n",
btr_get_input_queue_size(btrn), fn->min_iqs);
open:
- oret = ov_open_callbacks(fn, pod->vf,
+ oret = ov_open_callbacks(fn, vf,
NULL, /* no initial buffer */
0, /* no initial bytes */
ovc); /* the ov_open_callbacks */
ret = -E_OGGDEC_FAULT;
if (oret < 0)
goto out;
- pod->channels = ov_info(pod->vf, 0)->channels;
- pod->sample_rate = ov_info(pod->vf, 0)->rate;
- tv_add(now, &(struct timeval)EMBRACE(0, 300 * 1000), &pod->stream_start);
+ pod->channels = ov_info(vf, 0)->channels;
+ pod->sample_rate = ov_info(vf, 0)->rate;
PARA_NOTICE_LOG("%d channels, %d Hz\n", pod->channels,
pod->sample_rate);
ret = 1;
out:
- if (ret <= 0) {
- free(pod->vf);
- pod->vf = NULL;
- } else {
+ if (ret <= 0)
+ free(vf);
+ else {
btr_consume(btrn, pod->converted);
pod->converted = 0;
fn->min_iqs = 0;
+ pod->vf = vf;
}
return ret;
}
static void ogg_pre_select(__a_unused struct sched *s, struct task *t)
{
struct filter_node *fn = container_of(t, struct filter_node, task);
- struct private_oggdec_data *pod = fn->private_data;
int ret;
t->error = 0;
ret = btr_node_status(fn->btrn, fn->min_iqs, BTR_NT_INTERNAL);
- if (ret < 0)
+ if (ret != 0)
sched_min_delay(s);
else
- sched_request_barrier(&pod->stream_start, s);
+ sched_request_timeout_ms(100, s);
}
static void ogg_post_select(__a_unused struct sched *s, struct task *t)
struct private_oggdec_data *pod = fn->private_data;
struct btr_node *btrn = fn->btrn;
int ret, ns;
+ char *out;
+ ssize_t read_ret, have;
- if (tv_diff(&pod->stream_start, now, NULL) > 0)
- return;
pod->converted = 0;
t->error = 0;
ret = ns = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
+ if (ret <= 0)
+ goto out;
if (!pod->vf) {
if (ret <= 0)
goto out;
if (ret <= 0)
goto out;
}
- for (;;) {
- char *out = para_malloc(OGGDEC_OUTPUT_CHUNK_SIZE);
- ssize_t read_ret = ov_read(pod->vf, out, OGGDEC_OUTPUT_CHUNK_SIZE,
+ out = para_malloc(OGGDEC_OUTPUT_CHUNK_SIZE);
+ for (have = 0;;) {
+ read_ret = ov_read(pod->vf, out + have,
+ OGGDEC_OUTPUT_CHUNK_SIZE - have,
ENDIAN, 2 /* 16 bit */, 1 /* signed */, NULL);
btr_consume(btrn, pod->converted);
pod->converted = 0;
if (read_ret <= 0)
- free(out);
- ret = ns;
- if (read_ret == 0 || read_ret == OV_HOLE)
- goto out;
- ret = -E_OGGDEC_BADLINK;
- if (read_ret < 0)
- goto out;
- btr_add_output(out, read_ret, btrn);
- if (btr_get_output_queue_size(btrn) > 128 * 1024)
- return; /* enough data for the moment */
+ break;
+ have += read_ret;
+ if (have >= OGGDEC_OUTPUT_CHUNK_SIZE)
+ break;
+ }
+ if (have == 0)
+ free(out);
+ else if (have < OGGDEC_OUTPUT_CHUNK_SIZE)
+ out = para_realloc(out, have);
+ if (have > 0) {
+ btr_add_output(out, have, btrn);
+ fn->min_iqs = 0;
}
+ ret = ns;
+ if (read_ret == OV_HOLE) /* avoid buffer underruns */
+ fn->min_iqs = 9000;
+ if (read_ret == 0 || read_ret == OV_HOLE)
+ goto out;
+ ret = -E_OGGDEC_BADLINK;
+ if (read_ret < 0)
+ goto out;
+ ret = 0;
out:
if (ret < 0) {
t->error = ret;
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; \
*/
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;
/** 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);