[vlc-devel] [PATCH] opus: add opus encoder
Tristan Matthews
le.businessman at gmail.com
Tue Sep 17 05:52:47 CEST 2013
This patch adds encoding support to the Opus module. It was also
worked on by Rafaël Carré <funman at videolan.org> and with feedback
from Timothy B. Terriberry <tterribe at xiph.org>.
---
modules/codec/avcodec/fourcc.c | 2 +
modules/codec/opus.c | 233 +++++++++++++++++++++++++++++++++++++++++
modules/codec/opus_header.c | 157 +++++++++++++++++++++++++++
modules/codec/opus_header.h | 3 +
modules/mux/ogg.c | 8 ++
5 files changed, 403 insertions(+)
diff --git a/modules/codec/avcodec/fourcc.c b/modules/codec/avcodec/fourcc.c
index 3746ac8..487966b 100644
--- a/modules/codec/avcodec/fourcc.c
+++ b/modules/codec/avcodec/fourcc.c
@@ -311,6 +311,8 @@ static const struct
{ VLC_CODEC_DVAUDIO, AV_CODEC_ID_DVAUDIO, AUDIO_ES },
+ { VLC_CODEC_OPUS, AV_CODEC_ID_OPUS, AUDIO_ES },
+
{ VLC_CODEC_MACE3, AV_CODEC_ID_MACE3, AUDIO_ES },
{ VLC_CODEC_MACE6, AV_CODEC_ID_MACE6, AUDIO_ES },
diff --git a/modules/codec/opus.c b/modules/codec/opus.c
index cdf63b8..c33867b 100644
--- a/modules/codec/opus.c
+++ b/modules/codec/opus.c
@@ -54,6 +54,10 @@
*****************************************************************************/
static int OpenDecoder ( vlc_object_t * );
static void CloseDecoder ( vlc_object_t * );
+#ifdef ENABLE_SOUT
+static int OpenEncoder ( vlc_object_t * );
+static void CloseEncoder ( vlc_object_t * );
+#endif
vlc_module_begin ()
set_category( CAT_INPUT )
@@ -64,6 +68,14 @@ vlc_module_begin ()
set_shortname( N_("Opus") )
set_callbacks( OpenDecoder, CloseDecoder )
+#ifdef ENABLE_SOUT
+ add_submodule ()
+ set_description( N_("Opus audio encoder") )
+ set_capability( "encoder", 150 )
+ set_shortname( N_("Opus") )
+ set_callbacks( OpenEncoder, CloseEncoder )
+#endif
+
vlc_module_end ()
/*****************************************************************************
@@ -433,3 +445,224 @@ static void CloseDecoder( vlc_object_t *p_this )
free( p_sys );
}
+
+#ifdef ENABLE_SOUT
+
+// only ever encode 20 ms at a time
+static const unsigned OPUS_FRAME_SIZE = 960;
+
+struct encoder_sys_t
+{
+ OpusMSEncoder *enc;
+ float *buffer;
+ unsigned i_nb_samples;
+ int i_samples_delay;
+ block_t *padding;
+};
+
+static unsigned fill_buffer(encoder_t *enc, unsigned src_start, block_t *src,
+ unsigned samples)
+{
+ encoder_sys_t *p_sys = enc->p_sys;
+ const unsigned channels = enc->fmt_out.audio.i_channels;
+ const float *src_buf = ((const float *) src->p_buffer) + src_start;
+ float *dest_buf = p_sys->buffer + (p_sys->i_nb_samples * channels);
+ const unsigned end = samples * channels;
+
+ for (unsigned i = 0; i < end; ++i)
+ dest_buf[i] = src_buf[i];
+
+ p_sys->i_nb_samples += samples;
+ src_start += end;
+
+ src->i_nb_samples -= samples;
+ return src_start;
+}
+
+static block_t *Encode(encoder_t *enc, block_t *buf)
+{
+ encoder_sys_t *sys = enc->p_sys;
+ opus_int32 bytes_encoded;
+
+ if (!buf)
+ return NULL;
+
+ mtime_t i_pts = buf->i_pts -
+ (mtime_t) CLOCK_FREQ * (mtime_t) sys->i_samples_delay /
+ (mtime_t) enc->fmt_in.audio.i_rate;
+
+ sys->i_samples_delay += buf->i_nb_samples;
+
+ block_t *result = 0;
+ unsigned src_start = 0;
+ unsigned padding_start = 0;
+
+ while (sys->i_nb_samples + buf->i_nb_samples >= OPUS_FRAME_SIZE) {
+
+ block_t *out_block = block_Alloc(1276);
+
+ // add padding to beginning
+ if (sys->padding) {
+ const size_t leftover_space = OPUS_FRAME_SIZE - sys->i_nb_samples;
+ padding_start = fill_buffer(enc, padding_start, sys->padding,
+ __MIN(sys->padding->i_nb_samples, leftover_space));
+ if (sys->padding->i_nb_samples <= 0) {
+ block_Release(sys->padding);
+ sys->padding = 0;
+ }
+ }
+
+ if (!sys->padding) {
+ const size_t leftover_space = OPUS_FRAME_SIZE - sys->i_nb_samples;
+ src_start = fill_buffer(enc, src_start, buf,
+ __MIN(buf->i_nb_samples, leftover_space));
+ }
+ // TODO: integer pcm
+ bytes_encoded = opus_multistream_encode_float(sys->enc, sys->buffer,
+ OPUS_FRAME_SIZE, out_block->p_buffer, out_block->i_buffer);
+
+ out_block->i_length = (mtime_t) CLOCK_FREQ *
+ (mtime_t) OPUS_FRAME_SIZE / (mtime_t) enc->fmt_in.audio.i_rate;
+
+ out_block->i_dts = out_block->i_pts = i_pts;
+
+ sys->i_samples_delay -= OPUS_FRAME_SIZE;
+
+ i_pts += out_block->i_length;
+
+ sys->i_nb_samples = 0;
+
+ if (bytes_encoded < 0) {
+ block_Release(out_block);
+ result = NULL;
+ } else {
+ out_block->i_buffer = bytes_encoded;
+ block_ChainAppend(&result, out_block);
+ }
+ }
+
+ // put leftover samples at beginning of buffer
+ if (buf->i_nb_samples > 0)
+ fill_buffer(enc, src_start, buf, buf->i_nb_samples);
+
+ return result;
+}
+
+static int OpenEncoder(vlc_object_t *p_this)
+{
+ encoder_t *enc = (encoder_t *)p_this;
+
+ if (enc->fmt_out.i_codec != VLC_CODEC_OPUS)
+ return VLC_EGENERIC;
+
+ encoder_sys_t *sys = malloc(sizeof(*sys));
+ if (!sys)
+ return VLC_ENOMEM;
+
+ enc->pf_encode_audio = Encode;
+ enc->fmt_in.i_codec = VLC_CODEC_FL32; // ?
+ enc->fmt_in.audio.i_rate = /* Only 48kHz */
+ enc->fmt_out.audio.i_rate = 48000;
+ enc->fmt_out.audio.i_channels = enc->fmt_in.audio.i_channels;
+
+ static const char *const options[] = {
+ NULL,
+ };
+ config_ChainParse(enc, "sout-opus-", options, enc->p_cfg);
+
+ int force_narrow = 0;
+ OpusHeader header;
+
+ unsigned char mapping[256];
+ if (opus_prepare_header(enc->fmt_out.audio.i_channels,
+ enc->fmt_out.audio.i_rate,
+ &force_narrow,
+ &header,
+ mapping)) {
+ msg_Err(enc, "Failed to prepare header.");
+ return VLC_ENOMEM;
+ }
+
+ int err;
+ sys->enc = opus_multistream_encoder_create(enc->fmt_in.audio.i_rate,
+ enc->fmt_in.audio.i_channels, header.nb_streams, header.nb_coupled,
+ mapping, OPUS_APPLICATION_AUDIO, &err);
+
+ if (err != OPUS_OK) {
+ msg_Err(enc, "Could not create encoder: error %d", err);
+ free(sys);
+ return VLC_EGENERIC;
+ }
+
+ if (force_narrow != 0) {
+ for(int i = 0; i < header.nb_streams; i++) {
+ if (force_narrow & (1 << i)) {
+ OpusEncoder *oe;
+ opus_multistream_encoder_ctl(sys->enc,
+ OPUS_MULTISTREAM_GET_ENCODER_STATE(i, &oe));
+ int ret = opus_encoder_ctl(oe,
+ OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_NARROWBAND));
+ if (ret != OPUS_OK) {
+ msg_Err(enc, "OPUS_SET_MAX_BANDWIDTH on stream %d "
+ "returned: %s\n", i, opus_strerror(ret));
+ return VLC_EGENERIC;
+ }
+ }
+ }
+ }
+
+ // TODO: vbr, bitrate, fec, ..
+
+ // Buffer for incoming audio, since opus only accepts frame sizes that are
+ // multiples of 2.5ms
+ enc->p_sys = sys;
+ sys->buffer = (float *) malloc(OPUS_FRAME_SIZE * header.channels *
+ sizeof(float));
+ if (!sys->buffer)
+ return VLC_ENOMEM;
+
+ sys->i_nb_samples = 0;
+
+ sys->i_samples_delay = 0;
+ int ret = opus_multistream_encoder_ctl(enc->p_sys->enc,
+ OPUS_GET_LOOKAHEAD(&sys->i_samples_delay));
+ if (ret != OPUS_OK)
+ msg_Err(enc, "Unable to get number of lookahead samples: %s\n",
+ opus_strerror(ret));
+
+ header.preskip = sys->i_samples_delay;
+
+ /* Now that we have preskip, we can write the header to extradata */
+ if (opus_write_header((uint8_t **) &enc->fmt_out.p_extra,
+ &enc->fmt_out.i_extra, &header)) {
+ msg_Err(enc, "Failed to write header.");
+ return VLC_ENOMEM;
+ }
+
+ if (sys->i_samples_delay > 0) {
+ const unsigned padding_samples = sys->i_samples_delay *
+ enc->fmt_out.audio.i_channels;
+ sys->padding = block_Alloc(padding_samples * sizeof(float));
+ if (!sys->padding)
+ return VLC_ENOMEM;
+ sys->padding->i_nb_samples = sys->i_samples_delay;
+ float *pad_ptr = (float *) sys->padding->p_buffer;
+ for (unsigned i = 0; i < padding_samples; ++i)
+ pad_ptr[i] = 0.0f;
+ } else {
+ sys->padding = 0;
+ }
+
+ return VLC_SUCCESS;
+}
+
+static void CloseEncoder(vlc_object_t *p_this)
+{
+ encoder_t *enc = (encoder_t *)p_this;
+ encoder_sys_t *sys = enc->p_sys;
+
+ opus_multistream_encoder_destroy(sys->enc);
+ free(sys->buffer);
+ free(sys);
+}
+#endif /* ENABLE_SOUT */
diff --git a/modules/codec/opus_header.c b/modules/codec/opus_header.c
index cb5fb93..3c16d0d 100644
--- a/modules/codec/opus_header.c
+++ b/modules/codec/opus_header.c
@@ -30,8 +30,13 @@
#endif
#include "opus_header.h"
+#include <opus.h>
#include <string.h>
#include <stdio.h>
+#include <stdlib.h>
+
+#include <vlc_common.h>
+#include "../demux/xiph.h"
/* Header contents:
- "OpusHead" (64 bits)
@@ -213,6 +218,158 @@ int opus_header_parse(const unsigned char *packet, int len, OpusHeader *h)
return 1;
}
+#define readint(buf, base) (((buf[base+3]<<24)&0xff000000)| \
+ ((buf[base+2]<<16)&0xff0000)| \
+ ((buf[base+1]<<8)&0xff00)| \
+ (buf[base]&0xff))
+#define writeint(buf, base, val) do{ buf[base+3]=((val)>>24)&0xff; \
+ buf[base+2]=((val)>>16)&0xff; \
+ buf[base+1]=((val)>>8)&0xff; \
+ buf[base]=(val)&0xff; \
+ }while(0)
+#define writeshort(buf, base, val) do{ buf[base+1]=((val)>>8)&0xff; \
+ buf[base]=(val)&0xff; \
+ }while(0)
+
+/*
+ Comments will be stored in the Vorbis style.
+ It is describled in the "Structure" section of
+ http://www.xiph.org/ogg/vorbis/doc/v-comment.html
+
+ However, Opus and other non-vorbis formats omit the "framing_bit".
+
+The comment header is decoded as follows:
+ 1) [vendor_length] = read an unsigned integer of 32 bits
+ 2) [vendor_string] = read a UTF-8 vector as [vendor_length] octets
+ 3) [user_comment_list_length] = read an unsigned integer of 32 bits
+ 4) iterate [user_comment_list_length] times {
+ 5) [length] = read an unsigned integer of 32 bits
+ 6) this iteration's user comment = read a UTF-8 vector as [length] octets
+ }
+ 7) done.
+*/
+
+static int comment_init(char **comments, int* length, const char *vendor_string)
+{
+ /*The 'vendor' field should be the actual encoding library used.*/
+ int vendor_length = strlen(vendor_string);
+ int user_comment_list_length = 0;
+ int len = 8 + 4 + vendor_length + 4;
+ char *p = (char *) malloc(len);
+ if (p == NULL)
+ return 1;
+
+ memcpy(p, "OpusTags", 8);
+ writeint(p, 8, vendor_length);
+ memcpy(p + 12, vendor_string, vendor_length);
+ writeint(p, 12 + vendor_length, user_comment_list_length);
+ *length = len;
+ *comments = p;
+ return 0;
+}
+
+static int comment_add(char **comments, int* length, const char *tag, const char *val)
+{
+ char *p = *comments;
+ int vendor_length = readint(p, 8);
+ int user_comment_list_length = readint(p, 8 + 4 + vendor_length);
+ int tag_len = (tag ? strlen(tag) : 0);
+ int val_len = strlen(val);
+ int len=(*length) + 4 + tag_len + val_len;
+
+ p = (char*) realloc(p, len);
+ if (p == NULL)
+ return 1;
+
+ writeint(p, *length, tag_len + val_len); /* length of comment */
+ if (tag) memcpy(p + *length + 4, tag, tag_len); /* comment */
+ memcpy(p + *length + 4 + tag_len, val, val_len); /* comment */
+ writeint(p, 8 + 4 + vendor_length, user_comment_list_length + 1);
+ *comments = p;
+ *length = len;
+ return 0;
+}
+
+int opus_prepare_header(unsigned channels, unsigned rate, int *force_narrow,
+ OpusHeader *header, unsigned char mapping[256])
+{
+ header->version = 1;
+ header->channels = channels;
+ header->nb_streams = header->channels;
+ header->nb_coupled = 0;
+ header->input_sample_rate = rate;
+ header->gain = 0; // 0dB
+
+ for (size_t i = 0; i < 256; i++)
+ mapping[i] = i;
+
+ if (header->channels <= 8) {
+ static const unsigned char opusenc_streams[8][10] = {
+ /*Coupled, NB_bitmap, mapping...*/
+ /*1*/ {0, 0, 0},
+ /*2*/ {1, 0, 0,1},
+ /*3*/ {1, 0, 0,2,1},
+ /*4*/ {2, 0, 0,1,2,3},
+ /*5*/ {2, 0, 0,4,1,2,3},
+ /*6*/ {2,1<<3, 0,4,1,2,3,5},
+ /*7*/ {2,1<<4, 0,4,1,2,3,5,6},
+ /*6*/ {3,1<<4, 0,6,1,2,3,4,5,7}
+ };
+ for (int i=0; i < header->channels; i++)
+ mapping[i] = opusenc_streams[header->channels - 1][i + 2];
+ *force_narrow = opusenc_streams[header->channels - 1][1];
+ header->nb_coupled = opusenc_streams[header->channels - 1][0];
+ header->nb_streams = header->channels - header->nb_coupled;
+ }
+ header->channel_mapping = header->channels > 8 ? 255 : header->nb_streams > 1;
+ if (header->channel_mapping > 0)
+ for(int i = 0; i < header->channels; i++)
+ header->stream_map[i] = mapping[i];
+
+ return 0;
+}
+
+int opus_write_header(uint8_t **p_extra, int *i_extra, OpusHeader *header)
+{
+ unsigned char header_data[100];
+ const int packet_size = opus_header_to_packet(header, header_data, sizeof(header_data));
+ ogg_packet headers[2];
+ headers[0].packet = header_data;
+ headers[0].bytes = packet_size;
+ headers[0].b_o_s = 1;
+ headers[0].e_o_s = 0;
+ headers[0].granulepos = 0;
+ headers[0].packetno = 0;
+
+ const char *opus_version = opus_get_version_string();
+ int comments_length;
+ char *comments;
+ if (comment_init(&comments, &comments_length, opus_version))
+ return 1;
+ if (comment_add(&comments, &comments_length, "ENCODER=", "opus encoder from VLC media player"))
+ return 1;
+
+ headers[1].packet = (unsigned char *) comments;
+ headers[1].bytes = comments_length;
+ headers[1].b_o_s = 0;
+ headers[1].e_o_s = 0;
+ headers[1].granulepos = 0;
+ headers[1].packetno = 1;
+
+ for (unsigned i = 0; i < ARRAY_SIZE(headers); ++i) {
+ if (xiph_AppendHeaders(i_extra, (void **) p_extra,
+ headers[i].bytes, headers[i].packet)) {
+ *i_extra = 0;
+ *p_extra = NULL;
+ }
+ }
+
+ return 0;
+}
+
+#undef writeint
+#undef writeshort
+
int opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len)
{
Packet p;
diff --git a/modules/codec/opus_header.h b/modules/codec/opus_header.h
index 633c2f4..f7b6a7d 100644
--- a/modules/codec/opus_header.h
+++ b/modules/codec/opus_header.h
@@ -45,5 +45,8 @@ typedef struct {
int opus_header_parse(const unsigned char *header, int len, OpusHeader *h);
int opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len);
+int opus_prepare_header(unsigned channels, unsigned rate, int *force_narrow,
+ OpusHeader *header, unsigned char mapping[256]);
+int opus_write_header(uint8_t **p_extra, int *i_extra, OpusHeader *header);
#endif
diff --git a/modules/mux/ogg.c b/modules/mux/ogg.c
index 99252b0..a805996 100644
--- a/modules/mux/ogg.c
+++ b/modules/mux/ogg.c
@@ -361,6 +361,10 @@ static int AddStream( sout_mux_t *p_mux, sout_input_t *p_input )
case AUDIO_ES:
switch( p_stream->i_fourcc )
{
+ case VLC_CODEC_OPUS:
+ msg_Dbg( p_mux, "opus stream" );
+ break;
+
case VLC_CODEC_VORBIS:
msg_Dbg( p_mux, "vorbis stream" );
break;
@@ -626,6 +630,7 @@ static block_t *OggCreateHeader( sout_mux_t *p_mux )
if( p_stream->i_fourcc == VLC_CODEC_VORBIS ||
p_stream->i_fourcc == VLC_CODEC_SPEEX ||
+ p_stream->i_fourcc == VLC_CODEC_OPUS ||
p_stream->i_fourcc == VLC_CODEC_THEORA )
{
/* First packet in order: vorbis/speex/theora info */
@@ -713,6 +718,7 @@ static block_t *OggCreateHeader( sout_mux_t *p_mux )
if( p_stream->i_fourcc == VLC_CODEC_VORBIS ||
p_stream->i_fourcc == VLC_CODEC_SPEEX ||
+ p_stream->i_fourcc == VLC_CODEC_OPUS ||
p_stream->i_fourcc == VLC_CODEC_THEORA )
{
unsigned pi_size[XIPH_MAX_HEADER_COUNT];
@@ -977,6 +983,7 @@ static int MuxBlock( sout_mux_t *p_mux, sout_input_t *p_input )
if( p_stream->i_fourcc != VLC_CODEC_VORBIS &&
p_stream->i_fourcc != VLC_CODEC_FLAC &&
p_stream->i_fourcc != VLC_CODEC_SPEEX &&
+ p_stream->i_fourcc != VLC_CODEC_OPUS &&
p_stream->i_fourcc != VLC_CODEC_THEORA &&
p_stream->i_fourcc != VLC_CODEC_DIRAC )
{
@@ -994,6 +1001,7 @@ static int MuxBlock( sout_mux_t *p_mux, sout_input_t *p_input )
{
if( p_stream->i_fourcc == VLC_CODEC_VORBIS ||
p_stream->i_fourcc == VLC_CODEC_FLAC ||
+ p_stream->i_fourcc == VLC_CODEC_OPUS ||
p_stream->i_fourcc == VLC_CODEC_SPEEX )
{
/* number of sample from begining + current packet */
--
1.8.1.2
More information about the vlc-devel
mailing list