diff --git a/configure.ac b/configure.ac index 800d3b8..0ba91ce 100644 --- a/configure.ac +++ b/configure.ac @@ -159,16 +159,26 @@ then if test "x$ac_cv_have_decl_OV_ECTL_COUPLING_SET" = "xno" then AC_MSG_ERROR([Vorbis >= 1.3.0 required !]) HAVE_VORBIS=no fi fi AM_CONDITIONAL(HAVE_OV_READ_FILTER, test "x$have_ov_read_filter" = "xyes") +if test "x$HAVE_PKG_CONFIG" = "xyes" +then + PKG_CHECK_MODULES(OPUSFILE, opusfile >= 0.2, HAVE_LIBOPUSFILE=yes, HAVE_LIBOPUSFILE=no) + AC_SUBST(OPUSFILE_LIBS) + if test "x$HAVE_LIBOPUSFILE" = xyes; then + AC_DEFINE(HAVE_LIBOPUSFILE, 1, [Defined if we have libopusfile]) + fi +fi +AM_CONDITIONAL(HAVE_LIBOPUSFILE, test "x$HAVE_LIBOPUSFILE" = "xyes") + SHARE_LIBS='$(top_builddir)/share/libutf8.a $(top_builddir)/share/libgetopt.a' SHARE_CFLAGS='-I$(top_srcdir)/include' I18N_CFLAGS='-I$(top_srcdir)/intl' I18N_LIBS=$INTLLIBS SOCKET_LIBS= diff --git a/ogg123/Makefile.am b/ogg123/Makefile.am index f7c5b46..df39c91 100644 --- a/ogg123/Makefile.am +++ b/ogg123/Makefile.am @@ -4,50 +4,58 @@ flac_sources = flac_format.c easyflac.c easyflac.h else flac_sources = endif if HAVE_LIBSPEEX speex_sources = speex_format.c else speex_sources = endif +if HAVE_LIBOPUSFILE +opus_sources = opus_format.c +else +opus_sources = +endif + if HAVE_OV_READ_FILTER vgfilter_sources = vgfilter.c vgfilter.h else vgfilter_sources = endif datadir = @datadir@ localedir = $(datadir)/locale DEFS = -DSYSCONFDIR=\"$(sysconfdir)\" -DLOCALEDIR=\"$(localedir)\" @DEFS@ docdir = $(datadir)/doc/$(PACKAGE)-$(VERSION) mandir = @MANDIR@ bin_PROGRAMS = ogg123 -INCLUDES = @OGG_CFLAGS@ @VORBIS_CFLAGS@ @AO_CFLAGS@ @CURL_CFLAGS@ \ +INCLUDES = @OGG_CFLAGS@ @VORBIS_CFLAGS@ @OPUSFILE_CFLAGS@ @AO_CFLAGS@ @CURL_CFLAGS@ \ @PTHREAD_CFLAGS@ @SHARE_CFLAGS@ @I18N_CFLAGS@ ogg123_LDADD = @SHARE_LIBS@ \ @VORBISFILE_LIBS@ @VORBIS_LIBS@ @OGG_LIBS@ @AO_LIBS@ \ @SOCKET_LIBS@ @LIBICONV@ @CURL_LIBS@ @PTHREAD_CFLAGS@ \ - @PTHREAD_LIBS@ @I18N_LIBS@ @FLAC_LIBS@ @SPEEX_LIBS@ + @PTHREAD_LIBS@ @I18N_LIBS@ @FLAC_LIBS@ @SPEEX_LIBS@ \ + @OPUSFILE_LIBS@ ogg123_DEPENDENCIES = @SHARE_LIBS@ ogg123_SOURCES = audio.c buffer.c callbacks.c \ cfgfile_options.c cmdline_options.c \ file_transport.c format.c http_transport.c \ ogg123.c oggvorbis_format.c playlist.c \ status.c remote.c transport.c vorbis_comments.c \ audio.h buffer.h callbacks.h compat.h \ cfgfile_options.h cmdline_options.h \ format.h ogg123.h playlist.h status.h \ transport.h remote.h vorbis_comments.h \ - $(flac_sources) $(speex_sources) $(vgfilter_sources) + $(flac_sources) $(speex_sources) $(vgfilter_sources) \ + $(opus_sources) man_MANS = ogg123.1 doc_DATA = ogg123rc-example EXTRA_ogg123_SOURCES = \ $(man_MANS) $(doc_DATA) debug: diff --git a/ogg123/cmdline_options.c b/ogg123/cmdline_options.c index f3107f9..2194ffc 100644 --- a/ogg123/cmdline_options.c +++ b/ogg123/cmdline_options.c @@ -314,16 +314,20 @@ void cmdline_usage (void) #ifdef HAVE_LIBFLAC printf (_("FLAC, ")); #endif #ifdef HAVE_LIBSPEEX printf (_("Speex, ")); #endif +#ifdef HAVE_LIBOPUSFILE + printf (_("Opus, ")); +#endif + printf (_("Ogg Vorbis.\n\n")); printf (_("Output options\n")); printf (_(" -d dev, --device dev Use output device \"dev\". Available devices:\n")); printf (" "); printf (_("Live:")); for(i = 0, j = 0; i < driver_count; i++) { diff --git a/ogg123/format.c b/ogg123/format.c index 8392afd..6e8c449 100644 --- a/ogg123/format.c +++ b/ogg123/format.c @@ -33,24 +33,32 @@ extern format_t speex_format; extern format_t flac_format; extern format_t oggflac_format; #endif #ifdef HAVE_LIBSPEEX extern format_t speex_format; #endif +#ifdef HAVE_LIBOPUSFILE +extern format_t opus_format; +#endif + + format_t *formats[] = { #ifdef HAVE_LIBFLAC &flac_format, &oggflac_format, #endif #ifdef HAVE_LIBSPEEX &speex_format, #endif +#ifdef HAVE_LIBOPUSFILE + &opus_format, +#endif &oggvorbis_format, NULL }; format_t *get_format_by_name (char *name) { int i = 0; diff --git a/ogg123/opus_format.c b/ogg123/opus_format.c new file mode 100644 index 0000000..98298e9 --- /dev/null +++ b/ogg123/opus_format.c @@ -0,0 +1,379 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS SOURCE IS GOVERNED BY * + * THE GNU PUBLIC LICENSE 2, WHICH IS INCLUDED WITH THIS SOURCE. * + * PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE Ogg123 SOURCE CODE IS (C) COPYRIGHT 2000-2003 * + * by Stan Seibert AND OTHER CONTRIBUTORS * + * http://www.xiph.org/ * + * * + ******************************************************************** + + last mod: $Id: opus_format.c 16825 2010-01-27 04:14:08Z xiphmont $ + + ********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include "transport.h" +#include "format.h" +#include "vorbis_comments.h" +#include "utf8.h" +#include "i18n.h" + +typedef struct opf_private_t { + OggOpusFile *of; + const OpusTags *ot; + const OpusHead *oh; + int current_section; + + int bos; /* At beginning of logical bitstream */ + + decoder_stats_t stats; +} opf_private_t; + +/* Forward declarations */ +format_t opus_format; +OpusFileCallbacks opusfile_callbacks; + + +void print_opus_stream_info (decoder_t *decoder); +void print_opus_comments (const OpusTags *ot, decoder_callbacks_t *cb, + void *callback_arg); + + +/* ----------------------------------------------------------- */ + + +int opf_can_decode (data_source_t *source) +{ + char buf[36]; + int len; + + len = source->transport->peek(source, buf, sizeof(char), 36); + + if (len >= 32 && memcmp(buf, "OggS", 4) == 0 + && memcmp(buf+28, "OpusHead", 8) == 0) /* 3 trailing spaces */ + return 1; + else + return 0; +} + + +decoder_t* opf_init (data_source_t *source, ogg123_options_t *ogg123_opts, + audio_format_t *audio_fmt, + decoder_callbacks_t *callbacks, void *callback_arg) +{ + decoder_t *decoder; + opf_private_t *private; + int ret; + + + /* Allocate data source structures */ + decoder = malloc(sizeof(decoder_t)); + private = malloc(sizeof(opf_private_t)); + + if (decoder != NULL && private != NULL) { + decoder->source = source; + decoder->actual_fmt = decoder->request_fmt = *audio_fmt; + decoder->format = &opus_format; + decoder->callbacks = callbacks; + decoder->callback_arg = callback_arg; + decoder->private = private; + + private->bos = 1; + private->current_section = -1; + + private->stats.total_time = 0.0; + private->stats.current_time = 0.0; + private->stats.instant_bitrate = 0; + private->stats.avg_bitrate = 0; + + } else { + fprintf(stderr, _("ERROR: Out of memory.\n")); + exit(1); + } + + /* Initialize opusfile decoder */ + + private->of = op_open_callbacks (decoder, &opusfile_callbacks, NULL, 0, &ret); + + if (private->of == NULL) { + free(private); +/* free(source); nope. caller frees. */ + return NULL; + } + + return decoder; +} + + +int opf_read (decoder_t *decoder, void *ptr, int nbytes, int *eos, + audio_format_t *audio_fmt) +{ + opf_private_t *priv = decoder->private; + decoder_callbacks_t *cb = decoder->callbacks; + int bytes_read = 0; + int ret; + int old_section; + + /* Read comments and audio info at the start of a logical bitstream */ + if (priv->bos) { + priv->ot = op_tags(priv->of, -1); + priv->oh = op_head(priv->of, -1); + + decoder->actual_fmt.channels = priv->oh->channel_count; + decoder->actual_fmt.rate = 48000; + + switch(decoder->actual_fmt.channels){ + case 1: + decoder->actual_fmt.matrix="M"; + break; + case 2: + decoder->actual_fmt.matrix="L,R"; + break; + case 3: + decoder->actual_fmt.matrix="L,C,R"; + break; + case 4: + decoder->actual_fmt.matrix="L,R,BL,BR"; + break; + case 5: + decoder->actual_fmt.matrix="L,C,R,BL,BR"; + break; + case 6: + decoder->actual_fmt.matrix="L,C,R,BL,BR,LFE"; + break; + case 7: + decoder->actual_fmt.matrix="L,C,R,SL,SR,BC,LFE"; + break; + case 8: + decoder->actual_fmt.matrix="L,C,R,SL,SR,BL,BR,LFE"; + break; + default: + decoder->actual_fmt.matrix=NULL; + break; + } + + + print_opus_stream_info(decoder); + print_opus_comments(priv->ot, cb, decoder->callback_arg); + priv->bos = 0; + } + + *audio_fmt = decoder->actual_fmt; + + /* Attempt to read as much audio as is requested */ + while (nbytes >= audio_fmt->word_size * audio_fmt->channels) { + + old_section = priv->current_section; + ret = op_read(priv->of, ptr, nbytes/2, NULL); + + if (ret == 0) { + + /* EOF */ + *eos = 1; + break; + + } else if (ret == OP_HOLE) { + + if (cb->printf_error != NULL) + cb->printf_error(decoder->callback_arg, INFO, + _("--- Hole in the stream; probably harmless\n")); + + } else if (ret < 0) { + + if (cb->printf_error != NULL) + cb->printf_error(decoder->callback_arg, ERROR, + _("=== Vorbis library reported a stream error.\n")); + + /* EOF */ + *eos = 1; + break; + } else { + + bytes_read += ret*2*audio_fmt->channels; + ptr = (void *)((unsigned char *)ptr + ret*2*audio_fmt->channels); + nbytes -= ret*2*audio_fmt->channels; + + /* did we enter a new logical bitstream? */ + if (old_section != priv->current_section && old_section != -1) { + + *eos = 1; + priv->bos = 1; /* Read new headers next time through */ + break; + } + } + + } + + return bytes_read; +} + + +int opf_seek (decoder_t *decoder, double offset, int whence) +{ + opf_private_t *priv = decoder->private; + int ret; + int cur; + int samples = offset * 48000; + + if (whence == DECODER_SEEK_CUR) { + cur = op_pcm_tell(priv->of); + if (cur >= 0) + samples += cur; + else + return 0; + } + + ret = op_pcm_seek(priv->of, samples); + if (ret == 0) + return 1; + else + return 0; +} + + +decoder_stats_t *opf_statistics (decoder_t *decoder) +{ + opf_private_t *priv = decoder->private; + long instant_bitrate; + long avg_bitrate; + + /* ov_time_tell() doesn't work on non-seekable streams, so we use + ov_pcm_tell() */ + priv->stats.total_time = (double) op_pcm_total(priv->of, -1) / + (double) decoder->actual_fmt.rate; + priv->stats.current_time = (double) op_pcm_tell(priv->of) / + (double) decoder->actual_fmt.rate; + + /* opusfile returns 0 when no bitrate change has occurred */ + instant_bitrate = op_bitrate_instant(priv->of); + if (instant_bitrate > 0) + priv->stats.instant_bitrate = instant_bitrate; + + avg_bitrate = op_bitrate(priv->of, priv->current_section); + /* Catch error case caused by non-seekable stream */ + priv->stats.avg_bitrate = avg_bitrate > 0 ? avg_bitrate : 0; + + + return malloc_decoder_stats(&priv->stats); +} + + +void opf_cleanup (decoder_t *decoder) +{ + opf_private_t *priv = decoder->private; + + op_free(priv->of); + + free(decoder->private); + free(decoder); +} + + +format_t opus_format = { + "oggopus", + &opf_can_decode, + &opf_init, + &opf_read, + &opf_seek, + &opf_statistics, + &opf_cleanup, +}; + + +/* ------------------- Opusfile Callbacks ----------------- */ + +int opusfile_cb_read (void *stream, unsigned char *ptr, int nbytes) +{ + decoder_t *decoder = stream; + + return decoder->source->transport->read(decoder->source, ptr, 1, nbytes); +} + +int opusfile_cb_seek (void *arg, opus_int64 offset, int whence) +{ + decoder_t *decoder = arg; + + return decoder->source->transport->seek(decoder->source, offset, whence); +} + +int opusfile_cb_close (void *arg) +{ + return 1; /* Ignore close request so transport can be closed later */ +} + +opus_int64 opusfile_cb_tell (void *arg) +{ + decoder_t *decoder = arg; + + return decoder->source->transport->tell(decoder->source); +} + + +OpusFileCallbacks opusfile_callbacks = { + &opusfile_cb_read, + &opusfile_cb_seek, + &opusfile_cb_tell, + &opusfile_cb_close +}; + + +/* ------------------- Private functions -------------------- */ + + +void print_opus_stream_info (decoder_t *decoder) +{ + opf_private_t *priv = decoder->private; + decoder_callbacks_t *cb = decoder->callbacks; + + + if (cb == NULL || cb->printf_metadata == NULL) + return; + + cb->printf_metadata(decoder->callback_arg, 2, + _("Ogg Opus stream: %d channel, 48000 Hz"), + priv->oh->channel_count); + + cb->printf_metadata(decoder->callback_arg, 3, + _("Vorbis format: Version %d"), + priv->oh->version); + + cb->printf_metadata(decoder->callback_arg, 3, + _("Encoded by: %s"), priv->ot->vendor); +} + +void print_opus_comments (const OpusTags *ot, decoder_callbacks_t *cb, + void *callback_arg) +{ + int i; + char *temp = NULL; + int temp_len = 0; + + for (i = 0; i < ot->comments; i++) { + + /* Gotta null terminate these things */ + if (temp_len < ot->comment_lengths[i] + 1) { + temp_len = ot->comment_lengths[i] + 1; + temp = realloc(temp, sizeof(char) * temp_len); + } + + strncpy(temp, ot->user_comments[i], ot->comment_lengths[i]); + temp[ot->comment_lengths[i]] = '\0'; + + print_vorbis_comment(temp, cb, callback_arg); + } + + free(temp); +}