src/decoder/opus: new decoder plugin for the Opus codec
Using libopus and libogg.
This commit is contained in:
parent
bab756a5d0
commit
1536b5a9d6
3
INSTALL
3
INSTALL
|
@ -81,6 +81,9 @@ Alternative for MP3 support.
|
||||||
Ogg Vorbis - http://www.xiph.org/ogg/vorbis/
|
Ogg Vorbis - http://www.xiph.org/ogg/vorbis/
|
||||||
For Ogg Vorbis support. You will need libogg and libvorbis.
|
For Ogg Vorbis support. You will need libogg and libvorbis.
|
||||||
|
|
||||||
|
libopus - http://libopus.sourceforge.net/
|
||||||
|
Opus codec support
|
||||||
|
|
||||||
FLAC - http://flac.sourceforge.net/
|
FLAC - http://flac.sourceforge.net/
|
||||||
For FLAC support. You will need version 1.1.0 or higher of libflac.
|
For FLAC support. You will need version 1.1.0 or higher of libflac.
|
||||||
|
|
||||||
|
|
15
Makefile.am
15
Makefile.am
|
@ -538,6 +538,7 @@ libdecoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||||
$(WAVPACK_CFLAGS) \
|
$(WAVPACK_CFLAGS) \
|
||||||
$(MAD_CFLAGS) \
|
$(MAD_CFLAGS) \
|
||||||
$(MPG123_CFLAGS) \
|
$(MPG123_CFLAGS) \
|
||||||
|
$(OPUS_CFLAGS) \
|
||||||
$(FFMPEG_CFLAGS) \
|
$(FFMPEG_CFLAGS) \
|
||||||
$(MPCDEC_CFLAGS) \
|
$(MPCDEC_CFLAGS) \
|
||||||
$(FAAD_CFLAGS)
|
$(FAAD_CFLAGS)
|
||||||
|
@ -555,6 +556,7 @@ DECODER_LIBS = \
|
||||||
$(WAVPACK_LIBS) \
|
$(WAVPACK_LIBS) \
|
||||||
$(MAD_LIBS) \
|
$(MAD_LIBS) \
|
||||||
$(MPG123_LIBS) \
|
$(MPG123_LIBS) \
|
||||||
|
$(OPUS_LIBS) \
|
||||||
$(MP4FF_LIBS) \
|
$(MP4FF_LIBS) \
|
||||||
$(FFMPEG_LIBS) \
|
$(FFMPEG_LIBS) \
|
||||||
$(MPCDEC_LIBS) \
|
$(MPCDEC_LIBS) \
|
||||||
|
@ -574,6 +576,19 @@ if HAVE_MPCDEC
|
||||||
libdecoder_plugins_a_SOURCES += src/decoder/mpcdec_decoder_plugin.c
|
libdecoder_plugins_a_SOURCES += src/decoder/mpcdec_decoder_plugin.c
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if HAVE_OPUS
|
||||||
|
libdecoder_plugins_a_SOURCES += \
|
||||||
|
src/decoder/OggUtil.cxx \
|
||||||
|
src/decoder/OggUtil.hxx \
|
||||||
|
src/decoder/OpusReader.hxx \
|
||||||
|
src/decoder/OpusHead.hxx \
|
||||||
|
src/decoder/OpusHead.cxx \
|
||||||
|
src/decoder/OpusTags.cxx \
|
||||||
|
src/decoder/OpusTags.hxx \
|
||||||
|
src/decoder/OpusDecoderPlugin.cxx \
|
||||||
|
src/decoder/OpusDecoderPlugin.h
|
||||||
|
endif
|
||||||
|
|
||||||
if HAVE_WAVPACK
|
if HAVE_WAVPACK
|
||||||
libdecoder_plugins_a_SOURCES += src/decoder/wavpack_decoder_plugin.c
|
libdecoder_plugins_a_SOURCES += src/decoder/wavpack_decoder_plugin.c
|
||||||
endif
|
endif
|
||||||
|
|
2
NEWS
2
NEWS
|
@ -1,4 +1,6 @@
|
||||||
ver 0.18 (2012/??/??)
|
ver 0.18 (2012/??/??)
|
||||||
|
* decoder:
|
||||||
|
- opus: new decoder plugin for the Opus codec
|
||||||
* improved decoder/output error reporting
|
* improved decoder/output error reporting
|
||||||
|
|
||||||
ver 0.17.2 (2012/??/??)
|
ver 0.17.2 (2012/??/??)
|
||||||
|
|
17
configure.ac
17
configure.ac
|
@ -329,6 +329,11 @@ AC_ARG_ENABLE(openal,
|
||||||
[enable OpenAL support (default: disable)]),,
|
[enable OpenAL support (default: disable)]),,
|
||||||
enable_openal=no)
|
enable_openal=no)
|
||||||
|
|
||||||
|
AC_ARG_ENABLE(opus,
|
||||||
|
AS_HELP_STRING([--enable-opus],
|
||||||
|
[enable Opus codec support (default: auto)]),,
|
||||||
|
enable_opus=auto)
|
||||||
|
|
||||||
AC_ARG_ENABLE(oss,
|
AC_ARG_ENABLE(oss,
|
||||||
AS_HELP_STRING([--disable-oss],
|
AS_HELP_STRING([--disable-oss],
|
||||||
[disable OSS support (default: enable)]),,
|
[disable OSS support (default: enable)]),,
|
||||||
|
@ -919,6 +924,14 @@ if test x$enable_modplug = xyes; then
|
||||||
fi
|
fi
|
||||||
AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
|
AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
|
||||||
|
|
||||||
|
dnl -------------------------------- libopus ----------------------------------
|
||||||
|
MPD_AUTO_PKG(opus, OPUS, [opus ogg],
|
||||||
|
[opus decoder plugin], [libopus not found])
|
||||||
|
if test x$enable_opus = xyes; then
|
||||||
|
AC_DEFINE(HAVE_OPUS, 1, [Define to use libopus])
|
||||||
|
fi
|
||||||
|
AM_CONDITIONAL(HAVE_OPUS, test x$enable_opus = xyes)
|
||||||
|
|
||||||
dnl -------------------------------- libsndfile -------------------------------
|
dnl -------------------------------- libsndfile -------------------------------
|
||||||
dnl See above test, which may disable this.
|
dnl See above test, which may disable this.
|
||||||
MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile],
|
MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile],
|
||||||
|
@ -1096,6 +1109,7 @@ if
|
||||||
test x$enable_mp4 = xno &&
|
test x$enable_mp4 = xno &&
|
||||||
test x$enable_mpc = xno &&
|
test x$enable_mpc = xno &&
|
||||||
test x$enable_mpg123 = xno &&
|
test x$enable_mpg123 = xno &&
|
||||||
|
test x$enable_opus = xno &&
|
||||||
test x$enable_sidplay = xno &&
|
test x$enable_sidplay = xno &&
|
||||||
test x$enable_tremor = xno &&
|
test x$enable_tremor = xno &&
|
||||||
test x$enable_vorbis = xno &&
|
test x$enable_vorbis = xno &&
|
||||||
|
@ -1106,7 +1120,7 @@ if
|
||||||
fi
|
fi
|
||||||
|
|
||||||
AM_CONDITIONAL(HAVE_XIPH,
|
AM_CONDITIONAL(HAVE_XIPH,
|
||||||
test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_flac = xyes)
|
test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_flac = xyes || test x$enable_opus = xyes)
|
||||||
|
|
||||||
AM_CONDITIONAL(HAVE_FLAC_COMMON,
|
AM_CONDITIONAL(HAVE_FLAC_COMMON,
|
||||||
test x$enable_flac = xyes)
|
test x$enable_flac = xyes)
|
||||||
|
@ -1581,6 +1595,7 @@ results(mpg123, [MPG123])
|
||||||
results(mp4, [MP4])
|
results(mp4, [MP4])
|
||||||
results(mpc, [Musepack])
|
results(mpc, [Musepack])
|
||||||
printf '\n\t'
|
printf '\n\t'
|
||||||
|
results(opus, [OPUS])
|
||||||
results(tremor, [OggTremor])
|
results(tremor, [OggTremor])
|
||||||
results(vorbis, [OggVorbis])
|
results(vorbis, [OggVorbis])
|
||||||
results(audiofile, [WAVE])
|
results(audiofile, [WAVE])
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "OggUtil.hxx"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "decoder_api.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
OggFeed(ogg_sync_state &oy, struct decoder *decoder,
|
||||||
|
struct input_stream *input_stream, size_t size)
|
||||||
|
{
|
||||||
|
char *buffer = ogg_sync_buffer(&oy, size);
|
||||||
|
if (buffer == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t nbytes = decoder_read(decoder, input_stream,
|
||||||
|
buffer, size);
|
||||||
|
if (nbytes == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ogg_sync_wrote(&oy, nbytes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
OggExpectPage(ogg_sync_state &oy, ogg_page &page,
|
||||||
|
struct decoder *decoder, struct input_stream *input_stream)
|
||||||
|
{
|
||||||
|
while (true) {
|
||||||
|
int r = ogg_sync_pageout(&oy, &page);
|
||||||
|
if (r != 0)
|
||||||
|
return r > 0;
|
||||||
|
|
||||||
|
if (!OggFeed(oy, decoder, input_stream, 1024))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_OGG_UTIL_HXX
|
||||||
|
#define MPD_OGG_UTIL_HXX
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
|
||||||
|
#include <ogg/ogg.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feed data from the #input_stream into the #ogg_sync_state.
|
||||||
|
*
|
||||||
|
* @return false on error or end-of-file
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
OggFeed(ogg_sync_state &oy, struct decoder *decoder,
|
||||||
|
struct input_stream *input_stream, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feed into the #ogg_sync_state until a page gets available. Garbage
|
||||||
|
* data at the beginning is considered a fatal error.
|
||||||
|
*
|
||||||
|
* @return true if a page is available
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
OggExpectPage(ogg_sync_state &oy, ogg_page &page,
|
||||||
|
struct decoder *decoder, struct input_stream *input_stream);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,366 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h" /* must be first for large file support */
|
||||||
|
#include "OpusDecoderPlugin.h"
|
||||||
|
#include "OpusHead.hxx"
|
||||||
|
#include "OpusTags.hxx"
|
||||||
|
#include "OggUtil.hxx"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "ogg_codec.h"
|
||||||
|
#include "decoder_api.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "audio_check.h"
|
||||||
|
#include "tag_handler.h"
|
||||||
|
|
||||||
|
#include <opus.h>
|
||||||
|
#include <ogg/ogg.h>
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#undef G_LOG_DOMAIN
|
||||||
|
#define G_LOG_DOMAIN "opus"
|
||||||
|
|
||||||
|
static const opus_int32 opus_sample_rate = 48000;
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
|
static bool
|
||||||
|
IsOpusHead(const ogg_packet &packet)
|
||||||
|
{
|
||||||
|
return packet.bytes >= 8 && memcmp(packet.packet, "OpusHead", 8) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
|
static bool
|
||||||
|
IsOpusTags(const ogg_packet &packet)
|
||||||
|
{
|
||||||
|
return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
mpd_opus_init(G_GNUC_UNUSED const struct config_param *param)
|
||||||
|
{
|
||||||
|
g_debug("%s", opus_get_version_string());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MPDOpusDecoder {
|
||||||
|
struct decoder *decoder;
|
||||||
|
struct input_stream *input_stream;
|
||||||
|
|
||||||
|
ogg_stream_state os;
|
||||||
|
|
||||||
|
OpusDecoder *opus_decoder = nullptr;
|
||||||
|
opus_int16 *output_buffer = nullptr;
|
||||||
|
unsigned output_size = 0;
|
||||||
|
|
||||||
|
bool os_initialized = false;
|
||||||
|
bool found_opus = false;
|
||||||
|
|
||||||
|
int opus_serialno;
|
||||||
|
|
||||||
|
size_t frame_size;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MPDOpusDecoder(struct decoder *_decoder,
|
||||||
|
struct input_stream *_input_stream)
|
||||||
|
:decoder(_decoder), input_stream(_input_stream) {}
|
||||||
|
~MPDOpusDecoder();
|
||||||
|
|
||||||
|
enum decoder_command HandlePage(ogg_page &page);
|
||||||
|
enum decoder_command HandlePacket(const ogg_packet &packet);
|
||||||
|
enum decoder_command HandleBOS(const ogg_packet &packet);
|
||||||
|
enum decoder_command HandleTags(const ogg_packet &packet);
|
||||||
|
enum decoder_command HandleAudio(const ogg_packet &packet);
|
||||||
|
};
|
||||||
|
|
||||||
|
MPDOpusDecoder::~MPDOpusDecoder()
|
||||||
|
{
|
||||||
|
g_free(output_buffer);
|
||||||
|
|
||||||
|
if (opus_decoder != nullptr)
|
||||||
|
opus_decoder_destroy(opus_decoder);
|
||||||
|
|
||||||
|
if (os_initialized)
|
||||||
|
ogg_stream_clear(&os);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum decoder_command
|
||||||
|
MPDOpusDecoder::HandlePage(ogg_page &page)
|
||||||
|
{
|
||||||
|
const auto page_serialno = ogg_page_serialno(&page);
|
||||||
|
if (!os_initialized) {
|
||||||
|
os_initialized = true;
|
||||||
|
ogg_stream_init(&os, page_serialno);
|
||||||
|
} else if (page_serialno != os.serialno)
|
||||||
|
ogg_stream_reset_serialno(&os, page_serialno);
|
||||||
|
|
||||||
|
ogg_stream_pagein(&os, &page);
|
||||||
|
|
||||||
|
ogg_packet packet;
|
||||||
|
while (ogg_stream_packetout(&os, &packet) == 1) {
|
||||||
|
enum decoder_command cmd = HandlePacket(packet);
|
||||||
|
if (cmd != DECODE_COMMAND_NONE)
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DECODE_COMMAND_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum decoder_command
|
||||||
|
MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
|
||||||
|
{
|
||||||
|
if (packet.e_o_s)
|
||||||
|
return DECODE_COMMAND_STOP;
|
||||||
|
|
||||||
|
if (packet.b_o_s)
|
||||||
|
return HandleBOS(packet);
|
||||||
|
else if (!found_opus)
|
||||||
|
return DECODE_COMMAND_STOP;
|
||||||
|
|
||||||
|
if (IsOpusTags(packet))
|
||||||
|
return HandleTags(packet);
|
||||||
|
|
||||||
|
return HandleAudio(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum decoder_command
|
||||||
|
MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
|
||||||
|
{
|
||||||
|
assert(packet.b_o_s);
|
||||||
|
|
||||||
|
if (found_opus || !IsOpusHead(packet))
|
||||||
|
return DECODE_COMMAND_STOP;
|
||||||
|
|
||||||
|
unsigned channels;
|
||||||
|
if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
|
||||||
|
!audio_valid_channel_count(channels))
|
||||||
|
return DECODE_COMMAND_STOP;
|
||||||
|
|
||||||
|
assert(opus_decoder == nullptr);
|
||||||
|
assert(output_buffer == nullptr);
|
||||||
|
|
||||||
|
opus_serialno = os.serialno;
|
||||||
|
found_opus = true;
|
||||||
|
|
||||||
|
/* TODO: parse attributes from the OpusHead (sample rate,
|
||||||
|
channels, ...) */
|
||||||
|
|
||||||
|
int opus_error;
|
||||||
|
opus_decoder = opus_decoder_create(opus_sample_rate, channels,
|
||||||
|
&opus_error);
|
||||||
|
if (opus_decoder == nullptr) {
|
||||||
|
g_warning("libopus error: %s",
|
||||||
|
opus_strerror(opus_error));
|
||||||
|
return DECODE_COMMAND_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct audio_format audio_format;
|
||||||
|
audio_format_init(&audio_format, opus_sample_rate,
|
||||||
|
SAMPLE_FORMAT_S16, channels);
|
||||||
|
decoder_initialized(decoder, &audio_format, false, -1);
|
||||||
|
frame_size = audio_format_frame_size(&audio_format);
|
||||||
|
|
||||||
|
/* allocate an output buffer for 16 bit PCM samples big enough
|
||||||
|
to hold a quarter second, larger than 120ms required by
|
||||||
|
libopus */
|
||||||
|
output_size = audio_format.sample_rate / 4;
|
||||||
|
output_buffer = (opus_int16 *)
|
||||||
|
g_malloc(sizeof(*output_buffer) * output_size *
|
||||||
|
audio_format.channels);
|
||||||
|
|
||||||
|
return decoder_get_command(decoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum decoder_command
|
||||||
|
MPDOpusDecoder::HandleTags(const ogg_packet &packet)
|
||||||
|
{
|
||||||
|
struct tag *tag = tag_new();
|
||||||
|
|
||||||
|
enum decoder_command cmd;
|
||||||
|
if (ScanOpusTags(packet.packet, packet.bytes, &add_tag_handler, tag) &&
|
||||||
|
!tag_is_empty(tag))
|
||||||
|
cmd = decoder_tag(decoder, input_stream, tag);
|
||||||
|
else
|
||||||
|
cmd = decoder_get_command(decoder);
|
||||||
|
|
||||||
|
tag_free(tag);
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum decoder_command
|
||||||
|
MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
|
||||||
|
{
|
||||||
|
assert(opus_decoder != nullptr);
|
||||||
|
|
||||||
|
int nframes = opus_decode(opus_decoder,
|
||||||
|
(const unsigned char*)packet.packet,
|
||||||
|
packet.bytes,
|
||||||
|
output_buffer, output_size,
|
||||||
|
0);
|
||||||
|
if (nframes < 0) {
|
||||||
|
g_warning("%s", opus_strerror(nframes));
|
||||||
|
return DECODE_COMMAND_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nframes > 0) {
|
||||||
|
const size_t nbytes = nframes * frame_size;
|
||||||
|
enum decoder_command cmd =
|
||||||
|
decoder_data(decoder, input_stream,
|
||||||
|
output_buffer, nbytes,
|
||||||
|
0);
|
||||||
|
if (cmd != DECODE_COMMAND_NONE)
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DECODE_COMMAND_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mpd_opus_stream_decode(struct decoder *decoder,
|
||||||
|
struct input_stream *input_stream)
|
||||||
|
{
|
||||||
|
if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_OPUS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* rewind the stream, because ogg_codec_detect() has
|
||||||
|
moved it */
|
||||||
|
input_stream_lock_seek(input_stream, 0, SEEK_SET, nullptr);
|
||||||
|
|
||||||
|
MPDOpusDecoder d(decoder, input_stream);
|
||||||
|
|
||||||
|
ogg_sync_state oy;
|
||||||
|
ogg_sync_init(&oy);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (!OggFeed(oy, decoder, input_stream, 1024))
|
||||||
|
break;
|
||||||
|
|
||||||
|
ogg_page page;
|
||||||
|
while (ogg_sync_pageout(&oy, &page) == 1) {
|
||||||
|
enum decoder_command cmd = d.HandlePage(page);
|
||||||
|
if (cmd != DECODE_COMMAND_NONE)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ogg_sync_clear(&oy);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
mpd_opus_scan_stream(struct input_stream *is,
|
||||||
|
const struct tag_handler *handler, void *handler_ctx)
|
||||||
|
{
|
||||||
|
ogg_sync_state oy;
|
||||||
|
ogg_sync_init(&oy);
|
||||||
|
|
||||||
|
ogg_page page;
|
||||||
|
if (!OggExpectPage(oy, page, nullptr, is)) {
|
||||||
|
ogg_sync_clear(&oy);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read at most two more pages */
|
||||||
|
unsigned remaining_pages = 2;
|
||||||
|
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
ogg_stream_state os;
|
||||||
|
ogg_stream_init(&os, ogg_page_serialno(&page));
|
||||||
|
ogg_stream_pagein(&os, &page);
|
||||||
|
|
||||||
|
ogg_packet packet;
|
||||||
|
while (true) {
|
||||||
|
int r = ogg_stream_packetout(&os, &packet);
|
||||||
|
if (r < 0) {
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == 0) {
|
||||||
|
if (remaining_pages-- == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!OggExpectPage(oy, page, nullptr, is)) {
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ogg_stream_pagein(&os, &page);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.b_o_s) {
|
||||||
|
if (!IsOpusHead(packet))
|
||||||
|
break;
|
||||||
|
|
||||||
|
unsigned channels;
|
||||||
|
if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
|
||||||
|
!audio_valid_channel_count(channels)) {
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = true;
|
||||||
|
} else if (!result)
|
||||||
|
break;
|
||||||
|
else if (IsOpusTags(packet)) {
|
||||||
|
if (!ScanOpusTags(packet.packet, packet.bytes,
|
||||||
|
handler, handler_ctx))
|
||||||
|
result = false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ogg_stream_clear(&os);
|
||||||
|
ogg_sync_clear(&oy);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *const opus_suffixes[] = {
|
||||||
|
"opus",
|
||||||
|
"ogg",
|
||||||
|
"oga",
|
||||||
|
nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *const opus_mime_types[] = {
|
||||||
|
"audio/opus",
|
||||||
|
nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct decoder_plugin opus_decoder_plugin = {
|
||||||
|
"opus",
|
||||||
|
mpd_opus_init,
|
||||||
|
nullptr,
|
||||||
|
mpd_opus_stream_decode,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
mpd_opus_scan_stream,
|
||||||
|
nullptr,
|
||||||
|
opus_suffixes,
|
||||||
|
opus_mime_types,
|
||||||
|
};
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_DECODER_OPUS_H
|
||||||
|
#define MPD_DECODER_OPUS_H
|
||||||
|
|
||||||
|
extern const struct decoder_plugin opus_decoder_plugin;
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "OpusHead.hxx"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
struct OpusHead {
|
||||||
|
char signature[8];
|
||||||
|
uint8_t version, channels;
|
||||||
|
uint16_t pre_skip;
|
||||||
|
uint32_t sample_rate;
|
||||||
|
uint16_t output_gain;
|
||||||
|
uint8_t channel_mapping;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r)
|
||||||
|
{
|
||||||
|
const OpusHead *h = (const OpusHead *)data;
|
||||||
|
if (size < 19 || (h->version & 0xf0) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
channels_r = h->channels;
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_OPUS_HEAD_HXX
|
||||||
|
#define MPD_OPUS_HEAD_HXX
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
bool
|
||||||
|
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_OPUS_READER_HXX
|
||||||
|
#define MPD_OPUS_READER_HXX
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
class OpusReader {
|
||||||
|
const uint8_t *p, *const end;
|
||||||
|
|
||||||
|
public:
|
||||||
|
OpusReader(const void *_p, size_t size)
|
||||||
|
:p((const uint8_t *)_p), end(p + size) {}
|
||||||
|
|
||||||
|
bool Skip(size_t length) {
|
||||||
|
p += length;
|
||||||
|
return p <= end;
|
||||||
|
}
|
||||||
|
|
||||||
|
const void *Read(size_t length) {
|
||||||
|
const uint8_t *result = p;
|
||||||
|
return Skip(length)
|
||||||
|
? result
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Expect(const void *value, size_t length) {
|
||||||
|
const void *data = Read(length);
|
||||||
|
return data != nullptr && memcmp(value, data, length) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadByte(uint8_t &value_r) {
|
||||||
|
if (p >= end)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
value_r = *p++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadShort(uint16_t &value_r) {
|
||||||
|
const uint8_t *value = (const uint8_t *)Read(sizeof(value_r));
|
||||||
|
if (value == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
value_r = value[0] | (value[1] << 8);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadWord(uint32_t &value_r) {
|
||||||
|
const uint8_t *value = (const uint8_t *)Read(sizeof(value_r));
|
||||||
|
if (value == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
value_r = value[0] | (value[1] << 8)
|
||||||
|
| (value[2] << 16) | (value[3] << 24);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SkipString() {
|
||||||
|
uint32_t length;
|
||||||
|
return ReadWord(length) && Skip(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *ReadString() {
|
||||||
|
uint32_t length;
|
||||||
|
if (!ReadWord(length))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
const char *src = (const char *)Read(length);
|
||||||
|
if (src == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return strndup(src, length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "OpusTags.hxx"
|
||||||
|
#include "OpusReader.hxx"
|
||||||
|
#include "XiphTags.h"
|
||||||
|
#include "tag_handler.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static void
|
||||||
|
ScanOneOpusTag(const char *name, const char *value,
|
||||||
|
const struct tag_handler *handler, void *ctx)
|
||||||
|
{
|
||||||
|
tag_handler_invoke_pair(handler, ctx, name, value);
|
||||||
|
|
||||||
|
if (handler->tag != nullptr) {
|
||||||
|
enum tag_type t = tag_table_lookup_i(xiph_tags, name);
|
||||||
|
if (t != TAG_NUM_OF_ITEM_TYPES)
|
||||||
|
tag_handler_invoke_tag(handler, ctx, t, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
ScanOpusTags(const void *data, size_t size,
|
||||||
|
const struct tag_handler *handler, void *ctx)
|
||||||
|
{
|
||||||
|
OpusReader r(data, size);
|
||||||
|
if (!r.Expect("OpusTags", 8))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (handler->pair == nullptr && handler->tag == nullptr)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!r.SkipString())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
uint32_t n;
|
||||||
|
if (!r.ReadWord(n))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (n-- > 0) {
|
||||||
|
char *p = r.ReadString();
|
||||||
|
if (p == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
char *eq = strchr(p, '=');
|
||||||
|
if (eq != nullptr && eq > p) {
|
||||||
|
*eq = 0;
|
||||||
|
|
||||||
|
ScanOneOpusTag(p, eq + 1, handler, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_OPUS_TAGS_HXX
|
||||||
|
#define MPD_OPUS_TAGS_HXX
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
bool
|
||||||
|
ScanOpusTags(const void *data, size_t size,
|
||||||
|
const struct tag_handler *handler, void *ctx);
|
||||||
|
|
||||||
|
#endif
|
|
@ -41,5 +41,8 @@ ogg_codec_detect(struct decoder *decoder, struct input_stream *is)
|
||||||
memcmp(buf + 28, "fLaC", 4) == 0)
|
memcmp(buf + 28, "fLaC", 4) == 0)
|
||||||
return OGG_CODEC_FLAC;
|
return OGG_CODEC_FLAC;
|
||||||
|
|
||||||
|
if (memcmp(buf + 28, "Opus", 4) == 0)
|
||||||
|
return OGG_CODEC_OPUS;
|
||||||
|
|
||||||
return OGG_CODEC_VORBIS;
|
return OGG_CODEC_VORBIS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ enum ogg_codec {
|
||||||
OGG_CODEC_UNKNOWN,
|
OGG_CODEC_UNKNOWN,
|
||||||
OGG_CODEC_VORBIS,
|
OGG_CODEC_VORBIS,
|
||||||
OGG_CODEC_FLAC,
|
OGG_CODEC_FLAC,
|
||||||
|
OGG_CODEC_OPUS,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ogg_codec
|
enum ogg_codec
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include "decoder/pcm_decoder_plugin.h"
|
#include "decoder/pcm_decoder_plugin.h"
|
||||||
#include "decoder/dsdiff_decoder_plugin.h"
|
#include "decoder/dsdiff_decoder_plugin.h"
|
||||||
#include "decoder/dsf_decoder_plugin.h"
|
#include "decoder/dsf_decoder_plugin.h"
|
||||||
|
#include "decoder/OpusDecoderPlugin.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
|
@ -66,6 +67,9 @@ const struct decoder_plugin *const decoder_plugins[] = {
|
||||||
#ifdef HAVE_FLAC
|
#ifdef HAVE_FLAC
|
||||||
&flac_decoder_plugin,
|
&flac_decoder_plugin,
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_OPUS
|
||||||
|
&opus_decoder_plugin,
|
||||||
|
#endif
|
||||||
#ifdef ENABLE_SNDFILE
|
#ifdef ENABLE_SNDFILE
|
||||||
&sndfile_decoder_plugin,
|
&sndfile_decoder_plugin,
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue