[vlc-devel] [PATCH] opus: add opus encoder
Denis Charmet
typx at dinauz.org
Tue Sep 17 10:59:20 CEST 2013
Hi,
Le lundi 16 septembre 2013 à 11:52:47, Tristan Matthews a écrit :
> +{
> + 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];
memcpy?
> +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) {
Usual coding style give brackets their own line :)
> +
> + block_t *out_block = block_Alloc(1276);
Maybe a const/define or comment to explain this value.
> +
> + // 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) {
use else
> + 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;
what happens to result if block_ChainAppend occured before but this
specific encoding failed?
> + } 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);
Is that necessary if you don't have any options?
> +
> + 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.");
memleak
> + 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));
memleak
> + 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)
memleak
> + 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.");
memleak
> + 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)
memleak
> + 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;
memset?
> + } 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);
what about sys->padding?
> + 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)
> +
You are assuming little endian running host what about a big endian?
(yes it is rare but still).
> +/*
> + 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];
memcpy?
> +
> + 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();
Is that an allocated string? if yes don't you need a free?
> + 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
>
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel
Regards,
--
Denis Charmet - TypX
Le mauvais esprit est un art de vivre
More information about the vlc-devel
mailing list