shithub: libopusenc

Download patch

ref: 40e3c7034f9003326725f114b7530d085e8c1dde
parent: a1b213a3b9e4025ebe8a31c080081f4e4430477d
author: Andrew Allen <bitllama@google.com>
date: Mon Mar 19 10:56:50 EDT 2018

Support for Ambisonics.

Signed-off-by: Jean-Marc Valin <jmvalin@jmvalin.ca>

--- a/src/opus_header.c
+++ b/src/opus_header.c
@@ -46,7 +46,8 @@
   - if (mapping != 0)
      - N = total number of streams (8 bits)
      - M = number of paired streams (8 bits)
-     - C times channel origin
+     - if (mapping != a projection family)
+       - C times channel origin
           - if (C<2*M)
              - stream = byte/2
              - if (byte&0x1 == 0)
@@ -55,6 +56,8 @@
                  - right
           - else
              - stream = byte-M
+    - else
+       - D demixing matrix (C*(N+M)*16 bits)
 */
 
 typedef struct {
@@ -101,8 +104,50 @@
    return 1;
 }
 
-int _ope_opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len)
+static int write_matrix_chars(Packet *p, const OpusGenericEncoder *st)
 {
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+  opus_int32 size;
+  int ret;
+  ret=opeint_encoder_ctl(st, OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE(&size));
+  if (ret != OPUS_OK) return 0;
+  if (size>p->maxlen-p->pos) return 0;
+  ret=opeint_encoder_ctl(st, OPUS_PROJECTION_GET_DEMIXING_MATRIX(&p->data[p->pos], size));
+  if (ret != OPUS_OK) return 0;
+  p->pos += size;
+  return 1;
+#else
+  (void)p;
+  (void)st;
+  return 0;
+#endif
+}
+
+int _ope_opus_header_get_size(const OpusHeader *h)
+{
+  int len=0;
+  if (opeint_use_projection(h->channel_mapping))
+  {
+    /* 19 bytes from fixed header,
+     * 2 bytes for nb_streams & nb_coupled,
+     * 2 bytes per cell of demixing matrix, where:
+     *    rows=channels, cols=nb_streams+nb_coupled
+     */
+    len=21+(h->channels*(h->nb_streams+h->nb_coupled)*2);
+  }
+  else
+  {
+    /* 19 bytes from fixed header,
+     * 2 bytes for nb_streams & nb_coupled,
+     * 1 byte per channel
+     */
+    len=21+h->channels;
+  }
+  return len;
+}
+
+int _ope_opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len, const OpusGenericEncoder *st)
+{
    int i;
    Packet p;
    unsigned char ch;
@@ -128,8 +173,24 @@
    if (!write_uint32(&p, h->input_sample_rate))
       return 0;
 
-   if (!write_uint16(&p, h->gain))
+   if (opeint_use_projection(h->channel_mapping))
+   {
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+      opus_int32 matrix_gain;
+      int ret;
+      ret=opeint_encoder_ctl(st, OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN(&matrix_gain));
+      if (ret != OPUS_OK) return 0;
+      if (!write_uint16(&p, h->gain + matrix_gain))
+         return 0;
+#else
       return 0;
+#endif
+   }
+   else
+   {
+      if (!write_uint16(&p, h->gain))
+         return 0;
+   }
 
    ch = h->channel_mapping;
    if (!write_chars(&p, &ch, 1))
@@ -146,10 +207,18 @@
          return 0;
 
       /* Multi-stream support */
-      for (i=0;i<h->channels;i++)
+      if (opeint_use_projection(h->channel_mapping))
       {
-         if (!write_chars(&p, &h->stream_map[i], 1))
-            return 0;
+        if (!write_matrix_chars(&p, st))
+           return 0;
+      }
+      else
+      {
+        for (i=0;i<h->channels;i++)
+        {
+          if (!write_chars(&p, &h->stream_map[i], 1))
+             return 0;
+        }
       }
    }
 
--- a/src/opus_header.h
+++ b/src/opus_header.h
@@ -31,6 +31,45 @@
 #include <stdlib.h>
 #include <opus.h>
 
+#include <opus_multistream.h>
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+#include <opus_projection.h>
+#endif
+
+typedef struct OpusGenericEncoder OpusGenericEncoder;
+struct OpusGenericEncoder {
+  OpusMSEncoder *ms;
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+  OpusProjectionEncoder *pr;
+#endif
+};
+
+int opeint_use_projection(int channel_mapping);
+
+int opeint_encoder_surround_init(OpusGenericEncoder *st, int Fs, int channels, int channel_mapping, int *nb_streams, int *nb_coupled, unsigned char *stream_map, int application);
+
+void opeint_encoder_cleanup(OpusGenericEncoder *st);
+
+int opeint_encoder_init(OpusGenericEncoder *st, opus_int32 Fs, int channels, int streams, int coupled_streams, const unsigned char *mapping, int application);
+
+int opeint_encode_float(OpusGenericEncoder *st, const float *pcm, int frame_size, unsigned char *data, opus_int32 max_data_bytes);
+
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+# define opeint_encoder_ctl(st, request) \
+    ((st)->pr!=NULL ? \
+    opus_projection_encoder_ctl((st)->pr, request) : \
+    opus_multistream_encoder_ctl((st)->ms, request))
+# define opeint_encoder_ctl2(st, request, value) \
+    ((st)->pr!=NULL ? \
+    opus_projection_encoder_ctl((st)->pr, request, value) : \
+    opus_multistream_encoder_ctl((st)->ms, request, value))
+#else
+# define opeint_encoder_ctl(st, request) \
+    opus_multistream_encoder_ctl((st)->ms, request)
+# define opeint_encoder_ctl2(st, request, value) \
+    opus_multistream_encoder_ctl((st)->ms, request, value)
+#endif
+
 typedef struct {
    int version;
    int channels; /* Number of channels: 1..255 */
@@ -44,7 +83,9 @@
    unsigned char stream_map[255];
 } OpusHeader;
 
-int _ope_opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len);
+int _ope_opus_header_get_size(const OpusHeader *h);
+
+int _ope_opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len, const OpusGenericEncoder *st);
 
 void _ope_comment_init(char **comments, int* length, const char *vendor_string);
 
--- a/src/opusenc.c
+++ b/src/opusenc.c
@@ -36,7 +36,6 @@
 #include <stdio.h>
 #include <string.h>
 #include <assert.h>
-#include <opus_multistream.h>
 #include "opusenc.h"
 #include "opus_header.h"
 #include "speex_resampler.h"
@@ -178,8 +177,75 @@
   EncStream *next;
 };
 
+int opeint_use_projection(int channel_mapping) {
+  if (channel_mapping==253){
+    return 1;
+  }
+  return 0;
+}
+
+int opeint_encoder_surround_init(
+    OpusGenericEncoder *st, int Fs, int channels, int channel_mapping,
+    int *nb_streams, int *nb_coupled, unsigned char *stream_map, int application) {
+  int ret;
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+  if(opeint_use_projection(channel_mapping)){
+    int ci;
+    st->pr=opus_projection_ambisonics_encoder_create(Fs, channels,
+        channel_mapping, nb_streams, nb_coupled, application, &ret);
+    for (ci = 0; ci < channels; ci++) {
+      stream_map[ci] = ci;
+    }
+    st->ms=NULL;
+  }
+  else
+#endif
+  {
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+    st->pr=NULL;
+#endif
+    st->ms=opus_multistream_surround_encoder_create(Fs, channels,
+        channel_mapping, nb_streams, nb_coupled, stream_map, application, &ret);
+  }
+  return ret;
+}
+
+void opeint_encoder_cleanup(OpusGenericEncoder *st) {
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+    if (st->pr) opus_projection_encoder_destroy(st->pr);
+#endif
+    if (st->ms) opus_multistream_encoder_destroy(st->ms);
+}
+
+int opeint_encoder_init(
+    OpusGenericEncoder *st, opus_int32 Fs, int channels, int streams,
+    int coupled_streams, const unsigned char *mapping, int application) {
+  int ret;
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+  st->pr=NULL;
+#endif
+  st->ms=opus_multistream_encoder_create(Fs, channels, streams,
+      coupled_streams, mapping, application, &ret);
+  return ret;
+}
+
+int opeint_encode_float(
+    OpusGenericEncoder *st,
+    const float *pcm,
+    int frame_size,
+    unsigned char *data,
+    opus_int32 max_data_bytes) {
+  int ret;
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+  if (st->pr) ret=opus_projection_encode_float(st->pr, pcm, frame_size, data, max_data_bytes);
+  else
+#endif
+    ret=opus_multistream_encode_float(st->ms, pcm, frame_size, data, max_data_bytes);
+  return ret;
+}
+
 struct OggOpusEnc {
-  OpusMSEncoder *st;
+  OpusGenericEncoder st;
   oggpacker *oggp;
   int unrecoverable;
   int pull_api;
@@ -215,7 +281,7 @@
   int len;
   while (oggp_get_next_page(enc->oggp, &page, &len)) {
     int ret = enc->callbacks.write(enc->streams->user_data, page, len);
-    if (ret) return ret; 
+    if (ret) return ret;
   }
   return 0;
 }
@@ -298,10 +364,13 @@
 /* Create a new OggOpus file (callback-based). */
 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;
-  if (family != 0 && family != 1 && family != 255 && family != -1) {
+  if (family != 0 && family != 1 &&
+#ifdef OPUS_HAVE_OPUS_PROJECTION_H
+      family != 253 && family != 254 &&
+#endif
+      family != 255 && family != -1) {
     if (error) {
       if (family < -1 || family > 255) *error = OPE_BAD_ARG;
       else *error = OPE_UNIMPLEMENTED;
@@ -341,10 +410,11 @@
   enc->header.input_sample_rate=rate;
   enc->header.gain=0;
   if (family != -1) {
-    st=opus_multistream_surround_encoder_create(48000, channels, enc->header.channel_mapping,
-        &enc->header.nb_streams, &enc->header.nb_coupled,
-        enc->header.stream_map, OPUS_APPLICATION_AUDIO, &ret);
-    if (! (ret == OPUS_OK && st != NULL) ) {
+    ret=opeint_encoder_surround_init(&enc->st, 48000, channels,
+        enc->header.channel_mapping, &enc->header.nb_streams,
+        &enc->header.nb_coupled, enc->header.stream_map,
+        OPUS_APPLICATION_AUDIO);
+    if (! (ret == OPUS_OK) ) {
       if (ret == OPUS_BAD_ARG) ret = OPE_BAD_ARG;
       else if (ret == OPUS_INTERNAL_ERROR) ret = OPE_INTERNAL_ERROR;
       else if (ret == OPUS_UNIMPLEMENTED) ret = OPE_UNIMPLEMENTED;
@@ -353,8 +423,7 @@
       if (error) *error = ret;
       goto fail;
     }
-    enc->st = st;
-    opus_multistream_encoder_ctl(st, OPUS_SET_EXPERT_FRAME_DURATION(OPUS_FRAMESIZE_20_MS));
+    opeint_encoder_ctl(&enc->st, OPUS_SET_EXPERT_FRAME_DURATION(OPUS_FRAMESIZE_20_MS));
   }
   if (rate != 48000) {
     enc->re = speex_resampler_init(channels, rate, 48000, 5, NULL);
@@ -384,14 +453,12 @@
   return enc;
 fail:
   if (enc) {
+    opeint_encoder_cleanup(&enc->st);
     if (enc->buffer) free(enc->buffer);
     if (enc->streams) stream_destroy(enc->streams);
     if (enc->lpc_buffer) free(enc->lpc_buffer);
     free(enc);
   }
-  if (st) {
-    opus_multistream_encoder_destroy(st);
-  }
   return NULL;
 }
 
@@ -402,19 +469,19 @@
   return enc;
 }
 
-int ope_encoder_deferred_init_with_mapping(OggOpusEnc *enc, int family, int streams, 
+int ope_encoder_deferred_init_with_mapping(OggOpusEnc *enc, int family, int streams,
     int coupled_streams, const unsigned char *mapping) {
   int ret;
   int i;
-  OpusMSEncoder *st;
-  if (enc->st!=NULL) {
-    return OPE_TOO_LATE;
-  }
   if (family < 0 || family > 255) return OPE_BAD_ARG;
-  else if (family != 1 && family != 255) return OPE_UNIMPLEMENTED;
+  else if (family != 1 &&
+  #ifdef OPUS_HAVE_OPUS_PROJECTION_H
+      family != 254 &&
+  #endif
+      family != 255) return OPE_UNIMPLEMENTED;
   else if (streams <= 0 || streams>255 || coupled_streams<0 || coupled_streams >= 128 || streams+coupled_streams > 255) return OPE_BAD_ARG;
-  st=opus_multistream_encoder_create(48000, enc->channels, streams, coupled_streams, mapping, OPUS_APPLICATION_AUDIO, &ret);
-  if (! (ret == OPUS_OK && st != NULL) ) {
+  ret=opeint_encoder_init(&enc->st, 48000, enc->channels, streams, coupled_streams, mapping, OPUS_APPLICATION_AUDIO);
+  if (! (ret == OPUS_OK) ) {
     if (ret == OPUS_BAD_ARG) ret = OPE_BAD_ARG;
     else if (ret == OPUS_INTERNAL_ERROR) ret = OPE_INTERNAL_ERROR;
     else if (ret == OPUS_UNIMPLEMENTED) ret = OPE_UNIMPLEMENTED;
@@ -422,8 +489,7 @@
     else ret = OPE_INTERNAL_ERROR;
     return ret;
   }
-  enc->st = st;
-  opus_multistream_encoder_ctl(st, OPUS_SET_EXPERT_FRAME_DURATION(OPUS_FRAMESIZE_20_MS));
+  opeint_encoder_ctl(&enc->st, OPUS_SET_EXPERT_FRAME_DURATION(OPUS_FRAMESIZE_20_MS));
   enc->unrecoverable = 0;
   enc->header.channel_mapping=family;
   enc->header.nb_streams = streams;
@@ -454,7 +520,7 @@
   if (enc->global_granule_offset == -1) {
     opus_int32 tmp;
     int ret;
-    ret = opus_multistream_encoder_ctl(enc->st, OPUS_GET_LOOKAHEAD(&tmp));
+    ret=opeint_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;
@@ -461,11 +527,13 @@
   }
   /*Write header*/
   {
+    int header_size;
     int ret;
     int packet_size;
     unsigned char *p;
-    p = oggp_get_packet_buffer(enc->oggp, 276);
-    packet_size = _ope_opus_header_to_packet(&enc->header, p, 276);
+    header_size = _ope_opus_header_get_size(&enc->header);
+    p = oggp_get_packet_buffer(enc->oggp, header_size);
+    packet_size = _ope_opus_header_to_packet(&enc->header, p, header_size, &enc->st);
     if (enc->packet_callback) enc->packet_callback(enc->packet_callback_data, p, packet_size, 0);
     oggp_commit_packet(enc->oggp, packet_size, 0, 0);
     ret = oe_flush_page(enc);
@@ -516,11 +584,11 @@
     unsigned char *packet_copy = NULL;
     int is_keyframe=0;
     if (enc->unrecoverable) return;
-    opus_multistream_encoder_ctl(enc->st, OPUS_GET_PREDICTION_DISABLED(&pred));
+    opeint_encoder_ctl(&enc->st, OPUS_GET_PREDICTION_DISABLED(&pred));
     /* FIXME: a frame that follows a keyframe generally doesn't need to be a keyframe
        unless there's two consecutive stream boundaries. */
     if (enc->curr_granule + 2*enc->frame_size>= end_granule48k && enc->streams->next) {
-      opus_multistream_encoder_ctl(enc->st, OPUS_SET_PREDICTION_DISABLED(1));
+      opeint_encoder_ctl(&enc->st, OPUS_SET_PREDICTION_DISABLED(1));
       is_keyframe = 1;
     }
     /* Handle the last packet by making sure not to encode too much padding. */
@@ -534,7 +602,7 @@
       ope_encoder_ctl(enc, OPUS_SET_EXPERT_FRAME_DURATION(frame_size_request));
     }
     packet = oggp_get_packet_buffer(enc->oggp, max_packet_size);
-    nbBytes = opus_multistream_encode_float(enc->st, &enc->buffer[enc->channels*enc->buffer_start],
+    nbBytes = opeint_encode_float(&enc->st, &enc->buffer[enc->channels*enc->buffer_start],
         enc->buffer_end-enc->buffer_start, packet, max_packet_size);
     if (nbBytes < 0) {
       /* Anything better we can do here? */
@@ -541,7 +609,7 @@
       enc->unrecoverable = OPE_INTERNAL_ERROR;
       return;
     }
-    opus_multistream_encoder_ctl(enc->st, OPUS_SET_PREDICTION_DISABLED(pred));
+    opeint_encoder_ctl(&enc->st, OPUS_SET_PREDICTION_DISABLED(pred));
     assert(nbBytes > 0);
     enc->curr_granule += enc->frame_size;
     do {
@@ -782,7 +850,7 @@
   if (enc->chaining_keyframe) free(enc->chaining_keyframe);
   free(enc->buffer);
   if (enc->oggp) oggp_destroy(enc->oggp);
-  if (enc->st) opus_multistream_encoder_destroy(enc->st);
+  opeint_encoder_cleanup(&enc->st);
   if (enc->re) speex_resampler_destroy(enc->re);
   if (enc->lpc_buffer) free(enc->lpc_buffer);
   free(enc);
@@ -863,13 +931,13 @@
 #endif
     {
       opus_int32 value = va_arg(ap, opus_int32);
-      ret = opus_multistream_encoder_ctl(enc->st, request, value);
+      ret = opeint_encoder_ctl2(&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);
+      ret = opeint_encoder_ctl(&enc->st, OPUS_GET_LOOKAHEAD(value));
     }
     break;
     case OPUS_SET_EXPERT_FRAME_DURATION_REQUEST:
@@ -883,7 +951,7 @@
         ret = OPUS_UNIMPLEMENTED;
         break;
       }
-      ret = opus_multistream_encoder_ctl(enc->st, request, value);
+      ret = opeint_encoder_ctl(&enc->st, OPUS_SET_EXPERT_FRAME_DURATION(value));
       if (ret == OPUS_OK) {
         enc->frame_size = compute_frame_samples(value);
         enc->frame_size_request = value;
@@ -909,7 +977,7 @@
 #endif
     {
       opus_int32 *value = va_arg(ap, opus_int32*);
-      ret = opus_multistream_encoder_ctl(enc->st, request, value);
+      ret = opeint_encoder_ctl2(&enc->st, request, value);
     }
     break;
     case OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST:
@@ -918,7 +986,7 @@
       OpusEncoder **value;
       stream_id = va_arg(ap, opus_int32);
       value = va_arg(ap, OpusEncoder**);
-      ret = opus_multistream_encoder_ctl(enc->st, request, stream_id, value);
+      opeint_encoder_ctl(&enc->st, OPUS_MULTISTREAM_GET_ENCODER_STATE(stream_id, value));
     }
     break;