2012-10-01 20:15:15 +02:00
|
|
|
/*
|
2017-01-03 20:48:59 +01:00
|
|
|
* Copyright 2003-2017 The Music Player Daemon Project
|
2012-10-01 20:15:15 +02:00
|
|
|
* 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"
|
2016-05-04 18:52:57 +02:00
|
|
|
#include "OggEncoder.hxx"
|
2013-08-03 21:00:50 +02:00
|
|
|
#include "AudioFormat.hxx"
|
2014-01-24 00:20:01 +01:00
|
|
|
#include "config/ConfigError.hxx"
|
2014-02-22 13:40:08 +01:00
|
|
|
#include "util/Alloc.hxx"
|
2013-10-16 21:09:19 +02:00
|
|
|
#include "system/ByteOrder.hxx"
|
2012-10-01 20:15:15 +02:00
|
|
|
|
|
|
|
#include <opus.h>
|
|
|
|
#include <ogg/ogg.h>
|
|
|
|
|
2016-10-28 21:29:01 +02:00
|
|
|
#include <stdexcept>
|
|
|
|
|
2012-10-01 20:15:15 +02:00
|
|
|
#include <assert.h>
|
2013-11-11 08:17:29 +01:00
|
|
|
#include <stdlib.h>
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
namespace {
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-04 18:52:57 +02:00
|
|
|
class OpusEncoder final : public OggEncoder {
|
2016-05-04 09:31:21 +02:00
|
|
|
const AudioFormat audio_format;
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
const size_t frame_size;
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
const size_t buffer_frames, buffer_size;
|
|
|
|
size_t buffer_position = 0;
|
|
|
|
uint8_t *const buffer;
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
::OpusEncoder *const enc;
|
2012-10-01 20:15:15 +02:00
|
|
|
|
|
|
|
unsigned char buffer2[1275 * 3 + 7];
|
|
|
|
|
2012-10-02 09:42:03 +02:00
|
|
|
int lookahead;
|
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
ogg_int64_t packetno = 0;
|
2012-10-02 09:16:44 +02:00
|
|
|
|
2018-02-17 01:18:03 +01:00
|
|
|
ogg_int64_t granulepos = 0;
|
2013-01-15 20:18:54 +01:00
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
public:
|
|
|
|
OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc);
|
|
|
|
~OpusEncoder() override;
|
2016-05-03 23:56:47 +02:00
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
/* virtual methods from class Encoder */
|
2016-11-07 09:20:12 +01:00
|
|
|
void End() override;
|
|
|
|
void Write(const void *data, size_t length) override;
|
2016-05-03 23:56:47 +02:00
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
size_t Read(void *dest, size_t length) override;
|
2016-05-03 23:56:47 +02:00
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
private:
|
2016-11-07 09:20:12 +01:00
|
|
|
void DoEncode(bool eos);
|
|
|
|
void WriteSilence(unsigned fill_frames);
|
2016-05-03 23:56:47 +02:00
|
|
|
|
|
|
|
void GenerateHead();
|
|
|
|
void GenerateTags();
|
2016-05-04 09:31:21 +02:00
|
|
|
};
|
|
|
|
|
2016-05-04 18:29:31 +02:00
|
|
|
class PreparedOpusEncoder final : public PreparedEncoder {
|
2016-05-04 09:31:21 +02:00
|
|
|
opus_int32 bitrate;
|
|
|
|
int complexity;
|
|
|
|
int signal;
|
|
|
|
|
2016-05-04 18:29:31 +02:00
|
|
|
public:
|
2016-10-28 21:29:01 +02:00
|
|
|
PreparedOpusEncoder(const ConfigBlock &block);
|
2016-05-04 18:29:31 +02:00
|
|
|
|
|
|
|
/* virtual methods from class PreparedEncoder */
|
2016-11-07 09:20:12 +01:00
|
|
|
Encoder *Open(AudioFormat &audio_format) override;
|
2016-05-04 18:29:31 +02:00
|
|
|
|
|
|
|
const char *GetMimeType() const override {
|
|
|
|
return "audio/ogg";
|
|
|
|
}
|
2012-10-01 20:15:15 +02:00
|
|
|
};
|
|
|
|
|
2016-10-28 21:29:01 +02:00
|
|
|
PreparedOpusEncoder::PreparedOpusEncoder(const ConfigBlock &block)
|
2012-10-01 20:15:15 +02:00
|
|
|
{
|
2015-01-21 22:13:44 +01:00
|
|
|
const char *value = block.GetBlockValue("bitrate", "auto");
|
2012-10-01 20:15:15 +02:00
|
|
|
if (strcmp(value, "auto") == 0)
|
2016-05-03 23:56:47 +02:00
|
|
|
bitrate = OPUS_AUTO;
|
2012-10-01 20:15:15 +02:00
|
|
|
else if (strcmp(value, "max") == 0)
|
2016-05-03 23:56:47 +02:00
|
|
|
bitrate = OPUS_BITRATE_MAX;
|
2012-10-01 20:15:15 +02:00
|
|
|
else {
|
|
|
|
char *endptr;
|
2016-05-03 23:56:47 +02:00
|
|
|
bitrate = strtoul(value, &endptr, 10);
|
2012-10-01 20:15:15 +02:00
|
|
|
if (endptr == value || *endptr != 0 ||
|
2016-10-28 21:29:01 +02:00
|
|
|
bitrate < 500 || bitrate > 512000)
|
|
|
|
throw std::runtime_error("Invalid bit rate");
|
2012-10-01 20:15:15 +02:00
|
|
|
}
|
|
|
|
|
2016-05-03 23:56:47 +02:00
|
|
|
complexity = block.GetBlockValue("complexity", 10u);
|
2016-10-28 21:29:01 +02:00
|
|
|
if (complexity > 10)
|
|
|
|
throw std::runtime_error("Invalid complexity");
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2015-01-21 22:13:44 +01:00
|
|
|
value = block.GetBlockValue("signal", "auto");
|
2012-10-01 20:15:15 +02:00
|
|
|
if (strcmp(value, "auto") == 0)
|
2016-05-03 23:56:47 +02:00
|
|
|
signal = OPUS_AUTO;
|
2012-10-01 20:15:15 +02:00
|
|
|
else if (strcmp(value, "voice") == 0)
|
2016-05-03 23:56:47 +02:00
|
|
|
signal = OPUS_SIGNAL_VOICE;
|
2012-10-01 20:15:15 +02:00
|
|
|
else if (strcmp(value, "music") == 0)
|
2016-05-03 23:56:47 +02:00
|
|
|
signal = OPUS_SIGNAL_MUSIC;
|
2016-10-28 21:29:01 +02:00
|
|
|
else
|
|
|
|
throw std::runtime_error("Invalid signal");
|
2012-10-01 20:15:15 +02:00
|
|
|
}
|
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
static PreparedEncoder *
|
2016-10-28 21:29:01 +02:00
|
|
|
opus_encoder_init(const ConfigBlock &block)
|
2012-10-01 20:15:15 +02:00
|
|
|
{
|
2016-10-28 21:29:01 +02:00
|
|
|
return new PreparedOpusEncoder(block);
|
2012-10-01 20:15:15 +02:00
|
|
|
}
|
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
OpusEncoder::OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc)
|
2016-05-04 18:52:57 +02:00
|
|
|
:OggEncoder(false),
|
2016-05-04 09:31:21 +02:00
|
|
|
audio_format(_audio_format),
|
|
|
|
frame_size(_audio_format.GetFrameSize()),
|
|
|
|
buffer_frames(_audio_format.sample_rate / 50),
|
|
|
|
buffer_size(frame_size * buffer_frames),
|
|
|
|
buffer((unsigned char *)xalloc(buffer_size)),
|
|
|
|
enc(_enc)
|
|
|
|
{
|
|
|
|
opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&lookahead));
|
|
|
|
}
|
|
|
|
|
|
|
|
Encoder *
|
2016-11-07 09:20:12 +01:00
|
|
|
PreparedOpusEncoder::Open(AudioFormat &audio_format)
|
2012-10-01 20:15:15 +02:00
|
|
|
{
|
|
|
|
/* libopus supports only 48 kHz */
|
2016-05-04 09:31:21 +02:00
|
|
|
audio_format.sample_rate = 48000;
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
if (audio_format.channels > 2)
|
|
|
|
audio_format.channels = 1;
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
switch (audio_format.format) {
|
2013-08-03 21:00:50 +02:00
|
|
|
case SampleFormat::S16:
|
|
|
|
case SampleFormat::FLOAT:
|
2012-10-01 20:15:15 +02:00
|
|
|
break;
|
|
|
|
|
2013-08-03 21:00:50 +02:00
|
|
|
case SampleFormat::S8:
|
2016-05-04 09:31:21 +02:00
|
|
|
audio_format.format = SampleFormat::S16;
|
2012-10-01 20:15:15 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2016-05-04 09:31:21 +02:00
|
|
|
audio_format.format = SampleFormat::FLOAT;
|
2012-10-01 20:15:15 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2013-08-10 18:02:44 +02:00
|
|
|
int error_code;
|
2016-05-04 09:31:21 +02:00
|
|
|
auto *enc = opus_encoder_create(audio_format.sample_rate,
|
|
|
|
audio_format.channels,
|
|
|
|
OPUS_APPLICATION_AUDIO,
|
|
|
|
&error_code);
|
2016-11-07 09:20:12 +01:00
|
|
|
if (enc == nullptr)
|
|
|
|
throw std::runtime_error(opus_strerror(error_code));
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-03 23:56:47 +02:00
|
|
|
opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate));
|
|
|
|
opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity));
|
|
|
|
opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal));
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
return new OpusEncoder(audio_format, enc);
|
2012-10-01 20:15:15 +02:00
|
|
|
}
|
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
OpusEncoder::~OpusEncoder()
|
2016-05-03 23:56:47 +02:00
|
|
|
{
|
|
|
|
free(buffer);
|
|
|
|
opus_encoder_destroy(enc);
|
|
|
|
}
|
|
|
|
|
2016-11-07 09:20:12 +01:00
|
|
|
void
|
|
|
|
OpusEncoder::DoEncode(bool eos)
|
2012-10-01 20:15:15 +02:00
|
|
|
{
|
2016-05-03 23:56:47 +02:00
|
|
|
assert(buffer_position == buffer_size);
|
2012-10-01 20:15:15 +02:00
|
|
|
|
|
|
|
opus_int32 result =
|
2016-05-03 23:56:47 +02:00
|
|
|
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));
|
2016-11-07 09:20:12 +01:00
|
|
|
if (result < 0)
|
|
|
|
throw std::runtime_error("Opus encoder error");
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-03 23:56:47 +02:00
|
|
|
granulepos += buffer_frames;
|
2012-10-02 09:16:44 +02:00
|
|
|
|
2012-10-01 20:15:15 +02:00
|
|
|
ogg_packet packet;
|
2016-05-03 23:56:47 +02:00
|
|
|
packet.packet = buffer2;
|
2012-10-01 20:15:15 +02:00
|
|
|
packet.bytes = result;
|
|
|
|
packet.b_o_s = false;
|
|
|
|
packet.e_o_s = eos;
|
2016-05-03 23:56:47 +02:00
|
|
|
packet.granulepos = granulepos;
|
|
|
|
packet.packetno = packetno++;
|
|
|
|
stream.PacketIn(packet);
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-03 23:56:47 +02:00
|
|
|
buffer_position = 0;
|
2012-10-01 20:15:15 +02:00
|
|
|
}
|
|
|
|
|
2016-11-07 09:20:12 +01:00
|
|
|
void
|
|
|
|
OpusEncoder::End()
|
2012-10-01 20:15:15 +02:00
|
|
|
{
|
2016-05-09 14:13:45 +02:00
|
|
|
Flush();
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-03 23:56:47 +02:00
|
|
|
memset(buffer + buffer_position, 0,
|
|
|
|
buffer_size - buffer_position);
|
|
|
|
buffer_position = buffer_size;
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-11-07 09:20:12 +01:00
|
|
|
DoEncode(true);
|
2012-10-01 20:15:15 +02:00
|
|
|
}
|
|
|
|
|
2016-11-07 09:20:12 +01:00
|
|
|
void
|
|
|
|
OpusEncoder::WriteSilence(unsigned fill_frames)
|
2016-05-03 23:56:47 +02:00
|
|
|
{
|
|
|
|
size_t fill_bytes = fill_frames * frame_size;
|
2012-10-02 09:42:03 +02:00
|
|
|
|
|
|
|
while (fill_bytes > 0) {
|
2016-05-03 23:56:47 +02:00
|
|
|
size_t nbytes = buffer_size - buffer_position;
|
2012-10-02 09:42:03 +02:00
|
|
|
if (nbytes > fill_bytes)
|
|
|
|
nbytes = fill_bytes;
|
|
|
|
|
2016-05-03 23:56:47 +02:00
|
|
|
memset(buffer + buffer_position, 0, nbytes);
|
|
|
|
buffer_position += nbytes;
|
2012-10-02 09:42:03 +02:00
|
|
|
fill_bytes -= nbytes;
|
|
|
|
|
2016-11-07 09:20:12 +01:00
|
|
|
if (buffer_position == buffer_size)
|
|
|
|
DoEncode(false);
|
2012-10-02 09:42:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-07 09:20:12 +01:00
|
|
|
void
|
|
|
|
OpusEncoder::Write(const void *_data, size_t length)
|
2012-10-01 20:15:15 +02:00
|
|
|
{
|
|
|
|
const uint8_t *data = (const uint8_t *)_data;
|
|
|
|
|
2016-05-03 23:56:47 +02:00
|
|
|
if (lookahead > 0) {
|
2012-10-02 09:42:03 +02:00
|
|
|
/* generate some silence at the beginning of the
|
|
|
|
stream */
|
|
|
|
|
2016-05-03 23:56:47 +02:00
|
|
|
assert(buffer_position == 0);
|
2012-10-02 09:42:03 +02:00
|
|
|
|
2016-11-07 09:20:12 +01:00
|
|
|
WriteSilence(lookahead);
|
2016-05-03 23:56:47 +02:00
|
|
|
lookahead = 0;
|
2012-10-02 09:42:03 +02:00
|
|
|
}
|
|
|
|
|
2012-10-01 20:15:15 +02:00
|
|
|
while (length > 0) {
|
2016-05-03 23:56:47 +02:00
|
|
|
size_t nbytes = buffer_size - buffer_position;
|
2012-10-01 20:15:15 +02:00
|
|
|
if (nbytes > length)
|
|
|
|
nbytes = length;
|
|
|
|
|
2016-05-03 23:56:47 +02:00
|
|
|
memcpy(buffer + buffer_position, data, nbytes);
|
2012-10-01 20:15:15 +02:00
|
|
|
data += nbytes;
|
|
|
|
length -= nbytes;
|
2016-05-03 23:56:47 +02:00
|
|
|
buffer_position += nbytes;
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-11-07 09:20:12 +01:00
|
|
|
if (buffer_position == buffer_size)
|
|
|
|
DoEncode(false);
|
2012-10-01 20:15:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-03 23:56:47 +02:00
|
|
|
void
|
|
|
|
OpusEncoder::GenerateHead()
|
2012-10-01 20:15:15 +02:00
|
|
|
{
|
|
|
|
unsigned char header[19];
|
|
|
|
memcpy(header, "OpusHead", 8);
|
|
|
|
header[8] = 1;
|
2016-05-03 23:56:47 +02:00
|
|
|
header[9] = audio_format.channels;
|
|
|
|
*(uint16_t *)(header + 10) = ToLE16(lookahead);
|
|
|
|
*(uint32_t *)(header + 12) = ToLE32(audio_format.sample_rate);
|
2012-10-01 20:15:15 +02:00
|
|
|
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;
|
2016-05-03 23:56:47 +02:00
|
|
|
packet.packetno = packetno++;
|
|
|
|
stream.PacketIn(packet);
|
2016-05-09 14:13:45 +02:00
|
|
|
Flush();
|
2012-10-01 20:15:15 +02:00
|
|
|
}
|
|
|
|
|
2016-05-03 23:56:47 +02:00
|
|
|
void
|
|
|
|
OpusEncoder::GenerateTags()
|
2012-10-01 20:15:15 +02:00
|
|
|
{
|
|
|
|
const char *version = opus_get_version_string();
|
|
|
|
size_t version_length = strlen(version);
|
|
|
|
|
|
|
|
size_t comments_size = 8 + 4 + version_length + 4;
|
2014-02-22 13:40:08 +01:00
|
|
|
unsigned char *comments = (unsigned char *)xalloc(comments_size);
|
2012-10-01 20:15:15 +02:00
|
|
|
memcpy(comments, "OpusTags", 8);
|
2013-10-16 21:09:19 +02:00
|
|
|
*(uint32_t *)(comments + 8) = ToLE32(version_length);
|
2012-10-01 20:15:15 +02:00
|
|
|
memcpy(comments + 12, version, version_length);
|
2013-10-16 21:09:19 +02:00
|
|
|
*(uint32_t *)(comments + 12 + version_length) = ToLE32(0);
|
2012-10-01 20:15:15 +02:00
|
|
|
|
|
|
|
ogg_packet packet;
|
|
|
|
packet.packet = comments;
|
|
|
|
packet.bytes = comments_size;
|
|
|
|
packet.b_o_s = false;
|
|
|
|
packet.e_o_s = false;
|
|
|
|
packet.granulepos = 0;
|
2016-05-03 23:56:47 +02:00
|
|
|
packet.packetno = packetno++;
|
|
|
|
stream.PacketIn(packet);
|
2016-05-09 14:13:45 +02:00
|
|
|
Flush();
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2014-02-22 13:40:08 +01:00
|
|
|
free(comments);
|
2012-10-01 20:15:15 +02:00
|
|
|
}
|
|
|
|
|
2016-05-03 23:56:47 +02:00
|
|
|
size_t
|
|
|
|
OpusEncoder::Read(void *dest, size_t length)
|
2012-10-01 20:15:15 +02:00
|
|
|
{
|
2016-05-03 23:56:47 +02:00
|
|
|
if (packetno == 0)
|
|
|
|
GenerateHead();
|
|
|
|
else if (packetno == 1)
|
|
|
|
GenerateTags();
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-09 14:11:51 +02:00
|
|
|
return OggEncoder::Read(dest, length);
|
2016-05-03 23:56:47 +02:00
|
|
|
}
|
2012-10-01 20:15:15 +02:00
|
|
|
|
2016-05-04 09:31:21 +02:00
|
|
|
}
|
|
|
|
|
2013-07-30 09:04:05 +02:00
|
|
|
const EncoderPlugin opus_encoder_plugin = {
|
2012-10-01 20:15:15 +02:00
|
|
|
"opus",
|
|
|
|
opus_encoder_init,
|
|
|
|
};
|