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 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 = oac_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));
+ *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)
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;
+}