From af4805700c96f17972ab6d3292404f9248be2b90 Mon Sep 17 00:00:00 2001 From: "Timothy B. Terriberry" Date: Sun, 2 Sep 2012 07:43:41 -0700 Subject: [PATCH] Opus decoder. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This patch uses the information passed by the demuxer in block_t's i_nb_samples and i_length to properly handle pre-skip, seeking pre-roll, and end-trim. Multi-channel decoding should work. It also adds .opus to the list of supported formats. Signed-off-by: Rafaël Carré --- NEWS | 1 + configure.ac | 7 +- doc/vlc.1 | 2 +- extras/package/win32/helpers/extensions.nsh | 3 +- include/vlc_interface.h | 1 + modules/LIST | 1 + modules/codec/Modules.am | 1 + modules/codec/opus.c | 435 ++++++++++++++++++ modules/codec/opus_header.c | 268 +++++++++++ modules/codec/opus_header.h | 49 ++ .../gui/qt4/components/simple_preferences.cpp | 4 +- src/input/demux.c | 1 + src/misc/mime.c | 1 + 13 files changed, 769 insertions(+), 5 deletions(-) create mode 100644 modules/codec/opus.c create mode 100644 modules/codec/opus_header.c create mode 100644 modules/codec/opus_header.h diff --git a/NEWS b/NEWS index 671097a1f5..b5b419f85e 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,7 @@ Important changes for packagers: * /extras/contrib has been replaced by a better system in /contrib Codecs: + * Support for OPUS via libopus. * Support for CDXL, Ut Video, VBLE, Dxtory codecs via libavcodec. * Numerous improvements in OpenMAX IL codec * Support for Ulead DV audio diff --git a/configure.ac b/configure.ac index 8169fe821e..5384caa0e8 100644 --- a/configure.ac +++ b/configure.ac @@ -617,7 +617,7 @@ AC_CHECK_FUNC(getopt_long,, [ AC_SUBST(GNUGETOPT_LIBS) AC_CHECK_LIB(m,cos,[ - VLC_ADD_LIBS([adjust wave ripple psychedelic gradient a52tofloat32 dtstofloat32 x264 goom visual panoramix rotate noise grain scene kate flac lua chorus_flanger freetype avcodec avformat access_avio swscale postproc i420_rgb faad twolame equalizer spatializer param_eq samplerate freetype mpc dmo mp4 quicktime qt4 compressor headphone_channel_mixer normvol audiobargraph_a speex mono colorthres extract ball access_imem hotkeys mosaic gaussianblur dbus x26410b hqdn3d anaglyph oldrc ncurses],[-lm]) + VLC_ADD_LIBS([adjust wave ripple psychedelic gradient a52tofloat32 dtstofloat32 x264 goom visual panoramix rotate noise grain scene kate flac lua chorus_flanger freetype avcodec avformat access_avio swscale postproc i420_rgb faad twolame equalizer spatializer param_eq samplerate freetype mpc dmo mp4 quicktime qt4 compressor headphone_channel_mixer normvol audiobargraph_a speex opus mono colorthres extract ball access_imem hotkeys mosaic gaussianblur dbus x26410b hqdn3d anaglyph oldrc ncurses],[-lm]) LIBM="-lm" ], [ LIBM="" @@ -2711,6 +2711,11 @@ AS_IF([test "${enable_speex}" != "no"], [ ]) AM_CONDITIONAL([HAVE_SPEEXDSP], [test "$have_speexdsp" = "yes"]) +dnl +dnl Opus plugin +dnl +PKG_ENABLE_MODULES_VLC([OPUS], [], [ogg opus], [Opus support], [auto]) + dnl dnl theora decoder plugin dnl diff --git a/doc/vlc.1 b/doc/vlc.1 index 3ac190b4ca..63247700b7 100644 --- a/doc/vlc.1 +++ b/doc/vlc.1 @@ -42,7 +42,7 @@ complete list of available options. .B VLC recognizes several URL-style items: .TP -.B *.mpg, *.vob, *.avi, *.mp3, *.ogg +.B *.mpg, *.vob, *.avi, *.mp3, *.ogg, *.opus Various multimedia file formats .TP .B dvd://[][@][#[][:[<chapter>][:<angle>]]] diff --git a/extras/package/win32/helpers/extensions.nsh b/extras/package/win32/helpers/extensions.nsh index 48116e9aba..324f1ab4aa 100644 --- a/extras/package/win32/helpers/extensions.nsh +++ b/extras/package/win32/helpers/extensions.nsh @@ -142,8 +142,9 @@ FunctionEnd !insertmacro ${_action} ".mp3" !insertmacro ${_action} ".mpc" !insertmacro ${_action} ".mpga" - !insertmacro ${_action} ".oma" !insertmacro ${_action} ".oga" + !insertmacro ${_action} ".oma" + !insertmacro ${_action} ".opus" !insertmacro ${_action} ".qcp" !insertmacro ${_action} ".ra" !insertmacro ${_action} ".rmi" diff --git a/include/vlc_interface.h b/include/vlc_interface.h index fca1c9f3d0..b70ccbdc81 100644 --- a/include/vlc_interface.h +++ b/include/vlc_interface.h @@ -228,6 +228,7 @@ typedef enum vlc_dialog { "*.oga;" \ "*.ogg;" \ "*.oma;" \ + "*.opus;" \ "*.qcp;" \ "*.ra;" \ "*.rmi;" \ diff --git a/modules/LIST b/modules/LIST index bc3075187f..52b34c3344 100644 --- a/modules/LIST +++ b/modules/LIST @@ -226,6 +226,7 @@ $Id$ * opencv_example: OpenCV example (face identification) * opencv_wrapper: OpenCV wrapper video filter * opensles_android: OpenSL ES audio output for Android + * opus: a opus audio decoder/packetizer using the libopus library * os2drive: service discovery for OS/2 drives * osd_parser: OSD import module * osdmenu: video_filter for displaying and streaming a On Screen Display menu diff --git a/modules/codec/Modules.am b/modules/codec/Modules.am index 6f0fddd086..7f7ffca986 100644 --- a/modules/codec/Modules.am +++ b/modules/codec/Modules.am @@ -9,6 +9,7 @@ SOURCES_theora = theora.c SOURCES_tremor = vorbis.c SOURCES_speex = speex.c SOURCES_adpcm = adpcm.c +SOURCES_opus = opus.c opus_header.c opus_header.h SOURCES_uleaddvaudio = uleaddvaudio.c SOURCES_mpeg_audio = mpeg_audio.c SOURCES_libmpeg2 = libmpeg2.c diff --git a/modules/codec/opus.c b/modules/codec/opus.c new file mode 100644 index 0000000000..d9d32570cf --- /dev/null +++ b/modules/codec/opus.c @@ -0,0 +1,435 @@ +/***************************************************************************** + * opus.c: opus decoder/encoder module making use of libopus. + ***************************************************************************** + * Copyright (C) 2003-2009, 2012 the VideoLAN team + * + * Authors: Gregory Maxwell <greg@xiph.org> + * Based on speex.c by: Gildas Bazin <gbazin@videolan.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/* + * TODO: preskip, trimming, file duration + */ + +/***************************************************************************** + * Preamble + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <vlc_common.h> +#include <vlc_plugin.h> +#include <vlc_input.h> +#include <vlc_codec.h> +#include <vlc_aout.h> +#include "../demux/xiph.h" + +#include <ogg/ogg.h> +#include <opus.h> +#include <opus_multistream.h> + +#include "opus_header.h" + +#ifndef OPUS_SET_GAIN +#include <math.h> +#endif + +/***************************************************************************** + * Module descriptor + *****************************************************************************/ +static int OpenDecoder ( vlc_object_t * ); +static void CloseDecoder ( vlc_object_t * ); + +vlc_module_begin () + set_category( CAT_INPUT ) + set_subcategory( SUBCAT_INPUT_ACODEC ) + + set_description( N_("Opus audio decoder") ) + set_capability( "decoder", 100 ) + set_shortname( N_("Opus") ) + set_callbacks( OpenDecoder, CloseDecoder ) + +vlc_module_end () + +/***************************************************************************** + * decoder_sys_t : opus decoder descriptor + *****************************************************************************/ +struct decoder_sys_t +{ + /* + * Input properties + */ + bool b_has_headers; + + /* + * Opus properties + */ + OpusHeader header; + OpusMSDecoder *p_st; + + /* + * Common properties + */ + date_t end_date; +}; + +static const int pi_channels_maps[9] = +{ + 0, + AOUT_CHAN_CENTER, + AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT, + AOUT_CHAN_CENTER | AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT, + AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_REARLEFT + | AOUT_CHAN_REARRIGHT, + AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER + | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT, + AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER + | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT | AOUT_CHAN_LFE, + AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER + | AOUT_CHAN_REARCENTER | AOUT_CHAN_MIDDLELEFT + | AOUT_CHAN_MIDDLERIGHT | AOUT_CHAN_LFE, + AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER | AOUT_CHAN_REARLEFT + | AOUT_CHAN_REARRIGHT | AOUT_CHAN_MIDDLELEFT | AOUT_CHAN_MIDDLERIGHT + | AOUT_CHAN_LFE, +}; + +/* +** channel order as defined in http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9 +*/ + +/* recommended vorbis channel order for 8 channels */ +static const uint32_t pi_8channels_in[] = +{ AOUT_CHAN_LEFT, AOUT_CHAN_CENTER, AOUT_CHAN_RIGHT, + AOUT_CHAN_MIDDLELEFT, AOUT_CHAN_MIDDLERIGHT, + AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT,AOUT_CHAN_LFE, 0 }; + +/* recommended vorbis channel order for 7 channels */ +static const uint32_t pi_7channels_in[] = +{ AOUT_CHAN_LEFT, AOUT_CHAN_CENTER, AOUT_CHAN_RIGHT, + AOUT_CHAN_MIDDLELEFT, AOUT_CHAN_MIDDLERIGHT, + AOUT_CHAN_REARCENTER, AOUT_CHAN_LFE, 0 }; + +/* recommended vorbis channel order for 6 channels */ +static const uint32_t pi_6channels_in[] = +{ AOUT_CHAN_LEFT, AOUT_CHAN_CENTER, AOUT_CHAN_RIGHT, + AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT, AOUT_CHAN_LFE, 0 }; + +/* recommended vorbis channel order for 5 channels */ +static const uint32_t pi_5channels_in[] = +{ AOUT_CHAN_LEFT, AOUT_CHAN_CENTER, AOUT_CHAN_RIGHT, + AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT, 0 }; + +/* recommended vorbis channel order for 4 channels */ +static const uint32_t pi_4channels_in[] = +{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT, 0 }; + +/* recommended vorbis channel order for 3 channels */ +static const uint32_t pi_3channels_in[] = +{ AOUT_CHAN_LEFT, AOUT_CHAN_CENTER, AOUT_CHAN_RIGHT, 0 }; + +/**************************************************************************** + * Local prototypes + ****************************************************************************/ + +static block_t *DecodeBlock ( decoder_t *, block_t ** ); +static int ProcessHeaders( decoder_t * ); +static int ProcessInitialHeader ( decoder_t *, ogg_packet * ); +static void *ProcessPacket( decoder_t *, ogg_packet *, block_t ** ); + +static block_t *DecodePacket( decoder_t *, ogg_packet *, int, int ); + +/***************************************************************************** + * OpenDecoder: probe the decoder and return score + *****************************************************************************/ +static int OpenDecoder( vlc_object_t *p_this ) +{ + decoder_t *p_dec = (decoder_t*)p_this; + decoder_sys_t *p_sys; + + if( p_dec->fmt_in.i_codec != VLC_CODEC_OPUS ) + return VLC_EGENERIC; + + /* Allocate the memory needed to store the decoder's structure */ + if( ( p_dec->p_sys = p_sys = malloc(sizeof(decoder_sys_t)) ) == NULL ) + return VLC_ENOMEM; + p_dec->p_sys->b_has_headers = false; + + date_Set( &p_sys->end_date, 0 ); + + /* Set output properties */ + p_dec->fmt_out.i_cat = AUDIO_ES; + p_dec->fmt_out.i_codec = VLC_CODEC_FL32; + + p_dec->pf_decode_audio = DecodeBlock; + p_dec->pf_packetize = DecodeBlock; + + p_sys->p_st = NULL; + + return VLC_SUCCESS; +} + +/**************************************************************************** + * DecodeBlock: the whole thing + **************************************************************************** + * This function must be fed with ogg packets. + ****************************************************************************/ +static block_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block ) +{ + decoder_sys_t *p_sys = p_dec->p_sys; + ogg_packet oggpacket; + + if( !pp_block || !*pp_block) + return NULL; + + /* Block to Ogg packet */ + oggpacket.packet = (*pp_block)->p_buffer; + oggpacket.bytes = (*pp_block)->i_buffer; + + oggpacket.granulepos = -1; + oggpacket.b_o_s = 0; + oggpacket.e_o_s = 0; + oggpacket.packetno = 0; + + /* Check for headers */ + if( !p_sys->b_has_headers ) + { + if( ProcessHeaders( p_dec ) ) + { + block_Release( *pp_block ); + return NULL; + } + p_sys->b_has_headers = true; + } + + return ProcessPacket( p_dec, &oggpacket, pp_block ); +} + +/***************************************************************************** + * ProcessHeaders: process Opus headers. + *****************************************************************************/ +static int ProcessHeaders( decoder_t *p_dec ) +{ + ogg_packet oggpacket; + + unsigned pi_size[XIPH_MAX_HEADER_COUNT]; + void *pp_data[XIPH_MAX_HEADER_COUNT]; + unsigned i_count; + int ret = VLC_EGENERIC; + + if( xiph_SplitHeaders( pi_size, pp_data, &i_count, + p_dec->fmt_in.i_extra, p_dec->fmt_in.p_extra) ) + return VLC_EGENERIC; + if( i_count < 2 ) + goto end; + + oggpacket.granulepos = -1; + oggpacket.e_o_s = 0; + oggpacket.packetno = 0; + + /* Take care of the initial Opus header */ + oggpacket.b_o_s = 1; /* yes this actually is a b_o_s packet :) */ + oggpacket.bytes = pi_size[0]; + oggpacket.packet = pp_data[0]; + ret = ProcessInitialHeader( p_dec, &oggpacket ); + + if (ret != VLC_SUCCESS) + msg_Err( p_dec, "initial Opus header is corrupted" ); + +end: + for( unsigned i = 0; i < i_count; i++ ) + free( pp_data[i] ); + + return ret; +} + +/***************************************************************************** + * ProcessInitialHeader: processes the inital Opus header packet. + *****************************************************************************/ +static int ProcessInitialHeader( decoder_t *p_dec, ogg_packet *p_oggpacket ) +{ + int err; + int pi_chan_table[AOUT_CHAN_MAX]; + unsigned char new_stream_map[8]; + decoder_sys_t *p_sys = p_dec->p_sys; + + OpusHeader *p_header = &p_sys->header; + + if( !opus_header_parse((unsigned char *)p_oggpacket->packet,p_oggpacket->bytes,p_header) ) + { + msg_Err( p_dec, "cannot read Opus header" ); + return VLC_EGENERIC; + } + msg_Dbg( p_dec, "Opus audio with %d channels", p_header->channels); + + if((p_header->channels>2 && p_header->channel_mapping==0) || + (p_header->channels>8 && p_header->channel_mapping==1) || + p_header->channel_mapping>1) + { + msg_Err( p_dec, "Unsupported channel mapping" ); + return VLC_EGENERIC; + } + + /* Setup the format */ + p_dec->fmt_out.audio.i_physical_channels = + p_dec->fmt_out.audio.i_original_channels = + pi_channels_maps[p_header->channels]; + p_dec->fmt_out.audio.i_channels = p_header->channels; + p_dec->fmt_out.audio.i_rate = 48000; + + if( p_header->channels>2 ) + { + static const uint32_t *pi_ch[6] = { pi_3channels_in, pi_4channels_in, + pi_5channels_in, pi_6channels_in, + pi_7channels_in, pi_8channels_in }; + aout_CheckChannelReorder( pi_ch[p_header->channels-3], NULL, + p_dec->fmt_out.audio.i_physical_channels, + p_header->channels, + pi_chan_table ); + for(int i=0;i<p_header->channels;i++) + new_stream_map[pi_chan_table[i]]=p_header->stream_map[i]; + } + /* Opus decoder init */ + p_sys->p_st = opus_multistream_decoder_create( 48000, p_header->channels, + p_header->nb_streams, p_header->nb_coupled, + p_header->channels>2?new_stream_map:p_header->stream_map, + &err ); + if( !p_sys->p_st || err!=OPUS_OK ) + { + msg_Err( p_dec, "decoder initialization failed" ); + return VLC_EGENERIC; + } + +#ifdef OPUS_SET_GAIN + if( opus_multistream_decoder_ctl( p_sys->p_st,OPUS_SET_GAIN(p_header->gain) ) != OPUS_OK ) + { + msg_Err( p_dec, "OPUS_SET_GAIN failed" ); + opus_multistream_decoder_destroy( p_sys->p_st ); + return VLC_EGENERIC; + } +#endif + + date_Init( &p_sys->end_date, 48000, 1 ); + + return VLC_SUCCESS; +} + +/***************************************************************************** + * ProcessPacket: processes a Opus packet. + *****************************************************************************/ +static void *ProcessPacket( decoder_t *p_dec, ogg_packet *p_oggpacket, + block_t **pp_block ) +{ + decoder_sys_t *p_sys = p_dec->p_sys; + block_t *p_block = *pp_block; + + /* Date management */ + if( p_block && p_block->i_pts > VLC_TS_INVALID && + p_block->i_pts != date_Get( &p_sys->end_date ) ) + { + date_Set( &p_sys->end_date, p_block->i_pts ); + } + + if( !date_Get( &p_sys->end_date ) ) + { + /* We've just started the stream, wait for the first PTS. */ + if( p_block ) block_Release( p_block ); + return NULL; + } + + *pp_block = NULL; /* To avoid being fed the same packet again */ + + { + block_t *p_aout_buffer = DecodePacket( p_dec, p_oggpacket, + p_block->i_nb_samples, + (int)p_block->i_length ); + + block_Release( p_block ); + return p_aout_buffer; + } +} + +/***************************************************************************** + * DecodePacket: decodes a Opus packet. + *****************************************************************************/ +static block_t *DecodePacket( decoder_t *p_dec, ogg_packet *p_oggpacket, + int i_nb_samples, int i_end_trim ) +{ + decoder_sys_t *p_sys = p_dec->p_sys; + + if( !p_oggpacket->bytes ) + return NULL; + + int spp; + spp=opus_packet_get_nb_frames(p_oggpacket->packet,p_oggpacket->bytes); + if(spp>0)spp*=opus_packet_get_samples_per_frame(p_oggpacket->packet,48000); + if(spp<120||spp>120*48)return NULL; + + block_t *p_aout_buffer=decoder_NewAudioBuffer( p_dec, spp ); + if ( !p_aout_buffer ) + { + msg_Err(p_dec, "Oops: No new buffer was returned!"); + return NULL; + } + + spp=opus_multistream_decode_float(p_sys->p_st, p_oggpacket->packet, + p_oggpacket->bytes, (float *)p_aout_buffer->p_buffer, spp, 0); + if( spp < 0 || i_nb_samples <= 0 || i_end_trim >= i_nb_samples) + { + block_Release(p_aout_buffer); + if( spp < 0 ) + msg_Err( p_dec, "Error: corrupted stream?" ); + return NULL; + } + if( spp > i_nb_samples ) + { + memmove(p_aout_buffer->p_buffer, + p_aout_buffer->p_buffer + + (spp - i_nb_samples)*p_sys->header.channels*sizeof(float), + (i_nb_samples - i_end_trim)*p_sys->header.channels*sizeof(float)); + } + i_nb_samples -= i_end_trim; + +#ifndef OPUS_SET_GAIN + if(p_sys->header.gain!=0) + { + float gain = pow(10., p_sys->header.gain/5120.); + float *buf =(float *)p_aout_buffer->p_buffer; + int i; + for( i = 0; i < i_nb_samples*p_sys->header.channels; i++) + buf[i] *= gain; + } +#endif + p_aout_buffer->i_nb_samples = i_nb_samples; + p_aout_buffer->i_pts = date_Get( &p_sys->end_date ); + p_aout_buffer->i_length = date_Increment( &p_sys->end_date, i_nb_samples ) + - p_aout_buffer->i_pts; + return p_aout_buffer; +} + +/***************************************************************************** + * CloseDecoder: Opus decoder destruction + *****************************************************************************/ +static void CloseDecoder( vlc_object_t *p_this ) +{ + decoder_t * p_dec = (decoder_t *)p_this; + decoder_sys_t *p_sys = p_dec->p_sys; + + if( p_sys->p_st ) opus_multistream_decoder_destroy(p_sys->p_st); + + free( p_sys ); +} diff --git a/modules/codec/opus_header.c b/modules/codec/opus_header.c new file mode 100644 index 0000000000..cb5fb9345b --- /dev/null +++ b/modules/codec/opus_header.c @@ -0,0 +1,268 @@ +/* Copyright (C)2012 Xiph.Org Foundation + File: opus_header.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 FOUNDATION 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 + +#include "opus_header.h" +#include <string.h> +#include <stdio.h> + +/* Header contents: + - "OpusHead" (64 bits) + - version number (8 bits) + - Channels C (8 bits) + - Pre-skip (16 bits) + - Sampling rate (32 bits) + - Gain in dB (16 bits, S7.8) + - Mapping (8 bits, 0=single stream (mono/stereo) 1=Vorbis mapping, + 2..254: reserved, 255: multistream with no mapping) + + - if (mapping != 0) + - N = totel number of streams (8 bits) + - M = number of paired streams (8 bits) + - C times channel origin + - if (C<2*M) + - stream = byte/2 + - if (byte&0x1 == 0) + - left + else + - right + - else + - stream = byte-M +*/ + +typedef struct { + unsigned char *data; + int maxlen; + int pos; +} Packet; + +typedef struct { + const unsigned char *data; + int maxlen; + int pos; +} ROPacket; + +static int write_uint32(Packet *p, ogg_uint32_t val) +{ + if (p->pos>p->maxlen-4) + return 0; + p->data[p->pos ] = (val ) & 0xFF; + p->data[p->pos+1] = (val>> 8) & 0xFF; + p->data[p->pos+2] = (val>>16) & 0xFF; + p->data[p->pos+3] = (val>>24) & 0xFF; + p->pos += 4; + return 1; +} + +static int write_uint16(Packet *p, ogg_uint16_t val) +{ + if (p->pos>p->maxlen-2) + return 0; + p->data[p->pos ] = (val ) & 0xFF; + p->data[p->pos+1] = (val>> 8) & 0xFF; + p->pos += 2; + return 1; +} + +static int write_chars(Packet *p, const unsigned char *str, int nb_chars) +{ + if (p->pos>p->maxlen-nb_chars) + return 0; + for (int i=0;i<nb_chars;i++) + p->data[p->pos++] = str[i]; + return 1; +} + +static int read_uint32(ROPacket *p, ogg_uint32_t *val) +{ + if (p->pos>p->maxlen-4) + return 0; + *val = (ogg_uint32_t)p->data[p->pos ]; + *val |= (ogg_uint32_t)p->data[p->pos+1]<< 8; + *val |= (ogg_uint32_t)p->data[p->pos+2]<<16; + *val |= (ogg_uint32_t)p->data[p->pos+3]<<24; + p->pos += 4; + return 1; +} + +static int read_uint16(ROPacket *p, ogg_uint16_t *val) +{ + if (p->pos>p->maxlen-2) + return 0; + *val = (ogg_uint16_t)p->data[p->pos ]; + *val |= (ogg_uint16_t)p->data[p->pos+1]<<8; + p->pos += 2; + return 1; +} + +static int read_chars(ROPacket *p, unsigned char *str, int nb_chars) +{ + if (p->pos>p->maxlen-nb_chars) + return 0; + for (int i=0;i<nb_chars;i++) + str[i] = p->data[p->pos++]; + return 1; +} + +int opus_header_parse(const unsigned char *packet, int len, OpusHeader *h) +{ + char str[9]; + ROPacket p; + unsigned char ch; + ogg_uint16_t shortval; + + p.data = packet; + p.maxlen = len; + p.pos = 0; + str[8] = 0; + if (len<19)return 0; + read_chars(&p, (unsigned char*)str, 8); + if (memcmp(str, "OpusHead", 8)!=0) + return 0; + + if (!read_chars(&p, &ch, 1)) + return 0; + h->version = ch; + if((h->version&240) != 0) /* Only major version 0 supported. */ + return 0; + + if (!read_chars(&p, &ch, 1)) + return 0; + h->channels = ch; + if (h->channels == 0) + return 0; + + if (!read_uint16(&p, &shortval)) + return 0; + h->preskip = shortval; + + if (!read_uint32(&p, &h->input_sample_rate)) + return 0; + + if (!read_uint16(&p, &shortval)) + return 0; + h->gain = (short)shortval; + + if (!read_chars(&p, &ch, 1)) + return 0; + h->channel_mapping = ch; + + if (h->channel_mapping != 0) + { + if (!read_chars(&p, &ch, 1)) + return 0; + + if (ch<1) + return 0; + h->nb_streams = ch; + + if (!read_chars(&p, &ch, 1)) + return 0; + + if (ch>h->nb_streams || (ch+h->nb_streams)>255) + return 0; + h->nb_coupled = ch; + + /* Multi-stream support */ + for (int i=0;i<h->channels;i++) + { + if (!read_chars(&p, &h->stream_map[i], 1)) + return 0; + if (h->stream_map[i]>(h->nb_streams+h->nb_coupled) && h->stream_map[i]!=255) + return 0; + } + } else { + if(h->channels>2) + return 0; + h->nb_streams = 1; + h->nb_coupled = h->channels>1; + h->stream_map[0]=0; + h->stream_map[1]=1; + } + /*For version 0/1 we know there won't be any more data + so reject any that have data past the end.*/ + if ((h->version==0 || h->version==1) && p.pos != len) + return 0; + return 1; +} + +int opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len) +{ + Packet p; + unsigned char ch; + + p.data = packet; + p.maxlen = len; + p.pos = 0; + if (len<19)return 0; + if (!write_chars(&p, (const unsigned char*)"OpusHead", 8)) + return 0; + /* Version is 1 */ + ch = 1; + if (!write_chars(&p, &ch, 1)) + return 0; + + ch = h->channels; + if (!write_chars(&p, &ch, 1)) + return 0; + + if (!write_uint16(&p, h->preskip)) + return 0; + + if (!write_uint32(&p, h->input_sample_rate)) + return 0; + + if (!write_uint16(&p, h->gain)) + return 0; + + ch = h->channel_mapping; + if (!write_chars(&p, &ch, 1)) + return 0; + + if (h->channel_mapping != 0) + { + ch = h->nb_streams; + if (!write_chars(&p, &ch, 1)) + return 0; + + ch = h->nb_coupled; + if (!write_chars(&p, &ch, 1)) + return 0; + + /* Multi-stream support */ + for (int i=0;i<h->channels;i++) + { + if (!write_chars(&p, &h->stream_map[i], 1)) + return 0; + } + } + + return p.pos; +} diff --git a/modules/codec/opus_header.h b/modules/codec/opus_header.h new file mode 100644 index 0000000000..633c2f49a7 --- /dev/null +++ b/modules/codec/opus_header.h @@ -0,0 +1,49 @@ +/* Copyright (C)2012 Xiph.Org Foundation + File: opus_header.h + + 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 FOUNDATION 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. +*/ + +#ifndef OPUS_HEADER_H +#define OPUS_HEADER_H + +#include <ogg/ogg.h> + +typedef struct { + int version; + int channels; /* Number of channels: 1..255 */ + int preskip; + ogg_uint32_t input_sample_rate; + int gain; /* in dB S7.8 should be zero whenever possible */ + int channel_mapping; + /* The rest is only used if channel_mapping != 0 */ + int nb_streams; + int nb_coupled; + unsigned char stream_map[255]; +} OpusHeader; + +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); + +#endif diff --git a/modules/gui/qt4/components/simple_preferences.cpp b/modules/gui/qt4/components/simple_preferences.cpp index 741344f1a8..e3d36e3d55 100644 --- a/modules/gui/qt4/components/simple_preferences.cpp +++ b/modules/gui/qt4/components/simple_preferences.cpp @@ -978,8 +978,8 @@ void SPrefsPanel::assoDialog() aTa( ".a52" ); aTa( ".aac" ); aTa( ".ac3" ); aTa( ".dts" ); aTa( ".flac" ); aTa( ".m4a" ); aTa( ".m4p" ); aTa( ".mka" ); aTa( ".mod" ); aTa( ".mp1" ); - aTa( ".mp2" ); aTa( ".mp3" ); aTa( ".oma" ); aTa( ".oga" ); aTa( ".spx" ); - aTa( ".tta" ); aTa( ".wav" ); aTa( ".wma" ); aTa( ".xm" ); + aTa( ".mp2" ); aTa( ".mp3" ); aTa( ".oma" ); aTa( ".oga" ); aTa( ".opus" ); + aTa( ".spx" ); aTa( ".tta" ); aTa( ".wav" ); aTa( ".wma" ); aTa( ".xm" ); audioType->setCheckState( 0, ( i_temp > 0 ) ? ( ( i_temp == audioType->childCount() ) ? Qt::Checked : Qt::PartiallyChecked ) diff --git a/src/input/demux.c b/src/input/demux.c index e01b1a3b45..e44df63137 100644 --- a/src/input/demux.c +++ b/src/input/demux.c @@ -132,6 +132,7 @@ demux_t *demux_New( vlc_object_t *p_obj, input_thread_t *p_parent_input, { "ogg", "ogg" }, { "ogm", "ogg" }, /* legacy Ogg */ { "oga", "ogg" }, { "spx", "ogg" }, { "ogv", "ogg" }, { "ogx", "ogg" }, /*RFC5334*/ + { "opus", "ogg" }, /*draft-terriberry-oggopus-01*/ { "pva", "pva" }, { "rm", "avformat" }, { "m4v", "m4v" }, diff --git a/src/misc/mime.c b/src/misc/mime.c index e56a7302df..2763e9bb67 100644 --- a/src/misc/mime.c +++ b/src/misc/mime.c @@ -74,6 +74,7 @@ static const struct { ".ogm", "application/ogg" }, { ".ogv", "video/ogg" }, { ".ogx", "application/ogg" }, + { ".opus", "audio/ogg; codecs=opus" }, { ".spx", "audio/ogg" }, { ".wav", "audio/wav" }, { ".wma", "audio/x-ms-wma" }, -- 2.39.2