aboutsummaryrefslogblamecommitdiffstats
path: root/src/encoder/OpusEncoderPlugin.cxx
blob: 4a51dabb30e137fd165758ba25505a9ac03b0fd5 (plain) (tree)




















































































































































































































































































































































































                                                                              

                                                             

                                                               

         





































                                                                        
/*
 * 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 = ogg_stream_pageout(&encoder->os, &page);
	if (result == 0 && encoder->flush) {
		encoder->flush = false;
		result = ogg_stream_flush(&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,
};