From 4fda33a20e7afb70e6b9a6c81be879de6f715d7b Mon Sep 17 00:00:00 2001 From: Hannes Ellinger Date: Sun, 14 Jun 2015 11:35:41 +0200 Subject: [PATCH] Add encoder and decoder for opus audio codec --- client/Makefile | 4 +-- client/controller.cpp | 3 ++ client/opusDecoder.cpp | 76 ++++++++++++++++++++++++++++++++++++++++++ client/opusDecoder.h | 36 ++++++++++++++++++++ server/Makefile | 6 ++-- server/opusEncoder.cpp | 67 +++++++++++++++++++++++++++++++++++++ server/opusEncoder.h | 33 ++++++++++++++++++ server/snapServer.cpp | 20 +++++------ 8 files changed, 228 insertions(+), 17 deletions(-) create mode 100644 client/opusDecoder.cpp create mode 100644 client/opusDecoder.h create mode 100644 server/opusEncoder.cpp create mode 100644 server/opusEncoder.h diff --git a/client/Makefile b/client/Makefile index 4479f360..f2b47eb5 100644 --- a/client/Makefile +++ b/client/Makefile @@ -8,9 +8,9 @@ else endif CC = /usr/bin/g++ 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 all: $(TARGET) diff --git a/client/controller.cpp b/client/controller.cpp index 9b89d079..bdb4e777 100644 --- a/client/controller.cpp +++ b/client/controller.cpp @@ -22,6 +22,7 @@ #include #include #include "oggDecoder.h" +#include "opusDecoder.h" #include "pcmDecoder.h" #include "flacDecoder.h" #include "alsaPlayer.h" @@ -116,6 +117,8 @@ void Controller::worker() logO << "Codec: " << headerChunk->codec << "\n"; if (headerChunk->codec == "ogg") decoder_ = new OggDecoder(); + else if (headerChunk->codec == "opus") + decoder_ = new OpusDecoderWrapper(); else if (headerChunk->codec == "pcm") decoder_ = new PcmDecoder(); else if (headerChunk->codec == "flac") diff --git a/client/opusDecoder.cpp b/client/opusDecoder.cpp new file mode 100644 index 00000000..7b0f88de --- /dev/null +++ b/client/opusDecoder.cpp @@ -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 . +***/ + +#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; ipayload[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; +} diff --git a/client/opusDecoder.h b/client/opusDecoder.h new file mode 100644 index 00000000..9bd9e2db --- /dev/null +++ b/client/opusDecoder.h @@ -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 . +***/ + +#pragma once + +#include "decoder.h" +#include + + +class OpusDecoderWrapper : public Decoder +{ +public: + OpusDecoderWrapper(); + ~OpusDecoderWrapper(); + bool decode(msg::PcmChunk* chunk); + bool setHeader(msg::Header* chunk); + +private: + + OpusDecoder *dec; +}; diff --git a/server/Makefile b/server/Makefile index 27ad269f..27eb6b90 100644 --- a/server/Makefile +++ b/server/Makefile @@ -1,9 +1,9 @@ VERSION = 0.2 CC = /usr/bin/g++ 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 all: server @@ -30,4 +30,4 @@ uninstall: rm -f /usr/sbin/snapserver; \ rm -f /etc/init.d/snapserver; \ update-rc.d -f snapserver remove; \ - + diff --git a/server/opusEncoder.cpp b/server/opusEncoder.cpp new file mode 100644 index 00000000..fde97c1d --- /dev/null +++ b/server/opusEncoder.cpp @@ -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 . +***/ + +#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; ipayload[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.); +} diff --git a/server/opusEncoder.h b/server/opusEncoder.h new file mode 100644 index 00000000..5e7fc6d5 --- /dev/null +++ b/server/opusEncoder.h @@ -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 . +***/ + +#pragma once + +#include "encoder.h" +#include + + +class OpusEncoderWrapper : public Encoder +{ +public: + OpusEncoderWrapper(const msg::SampleFormat& format); + double encode(msg::PcmChunk* chunk); + +private: + OpusEncoder *enc; +}; diff --git a/server/snapServer.cpp b/server/snapServer.cpp index b53d0c4e..4fee5693 100644 --- a/server/snapServer.cpp +++ b/server/snapServer.cpp @@ -29,6 +29,7 @@ #include "message/message.h" #include "pcmEncoder.h" #include "oggEncoder.h" +#include "opusEncoder.h" #include "flacEncoder.h" #include "controlServer.h" #include "publishAvahi.h" @@ -60,8 +61,8 @@ int main(int argc, char* argv[]) ("help,h", "produce help message") ("version,v", "show version number") ("port,p", po::value(&port)->default_value(98765), "server port") - ("sampleformat,s", po::value(&sampleFormat)->default_value("44100:16:2"), "sample format") - ("codec,c", po::value(&codec)->default_value("flac"), "transport codec [flac|ogg|pcm]") + ("sampleformat,s", po::value(&sampleFormat)->default_value("48000:16:2"), "sample format") + ("codec,c", po::value(&codec)->default_value("flac"), "transport codec [flac|ogg|opus|pcm]") ("fifo,f", po::value(&fifoName)->default_value("/tmp/snapfifo"), "name of the input fifo file") ("daemon,d", po::bool_switch(&runAsDaemon)->default_value(false), "daemonize") ("buffer,b", po::value(&bufferMs)->default_value(1000), "buffer [ms]") @@ -96,22 +97,22 @@ int main(int argc, char* argv[]) std::clog.rdbuf(new Log("snapserver", LOG_DAEMON)); - using namespace std; // For atoi. - timeval tvChunk; gettimeofday(&tvChunk, NULL); long nextTick = chronos::getTickCount(); - + umask(0); mkfifo(fifoName.c_str(), 0666); msg::SampleFormat format(sampleFormat); - size_t duration = 50; + size_t duration = 10; //size_t chunkSize = duration*format.rate*format.frameSize / 1000; std::auto_ptr encoder; if (codec == "ogg") encoder.reset(new OggEncoder(sampleFormat)); else if (codec == "pcm") encoder.reset(new PcmEncoder(sampleFormat)); + else if (codec == "opus") + encoder.reset(new OpusEncoderWrapper(sampleFormat)); else if (codec == "flac") encoder.reset(new FlacEncoder(sampleFormat)); else @@ -174,7 +175,7 @@ int main(int argc, char* argv[]) if (nextTick > currentTick) { usleep((nextTick - currentTick) * 1000); - } + } else { gettimeofday(&tvChunk, NULL); @@ -198,8 +199,3 @@ int main(int argc, char* argv[]) logS(kLogNotice) << "daemon terminated." << endl; daemonShutdown(); } - - - - -