server: Close logfile later on SIGHUP.
[paraslash.git] / ogg_afh_common.c
index 21444fa33b1b35f8f1a7653b103f27469956e680..62cde3d465ab7c86a23d6c539b8a2b447d465f78 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2004-2014 Andre Noll <maan@systemlinux.org>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2004 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file ogg_afh_common.c Functions common to all ogg/ codecs. */
 
 #include "error.h"
 #include "string.h"
 #include "ogg_afh_common.h"
-
+#include "fd.h"
 
 /* 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;
@@ -60,7 +56,7 @@ static int process_packets_2_and_3(ogg_sync_state *oss,
 }
 
 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;
@@ -122,24 +118,23 @@ static void set_chunk_tv(int frames_per_chunk, int frequency,
  *
  * \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;
-       long len = numbytes;
        char *buf;
        int ret, i, j, frames_per_chunk, ct_size;
        long long unsigned num_frames = 0;
 
        ogg_sync_init(&oss);
        ret = -E_OGG_SYNC;
-       buf = ogg_sync_buffer(&oss, len);
+       buf = ogg_sync_buffer(&oss, numbytes);
        if (!buf)
                goto out;
-       memcpy(buf, map, len);
+       memcpy(buf, map, numbytes);
        ret = -E_OGG_SYNC;
-       if (ogg_sync_wrote(&oss, len) < 0)
+       if (ogg_sync_wrote(&oss, numbytes) < 0)
                goto out;
        ret = process_ogg_packets(&oss, afhi, ci);
        if (ret < 0)
@@ -159,7 +154,7 @@ int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
        afhi->seconds_total = num_frames / afhi->frequency;
        /* use roughly one page per chunk */
        frames_per_chunk = num_frames / i;
-       PARA_INFO_LOG("%lu seconds, %d frames/chunk\n",
+       PARA_INFO_LOG("%" PRIu32 " seconds, %d frames/chunk\n",
                afhi->seconds_total, frames_per_chunk);
        ct_size = 250;
        afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t));
@@ -182,9 +177,259 @@ int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
                }
        }
        afhi->chunks_total = j;
+       set_max_chunk_size(afhi);
        set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv);
        ret = 0;
 out:
        ogg_sync_clear(&oss);
        return ret;
 }
+
+static int write_ogg_page(int fd, const ogg_page *op)
+{
+       int ret;
+
+       PARA_DEBUG_LOG("header/body: %li/%li\n", op->header_len, op->body_len);
+       ret = xwrite(fd, (const char *)op->header, op->header_len);
+       if (ret < 0)
+               return ret;
+       return xwrite(fd, (const char *)op->body, op->body_len);
+}
+
+/**
+ * Change meta tags of ogg files.
+ *
+ * \param map The (read-only) memory map of the input file.
+ * \param map_sz The size of the input file in bytes.
+ * \param fd The output file descriptor.
+ * \param meta_packet Codec-specific packet containing modified tags.
+ * \param meta_sz Size of the metadata packet.
+ *
+ * This function writes a new ogg file content using file descriptor \a fd,
+ * which must correspond to a file which has been opened for writing.  The
+ * second packet is supposed to contain the metadata, and is replaced by \a
+ * meta_packet. This output file has to be closed by the caller.
+ *
+ * \return Standard.
+ */
+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_state stream_in, stream_out, *si = NULL, *so = NULL;
+       ogg_packet packet;
+       ogg_page op;
+       char *buf;
+       int serial, ret;
+       long len = map_sz;
+
+       ogg_sync_init(&oss_in);
+       ogg_sync_init(&oss_out);
+
+       ret = -E_OGG_SYNC;
+       buf = ogg_sync_buffer(&oss_in, len);
+       if (!buf)
+               goto out;
+       memcpy(buf, map, len);
+       ret = -E_OGG_SYNC;
+       if (ogg_sync_wrote(&oss_in, len) < 0)
+               goto out;
+       if (ogg_sync_pageout(&oss_in, &op) != 1)
+               goto out;
+       ret = ogg_page_serialno(&op);
+       serial = ret;
+
+       si = &stream_in;
+       ogg_stream_init(si, serial);
+       /* Packet #0 goes to an own page */
+       ret = -E_STREAM_PAGEIN;
+       if (ogg_stream_pagein(si, &op) < 0)
+               goto out;
+       ret = -E_STREAM_PACKETOUT;
+       if (ogg_stream_packetout(si, &packet) != 1)
+               goto out;
+       ret = -E_STREAM_PACKETIN;
+       so = &stream_out;
+       ogg_stream_init(so, serial);
+       if (ogg_stream_packetin(so, &packet) != 0)
+               goto out;
+       ret = ogg_stream_flush(so, &op);
+       assert(ret != 0);
+       /* packets have been flushed into the page. */
+       ret = write_ogg_page(fd, &op);
+       if (ret < 0)
+               goto out;
+       /*
+        * For all supported ogg/xxx audio formats the meta data packet is
+        * packet #1. Write out our modified version of this packet.
+        */
+       packet.packetno = 1;
+       packet.b_o_s = packet.e_o_s = 0;
+       packet.packet = (typeof(packet.packet))meta_packet;
+       packet.bytes = meta_sz;
+       ret = -E_STREAM_PACKETIN;
+       if (ogg_stream_packetin(so, &packet) != 0)
+               goto out;
+       /* Copy ogg packets, ignoring the meta data packet. */
+       for (;;) {
+               ret = ogg_stream_packetout(si, &packet);
+               if (ret == -1)
+                       break;
+               if (ret != 1) {
+                       ret = -E_STREAM_PAGEOUT;
+                       if (ogg_sync_pageout(&oss_in, &op) < 0)
+                               goto out;
+                       ret = -E_STREAM_PAGEIN;
+                       if (ogg_stream_pagein(si, &op))
+                               goto out;
+                       continue;
+               }
+               PARA_DEBUG_LOG("packet: bytes: %d, granule: %d, packetno: %d\n",
+                       (int)packet.bytes, (int)packet.granulepos,
+                       (int)packet.packetno);
+               /* ignore meta data packet which we replaced */
+               if (packet.packetno == 1)
+                       continue;
+               ret = -E_STREAM_PACKETIN;
+               if (ogg_stream_packetin(so, &packet) != 0)
+                       goto out;
+               /* only create a new ogg page if granulepos is valid */
+               if (packet.granulepos == -1)
+                       continue;
+               /* force remaining packets into a page */
+               for (;;) {
+#ifdef HAVE_OGG_STREAM_FLUSH_FILL
+                       ret = ogg_stream_flush_fill(so, &op, INT_MAX);
+#else
+                       ret = ogg_stream_flush(so, &op);
+#endif
+                       if (ret <= 0)
+                               break;
+                       PARA_DEBUG_LOG("writing page (%li bytes)\n",
+                               op.header_len + op.body_len);
+                       ret = write_ogg_page(fd, &op);
+                       if (ret < 0)
+                               goto out;
+               }
+       }
+       if (ogg_stream_flush(so, &op)) {
+               /* write remaining data */
+               ret = write_ogg_page(fd, &op);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       ogg_sync_clear(&oss_in);
+       ogg_sync_clear(&oss_out);
+       if (si)
+               ogg_stream_clear(si);
+       if (so)
+               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;
+}