aboutsummaryrefslogtreecommitdiffstats
path: root/src/encoder
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2012-10-01 20:15:15 +0200
committerMax Kellermann <max@duempel.org>2012-10-02 00:45:24 +0200
commitd793b7c03ff7444b0aab485e5895573b4a4ff0a1 (patch)
tree94d3bc20c57b90045ea67bfb866f42ae671f6b24 /src/encoder
parent9a715267ad1a86de4c522ff6ed5fd11827132c77 (diff)
downloadmpd-d793b7c03ff7444b0aab485e5895573b4a4ff0a1.tar.gz
mpd-d793b7c03ff7444b0aab485e5895573b4a4ff0a1.tar.xz
mpd-d793b7c03ff7444b0aab485e5895573b4a4ff0a1.zip
encoder/opus: new encoder plugin for the Opus codec
Diffstat (limited to 'src/encoder')
-rw-r--r--src/encoder/OpusEncoderPlugin.cxx417
-rw-r--r--src/encoder/OpusEncoderPlugin.hxx25
2 files changed, 442 insertions, 0 deletions
diff --git a/src/encoder/OpusEncoderPlugin.cxx b/src/encoder/OpusEncoderPlugin.cxx
new file mode 100644
index 000000000..c967a24a7
--- /dev/null
+++ b/src/encoder/OpusEncoderPlugin.cxx
@@ -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,
+};
diff --git a/src/encoder/OpusEncoderPlugin.hxx b/src/encoder/OpusEncoderPlugin.hxx
new file mode 100644
index 000000000..f54377202
--- /dev/null
+++ b/src/encoder/OpusEncoderPlugin.hxx
@@ -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