encoder/opus: new encoder plugin for the Opus codec
This commit is contained in:
parent
9a715267ad
commit
d793b7c03f
|
@ -684,6 +684,7 @@ libencoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
|||
$(LAME_CFLAGS) \
|
||||
$(TWOLAME_CFLAGS) \
|
||||
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
|
||||
$(OPUS_CFLAGS) \
|
||||
$(VORBISENC_CFLAGS)
|
||||
|
||||
ENCODER_LIBS = \
|
||||
|
@ -691,6 +692,7 @@ ENCODER_LIBS = \
|
|||
$(LAME_LIBS) \
|
||||
$(TWOLAME_LIBS) \
|
||||
$(FLAC_LIBS) \
|
||||
$(OPUS_LIBS) \
|
||||
$(VORBISENC_LIBS)
|
||||
|
||||
libencoder_plugins_a_SOURCES =
|
||||
|
@ -708,6 +710,12 @@ libencoder_plugins_a_SOURCES += \
|
|||
src/encoder/VorbisEncoderPlugin.hxx
|
||||
endif
|
||||
|
||||
if HAVE_OPUS
|
||||
libencoder_plugins_a_SOURCES += \
|
||||
src/encoder/OpusEncoderPlugin.cxx \
|
||||
src/encoder/OpusEncoderPlugin.hxx
|
||||
endif
|
||||
|
||||
if ENABLE_LAME_ENCODER
|
||||
libencoder_plugins_a_SOURCES += src/encoder/lame_encoder.c
|
||||
endif
|
||||
|
|
2
NEWS
2
NEWS
|
@ -3,6 +3,8 @@ ver 0.18 (2012/??/??)
|
|||
- adplug: new decoder plugin using libadplug
|
||||
- opus: new decoder plugin for the Opus codec
|
||||
- vorbis: skip 16 bit quantisation, provide float samples
|
||||
* encoder:
|
||||
- opus: new encoder plugin for the Opus codec
|
||||
* output:
|
||||
- new option "tags" may be used to disable sending tags to output
|
||||
* improved decoder/output error reporting
|
||||
|
|
|
@ -1229,6 +1229,7 @@ fi
|
|||
|
||||
dnl --------------------------- encoder plugins test --------------------------
|
||||
if test x$enable_vorbis_encoder != xno ||
|
||||
test x$enable_opus != xno ||
|
||||
test x$enable_lame_encoder != xno ||
|
||||
test x$enable_twolame_encoder != xno ||
|
||||
test x$enable_flac_encoder != xno ||
|
||||
|
@ -1657,6 +1658,7 @@ if
|
|||
results(flac_encoder, [FLAC])
|
||||
results(lame_encoder, [LAME])
|
||||
results(vorbis_encoder, [Ogg Vorbis])
|
||||
results(opus, [Opus])
|
||||
results(twolame_encoder, [TwoLAME])
|
||||
results(wave_encoder, [WAVE])
|
||||
fi
|
||||
|
|
|
@ -0,0 +1,417 @@
|
|||
/*
|
||||
* 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 "OpusEncoderPlugin.hxx"
|
||||
|
||||
extern "C" {
|
||||
#include "encoder_api.h"
|
||||
}
|
||||
|
||||
#include "encoder_plugin.h"
|
||||
#include "audio_format.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <opus.h>
|
||||
#include <ogg/ogg.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "opus_encoder"
|
||||
|
||||
struct opus_encoder {
|
||||
/** the base class */
|
||||
struct encoder encoder;
|
||||
|
||||
/* configuration */
|
||||
|
||||
opus_int32 bitrate;
|
||||
int complexity;
|
||||
int signal;
|
||||
|
||||
/* runtime information */
|
||||
|
||||
struct audio_format audio_format;
|
||||
|
||||
size_t frame_size;
|
||||
|
||||
size_t buffer_frames, buffer_size, buffer_position;
|
||||
uint8_t *buffer;
|
||||
|
||||
OpusEncoder *enc;
|
||||
|
||||
unsigned char buffer2[1275 * 3 + 7];
|
||||
|
||||
ogg_stream_state os;
|
||||
|
||||
ogg_int64_t packetno;
|
||||
|
||||
bool flush;
|
||||
};
|
||||
|
||||
gcc_const
|
||||
static inline GQuark
|
||||
opus_encoder_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("opus_encoder");
|
||||
}
|
||||
|
||||
static bool
|
||||
opus_encoder_configure(struct opus_encoder *encoder,
|
||||
const struct config_param *param, GError **error_r)
|
||||
{
|
||||
const char *value = config_get_block_string(param, "bitrate", "auto");
|
||||
if (strcmp(value, "auto") == 0)
|
||||
encoder->bitrate = OPUS_AUTO;
|
||||
else if (strcmp(value, "max") == 0)
|
||||
encoder->bitrate = OPUS_BITRATE_MAX;
|
||||
else {
|
||||
char *endptr;
|
||||
encoder->bitrate = strtoul(value, &endptr, 10);
|
||||
if (endptr == value || *endptr != 0 ||
|
||||
encoder->bitrate < 500 || encoder->bitrate > 512000) {
|
||||
g_set_error(error_r, opus_encoder_quark(), 0,
|
||||
"Invalid bit rate");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
encoder->complexity = config_get_block_unsigned(param, "complexity",
|
||||
10);
|
||||
if (encoder->complexity > 10) {
|
||||
g_set_error(error_r, opus_encoder_quark(), 0,
|
||||
"Invalid complexity");
|
||||
return false;
|
||||
}
|
||||
|
||||
value = config_get_block_string(param, "signal", "auto");
|
||||
if (strcmp(value, "auto") == 0)
|
||||
encoder->bitrate = OPUS_AUTO;
|
||||
else if (strcmp(value, "voice") == 0)
|
||||
encoder->bitrate = OPUS_SIGNAL_VOICE;
|
||||
else if (strcmp(value, "music") == 0)
|
||||
encoder->bitrate = OPUS_SIGNAL_MUSIC;
|
||||
else {
|
||||
g_set_error(error_r, opus_encoder_quark(), 0,
|
||||
"Invalid signal");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct encoder *
|
||||
opus_encoder_init(const struct config_param *param, GError **error)
|
||||
{
|
||||
struct opus_encoder *encoder;
|
||||
|
||||
encoder = g_new(struct opus_encoder, 1);
|
||||
encoder_struct_init(&encoder->encoder, &opus_encoder_plugin);
|
||||
|
||||
/* load configuration from "param" */
|
||||
if (!opus_encoder_configure(encoder, param, error)) {
|
||||
/* configuration has failed, roll back and return error */
|
||||
g_free(encoder);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &encoder->encoder;
|
||||
}
|
||||
|
||||
static void
|
||||
opus_encoder_finish(struct encoder *_encoder)
|
||||
{
|
||||
struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
|
||||
|
||||
/* the real libopus cleanup was already performed by
|
||||
opus_encoder_close(), so no real work here */
|
||||
g_free(encoder);
|
||||
}
|
||||
|
||||
static bool
|
||||
opus_encoder_open(struct encoder *_encoder,
|
||||
struct audio_format *audio_format,
|
||||
GError **error_r)
|
||||
{
|
||||
struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
|
||||
|
||||
/* libopus supports only 48 kHz */
|
||||
audio_format->sample_rate = 48000;
|
||||
|
||||
if (audio_format->channels > 2)
|
||||
audio_format->channels = 1;
|
||||
|
||||
switch ((enum sample_format)audio_format->format) {
|
||||
case SAMPLE_FORMAT_S16:
|
||||
case SAMPLE_FORMAT_FLOAT:
|
||||
break;
|
||||
|
||||
case SAMPLE_FORMAT_S8:
|
||||
audio_format->format = SAMPLE_FORMAT_S16;
|
||||
break;
|
||||
|
||||
default:
|
||||
audio_format->format = SAMPLE_FORMAT_FLOAT;
|
||||
break;
|
||||
}
|
||||
|
||||
encoder->audio_format = *audio_format;
|
||||
encoder->frame_size = audio_format_frame_size(audio_format);
|
||||
|
||||
int error;
|
||||
encoder->enc = opus_encoder_create(audio_format->sample_rate,
|
||||
audio_format->channels,
|
||||
OPUS_APPLICATION_AUDIO,
|
||||
&error);
|
||||
if (encoder->enc == nullptr) {
|
||||
g_set_error_literal(error_r, opus_encoder_quark(), error,
|
||||
opus_strerror(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
opus_encoder_ctl(encoder->enc, OPUS_SET_BITRATE(encoder->bitrate));
|
||||
opus_encoder_ctl(encoder->enc,
|
||||
OPUS_SET_COMPLEXITY(encoder->complexity));
|
||||
opus_encoder_ctl(encoder->enc, OPUS_SET_SIGNAL(encoder->signal));
|
||||
|
||||
encoder->buffer_frames = audio_format->sample_rate / 50;
|
||||
encoder->buffer_size = encoder->frame_size * encoder->buffer_frames;
|
||||
encoder->buffer_position = 0;
|
||||
encoder->buffer = (unsigned char *)g_malloc(encoder->buffer_size);
|
||||
|
||||
ogg_stream_init(&encoder->os, g_random_int());
|
||||
encoder->packetno = 0;
|
||||
|
||||
/* set "flush" to true, so the caller gets the full headers on
|
||||
the first read() */
|
||||
encoder->flush = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
opus_encoder_close(struct encoder *_encoder)
|
||||
{
|
||||
struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
|
||||
|
||||
ogg_stream_clear(&encoder->os);
|
||||
g_free(encoder->buffer);
|
||||
opus_encoder_destroy(encoder->enc);
|
||||
}
|
||||
|
||||
static bool
|
||||
opus_encoder_do_encode(struct opus_encoder *encoder, bool eos,
|
||||
GError **error_r)
|
||||
{
|
||||
assert(encoder->buffer_position == encoder->buffer_size);
|
||||
|
||||
opus_int32 result =
|
||||
encoder->audio_format.format == SAMPLE_FORMAT_S16
|
||||
? opus_encode(encoder->enc,
|
||||
(const opus_int16 *)encoder->buffer,
|
||||
encoder->buffer_frames,
|
||||
encoder->buffer2,
|
||||
sizeof(encoder->buffer2))
|
||||
: opus_encode_float(encoder->enc,
|
||||
(const float *)encoder->buffer,
|
||||
encoder->buffer_frames,
|
||||
encoder->buffer2,
|
||||
sizeof(encoder->buffer2));
|
||||
if (result < 0) {
|
||||
g_set_error_literal(error_r, opus_encoder_quark(), 0,
|
||||
"Opus encoder error");
|
||||
return false;
|
||||
}
|
||||
|
||||
ogg_packet packet;
|
||||
packet.packet = encoder->buffer2;
|
||||
packet.bytes = result;
|
||||
packet.b_o_s = false;
|
||||
packet.e_o_s = eos;
|
||||
packet.granulepos = 0; // TODO
|
||||
packet.packetno = encoder->packetno++;
|
||||
ogg_stream_packetin(&encoder->os, &packet);
|
||||
|
||||
encoder->buffer_position = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
opus_encoder_end(struct encoder *_encoder, GError **error_r)
|
||||
{
|
||||
struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
|
||||
|
||||
encoder->flush = true;
|
||||
|
||||
memset(encoder->buffer + encoder->buffer_position, 0,
|
||||
encoder->buffer_size - encoder->buffer_position);
|
||||
encoder->buffer_position = encoder->buffer_size;
|
||||
|
||||
return opus_encoder_do_encode(encoder, true, error_r);
|
||||
}
|
||||
|
||||
static bool
|
||||
opus_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
|
||||
{
|
||||
struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
|
||||
|
||||
encoder->flush = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
opus_encoder_write(struct encoder *_encoder,
|
||||
const void *_data, size_t length,
|
||||
GError **error_r)
|
||||
{
|
||||
struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
|
||||
const uint8_t *data = (const uint8_t *)_data;
|
||||
|
||||
while (length > 0) {
|
||||
size_t nbytes =
|
||||
encoder->buffer_size - encoder->buffer_position;
|
||||
if (nbytes > length)
|
||||
nbytes = length;
|
||||
|
||||
memcpy(encoder->buffer + encoder->buffer_position,
|
||||
data, nbytes);
|
||||
data += nbytes;
|
||||
length -= nbytes;
|
||||
encoder->buffer_position += nbytes;
|
||||
|
||||
if (encoder->buffer_position == encoder->buffer_size &&
|
||||
!opus_encoder_do_encode(encoder, false, error_r))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
opus_encoder_generate_head(struct opus_encoder *encoder)
|
||||
{
|
||||
unsigned char header[19];
|
||||
memcpy(header, "OpusHead", 8);
|
||||
header[8] = 1;
|
||||
header[9] = encoder->audio_format.channels;
|
||||
header[10] = 0;
|
||||
header[11] = 0;
|
||||
*(uint32_t *)(header + 12) =
|
||||
GUINT32_TO_LE(encoder->audio_format.sample_rate);
|
||||
header[16] = 0;
|
||||
header[17] = 0;
|
||||
header[18] = 0;
|
||||
|
||||
ogg_packet packet;
|
||||
packet.packet = header;
|
||||
packet.bytes = 19;
|
||||
packet.b_o_s = true;
|
||||
packet.e_o_s = false;
|
||||
packet.granulepos = 0;
|
||||
packet.packetno = encoder->packetno++;
|
||||
ogg_stream_packetin(&encoder->os, &packet);
|
||||
|
||||
encoder->flush = true;
|
||||
}
|
||||
|
||||
static void
|
||||
opus_encoder_generate_tags(struct opus_encoder *encoder)
|
||||
{
|
||||
const char *version = opus_get_version_string();
|
||||
size_t version_length = strlen(version);
|
||||
|
||||
size_t comments_size = 8 + 4 + version_length + 4;
|
||||
unsigned char *comments = (unsigned char *)g_malloc(comments_size);
|
||||
memcpy(comments, "OpusTags", 8);
|
||||
*(uint32_t *)(comments + 8) = GUINT32_TO_LE(version_length);
|
||||
memcpy(comments + 12, version, version_length);
|
||||
*(uint32_t *)(comments + 12 + version_length) = GUINT32_TO_LE(0);
|
||||
|
||||
ogg_packet packet;
|
||||
packet.packet = comments;
|
||||
packet.bytes = comments_size;
|
||||
packet.b_o_s = false;
|
||||
packet.e_o_s = false;
|
||||
packet.granulepos = 0;
|
||||
packet.packetno = encoder->packetno++;
|
||||
ogg_stream_packetin(&encoder->os, &packet);
|
||||
|
||||
g_free(comments);
|
||||
|
||||
encoder->flush = true;
|
||||
}
|
||||
|
||||
static size_t
|
||||
opus_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
|
||||
{
|
||||
struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
|
||||
unsigned char *dest = (unsigned char *)_dest;
|
||||
|
||||
if (encoder->packetno == 0)
|
||||
opus_encoder_generate_head(encoder);
|
||||
else if (encoder->packetno == 1)
|
||||
opus_encoder_generate_tags(encoder);
|
||||
|
||||
ogg_page page;
|
||||
int result;
|
||||
if (encoder->flush) {
|
||||
encoder->flush = false;
|
||||
result = ogg_stream_flush(&encoder->os, &page);
|
||||
} else
|
||||
result = ogg_stream_pageout(&encoder->os, &page);
|
||||
|
||||
if (result == 0)
|
||||
return 0;
|
||||
|
||||
assert(page.header_len > 0 || page.body_len > 0);
|
||||
|
||||
size_t nbytes = (size_t)page.header_len + (size_t)page.body_len;
|
||||
|
||||
if (nbytes > length)
|
||||
/* XXX better error handling */
|
||||
MPD_ERROR("buffer too small");
|
||||
|
||||
memcpy(dest, page.header, page.header_len);
|
||||
memcpy(dest + page.header_len, page.body, page.body_len);
|
||||
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
static const char *
|
||||
opus_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
|
||||
{
|
||||
return "audio/ogg";
|
||||
}
|
||||
|
||||
const struct encoder_plugin opus_encoder_plugin = {
|
||||
"opus",
|
||||
opus_encoder_init,
|
||||
opus_encoder_finish,
|
||||
opus_encoder_open,
|
||||
opus_encoder_close,
|
||||
opus_encoder_end,
|
||||
opus_encoder_flush,
|
||||
nullptr,
|
||||
nullptr,
|
||||
opus_encoder_write,
|
||||
opus_encoder_read,
|
||||
opus_encoder_get_mime_type,
|
||||
};
|
|
@ -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_ENCODER_OPUS_H
|
||||
#define MPD_ENCODER_OPUS_H
|
||||
|
||||
extern const struct encoder_plugin opus_encoder_plugin;
|
||||
|
||||
#endif
|
|
@ -21,6 +21,7 @@
|
|||
#include "encoder_list.h"
|
||||
#include "encoder_plugin.h"
|
||||
#include "encoder/VorbisEncoderPlugin.hxx"
|
||||
#include "encoder/OpusEncoderPlugin.hxx"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
@ -35,6 +36,9 @@ const struct encoder_plugin *const encoder_plugins[] = {
|
|||
#ifdef ENABLE_VORBIS_ENCODER
|
||||
&vorbis_encoder_plugin,
|
||||
#endif
|
||||
#ifdef HAVE_OPUS
|
||||
&opus_encoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_LAME_ENCODER
|
||||
&lame_encoder_plugin,
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue