From 9cff5bc22740bf80e59b76f20ccaa2888d9d6426 Mon Sep 17 00:00:00 2001 From: Elimar Riesebieter Date: Sun, 27 Nov 2016 12:12:43 +0100 Subject: [PATCH] Refreshed opus patch from Tomasz Golinski --- decoder_plugins/Makefile.am | 4 + decoder_plugins/decoders.m4 | 1 + decoder_plugins/opus/Makefile.am | 6 + decoder_plugins/opus/opus.c | 473 +++++++++++++++++++++++++++++++++++++++ decoder_plugins/opus/opus.m4 | 25 +++ options.c | 4 +- 6 files changed, 511 insertions(+), 2 deletions(-) create mode 100644 decoder_plugins/opus/Makefile.am create mode 100644 decoder_plugins/opus/opus.c create mode 100644 decoder_plugins/opus/opus.m4 diff --git a/decoder_plugins/Makefile.am b/decoder_plugins/Makefile.am index 32a6376..d746099 100644 --- a/decoder_plugins/Makefile.am +++ b/decoder_plugins/Makefile.am @@ -16,6 +16,10 @@ if BUILD_vorbis SUBDIRS += vorbis endif +if BUILD_opus + SUBDIRS += opus +endif + if BUILD_flac SUBDIRS += flac endif diff --git a/decoder_plugins/decoders.m4 b/decoder_plugins/decoders.m4 index 77bc2b3..a851967 100644 --- a/decoder_plugins/decoders.m4 +++ b/decoder_plugins/decoders.m4 @@ -20,6 +20,7 @@ m4_include(decoder_plugins/flac/flac.m4) m4_include(decoder_plugins/modplug/modplug.m4) m4_include(decoder_plugins/mp3/mp3.m4) m4_include(decoder_plugins/musepack/musepack.m4) +m4_include(decoder_plugins/opus/opus.m4) m4_include(decoder_plugins/sidplay2/sidplay2.m4) m4_include(decoder_plugins/sndfile/sndfile.m4) m4_include(decoder_plugins/speex/speex.m4) diff --git a/decoder_plugins/opus/Makefile.am b/decoder_plugins/opus/Makefile.am new file mode 100644 index 0000000..d0c183d --- /dev/null +++ b/decoder_plugins/opus/Makefile.am @@ -0,0 +1,6 @@ +lib_LTLIBRARIES = libopus_decoder.la +libdir = $(plugindir)/$(DECODER_PLUGIN_DIR) +libopus_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ +libopus_decoder_la_LIBADD = $(OPUSFILE_LIBS) +libopus_decoder_la_CFLAGS = $(OPUSFILE_CFLAGS) -I$(top_srcdir) +libopus_decoder_la_SOURCES = opus.c diff --git a/decoder_plugins/opus/opus.c b/decoder_plugins/opus/opus.c new file mode 100644 index 0000000..6cc9d55 --- /dev/null +++ b/decoder_plugins/opus/opus.c @@ -0,0 +1,473 @@ +/* + * MOC - music on console + * Copyright (C) 2002 - 2005 Damian Pietras + * + * 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#define DEBUG + +#include "common.h" +#include "log.h" +#include "decoder.h" +#include "io.h" +#include "audio.h" + + +struct opus_data +{ + struct io_stream *stream; + OggOpusFile *of; + int last_section; + opus_int32 bitrate; + opus_int32 avg_bitrate; + int duration; + struct decoder_error error; + int ok; /* was this stream successfully opened? */ + int tags_change; /* the tags were changed from the last call of + ogg_current_tags */ + struct file_tags *tags; +}; + + +static void get_comment_tags (OggOpusFile *of, struct file_tags *info) +{ + int i; + const OpusTags *comments; + + comments = op_tags (of, -1); + for (i = 0; i < comments->comments; i++) { + if (!strncasecmp(comments->user_comments[i], "title=", + strlen ("title="))) + info->title = xstrdup(comments->user_comments[i] + + strlen ("title=")); + else if (!strncasecmp(comments->user_comments[i], + "artist=", strlen ("artist="))) + info->artist = xstrdup ( + comments->user_comments[i] + + strlen ("artist=")); + else if (!strncasecmp(comments->user_comments[i], + "album=", strlen ("album="))) + info->album = xstrdup ( + comments->user_comments[i] + + strlen ("album=")); + else if (!strncasecmp(comments->user_comments[i], + "tracknumber=", + strlen ("tracknumber="))) + info->track = atoi (comments->user_comments[i] + + strlen ("tracknumber=")); + else if (!strncasecmp(comments->user_comments[i], + "track=", strlen ("track="))) + info->track = atoi (comments->user_comments[i] + + strlen ("track=")); + } +} + +/* Return a malloc()ed description of an op_*() error. */ +static char *opus_str_error (const int code) +{ + char *err; + + switch (code) { + case OP_FALSE: + err = "Request was not successful"; + break; + case OP_EOF: + err = "End Of File"; + break; + case OP_HOLE: + err = "Hole in stream"; + break; + case OP_EREAD: + err = "An underlying read, seek, or tell operation failed."; + break; + case OP_EFAULT: + err = "Internal (Opus) logic fault"; + break; + case OP_EIMPL: + err = "Unimplemented feature"; + break; + case OP_EINVAL: + err = "Invalid argument"; + break; + case OP_ENOTFORMAT: + err = "Not an Opus file"; + break; + case OP_EBADHEADER: + err = "Invalid or corrupt header"; + break; + case OP_EVERSION: + err = "Opus header version mismatch"; + break; + case OP_EBADPACKET: + err = "An audio packet failed to decode properly"; + break; + case OP_ENOSEEK: + err = "Requested seeking in unseekable stream"; + break; + case OP_EBADTIMESTAMP: + err = "File timestamps fail sanity tests"; + break; + default: + err = "Unknown error"; + } + + return xstrdup (err); +} + + +/* Fill info structure with data from ogg comments */ +static void opus_tags (const char *file_name, struct file_tags *info, + const int tags_sel) +{ + OggOpusFile *of; + int err_code; + + // op_test() is faster than op_open(), but we can't read file time with it. + if (tags_sel & TAGS_TIME) { + of = op_open_file(file_name,&err_code); + if (err_code < 0) { + char *opus_err = opus_str_error (err_code); + + logit ("Can't open %s: %s", file_name, opus_err); + free (opus_err); + op_free(of); + + return; + } + } + else { + of = op_open_file(file_name,&err_code); + if (err_code < 0) { + char *opus_err = opus_str_error (err_code); + + logit ("Can't open %s: %s", file_name, opus_err); + free (opus_err); + op_free (of); + + return; + } + } + + if (tags_sel & TAGS_COMMENTS) + get_comment_tags (of, info); + + if (tags_sel & TAGS_TIME) { + ogg_int64_t opus_time; + + opus_time = op_pcm_total (of, -1); + if (opus_time >= 0) + info->time = opus_time / 48000; + debug("Duration tags: %d, samples %lld",info->time,(long long)opus_time); + } + + op_free (of); +} + +static int read_callback (void *datasource, unsigned char *ptr, int bytes) +{ + ssize_t res; + + res = io_read (datasource, ptr, bytes); + + if (res < 0) { + logit ("Read error"); + res = -1; + } + + return res; +} + +static int seek_callback (void *datasource, opus_int64 offset, int whence) +{ + debug ("Seek request to %ld (%s)", (long)offset, + whence == SEEK_SET ? "SEEK_SET" + : (whence == SEEK_CUR ? "SEEK_CUR" : "SEEK_END")); + return io_seek (datasource, offset, whence)<0 ? -1 : 0; +} + +static int close_callback (void *datasource ATTR_UNUSED) +{ + return 0; +} + +static opus_int64 tell_callback (void *datasource) +{ + return io_tell (datasource); +} + +static void opus_open_stream_internal (struct opus_data *data) +{ + int res; + OpusFileCallbacks callbacks = { + read_callback, + seek_callback, + tell_callback, + close_callback + }; + + data->tags = tags_new (); + + data->of = op_open_callbacks(data->stream, &callbacks, NULL, 0, &res); + if (res < 0) { + char *opus_err = opus_str_error (res); + + decoder_error (&data->error, ERROR_FATAL, 0, "%s", + opus_err); + debug ("op_open error: %s", opus_err); + free (opus_err); + op_free (data->of); + data->of = NULL; + io_close (data->stream); + } + else { + ogg_int64_t samples; + data->last_section = -1; + data->avg_bitrate = op_bitrate (data->of, -1) / 1000; + data->bitrate = data->avg_bitrate; + samples = op_pcm_total (data->of, -1); + if (samples == OP_EINVAL) + data->duration = -1; + else + data->duration =samples/48000; + debug("Duration: %d, samples %lld",data->duration,(long long)samples); + data->ok = 1; + get_comment_tags (data->of, data->tags); + } +} + +static void *opus_open (const char *file) +{ + struct opus_data *data; + data = (struct opus_data *)xmalloc (sizeof(struct opus_data)); + data->ok = 0; + + decoder_error_init (&data->error); + data->tags_change = 0; + data->tags = NULL; + + data->stream = io_open (file, 1); + if (!io_ok(data->stream)) { + decoder_error (&data->error, ERROR_FATAL, 0, + "Can't load Opus: %s", + io_strerror(data->stream)); + io_close (data->stream); + } + else + opus_open_stream_internal (data); + + return data; +} + +static int opus_can_decode (struct io_stream *stream) +{ + char buf[36]; + + if (io_peek (stream, buf, 36) == 36 && !memcmp (buf, "OggS", 4) + && !memcmp (buf + 28, "OpusHead", 8)) + return 1; + + return 0; +} + +static void *opus_open_stream (struct io_stream *stream) +{ + struct opus_data *data; + + data = (struct opus_data *)xmalloc (sizeof(struct opus_data)); + data->ok = 0; + + decoder_error_init (&data->error); + data->stream = stream; + opus_open_stream_internal (data); + + return data; +} + +static void opus_close (void *prv_data) +{ + struct opus_data *data = (struct opus_data *)prv_data; + + if (data->ok) { + op_free (data->of); + io_close (data->stream); + } + + decoder_error_clear (&data->error); + if (data->tags) + tags_free (data->tags); + free (data); +} + +static int opus_seek (void *prv_data, int sec) +{ + struct opus_data *data = (struct opus_data *)prv_data; + + assert (sec >= 0); + + return op_pcm_seek (data->of, sec * (ogg_int64_t)48000)<0 ? -1 : sec; +} + +static int opus_decodeX (void *prv_data, char *buf, int buf_len, + struct sound_params *sound_params) +{ + struct opus_data *data = (struct opus_data *)prv_data; + int ret; + int current_section; + int bitrate; + + decoder_error_clear (&data->error); + + while (1) { +#ifdef HAVE_OPUSFILE_FLOAT + ret = op_read_float(data->of, (float *)buf, buf_len/sizeof(float), ¤t_section); +debug("opus float!"); +#else + ret = op_read(data->of, (opus_int16 *)buf, buf_len/sizeof(opus_int16), ¤t_section); +debug("opus fixed!"); +#endif + if (ret == 0) + return 0; + if (ret < 0) { + decoder_error (&data->error, ERROR_STREAM, 0, + "Error in the stream!"); + continue; + } + + if (current_section != data->last_section) { + logit ("section change or first section"); + + data->last_section = current_section; + data->tags_change = 1; + tags_free (data->tags); + data->tags = tags_new (); + get_comment_tags (data->of, data->tags); + } + + sound_params->channels = op_channel_count (data->of, current_section); + sound_params->rate = 48000; +#ifdef HAVE_OPUSFILE_FLOAT + sound_params->fmt = SFMT_FLOAT; + ret *= sound_params->channels * sizeof(float); +#else + sound_params->fmt = SFMT_S16 | SFMT_NE; + ret *= sound_params->channels * sizeof(opus_int16); +#endif + /* Update the bitrate information */ + bitrate = op_bitrate_instant (data->of); + if (bitrate > 0) + data->bitrate = bitrate / 1000; + + break; + } + return ret; +} + +static int opus_current_tags (void *prv_data, struct file_tags *tags) +{ + struct opus_data *data = (struct opus_data *)prv_data; + + tags_copy (tags, data->tags); + + if (data->tags_change) { + data->tags_change = 0; + return 1; + } + + return 0; +} + + +static int opus_get_bitrate (void *prv_data) +{ + struct opus_data *data = (struct opus_data *)prv_data; + + return data->bitrate; +} + +static int opus_get_avg_bitrate (void *prv_data) +{ + struct opus_data *data = (struct opus_data *)prv_data; + + return data->avg_bitrate; +} + +static int opus_get_duration (void *prv_data) +{ + + struct opus_data *data = (struct opus_data *)prv_data; + + return data->duration; +} + +static struct io_stream *opus_get_stream (void *prv_data) +{ + struct opus_data *data = (struct opus_data *)prv_data; + + return data->stream; +} + +static void opus_get_name (const char *file ATTR_UNUSED, char buf[4]) +{ + strcpy (buf, "OPS"); +} + +static int opus_our_format_ext (const char *ext) +{ + return !strcasecmp (ext, "opus"); +} + +static void opus_get_error (void *prv_data, struct decoder_error *error) +{ + struct opus_data *data = (struct opus_data *)prv_data; + + decoder_error_copy (error, &data->error); +} + +static int opus_our_mime (const char *mime) +{ + return !strcasecmp (mime, "audio/ogg") + || !strcasecmp (mime, "audio/ogg; codecs=opus"); +} + +static struct decoder opus_decoder = { + DECODER_API_VERSION, + NULL, + NULL, + opus_open, + opus_open_stream, + opus_can_decode, + opus_close, + opus_decodeX, + opus_seek, + opus_tags, + opus_get_bitrate, + opus_get_duration, + opus_get_error, + opus_our_format_ext, + opus_our_mime, + opus_get_name, + opus_current_tags, + opus_get_stream, + opus_get_avg_bitrate +}; + +struct decoder *plugin_init () +{ + return &opus_decoder; +} diff --git a/decoder_plugins/opus/opus.m4 b/decoder_plugins/opus/opus.m4 new file mode 100644 index 0000000..08cceb9 --- /dev/null +++ b/decoder_plugins/opus/opus.m4 @@ -0,0 +1,25 @@ +dnl opus + +AC_ARG_WITH(opus, AS_HELP_STRING([--without-opus], + [Compile without Opus support])) + + if test "x$with_opus" != "xno" + then + PKG_CHECK_MODULES(OPUSFILE, + [opusfile >= 0.1], + [AC_SUBST(OPUSFILE_LIBS) + AC_SUBST(OPUSFILE_CFLAGS) + want_opus="yes" + DECODER_PLUGINS="$DECODER_PLUGINS opus"], + [true]) + if test "x$want_opus" = "xyes" + then + AC_SEARCH_LIBS(op_read_float, opusfile, + [AC_DEFINE([HAVE_OPUSFILE_FLOAT], 1, + [Define to 1 if you have the `op_read_float' function.])]) + fi + + fi + +AM_CONDITIONAL([BUILD_opus], [test "$want_opus"]) +AC_CONFIG_FILES([decoder_plugins/opus/Makefile]) diff --git a/options.c b/options.c index 62835ac..bb97b94 100644 --- a/options.c +++ b/options.c @@ -659,9 +659,9 @@ void options_init () "audio/aac(aac):audio/aacp(aac):audio/m4a(ffmpeg):" "audio/wav(sndfile,*):" "ogg(vorbis,*,ffmpeg):oga(vorbis,*,ffmpeg):ogv(ffmpeg):" - "application/ogg(vorbis):audio/ogg(vorbis):" + "application/ogg(vorbis,opus):audio/ogg(vorbis,opus):" "flac(flac,*,ffmpeg):" - "opus(ffmpeg):" + "opus(opus,ffmpeg):" "spx(speex)", CHECK_FUNCTION); -- 2.10.2