Avoid symbol collisions (especially with static linking)
[libopusenc.git] / src / opusenc.c
index abf4324..ae47d14 100644 (file)
 #define MIN(a,b) ((a) < (b) ? (a) : (b))
 #define MAX(a,b) ((a) > (b) ? (a) : (b))
 
+#ifdef _MSC_VER
+# if (_MSC_VER < 1900)
+#  define snprintf _snprintf
+# endif
+#endif
+
 struct StdioObject {
   FILE *file;
 };
@@ -80,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;
@@ -116,25 +122,25 @@ void ope_comments_destroy(OggOpusComments *comments){
 int ope_comments_add(OggOpusComments *comments, const char *tag, const char *val) {
   if (tag == NULL || val == NULL) return OPE_BAD_ARG;
   if (strchr(tag, '=')) return OPE_BAD_ARG;
-  if (comment_add(&comments->comment, &comments->comment_length, tag, val)) return OPE_ALLOC_FAIL;
+  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 (comment_add(&comments->comment, &comments->comment_length, tag_and_val, NULL)) return OPE_ALLOC_FAIL;
+  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 *filename, int picture_type, const char *description) {
   char *picture_data;
   int err;
-  picture_data = parse_picture_specification(filename, picture_type, description, &err, &comments->seen_file_icons);
+  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;
 }
@@ -176,6 +182,7 @@ struct OggOpusEnc {
   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;
@@ -199,12 +206,12 @@ static void oe_flush_page(OggOpusEnc *enc) {
   if (!enc->pull_api) output_pages(enc);
 }
 
-int stdio_write(void *user_data, const unsigned char *ptr, opus_int32 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);
@@ -316,21 +323,24 @@ OggOpusEnc *ope_encoder_create_callbacks(const OpusEncCallbacks *callbacks, void
     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;
@@ -339,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);
@@ -348,9 +359,9 @@ fail:
 }
 
 /* Create a new OggOpus stream, pulling one page at a time. */
-OPE_EXPORT OggOpusEnc *ope_encoder_create_pull(OggOpusComments *comments, opus_int32 rate, int channels, int family, int *error) {
+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);
-  enc->pull_api = 1;
+  if (enc) enc->pull_api = 1;
   return enc;
 }
 
@@ -369,14 +380,23 @@ static void init_stream(OggOpusEnc *enc) {
     }
     oggp_set_muxing_delay(enc->oggp, enc->max_ogg_delay);
   }
-  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*/
   {
     int packet_size;
     unsigned char *p;
     p = oggp_get_packet_buffer(enc->oggp, 276);
-    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);
@@ -458,7 +478,7 @@ static void encode_buffer(OggOpusEnc *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;
@@ -515,6 +535,15 @@ int ope_encoder_write_float(OggOpusEnc *enc, const float *pcm, int samples_per_c
   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;
@@ -550,6 +579,15 @@ int ope_encoder_write(OggOpusEnc *enc, const opus_int16 *pcm, int samples_per_ch
   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;
@@ -579,7 +617,7 @@ int ope_encoder_write(OggOpusEnc *enc, const opus_int16 *pcm, int samples_per_ch
 }
 
 /* Get the next page from the stream. Returns 1 if there is a page available, 0 if not. */
-OPE_EXPORT int ope_encoder_get_page(OggOpusEnc *enc, unsigned char **page, opus_int32 *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 {
@@ -592,17 +630,33 @@ static void extend_signal(float *x, int before, int after, int channels);
 
 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;
@@ -625,6 +679,7 @@ void ope_encoder_destroy(OggOpusEnc *enc) {
   if (enc->oggp) oggp_destroy(enc->oggp);
   opus_multistream_encoder_destroy(enc->st);
   if (enc->re) speex_resampler_destroy(enc->re);
+  if (enc->lpc_buffer) free(enc->lpc_buffer);
   free(enc);
 }
 
@@ -772,6 +827,7 @@ int ope_encoder_ctl(OggOpusEnc *enc, int request, ...) {
         ret = OPE_BAD_ARG;
         break;
       }
+      value = MIN(value, MAX_LOOKAHEAD);
       enc->decision_delay = value;
     }
     break;
@@ -789,7 +845,7 @@ int ope_encoder_ctl(OggOpusEnc *enc, int request, ...) {
         break;
       }
       enc->max_ogg_delay = value;
-      oggp_set_muxing_delay(enc->oggp, enc->max_ogg_delay);
+      if (enc->oggp) oggp_set_muxing_delay(enc->oggp, enc->max_ogg_delay);
     }
     break;
     case OPE_GET_MUXING_DELAY_REQUEST: