per-stream end granule
[libopusenc.git] / src / opusenc.c
1 /* Copyright (C)2002-2017 Jean-Marc Valin
2    Copyright (C)2007-2013 Xiph.Org Foundation
3    Copyright (C)2008-2013 Gregory Maxwell
4    File: opusenc.c
5
6    Redistribution and use in source and binary forms, with or without
7    modification, are permitted provided that the following conditions
8    are met:
9
10    - Redistributions of source code must retain the above copyright
11    notice, this list of conditions and the following disclaimer.
12
13    - Redistributions in binary form must reproduce the above copyright
14    notice, this list of conditions and the following disclaimer in the
15    documentation and/or other materials provided with the distribution.
16
17    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21    OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33
34 #include <stdarg.h>
35 #include <time.h>
36 #include <unistd.h>
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <assert.h>
41 #include <opus_multistream.h>
42 #include "opusenc.h"
43 #include "opus_header.h"
44 #include "speex_resampler.h"
45 #include "picture.h"
46
47 #define MAX_CHANNELS 8
48
49 #define LPC_PADDING 120
50
51 /* Allow up to 2 seconds for delayed decision. */
52 #define MAX_LOOKAHEAD 96000
53 /* We can't have a circular buffer (because of delayed decision), so let's not copy too often. */
54 #define BUFFER_EXTRA 24000
55
56 #define BUFFER_SAMPLES (MAX_LOOKAHEAD + BUFFER_EXTRA)
57
58 #define MIN(a,b) ((a) < (b) ? (a) : (b))
59 #define MAX(a,b) ((a) > (b) ? (a) : (b))
60
61 #define MAX_PACKET_SIZE (1276*8)
62
63 static int oe_write_page(ogg_page *page, OpusEncCallbacks *cb, void *user_data)
64 {
65    int err;
66    err = cb->write(user_data, page->header, page->header_len);
67    if (err) return -1;
68    err = cb->write(user_data, page->body, page->body_len);
69    if (err) return -1;
70    return page->header_len+page->body_len;
71 }
72
73 struct StdioObject {
74   FILE *file;
75 };
76
77 typedef struct EncStream EncStream;
78
79 struct EncStream {
80   void *user_data;
81   ogg_stream_state os;
82   int serialno_is_set;
83   int serialno;
84   int stream_is_init;
85   int packetno;
86   char *comment;
87   int comment_length;
88   int seen_file_icons;
89   int close_at_end;
90   ogg_int64_t end_granule;
91   EncStream *next;
92 };
93
94 struct OggOpusEnc {
95   OpusMSEncoder *st;
96   int rate;
97   int channels;
98   float *buffer;
99   int buffer_start;
100   int buffer_end;
101   SpeexResamplerState *re;
102   int frame_size;
103   int decision_delay;
104   int max_ogg_delay;
105   ogg_int64_t curr_granule;
106   ogg_int64_t write_granule;
107   ogg_int64_t last_page_granule;
108   OpusEncCallbacks callbacks;
109   OpusHeader header;
110   int comment_padding;
111   EncStream *streams;
112   EncStream *last_stream;
113 };
114
115 static int oe_flush_page(OggOpusEnc *enc) {
116   ogg_page og;
117   int ret;
118   int written = 0;
119   while ( (ret = ogg_stream_flush(&enc->streams->os, &og)) ) {
120     if (!ret) break;
121     ret = oe_write_page(&og, &enc->callbacks, enc->streams->user_data);
122     if (ret == -1) {
123       return -1;
124     }
125     written += ret;
126   }
127   return written;
128 }
129
130 int stdio_write(void *user_data, const unsigned char *ptr, int len) {
131   struct StdioObject *obj = (struct StdioObject*)user_data;
132   return fwrite(ptr, 1, len, obj->file) != (size_t)len;
133 }
134
135 int stdio_close(void *user_data) {
136   struct StdioObject *obj = (struct StdioObject*)user_data;
137   int ret = fclose(obj->file);
138   free(obj);
139   return ret;
140 }
141
142 static const OpusEncCallbacks stdio_callbacks = {
143   stdio_write,
144   stdio_close
145 };
146
147 /* Create a new OggOpus file. */
148 OggOpusEnc *ope_create_file(const char *path, int rate, int channels, int family, int *error) {
149   OggOpusEnc *enc;
150   struct StdioObject *obj;
151   obj = malloc(sizeof(*obj));
152   enc = ope_create_callbacks(&stdio_callbacks, obj, rate, channels, family, error);
153   if (enc == NULL || (error && *error)) {
154     return NULL;
155   }
156   obj->file = fopen(path, "wb");
157   if (!obj->file) {
158     if (error) *error = OPE_CANNOT_OPEN;
159     /* FIXME: Destroy the encoder properly. */
160     free(obj);
161     return NULL;
162   }
163   return enc;
164 }
165
166 EncStream *stream_create() {
167   EncStream *stream;
168   stream = malloc(sizeof(*stream));
169   if (!stream) return NULL;
170   stream->next = NULL;
171   stream->close_at_end = 1;
172   stream->serialno_is_set = 0;
173   stream->seen_file_icons = 0;
174   stream->stream_is_init = 0;
175   stream->comment = NULL;
176   comment_init(&stream->comment, &stream->comment_length, opus_get_version_string());
177   if (!stream->comment) goto fail;
178   {
179     char encoder_string[1024];
180     snprintf(encoder_string, sizeof(encoder_string), "%s version %s", PACKAGE_NAME, PACKAGE_VERSION);
181     comment_add(&stream->comment, &stream->comment_length, "ENCODER", encoder_string);
182   }
183   return stream;
184 fail:
185   if (stream->comment) free(stream->comment);
186   free(stream);
187   return NULL;
188 }
189
190 static void stream_destroy(EncStream *stream) {
191   if (stream->comment) free(stream->comment);
192   if (stream->stream_is_init) ogg_stream_clear(&stream->os);
193   free(stream);
194 }
195
196 /* Create a new OggOpus file (callback-based). */
197 OggOpusEnc *ope_create_callbacks(const OpusEncCallbacks *callbacks, void *user_data,
198     int rate, int channels, int family, int *error) {
199   OpusMSEncoder *st=NULL;
200   OggOpusEnc *enc=NULL;
201   int ret;
202   if (family != 0 && family != 1 && family != 255) {
203     if (error) *error = OPE_UNIMPLEMENTED;
204     return NULL;
205   }
206   if (channels <= 0 || channels > 255) {
207     if (error) *error = OPE_BAD_ARG;
208     return NULL;
209   }
210   /* FIXME: Add resampling support. */
211   if (rate <= 0) {
212     if (error) *error = OPE_BAD_ARG;
213     return NULL;
214   }
215
216   if ( (enc = malloc(sizeof(*enc))) == NULL) goto fail;
217   enc->streams = NULL;
218   if ( (enc->streams = stream_create()) == NULL) goto fail;
219   enc->streams->next = NULL;
220   enc->last_stream = enc->streams;
221   enc->rate = rate;
222   enc->channels = channels;
223   enc->frame_size = 960;
224   enc->decision_delay = 96000;
225   enc->max_ogg_delay = 48000;
226   enc->comment_padding = 512;
227   enc->header.channels=channels;
228   enc->header.channel_mapping=family;
229   enc->header.input_sample_rate=rate;
230   enc->header.gain=0;
231   st=opus_multistream_surround_encoder_create(48000, channels, enc->header.channel_mapping,
232       &enc->header.nb_streams, &enc->header.nb_coupled,
233       enc->header.stream_map, OPUS_APPLICATION_AUDIO, &ret);
234   if (! (ret == OPUS_OK && st != NULL) ) {
235     goto fail;
236   }
237   if (rate != 48000) {
238     enc->re = speex_resampler_init(channels, rate, 48000, 5, NULL);
239     if (enc->re == NULL) goto fail;
240     speex_resampler_skip_zeros(enc->re);
241   } else {
242     enc->re = NULL;
243   }
244   opus_multistream_encoder_ctl(st, OPUS_SET_EXPERT_FRAME_DURATION(OPUS_FRAMESIZE_20_MS));
245   {
246     opus_int32 tmp;
247     int ret;
248     ret = opus_multistream_encoder_ctl(st, OPUS_GET_LOOKAHEAD(&tmp));
249     if (ret == OPUS_OK) enc->header.preskip = tmp;
250     else enc->header.preskip = 0;
251   }
252   enc->curr_granule = 0;
253   enc->write_granule = 0;
254   enc->last_page_granule = 0;
255   if ( (enc->buffer = malloc(sizeof(*enc->buffer)*BUFFER_SAMPLES*channels)) == NULL) goto fail;
256   enc->buffer_start = enc->buffer_end = 0;
257   enc->st = st;
258   enc->callbacks = *callbacks;
259   enc->streams->user_data = user_data;
260   if (error) *error = OPUS_OK;
261   return enc;
262 fail:
263   if (enc) {
264     free(enc);
265     if (enc->buffer) free(enc->buffer);
266     if (enc->streams) stream_destroy(enc->streams);
267   }
268   if (st) {
269     opus_multistream_encoder_destroy(st);
270   }
271   return NULL;
272 }
273
274 static void init_stream(OggOpusEnc *enc) {
275   time_t start_time;
276   assert(!enc->streams->stream_is_init);
277   if (!enc->streams->serialno_is_set) {
278     start_time = time(NULL);
279     srand(((getpid()&65535)<<15)^start_time);
280
281     enc->streams->serialno = rand();
282   }
283   
284   if (ogg_stream_init(&enc->streams->os, enc->streams->serialno) == -1) {
285     assert(0);
286     /* FIXME: How the hell do we handle that? */
287   }
288   comment_pad(&enc->streams->comment, &enc->streams->comment_length, enc->comment_padding);
289
290   /*Write header*/
291   {
292     ogg_packet op;
293     /*The Identification Header is 19 bytes, plus a Channel Mapping Table for
294       mapping families other than 0. The Channel Mapping Table is 2 bytes +
295       1 byte per channel. Because the maximum number of channels is 255, the
296       maximum size of this header is 19 + 2 + 255 = 276 bytes.*/
297     unsigned char header_data[276];
298     int packet_size = opus_header_to_packet(&enc->header, header_data, sizeof(header_data));
299     op.packet=header_data;
300     op.bytes=packet_size;
301     op.b_o_s=1;
302     op.e_o_s=0;
303     op.granulepos=0;
304     op.packetno=0;
305     ogg_stream_packetin(&enc->streams->os, &op);
306     oe_flush_page(enc);
307
308     op.packet = (unsigned char *)enc->streams->comment;
309     op.bytes = enc->streams->comment_length;
310     op.b_o_s = 0;
311     op.e_o_s = 0;
312     op.granulepos = 0;
313     op.packetno = 1;
314     ogg_stream_packetin(&enc->streams->os, &op);
315     oe_flush_page(enc);
316   }
317   enc->streams->stream_is_init = 1;
318   enc->streams->packetno = 2;
319 }
320
321 static void shift_buffer(OggOpusEnc *enc) {
322     memmove(enc->buffer, &enc->buffer[enc->channels*enc->buffer_start], enc->channels*(enc->buffer_end-enc->buffer_start)*sizeof(*enc->buffer));
323     enc->buffer_end -= enc->buffer_start;
324     enc->buffer_start = 0;
325 }
326
327 static void encode_buffer(OggOpusEnc *enc) {
328   /* Round up when converting the granule pos because the decoder will round down. */
329   ogg_int64_t end_granule48k = (enc->streams->end_granule*48000 + enc->rate - 1)/enc->rate + enc->header.preskip;
330   while (enc->buffer_end-enc->buffer_start > enc->frame_size + enc->decision_delay) {
331     int flush_needed;
332     ogg_packet op;
333     ogg_page og;
334     int nbBytes;
335     unsigned char packet[MAX_PACKET_SIZE];
336     nbBytes = opus_multistream_encode_float(enc->st, &enc->buffer[enc->channels*enc->buffer_start],
337         enc->buffer_end-enc->buffer_start, packet, MAX_PACKET_SIZE);
338     /* FIXME: How do we handle failure here. */
339     assert(nbBytes > 0);
340     enc->curr_granule += enc->frame_size;
341     op.packet=packet;
342     op.bytes=nbBytes;
343     op.b_o_s=0;
344     op.packetno=enc->streams->packetno++;
345     op.granulepos=enc->curr_granule;
346     op.e_o_s=enc->curr_granule >= end_granule48k;
347     if (op.e_o_s) op.granulepos=end_granule48k;
348     ogg_stream_packetin(&enc->streams->os, &op);
349     /* FIXME: Also flush on too many segments. */
350     flush_needed = op.e_o_s || enc->curr_granule - enc->last_page_granule > enc->max_ogg_delay;
351     if (flush_needed) {
352       while (ogg_stream_flush_fill(&enc->streams->os, &og, 255*255)) {
353         if (ogg_page_packets(&og) != 0) enc->last_page_granule = ogg_page_granulepos(&og);
354         int ret = oe_write_page(&og, &enc->callbacks, enc->streams->user_data);
355         /* FIXME: what do we do if this fails? */
356         assert(ret != -1);
357       }
358     } else {
359       while (ogg_stream_pageout_fill(&enc->streams->os, &og, 255*255)) {
360         if (ogg_page_packets(&og) != 0) enc->last_page_granule = ogg_page_granulepos(&og);
361         int ret = oe_write_page(&og, &enc->callbacks, enc->streams->user_data);
362         /* FIXME: what do we do if this fails? */
363         assert(ret != -1);
364       }
365     }
366     if (op.e_o_s) {
367       EncStream *tmp;
368       tmp = enc->streams->next;
369       if (enc->streams->close_at_end) enc->callbacks.close(enc->streams->user_data);
370       stream_destroy(enc->streams);
371       enc->streams = tmp;
372       if (!tmp) enc->last_stream = 0;
373       return;
374     }
375     enc->buffer_start += enc->frame_size;
376   }
377   /* If we've reached the end of the buffer, move everything back to the front. */
378   if (enc->buffer_end == BUFFER_SAMPLES) {
379     shift_buffer(enc);
380   }
381   /* This function must never leave the buffer full. */
382   assert(enc->buffer_end < BUFFER_SAMPLES);
383 }
384
385 /* Add/encode any number of float samples to the file. */
386 int ope_write_float(OggOpusEnc *enc, const float *pcm, int samples_per_channel) {
387   int channels = enc->channels;
388   if (!enc->streams->stream_is_init) init_stream(enc);
389   if (samples_per_channel < 0) return OPE_BAD_ARG;
390   enc->write_granule += samples_per_channel;
391   enc->last_stream->end_granule = enc->write_granule;
392   do {
393     int i;
394     spx_uint32_t in_samples, out_samples;
395     out_samples = BUFFER_SAMPLES-enc->buffer_end;
396     if (enc->re != NULL) {
397       in_samples = samples_per_channel;
398       speex_resampler_process_interleaved_float(enc->re, pcm, &in_samples, &enc->buffer[channels*enc->buffer_end], &out_samples);
399     } else {
400       int curr;
401       curr = MIN((spx_uint32_t)samples_per_channel, out_samples);
402       for (i=0;i<channels*curr;i++) {
403       enc->buffer[channels*enc->buffer_end+i] = pcm[i];
404       }
405       in_samples = out_samples = curr;
406     }
407     enc->buffer_end += out_samples;
408     pcm += in_samples*channels;
409     samples_per_channel -= in_samples;
410     encode_buffer(enc);
411   } while (samples_per_channel > 0);
412   return OPE_OK;
413 }
414
415 #define CONVERT_BUFFER 256
416
417 /* Add/encode any number of int16 samples to the file. */
418 int ope_write(OggOpusEnc *enc, const opus_int16 *pcm, int samples_per_channel) {
419   int channels = enc->channels;
420   if (!enc->streams->stream_is_init) init_stream(enc);
421   if (samples_per_channel < 0) return OPE_BAD_ARG;
422   enc->write_granule += samples_per_channel;
423   enc->last_stream->end_granule = enc->write_granule;
424   do {
425     int i;
426     spx_uint32_t in_samples, out_samples;
427     out_samples = BUFFER_SAMPLES-enc->buffer_end;
428     if (enc->re != NULL) {
429       float buf[CONVERT_BUFFER*MAX_CHANNELS];
430       in_samples = MIN(CONVERT_BUFFER, samples_per_channel);
431       for (i=0;i<channels*(int)in_samples;i++) {
432         buf[i] = (1.f/32768)*pcm[i];
433       }
434       speex_resampler_process_interleaved_float(enc->re, buf, &in_samples, &enc->buffer[channels*enc->buffer_end], &out_samples);
435     } else {
436       int curr;
437       curr = MIN((spx_uint32_t)samples_per_channel, out_samples);
438       for (i=0;i<channels*curr;i++) {
439         enc->buffer[channels*enc->buffer_end+i] = (1.f/32768)*pcm[i];
440       }
441       in_samples = out_samples = curr;
442     }
443     enc->buffer_end += out_samples;
444     pcm += in_samples*channels;
445     samples_per_channel -= in_samples;
446     encode_buffer(enc);
447   } while (samples_per_channel > 0);
448   return OPE_OK;
449 }
450
451 static void finalize_all_streams(OggOpusEnc *enc) {
452   /* FIXME: Use a better value. */
453   int pad_samples = 3000;
454   if (!enc->streams->stream_is_init) init_stream(enc);
455   shift_buffer(enc);
456   /* FIXME: Do LPC extension instead. */
457   memset(&enc->buffer[enc->channels*enc->buffer_end], 0, pad_samples*enc->channels);
458   enc->decision_delay = 0;
459   enc->buffer_end += pad_samples;
460   assert(enc->buffer_end <= BUFFER_SAMPLES);
461   encode_buffer(enc);
462   assert(enc->streams == NULL);
463 }
464
465 /* Close/finalize the stream. */
466 int ope_close_and_free(OggOpusEnc *enc) {
467   finalize_all_streams(enc);
468   free(enc->buffer);
469   opus_multistream_encoder_destroy(enc->st);
470   if (enc->re) speex_resampler_destroy(enc->re);
471   free(enc);
472   return OPE_OK;
473 }
474
475 /* Ends the stream and create a new stream within the same file. */
476 int ope_chain_current(OggOpusEnc *enc) {
477   enc->last_stream->close_at_end = 0;
478   return ope_continue_new_callbacks(enc, enc->last_stream->user_data);
479 }
480
481 /* Ends the stream and create a new file. */
482 int ope_continue_new_file(OggOpusEnc *enc, const char *path) {
483   (void)enc;
484   (void)path;
485   return OPE_UNIMPLEMENTED;
486 }
487
488 /* Ends the stream and create a new file (callback-based). */
489 int ope_continue_new_callbacks(OggOpusEnc *enc, void *user_data) {
490   (void)enc;
491   (void)user_data;
492   return OPE_UNIMPLEMENTED;
493 }
494
495 /* Add a comment to the file (can only be called before encoding samples). */
496 int ope_add_comment(OggOpusEnc *enc, const char *tag, const char *val) {
497   if (enc->streams->stream_is_init) return OPE_TOO_LATE;
498   if (comment_add(&enc->streams->comment, &enc->streams->comment_length, tag, val)) return OPE_INTERNAL_ERROR;
499   return OPE_OK;
500 }
501
502 int ope_add_picture(OggOpusEnc *enc, const char *spec) {
503   const char *error_message;
504   char *picture_data;
505   if (enc->streams->stream_is_init) return OPE_TOO_LATE;
506   picture_data = parse_picture_specification(spec, &error_message, &enc->streams->seen_file_icons);
507   if(picture_data==NULL){
508     /* FIXME: return proper errors rather than printing a message. */
509     fprintf(stderr,"Error parsing picture option: %s\n",error_message);
510     return OPE_BAD_ARG;
511   }
512   comment_add(&enc->streams->comment, &enc->streams->comment_length, "METADATA_BLOCK_PICTURE", picture_data);
513   free(picture_data);
514   return OPE_OK;
515 }
516
517 /* Sets the Opus comment vendor string (optional, defaults to library info). */
518 int ope_set_vendor_string(OggOpusEnc *enc, const char *vendor) {
519   if (enc->streams->stream_is_init) return OPE_TOO_LATE;
520   (void)vendor;
521   return OPE_UNIMPLEMENTED;
522 }
523
524 int ope_flush_header(OggOpusEnc *enc) {
525   if (enc->streams->stream_is_init) return OPE_TOO_LATE;
526   else init_stream(enc);
527   return OPE_OK;
528 }
529
530 /* Goes straight to the libopus ctl() functions. */
531 int ope_encoder_ctl(OggOpusEnc *enc, int request, ...) {
532   int ret;
533   va_list ap;
534   va_start(ap, request);
535   switch (request) {
536     case OPUS_SET_APPLICATION_REQUEST:
537     case OPUS_SET_BITRATE_REQUEST:
538     case OPUS_SET_MAX_BANDWIDTH_REQUEST:
539     case OPUS_SET_VBR_REQUEST:
540     case OPUS_SET_BANDWIDTH_REQUEST:
541     case OPUS_SET_COMPLEXITY_REQUEST:
542     case OPUS_SET_INBAND_FEC_REQUEST:
543     case OPUS_SET_PACKET_LOSS_PERC_REQUEST:
544     case OPUS_SET_DTX_REQUEST:
545     case OPUS_SET_VBR_CONSTRAINT_REQUEST:
546     case OPUS_SET_FORCE_CHANNELS_REQUEST:
547     case OPUS_SET_SIGNAL_REQUEST:
548     case OPUS_SET_LSB_DEPTH_REQUEST:
549     case OPUS_SET_PREDICTION_DISABLED_REQUEST:
550 #ifdef OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST
551     case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST:
552 #endif
553     {
554       opus_int32 value = va_arg(ap, opus_int32);
555       ret = opus_multistream_encoder_ctl(enc->st, request, value);
556     }
557     break;
558     case OPUS_SET_EXPERT_FRAME_DURATION_REQUEST:
559     {
560       opus_int32 value = va_arg(ap, opus_int32);
561       int max_supported = OPUS_FRAMESIZE_60_MS;
562 #ifdef OPUS_FRAMESIZE_120_MS
563       max_supported = OPUS_FRAMESIZE_120_MS;
564 #endif
565       if (value < OPUS_FRAMESIZE_2_5_MS || value > max_supported) {
566         ret = OPUS_UNIMPLEMENTED;
567         break;
568       }
569       ret = opus_multistream_encoder_ctl(enc->st, request, value);
570       if (ret == OPUS_OK) {
571         if (value <= OPUS_FRAMESIZE_40_MS)
572           enc->frame_size = 120<<(value-OPUS_FRAMESIZE_2_5_MS);
573         else
574           enc->frame_size = (value-OPUS_FRAMESIZE_2_5_MS-2)*960;
575       }
576     }
577     break;
578     default:
579       ret = OPUS_UNIMPLEMENTED;
580   }
581   va_end(ap);
582   return ret;
583 }
584
585 /* ctl()-type call for the OggOpus layer. */
586 int ope_set_params(OggOpusEnc *enc, int request, ...) {
587   int ret;
588   va_list ap;
589   va_start(ap, request);
590   switch (request) {
591     case OPE_SET_DECISION_DELAY_REQUEST:
592     {
593       opus_int32 value = va_arg(ap, opus_int32);
594       if (value < 0) {
595         ret = OPE_BAD_ARG;
596         break;
597       }
598       enc->decision_delay = value;
599       ret = OPE_OK;
600     }
601     break;
602     case OPE_SET_MUXING_DELAY_REQUEST:
603     {
604       opus_int32 value = va_arg(ap, opus_int32);
605       if (value < 0) {
606         ret = OPE_BAD_ARG;
607         break;
608       }
609       enc->max_ogg_delay = value;
610       ret = OPE_OK;
611     }
612     break;
613     case OPE_SET_COMMENT_PADDING_REQUEST:
614     {
615       opus_int32 value = va_arg(ap, opus_int32);
616       if (value < 0) {
617         ret = OPE_BAD_ARG;
618         break;
619       }
620       enc->comment_padding = value;
621       ret = OPE_OK;
622     }
623     break;
624     case OPE_SET_SERIALNO_REQUEST:
625     {
626       opus_int32 value = va_arg(ap, opus_int32);
627       enc->streams->serialno = value;
628       enc->streams->serialno_is_set = 1;
629       ret = OPE_OK;
630     }
631     break;
632     default:
633       return OPE_UNIMPLEMENTED;
634   }
635   va_end(ap);
636   return ret;
637 }