Avoid symbol collisions (especially with static linking)
[libopusenc.git] / src / opusenc.c
index df099c4..ae47d14 100644 (file)
@@ -32,8 +32,6 @@
 #endif
 
 #include <stdarg.h>
-#include <time.h>
-#include <unistd.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
@@ -45,7 +43,8 @@
 #include "picture.h"
 #include "ogg_packer.h"
 
-#define MAX_CHANNELS 8
+/* Bump this when we change the ABI. */
+#define OPE_ABI_VERSION 0
 
 #define LPC_PADDING 120
 #define LPC_ORDER 24
 #define MIN(a,b) ((a) < (b) ? (a) : (b))
 #define MAX(a,b) ((a) > (b) ? (a) : (b))
 
-#define MAX_PACKET_SIZE (1276*8)
-
-#define USE_OGGP
+#ifdef _MSC_VER
+# if (_MSC_VER < 1900)
+#  define snprintf _snprintf
+# endif
+#endif
 
 struct StdioObject {
   FILE *file;
@@ -85,8 +86,8 @@ OggOpusComments *ope_comments_create() {
   c = malloc(sizeof(*c));
   if (c == NULL) return NULL;
   libopus_str = opus_get_version_string();
-  snprintf(vendor_str, sizeof(vendor_str), "%s, %s version %s", libopus_str, PACKAGE_NAME, PACKAGE_VERSION);
-  comment_init(&c->comment, &c->comment_length, vendor_str);
+  snprintf(vendor_str, sizeof(vendor_str), "%s, %s %s", libopus_str, PACKAGE_NAME, PACKAGE_VERSION);
+  _ope_comment_init(&c->comment, &c->comment_length, vendor_str);
   if (c->comment == NULL) {
     free(c);
     return NULL;
@@ -119,20 +120,27 @@ void ope_comments_destroy(OggOpusComments *comments){
 
 /* Add a comment. */
 int ope_comments_add(OggOpusComments *comments, const char *tag, const char *val) {
-  if (comment_add(&comments->comment, &comments->comment_length, tag, val)) return OPE_ALLOC_FAIL;
+  if (tag == NULL || val == NULL) return OPE_BAD_ARG;
+  if (strchr(tag, '=')) return OPE_BAD_ARG;
+  if (_ope_comment_add(&comments->comment, &comments->comment_length, tag, val)) return OPE_ALLOC_FAIL;
+  return OPE_OK;
+}
+
+/* Add a comment. */
+int ope_comments_add_string(OggOpusComments *comments, const char *tag_and_val) {
+  if (!strchr(tag_and_val, '=')) return OPE_BAD_ARG;
+  if (_ope_comment_add(&comments->comment, &comments->comment_length, NULL, tag_and_val)) return OPE_ALLOC_FAIL;
   return OPE_OK;
 }
 
-int ope_comments_add_picture(OggOpusComments *comments, const char *spec) {
-  const char *error_message;
+int ope_comments_add_picture(OggOpusComments *comments, const char *filename, int picture_type, const char *description) {
   char *picture_data;
-  picture_data = parse_picture_specification(spec, &error_message, &comments->seen_file_icons);
-  if(picture_data==NULL){
-    /* FIXME: return proper errors rather than printing a message. */
-    fprintf(stderr,"Error parsing picture option: %s\n",error_message);
-    return OPE_BAD_ARG;
+  int err;
+  picture_data = _ope_parse_picture_specification(filename, picture_type, description, &err, &comments->seen_file_icons);
+  if (picture_data == NULL || err != OPE_OK){
+    return err;
   }
-  comment_add(&comments->comment, &comments->comment_length, "METADATA_BLOCK_PICTURE", picture_data);
+  _ope_comment_add(&comments->comment, &comments->comment_length, "METADATA_BLOCK_PICTURE", picture_data);
   free(picture_data);
   return OPE_OK;
 }
@@ -142,9 +150,6 @@ typedef struct EncStream EncStream;
 
 struct EncStream {
   void *user_data;
-#ifndef USE_OGGP
-  ogg_stream_state os;
-#endif
   int serialno_is_set;
   int serialno;
   int stream_is_init;
@@ -154,16 +159,14 @@ struct EncStream {
   int seen_file_icons;
   int close_at_end;
   int header_is_frozen;
-  ogg_int64_t end_granule;
-  ogg_int64_t granule_offset;
+  opus_int64 end_granule;
+  opus_int64 granule_offset;
   EncStream *next;
 };
 
 struct OggOpusEnc {
   OpusMSEncoder *st;
-#ifdef USE_OGGP
   oggpacker *oggp;
-#endif
   int unrecoverable;
   int pull_api;
   int rate;
@@ -176,20 +179,21 @@ struct OggOpusEnc {
   int decision_delay;
   int max_ogg_delay;
   int global_granule_offset;
-  ogg_int64_t curr_granule;
-  ogg_int64_t write_granule;
-  ogg_int64_t last_page_granule;
+  opus_int64 curr_granule;
+  opus_int64 write_granule;
+  opus_int64 last_page_granule;
+  float *lpc_buffer;
   unsigned char *chaining_keyframe;
   int chaining_keyframe_length;
   OpusEncCallbacks callbacks;
   ope_packet_func packet_callback;
+  void *packet_callback_data;
   OpusHeader header;
   int comment_padding;
   EncStream *streams;
   EncStream *last_stream;
 };
 
-#ifdef USE_OGGP
 static void output_pages(OggOpusEnc *enc) {
   unsigned char *page;
   int len;
@@ -202,41 +206,12 @@ static void oe_flush_page(OggOpusEnc *enc) {
   if (!enc->pull_api) output_pages(enc);
 }
 
-#else
-
-static int oe_write_page(OggOpusEnc *enc, ogg_page *page, void *user_data)
-{
-  int length;
-  int err;
-  err = enc->callbacks.write(user_data, page->header, page->header_len);
-  if (err) return -1;
-  err = enc->callbacks.write(user_data, page->body, page->body_len);
-  if (err) return -1;
-  length = page->header_len+page->body_len;
-  return length;
-}
-
-static void oe_flush_page(OggOpusEnc *enc) {
-  ogg_page og;
-  int ret;
-
-  while ( (ret = ogg_stream_flush_fill(&enc->streams->os, &og, 255*255))) {
-    if (ogg_page_packets(&og) != 0) enc->last_page_granule = ogg_page_granulepos(&og) + enc->streams->granule_offset;
-    ret = oe_write_page(enc, &og, enc->streams->user_data);
-    if (ret == -1) {
-      return;
-    }
-  }
-}
-#endif
-
-
-int stdio_write(void *user_data, const unsigned char *ptr, int len) {
+static int stdio_write(void *user_data, const unsigned char *ptr, opus_int32 len) {
   struct StdioObject *obj = (struct StdioObject*)user_data;
   return fwrite(ptr, 1, len, obj->file) != (size_t)len;
 }
 
-int stdio_close(void *user_data) {
+static int stdio_close(void *user_data) {
   struct StdioObject *obj = (struct StdioObject*)user_data;
   int ret = 0;
   if (obj->file) ret = fclose(obj->file);
@@ -250,24 +225,24 @@ static const OpusEncCallbacks stdio_callbacks = {
 };
 
 /* Create a new OggOpus file. */
-OggOpusEnc *ope_create_file(const char *path, const OggOpusComments *comments, int rate, int channels, int family, int *error) {
+OggOpusEnc *ope_encoder_create_file(const char *path, OggOpusComments *comments, opus_int32 rate, int channels, int family, int *error) {
   OggOpusEnc *enc;
   struct StdioObject *obj;
   obj = malloc(sizeof(*obj));
-  enc = ope_create_callbacks(&stdio_callbacks, obj, comments, rate, channels, family, error);
+  enc = ope_encoder_create_callbacks(&stdio_callbacks, obj, comments, rate, channels, family, error);
   if (enc == NULL || (error && *error)) {
     return NULL;
   }
   obj->file = fopen(path, "wb");
   if (!obj->file) {
     if (error) *error = OPE_CANNOT_OPEN;
-    ope_destroy(enc);
+    ope_encoder_destroy(enc);
     return NULL;
   }
   return enc;
 }
 
-EncStream *stream_create(const OggOpusComments *comments) {
+EncStream *stream_create(OggOpusComments *comments) {
   EncStream *stream;
   stream = malloc(sizeof(*stream));
   if (!stream) return NULL;
@@ -291,15 +266,12 @@ fail:
 
 static void stream_destroy(EncStream *stream) {
   if (stream->comment) free(stream->comment);
-#ifndef USE_OGGP
-  if (stream->stream_is_init) ogg_stream_clear(&stream->os);
-#endif
   free(stream);
 }
 
 /* Create a new OggOpus file (callback-based). */
-OggOpusEnc *ope_create_callbacks(const OpusEncCallbacks *callbacks, void *user_data,
-    const OggOpusComments *comments, int rate, int channels, int family, int *error) {
+OggOpusEnc *ope_encoder_create_callbacks(const OpusEncCallbacks *callbacks, void *user_data,
+    OggOpusComments *comments, opus_int32 rate, int channels, int family, int *error) {
   OpusMSEncoder *st=NULL;
   OggOpusEnc *enc=NULL;
   int ret;
@@ -321,9 +293,7 @@ OggOpusEnc *ope_create_callbacks(const OpusEncCallbacks *callbacks, void *user_d
   if ( (enc->streams = stream_create(comments)) == NULL) goto fail;
   enc->streams->next = NULL;
   enc->last_stream = enc->streams;
-#ifdef USE_OGGP
   enc->oggp = NULL;
-#endif
   enc->unrecoverable = 0;
   enc->pull_api = 0;
   enc->packet_callback = NULL;
@@ -353,21 +323,24 @@ OggOpusEnc *ope_create_callbacks(const OpusEncCallbacks *callbacks, void *user_d
     enc->re = NULL;
   }
   opus_multistream_encoder_ctl(st, OPUS_SET_EXPERT_FRAME_DURATION(OPUS_FRAMESIZE_20_MS));
-  {
-    opus_int32 tmp;
-    int ret;
-    ret = opus_multistream_encoder_ctl(st, OPUS_GET_LOOKAHEAD(&tmp));
-    if (ret == OPUS_OK) enc->header.preskip = tmp;
-    else enc->header.preskip = 0;
-    enc->global_granule_offset = enc->header.preskip;
-  }
+  enc->global_granule_offset = -1;
   enc->curr_granule = 0;
   enc->write_granule = 0;
   enc->last_page_granule = 0;
   if ( (enc->buffer = malloc(sizeof(*enc->buffer)*BUFFER_SAMPLES*channels)) == NULL) goto fail;
+  if (rate != 48000) {
+    /* Allocate an extra LPC_PADDING samples so we can do the padding in-place. */
+    if ( (enc->lpc_buffer = malloc(sizeof(*enc->lpc_buffer)*(LPC_INPUT+LPC_PADDING)*channels)) == NULL) goto fail;
+    memset(enc->lpc_buffer, 0, sizeof(*enc->lpc_buffer)*LPC_INPUT*channels);
+  } else {
+    enc->lpc_buffer = NULL;
+  }
   enc->buffer_start = enc->buffer_end = 0;
   enc->st = st;
-  enc->callbacks = *callbacks;
+  if (callbacks != NULL)
+  {
+    enc->callbacks = *callbacks;
+  }
   enc->streams->user_data = user_data;
   if (error) *error = OPE_OK;
   return enc;
@@ -376,6 +349,7 @@ fail:
     free(enc);
     if (enc->buffer) free(enc->buffer);
     if (enc->streams) stream_destroy(enc->streams);
+    if (enc->lpc_buffer) free(enc->lpc_buffer);
   }
   if (st) {
     opus_multistream_encoder_destroy(st);
@@ -385,9 +359,9 @@ fail:
 }
 
 /* Create a new OggOpus stream, pulling one page at a time. */
-OPE_EXPORT OggOpusEnc *ope_create_pull(const OggOpusComments *comments, int rate, int channels, int family, int *error) {
-  OggOpusEnc *enc = ope_create_callbacks(NULL, NULL, comments, rate, channels, family, error);
-  enc->pull_api = 1;
+OggOpusEnc *ope_encoder_create_pull(OggOpusComments *comments, opus_int32 rate, int channels, int family, int *error) {
+  OggOpusEnc *enc = ope_encoder_create_callbacks(NULL, NULL, comments, rate, channels, family, error);
+  if (enc) enc->pull_api = 1;
   return enc;
 }
 
@@ -397,7 +371,6 @@ static void init_stream(OggOpusEnc *enc) {
     enc->streams->serialno = rand();
   }
 
-#ifdef USE_OGGP
   if (enc->oggp != NULL) oggp_chain(enc->oggp, enc->streams->serialno);
   else {
     enc->oggp = oggp_create(enc->streams->serialno);
@@ -407,54 +380,31 @@ static void init_stream(OggOpusEnc *enc) {
     }
     oggp_set_muxing_delay(enc->oggp, enc->max_ogg_delay);
   }
-#else
-  if (ogg_stream_init(&enc->streams->os, enc->streams->serialno) == -1) {
-    enc->unrecoverable = 1;
-    return;
-  }
-#endif
-  comment_pad(&enc->streams->comment, &enc->streams->comment_length, enc->comment_padding);
+  _ope_comment_pad(&enc->streams->comment, &enc->streams->comment_length, enc->comment_padding);
 
+  /* Get preskip at the last minute (when it can no longer change). */
+  if (enc->global_granule_offset == -1) {
+    opus_int32 tmp;
+    int ret;
+    ret = opus_multistream_encoder_ctl(enc->st, OPUS_GET_LOOKAHEAD(&tmp));
+    if (ret == OPUS_OK) enc->header.preskip = tmp;
+    else enc->header.preskip = 0;
+    enc->global_granule_offset = enc->header.preskip;
+  }
   /*Write header*/
   {
-#ifdef USE_OGGP
+    int packet_size;
     unsigned char *p;
     p = oggp_get_packet_buffer(enc->oggp, 276);
-    int packet_size = opus_header_to_packet(&enc->header, p, 276);
+    packet_size = _ope_opus_header_to_packet(&enc->header, p, 276);
+    if (enc->packet_callback) enc->packet_callback(enc->packet_callback_data, p, packet_size, 0);
     oggp_commit_packet(enc->oggp, packet_size, 0, 0);
     oe_flush_page(enc);
     p = oggp_get_packet_buffer(enc->oggp, enc->streams->comment_length);
     memcpy(p, enc->streams->comment, enc->streams->comment_length);
+    if (enc->packet_callback) enc->packet_callback(enc->packet_callback_data, p, enc->streams->comment_length, 0);
     oggp_commit_packet(enc->oggp, enc->streams->comment_length, 0, 0);
     oe_flush_page(enc);
-
-#else
-
-    ogg_packet op;
-    /*The Identification Header is 19 bytes, plus a Channel Mapping Table for
-      mapping families other than 0. The Channel Mapping Table is 2 bytes +
-      1 byte per channel. Because the maximum number of channels is 255, the
-      maximum size of this header is 19 + 2 + 255 = 276 bytes.*/
-    unsigned char header_data[276];
-    int packet_size = opus_header_to_packet(&enc->header, header_data, sizeof(header_data));
-    op.packet=header_data;
-    op.bytes=packet_size;
-    op.b_o_s=1;
-    op.e_o_s=0;
-    op.granulepos=0;
-    op.packetno=0;
-    ogg_stream_packetin(&enc->streams->os, &op);
-    oe_flush_page(enc);
-
-    op.packet = (unsigned char *)enc->streams->comment;
-    op.bytes = enc->streams->comment_length;
-    op.b_o_s = 0;
-    op.e_o_s = 0;
-    op.granulepos = 0;
-    op.packetno = 1;
-    ogg_stream_packetin(&enc->streams->os, &op);
-    oe_flush_page(enc);
-#endif
   }
   enc->streams->stream_is_init = 1;
   enc->streams->packetno = 2;
@@ -463,25 +413,25 @@ static void init_stream(OggOpusEnc *enc) {
 static void shift_buffer(OggOpusEnc *enc) {
   /* Leaving enough in the buffer to do LPC extension if needed. */
   if (enc->buffer_start > LPC_INPUT) {
-    memmove(&enc->buffer[enc->channels*LPC_INPUT], &enc->buffer[enc->channels*enc->buffer_start], enc->channels*(enc->buffer_end-enc->buffer_start)*sizeof(*enc->buffer));
+    memmove(&enc->buffer[0], &enc->buffer[enc->channels*(enc->buffer_start-LPC_INPUT)],
+            enc->channels*(enc->buffer_end-enc->buffer_start+LPC_INPUT)*sizeof(*enc->buffer));
     enc->buffer_end -= enc->buffer_start-LPC_INPUT;
     enc->buffer_start = LPC_INPUT;
   }
 }
 
 static void encode_buffer(OggOpusEnc *enc) {
+  opus_int32 max_packet_size;
   /* Round up when converting the granule pos because the decoder will round down. */
-  ogg_int64_t end_granule48k = (enc->streams->end_granule*48000 + enc->rate - 1)/enc->rate + enc->global_granule_offset;
+  opus_int64 end_granule48k = (enc->streams->end_granule*48000 + enc->rate - 1)/enc->rate + enc->global_granule_offset;
+  max_packet_size = (1277*6+2)*enc->header.nb_streams;
   while (enc->buffer_end-enc->buffer_start > enc->frame_size + enc->decision_delay) {
     int cont;
+    int e_o_s;
     opus_int32 pred;
-    int flush_needed;
-    ogg_packet op;
-#ifndef USE_OGGP
-    ogg_page og;
-#endif
     int nbBytes;
-    unsigned char packet[MAX_PACKET_SIZE];
+    unsigned char *packet;
+    unsigned char *packet_copy = NULL;
     int is_keyframe=0;
     if (enc->unrecoverable) return;
     opus_multistream_encoder_ctl(enc->st, OPUS_GET_PREDICTION_DISABLED(&pred));
@@ -491,8 +441,9 @@ static void encode_buffer(OggOpusEnc *enc) {
       opus_multistream_encoder_ctl(enc->st, OPUS_SET_PREDICTION_DISABLED(1));
       is_keyframe = 1;
     }
+    packet = oggp_get_packet_buffer(enc->oggp, max_packet_size);
     nbBytes = opus_multistream_encode_float(enc->st, &enc->buffer[enc->channels*enc->buffer_start],
-        enc->buffer_end-enc->buffer_start, packet, MAX_PACKET_SIZE);
+        enc->buffer_end-enc->buffer_start, packet, max_packet_size);
     if (nbBytes < 0) {
       /* Anything better we can do here? */
       enc->unrecoverable = 1;
@@ -501,52 +452,40 @@ static void encode_buffer(OggOpusEnc *enc) {
     opus_multistream_encoder_ctl(enc->st, OPUS_SET_PREDICTION_DISABLED(pred));
     assert(nbBytes > 0);
     enc->curr_granule += enc->frame_size;
-    op.packet=packet;
-    op.bytes=nbBytes;
-    op.b_o_s=0;
-    op.packetno=enc->streams->packetno++;
     do {
-      op.granulepos=enc->curr_granule-enc->streams->granule_offset;
-      op.e_o_s=enc->curr_granule >= end_granule48k;
+      opus_int64 granulepos;
+      granulepos=enc->curr_granule-enc->streams->granule_offset;
+      e_o_s=enc->curr_granule >= end_granule48k;
       cont = 0;
-      if (op.e_o_s) op.granulepos=end_granule48k-enc->streams->granule_offset;
-#ifdef USE_OGGP
-      {
-        unsigned char *p;
-        p = oggp_get_packet_buffer(enc->oggp, MAX_PACKET_SIZE);
-        memcpy(p, packet, nbBytes);
-        oggp_commit_packet(enc->oggp, nbBytes, op.granulepos, op.e_o_s);
+      if (e_o_s) granulepos=end_granule48k-enc->streams->granule_offset;
+      if (packet_copy != NULL) {
+        packet = oggp_get_packet_buffer(enc->oggp, max_packet_size);
+        memcpy(packet, packet_copy, nbBytes);
       }
-#else
-      ogg_stream_packetin(&enc->streams->os, &op);
-#endif
-      if (enc->packet_callback) enc->packet_callback(enc->streams->user_data, op.packet, op.bytes, 0);
-      /* FIXME: Also flush on too many segments. */
-#ifdef USE_OGGP
-      flush_needed = op.e_o_s;
-      if (flush_needed) oe_flush_page(enc);
-      else if (!enc->pull_api) output_pages(enc);
-#else
-      flush_needed = op.e_o_s || enc->curr_granule - enc->last_page_granule >= enc->max_ogg_delay;
-      if (flush_needed) {
-        oe_flush_page(enc);
-      } else {
-        while (ogg_stream_pageout_fill(&enc->streams->os, &og, 255*255)) {
-          if (ogg_page_packets(&og) != 0) enc->last_page_granule = ogg_page_granulepos(&og) + enc->streams->granule_offset;
-          int ret = oe_write_page(enc, &og, enc->streams->user_data);
-          /* FIXME: what do we do if this fails? */
-          assert(ret != -1);
+      if (enc->packet_callback) enc->packet_callback(enc->packet_callback_data, packet, nbBytes, 0);
+      if ((e_o_s || is_keyframe) && packet_copy == NULL) {
+        packet_copy = malloc(nbBytes);
+        if (packet_copy == NULL) {
+          /* Can't recover from allocation failing here. */
+          enc->unrecoverable = 1;
+          return;
         }
+        memcpy(packet_copy, packet, nbBytes);
       }
-#endif
-      if (op.e_o_s) {
+      oggp_commit_packet(enc->oggp, nbBytes, granulepos, e_o_s);
+      if (e_o_s) oe_flush_page(enc);
+      else if (!enc->pull_api) output_pages(enc);
+      if (e_o_s) {
         EncStream *tmp;
         tmp = enc->streams->next;
-        if (enc->streams->close_at_end) enc->callbacks.close(enc->streams->user_data);
+        if (enc->streams->close_at_end && !enc->pull_api) enc->callbacks.close(enc->streams->user_data);
         stream_destroy(enc->streams);
         enc->streams = tmp;
         if (!tmp) enc->last_stream = NULL;
-        if (enc->last_stream == NULL) return;
+        if (enc->last_stream == NULL) {
+          if (packet_copy) free(packet_copy);
+          return;
+        }
         /* We're done with this stream, start the next one. */
         enc->header.preskip = end_granule48k + enc->frame_size - enc->curr_granule;
         enc->streams->granule_offset = enc->curr_granule - enc->frame_size;
@@ -556,24 +495,12 @@ static void encode_buffer(OggOpusEnc *enc) {
         }
         init_stream(enc);
         if (enc->chaining_keyframe) {
-          ogg_packet op2;
-          op2.packet = enc->chaining_keyframe;
-          op2.bytes = enc->chaining_keyframe_length;
-          op2.b_o_s = 0;
-          op2.e_o_s = 0;
-          op2.packetno=enc->streams->packetno++;
-          op2.granulepos=enc->curr_granule - enc->streams->granule_offset - enc->frame_size;
-#ifdef USE_OGGP
-          {
-            unsigned char *p;
-            p = oggp_get_packet_buffer(enc->oggp, MAX_PACKET_SIZE);
-            memcpy(p, enc->chaining_keyframe, enc->chaining_keyframe_length);
-            oggp_commit_packet(enc->oggp, enc->chaining_keyframe_length, op2.granulepos, 0);
-          }
-#else
-          ogg_stream_packetin(&enc->streams->os, &op2);
-#endif
-          if (enc->packet_callback) enc->packet_callback(enc->streams->user_data, op2.packet, op2.bytes, 0);
+          unsigned char *p;
+          opus_int64 granulepos2=enc->curr_granule - enc->streams->granule_offset - enc->frame_size;
+          p = oggp_get_packet_buffer(enc->oggp, enc->chaining_keyframe_length);
+          memcpy(p, enc->chaining_keyframe, enc->chaining_keyframe_length);
+          if (enc->packet_callback) enc->packet_callback(enc->packet_callback_data, enc->chaining_keyframe, enc->chaining_keyframe_length, 0);
+          oggp_commit_packet(enc->oggp, enc->chaining_keyframe_length, granulepos2, 0);
         }
         end_granule48k = (enc->streams->end_granule*48000 + enc->rate - 1)/enc->rate + enc->global_granule_offset;
         cont = 1;
@@ -581,13 +508,14 @@ static void encode_buffer(OggOpusEnc *enc) {
     } while (cont);
     if (enc->chaining_keyframe) free(enc->chaining_keyframe);
     if (is_keyframe) {
-      enc->chaining_keyframe = malloc(nbBytes);
       enc->chaining_keyframe_length = nbBytes;
-      memcpy(enc->chaining_keyframe, packet, nbBytes);
+      enc->chaining_keyframe = packet_copy;
+      packet_copy = NULL;
     } else {
       enc->chaining_keyframe = NULL;
       enc->chaining_keyframe_length = -1;
     }
+    if (packet_copy) free(packet_copy);
     enc->buffer_start += enc->frame_size;
   }
   /* If we've reached the end of the buffer, move everything back to the front. */
@@ -599,7 +527,7 @@ static void encode_buffer(OggOpusEnc *enc) {
 }
 
 /* Add/encode any number of float samples to the file. */
-int ope_write_float(OggOpusEnc *enc, const float *pcm, int samples_per_channel) {
+int ope_encoder_write_float(OggOpusEnc *enc, const float *pcm, int samples_per_channel) {
   int channels = enc->channels;
   if (enc->unrecoverable) return OPE_UNRECOVERABLE;
   enc->last_stream->header_is_frozen = 1;
@@ -607,6 +535,15 @@ int ope_write_float(OggOpusEnc *enc, const float *pcm, int samples_per_channel)
   if (samples_per_channel < 0) return OPE_BAD_ARG;
   enc->write_granule += samples_per_channel;
   enc->last_stream->end_granule = enc->write_granule;
+  if (enc->lpc_buffer) {
+    int i;
+    if (samples_per_channel < LPC_INPUT) {
+      for (i=0;i<(LPC_INPUT-samples_per_channel)*channels;i++) enc->lpc_buffer[i] = enc->lpc_buffer[samples_per_channel*channels + i];
+      for (i=0;i<samples_per_channel*channels;i++) enc->lpc_buffer[(LPC_INPUT-samples_per_channel)*channels] = pcm[i];
+    } else {
+      for (i=0;i<LPC_INPUT*channels;i++) enc->lpc_buffer[i] = pcm[(samples_per_channel-LPC_INPUT)*channels + i];
+    }
+  }
   do {
     int i;
     spx_uint32_t in_samples, out_samples;
@@ -631,10 +568,10 @@ int ope_write_float(OggOpusEnc *enc, const float *pcm, int samples_per_channel)
   return OPE_OK;
 }
 
-#define CONVERT_BUFFER 256
+#define CONVERT_BUFFER 4096
 
 /* Add/encode any number of int16 samples to the file. */
-int ope_write(OggOpusEnc *enc, const opus_int16 *pcm, int samples_per_channel) {
+int ope_encoder_write(OggOpusEnc *enc, const opus_int16 *pcm, int samples_per_channel) {
   int channels = enc->channels;
   if (enc->unrecoverable) return OPE_UNRECOVERABLE;
   enc->last_stream->header_is_frozen = 1;
@@ -642,13 +579,22 @@ int ope_write(OggOpusEnc *enc, const opus_int16 *pcm, int samples_per_channel) {
   if (samples_per_channel < 0) return OPE_BAD_ARG;
   enc->write_granule += samples_per_channel;
   enc->last_stream->end_granule = enc->write_granule;
+  if (enc->lpc_buffer) {
+    int i;
+    if (samples_per_channel < LPC_INPUT) {
+      for (i=0;i<(LPC_INPUT-samples_per_channel)*channels;i++) enc->lpc_buffer[i] = enc->lpc_buffer[samples_per_channel*channels + i];
+      for (i=0;i<samples_per_channel*channels;i++) enc->lpc_buffer[(LPC_INPUT-samples_per_channel)*channels + i] = (1.f/32768)*pcm[i];
+    } else {
+      for (i=0;i<LPC_INPUT*channels;i++) enc->lpc_buffer[i] = (1.f/32768)*pcm[(samples_per_channel-LPC_INPUT)*channels + i];
+    }
+  }
   do {
     int i;
     spx_uint32_t in_samples, out_samples;
     out_samples = BUFFER_SAMPLES-enc->buffer_end;
     if (enc->re != NULL) {
-      float buf[CONVERT_BUFFER*MAX_CHANNELS];
-      in_samples = MIN(CONVERT_BUFFER, samples_per_channel);
+      float buf[CONVERT_BUFFER];
+      in_samples = MIN(CONVERT_BUFFER/channels, samples_per_channel);
       for (i=0;i<channels*(int)in_samples;i++) {
         buf[i] = (1.f/32768)*pcm[i];
       }
@@ -671,7 +617,7 @@ int ope_write(OggOpusEnc *enc, const opus_int16 *pcm, int samples_per_channel) {
 }
 
 /* Get the next page from the stream. Returns 1 if there is a page available, 0 if not. */
-OPE_EXPORT int ope_get_page(OggOpusEnc *enc, unsigned char **page, int *len, int flush) {
+int ope_encoder_get_page(OggOpusEnc *enc, unsigned char **page, opus_int32 *len, int flush) {
   if (enc->unrecoverable) return OPE_UNRECOVERABLE;
   if (!enc->pull_api) return 0;
   else {
@@ -682,19 +628,35 @@ OPE_EXPORT int ope_get_page(OggOpusEnc *enc, unsigned char **page, int *len, int
 
 static void extend_signal(float *x, int before, int after, int channels);
 
-int ope_drain(OggOpusEnc *enc) {
+int ope_encoder_drain(OggOpusEnc *enc) {
   int pad_samples;
+  int resampler_drain = 0;
   if (enc->unrecoverable) return OPE_UNRECOVERABLE;
   /* Check if it's already been drained. */
   if (enc->streams == NULL) return OPE_TOO_LATE;
-  pad_samples = MAX(LPC_PADDING, enc->global_granule_offset + enc->frame_size);
+  if (enc->re) resampler_drain = speex_resampler_get_output_latency(enc->re);
+  pad_samples = MAX(LPC_PADDING, enc->global_granule_offset + enc->frame_size + resampler_drain);
   if (!enc->streams->stream_is_init) init_stream(enc);
   shift_buffer(enc);
   assert(enc->buffer_end + pad_samples <= BUFFER_SAMPLES);
   memset(&enc->buffer[enc->channels*enc->buffer_end], 0, pad_samples*enc->channels*sizeof(enc->buffer[0]));
-  extend_signal(&enc->buffer[enc->channels*enc->buffer_end], enc->buffer_end, LPC_PADDING, enc->channels);
+  if (enc->re) {
+    spx_uint32_t in_samples, out_samples;
+    extend_signal(&enc->lpc_buffer[LPC_INPUT*enc->channels], LPC_INPUT, LPC_PADDING, enc->channels);
+    do {
+      in_samples = LPC_PADDING;
+      out_samples = pad_samples;
+      speex_resampler_process_interleaved_float(enc->re, &enc->lpc_buffer[LPC_INPUT*enc->channels], &in_samples, &enc->buffer[enc->channels*enc->buffer_end], &out_samples);
+      enc->buffer_end += out_samples;
+      pad_samples -= out_samples;
+      /* If we don't have enough padding, zero all zeros and repeat. */
+      memset(&enc->lpc_buffer[LPC_INPUT*enc->channels], 0, LPC_PADDING*enc->channels*sizeof(enc->lpc_buffer[0]));
+    } while (pad_samples > 0);
+  } else {
+    extend_signal(&enc->buffer[enc->channels*enc->buffer_end], enc->buffer_end, LPC_PADDING, enc->channels);
+    enc->buffer_end += pad_samples;
+  }
   enc->decision_delay = 0;
-  enc->buffer_end += pad_samples;
   assert(enc->buffer_end <= BUFFER_SAMPLES);
   encode_buffer(enc);
   if (enc->unrecoverable) return OPE_UNRECOVERABLE;
@@ -703,7 +665,7 @@ int ope_drain(OggOpusEnc *enc) {
   return OPE_OK;
 }
 
-void ope_destroy(OggOpusEnc *enc) {
+void ope_encoder_destroy(OggOpusEnc *enc) {
   EncStream *stream;
   stream = enc->streams;
   while (stream != NULL) {
@@ -714,22 +676,21 @@ void ope_destroy(OggOpusEnc *enc) {
   }
   if (enc->chaining_keyframe) free(enc->chaining_keyframe);
   free(enc->buffer);
-#ifdef USE_OGGP
   if (enc->oggp) oggp_destroy(enc->oggp);
-#endif
   opus_multistream_encoder_destroy(enc->st);
   if (enc->re) speex_resampler_destroy(enc->re);
+  if (enc->lpc_buffer) free(enc->lpc_buffer);
   free(enc);
 }
 
 /* Ends the stream and create a new stream within the same file. */
-int ope_chain_current(OggOpusEnc *enc, const OggOpusComments *comments) {
+int ope_encoder_chain_current(OggOpusEnc *enc, OggOpusComments *comments) {
   enc->last_stream->close_at_end = 0;
-  return ope_continue_new_callbacks(enc, enc->last_stream->user_data, comments);
+  return ope_encoder_continue_new_callbacks(enc, enc->last_stream->user_data, comments);
 }
 
 /* Ends the stream and create a new file. */
-int ope_continue_new_file(OggOpusEnc *enc, const char *path, const OggOpusComments *comments) {
+int ope_encoder_continue_new_file(OggOpusEnc *enc, const char *path, OggOpusComments *comments) {
   int ret;
   struct StdioObject *obj;
   if (!(obj = malloc(sizeof(*obj)))) return OPE_ALLOC_FAIL;
@@ -739,7 +700,7 @@ int ope_continue_new_file(OggOpusEnc *enc, const char *path, const OggOpusCommen
     /* By trying to open the file first, we can recover if we can't open it. */
     return OPE_CANNOT_OPEN;
   }
-  ret = ope_continue_new_callbacks(enc, obj, comments);
+  ret = ope_encoder_continue_new_callbacks(enc, obj, comments);
   if (ret == OPE_OK) return ret;
   fclose(obj->file);
   free(obj);
@@ -747,20 +708,21 @@ int ope_continue_new_file(OggOpusEnc *enc, const char *path, const OggOpusCommen
 }
 
 /* Ends the stream and create a new file (callback-based). */
-int ope_continue_new_callbacks(OggOpusEnc *enc, void *user_data, const OggOpusComments *comments) {
-  if (enc->unrecoverable) return OPE_UNRECOVERABLE;
+int ope_encoder_continue_new_callbacks(OggOpusEnc *enc, void *user_data, OggOpusComments *comments) {
   EncStream *new_stream;
+  if (enc->unrecoverable) return OPE_UNRECOVERABLE;
   assert(enc->streams);
   assert(enc->last_stream);
   new_stream = stream_create(comments);
   if (!new_stream) return OPE_ALLOC_FAIL;
   new_stream->user_data = user_data;
+  new_stream->end_granule = enc->write_granule;
   enc->last_stream->next = new_stream;
   enc->last_stream = new_stream;
   return OPE_OK;
 }
 
-int ope_flush_header(OggOpusEnc *enc) {
+int ope_encoder_flush_header(OggOpusEnc *enc) {
   if (enc->unrecoverable) return OPE_UNRECOVERABLE;
   if (enc->last_stream->header_is_frozen) return OPE_TOO_LATE;
   if (enc->last_stream->stream_is_init) return OPE_TOO_LATE;
@@ -775,6 +737,7 @@ int ope_encoder_ctl(OggOpusEnc *enc, int request, ...) {
   va_list ap;
   if (enc->unrecoverable) return OPE_UNRECOVERABLE;
   va_start(ap, request);
+  ret = OPE_OK;
   switch (request) {
     case OPUS_SET_APPLICATION_REQUEST:
     case OPUS_SET_BITRATE_REQUEST:
@@ -798,6 +761,12 @@ int ope_encoder_ctl(OggOpusEnc *enc, int request, ...) {
       ret = opus_multistream_encoder_ctl(enc->st, request, value);
     }
     break;
+    case OPUS_GET_LOOKAHEAD_REQUEST:
+    {
+      opus_int32 *value = va_arg(ap, opus_int32*);
+      ret = opus_multistream_encoder_ctl(enc->st, request, value);
+    }
+    break;
     case OPUS_SET_EXPERT_FRAME_DURATION_REQUEST:
     {
       opus_int32 value = va_arg(ap, opus_int32);
@@ -818,6 +787,28 @@ int ope_encoder_ctl(OggOpusEnc *enc, int request, ...) {
       }
     }
     break;
+    case OPUS_GET_APPLICATION_REQUEST:
+    case OPUS_GET_BITRATE_REQUEST:
+    case OPUS_GET_MAX_BANDWIDTH_REQUEST:
+    case OPUS_GET_VBR_REQUEST:
+    case OPUS_GET_BANDWIDTH_REQUEST:
+    case OPUS_GET_COMPLEXITY_REQUEST:
+    case OPUS_GET_INBAND_FEC_REQUEST:
+    case OPUS_GET_PACKET_LOSS_PERC_REQUEST:
+    case OPUS_GET_DTX_REQUEST:
+    case OPUS_GET_VBR_CONSTRAINT_REQUEST:
+    case OPUS_GET_FORCE_CHANNELS_REQUEST:
+    case OPUS_GET_SIGNAL_REQUEST:
+    case OPUS_GET_LSB_DEPTH_REQUEST:
+    case OPUS_GET_PREDICTION_DISABLED_REQUEST:
+#ifdef OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST
+    case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST:
+#endif
+    {
+      opus_int32 *value = va_arg(ap, opus_int32*);
+      ret = opus_multistream_encoder_ctl(enc->st, request, value);
+    }
+    break;
     case OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST:
     {
       opus_int32 stream_id;
@@ -836,8 +827,14 @@ int ope_encoder_ctl(OggOpusEnc *enc, int request, ...) {
         ret = OPE_BAD_ARG;
         break;
       }
+      value = MIN(value, MAX_LOOKAHEAD);
       enc->decision_delay = value;
-      ret = OPE_OK;
+    }
+    break;
+    case OPE_GET_DECISION_DELAY_REQUEST:
+    {
+      opus_int32 *value = va_arg(ap, opus_int32*);
+      *value = enc->decision_delay;
     }
     break;
     case OPE_SET_MUXING_DELAY_REQUEST:
@@ -848,10 +845,13 @@ int ope_encoder_ctl(OggOpusEnc *enc, int request, ...) {
         break;
       }
       enc->max_ogg_delay = value;
-#ifdef USE_OGGP
-      oggp_set_muxing_delay(enc->oggp, enc->max_ogg_delay);
-#endif
-      ret = OPE_OK;
+      if (enc->oggp) oggp_set_muxing_delay(enc->oggp, enc->max_ogg_delay);
+    }
+    break;
+    case OPE_GET_MUXING_DELAY_REQUEST:
+    {
+      opus_int32 *value = va_arg(ap, opus_int32*);
+      *value = enc->max_ogg_delay;
     }
     break;
     case OPE_SET_COMMENT_PADDING_REQUEST:
@@ -865,26 +865,61 @@ int ope_encoder_ctl(OggOpusEnc *enc, int request, ...) {
       ret = OPE_OK;
     }
     break;
+    case OPE_GET_COMMENT_PADDING_REQUEST:
+    {
+      opus_int32 *value = va_arg(ap, opus_int32*);
+      *value = enc->comment_padding;
+    }
+    break;
     case OPE_SET_SERIALNO_REQUEST:
     {
       opus_int32 value = va_arg(ap, opus_int32);
-      if (enc->last_stream->header_is_frozen) return OPE_TOO_LATE;
+      if (!enc->last_stream || enc->last_stream->header_is_frozen) {
+        ret = OPE_TOO_LATE;
+        break;
+      }
       enc->last_stream->serialno = value;
       enc->last_stream->serialno_is_set = 1;
       ret = OPE_OK;
     }
+    break;
+    case OPE_GET_SERIALNO_REQUEST:
+    {
+      opus_int32 *value = va_arg(ap, opus_int32*);
+      *value = enc->last_stream->serialno;
+    }
+    break;
     case OPE_SET_PACKET_CALLBACK_REQUEST:
     {
       ope_packet_func value = va_arg(ap, ope_packet_func);
+      void *data = va_arg(ap, void *);
       enc->packet_callback = value;
+      enc->packet_callback_data = data;
       ret = OPE_OK;
     }
     break;
+    case OPE_SET_HEADER_GAIN_REQUEST:
+    {
+      opus_int32 value = va_arg(ap, opus_int32);
+      if (!enc->last_stream || enc->last_stream->header_is_frozen) {
+        ret = OPE_TOO_LATE;
+        break;
+      }
+      enc->header.gain = value;
+      ret = OPE_OK;
+    }
+    break;
+    case OPE_GET_HEADER_GAIN_REQUEST:
+    {
+      opus_int32 *value = va_arg(ap, opus_int32*);
+      *value = enc->header.gain;
+    }
+    break;
     default:
-      return OPE_UNIMPLEMENTED;
+      ret = OPE_UNIMPLEMENTED;
   }
   va_end(ap);
-  translate = request < 14000 || (ret < 0 && ret >= -10);
+  translate = ret != 0 && (request < 14000 || (ret < 0 && ret >= -10));
   if (translate) {
     if (ret == OPUS_BAD_ARG) ret = OPE_BAD_ARG;
     else if (ret == OPUS_INTERNAL_ERROR) ret = OPE_INTERNAL_ERROR;
@@ -896,6 +931,29 @@ int ope_encoder_ctl(OggOpusEnc *enc, int request, ...) {
   return ret;
 }
 
+const char *ope_strerror(int error) {
+  static const char * const ope_error_strings[5] = {
+    "cannot open file",
+    "call cannot be made at this point",
+    "unrecoverable error",
+    "invalid picture file",
+    "invalid icon file (pictures of type 1 MUST be 32x32 PNGs)"
+  };
+  if (error == 0) return "success";
+  else if (error >= -10) return "unknown error";
+  else if (error > -30) return opus_strerror(error+10);
+  else if (error >= OPE_INVALID_ICON) return ope_error_strings[-error-30];
+  else return "unknown error";
+}
+
+const char *ope_get_version_string(void)
+{
+  return "libopusenc " PACKAGE_VERSION;
+}
+
+int ope_get_abi_version(void) {
+  return OPE_ABI_VERSION;
+}
 
 static void vorbis_lpc_from_data(float *data, float *lpci, int n, int stride);