Skeleton support for Speexenc (by Tahseen Mohammad)
authorjm <jm@0101bb08-14d6-0310-b084-bc0e0c8e3800>
Fri, 11 Aug 2006 05:55:22 +0000 (05:55 +0000)
committerjm <jm@0101bb08-14d6-0310-b084-bc0e0c8e3800>
Fri, 11 Aug 2006 05:55:22 +0000 (05:55 +0000)
git-svn-id: http://svn.xiph.org/trunk/speex@11759 0101bb08-14d6-0310-b084-bc0e0c8e3800

src/Makefile.am
src/skeleton.c [new file with mode: 0644]
src/skeleton.h [new file with mode: 0644]
src/speexenc.c

index 4c688f7..1ef61b2 100644 (file)
@@ -10,14 +10,14 @@ INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir) @
 mandir = $(prefix)/share/man
 man_MANS = speexenc.1 speexdec.1
 
-EXTRA_DIST = $(man_MANS) getopt_win.h getopt.c getopt1.c wave_out.c wave_out.h
+EXTRA_DIST = $(man_MANS) getopt_win.h getopt.c getopt1.c wave_out.c wave_out.h skeleton.h
 
 include_HEADERS = 
 noinst_HEADERS = wav_io.h
 
 bin_PROGRAMS = speexenc speexdec
 
-speexenc_SOURCES = speexenc.c wav_io.c
+speexenc_SOURCES = speexenc.c wav_io.c skeleton.c
 speexenc_LDADD = $(top_builddir)/libspeex/libspeex.la \
        $(OGG_LIBS)
 
diff --git a/src/skeleton.c b/src/skeleton.c
new file mode 100644 (file)
index 0000000..22159d5
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * skeleton.c
+ * author: Tahseen Mohammad
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <ogg/ogg.h>
+
+#include "skeleton.h"
+
+/* write an ogg_page to a file pointer */
+int write_ogg_page_to_file(ogg_page *og, FILE *out) {
+   int written;
+   
+   written = fwrite(og->header,1, og->header_len, out);
+   written += fwrite(og->body,1, og->body_len, out);
+
+   return written;
+}
+
+int add_message_header_field(fisbone_packet *fp, 
+                                        char *header_key, 
+                                        char *header_value) {
+
+    /* size of both key and value + ': ' + CRLF */
+    int this_message_size = strlen(header_key) + strlen(header_value) + 4;
+    if (fp->message_header_fields == NULL) {
+        fp->message_header_fields = _ogg_calloc(this_message_size, sizeof(char));
+    } else {
+        int new_size = (fp->current_header_size + this_message_size) * sizeof(char);
+        fp->message_header_fields = _ogg_realloc(fp->message_header_fields, new_size);
+    }
+    snprintf(fp->message_header_fields + fp->current_header_size, 
+                this_message_size+1, 
+                "%s: %s\r\n", 
+                header_key, 
+                header_value);
+    fp->current_header_size += this_message_size;
+
+    return 0;
+}
+
+/* create a ogg_packet from a fishead_packet structure */
+ogg_packet ogg_from_fishead(fishead_packet *fp) {
+
+    ogg_packet op;
+
+    memset(&op, 0, sizeof(op));
+    op.packet = _ogg_calloc(FISHEAD_SIZE, sizeof(unsigned char));
+    memset(op.packet, 0, FISHEAD_SIZE);
+
+    memcpy (op.packet, FISHEAD_IDENTIFIER, 8); /* identifier */
+    *((ogg_uint16_t*)(op.packet+8)) = SKELETON_VERSION_MAJOR; /* version major */
+    *((ogg_uint16_t*)(op.packet+10)) = SKELETON_VERSION_MINOR; /* version minor */
+    *((ogg_int64_t*)(op.packet+12)) = (ogg_int64_t)fp->ptime_n; /* presentationtime numerator */
+    *((ogg_int64_t*)(op.packet+20)) = (ogg_int64_t)fp->ptime_d; /* presentationtime denominator */
+    *((ogg_int64_t*)(op.packet+28)) = (ogg_int64_t)fp->btime_n; /* basetime numerator */
+    *((ogg_int64_t*)(op.packet+36)) = (ogg_int64_t)fp->btime_d; /* basetime denominator */
+    /* TODO: UTC time, set to zero for now */
+
+    op.b_o_s = 1;   /* its the first packet of the stream */
+    op.e_o_s = 0;   /* its not the last packet of the stream */
+    op.bytes = FISHEAD_SIZE;  /* length of the packet in bytes */
+
+    return op;
+}
+
+/* create a ogg_packet from a fisbone_packet structure. 
+ * call this method after the fisbone_packet is filled and all message header fields are added
+ * by calling add_message_header_field method.
+ */
+ogg_packet ogg_from_fisbone(fisbone_packet *fp) {
+    
+    ogg_packet op;
+    int packet_size = FISBONE_SIZE + fp->current_header_size;
+
+    memset (&op, 0, sizeof (op));       
+    op.packet = _ogg_calloc (packet_size, sizeof(unsigned char));
+    memset (op.packet, 0, packet_size);
+    memcpy (op.packet, FISBONE_IDENTIFIER, 8); /* identifier */
+    *((ogg_uint32_t*)(op.packet+8)) = FISBONE_MESSAGE_HEADER_OFFSET; /* offset of the message header fields */
+    *((ogg_uint32_t*)(op.packet+12)) = fp->serial_no; /* serialno of the respective stream */
+    *((ogg_uint32_t*)(op.packet+16)) = fp->nr_header_packet; /* number of header packets */
+    *((ogg_int64_t*)(op.packet+20)) = fp->granule_rate_n; /* granulrate numerator */
+    *((ogg_int64_t*)(op.packet+28)) = fp->granule_rate_d; /* granulrate denominator */
+    *((ogg_int64_t*)(op.packet+36)) = fp->start_granule; /* start granule */
+    *((ogg_uint32_t*)(op.packet+44)) = fp->preroll; /* preroll, for theora its 0 */
+    *(op.packet+48) = fp->granule_shift; /* granule shift */
+    memcpy((op.packet+FISBONE_SIZE), fp->message_header_fields, fp->current_header_size);
+
+    op.b_o_s = 0;
+    op.e_o_s = 0;
+    op.bytes = packet_size; /* size of the packet in bytes */
+
+    return op;
+}
+
+/* fills up a fishead_packet from a fishead ogg_packet of a skeleton bistream */
+fishead_packet fishead_from_ogg(ogg_packet *op) {
+       
+    fishead_packet fp;
+
+    if (memcmp(op->packet, FISHEAD_IDENTIFIER, 8))
+       ; /* invalid packet what do we do? */
+
+    fp.version_major = *((ogg_uint16_t*)(op->packet+8)); /* version major */
+    fp.version_minor = *((ogg_uint16_t*)(op->packet+10)); /* version minor */
+    fp.ptime_n = *((ogg_int64_t*)(op->packet+12)); /* presentationtime numerator */
+    fp.ptime_d = *((ogg_int64_t*)(op->packet+20)); /* presentationtime denominator */
+    fp.btime_n = *((ogg_int64_t*)(op->packet+28)); /* basetime numerator */
+    fp.btime_d = *((ogg_int64_t*)(op->packet+36)); /* basetime denominator */
+    memcpy(fp.UTC, op->packet+44, 20);
+
+    return fp;
+}
+
+/* fills up a fisbone_packet from a fisbone ogg_packet of a skeleton bitstream */
+fisbone_packet fisbone_from_ogg(ogg_packet *op) {
+
+    fisbone_packet fp;
+    
+    if (memcmp(op->packet, FISBONE_IDENTIFIER, 8))
+       ; /* invalid value, what do we do? */
+    fp.serial_no = *((ogg_uint32_t*)(op->packet+12)); /* serialno of the stream represented by this fisbone packet */
+    fp.nr_header_packet = *((ogg_uint32_t*)(op->packet+16)); /* number of header packets */
+    fp.granule_rate_n = *((ogg_int64_t*)(op->packet+20)); /* granulrate numerator */
+    fp.granule_rate_d = *((ogg_int64_t*)(op->packet+28)); /* granulrate denominator */
+    fp.start_granule = *((ogg_int64_t*)(op->packet+36)); /* start granule */
+    fp.preroll = *((ogg_uint32_t*)(op->packet+44)); /* preroll, for theora its 0 */
+    fp.granule_shift = *(op->packet+48); /* granule shift */
+    fp.current_header_size = op->bytes - FISBONE_SIZE;
+    fp.message_header_fields = _ogg_calloc(fp.current_header_size+1, sizeof(char));
+    memcpy(fp.message_header_fields, op->packet+FISBONE_SIZE, fp.current_header_size);
+
+    return fp;
+}
+
+int add_fishead_to_stream(ogg_stream_state *os, fishead_packet *fp) {
+
+    ogg_packet op;
+
+    op = ogg_from_fishead(fp);
+    ogg_stream_packetin(os, &op);
+    _ogg_free(op.packet);
+
+    return 0;
+}
+
+int add_fisbone_to_stream(ogg_stream_state *os, fisbone_packet *fp) {
+
+    ogg_packet op;
+
+    op = ogg_from_fisbone(fp);
+    ogg_stream_packetin(os, &op);
+    _ogg_free(op.packet);
+
+    return 0;
+}
+
+int add_eos_packet_to_stream(ogg_stream_state *os) {
+
+    ogg_packet op;
+
+    memset (&op, 0, sizeof(op));
+    op.e_o_s = 1;
+    ogg_stream_packetin(os, &op);
+
+    return 0;
+}
+
+int flush_ogg_stream_to_file(ogg_stream_state *os, FILE *out) {
+
+    ogg_page og;
+    int result;
+
+    while((result = ogg_stream_flush(os, &og)))
+    {
+        if(!result) break;
+        result = write_ogg_page_to_file(&og, out);
+        if(result != og.header_len + og.body_len)
+            return 1;
+    }
+
+    return 0;
+}
diff --git a/src/skeleton.h b/src/skeleton.h
new file mode 100644 (file)
index 0000000..aa27231
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * skeleton.h
+ * author: Tahseen Mohammad
+ */
+
+#ifndef _SKELETON_H
+#define _SKELETON_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <ogg/ogg.h>
+
+#define SKELETON_VERSION_MAJOR 3
+#define SKELETON_VERSION_MINOR 0
+#define FISHEAD_IDENTIFIER "fishead\0"
+#define FISBONE_IDENTIFIER "fisbone\0"
+#define FISHEAD_SIZE 64
+#define FISBONE_SIZE 52
+#define FISBONE_MESSAGE_HEADER_OFFSET 44
+
+/* fishead_packet holds a fishead header packet. */
+typedef struct {
+    ogg_uint16_t version_major;                                    /* skeleton version major */
+    ogg_uint16_t version_minor;                                    /* skeleton version minor */
+    /* Start time of the presentation
+     * For a new stream presentationtime & basetime is same. */
+    ogg_int64_t ptime_n;                                    /* presentation time numerator */
+    ogg_int64_t ptime_d;                                    /* presentation time denominator */
+    ogg_int64_t btime_n;                                    /* basetime numerator */
+    ogg_int64_t btime_d;                                    /* basetime denominator */
+    /* will holds the time of origin of the stream, a 20 bit field. */
+    unsigned char UTC[20];
+} fishead_packet;
+
+/* fisbone_packet holds a fisbone header packet. */
+typedef struct {
+    ogg_uint32_t serial_no;                                 /* serial no of the corresponding stream */
+    ogg_uint32_t nr_header_packet;                      /* number of header packets */
+    /* granule rate is the temporal resolution of the logical bitstream */
+    ogg_int64_t granule_rate_n;                            /* granule rate numerator */
+    ogg_int64_t granule_rate_d;                            /* granule rate denominator */
+    ogg_int64_t start_granule;                             /* start granule value */
+    ogg_uint32_t preroll;                                   /* preroll */
+    unsigned char granule_shift;                           /* 1 byte value holding the granule shift */
+    char *message_header_fields;                            /* holds all the message header fields */
+    /* current total size of the message header fields, for realloc purpose, initially zero */
+    ogg_uint32_t current_header_size;
+} fisbone_packet;
+
+extern int write_ogg_page_to_file(ogg_page *og, FILE *out);
+extern int add_message_header_field(fisbone_packet *fp, char *header_key, char *header_value);
+/* remember to deallocate the returned ogg_packet properly */
+extern ogg_packet ogg_from_fishead(fishead_packet *fp);
+extern ogg_packet ogg_from_fisbone(fisbone_packet *fp);
+extern fishead_packet fishead_from_ogg(ogg_packet *op);
+extern fisbone_packet fisbone_from_ogg(ogg_packet *op);
+extern int add_fishead_to_stream(ogg_stream_state *os, fishead_packet *fp);
+extern int add_fisbone_to_stream(ogg_stream_state *os, fisbone_packet *fp);
+extern int add_eos_packet_to_stream(ogg_stream_state *os);
+extern int flush_ogg_stream_to_file(ogg_stream_state *os, FILE *out);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _SKELETON_H */
+
+
+
+
+
+
index d49860d..58154d0 100644 (file)
@@ -59,6 +59,8 @@
 #include <fcntl.h>
 #endif
 
+#include "skeleton.h"
+
 
 void comment_init(char **comments, int* length, char *vendor_string);
 void comment_add(char **comments, int* length, char *tag, char *val);
@@ -144,6 +146,40 @@ static int read_samples(FILE *fin,int frame_size, int bits, int channels, int ls
    return nb_read;
 }
 
+void add_fishead_packet (ogg_stream_state *os) {
+
+   fishead_packet fp;
+
+   memset(&fp, 0, sizeof(fp));
+   fp.ptime_n = 0;
+   fp.ptime_d = 1000;
+   fp.btime_n = 0;
+   fp.btime_d = 1000;
+
+   add_fishead_to_stream(os, &fp);
+}
+
+/*
+ * Adds the fishead packets in the skeleton output stream along with the e_o_s packet
+ */
+void add_fisbone_packet (ogg_stream_state *os, spx_int32_t serialno, SpeexHeader *header) {
+
+   fisbone_packet fp;
+
+   memset(&fp, 0, sizeof(fp));
+   fp.serial_no = serialno;
+   fp.nr_header_packet = 2 + header->extra_headers;
+   fp.granule_rate_n = header->rate;
+   fp.granule_rate_d = 1;
+   fp.start_granule = 0;
+   fp.preroll = 3;
+   fp.granule_shift = 0;
+
+   add_message_header_field(&fp, "Content-Type", "audio/x-speex");
+
+   add_fisbone_to_stream(os, &fp);
+}
+
 void version()
 {
    printf ("speexenc (Speex encoder) version " SPEEX_VERSION " (compiled " __DATE__ ")\n");
@@ -186,6 +222,7 @@ void usage()
    printf (" --nframes n        Number of frames per Ogg packet (1-10), default 1\n"); 
    printf (" --denoise          Denoise the input before encoding\n"); 
    printf (" --agc              Apply adaptive gain control (AGC) before encoding\n"); 
+   printf (" --skeleton         Outputs ogg skeleton metadata (may cause incompatibilities)\n");
    printf (" --comment          Add the given string as an extra comment. This may be\n");
    printf ("                     used multiple times\n");
    printf (" --author           Author of this track\n");
@@ -229,6 +266,7 @@ int main(int argc, char **argv)
    void *st;
    SpeexBits bits;
    char cbits[MAX_FRAME_BYTES];
+   int with_skeleton = 0;
    struct option long_options[] =
    {
       {"wideband", no_argument, NULL, 0},
@@ -245,6 +283,7 @@ int main(int argc, char **argv)
       {"comp", required_argument, NULL, 0},
       {"denoise", no_argument, NULL, 0},
       {"agc", no_argument, NULL, 0},
+      {"skeleton",no_argument,NULL, 0},
       {"help", no_argument, NULL, 0},
       {"quiet", no_argument, NULL, 0},
       {"le", no_argument, NULL, 0},
@@ -268,6 +307,7 @@ int main(int argc, char **argv)
    float vbr_quality=-1;
    int lsb=1;
    ogg_stream_state os;
+   ogg_stream_state so; /* ogg stream for skeleton bitstream */
    ogg_page             og;
    ogg_packet           op;
    int bytes_written=0, ret, result;
@@ -359,6 +399,9 @@ int main(int argc, char **argv)
          } else if (strcmp(long_options[option_index].name,"agc")==0)
          {
             agc_enabled=1;
+         } else if (strcmp(long_options[option_index].name,"skeleton")==0)
+         {
+            with_skeleton=1;
          } else if (strcmp(long_options[option_index].name,"help")==0)
          {
             usage();
@@ -451,6 +494,11 @@ int main(int argc, char **argv)
       fprintf(stderr,"Error: stream init failed\n");
       exit(1);
    }
+   if (with_skeleton && ogg_stream_init(&so, rand())==-1)
+   {
+      fprintf(stderr,"Error: stream init failed\n");
+      exit(1);
+   }
 
    if (strcmp(inFile, "-")==0)
    {
@@ -627,6 +675,9 @@ int main(int argc, char **argv)
    {
       fprintf (stderr, "Warning: --vad is already implied by --vbr or --abr\n");
    }
+   if (with_skeleton) {
+      fprintf (stderr, "Warning: Enabling skeleton output may cause some decoders to fail.\n");
+   }
 
    if (abr_enabled)
    {
@@ -643,6 +694,17 @@ int main(int argc, char **argv)
       lookahead += frame_size;
    }
 
+   /* first packet should be the skeleton header. */
+
+   if (with_skeleton) {
+      add_fishead_packet(&so);
+      if ((ret = flush_ogg_stream_to_file(&so, fout))) {
+        fprintf (stderr,"Error: failed skeleton (fishead) header to output stream\n");
+         exit(1);
+      } else
+        bytes_written += ret;
+   }
+
    /*Write header*/
    {
       int packet_size;
@@ -655,14 +717,6 @@ int main(int argc, char **argv)
       ogg_stream_packetin(&os, &op);
       free(op.packet);
 
-      op.packet = (unsigned char *)comments;
-      op.bytes = comments_length;
-      op.b_o_s = 0;
-      op.e_o_s = 0;
-      op.granulepos = 0;
-      op.packetno = 1;
-      ogg_stream_packetin(&os, &op);
-      
       while((result = ogg_stream_flush(&os, &og)))
       {
          if(!result) break;
@@ -675,10 +729,53 @@ int main(int argc, char **argv)
          else
             bytes_written += ret;
       }
+
+      op.packet = (unsigned char *)comments;
+      op.bytes = comments_length;
+      op.b_o_s = 0;
+      op.e_o_s = 0;
+      op.granulepos = 0;
+      op.packetno = 1;
+      ogg_stream_packetin(&os, &op);
+   }
+
+   /* fisbone packet should be write after all bos pages */
+   if (with_skeleton) {
+      add_fisbone_packet(&so, os.serialno, &header);
+      if ((ret = flush_ogg_stream_to_file(&so, fout))) {
+        fprintf (stderr,"Error: failed writing skeleton (fisbone )header to output stream\n");
+         exit(1);
+      } else
+        bytes_written += ret;
+   }
+
+   /* writing the rest of the speex header packets */
+   while((result = ogg_stream_flush(&os, &og)))
+   {
+      if(!result) break;
+      ret = oe_write_page(&og, fout);
+      if(ret != og.header_len + og.body_len)
+      {
+         fprintf (stderr,"Error: failed writing header to output stream\n");
+         exit(1);
+      }
+      else
+         bytes_written += ret;
    }
 
    free(comments);
 
+   /* write the skeleton eos packet */
+   if (with_skeleton) {
+      add_eos_packet_to_stream(&so);
+      if ((ret = flush_ogg_stream_to_file(&so, fout))) {
+         fprintf (stderr,"Error: failed writing skeleton header to output stream\n");
+         exit(1);
+      } else
+        bytes_written += ret;
+   }
+
+
    speex_bits_init(&bits);
 
    if (!wave_input)