Some refactoring for chaining
[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   EncStream *next;
90 };
91
92 struct OggOpusEnc {
93   OpusMSEncoder *st;
94   int rate;
95   int channels;
96   float *buffer;
97   int buffer_start;
98   int buffer_end;
99   SpeexResamplerState *re;
100   int frame_size;
101   int decision_delay;
102   int max_ogg_delay;
103   ogg_int64_t curr_granule;
104   ogg_int64_t end_granule;
105   ogg_int64_t last_page_granule;
106   OpusEncCallbacks callbacks;
107   OpusHeader header;
108   int comment_padding;
109   EncStream *streams;
110 };
111
112 static int oe_flush_page(OggOpusEnc *enc) {
113   ogg_page og;
114   int ret;
115   int written = 0;
116   while ( (ret = ogg_stream_flush(&enc->streams->os, &og)) ) {
117     if (!ret) break;
118     ret = oe_write_page(&og, &enc->callbacks, enc->streams->user_data);
119     if (ret == -1) {
120       return -1;
121     }
122     written += ret;
123   }
124   return written;
125 }
126
127 int stdio_write(void *user_data, const unsigned char *ptr, int len) {
128   struct StdioObject *obj = (struct StdioObject*)user_data;
129   return fwrite(ptr, 1, len, obj->file) != (size_t)len;
130 }
131
132 int stdio_close(void *user_data) {
133   struct StdioObject *obj = (struct StdioObject*)user_data;
134   int ret = fclose(obj->file);
135   free(obj);
136   return ret;
137 }
138
139 static const OpusEncCallbacks stdio_callbacks = {
140   stdio_write,
141   stdio_close
142 };
143
144 /* Create a new OggOpus file. */
145 OggOpusEnc *ope_create_file(const char *path, int rate, int channels, int family, int *error) {
146   OggOpusEnc *enc;
147   struct StdioObject *obj;
148   obj = malloc(sizeof(*obj));
149   enc = ope_create_callbacks(&stdio_callbacks, obj, rate, channels, family, error);
150   if (enc == NULL || (error && *error)) {
151     return NULL;
152   }
153   obj->file = fopen(path, "wb");
154   if (!obj->file) {
155     if (error) *error = OPE_CANNOT_OPEN;
156     /* FIXME: Destroy the encoder properly. */
157     free(obj);
158     return NULL;
159   }
160   return enc;
161 }
162
163 /* Create a new OggOpus file (callback-based). */
164 OggOpusEnc *ope_create_callbacks(const OpusEncCallbacks *callbacks, void *user_data,
165     int rate, int channels, int family, int *error) {
166   OpusMSEncoder *st=NULL;
167   OggOpusEnc *enc=NULL;
168   int ret;
169   if (family != 0 && family != 1 && family != 255) {
170     if (error) *error = OPE_UNIMPLEMENTED;
171     return NULL;
172   }
173   if (channels <= 0 || channels > 255) {
174     if (error) *error = OPE_BAD_ARG;
175     return NULL;
176   }
177   /* FIXME: Add resampling support. */
178   if (rate <= 0) {
179     if (error) *error = OPE_BAD_ARG;
180     return NULL;
181   }
182
183   if ( (enc = malloc(sizeof(*enc))) == NULL) goto fail;
184   enc->streams = NULL;
185   if ( (enc->streams = malloc(sizeof(*enc->streams))) == NULL) goto fail;
186   enc->streams->next = NULL;
187   enc->rate = rate;
188   enc->channels = channels;
189   enc->frame_size = 960;
190   enc->decision_delay = 96000;
191   enc->max_ogg_delay = 48000;
192   enc->comment_padding = 512;
193   enc->streams->serialno_is_set = 0;
194   enc->streams->seen_file_icons = 0;
195   enc->header.channels=channels;
196   enc->header.channel_mapping=family;
197   enc->header.input_sample_rate=rate;
198   enc->header.gain=0;
199   st=opus_multistream_surround_encoder_create(48000, channels, enc->header.channel_mapping,
200       &enc->header.nb_streams, &enc->header.nb_coupled,
201       enc->header.stream_map, OPUS_APPLICATION_AUDIO, &ret);
202   if (! (ret == OPUS_OK && st != NULL) ) {
203     goto fail;
204   }
205   if (rate != 48000) {
206     enc->re = speex_resampler_init(channels, rate, 48000, 5, NULL);
207     if (enc->re == NULL) goto fail;
208     speex_resampler_skip_zeros(enc->re);
209   } else {
210     enc->re = NULL;
211   }
212   opus_multistream_encoder_ctl(st, OPUS_SET_EXPERT_FRAME_DURATION(OPUS_FRAMESIZE_20_MS));
213   enc->streams->stream_is_init = 0;
214   enc->streams->comment = NULL;
215   {
216     opus_int32 tmp;
217     int ret;
218     ret = opus_multistream_encoder_ctl(st, OPUS_GET_LOOKAHEAD(&tmp));
219     if (ret == OPUS_OK) enc->header.preskip = tmp;
220     else enc->header.preskip = 0;
221   }
222   enc->curr_granule = 0;
223   enc->end_granule = 0;
224   enc->last_page_granule = 0;
225   comment_init(&enc->streams->comment, &enc->streams->comment_length, opus_get_version_string());
226   {
227     char encoder_string[1024];
228     snprintf(encoder_string, sizeof(encoder_string), "%s version %s", PACKAGE_NAME, PACKAGE_VERSION);
229     comment_add(&enc->streams->comment, &enc->streams->comment_length, "ENCODER", encoder_string);
230   }
231   if (enc->streams->comment == NULL) goto fail;
232   if ( (enc->buffer = malloc(sizeof(*enc->buffer)*BUFFER_SAMPLES*channels)) == NULL) goto fail;
233   enc->buffer_start = enc->buffer_end = 0;
234   enc->st = st;
235   enc->callbacks = *callbacks;
236   enc->streams->user_data = user_data;
237   if (error) *error = OPUS_OK;
238   return enc;
239 fail:
240   if (enc) {
241     free(enc);
242     if (enc->buffer) free(enc->buffer);
243     if (enc->streams) {
244       free(enc->streams);
245       if (enc->streams->comment) free (enc->streams->comment);
246     }
247   }
248   if (st) {
249     opus_multistream_encoder_destroy(st);
250   }
251   return NULL;
252 }
253
254 static void init_stream(OggOpusEnc *enc) {
255   time_t start_time;
256   assert(!enc->streams->stream_is_init);
257   if (!enc->streams->serialno_is_set) {
258     start_time = time(NULL);
259     srand(((getpid()&65535)<<15)^start_time);
260
261     enc->streams->serialno = rand();
262   }
263   
264   if (ogg_stream_init(&enc->streams->os, enc->streams->serialno) == -1) {
265     assert(0);
266     /* FIXME: How the hell do we handle that? */
267   }
268   comment_pad(&enc->streams->comment, &enc->streams->comment_length, enc->comment_padding);
269
270   /*Write header*/
271   {
272     ogg_packet op;
273     /*The Identification Header is 19 bytes, plus a Channel Mapping Table for
274       mapping families other than 0. The Channel Mapping Table is 2 bytes +
275       1 byte per channel. Because the maximum number of channels is 255, the
276       maximum size of this header is 19 + 2 + 255 = 276 bytes.*/
277     unsigned char header_data[276];
278     int packet_size = opus_header_to_packet(&enc->header, header_data, sizeof(header_data));
279     op.packet=header_data;
280     op.bytes=packet_size;
281     op.b_o_s=1;
282     op.e_o_s=0;
283     op.granulepos=0;
284     op.packetno=0;
285     ogg_stream_packetin(&enc->streams->os, &op);
286     oe_flush_page(enc);
287
288     op.packet = (unsigned char *)enc->streams->comment;
289     op.bytes = enc->streams->comment_length;
290     op.b_o_s = 0;
291     op.e_o_s = 0;
292     op.granulepos = 0;
293     op.packetno = 1;
294     ogg_stream_packetin(&enc->streams->os, &op);
295     oe_flush_page(enc);
296   }
297   enc->streams->stream_is_init = 1;
298   enc->streams->packetno = 2;
299 }
300
301 static void shift_buffer(OggOpusEnc *enc) {
302     memmove(enc->buffer, &enc->buffer[enc->channels*enc->buffer_start], enc->channels*(enc->buffer_end-enc->buffer_start)*sizeof(*enc->buffer));
303     enc->buffer_end -= enc->buffer_start;
304     enc->buffer_start = 0;
305 }
306
307 static void encode_buffer(OggOpusEnc *enc) {
308   /* Round up when converting the granule pos because the decoder will round down. */
309   ogg_int64_t end_granule48k = (enc->end_granule*48000 + enc->rate - 1)/enc->rate + enc->header.preskip;
310   while (enc->buffer_end-enc->buffer_start > enc->frame_size + enc->decision_delay) {
311     int flush_needed;
312     ogg_packet op;
313     ogg_page og;
314     int nbBytes;
315     unsigned char packet[MAX_PACKET_SIZE];
316     nbBytes = opus_multistream_encode_float(enc->st, &enc->buffer[enc->channels*enc->buffer_start],
317         enc->buffer_end-enc->buffer_start, packet, MAX_PACKET_SIZE);
318     /* FIXME: How do we handle failure here. */
319     assert(nbBytes > 0);
320     enc->curr_granule += enc->frame_size;
321     op.packet=packet;
322     op.bytes=nbBytes;
323     op.b_o_s=0;
324     op.packetno=enc->streams->packetno++;
325     op.granulepos=enc->curr_granule;
326     op.e_o_s=enc->curr_granule >= end_granule48k;
327     if (op.e_o_s) op.granulepos=end_granule48k;
328     ogg_stream_packetin(&enc->streams->os, &op);
329     /* FIXME: Also flush on too many segments. */
330     flush_needed = op.e_o_s || enc->curr_granule - enc->last_page_granule > enc->max_ogg_delay;
331     if (flush_needed) {
332       while (ogg_stream_flush_fill(&enc->streams->os, &og, 255*255)) {
333         if (ogg_page_packets(&og) != 0) enc->last_page_granule = ogg_page_granulepos(&og);
334         int ret = oe_write_page(&og, &enc->callbacks, enc->streams->user_data);
335         /* FIXME: what do we do if this fails? */
336         assert(ret != -1);
337       }
338     } else {
339       while (ogg_stream_pageout_fill(&enc->streams->os, &og, 255*255)) {
340         if (ogg_page_packets(&og) != 0) enc->last_page_granule = ogg_page_granulepos(&og);
341         int ret = oe_write_page(&og, &enc->callbacks, enc->streams->user_data);
342         /* FIXME: what do we do if this fails? */
343         assert(ret != -1);
344       }
345     }
346     if (op.e_o_s) return;
347     enc->buffer_start += enc->frame_size;
348   }
349   /* If we've reached the end of the buffer, move everything back to the front. */
350   if (enc->buffer_end == BUFFER_SAMPLES) {
351     shift_buffer(enc);
352   }
353   /* This function must never leave the buffer full. */
354   assert(enc->buffer_end < BUFFER_SAMPLES);
355 }
356
357 /* Add/encode any number of float samples to the file. */
358 int ope_write_float(OggOpusEnc *enc, const float *pcm, int samples_per_channel) {
359   int channels = enc->channels;
360   if (!enc->streams->stream_is_init) init_stream(enc);
361   if (samples_per_channel < 0) return OPE_BAD_ARG;
362   enc->end_granule += samples_per_channel;
363   do {
364     int i;
365     spx_uint32_t in_samples, out_samples;
366     out_samples = BUFFER_SAMPLES-enc->buffer_end;
367     if (enc->re != NULL) {
368       in_samples = samples_per_channel;
369       speex_resampler_process_interleaved_float(enc->re, pcm, &in_samples, &enc->buffer[channels*enc->buffer_end], &out_samples);
370     } else {
371       int curr;
372       curr = MIN((spx_uint32_t)samples_per_channel, out_samples);
373       for (i=0;i<channels*curr;i++) {
374       enc->buffer[channels*enc->buffer_end+i] = pcm[i];
375       }
376       in_samples = out_samples = curr;
377     }
378     enc->buffer_end += out_samples;
379     pcm += in_samples*channels;
380     samples_per_channel -= in_samples;
381     encode_buffer(enc);
382   } while (samples_per_channel > 0);
383   return OPE_OK;
384 }
385
386 #define CONVERT_BUFFER 256
387
388 /* Add/encode any number of int16 samples to the file. */
389 int ope_write(OggOpusEnc *enc, const opus_int16 *pcm, int samples_per_channel) {
390   int channels = enc->channels;
391   if (!enc->streams->stream_is_init) init_stream(enc);
392   if (samples_per_channel < 0) return OPE_BAD_ARG;
393   enc->end_granule += samples_per_channel;
394   do {
395     int i;
396     spx_uint32_t in_samples, out_samples;
397     out_samples = BUFFER_SAMPLES-enc->buffer_end;
398     if (enc->re != NULL) {
399       float buf[CONVERT_BUFFER*MAX_CHANNELS];
400       in_samples = MIN(CONVERT_BUFFER, samples_per_channel);
401       for (i=0;i<channels*(int)in_samples;i++) {
402         buf[i] = (1.f/32768)*pcm[i];
403       }
404       speex_resampler_process_interleaved_float(enc->re, buf, &in_samples, &enc->buffer[channels*enc->buffer_end], &out_samples);
405     } else {
406       int curr;
407       curr = MIN((spx_uint32_t)samples_per_channel, out_samples);
408       for (i=0;i<channels*curr;i++) {
409         enc->buffer[channels*enc->buffer_end+i] = (1.f/32768)*pcm[i];
410       }
411       in_samples = out_samples = curr;
412     }
413     enc->buffer_end += out_samples;
414     pcm += in_samples*channels;
415     samples_per_channel -= in_samples;
416     encode_buffer(enc);
417   } while (samples_per_channel > 0);
418   return OPE_OK;
419 }
420
421 static void finalize_stream(OggOpusEnc *enc) {
422   /* FIXME: Use a better value. */
423   int pad_samples = 3000;
424   if (!enc->streams->stream_is_init) init_stream(enc);
425   shift_buffer(enc);
426   /* FIXME: Do LPC extension instead. */
427   memset(&enc->buffer[enc->channels*enc->buffer_end], 0, pad_samples*enc->channels);
428   enc->decision_delay = 0;
429   enc->buffer_end += pad_samples;
430   assert(enc->buffer_end <= BUFFER_SAMPLES);
431   encode_buffer(enc);
432 }
433
434 /* Close/finalize the stream. */
435 int ope_close_and_free(OggOpusEnc *enc) {
436   finalize_stream(enc);
437   enc->callbacks.close(enc->streams->user_data);
438   free(enc->streams->comment);
439   free(enc->buffer);
440   opus_multistream_encoder_destroy(enc->st);
441   if (enc->streams->stream_is_init) ogg_stream_clear(&enc->streams->os);
442   if (enc->re) speex_resampler_destroy(enc->re);
443   free(enc);
444   return OPE_OK;
445 }
446
447 /* Ends the stream and create a new stream within the same file. */
448 int ope_chain_current(OggOpusEnc *enc) {
449   EncStream *stream = enc->streams;
450   while (stream->next) stream = stream->next;
451   /* FIXME: Make sure we don't end up calling the close callback too early. */
452   return ope_continue_new_callbacks(enc, stream->user_data);
453 }
454
455 /* Ends the stream and create a new file. */
456 int ope_continue_new_file(OggOpusEnc *enc, const char *path) {
457   (void)enc;
458   (void)path;
459   return OPE_UNIMPLEMENTED;
460 }
461
462 /* Ends the stream and create a new file (callback-based). */
463 int ope_continue_new_callbacks(OggOpusEnc *enc, void *user_data) {
464   (void)enc;
465   (void)user_data;
466   return OPE_UNIMPLEMENTED;
467 }
468
469 /* Add a comment to the file (can only be called before encoding samples). */
470 int ope_add_comment(OggOpusEnc *enc, const char *tag, const char *val) {
471   if (enc->streams->stream_is_init) return OPE_TOO_LATE;
472   if (comment_add(&enc->streams->comment, &enc->streams->comment_length, tag, val)) return OPE_INTERNAL_ERROR;
473   return OPE_OK;
474 }
475
476 int ope_add_picture(OggOpusEnc *enc, const char *spec) {
477   const char *error_message;
478   char *picture_data;
479   if (enc->streams->stream_is_init) return OPE_TOO_LATE;
480   picture_data = parse_picture_specification(spec, &error_message, &enc->streams->seen_file_icons);
481   if(picture_data==NULL){
482     /* FIXME: return proper errors rather than printing a message. */
483     fprintf(stderr,"Error parsing picture option: %s\n",error_message);
484     return OPE_BAD_ARG;
485   }
486   comment_add(&enc->streams->comment, &enc->streams->comment_length, "METADATA_BLOCK_PICTURE", picture_data);
487   free(picture_data);
488   return OPE_OK;
489 }
490
491 /* Sets the Opus comment vendor string (optional, defaults to library info). */
492 int ope_set_vendor_string(OggOpusEnc *enc, const char *vendor) {
493   if (enc->streams->stream_is_init) return OPE_TOO_LATE;
494   (void)vendor;
495   return OPE_UNIMPLEMENTED;
496 }
497
498 int ope_flush_header(OggOpusEnc *enc) {
499   if (enc->streams->stream_is_init) return OPE_TOO_LATE;
500   else init_stream(enc);
501   return OPE_OK;
502 }
503
504 /* Goes straight to the libopus ctl() functions. */
505 int ope_encoder_ctl(OggOpusEnc *enc, int request, ...) {
506   int ret;
507   va_list ap;
508   va_start(ap, request);
509   switch (request) {
510     case OPUS_SET_APPLICATION_REQUEST:
511     case OPUS_SET_BITRATE_REQUEST:
512     case OPUS_SET_MAX_BANDWIDTH_REQUEST:
513     case OPUS_SET_VBR_REQUEST:
514     case OPUS_SET_BANDWIDTH_REQUEST:
515     case OPUS_SET_COMPLEXITY_REQUEST:
516     case OPUS_SET_INBAND_FEC_REQUEST:
517     case OPUS_SET_PACKET_LOSS_PERC_REQUEST:
518     case OPUS_SET_DTX_REQUEST:
519     case OPUS_SET_VBR_CONSTRAINT_REQUEST:
520     case OPUS_SET_FORCE_CHANNELS_REQUEST:
521     case OPUS_SET_SIGNAL_REQUEST:
522     case OPUS_SET_LSB_DEPTH_REQUEST:
523     case OPUS_SET_PREDICTION_DISABLED_REQUEST:
524 #ifdef OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST
525     case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST:
526 #endif
527     {
528       opus_int32 value = va_arg(ap, opus_int32);
529       ret = opus_multistream_encoder_ctl(enc->st, request, value);
530     }
531     break;
532     case OPUS_SET_EXPERT_FRAME_DURATION_REQUEST:
533     {
534       opus_int32 value = va_arg(ap, opus_int32);
535       int max_supported = OPUS_FRAMESIZE_60_MS;
536 #ifdef OPUS_FRAMESIZE_120_MS
537       max_supported = OPUS_FRAMESIZE_120_MS;
538 #endif
539       if (value < OPUS_FRAMESIZE_2_5_MS || value > max_supported) {
540         ret = OPUS_UNIMPLEMENTED;
541         break;
542       }
543       ret = opus_multistream_encoder_ctl(enc->st, request, value);
544       if (ret == OPUS_OK) {
545         if (value <= OPUS_FRAMESIZE_40_MS)
546           enc->frame_size = 120<<(value-OPUS_FRAMESIZE_2_5_MS);
547         else
548           enc->frame_size = (value-OPUS_FRAMESIZE_2_5_MS-2)*960;
549       }
550     }
551     break;
552     default:
553       ret = OPUS_UNIMPLEMENTED;
554   }
555   va_end(ap);
556   return ret;
557 }
558
559 /* ctl()-type call for the OggOpus layer. */
560 int ope_set_params(OggOpusEnc *enc, int request, ...) {
561   int ret;
562   va_list ap;
563   va_start(ap, request);
564   switch (request) {
565     case OPE_SET_DECISION_DELAY_REQUEST:
566     {
567       opus_int32 value = va_arg(ap, opus_int32);
568       if (value < 0) {
569         ret = OPE_BAD_ARG;
570         break;
571       }
572       enc->decision_delay = value;
573       ret = OPE_OK;
574     }
575     break;
576     case OPE_SET_MUXING_DELAY_REQUEST:
577     {
578       opus_int32 value = va_arg(ap, opus_int32);
579       if (value < 0) {
580         ret = OPE_BAD_ARG;
581         break;
582       }
583       enc->max_ogg_delay = value;
584       ret = OPE_OK;
585     }
586     break;
587     case OPE_SET_COMMENT_PADDING_REQUEST:
588     {
589       opus_int32 value = va_arg(ap, opus_int32);
590       if (value < 0) {
591         ret = OPE_BAD_ARG;
592         break;
593       }
594       enc->comment_padding = value;
595       ret = OPE_OK;
596     }
597     break;
598     case OPE_SET_SERIALNO_REQUEST:
599     {
600       opus_int32 value = va_arg(ap, opus_int32);
601       enc->streams->serialno = value;
602       enc->streams->serialno_is_set = 1;
603       ret = OPE_OK;
604     }
605     break;
606     default:
607       return OPE_UNIMPLEMENTED;
608   }
609   va_end(ap);
610   return ret;
611 }