opusenc.c 37.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
/* Copyright (C)2002-2017 Jean-Marc Valin
   Copyright (C)2007-2013 Xiph.Org Foundation
   Copyright (C)2008-2013 Gregory Maxwell
   File: opusenc.c

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:

   - Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

   - Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
   OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

34
#include <stdarg.h>
35
#include <stdlib.h>
36
#include <stdio.h>
Jean-Marc Valin's avatar
Jean-Marc Valin committed
37
#include <string.h>
Jean-Marc Valin's avatar
Jean-Marc Valin committed
38
#include <assert.h>
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
39
#include <opus_multistream.h>
40 41
#include "opusenc.h"
#include "opus_header.h"
Jean-Marc Valin's avatar
Jean-Marc Valin committed
42
#include "speex_resampler.h"
43
#include "picture.h"
44
#include "ogg_packer.h"
Jean-Marc Valin's avatar
Jean-Marc Valin committed
45
#include "unicode_support.h"
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
46

Jean-Marc Valin's avatar
Jean-Marc Valin committed
47 48 49
/* Bump this when we change the ABI. */
#define OPE_ABI_VERSION 0

Jean-Marc Valin's avatar
cleanup  
Jean-Marc Valin committed
50
#define LPC_PADDING 120
51 52
#define LPC_ORDER 24
#define LPC_INPUT 480
53 54
/* Make the following constant always equal to 2*cos(M_PI/LPC_PADDING) */
#define LPC_GOERTZEL_CONST 1.99931465f
Jean-Marc Valin's avatar
cleanup  
Jean-Marc Valin committed
55

Jean-Marc Valin's avatar
Jean-Marc Valin committed
56 57 58 59 60 61
/* Allow up to 2 seconds for delayed decision. */
#define MAX_LOOKAHEAD 96000
/* We can't have a circular buffer (because of delayed decision), so let's not copy too often. */
#define BUFFER_EXTRA 24000

#define BUFFER_SAMPLES (MAX_LOOKAHEAD + BUFFER_EXTRA)
Jean-Marc Valin's avatar
Jean-Marc Valin committed
62

Jean-Marc Valin's avatar
Jean-Marc Valin committed
63 64 65
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))

Jean-Marc Valin's avatar
Jean-Marc Valin committed
66
#ifdef _MSC_VER
67 68 69
# if (_MSC_VER < 1900)
#  define snprintf _snprintf
# endif
Jean-Marc Valin's avatar
Jean-Marc Valin committed
70 71
#endif

Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
72 73 74 75
struct StdioObject {
  FILE *file;
};

Jean-Marc Valin's avatar
Jean-Marc Valin committed
76 77 78 79 80 81 82 83 84 85 86 87 88 89
struct OggOpusComments {
  char *comment;
  int comment_length;
  int seen_file_icons;
};

/* Create a new comments object. The vendor string is optional. */
OggOpusComments *ope_comments_create() {
  OggOpusComments *c;
  const char *libopus_str;
  char vendor_str[1024];
  c = malloc(sizeof(*c));
  if (c == NULL) return NULL;
  libopus_str = opus_get_version_string();
Jean-Marc Valin's avatar
Jean-Marc Valin committed
90
  snprintf(vendor_str, sizeof(vendor_str), "%s, %s %s", libopus_str, PACKAGE_NAME, PACKAGE_VERSION);
91
  _ope_comment_init(&c->comment, &c->comment_length, vendor_str);
92
  c->seen_file_icons = 0;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
  if (c->comment == NULL) {
    free(c);
    return NULL;
  } else {
    return c;
  }
}

/* Create a deep copy of a comments object. */
OggOpusComments *ope_comments_copy(OggOpusComments *comments) {
  OggOpusComments *c;
  c = malloc(sizeof(*c));
  if (c == NULL) return NULL;
  memcpy(c, comments, sizeof(*c));
  c->comment = malloc(comments->comment_length);
  if (c->comment == NULL) {
    free(c);
    return NULL;
  } else {
    memcpy(c->comment, comments->comment, comments->comment_length);
    return c;
  }
}

/* Destroys a comments object. */
void ope_comments_destroy(OggOpusComments *comments){
  free(comments->comment);
  free(comments);
}

/* Add a comment. */
int ope_comments_add(OggOpusComments *comments, const char *tag, const char *val) {
125 126
  if (tag == NULL || val == NULL) return OPE_BAD_ARG;
  if (strchr(tag, '=')) return OPE_BAD_ARG;
127
  if (_ope_comment_add(&comments->comment, &comments->comment_length, tag, val)) return OPE_ALLOC_FAIL;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
128 129 130
  return OPE_OK;
}

131 132 133
/* Add a comment. */
int ope_comments_add_string(OggOpusComments *comments, const char *tag_and_val) {
  if (!strchr(tag_and_val, '=')) return OPE_BAD_ARG;
134
  if (_ope_comment_add(&comments->comment, &comments->comment_length, NULL, tag_and_val)) return OPE_ALLOC_FAIL;
135 136 137
  return OPE_OK;
}

Jean-Marc Valin's avatar
Jean-Marc Valin committed
138
int ope_comments_add_picture(OggOpusComments *comments, const char *filename, int picture_type, const char *description) {
Jean-Marc Valin's avatar
Jean-Marc Valin committed
139
  char *picture_data;
140
  int err;
141
  picture_data = _ope_parse_picture_specification(filename, picture_type, description, &err, &comments->seen_file_icons);
142 143
  if (picture_data == NULL || err != OPE_OK){
    return err;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
144
  }
145
  _ope_comment_add(&comments->comment, &comments->comment_length, "METADATA_BLOCK_PICTURE", picture_data);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
146 147 148 149 150
  free(picture_data);
  return OPE_OK;
}


151 152 153 154 155 156 157 158 159 160 161
typedef struct EncStream EncStream;

struct EncStream {
  void *user_data;
  int serialno_is_set;
  int serialno;
  int stream_is_init;
  int packetno;
  char *comment;
  int comment_length;
  int seen_file_icons;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
162
  int close_at_end;
163
  int header_is_frozen;
164 165
  opus_int64 end_granule;
  opus_int64 granule_offset;
166 167 168
  EncStream *next;
};

Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
169
struct OggOpusEnc {
170
  OpusMSEncoder *st;
171
  oggpacker *oggp;
172
  int unrecoverable;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
173
  int pull_api;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
174
  int rate;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
175
  int channels;
176
  float *buffer;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
177 178
  int buffer_start;
  int buffer_end;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
179
  SpeexResamplerState *re;
Jean-Marc Valin's avatar
oops  
Jean-Marc Valin committed
180 181
  int frame_size;
  int decision_delay;
182
  int max_ogg_delay;
183
  int global_granule_offset;
184 185 186
  opus_int64 curr_granule;
  opus_int64 write_granule;
  opus_int64 last_page_granule;
187
  int draining;
188
  float *lpc_buffer;
189 190
  unsigned char *chaining_keyframe;
  int chaining_keyframe_length;
191
  OpusEncCallbacks callbacks;
192
  ope_packet_func packet_callback;
193
  void *packet_callback_data;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
194
  OpusHeader header;
195
  int comment_padding;
196
  EncStream *streams;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
197
  EncStream *last_stream;
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
198
};
199

Jean-Marc Valin's avatar
Jean-Marc Valin committed
200 201 202 203 204 205 206
static void output_pages(OggOpusEnc *enc) {
  unsigned char *page;
  int len;
  while (oggp_get_next_page(enc->oggp, &page, &len)) {
    enc->callbacks.write(enc->streams->user_data, page, len);
  }
}
Jean-Marc Valin's avatar
Jean-Marc Valin committed
207 208
static void oe_flush_page(OggOpusEnc *enc) {
  oggp_flush_page(enc->oggp);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
209
  if (!enc->pull_api) output_pages(enc);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
210 211
}

Jean-Marc Valin's avatar
cleanup  
Jean-Marc Valin committed
212
static int stdio_write(void *user_data, const unsigned char *ptr, opus_int32 len) {
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
213
  struct StdioObject *obj = (struct StdioObject*)user_data;
214
  return fwrite(ptr, 1, len, obj->file) != (size_t)len;
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
215 216
}

Jean-Marc Valin's avatar
cleanup  
Jean-Marc Valin committed
217
static int stdio_close(void *user_data) {
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
218
  struct StdioObject *obj = (struct StdioObject*)user_data;
219 220
  int ret = 0;
  if (obj->file) ret = fclose(obj->file);
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
221 222
  free(obj);
  return ret;
223 224 225 226
}

static const OpusEncCallbacks stdio_callbacks = {
  stdio_write,
227
  stdio_close
228 229
};

230
/* Create a new OggOpus file. */
231
OggOpusEnc *ope_encoder_create_file(const char *path, OggOpusComments *comments, opus_int32 rate, int channels, int family, int *error) {
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
232 233 234
  OggOpusEnc *enc;
  struct StdioObject *obj;
  obj = malloc(sizeof(*obj));
Mark Harris's avatar
Mark Harris committed
235 236 237 238
  if (obj == NULL) {
    if (error) *error = OPE_ALLOC_FAIL;
    return NULL;
  }
239
  enc = ope_encoder_create_callbacks(&stdio_callbacks, obj, comments, rate, channels, family, error);
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
240
  if (enc == NULL || (error && *error)) {
Mark Harris's avatar
Mark Harris committed
241
    free(obj);
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
242 243
    return NULL;
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
244
  obj->file = _ope_fopen(path, "wb");
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
245
  if (!obj->file) {
Jean-Marc Valin's avatar
Jean-Marc Valin committed
246
    if (error) *error = OPE_CANNOT_OPEN;
247
    ope_encoder_destroy(enc);
248 249
    return NULL;
  }
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
250
  return enc;
251 252
}

253
EncStream *stream_create(OggOpusComments *comments) {
Jean-Marc Valin's avatar
Jean-Marc Valin committed
254 255 256 257 258 259 260
  EncStream *stream;
  stream = malloc(sizeof(*stream));
  if (!stream) return NULL;
  stream->next = NULL;
  stream->close_at_end = 1;
  stream->serialno_is_set = 0;
  stream->stream_is_init = 0;
261
  stream->header_is_frozen = 0;
262
  stream->granule_offset = 0;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
263 264 265 266 267
  stream->comment = malloc(comments->comment_length);
  if (stream->comment == NULL) goto fail;
  memcpy(stream->comment, comments->comment, comments->comment_length);
  stream->comment_length = comments->comment_length;
  stream->seen_file_icons = comments->seen_file_icons;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
268 269 270 271 272 273 274 275 276 277 278 279
  return stream;
fail:
  if (stream->comment) free(stream->comment);
  free(stream);
  return NULL;
}

static void stream_destroy(EncStream *stream) {
  if (stream->comment) free(stream->comment);
  free(stream);
}

280
/* Create a new OggOpus file (callback-based). */
281
OggOpusEnc *ope_encoder_create_callbacks(const OpusEncCallbacks *callbacks, void *user_data,
282
    OggOpusComments *comments, opus_int32 rate, int channels, int family, int *error) {
283 284 285
  OpusMSEncoder *st=NULL;
  OggOpusEnc *enc=NULL;
  int ret;
286
  if (family != 0 && family != 1 && family != 255 && family != -1) {
Jean-Marc Valin's avatar
Jean-Marc Valin committed
287
    if (error) *error = OPE_UNIMPLEMENTED;
288 289
    return NULL;
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
290 291 292 293
  if (channels <= 0 || channels > 255) {
    if (error) *error = OPE_BAD_ARG;
    return NULL;
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
294 295
  if (rate <= 0) {
    if (error) *error = OPE_BAD_ARG;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
296 297
    return NULL;
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
298

Jean-Marc Valin's avatar
Jean-Marc Valin committed
299
  if ( (enc = malloc(sizeof(*enc))) == NULL) goto fail;
Mark Harris's avatar
Mark Harris committed
300 301
  enc->buffer = NULL;
  enc->lpc_buffer = NULL;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
302
  if ( (enc->streams = stream_create(comments)) == NULL) goto fail;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
303
  enc->last_stream = enc->streams;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
304
  enc->oggp = NULL;
305 306
  /* Not initializing anything is an unrecoverable error. */
  enc->unrecoverable = family == -1;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
307
  enc->pull_api = 0;
308
  enc->packet_callback = NULL;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
309
  enc->rate = rate;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
310
  enc->channels = channels;
Jean-Marc Valin's avatar
oops  
Jean-Marc Valin committed
311 312
  enc->frame_size = 960;
  enc->decision_delay = 96000;
313
  enc->max_ogg_delay = 48000;
314 315
  enc->chaining_keyframe = NULL;
  enc->chaining_keyframe_length = -1;
316
  enc->comment_padding = 512;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
317 318 319 320
  enc->header.channels=channels;
  enc->header.channel_mapping=family;
  enc->header.input_sample_rate=rate;
  enc->header.gain=0;
321 322 323 324 325 326 327 328 329
  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) ) {
      goto fail;
    }
    enc->st = st;
    opus_multistream_encoder_ctl(st, OPUS_SET_EXPERT_FRAME_DURATION(OPUS_FRAMESIZE_20_MS));
330
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
331
  if (rate != 48000) {
Jean-Marc Valin's avatar
Jean-Marc Valin committed
332 333
    enc->re = speex_resampler_init(channels, rate, 48000, 5, NULL);
    if (enc->re == NULL) goto fail;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
334
    speex_resampler_skip_zeros(enc->re);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
335 336 337
  } else {
    enc->re = NULL;
  }
338
  enc->global_granule_offset = -1;
339
  enc->curr_granule = 0;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
340
  enc->write_granule = 0;
341
  enc->last_page_granule = 0;
342
  enc->draining = 0;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
343
  if ( (enc->buffer = malloc(sizeof(*enc->buffer)*BUFFER_SAMPLES*channels)) == NULL) goto fail;
344 345 346 347 348
  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);
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
349
  enc->buffer_start = enc->buffer_end = 0;
miv's avatar
miv committed
350 351 352 353
  if (callbacks != NULL)
  {
    enc->callbacks = *callbacks;
  }
354
  enc->streams->user_data = user_data;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
355
  if (error) *error = OPE_OK;
356 357 358
  return enc;
fail:
  if (enc) {
Jean-Marc Valin's avatar
Jean-Marc Valin committed
359
    if (enc->buffer) free(enc->buffer);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
360
    if (enc->streams) stream_destroy(enc->streams);
361
    if (enc->lpc_buffer) free(enc->lpc_buffer);
Mark Harris's avatar
Mark Harris committed
362
    free(enc);
363 364 365 366
  }
  if (st) {
    opus_multistream_encoder_destroy(st);
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
367
  if (error) *error = OPE_ALLOC_FAIL;
368 369 370
  return NULL;
}

Jean-Marc Valin's avatar
Jean-Marc Valin committed
371
/* Create a new OggOpus stream, pulling one page at a time. */
Jean-Marc Valin's avatar
cleanup  
Jean-Marc Valin committed
372
OggOpusEnc *ope_encoder_create_pull(OggOpusComments *comments, opus_int32 rate, int channels, int family, int *error) {
373
  OggOpusEnc *enc = ope_encoder_create_callbacks(NULL, NULL, comments, rate, channels, family, error);
374
  if (enc) enc->pull_api = 1;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
375 376 377
  return enc;
}

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
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 (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) ) {
    goto fail;
  }
  enc->st = st;
  opus_multistream_encoder_ctl(st, OPUS_SET_EXPERT_FRAME_DURATION(OPUS_FRAMESIZE_20_MS));
  enc->unrecoverable = 0;
  enc->header.channel_mapping=family;
  enc->header.nb_streams = streams;
  enc->header.nb_coupled = coupled_streams;
  for (i=0;i<streams+coupled_streams;i++)
    enc->header.stream_map[i] = mapping[i];
  return OPE_OK;
fail:
  return OPE_ALLOC_FAIL;
}

Jean-Marc Valin's avatar
Jean-Marc Valin committed
404
static void init_stream(OggOpusEnc *enc) {
405 406 407
  assert(!enc->streams->stream_is_init);
  if (!enc->streams->serialno_is_set) {
    enc->streams->serialno = rand();
408
  }
409

Jean-Marc Valin's avatar
Jean-Marc Valin committed
410
  if (enc->oggp != NULL) oggp_chain(enc->oggp, enc->streams->serialno);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
411 412
  else {
    enc->oggp = oggp_create(enc->streams->serialno);
413 414 415 416
    if (enc->oggp == NULL) {
      enc->unrecoverable = 1;
      return;
    }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
417 418
    oggp_set_muxing_delay(enc->oggp, enc->max_ogg_delay);
  }
419
  _ope_comment_pad(&enc->streams->comment, &enc->streams->comment_length, enc->comment_padding);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
420

421 422 423 424 425 426 427 428 429
  /* 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;
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
430 431
  /*Write header*/
  {
432
    int packet_size;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
433 434
    unsigned char *p;
    p = oggp_get_packet_buffer(enc->oggp, 276);
435
    packet_size = _ope_opus_header_to_packet(&enc->header, p, 276);
436
    if (enc->packet_callback) enc->packet_callback(enc->packet_callback_data, p, packet_size, 0);
437
    oggp_commit_packet(enc->oggp, packet_size, 0, 0);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
438 439 440
    oe_flush_page(enc);
    p = oggp_get_packet_buffer(enc->oggp, enc->streams->comment_length);
    memcpy(p, enc->streams->comment, enc->streams->comment_length);
441
    if (enc->packet_callback) enc->packet_callback(enc->packet_callback_data, p, enc->streams->comment_length, 0);
442
    oggp_commit_packet(enc->oggp, enc->streams->comment_length, 0, 0);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
443
    oe_flush_page(enc);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
444
  }
445 446
  enc->streams->stream_is_init = 1;
  enc->streams->packetno = 2;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
447 448
}

Jean-Marc Valin's avatar
Jean-Marc Valin committed
449
static void shift_buffer(OggOpusEnc *enc) {
Jean-Marc Valin's avatar
Jean-Marc Valin committed
450 451
  /* Leaving enough in the buffer to do LPC extension if needed. */
  if (enc->buffer_start > LPC_INPUT) {
452 453
    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));
Jean-Marc Valin's avatar
Jean-Marc Valin committed
454 455 456
    enc->buffer_end -= enc->buffer_start-LPC_INPUT;
    enc->buffer_start = LPC_INPUT;
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
457 458
}

459 460 461 462 463
static int compute_frame_samples(int size_request) {
  if (size_request <= OPUS_FRAMESIZE_40_MS) return 120<<(size_request-OPUS_FRAMESIZE_2_5_MS);
  else return (size_request-OPUS_FRAMESIZE_2_5_MS-2)*960;
}

Jean-Marc Valin's avatar
Jean-Marc Valin committed
464
static void encode_buffer(OggOpusEnc *enc) {
465
  opus_int32 max_packet_size;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
466
  /* Round up when converting the granule pos because the decoder will round down. */
467
  opus_int64 end_granule48k = (enc->streams->end_granule*48000 + enc->rate - 1)/enc->rate + enc->global_granule_offset;
468
  max_packet_size = (1277*6+2)*enc->header.nb_streams;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
469
  while (enc->buffer_end-enc->buffer_start > enc->frame_size + enc->decision_delay) {
470
    int cont;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
471
    int e_o_s;
472
    opus_int32 pred;
Jean-Marc Valin's avatar
oops  
Jean-Marc Valin committed
473
    int nbBytes;
474 475
    unsigned char *packet;
    unsigned char *packet_copy = NULL;
476
    int is_keyframe=0;
477
    if (enc->unrecoverable) return;
478
    opus_multistream_encoder_ctl(enc->st, OPUS_GET_PREDICTION_DISABLED(&pred));
479 480 481
    /* 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) {
482
      opus_multistream_encoder_ctl(enc->st, OPUS_SET_PREDICTION_DISABLED(1));
483
      is_keyframe = 1;
484
    }
485 486 487 488 489 490 491 492 493
    /* Handle the last packet by making sure not to encode too much padding. */
    if (enc->curr_granule+enc->frame_size >= end_granule48k && enc->draining) {
      int min_samples;
      int frame_size_request = OPUS_FRAMESIZE_2_5_MS;
      /* Minimum frame size required for the current frame to still meet the e_o_s condition. */
      min_samples = end_granule48k - enc->curr_granule;
      while (compute_frame_samples(frame_size_request) < min_samples) frame_size_request++;
      ope_encoder_ctl(enc, OPUS_SET_EXPERT_FRAME_DURATION(frame_size_request));
    }
494
    packet = oggp_get_packet_buffer(enc->oggp, max_packet_size);
Jean-Marc Valin's avatar
oops  
Jean-Marc Valin committed
495
    nbBytes = opus_multistream_encode_float(enc->st, &enc->buffer[enc->channels*enc->buffer_start],
496
        enc->buffer_end-enc->buffer_start, packet, max_packet_size);
497 498 499 500 501
    if (nbBytes < 0) {
      /* Anything better we can do here? */
      enc->unrecoverable = 1;
      return;
    }
502
    opus_multistream_encoder_ctl(enc->st, OPUS_SET_PREDICTION_DISABLED(pred));
503
    assert(nbBytes > 0);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
504
    enc->curr_granule += enc->frame_size;
505
    do {
Jean-Marc Valin's avatar
Jean-Marc Valin committed
506 507 508
      opus_int64 granulepos;
      granulepos=enc->curr_granule-enc->streams->granule_offset;
      e_o_s=enc->curr_granule >= end_granule48k;
509
      cont = 0;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
510
      if (e_o_s) granulepos=end_granule48k-enc->streams->granule_offset;
511 512 513 514
      if (packet_copy != NULL) {
        packet = oggp_get_packet_buffer(enc->oggp, max_packet_size);
        memcpy(packet, packet_copy, nbBytes);
      }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
515
      if (enc->packet_callback) enc->packet_callback(enc->packet_callback_data, packet, nbBytes, 0);
516
      if ((e_o_s || is_keyframe) && packet_copy == NULL) {
517
        packet_copy = malloc(nbBytes);
518 519 520 521 522
        if (packet_copy == NULL) {
          /* Can't recover from allocation failing here. */
          enc->unrecoverable = 1;
          return;
        }
523 524
        memcpy(packet_copy, packet, nbBytes);
      }
525
      oggp_commit_packet(enc->oggp, nbBytes, granulepos, e_o_s);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
526
      if (e_o_s) oe_flush_page(enc);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
527
      else if (!enc->pull_api) output_pages(enc);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
528
      if (e_o_s) {
529 530
        EncStream *tmp;
        tmp = enc->streams->next;
miv's avatar
miv committed
531
        if (enc->streams->close_at_end && !enc->pull_api) enc->callbacks.close(enc->streams->user_data);
532 533 534
        stream_destroy(enc->streams);
        enc->streams = tmp;
        if (!tmp) enc->last_stream = NULL;
535 536 537 538
        if (enc->last_stream == NULL) {
          if (packet_copy) free(packet_copy);
          return;
        }
539
        /* We're done with this stream, start the next one. */
540
        enc->header.preskip = end_granule48k + enc->frame_size - enc->curr_granule;
541
        enc->streams->granule_offset = enc->curr_granule - enc->frame_size;
542 543 544 545
        if (enc->chaining_keyframe) {
          enc->header.preskip += enc->frame_size;
          enc->streams->granule_offset -= enc->frame_size;
        }
546
        init_stream(enc);
547
        if (enc->chaining_keyframe) {
548
          unsigned char *p;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
549
          opus_int64 granulepos2=enc->curr_granule - enc->streams->granule_offset - enc->frame_size;
550
          p = oggp_get_packet_buffer(enc->oggp, enc->chaining_keyframe_length);
Jean-Marc Valin's avatar
cleanup  
Jean-Marc Valin committed
551
          memcpy(p, enc->chaining_keyframe, enc->chaining_keyframe_length);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
552
          if (enc->packet_callback) enc->packet_callback(enc->packet_callback_data, enc->chaining_keyframe, enc->chaining_keyframe_length, 0);
553
          oggp_commit_packet(enc->oggp, enc->chaining_keyframe_length, granulepos2, 0);
554
        }
555
        end_granule48k = (enc->streams->end_granule*48000 + enc->rate - 1)/enc->rate + enc->global_granule_offset;
556
        cont = 1;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
557
      }
558
    } while (cont);
559 560 561
    if (enc->chaining_keyframe) free(enc->chaining_keyframe);
    if (is_keyframe) {
      enc->chaining_keyframe_length = nbBytes;
562 563
      enc->chaining_keyframe = packet_copy;
      packet_copy = NULL;
564 565 566 567
    } else {
      enc->chaining_keyframe = NULL;
      enc->chaining_keyframe_length = -1;
    }
568
    if (packet_copy) free(packet_copy);
Jean-Marc Valin's avatar
oops  
Jean-Marc Valin committed
569
    enc->buffer_start += enc->frame_size;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
570
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
571 572
  /* If we've reached the end of the buffer, move everything back to the front. */
  if (enc->buffer_end == BUFFER_SAMPLES) {
Jean-Marc Valin's avatar
Jean-Marc Valin committed
573
    shift_buffer(enc);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
574
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
575 576
  /* This function must never leave the buffer full. */
  assert(enc->buffer_end < BUFFER_SAMPLES);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
577 578
}

579
/* Add/encode any number of float samples to the file. */
580
int ope_encoder_write_float(OggOpusEnc *enc, const float *pcm, int samples_per_channel) {
Jean-Marc Valin's avatar
Jean-Marc Valin committed
581
  int channels = enc->channels;
582
  if (enc->unrecoverable) return OPE_UNRECOVERABLE;
583
  enc->last_stream->header_is_frozen = 1;
584
  if (!enc->streams->stream_is_init) init_stream(enc);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
585
  if (samples_per_channel < 0) return OPE_BAD_ARG;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
586 587
  enc->write_granule += samples_per_channel;
  enc->last_stream->end_granule = enc->write_granule;
588 589 590 591 592 593 594 595 596
  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];
    }
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
597 598
  do {
    int i;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
599 600 601 602 603 604 605 606 607
    spx_uint32_t in_samples, out_samples;
    out_samples = BUFFER_SAMPLES-enc->buffer_end;
    if (enc->re != NULL) {
      in_samples = samples_per_channel;
      speex_resampler_process_interleaved_float(enc->re, pcm, &in_samples, &enc->buffer[channels*enc->buffer_end], &out_samples);
    } else {
      int curr;
      curr = MIN((spx_uint32_t)samples_per_channel, out_samples);
      for (i=0;i<channels*curr;i++) {
Jean-Marc Valin's avatar
Jean-Marc Valin committed
608
      enc->buffer[channels*enc->buffer_end+i] = pcm[i];
Jean-Marc Valin's avatar
Jean-Marc Valin committed
609 610
      }
      in_samples = out_samples = curr;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
611
    }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
612 613 614
    enc->buffer_end += out_samples;
    pcm += in_samples*channels;
    samples_per_channel -= in_samples;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
615
    encode_buffer(enc);
616
    if (enc->unrecoverable) return OPE_UNRECOVERABLE;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
617
  } while (samples_per_channel > 0);
618
  return OPE_OK;
619 620
}

621
#define CONVERT_BUFFER 4096
Jean-Marc Valin's avatar
Jean-Marc Valin committed
622

623
/* Add/encode any number of int16 samples to the file. */
624
int ope_encoder_write(OggOpusEnc *enc, const opus_int16 *pcm, int samples_per_channel) {
Jean-Marc Valin's avatar
Jean-Marc Valin committed
625
  int channels = enc->channels;
626
  if (enc->unrecoverable) return OPE_UNRECOVERABLE;
627
  enc->last_stream->header_is_frozen = 1;
628
  if (!enc->streams->stream_is_init) init_stream(enc);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
629
  if (samples_per_channel < 0) return OPE_BAD_ARG;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
630 631
  enc->write_granule += samples_per_channel;
  enc->last_stream->end_granule = enc->write_granule;
632 633 634 635 636 637 638 639 640
  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];
    }
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
641 642
  do {
    int i;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
643 644 645
    spx_uint32_t in_samples, out_samples;
    out_samples = BUFFER_SAMPLES-enc->buffer_end;
    if (enc->re != NULL) {
646 647
      float buf[CONVERT_BUFFER];
      in_samples = MIN(CONVERT_BUFFER/channels, samples_per_channel);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
648 649 650 651 652 653 654 655 656 657 658
      for (i=0;i<channels*(int)in_samples;i++) {
        buf[i] = (1.f/32768)*pcm[i];
      }
      speex_resampler_process_interleaved_float(enc->re, buf, &in_samples, &enc->buffer[channels*enc->buffer_end], &out_samples);
    } else {
      int curr;
      curr = MIN((spx_uint32_t)samples_per_channel, out_samples);
      for (i=0;i<channels*curr;i++) {
        enc->buffer[channels*enc->buffer_end+i] = (1.f/32768)*pcm[i];
      }
      in_samples = out_samples = curr;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
659
    }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
660 661 662
    enc->buffer_end += out_samples;
    pcm += in_samples*channels;
    samples_per_channel -= in_samples;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
663
    encode_buffer(enc);
664
    if (enc->unrecoverable) return OPE_UNRECOVERABLE;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
665
  } while (samples_per_channel > 0);
666
  return OPE_OK;
667 668
}

Jean-Marc Valin's avatar
Jean-Marc Valin committed
669
/* Get the next page from the stream. Returns 1 if there is a page available, 0 if not. */
Jean-Marc Valin's avatar
cleanup  
Jean-Marc Valin committed
670
int ope_encoder_get_page(OggOpusEnc *enc, unsigned char **page, opus_int32 *len, int flush) {
671
  if (enc->unrecoverable) return OPE_UNRECOVERABLE;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
672 673 674 675 676 677 678
  if (!enc->pull_api) return 0;
  else {
    if (flush) oggp_flush_page(enc->oggp);
    return oggp_get_next_page(enc->oggp, page, len);
  }
}

679 680
static void extend_signal(float *x, int before, int after, int channels);

681
int ope_encoder_drain(OggOpusEnc *enc) {
682
  int pad_samples;
683
  int resampler_drain = 0;
684
  if (enc->unrecoverable) return OPE_UNRECOVERABLE;
685 686
  /* Check if it's already been drained. */
  if (enc->streams == NULL) return OPE_TOO_LATE;
687
  if (enc->re) resampler_drain = speex_resampler_get_output_latency(enc->re);
688
  pad_samples = MAX(LPC_PADDING, enc->global_granule_offset + enc->frame_size + resampler_drain + 1);
689
  if (!enc->streams->stream_is_init) init_stream(enc);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
690
  shift_buffer(enc);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
691
  assert(enc->buffer_end + pad_samples <= BUFFER_SAMPLES);
Jean-Marc Valin's avatar
oops  
Jean-Marc Valin committed
692
  memset(&enc->buffer[enc->channels*enc->buffer_end], 0, pad_samples*enc->channels*sizeof(enc->buffer[0]));
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708
  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;
  }
Jean-Marc Valin's avatar
Jean-Marc Valin committed
709
  enc->decision_delay = 0;
710
  enc->draining = 1;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
711 712
  assert(enc->buffer_end <= BUFFER_SAMPLES);
  encode_buffer(enc);
713 714
  if (enc->unrecoverable) return OPE_UNRECOVERABLE;
  /* Draining should have called all the streams to complete. */
Jean-Marc Valin's avatar
Jean-Marc Valin committed
715
  assert(enc->streams == NULL);
716
  return OPE_OK;
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
717 718
}

719
void ope_encoder_destroy(OggOpusEnc *enc) {
720 721 722 723 724 725 726 727
  EncStream *stream;
  stream = enc->streams;
  while (stream != NULL) {
    EncStream *tmp = stream;
    stream = stream->next;
    if (tmp->close_at_end) enc->callbacks.close(tmp->user_data);
    stream_destroy(tmp);
  }
728
  if (enc->chaining_keyframe) free(enc->chaining_keyframe);
729
  free(enc->buffer);
730
  if (enc->oggp) oggp_destroy(enc->oggp);
731
  if (enc->st) opus_multistream_encoder_destroy(enc->st);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
732
  if (enc->re) speex_resampler_destroy(enc->re);
733
  if (enc->lpc_buffer) free(enc->lpc_buffer);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
734
  free(enc);
735 736 737
}

/* Ends the stream and create a new stream within the same file. */
738
int ope_encoder_chain_current(OggOpusEnc *enc, OggOpusComments *comments) {
Jean-Marc Valin's avatar
Jean-Marc Valin committed
739
  enc->last_stream->close_at_end = 0;
740
  return ope_encoder_continue_new_callbacks(enc, enc->last_stream->user_data, comments);
741 742 743
}

/* Ends the stream and create a new file. */
744
int ope_encoder_continue_new_file(OggOpusEnc *enc, const char *path, OggOpusComments *comments) {
745 746
  int ret;
  struct StdioObject *obj;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
747
  if (!(obj = malloc(sizeof(*obj)))) return OPE_ALLOC_FAIL;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
748
  obj->file = _ope_fopen(path, "wb");
749 750 751 752 753
  if (!obj->file) {
    free(obj);
    /* By trying to open the file first, we can recover if we can't open it. */
    return OPE_CANNOT_OPEN;
  }
754
  ret = ope_encoder_continue_new_callbacks(enc, obj, comments);
755 756 757
  if (ret == OPE_OK) return ret;
  fclose(obj->file);
  free(obj);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
758
  return ret;
759 760 761
}

/* Ends the stream and create a new file (callback-based). */
762
int ope_encoder_continue_new_callbacks(OggOpusEnc *enc, void *user_data, OggOpusComments *comments) {
763
  EncStream *new_stream;
764
  if (enc->unrecoverable) return OPE_UNRECOVERABLE;
765 766
  assert(enc->streams);
  assert(enc->last_stream);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
767
  new_stream = stream_create(comments);
Jean-Marc Valin's avatar
Jean-Marc Valin committed
768
  if (!new_stream) return OPE_ALLOC_FAIL;
769
  new_stream->user_data = user_data;
770
  new_stream->end_granule = enc->write_granule;
771 772
  enc->last_stream->next = new_stream;
  enc->last_stream = new_stream;
773
  return OPE_OK;
774 775
}

776
int ope_encoder_flush_header(OggOpusEnc *enc) {
777
  if (enc->unrecoverable) return OPE_UNRECOVERABLE;
778 779
  if (enc->last_stream->header_is_frozen) return OPE_TOO_LATE;
  if (enc->last_stream->stream_is_init) return OPE_TOO_LATE;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
780
  else init_stream(enc);
Jean-Marc Valin's avatar
wip  
Jean-Marc Valin committed
781
  return OPE_OK;
Jean-Marc Valin's avatar
Jean-Marc Valin committed
782 783
}

784 785
/* Goes straight to the libopus ctl() functions. */
int ope_encoder_ctl(OggOpusEnc *enc, int request, ...) {
786
  int ret;
787
  int translate;
788
  va_list ap;
789
  if (enc->unrecoverable) return OPE_UNRECOVERABLE;
790
  va_start(ap, request);
791
  ret = OPE_OK;
792
  switch (request) {
793
    case OPUS_SET_APPLICATION_REQUEST:
794
    case OPUS_SET_BITRATE_REQUEST:
795
    case OPUS_SET_MAX_BANDWIDTH_REQUEST:
796
    case OPUS_SET_VBR_REQUEST:
797
    case OPUS_SET_BANDWIDTH_REQUEST:
798
    case OPUS_SET_COMPLEXITY_REQUEST:
799
    case OPUS_SET_INBAND_FEC_REQUEST:
800
    case OPUS_SET_PACKET_LOSS_PERC_REQUEST:
801 802 803 804
    case OPUS_SET_DTX_REQUEST:
    case OPUS_SET_VBR_CONSTRAINT_REQUEST:
    case OPUS_SET_FORCE_CHANNELS_REQUEST:
    case OPUS_SET_SIGNAL_REQUEST:
805
    case OPUS_SET_LSB_DEPTH_REQUEST: