ref: 40e3c7034f9003326725f114b7530d085e8c1dde
parent: a1b213a3b9e4025ebe8a31c080081f4e4430477d
author: Andrew Allen <[email protected]>
date: Mon Mar 19 10:56:50 EDT 2018
Support for Ambisonics. Signed-off-by: Jean-Marc Valin <[email protected]>
--- 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;