Add encoder and decoder for opus audio codec

This commit is contained in:
Hannes Ellinger 2015-06-14 11:35:41 +02:00
parent 21a9aa3e32
commit 4fda33a20e
8 changed files with 228 additions and 17 deletions

View file

@ -8,9 +8,9 @@ else
endif endif
CC = /usr/bin/g++ CC = /usr/bin/g++
CFLAGS = -std=gnu++0x -static-libgcc -static-libstdc++ -Wall -Wno-unused-function -O3 -D_REENTRANT -DVERSION=\"$(VERSION)\" -I.. CFLAGS = -std=gnu++0x -static-libgcc -static-libstdc++ -Wall -Wno-unused-function -O3 -D_REENTRANT -DVERSION=\"$(VERSION)\" -I..
LDFLAGS = -lrt -lpthread -lboost_system -lboost_program_options -lasound -logg -lvorbis -lvorbisenc -lFLAC -lavahi-client -lavahi-common LDFLAGS = -lrt -lpthread -lboost_system -lboost_program_options -lasound -logg -lopus -lvorbis -lvorbisenc -lFLAC -lavahi-client -lavahi-common
OBJ = snapClient.o stream.o alsaPlayer.o clientConnection.o timeProvider.o oggDecoder.o pcmDecoder.o flacDecoder.o controller.o browseAvahi.o ../message/pcmChunk.o ../common/log.o ../message/sampleFormat.o OBJ = snapClient.o stream.o alsaPlayer.o clientConnection.o timeProvider.o opusDecoder.o oggDecoder.o pcmDecoder.o flacDecoder.o controller.o browseAvahi.o ../message/pcmChunk.o ../common/log.o ../message/sampleFormat.o
BIN = snapclient BIN = snapclient
all: $(TARGET) all: $(TARGET)

View file

@ -22,6 +22,7 @@
#include <memory> #include <memory>
#include <unistd.h> #include <unistd.h>
#include "oggDecoder.h" #include "oggDecoder.h"
#include "opusDecoder.h"
#include "pcmDecoder.h" #include "pcmDecoder.h"
#include "flacDecoder.h" #include "flacDecoder.h"
#include "alsaPlayer.h" #include "alsaPlayer.h"
@ -116,6 +117,8 @@ void Controller::worker()
logO << "Codec: " << headerChunk->codec << "\n"; logO << "Codec: " << headerChunk->codec << "\n";
if (headerChunk->codec == "ogg") if (headerChunk->codec == "ogg")
decoder_ = new OggDecoder(); decoder_ = new OggDecoder();
else if (headerChunk->codec == "opus")
decoder_ = new OpusDecoderWrapper();
else if (headerChunk->codec == "pcm") else if (headerChunk->codec == "pcm")
decoder_ = new PcmDecoder(); decoder_ = new PcmDecoder();
else if (headerChunk->codec == "flac") else if (headerChunk->codec == "flac")

76
client/opusDecoder.cpp Normal file
View file

@ -0,0 +1,76 @@
/***
This file is part of snapcast
Copyright (C) 2015 Hannes Ellinger
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 3 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, see <http://www.gnu.org/licenses/>.
***/
#include "opusDecoder.h"
#include "common/log.h"
OpusDecoderWrapper::OpusDecoderWrapper() : Decoder() {
int error;
dec = opus_decoder_create(48000, 2, &error); // fixme
if(error != 0) {
logE << "Failed to initialize opus decoder: " << error << '\n';
logE << " Rate: " << 48000 << '\n';
logE << " Channels: " << 2 << '\n';
}
}
OpusDecoderWrapper::~OpusDecoderWrapper() {}
bool OpusDecoderWrapper::decode(msg::PcmChunk* chunk) {
int samples = 480;
// reserve space for decoded audio
opus_int16 pcm[samples*2];
msg::PcmChunk* decodedChunk = new msg::PcmChunk();
decodedChunk->payloadSize = samples*4;
decodedChunk->payload = (char*)realloc(decodedChunk->payload, decodedChunk->payloadSize);
// decode
int frame_size = opus_decode(dec, (unsigned char*)chunk->payload, chunk->payloadSize, pcm, samples, 0);
if (frame_size < 0) {
logE << "Failed to decode chunk: " << frame_size << '\n';
logE << " IN size: " << chunk->payloadSize << '\n';
logE << " OUT size: " << decodedChunk->payloadSize << '\n';
}
else {
// logD << "Decoded chunk: size " << chunk->payloadSize << " bytes, decoded " << frame_size << " bytes" << '\n';
// copy encoded data to chunk
chunk->payloadSize = samples*4;
chunk->payload = (char*)realloc(chunk->payload, chunk->payloadSize);
// uninterleave audio
for(int i=0; i<samples*2; i++) {
chunk->payload[2*i+1] = (char)(pcm[i] >> 8);
chunk->payload[2*i] = (char)(pcm[i] & 0x00ff);
}
}
return true;
}
bool OpusDecoderWrapper::setHeader(msg::Header* chunk)
{
return true;
}

36
client/opusDecoder.h Normal file
View file

@ -0,0 +1,36 @@
/***
This file is part of snapcast
Copyright (C) 2015 Hannes Ellinger
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 3 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, see <http://www.gnu.org/licenses/>.
***/
#pragma once
#include "decoder.h"
#include <opus/opus.h>
class OpusDecoderWrapper : public Decoder
{
public:
OpusDecoderWrapper();
~OpusDecoderWrapper();
bool decode(msg::PcmChunk* chunk);
bool setHeader(msg::Header* chunk);
private:
OpusDecoder *dec;
};

View file

@ -1,9 +1,9 @@
VERSION = 0.2 VERSION = 0.2
CC = /usr/bin/g++ CC = /usr/bin/g++
CFLAGS = -std=gnu++0x -Wall -Wno-unused-function -O3 -D_REENTRANT -DVERSION=\"$(VERSION)\" -I.. CFLAGS = -std=gnu++0x -Wall -Wno-unused-function -O3 -D_REENTRANT -DVERSION=\"$(VERSION)\" -I..
LDFLAGS = -lrt -lpthread -lboost_system -lboost_program_options -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common LDFLAGS = -lrt -lpthread -lboost_system -lboost_program_options -lopus -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common
OBJ = snapServer.o controlServer.o flacEncoder.o pcmEncoder.o oggEncoder.o serverSession.o publishAvahi.o ../common/log.o ../message/pcmChunk.o ../message/sampleFormat.o OBJ = snapServer.o controlServer.o flacEncoder.o pcmEncoder.o opusEncoder.o oggEncoder.o serverSession.o publishAvahi.o ../common/log.o ../message/pcmChunk.o ../message/sampleFormat.o
BIN = snapserver BIN = snapserver
all: server all: server

67
server/opusEncoder.cpp Normal file
View file

@ -0,0 +1,67 @@
/***
This file is part of snapcast
Copyright (C) 2015 Hannes Ellinger
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 3 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, see <http://www.gnu.org/licenses/>.
***/
#include "opusEncoder.h"
#include "common/log.h"
OpusEncoderWrapper::OpusEncoderWrapper(const msg::SampleFormat& format) : Encoder(format)
{
int error;
headerChunk = new msg::Header("opus");
enc = opus_encoder_create(format.rate, format.channels, OPUS_APPLICATION_RESTRICTED_LOWDELAY, &error);
if(error != 0) {
logE << "Failed to initialize opus encoder: " << error << '\n';
logE << " Rate: " << format.rate << '\n';
logE << " Channels: " << format.channels << '\n';
}
}
double OpusEncoderWrapper::encode(msg::PcmChunk* chunk)
{
int samples = chunk->payloadSize/4;
opus_int16 pcm[samples*2];
unsigned char encoded[chunk->payloadSize];
// get 16 bit samples
for(int i=0; i<samples*2; i++)
{
pcm[i] = (opus_int16)(((opus_int16)chunk->payload[2*i+1] << 8) | (0x00ff & (opus_int16)chunk->payload[2*i]));
}
// encode
int len = opus_encode(enc, pcm, samples, encoded, chunk->payloadSize);
// logD << "Encoded: size " << chunk->payloadSize << " bytes, encoded: " << len << " bytes" << '\n';
if(len>0) {
// copy encoded data to chunk
chunk->payloadSize = len;
chunk->payload = (char*)realloc(chunk->payload, chunk->payloadSize);
memcpy(chunk->payload, encoded, len);
}
else {
logE << "Failed to encode chunk: " << len << '\n';
logE << " Frame size: " << samples/2 << '\n';
logE << " Max bytes: " << chunk->payloadSize << '\n';
}
// return chunk duration
return (double)samples / ((double)sampleFormat.rate / 1000.);
}

33
server/opusEncoder.h Normal file
View file

@ -0,0 +1,33 @@
/***
This file is part of snapcast
Copyright (C) 2015 Hannes Ellinger
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 3 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, see <http://www.gnu.org/licenses/>.
***/
#pragma once
#include "encoder.h"
#include <opus/opus.h>
class OpusEncoderWrapper : public Encoder
{
public:
OpusEncoderWrapper(const msg::SampleFormat& format);
double encode(msg::PcmChunk* chunk);
private:
OpusEncoder *enc;
};

View file

@ -29,6 +29,7 @@
#include "message/message.h" #include "message/message.h"
#include "pcmEncoder.h" #include "pcmEncoder.h"
#include "oggEncoder.h" #include "oggEncoder.h"
#include "opusEncoder.h"
#include "flacEncoder.h" #include "flacEncoder.h"
#include "controlServer.h" #include "controlServer.h"
#include "publishAvahi.h" #include "publishAvahi.h"
@ -60,8 +61,8 @@ int main(int argc, char* argv[])
("help,h", "produce help message") ("help,h", "produce help message")
("version,v", "show version number") ("version,v", "show version number")
("port,p", po::value<size_t>(&port)->default_value(98765), "server port") ("port,p", po::value<size_t>(&port)->default_value(98765), "server port")
("sampleformat,s", po::value<string>(&sampleFormat)->default_value("44100:16:2"), "sample format") ("sampleformat,s", po::value<string>(&sampleFormat)->default_value("48000:16:2"), "sample format")
("codec,c", po::value<string>(&codec)->default_value("flac"), "transport codec [flac|ogg|pcm]") ("codec,c", po::value<string>(&codec)->default_value("flac"), "transport codec [flac|ogg|opus|pcm]")
("fifo,f", po::value<string>(&fifoName)->default_value("/tmp/snapfifo"), "name of the input fifo file") ("fifo,f", po::value<string>(&fifoName)->default_value("/tmp/snapfifo"), "name of the input fifo file")
("daemon,d", po::bool_switch(&runAsDaemon)->default_value(false), "daemonize") ("daemon,d", po::bool_switch(&runAsDaemon)->default_value(false), "daemonize")
("buffer,b", po::value<int32_t>(&bufferMs)->default_value(1000), "buffer [ms]") ("buffer,b", po::value<int32_t>(&bufferMs)->default_value(1000), "buffer [ms]")
@ -96,8 +97,6 @@ int main(int argc, char* argv[])
std::clog.rdbuf(new Log("snapserver", LOG_DAEMON)); std::clog.rdbuf(new Log("snapserver", LOG_DAEMON));
using namespace std; // For atoi.
timeval tvChunk; timeval tvChunk;
gettimeofday(&tvChunk, NULL); gettimeofday(&tvChunk, NULL);
long nextTick = chronos::getTickCount(); long nextTick = chronos::getTickCount();
@ -105,13 +104,15 @@ int main(int argc, char* argv[])
umask(0); umask(0);
mkfifo(fifoName.c_str(), 0666); mkfifo(fifoName.c_str(), 0666);
msg::SampleFormat format(sampleFormat); msg::SampleFormat format(sampleFormat);
size_t duration = 50; size_t duration = 10;
//size_t chunkSize = duration*format.rate*format.frameSize / 1000; //size_t chunkSize = duration*format.rate*format.frameSize / 1000;
std::auto_ptr<Encoder> encoder; std::auto_ptr<Encoder> encoder;
if (codec == "ogg") if (codec == "ogg")
encoder.reset(new OggEncoder(sampleFormat)); encoder.reset(new OggEncoder(sampleFormat));
else if (codec == "pcm") else if (codec == "pcm")
encoder.reset(new PcmEncoder(sampleFormat)); encoder.reset(new PcmEncoder(sampleFormat));
else if (codec == "opus")
encoder.reset(new OpusEncoderWrapper(sampleFormat));
else if (codec == "flac") else if (codec == "flac")
encoder.reset(new FlacEncoder(sampleFormat)); encoder.reset(new FlacEncoder(sampleFormat));
else else
@ -198,8 +199,3 @@ int main(int argc, char* argv[])
logS(kLogNotice) << "daemon terminated." << endl; logS(kLogNotice) << "daemon terminated." << endl;
daemonShutdown(); daemonShutdown();
} }