mpd/src/encoder/plugins/OpusEncoderPlugin.cxx

460 lines
9.7 KiB
C++
Raw Normal View History

/*
2016-02-26 17:54:05 +01:00
* Copyright 2003-2016 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"
#include "lib/xiph/OggStream.hxx"
#include "lib/xiph/OggSerial.hxx"
#include "../EncoderAPI.hxx"
2013-08-03 21:00:50 +02:00
#include "AudioFormat.hxx"
2014-01-24 00:20:01 +01:00
#include "config/ConfigError.hxx"
#include "util/Alloc.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "system/ByteOrder.hxx"
#include <opus.h>
#include <ogg/ogg.h>
#include <assert.h>
#include <stdlib.h>
2016-05-03 23:53:42 +02:00
struct OpusEncoder {
/** the base class */
2013-07-30 09:04:05 +02:00
Encoder encoder;
/* configuration */
opus_int32 bitrate;
int complexity;
int signal;
/* runtime information */
2013-08-03 21:00:50 +02:00
AudioFormat 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];
OggStream stream;
int lookahead;
ogg_int64_t packetno;
ogg_int64_t granulepos;
2013-01-15 20:18:54 +01:00
2016-05-03 23:53:42 +02:00
OpusEncoder():encoder(opus_encoder_plugin), granulepos(0) {}
bool Configure(const ConfigBlock &block, Error &error);
bool Open(AudioFormat &audio_format, Error &error);
void Close();
bool DoEncode(bool eos, Error &error);
bool End(Error &error);
bool Flush(Error &error);
bool WriteSilence(unsigned fill_frames, Error &error);
bool Write(const void *_data, size_t length, Error &error);
void GenerateHead();
void GenerateTags();
size_t Read(void *dest, size_t length);
};
static constexpr Domain opus_encoder_domain("opus_encoder");
bool
OpusEncoder::Configure(const ConfigBlock &block, Error &error)
{
const char *value = block.GetBlockValue("bitrate", "auto");
if (strcmp(value, "auto") == 0)
bitrate = OPUS_AUTO;
else if (strcmp(value, "max") == 0)
bitrate = OPUS_BITRATE_MAX;
else {
char *endptr;
bitrate = strtoul(value, &endptr, 10);
if (endptr == value || *endptr != 0 ||
bitrate < 500 || bitrate > 512000) {
error.Set(config_domain, "Invalid bit rate");
return false;
}
}
complexity = block.GetBlockValue("complexity", 10u);
if (complexity > 10) {
error.Format(config_domain, "Invalid complexity");
return false;
}
value = block.GetBlockValue("signal", "auto");
if (strcmp(value, "auto") == 0)
signal = OPUS_AUTO;
else if (strcmp(value, "voice") == 0)
signal = OPUS_SIGNAL_VOICE;
else if (strcmp(value, "music") == 0)
signal = OPUS_SIGNAL_MUSIC;
else {
error.Format(config_domain, "Invalid signal");
return false;
}
return true;
}
2013-07-30 09:04:05 +02:00
static Encoder *
opus_encoder_init(const ConfigBlock &block, Error &error)
{
2016-05-03 23:53:42 +02:00
auto *encoder = new OpusEncoder();
/* load configuration from "block" */
if (!encoder->Configure(block, error)) {
/* configuration has failed, roll back and return error */
2013-01-15 20:18:54 +01:00
delete encoder;
return nullptr;
}
return &encoder->encoder;
}
static void
2013-07-30 09:04:05 +02:00
opus_encoder_finish(Encoder *_encoder)
{
2016-05-03 23:53:42 +02:00
auto *encoder = (OpusEncoder *)_encoder;
/* the real libopus cleanup was already performed by
opus_encoder_close(), so no real work here */
2013-01-15 20:18:54 +01:00
delete encoder;
}
bool
OpusEncoder::Open(AudioFormat &_audio_format, Error &error)
{
/* libopus supports only 48 kHz */
_audio_format.sample_rate = 48000;
if (_audio_format.channels > 2)
_audio_format.channels = 1;
switch (_audio_format.format) {
2013-08-03 21:00:50 +02:00
case SampleFormat::S16:
case SampleFormat::FLOAT:
break;
2013-08-03 21:00:50 +02:00
case SampleFormat::S8:
_audio_format.format = SampleFormat::S16;
break;
default:
_audio_format.format = SampleFormat::FLOAT;
break;
}
audio_format = _audio_format;
frame_size = _audio_format.GetFrameSize();
int error_code;
enc = opus_encoder_create(_audio_format.sample_rate,
_audio_format.channels,
OPUS_APPLICATION_AUDIO,
&error_code);
if (enc == nullptr) {
error.Set(opus_encoder_domain, error_code,
opus_strerror(error_code));
return false;
}
opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate));
opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity));
opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal));
opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&lookahead));
buffer_frames = _audio_format.sample_rate / 50;
buffer_size = frame_size * buffer_frames;
buffer_position = 0;
buffer = (unsigned char *)xalloc(buffer_size);
stream.Initialize(GenerateOggSerial());
packetno = 0;
return true;
}
static bool
opus_encoder_open(Encoder *_encoder,
AudioFormat &audio_format,
Error &error)
{
auto &encoder = *(OpusEncoder *)_encoder;
return encoder.Open(audio_format, error);
}
void
OpusEncoder::Close()
{
stream.Deinitialize();
free(buffer);
opus_encoder_destroy(enc);
}
static void
2013-07-30 09:04:05 +02:00
opus_encoder_close(Encoder *_encoder)
{
auto &encoder = *(OpusEncoder *)_encoder;
encoder.Close();
}
bool
OpusEncoder::DoEncode(bool eos, Error &error)
{
assert(buffer_position == buffer_size);
opus_int32 result =
audio_format.format == SampleFormat::S16
? opus_encode(enc,
(const opus_int16 *)buffer,
buffer_frames,
buffer2,
sizeof(buffer2))
: opus_encode_float(enc,
(const float *)buffer,
buffer_frames,
buffer2,
sizeof(buffer2));
if (result < 0) {
error.Set(opus_encoder_domain, "Opus encoder error");
return false;
}
granulepos += buffer_frames;
ogg_packet packet;
packet.packet = buffer2;
packet.bytes = result;
packet.b_o_s = false;
packet.e_o_s = eos;
packet.granulepos = granulepos;
packet.packetno = packetno++;
stream.PacketIn(packet);
buffer_position = 0;
return true;
}
bool
OpusEncoder::End(Error &error)
{
stream.Flush();
memset(buffer + buffer_position, 0,
buffer_size - buffer_position);
buffer_position = buffer_size;
return DoEncode(true, error);
}
static bool
opus_encoder_end(Encoder *_encoder, Error &error)
{
auto &encoder = *(OpusEncoder *)_encoder;
return encoder.End(error);
}
bool
OpusEncoder::Flush(gcc_unused Error &error)
{
stream.Flush();
return true;
}
static bool
opus_encoder_flush(Encoder *_encoder, Error &error)
{
auto &encoder = *(OpusEncoder *)_encoder;
return encoder.Flush(error);
}
bool
OpusEncoder::WriteSilence(unsigned fill_frames, Error &error)
{
size_t fill_bytes = fill_frames * frame_size;
while (fill_bytes > 0) {
size_t nbytes = buffer_size - buffer_position;
if (nbytes > fill_bytes)
nbytes = fill_bytes;
memset(buffer + buffer_position, 0, nbytes);
buffer_position += nbytes;
fill_bytes -= nbytes;
if (buffer_position == buffer_size &&
!DoEncode(false, error))
return false;
}
return true;
}
bool
OpusEncoder::Write(const void *_data, size_t length, Error &error)
{
const uint8_t *data = (const uint8_t *)_data;
if (lookahead > 0) {
/* generate some silence at the beginning of the
stream */
assert(buffer_position == 0);
if (!WriteSilence(lookahead, error))
return false;
lookahead = 0;
}
while (length > 0) {
size_t nbytes = buffer_size - buffer_position;
if (nbytes > length)
nbytes = length;
memcpy(buffer + buffer_position, data, nbytes);
data += nbytes;
length -= nbytes;
buffer_position += nbytes;
if (buffer_position == buffer_size &&
!DoEncode(false, error))
return false;
}
return true;
}
static bool
opus_encoder_write(Encoder *_encoder,
const void *data, size_t length,
Error &error)
{
auto &encoder = *(OpusEncoder *)_encoder;
return encoder.Write(data, length, error);
}
void
OpusEncoder::GenerateHead()
{
unsigned char header[19];
memcpy(header, "OpusHead", 8);
header[8] = 1;
header[9] = audio_format.channels;
*(uint16_t *)(header + 10) = ToLE16(lookahead);
*(uint32_t *)(header + 12) = ToLE32(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 = packetno++;
stream.PacketIn(packet);
stream.Flush();
}
void
OpusEncoder::GenerateTags()
{
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 *)xalloc(comments_size);
memcpy(comments, "OpusTags", 8);
*(uint32_t *)(comments + 8) = ToLE32(version_length);
memcpy(comments + 12, version, version_length);
*(uint32_t *)(comments + 12 + version_length) = ToLE32(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 = packetno++;
stream.PacketIn(packet);
stream.Flush();
free(comments);
}
size_t
OpusEncoder::Read(void *dest, size_t length)
{
if (packetno == 0)
GenerateHead();
else if (packetno == 1)
GenerateTags();
return stream.PageOut(dest, length);
}
static size_t
opus_encoder_read(Encoder *_encoder, void *dest, size_t length)
{
auto &encoder = *(OpusEncoder *)_encoder;
return encoder.Read(dest, length);
}
static const char *
2013-08-04 23:48:01 +02:00
opus_encoder_get_mime_type(gcc_unused Encoder *_encoder)
{
return "audio/ogg";
}
2013-07-30 09:04:05 +02:00
const EncoderPlugin 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,
};