- autogen.sh now runs the test suite after a successful build.
- The contents of overview.pdf have been integrated into the user
manual.
+- Fixed sized audio format headers for ogg/opus streams.
- The doxygen source browser has been disabled temporarily. The
API reference is still online, though.
- Overhaul of the source code documentation.
&pard->afh_context);
if (ret < 0)
goto out;
- PARA_INFO_LOG("adding %zu bytes\n", size);
+ PARA_DEBUG_LOG("adding %zu bytes\n", size);
btr_add_output_dont_free(start, size, btrn);
}
ret = -E_RECV_EOF;
{
int ret;
struct private_vorbis_data pvd;
- struct ogg_afh_callback_info vorbis_callback_info = {
+ struct oac_callback_info vorbis_callback_info = {
.packet_callback = vorbis_packet_callback,
.private_data = &pvd,
};
vorbis_info_init(&pvd.vi);
vorbis_comment_init(&pvd.vc);
- ret = ogg_get_file_info(map, numbytes, afhi, &vorbis_callback_info);
+ ret = oac_get_file_info(map, numbytes, afhi, &vorbis_callback_info);
vorbis_info_clear(&pvd.vi);
vorbis_comment_clear(&pvd.vc);
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: %li/%li/%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;
+ struct oac_custom_header *h = private_data;
static unsigned char dummy_packet[] = {
0x03,
'v', 'o', 'r', 'b', 'i', 's',
};
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 = ogg_stream_packetin(&vghd->os, packet);
+ oac_custom_header_init(serial, h);
+ ret = oac_custom_header_append(packet, h);
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 ret;
+ oac_custom_header_flush(h);
return 1;
}
if (packet_num == 1) {
PARA_INFO_LOG("replacing metadata packet\n");
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 = oac_custom_header_append(&replacement, h);
+ return ret < 0? ret : 1;
}
- 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;
+ assert(packet_num == 2);
+ ret = oac_custom_header_append(packet, h);
+ if (ret < 0)
+ return ret;
+ oac_custom_header_flush(h);
+ return 0;
}
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 = {
+ struct oac_custom_header *h = oac_custom_header_new();
+ struct oac_callback_info cb = {
.packet_callback = vorbis_get_header_callback,
- .private_data = &vghd,
+ .private_data = h,
};
- 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));
+ ret = oac_get_file_info(map, mapsize, NULL, &cb);
+ *len = oac_custom_header_get(buf, h);
+ if (ret < 0) {
+ PARA_ERROR_LOG("could not create ogg/vorbis header: %s\n",
+ para_strerror(-ret));
+ free(*buf);
+ *buf = NULL;
+ *len = 0;
+ } else
+ PARA_INFO_LOG("created %zu byte ogg vorbis header\n", *len);
}
static int vorbis_make_meta_packet(struct taginfo *tags, ogg_packet *result)
ret = vorbis_make_meta_packet(tags, &packet);
if (ret < 0)
return ret;
- ret = ogg_rewrite_tags(map, mapsize, output_fd, (char *)packet.packet,
+ ret = oac_rewrite_tags(map, mapsize, output_fd, (char *)packet.packet,
packet.bytes);
free(packet.packet);
return ret;
/* Taken from decoder_example.c of libvorbis-1.2.3. */
static int process_packets_2_and_3(ogg_sync_state *oss,
ogg_stream_state *stream, struct afh_info *afhi,
- struct ogg_afh_callback_info *ci)
+ struct oac_callback_info *ci)
{
ogg_page page;
ogg_packet packet;
}
static int process_ogg_packets(ogg_sync_state *oss, struct afh_info *afhi,
- struct ogg_afh_callback_info *ci)
+ struct oac_callback_info *ci)
{
ogg_packet packet;
ogg_stream_state stream;
*
* \return Standard.
*/
-int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
- struct ogg_afh_callback_info *ci)
+int oac_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
+ struct oac_callback_info *ci)
{
ogg_sync_state oss;
ogg_page op;
*
* \return Standard.
*/
-int ogg_rewrite_tags(const char *map, size_t map_sz, int fd,
+int oac_rewrite_tags(const char *map, size_t map_sz, int fd,
char *meta_packet, size_t meta_sz)
{
ogg_sync_state oss_in, oss_out;
ogg_stream_clear(so);
return ret;
}
+
+/* Structure for providing custom headers for streaming. */
+struct oac_custom_header {
+ char *buf;
+ size_t len;
+ ogg_stream_state oss;
+};
+
+/**
+ * Allocate and return a custom header structure.
+ *
+ * For some audio codecs which employ the ogg container format, the server side
+ * wants to replace the meta tags at the beginning of the file because they are
+ * not needed for streaming and can be arbitrary large. The structure returned
+ * by this function is typically used as the ->private field of struct \ref
+ * oac_callback_info for \ref oac_get_file_info(). This allows the audio format
+ * handler to set up a custom header which is identical to the original header,
+ * but with the meta data part replaced by fixed length dummy contents.
+ *
+ * \return The returned memory must be initialized with the serial number of
+ * the ogg stream before ogg packets can be submitted to it. This is not done
+ * here because the header structure is allocated before \ref
+ * oac_get_file_info() is called, and the serial number is not known at this
+ * point.
+ *
+ * \sa \ref oac_custom_header_init().
+ */
+struct oac_custom_header *oac_custom_header_new(void)
+{
+ return para_calloc(sizeof(struct oac_custom_header));
+}
+
+/**
+ * Set the serial number of an allocated header structure.
+ *
+ * \param serial Passed to the callback function.
+ * \param h As returned from \ref oac_custom_header_new().
+ *
+ * This function must be called before any packets are submitted.
+ */
+void oac_custom_header_init(int serial, struct oac_custom_header *h)
+{
+ ogg_stream_init(&h->oss, serial);
+}
+
+/**
+ * Submit an ogg packet to a custom header structure.
+ *
+ * \param op The packet to append.
+ * \param h Must be initialized.
+ *
+ * The packet may be the one which was passed to the callback, or a completely
+ * different one, like a dummy metadata packet.
+ *
+ * \return Standard.
+ */
+int oac_custom_header_append(ogg_packet *op, struct oac_custom_header *h)
+{
+ return ogg_stream_packetin(&h->oss, op) < 0? -E_OGG_PACKET_IN : 1;
+}
+
+/**
+ * Force remaining packets into an ogg page.
+ *
+ * \param h Should contain submitted but not yet flushed packets.
+ *
+ * This is called after the first packet has been submitted with \ref
+ * oac_custom_header_append() to make sure the first ogg page contains only
+ * this packet. Also when header processing is complete, the callbacks call
+ * this to force the previously submitted packets into a page.
+ */
+void oac_custom_header_flush(struct oac_custom_header *h)
+{
+ ogg_page og;
+
+ while (ogg_stream_flush(&h->oss, &og)) {
+ size_t len = og.header_len + og.body_len;
+ h->buf = para_realloc(h->buf, h->len + len);
+ memcpy(h->buf + h->len, og.header, og.header_len);
+ memcpy(h->buf + h->len + og.header_len, og.body, og.body_len);
+ h->len += len;
+ }
+}
+
+/**
+ * Return the custom header buffer and deallocate resources.
+ *
+ * This is called after the ogg packets which comprise the header have been
+ * submitted and flushed.
+ *
+ * \param buf Result pointer.
+ * \param h Must not be used any more after the call.
+ *
+ * \return The size of the header. This is the sum of the sizes of all ogg
+ * pages that have been flushed out.
+ */
+size_t oac_custom_header_get(char **buf, struct oac_custom_header *h)
+{
+ size_t ret = h->len;
+
+ *buf = h->buf;
+ ogg_stream_clear(&h->oss);
+ free(h);
+ return ret;
+}
* handlers that use the ogg container format.
*/
+struct oac_custom_header *oac_custom_header_new(void);
+void oac_custom_header_init(int serial, struct oac_custom_header *h);
+int oac_custom_header_append(ogg_packet *op, struct oac_custom_header *h);
+void oac_custom_header_flush(struct oac_custom_header *h);
+size_t oac_custom_header_get(char **buf, struct oac_custom_header *h);
+
/**
* Callback structure provided by audio format handlers.
*
* function whose purpose is to extract the meta information about the audio
* file from the first few ogg packets of the bitstream.
*/
-struct ogg_afh_callback_info {
+struct oac_callback_info {
/**
* ogg_get_file_info() calls this function for the first three ogg
* packets, or until the callback returns a non-positive value. If it
void *private_data;
};
-int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
- struct ogg_afh_callback_info *ci);
-int ogg_rewrite_tags(const char *map, size_t mapsize, int fd,
+int oac_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
+ struct oac_callback_info *ci);
+int oac_rewrite_tags(const char *map, size_t mapsize, int fd,
char *meta_packet, size_t meta_sz);
return 1;
}
+/*
+ * Ogg/Opus has two mandatory header packets:
+ *
+ * 1. ID header (identifies the stream as Opus). Dedicated "BOS" ogg page.
+ * 2. Comment header (metadata). May span multiple pages.
+ *
+ * See doc/draft-ietf-codec-oggopus.xml in the opus source tree for details.
+ */
static int opus_packet_callback(ogg_packet *packet, int packet_num,
__a_unused int serial, struct afh_info *afhi,
void *private_data)
int ret, ms;
struct opus_header oh = {.version = 0};
- struct ogg_afh_callback_info opus_callback_info = {
+ struct oac_callback_info opus_callback_info = {
.packet_callback = opus_packet_callback,
.private_data = &oh,
};
- ret = ogg_get_file_info(map, numbytes, afhi, &opus_callback_info);
+ ret = oac_get_file_info(map, numbytes, afhi, &opus_callback_info);
if (ret < 0)
return ret;
ret = (afhi->chunk_table[afhi->chunks_total] - afhi->chunk_table[0]) * 8; /* bits */
int ret;
meta_sz = opus_make_meta_packet(tags, &meta_packet);
- ret = ogg_rewrite_tags(map, mapsize, output_fd, meta_packet, meta_sz);
+ ret = oac_rewrite_tags(map, mapsize, output_fd, meta_packet, meta_sz);
free(meta_packet);
return ret;
}
+/*
+ * See doc/draft-ietf-codec-oggopus.xml in the opus source tree for details
+ * about the format of the comment header.
+ */
+static int opus_get_header_callback(ogg_packet *packet, int packet_num,
+ int serial, __a_unused struct afh_info *afhi, void *private_data)
+{
+ struct oac_custom_header *h = private_data;
+ int ret;
+ static unsigned char dummy_tags[] = { /* a minimal comment header */
+ 'O', 'p', 'u', 's', 'T', 'a', 'g', 's',
+ 0x06, 0x00, 0x00, 0x00, /* vendor string length */
+ 'd', 'u', 'm', 'm', 'y', '\0', /* vendor string */
+ 0x00, 0x00, 0x00, 0x00, /* user comment list length */
+ };
+ ogg_packet replacement;
+
+ if (packet_num == 0) {
+ oac_custom_header_init(serial, h);
+ ret = oac_custom_header_append(packet, h);
+ if (ret < 0)
+ return ret;
+ oac_custom_header_flush(h);
+ return 1;
+ }
+ assert(packet_num == 1);
+ PARA_INFO_LOG("replacing metadata packet\n");
+ replacement = *packet;
+ replacement.packet = dummy_tags;
+ replacement.bytes = sizeof(dummy_tags);
+ ret = oac_custom_header_append(&replacement, h);
+ if (ret < 0)
+ return ret;
+ oac_custom_header_flush(h);
+ return 0;
+}
+
+static void opus_get_header(void *map, size_t mapsize, char **buf,
+ size_t *len)
+{
+ int ret;
+ struct oac_custom_header *h = oac_custom_header_new();
+ struct oac_callback_info cb = {
+ .packet_callback = opus_get_header_callback,
+ .private_data = h,
+ };
+
+ ret = oac_get_file_info(map, mapsize, NULL, &cb);
+ *len = oac_custom_header_get(buf, h);
+ if (ret < 0) {
+ PARA_ERROR_LOG("could not create custom header: %s\n",
+ para_strerror(-ret));
+ free(*buf);
+ *buf = NULL;
+ *len = 0;
+ } else
+ PARA_INFO_LOG("created %zu byte ogg/opus header\n", *len);
+}
+
/**
* The init function of the ogg/opus audio format handler.
*
void opus_afh_init(struct audio_format_handler *afh)
{
afh->get_file_info = opus_get_file_info,
+ afh->get_header = opus_get_header;
afh->suffixes = opus_suffixes;
afh->rewrite_tags = opus_rewrite_tags;
}
struct afh_info *afhi)
{
struct private_spx_data psd;
- struct ogg_afh_callback_info spx_callback_info = {
+ struct oac_callback_info spx_callback_info = {
.packet_callback = spx_packet_callback,
.private_data = &psd,
};
memset(&psd, 0, sizeof(psd));
- return ogg_get_file_info(map, numbytes, afhi, &spx_callback_info);
+ return oac_get_file_info(map, numbytes, afhi, &spx_callback_info);
}
static size_t spx_make_meta_packet(struct taginfo *tags, char **result)
int ret;
meta_sz = spx_make_meta_packet(tags, &meta_packet);
- ret = ogg_rewrite_tags(map, mapsize, output_fd, meta_packet, meta_sz);
+ ret = oac_rewrite_tags(map, mapsize, output_fd, meta_packet, meta_sz);
free(meta_packet);
return ret;
}