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/
|
||||
For Ogg Vorbis support. You will need libogg and libvorbis.
|
||||
|
||||
libopus - http://libopus.sourceforge.net/
|
||||
Opus codec support
|
||||
|
||||
FLAC - http://flac.sourceforge.net/
|
||||
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) \
|
||||
$(MAD_CFLAGS) \
|
||||
$(MPG123_CFLAGS) \
|
||||
$(OPUS_CFLAGS) \
|
||||
$(FFMPEG_CFLAGS) \
|
||||
$(MPCDEC_CFLAGS) \
|
||||
$(FAAD_CFLAGS)
|
||||
|
@ -555,6 +556,7 @@ DECODER_LIBS = \
|
|||
$(WAVPACK_LIBS) \
|
||||
$(MAD_LIBS) \
|
||||
$(MPG123_LIBS) \
|
||||
$(OPUS_LIBS) \
|
||||
$(MP4FF_LIBS) \
|
||||
$(FFMPEG_LIBS) \
|
||||
$(MPCDEC_LIBS) \
|
||||
|
@ -574,6 +576,19 @@ if HAVE_MPCDEC
|
|||
libdecoder_plugins_a_SOURCES += src/decoder/mpcdec_decoder_plugin.c
|
||||
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
|
||||
libdecoder_plugins_a_SOURCES += src/decoder/wavpack_decoder_plugin.c
|
||||
endif
|
||||
|
|
2
NEWS
2
NEWS
|
@ -1,4 +1,6 @@
|
|||
ver 0.18 (2012/??/??)
|
||||
* decoder:
|
||||
- opus: new decoder plugin for the Opus codec
|
||||
* improved decoder/output error reporting
|
||||
|
||||
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=no)
|
||||
|
||||
AC_ARG_ENABLE(opus,
|
||||
AS_HELP_STRING([--enable-opus],
|
||||
[enable Opus codec support (default: auto)]),,
|
||||
enable_opus=auto)
|
||||
|
||||
AC_ARG_ENABLE(oss,
|
||||
AS_HELP_STRING([--disable-oss],
|
||||
[disable OSS support (default: enable)]),,
|
||||
|
@ -919,6 +924,14 @@ if test x$enable_modplug = xyes; then
|
|||
fi
|
||||
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 See above test, which may disable this.
|
||||
MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile],
|
||||
|
@ -1096,6 +1109,7 @@ if
|
|||
test x$enable_mp4 = xno &&
|
||||
test x$enable_mpc = xno &&
|
||||
test x$enable_mpg123 = xno &&
|
||||
test x$enable_opus = xno &&
|
||||
test x$enable_sidplay = xno &&
|
||||
test x$enable_tremor = xno &&
|
||||
test x$enable_vorbis = xno &&
|
||||
|
@ -1106,7 +1120,7 @@ if
|
|||
fi
|
||||
|
||||
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,
|
||||
test x$enable_flac = xyes)
|
||||
|
@ -1581,6 +1595,7 @@ results(mpg123, [MPG123])
|
|||
results(mp4, [MP4])
|
||||
results(mpc, [Musepack])
|
||||
printf '\n\t'
|
||||
results(opus, [OPUS])
|
||||
results(tremor, [OggTremor])
|
||||
results(vorbis, [OggVorbis])
|
||||
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)
|
||||
return OGG_CODEC_FLAC;
|
||||
|
||||
if (memcmp(buf + 28, "Opus", 4) == 0)
|
||||
return OGG_CODEC_OPUS;
|
||||
|
||||
return OGG_CODEC_VORBIS;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ enum ogg_codec {
|
|||
OGG_CODEC_UNKNOWN,
|
||||
OGG_CODEC_VORBIS,
|
||||
OGG_CODEC_FLAC,
|
||||
OGG_CODEC_OPUS,
|
||||
};
|
||||
|
||||
enum ogg_codec
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "decoder/pcm_decoder_plugin.h"
|
||||
#include "decoder/dsdiff_decoder_plugin.h"
|
||||
#include "decoder/dsf_decoder_plugin.h"
|
||||
#include "decoder/OpusDecoderPlugin.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
|
@ -66,6 +67,9 @@ const struct decoder_plugin *const decoder_plugins[] = {
|
|||
#ifdef HAVE_FLAC
|
||||
&flac_decoder_plugin,
|
||||
#endif
|
||||
#ifdef HAVE_OPUS
|
||||
&opus_decoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_SNDFILE
|
||||
&sndfile_decoder_plugin,
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue