Merge branch 'develop' into nonroot

This commit is contained in:
badaix 2016-10-22 20:24:40 +02:00
commit 8ae203eece
35 changed files with 1169 additions and 1010 deletions

View file

@ -8,8 +8,8 @@ android {
applicationId "de.badaix.snapcast"
minSdkVersion 16
targetSdkVersion 23
versionCode 7
versionName "0.8.0"
versionCode 900
versionName "0.9.0"
multiDexEnabled true
}
buildTypes {

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because it is too large Load diff

View file

@ -56,7 +56,6 @@ public class ClientItem extends LinearLayout implements SeekBar.OnSeekBarChangeL
ibMute.setOnClickListener(this);
ibOverflow = (ImageButton) findViewById(R.id.ibOverflow);
ibOverflow.setOnClickListener(this);
volumeSeekBar.setMax(100);
setClient(client);
volumeSeekBar.setOnSeekBarChangeListener(this);
this.server = server;

View file

@ -67,7 +67,8 @@
android:id="@+id/volumeSeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"/>
android:layout_gravity="center_vertical"
android:max="100"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>

View file

@ -86,7 +86,8 @@
android:id="@+id/volumeSeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"/>
android:layout_gravity="center_vertical"
android:max="100"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>

View file

@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0'
classpath 'com.android.tools.build:gradle:2.2.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View file

@ -1,4 +1,4 @@
VERSION = 0.8.0
VERSION = 0.9.0
TARGET = snapclient
SHELL = /bin/bash
@ -30,7 +30,7 @@ else ifeq ($(TARGET), OPENWRT)
STRIP = echo
CXXFLAGS += -pthread -DNO_CPP11_STRING -DHAS_TREMOR -DHAS_ALSA -DHAS_AVAHI -DHAS_DAEMON
LDFLAGS = -lasound -lvorbisidec -logg -lFLAC -lavahi-client -lavahi-common -latomic
OBJ += player/alsaPlayer.o browseAvahi.o
OBJ += player/alsaPlayer.o browseZeroConf/browseAvahi.o
else ifeq ($(TARGET), MACOS)
@ -38,7 +38,7 @@ CXX = /usr/bin/g++
STRIP = strip
CXXFLAGS += -DHAS_OGG -DHAS_COREAUDIO -DFREEBSD -DMACOS -DHAS_BONJOUR -I/usr/local/include -Wno-unused-local-typedef -Wno-deprecated
LDFLAGS = -logg -lvorbis -lFLAC -L/usr/local/lib -framework AudioToolbox -framework CoreFoundation
OBJ += player/coreAudioPlayer.o browseBonjour.o
OBJ += player/coreAudioPlayer.o browseZeroConf/browseBonjour.o
else
@ -46,7 +46,7 @@ CXX = /usr/bin/g++
STRIP = strip
CXXFLAGS += -pthread -DHAS_OGG -DHAS_ALSA -DHAS_AVAHI -DHAS_DAEMON
LDFLAGS = -lrt -lasound -logg -lvorbis -lFLAC -lavahi-client -lavahi-common -static-libgcc -static-libstdc++
OBJ += player/alsaPlayer.o browseAvahi.o
OBJ += player/alsaPlayer.o browseZeroConf/browseAvahi.o
endif
@ -77,6 +77,15 @@ ifdef DESTDIR
install:
$(MAKE) installfiles
else ifeq ($(TARGET), MACOS)
install:
echo macOS
install -g wheel -o root $(BIN) $(TARGET_DIR)/local/bin/$(BIN)
install -g wheel -o root $(BIN).1 $(TARGET_DIR)/local/share/man/man1/$(BIN).1
install -g wheel -o root debian/$(BIN).plist /Library/LaunchAgents/de.badaix.snapcast.$(BIN).plist
launchctl load /Library/LaunchAgents/de.badaix.snapcast.$(BIN).plist
else
install:
@ -120,6 +129,17 @@ adduser:
adduser --quiet --ingroup audio --system --no-create-home --home /var/lib/snapcast snapcast; \
fi; \
ifeq ($(TARGET), MACOS)
uninstall:
@launchctl unload /Library/LaunchAgents/de.badaix.snapcast.$(BIN).plist; \
killall -9 $(BIN); \
rm -f $(TARGET_DIR)/local/bin/$(BIN); \
rm -f $(TARGET_DIR)/local/share/man/man1/$(BIN).1; \
rm -f /Library/LaunchAgents/de.badaix.snapcast.$(BIN).plist; \
else
uninstall:
rm -f $(TARGET_DIR)/share/man/man1/$(BIN).1
@if [[ `systemctl` =~ -\.mount ]]; then \
@ -134,6 +154,8 @@ uninstall:
rm -rf /var/run/$(BIN)
$(MAKE) deluser
endif
uninstallsysv:
@/etc/init.d/$(BIN) stop; \
killall -9 $(BIN); \

View file

@ -1,3 +1,18 @@
snapclient (0.9.0) unstable; urgency=low
* Features
-Added (experimental) support for macOS (make TARGET=MACOS)
* Bugfixes
-Android client: Fixed crash on Nougat (Issue #97)
-Fixed FreeBSD compile error for Snapserver (Issue #107)
-Snapserver randomly dropped JSON control messages
-Long command line options (like --sampleformat)
didn't work on some systems (Issue #103)
* General
-Updated Android NDK to revision 13
-- Johannes Pohl <johannes.pohl@badaix.de> Sat, 22 Oct 2016 00:13:37 +0200
snapclient (0.8.0) unstable; urgency=low
* Features

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>de.badaix.snapcast.snapclient</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/snapclient</string>
<string>-d</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>

View file

@ -50,13 +50,14 @@ void CoreAudioPlayer::playerCallback(AudioQueueRef queue, AudioQueueBufferRef bu
/// Estimate the playout delay by checking the number of frames left in the buffer
/// and add ms_ (= complete buffer size). Based on trying.
AudioTimeStamp timestamp;
AudioQueueGetCurrentTime(queue, timeLine, &timestamp, NULL);
AudioQueueGetCurrentTime(queue, timeLine_, &timestamp, NULL);
size_t bufferedFrames = (frames_ - ((uint64_t)timestamp.mSampleTime % frames_)) % frames_;
size_t bufferedMs = bufferedFrames * 1000 / pubStream_->getFormat().rate + (ms_ * (NUM_BUFFERS - 1));
/// 15ms DAC delay. Based on trying.
bufferedMs += 15;
// logO << "buffered: " << bufferedFrames << ", ms: " << bufferedMs << ", mSampleTime: " << timestamp.mSampleTime << "\n";
/// TODO: sometimes this bufferedMS or AudioTimeStamp wraps around 1s (i.e. we're 1s out of sync (behind)) and recovers later on
chronos::usec delay(bufferedMs * 1000);
char *buffer = (char*)bufferRef->mAudioData;
if (!pubStream_->getPlayerChunk(buffer, delay, frames_))
@ -98,7 +99,7 @@ void CoreAudioPlayer::worker()
AudioQueueRef queue;
AudioQueueNewOutput(&format, callback, this, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &queue);
AudioQueueCreateTimeline(queue, &timeLine);
AudioQueueCreateTimeline(queue, &timeLine_);
// Apple recommends this as buffer size:
// https://developer.apple.com/library/content/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html
@ -121,7 +122,7 @@ void CoreAudioPlayer::worker()
}
logE << "CoreAudioPlayer::worker\n";
AudioQueueCreateTimeline(queue, &timeLine);
AudioQueueCreateTimeline(queue, &timeLine_);
AudioQueueStart(queue, NULL);
CFRunLoopRun();
}

View file

@ -45,7 +45,7 @@ public:
protected:
virtual void worker();
AudioQueueTimelineRef timeLine;
AudioQueueTimelineRef timeLine_;
size_t ms_;
size_t frames_;
size_t buff_size_;

View file

@ -21,7 +21,7 @@
#include "popl.hpp"
#include "controller.h"
#include "browsemDNS.h"
#include "browseZeroConf/browsemDNS.h"
#ifdef HAS_ALSA
#include "player/alsaPlayer.h"
@ -68,7 +68,7 @@ PcmDevice getPcmDevice(const std::string& soundcard)
int main (int argc, char **argv)
{
#ifdef MACOS
#pragma message "Warning: the Mac OS support is experimental and might not be maintained"
#pragma message "Warning: the macOS support is experimental and might not be maintained"
#endif
try
{

View file

@ -21,6 +21,10 @@
#include <chrono>
#include <sys/time.h>
#ifdef MACOS
#include <mach/clock.h>
#include <mach/mach.h>
#endif
namespace chronos
{
@ -49,9 +53,12 @@ namespace chronos
inline static long getTickCount()
{
#ifdef MACOS
struct timeval now;
gettimeofday(&now, NULL);
return now.tv_sec*1000 + now.tv_usec / 1000;
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
return mts.tv_sec*1000 + mts.tv_nsec / 1000000;
#else
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);

View file

@ -104,30 +104,50 @@ Start and stop the server with `sudo service snapserver start` and `sudo service
*Warning: macOS support is experimental*
Install Xcode from the App Store
1. Install Xcode from the App Store
2. Install [Homebrew](http://brew.sh)
3. Install the required libs
```
$ brew install flac libvorbis
```
###Build Snapclient
Install the required libs:
$ brew install flac libvorbis
`cd` into the Snapclient src-root directory:
$ cd <snapcast dir>/client
$ make TARGET=MACOS
Install Snapclient
$ sudo make install
This will copy the client binary to `/usr/local/bin` and create a Launch Agent to start the client as a daemon.
###Build Snapserver
`cd` into the Snapserver src-root directory:
$ cd <snapcast dir>/server
$ make TARGET=MACOS
Install Snapserver
$ sudo make install
This will copy the server binary to `/usr/local/bin` and create a Launch Agent to start the server as a daemon.
##Android (Cross compile)
Cross compilation for Android is done with the [Android NDK](http://developer.android.com/tools/sdk/ndk/index.html) on a Linux host machine.
###Android NDK setup
http://developer.android.com/ndk/guides/standalone_toolchain.html
1. Download NDK: `http://dl.google.com/android/repository/android-ndk-r12b-linux-x86_64.zip`
2. Extract to: `/SOME/LOCAL/PATH/android-ndk-r12b`
1. Download NDK: `https://dl.google.com/android/repository/android-ndk-r13-linux-x86_64.zip`
2. Extract to: `/SOME/LOCAL/PATH/android-ndk-r13`
3. Setup toolchain somewhere in your home dir (`<android-ndk dir>`):
````
$ cd /SOME/LOCAL/PATH/android-ndk-r10e/build/tools
$ ./make-standalone-toolchain.sh --arch=arm --platform=android-14 --install-dir=<android-ndk dir> --ndk-dir=/SOME/LOCAL/PATH/android-ndk-r12b
$ cd /SOME/LOCAL/PATH/android-ndk-r13/build/tools
$ ./make_standalone_toolchain.py --arch arm --api 14 --install-dir <android-ndk dir>
````
###Build Snapclient
@ -176,7 +196,7 @@ Within the OpenWrt directory create symbolic links to the Snapcast source direct
Build Snapcast:
$ cd <buildroot dir>
$ make package/sxx/snapcast/clean V=s
$ make package/sxx/snapcast/compile -j1 V=s
$ make package/sxx/snapcast/clean
$ make package/sxx/snapcast/compile
The packaged `ipk` files are in `<buildroot dir>/bin/ar71xx/packages/base/snap[client|server]_0.6.0_ar71xx.ipk`
The packaged `ipk` files are in `<buildroot dir>/bin/ar71xx/packages/base/snap[client|server]_x.x.x_ar71xx.ipk`

View file

@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/target.mk
PKG_NAME := snapcast
PKG_VERSION := 0.8.0
PKG_VERSION := 0.9.0
PKG_RELEASE := $(PKG_SOURCE_VERSION)
PKG_USE_MIPS16 := 0

View file

@ -1,4 +1,4 @@
VERSION = 0.8.0
VERSION = 0.9.0
TARGET = snapserver
SHELL = /bin/bash
@ -9,7 +9,8 @@ else
TARGET_DIR ?= /usr
endif
CXXFLAGS += -std=c++0x -Wall -Wno-unused-function -O3 -pthread -DASIO_STANDALONE -DVERSION=\"$(VERSION)\" -I. -I.. -I../externals/asio/asio/include -I../externals/popl/include
CXXFLAGS += -std=c++0x -Wall -Wno-unused-function -O3 -DASIO_STANDALONE -DVERSION=\"$(VERSION)\" -I. -I.. -I../externals/asio/asio/include -I../externals/popl/include
OBJ = snapServer.o config.o controlServer.o controlSession.o streamServer.o streamSession.o json/jsonrpc.o streamreader/streamUri.o streamreader/streamManager.o streamreader/pcmStream.o streamreader/pipeStream.o streamreader/fileStream.o encoder/encoderFactory.o encoder/flacEncoder.o encoder/pcmEncoder.o encoder/oggEncoder.o ../common/log.o ../common/sampleFormat.o ../message/pcmChunk.o
ifeq ($(ENDIAN), BIG)
CXXFLAGS += -DIS_BIG_ENDIAN
@ -18,27 +19,37 @@ endif
ifeq ($(TARGET), OPENWRT)
STRIP = echo
CXXFLAGS += -DNO_CPP11_STRING
CXXFLAGS += -DNO_CPP11_STRING -DHAS_AVAHI -pthread
LDFLAGS = -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common -latomic
OBJ += publishZeroConf/publishAvahi.o
else ifeq ($(TARGET), FREEBSD)
SHELL = /usr/local/bin/bash
CXX = /usr/local/bin/g++
CXXFLAGS += -DNO_CPP11_STRING -DFREEBSD
STRIP = strip
STRIP = echo
CXXFLAGS += -DNO_CPP11_STRING -DFREEBSD -DHAS_AVAHI -pthread
LDFLAGS = -lrt -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common -static-libgcc -static-libstdc++
OBJ += publishZeroConf/publishAvahi.o
else ifeq ($(TARGET), MACOS)
CXX = /usr/bin/g++
STRIP = strip
CXXFLAGS += -DFREEBSD -DMACOS -DHAS_BONJOUR -Wno-deprecated -I/usr/local/include
LDFLAGS = -lvorbis -lvorbisenc -logg -lFLAC -L/usr/local/lib
OBJ += publishZeroConf/publishBonjour.o
else
CXX = /usr/bin/g++
STRIP = strip
CXXFLAGS += -DHAS_AVAHI -pthread
LDFLAGS = -lrt -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common -static-libgcc -static-libstdc++
OBJ += publishZeroConf/publishAvahi.o
endif
OBJ = snapServer.o config.o controlServer.o controlSession.o streamServer.o streamSession.o json/jsonrpc.o streamreader/streamUri.o streamreader/streamManager.o streamreader/pcmStream.o streamreader/pipeStream.o streamreader/fileStream.o encoder/encoderFactory.o encoder/flacEncoder.o encoder/pcmEncoder.o encoder/oggEncoder.o publishAvahi.o ../common/log.o ../common/sampleFormat.o ../message/pcmChunk.o
BIN = snapserver
all: $(TARGET)
@ -73,6 +84,15 @@ install:
install -g wheel -o root -m 555 $(BIN).1 $(TARGET_DIR)/local/man/man1/$(BIN).1
install -g wheel -o root -m 555 debian/$(BIN).bsd $(TARGET_DIR)/local/etc/rc.d/$(BIN)
else ifeq ($(TARGET), MACOS)
install:
echo macOS
install -g wheel -o root $(BIN) $(TARGET_DIR)/local/bin/$(BIN)
install -g wheel -o root $(BIN).1 $(TARGET_DIR)/local/share/man/man1/$(BIN).1
install -g wheel -o root debian/$(BIN).plist /Library/LaunchAgents/de.badaix.snapcast.$(BIN).plist
launchctl load /Library/LaunchAgents/de.badaix.snapcast.$(BIN).plist
else
install:
@ -119,6 +139,15 @@ uninstall:
rm -f $(TARGET_DIR)/local/man/man1/$(BIN).1; \
rm -f $(TARGET_DIR)/local/etc/rc.d/$(BIN); \
else ifeq ($(TARGET), MACOS)
uninstall:
@launchctl unload /Library/LaunchAgents/de.badaix.snapcast.$(BIN).plist; \
killall -9 $(BIN); \
rm -f $(TARGET_DIR)/local/bin/$(BIN); \
rm -f $(TARGET_DIR)/local/share/man/man1/$(BIN).1; \
rm -f /Library/LaunchAgents/de.badaix.snapcast.$(BIN).plist; \
else
uninstall:

View file

@ -30,7 +30,7 @@
using json = nlohmann::json;
template<typename T>
T jGet(json j, const std::string& what, const T& def)
T jGet(const json& j, const std::string& what, const T& def)
{
try
{

View file

@ -1,3 +1,18 @@
snapserver (0.9.0) unstable; urgency=low
* Features
-Added (experimental) support for macOS (make TARGET=MACOS)
* Bugfixes
-Android client: Fixed crash on Nougat (Issue #97)
-Fixed FreeBSD compile error for Snapserver (Issue #107)
-Snapserver randomly dropped JSON control messages
-Long command line options (like --sampleformat)
didn't work on some systems (Issue #103)
* General
-Updated Android NDK to revision 13
-- Johannes Pohl <johannes.pohl@badaix.de> Sat, 22 Oct 2016 00:13:37 +0200
snapserver (0.8.0) unstable; urgency=low
* Features

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>de.badaix.snapcast.snapserver</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/snapserver</string>
<string>-d</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>

View file

@ -28,7 +28,7 @@
using namespace std;
OggEncoder::OggEncoder(const std::string& codecOptions) : Encoder(codecOptions), lastGranulepos(0)
OggEncoder::OggEncoder(const std::string& codecOptions) : Encoder(codecOptions), lastGranulepos_(0)
{
}
@ -56,7 +56,7 @@ void OggEncoder::encode(const msg::PcmChunk* chunk)
double res = 0;
logD << "payload: " << chunk->payloadSize << "\tframes: " << chunk->getFrameCount() << "\tduration: " << chunk->duration<chronos::msec>().count() << "\n";
int frames = chunk->getFrameCount();
float **buffer=vorbis_analysis_buffer(&vd, frames);
float **buffer=vorbis_analysis_buffer(&vd_, frames);
/* uninterleave samples */
for (size_t channel = 0; channel < sampleFormat_.channels; ++channel)
@ -82,7 +82,7 @@ void OggEncoder::encode(const msg::PcmChunk* chunk)
}
/* tell the library how much we actually submitted */
vorbis_analysis_wrote(&vd, frames);
vorbis_analysis_wrote(&vd_, frames);
msg::PcmChunk* oggChunk = new msg::PcmChunk(chunk->format, 0);
@ -90,36 +90,36 @@ void OggEncoder::encode(const msg::PcmChunk* chunk)
more involved (potentially parallel) processing. Get a single
block for encoding now */
size_t pos = 0;
while (vorbis_analysis_blockout(&vd, &vb)==1)
while (vorbis_analysis_blockout(&vd_, &vb_)==1)
{
/* analysis, assume we want to use bitrate management */
vorbis_analysis(&vb, NULL);
vorbis_bitrate_addblock(&vb);
vorbis_analysis(&vb_, NULL);
vorbis_bitrate_addblock(&vb_);
while (vorbis_bitrate_flushpacket(&vd, &op))
while (vorbis_bitrate_flushpacket(&vd_, &op_))
{
/* weld the packet into the bitstream */
ogg_stream_packetin(&os, &op);
ogg_stream_packetin(&os_, &op_);
/* write out pages (if any) */
while (true)
{
int result = ogg_stream_flush(&os, &og);
int result = ogg_stream_flush(&os_, &og_);
if (result == 0)
break;
res = os.granulepos - lastGranulepos;
res = os_.granulepos - lastGranulepos_;
size_t nextLen = pos + og.header_len + og.body_len;
size_t nextLen = pos + og_.header_len + og_.body_len;
// make chunk larger
if (oggChunk->payloadSize < nextLen)
oggChunk->payload = (char*)realloc(oggChunk->payload, nextLen);
memcpy(oggChunk->payload + pos, og.header, og.header_len);
pos += og.header_len;
memcpy(oggChunk->payload + pos, og.body, og.body_len);
pos += og.body_len;
memcpy(oggChunk->payload + pos, og_.header, og_.header_len);
pos += og_.header_len;
memcpy(oggChunk->payload + pos, og_.body, og_.body_len);
pos += og_.body_len;
if (ogg_page_eos(&og))
if (ogg_page_eos(&og_))
break;
}
}
@ -129,7 +129,7 @@ void OggEncoder::encode(const msg::PcmChunk* chunk)
{
res /= (sampleFormat_.rate / 1000.);
// logO << "res: " << res << "\n";
lastGranulepos = os.granulepos;
lastGranulepos_ = os_.granulepos;
// make oggChunk smaller
oggChunk->payload = (char*)realloc(oggChunk->payload, pos);
oggChunk->payloadSize = pos;
@ -164,7 +164,7 @@ void OggEncoder::initEncoder()
}
/********** Encode setup ************/
vorbis_info_init(&vi);
vorbis_info_init(&vi_);
/* choose an encoding mode. A few possibilities commented out, one
actually used: */
@ -195,7 +195,7 @@ void OggEncoder::initEncoder()
*********************************************************************/
int ret = vorbis_encode_init_vbr(&vi, sampleFormat_.channels, sampleFormat_.rate, quality);
int ret = vorbis_encode_init_vbr(&vi_, sampleFormat_.channels, sampleFormat_.rate, quality);
/* do not continue if setup failed; this can happen if we ask for a
mode that libVorbis does not support (eg, too low a bitrate, etc,
@ -205,20 +205,20 @@ void OggEncoder::initEncoder()
throw SnapException("failed to init encoder");
/* add a comment */
vorbis_comment_init(&vc);
vorbis_comment_add_tag(&vc, "TITLE", "SnapStream");
vorbis_comment_add_tag(&vc, "VERSION", VERSION);
vorbis_comment_add_tag(&vc, "SAMPLE_FORMAT", sampleFormat_.getFormat().c_str());
vorbis_comment_init(&vc_);
vorbis_comment_add_tag(&vc_, "TITLE", "SnapStream");
vorbis_comment_add_tag(&vc_, "VERSION", VERSION);
vorbis_comment_add_tag(&vc_, "SAMPLE_FORMAT", sampleFormat_.getFormat().c_str());
/* set up the analysis state and auxiliary encoding storage */
vorbis_analysis_init(&vd, &vi);
vorbis_block_init(&vd, &vb);
vorbis_analysis_init(&vd_, &vi_);
vorbis_block_init(&vd_, &vb_);
/* set up our packet->stream encoder */
/* pick a random serial number; that way we can more likely build
chained streams just by concatenation */
srand(time(NULL));
ogg_stream_init(&os, rand());
ogg_stream_init(&os_, rand());
/* Vorbis streams begin with three headers; the initial header (with
most of the codec setup parameters) which is mandated by the Ogg
@ -231,10 +231,10 @@ void OggEncoder::initEncoder()
ogg_packet header_comm;
ogg_packet header_code;
vorbis_analysis_headerout(&vd, &vc, &header, &header_comm, &header_code);
ogg_stream_packetin(&os, &header);
ogg_stream_packetin(&os, &header_comm);
ogg_stream_packetin(&os, &header_code);
vorbis_analysis_headerout(&vd_, &vc_, &header, &header_comm, &header_code);
ogg_stream_packetin(&os_, &header);
ogg_stream_packetin(&os_, &header_comm);
ogg_stream_packetin(&os_, &header_code);
/* This ensures the actual
* audio data will start on a new page, as per spec
@ -243,16 +243,16 @@ void OggEncoder::initEncoder()
headerChunk_.reset(new msg::CodecHeader("ogg"));
while (true)
{
int result = ogg_stream_flush(&os, &og);
int result = ogg_stream_flush(&os_, &og_);
if (result == 0)
break;
headerChunk_->payloadSize += og.header_len + og.body_len;
headerChunk_->payloadSize += og_.header_len + og_.body_len;
headerChunk_->payload = (char*)realloc(headerChunk_->payload, headerChunk_->payloadSize);
logD << "HeadLen: " << og.header_len << ", bodyLen: " << og.body_len << ", result: " << result << "\n";
memcpy(headerChunk_->payload + pos, og.header, og.header_len);
pos += og.header_len;
memcpy(headerChunk_->payload + pos, og.body, og.body_len);
pos += og.body_len;
logD << "HeadLen: " << og_.header_len << ", bodyLen: " << og_.body_len << ", result: " << result << "\n";
memcpy(headerChunk_->payload + pos, og_.header, og_.header_len);
pos += og_.header_len;
memcpy(headerChunk_->payload + pos, og_.body, og_.body_len);
pos += og_.body_len;
}
}

View file

@ -35,19 +35,17 @@ protected:
virtual void initEncoder();
private:
ogg_stream_state os; /* take physical pages, weld into a logical
stream of packets */
ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */
ogg_packet op; /* one raw packet of data for decode */
ogg_stream_state os_; /// take physical pages, weld into a logical stream of packets
ogg_page og_; /// one Ogg bitstream page. Vorbis packets are inside
ogg_packet op_; /// one raw packet of data for decode
vorbis_info vi; /* struct that stores all the static vorbis bitstream
settings */
vorbis_comment vc; /* struct that stores all the user comments */
vorbis_info vi_; /// struct that stores all the static vorbis bitstream settings
vorbis_comment vc_; /// struct that stores all the user comments
vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
vorbis_block vb; /* local working space for packet->PCM decode */
vorbis_dsp_state vd_; /// central working state for the packet->PCM decoder
vorbis_block vb_; /// local working space for packet->PCM decode
ogg_int64_t lastGranulepos;
ogg_int64_t lastGranulepos_;
};

View file

@ -27,8 +27,8 @@ static AvahiEntryGroup *group;
static AvahiSimplePoll *simple_poll;
static char* name;
PublishAvahi::PublishAvahi(const std::string& serviceName) :
client(NULL), serviceName_(serviceName), active_(false)
PublishAvahi::PublishAvahi(const std::string& serviceName) : PublishmDNS(serviceName),
client_(NULL), active_(false)
{
group = NULL;
simple_poll = NULL;
@ -36,24 +36,23 @@ PublishAvahi::PublishAvahi(const std::string& serviceName) :
}
void PublishAvahi::publish(const std::vector<AvahiService>& services)
void PublishAvahi::publish(const std::vector<mDNSService>& services)
{
this->services = services;
services_ = services;
AvahiClient *client = NULL;
int error;
/* Allocate main loop object */
/// Allocate main loop object
if (!(simple_poll = avahi_simple_poll_new()))
{
///TODO: error handling
logE << "Failed to create simple poll object.\n";
}
/* Allocate a new client */
client = avahi_client_new(avahi_simple_poll_get(simple_poll), AVAHI_CLIENT_IGNORE_USER_CONFIG, client_callback, this, &error);
/// Allocate a new client
int error;
client_ = avahi_client_new(avahi_simple_poll_get(simple_poll), AVAHI_CLIENT_IGNORE_USER_CONFIG, client_callback, this, &error);
/* Check wether creating the client object succeeded */
if (!client)
/// Check wether creating the client object succeeded
if (!client_)
{
logE << "Failed to create client: " << avahi_strerror(error) << "\n";
}
@ -74,8 +73,8 @@ PublishAvahi::~PublishAvahi()
active_ = false;
pollThread_.join();
if (client)
avahi_client_free(client);
if (client_)
avahi_client_free(client_);
if (simple_poll)
avahi_simple_poll_free(simple_poll);
@ -89,27 +88,26 @@ void PublishAvahi::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState
assert(g == group || group == NULL);
group = g;
/* Called whenever the entry group state changes */
/// Called whenever the entry group state changes
switch (state)
{
case AVAHI_ENTRY_GROUP_ESTABLISHED :
/* The entry group has been established successfully */
/// The entry group has been established successfully
logO << "Service '" << name << "' successfully established.\n";
break;
case AVAHI_ENTRY_GROUP_COLLISION : {
case AVAHI_ENTRY_GROUP_COLLISION :
{
char *n;
/* A service name collision with a remote service
* happened. Let's pick a new name */
/// A service name collision with a remote service happened. Let's pick a new name
n = avahi_alternative_service_name(name);
avahi_free(name);
name = n;
logO << "Service name collision, renaming service to '" << name << "'\n";
/* And recreate the services */
/// And recreate the services
static_cast<PublishAvahi*>(userdata)->create_services(avahi_entry_group_get_client(g));
break;
}
@ -118,7 +116,7 @@ void PublishAvahi::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState
logE << "Entry group failure: " << avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))) << "\n";
/* Some kind of failure happened while we were registering our services */
/// Some kind of failure happened while we were registering our services
avahi_simple_poll_quit(simple_poll);
break;
@ -130,13 +128,10 @@ void PublishAvahi::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState
void PublishAvahi::create_services(AvahiClient *c)
{
char *n, r[128];
int ret;
assert(c);
char *n;
/* If this is the first time we're called, let's create a new
* entry group if necessary */
/// If this is the first time we're called, let's create a new entry group if necessary
if (!group)
{
if (!(group = avahi_entry_group_new(c, entry_group_callback, this)))
@ -145,54 +140,34 @@ void PublishAvahi::create_services(AvahiClient *c)
goto fail;
}
}
/* If the group is empty (either because it was just created, or
* because it was reset previously, add our entries. */
/// If the group is empty (either because it was just created, or because it was reset previously, add our entries.
int ret;
if (avahi_entry_group_is_empty(group))
{
logO << "Adding service '" << name << "'\n";
/* Create some random TXT data */
snprintf(r, sizeof(r), "random=%i", rand());
/* We will now add two services and one subtype to the entry
* group. The two services have the same name, but differ in
* the service type (IPP vs. BSD LPR). Only services with the
* same name should be put in the same entry group. */
/* Add the service for IPP */
/* if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_PUBLISH_UNIQUE, name, "_ipp._tcp", NULL, NULL, 651, "test=blah", r, NULL)) < 0)
/// We will now add two services and one subtype to the entry group
for (const auto& service: services_)
{
if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, service.name_.c_str(), NULL, NULL, service.port_, NULL)) < 0)
{
if (ret == AVAHI_ERR_COLLISION)
goto collision;
fprintf(stderr, "Failed to add _ipp._tcp service: %s\n", avahi_strerror(ret));
goto fail;
}
*/
/* Add the same service for BSD LPR */
for (size_t n=0; n<services.size(); ++n)
{
if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, services[n].proto_, AvahiPublishFlags(0), name, services[n].name_.c_str(), NULL, NULL, services[n].port_, NULL)) < 0)
{
if (ret == AVAHI_ERR_COLLISION)
goto collision;
logE << "Failed to add " << services[n].name_ << " service: " << avahi_strerror(ret) << "\n";
logE << "Failed to add " << service.name_ << " service: " << avahi_strerror(ret) << "\n";
goto fail;
}
}
/* Add an additional (hypothetic) subtype */
/// Add an additional (hypothetic) subtype
/* if ((ret = avahi_entry_group_add_service_subtype(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, "_printer._tcp", NULL, "_magic._sub._printer._tcp") < 0))
{
fprintf(stderr, "Failed to add subtype _magic._sub._printer._tcp: %s\n", avahi_strerror(ret));
goto fail;
}
*/
/* Tell the server to register the service */
/// Tell the server to register the service
if ((ret = avahi_entry_group_commit(group)) < 0)
{
logE << "Failed to commit entry group: " << avahi_strerror(ret) << "\n";
@ -204,8 +179,7 @@ void PublishAvahi::create_services(AvahiClient *c)
collision:
/* A service name collision with a local service happened. Let's
* pick a new name */
/// A service name collision with a local service happened. Let's pick a new name
n = avahi_alternative_service_name(name);
avahi_free(name);
name = n;
@ -226,14 +200,12 @@ void PublishAvahi::client_callback(AvahiClient *c, AvahiClientState state, AVAHI
{
assert(c);
/* Called whenever the client or server state changes */
/// Called whenever the client or server state changes
switch (state)
{
case AVAHI_CLIENT_S_RUNNING:
/* The server has startup successfully and registered its host
* name on the network, so it's time to create our services */
/// The server has startup successfully and registered its host name on the network, so it's time to create our services
static_cast<PublishAvahi*>(userdata)->create_services(c);
break;
@ -241,25 +213,20 @@ void PublishAvahi::client_callback(AvahiClient *c, AvahiClientState state, AVAHI
logE << "Client failure: " << avahi_strerror(avahi_client_errno(c)) << "\n";
avahi_simple_poll_quit(simple_poll);
break;
case AVAHI_CLIENT_S_COLLISION:
/* Let's drop our registered services. When the server is back
* in AVAHI_SERVER_RUNNING state we will register them
* again with the new host name. */
/// Let's drop our registered services. When the server is back
/// in AVAHI_SERVER_RUNNING state we will register them again with the new host name.
case AVAHI_CLIENT_S_REGISTERING:
/* The server records are now being established. This
* might be caused by a host name change. We need to wait
* for our own records to register until the host name is
* properly esatblished. */
/// The server records are now being established. This might be caused by a host name change. We need to wait
/// for our own records to register until the host name is properly esatblished.
if (group)
avahi_entry_group_reset(group);
break;
case AVAHI_CLIENT_CONNECTING:

View file

@ -33,36 +33,26 @@
#include <thread>
#include <atomic>
class PublishAvahi;
struct AvahiService
{
AvahiService(const std::string& name, size_t port, int proto = AVAHI_PROTO_UNSPEC) : name_(name), port_(port), proto_(proto)
{
}
#include "publishmDNS.h"
std::string name_;
size_t port_;
int proto_;
};
class PublishAvahi
class PublishAvahi : public PublishmDNS
{
public:
PublishAvahi(const std::string& serviceName);
~PublishAvahi();
void publish(const std::vector<AvahiService>& services);
virtual ~PublishAvahi();
virtual void publish(const std::vector<mDNSService>& services);
private:
static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata);
static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata);
void create_services(AvahiClient *c);
AvahiClient* client;
std::string serviceName_;
std::thread pollThread_;
void worker();
AvahiClient* client_;
std::thread pollThread_;
std::atomic<bool> active_;
std::vector<AvahiService> services;
std::vector<mDNSService> services_;
};

View file

@ -0,0 +1,159 @@
/***
This file is part of snapcast
Copyright (C) 2014-2016 Johannes Pohl
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 <cstdlib>
#include <thread>
#include "publishBonjour.h"
#include "common/log.h"
typedef union { unsigned char b[2]; unsigned short NotAnInteger; } Opaque16;
PublishBonjour::PublishBonjour(const std::string& serviceName) : PublishmDNS(serviceName), active_(false)
{
/// dns-sd -R Snapcast _snapcast._tcp local 1704
/// dns-sd -R Snapcast _snapcast-jsonrpc._tcp local 1705
}
PublishBonjour::~PublishBonjour()
{
active_ = false;
pollThread_.join();
for (auto client: clients)
{
if (client)
DNSServiceRefDeallocate(client);
}
}
void PublishBonjour::worker()
{
// int dns_sd_fd = client ? DNSServiceRefSockFD(client) : -1;
// 1. Set up the fd_set as usual here.
// This example client has no file descriptors of its own,
// but a real application would call FD_SET to add them to the set here
fd_set readfds;
FD_ZERO(&readfds);
std::vector<int> dns_sd_fds;
int nfds = -1;
for (size_t n=0; n<clients.size(); ++n)
{
int dns_sd_fd = DNSServiceRefSockFD(clients[n]);
dns_sd_fds.push_back(dns_sd_fd);
if (nfds < dns_sd_fd)
nfds = dns_sd_fd;
// 2. Add the fd for our client(s) to the fd_set
FD_SET(dns_sd_fd, &readfds);
}
++nfds;
// 3. Set up the timeout.
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100*1000;
active_ = true;
while (active_)
{
FD_ZERO(&readfds);
for (size_t n=0; n<dns_sd_fds.size(); ++n)
FD_SET(dns_sd_fds[n], &readfds);
int result = select(nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv);
if (result > 0)
{
for (size_t n=0; n<dns_sd_fds.size(); ++n)
{
if (clients[n] && FD_ISSET(dns_sd_fds[n], &readfds))
{
DNSServiceErrorType err = DNSServiceProcessResult(clients[n]);
if (err)
{
logE << "DNSServiceProcessResult returned " << err << "\n";
active_ = false;
}
}
}
}
// else if (result == 0)
// myTimerCallBack();
else if (result < 0)
{
logE << "select() returned " << result << " errno " << errno << " " << strerror(errno) << "\n";
if (errno != EINTR)
active_ = false;
}
}
}
static void DNSSD_API reg_reply(DNSServiceRef sdref, const DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context)
{
(void)sdref; // Unused
(void)flags; // Unused
PublishBonjour* publishBonjour = (PublishBonjour*)context;
(void)publishBonjour; // unused
logO << "Got a reply for service " << name << "." << regtype << domain << "\n";
if (errorCode == kDNSServiceErr_NoError)
{
if (flags & kDNSServiceFlagsAdd)
logO << "Name now registered and active\n";
else
logO << "Name registration removed\n";
}
else if (errorCode == kDNSServiceErr_NameConflict)
{
/// TODO: Error handling
logO << "Name in use, please choose another\n";
exit(-1);
}
else
logO << "Error " << errorCode << "\n";
if (!(flags & kDNSServiceFlagsMoreComing))
fflush(stdout);
}
void PublishBonjour::publish(const std::vector<mDNSService>& services)
{
for (auto service: services)
{
DNSServiceFlags flags = 0;
Opaque16 registerPort = { { static_cast<unsigned char>(service.port_ >> 8), static_cast<unsigned char>(service.port_ & 0xFF) } };
DNSServiceRef client = NULL;
// DNSServiceRegister(&client, flags, kDNSServiceInterfaceIndexAny, serviceName_.c_str(), service.name_.c_str(), NULL, NULL, registerPort.NotAnInteger, service.txt_.size(), service.txt_.empty()?NULL:service.txt_.c_str(), reg_reply, this);
DNSServiceRegister(&client, flags, kDNSServiceInterfaceIndexAny, serviceName_.c_str(), service.name_.c_str(), NULL, NULL, registerPort.NotAnInteger, 0, NULL, reg_reply, this);
clients.push_back(client);
}
pollThread_ = std::thread(&PublishBonjour::worker, this);
}

View file

@ -0,0 +1,47 @@
/***
This file is part of snapcast
Copyright (C) 2014-2016 Johannes Pohl
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/>.
***/
#ifndef PUBLISH_BONJOUR_H
#define PUBLISH_BONJOUR_H
#include <string>
#include <dns_sd.h>
class PublishBonjour;
#include "publishmDNS.h"
class PublishBonjour : public PublishmDNS
{
public:
PublishBonjour(const std::string& serviceName);
virtual ~PublishBonjour();
virtual void publish(const std::vector<mDNSService>& services);
private:
std::thread pollThread_;
void worker();
std::atomic<bool> active_;
std::vector<DNSServiceRef> clients;
};
#endif

View file

@ -0,0 +1,44 @@
#ifndef PUBLISH_MDNS_H
#define PUBLISH_MDNS_H
#include <string>
#include <vector>
struct mDNSService
{
mDNSService(const std::string& name, size_t port) : name_(name), port_(port)
{
}
std::string name_;
size_t port_;
};
class PublishmDNS
{
public:
PublishmDNS(const std::string& serviceName) : serviceName_(serviceName)
{
}
virtual ~PublishmDNS()
{
}
virtual void publish(const std::vector<mDNSService>& services) = 0;
protected:
std::string serviceName_;
};
#if defined(HAS_AVAHI)
#include "publishAvahi.h"
typedef PublishAvahi PublishZeroConf;
#elif defined(HAS_BONJOUR)
#include "publishBonjour.h"
typedef PublishBonjour PublishZeroConf;
#endif
#endif

View file

@ -29,7 +29,9 @@
#include "message/message.h"
#include "encoder/encoderFactory.h"
#include "streamServer.h"
#include "publishAvahi.h"
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
#include "publishZeroConf/publishmDNS.h"
#endif
#include "config.h"
#include "common/log.h"
@ -43,6 +45,9 @@ using namespace popl;
int main(int argc, char* argv[])
{
#ifdef MACOS
#pragma message "Warning: the macOS support is experimental and might not be maintained"
#endif
try
{
StreamServerSettings settings;
@ -142,12 +147,10 @@ int main(int argc, char* argv[])
setpriority(PRIO_PROCESS, 0, processPriority);
logS(kLogNotice) << "daemon started" << std::endl;
}
PublishAvahi publishAvahi("Snapcast");
std::vector<AvahiService> services;
services.push_back(AvahiService("_snapcast._tcp", settings.port));
services.push_back(AvahiService("_snapcast-jsonrpc._tcp", settings.controlPort));
publishAvahi.publish(services);
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
PublishZeroConf publishZeroConfg("Snapcast");
publishZeroConfg.publish({mDNSService("_snapcast._tcp", settings.port), mDNSService("_snapcast-jsonrpc._tcp", settings.controlPort)});
#endif
if (settings.bufferMs < 400)
settings.bufferMs = 400;
@ -181,7 +184,3 @@ int main(int argc, char* argv[])
return 0;
}