ogg: Abstract out custom header code.
authorAndre Noll <maan@tuebingen.mpg.de>
Mon, 17 Jul 2017 23:02:50 +0000 (01:02 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sat, 22 Jul 2017 14:34:10 +0000 (16:34 +0200)
The ogg/vorbis audio format handler employs the callback mechanism of
oac_get_file_info() to replace the metadata packet of an ogg/vorbis
stream by a fixed sized dummy packet. The same approach should also be
used for the ogg/opus audio format handler because ogg/opus metadata
can be arbitrary large too.

As a preparation for modifying the ogg/opus audio format
handler to skip the metadata header in this way, this commit
makes the infrastructure available to all audio format handlers
which employ the ogg container format by providing a public
interface in ogg_afh_common.c. The central data structure is struct
oac_custom_header, which replaces the former vorbis_get_header_data
structure. The layout of the new structure is only known to
ogg_afh_common.c. Users of this API need to call the accessor functions
declared in ogg_afh_common.h.

Only the ogg/vorbis audio format handler is converted to the new API.
A subsequent patch will make the ogg/opus audio format handler the
second user.

ogg_afh.c
ogg_afh_common.c
ogg_afh_common.h

index 8a64fcc..8d8cadc 100644 (file)
--- a/ogg_afh.c
+++ b/ogg_afh.c
@@ -79,32 +79,11 @@ static int ogg_vorbis_get_file_info(char *map, size_t numbytes, __a_unused int f
        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',
@@ -115,17 +94,12 @@ static int vorbis_get_header_callback(ogg_packet *packet, int packet_num,
        };
 
        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) {
@@ -133,42 +107,37 @@ static int vorbis_get_header_callback(ogg_packet *packet, int packet_num,
                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)
index fd0c86c..eb90c5f 100644 (file)
@@ -332,3 +332,108 @@ 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;
+}
index 0a18b5c..8f3494a 100644 (file)
@@ -9,6 +9,12 @@
  * 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.
  *