mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-23 05:56:17 +02:00
Merge branch 'develop' into nonroot
This commit is contained in:
commit
8ae203eece
35 changed files with 1169 additions and 1010 deletions
|
@ -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.
Binary file not shown.
BIN
android/Snapcast/src/main/assets/files/Snapcast_800.png
Normal file
BIN
android/Snapcast/src/main/assets/files/Snapcast_800.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
VERSION = 0.8.0
|
||||
VERSION = 0.9.0
|
||||
TARGET = snapclient
|
||||
SHELL = /bin/bash
|
||||
|
||||
|
@ -19,34 +19,34 @@ endif
|
|||
|
||||
ifeq ($(TARGET), ANDROID)
|
||||
|
||||
CXX = $(NDK_DIR)/bin/arm-linux-androideabi-g++
|
||||
STRIP = $(NDK_DIR)/bin/arm-linux-androideabi-strip
|
||||
CXX = $(NDK_DIR)/bin/arm-linux-androideabi-g++
|
||||
STRIP = $(NDK_DIR)/bin/arm-linux-androideabi-strip
|
||||
CXXFLAGS += -pthread -DANDROID -DNO_CPP11_STRING -fPIC -DHAS_TREMOR -DHAS_OPENSL -I$(NDK_DIR)/include
|
||||
LDFLAGS = -L$(NDK_DIR)/lib -pie -lvorbisidec -logg -lFLAC -lOpenSLES
|
||||
LDFLAGS = -L$(NDK_DIR)/lib -pie -lvorbisidec -logg -lFLAC -lOpenSLES
|
||||
OBJ += player/openslPlayer.o
|
||||
|
||||
else ifeq ($(TARGET), OPENWRT)
|
||||
|
||||
STRIP = echo
|
||||
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
|
||||
LDFLAGS = -lasound -lvorbisidec -logg -lFLAC -lavahi-client -lavahi-common -latomic
|
||||
OBJ += player/alsaPlayer.o browseZeroConf/browseAvahi.o
|
||||
|
||||
else ifeq ($(TARGET), MACOS)
|
||||
|
||||
CXX = /usr/bin/g++
|
||||
STRIP = strip
|
||||
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
|
||||
LDFLAGS = -logg -lvorbis -lFLAC -L/usr/local/lib -framework AudioToolbox -framework CoreFoundation
|
||||
OBJ += player/coreAudioPlayer.o browseZeroConf/browseBonjour.o
|
||||
|
||||
else
|
||||
|
||||
CXX = /usr/bin/g++
|
||||
STRIP = strip
|
||||
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
|
||||
LDFLAGS = -lrt -lasound -logg -lvorbis -lFLAC -lavahi-client -lavahi-common -static-libgcc -static-libstdc++
|
||||
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); \
|
||||
|
|
|
@ -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
|
||||
|
|
17
client/debian/snapclient.plist
Normal file
17
client/debian/snapclient.plist
Normal 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>
|
|
@ -47,18 +47,19 @@ CoreAudioPlayer::~CoreAudioPlayer()
|
|||
|
||||
void CoreAudioPlayer::playerCallback(AudioQueueRef queue, AudioQueueBufferRef bufferRef)
|
||||
{
|
||||
/// 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, ×tamp, 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;
|
||||
/// 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_, ×tamp, 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;
|
||||
char *buffer = (char*)bufferRef->mAudioData;
|
||||
if (!pubStream_->getPlayerChunk(buffer, delay, frames_))
|
||||
{
|
||||
logO << "Failed to get chunk. Playing silence.\n";
|
||||
|
@ -70,60 +71,60 @@ void CoreAudioPlayer::playerCallback(AudioQueueRef queue, AudioQueueBufferRef bu
|
|||
}
|
||||
|
||||
// OSStatus status =
|
||||
AudioQueueEnqueueBuffer(queue, bufferRef, 0, NULL);
|
||||
AudioQueueEnqueueBuffer(queue, bufferRef, 0, NULL);
|
||||
|
||||
if (!active_)
|
||||
{
|
||||
AudioQueueStop(queue, false);
|
||||
AudioQueueDispose(queue, false);
|
||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||
}
|
||||
if (!active_)
|
||||
{
|
||||
AudioQueueStop(queue, false);
|
||||
AudioQueueDispose(queue, false);
|
||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CoreAudioPlayer::worker()
|
||||
{
|
||||
const SampleFormat& sampleFormat = pubStream_->getFormat();
|
||||
const SampleFormat& sampleFormat = pubStream_->getFormat();
|
||||
|
||||
AudioStreamBasicDescription format;
|
||||
format.mSampleRate = sampleFormat.rate;
|
||||
format.mFormatID = kAudioFormatLinearPCM;
|
||||
format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;// | kAudioFormatFlagIsPacked;
|
||||
format.mBitsPerChannel = sampleFormat.bits;
|
||||
format.mChannelsPerFrame = sampleFormat.channels;
|
||||
format.mBytesPerFrame = sampleFormat.frameSize;
|
||||
format.mFramesPerPacket = 1;
|
||||
format.mBytesPerPacket = format.mBytesPerFrame * format.mFramesPerPacket;
|
||||
format.mReserved = 0;
|
||||
AudioStreamBasicDescription format;
|
||||
format.mSampleRate = sampleFormat.rate;
|
||||
format.mFormatID = kAudioFormatLinearPCM;
|
||||
format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;// | kAudioFormatFlagIsPacked;
|
||||
format.mBitsPerChannel = sampleFormat.bits;
|
||||
format.mChannelsPerFrame = sampleFormat.channels;
|
||||
format.mBytesPerFrame = sampleFormat.frameSize;
|
||||
format.mFramesPerPacket = 1;
|
||||
format.mBytesPerPacket = format.mBytesPerFrame * format.mFramesPerPacket;
|
||||
format.mReserved = 0;
|
||||
|
||||
AudioQueueRef queue;
|
||||
AudioQueueNewOutput(&format, callback, this, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &queue);
|
||||
AudioQueueCreateTimeline(queue, &timeLine);
|
||||
AudioQueueRef queue;
|
||||
AudioQueueNewOutput(&format, callback, this, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &queue);
|
||||
AudioQueueCreateTimeline(queue, &timeLine_);
|
||||
|
||||
// Apple recommends this as buffer size:
|
||||
// https://developer.apple.com/library/content/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html
|
||||
// static const int maxBufferSize = 0x10000; // limit maximum size to 64K
|
||||
// static const int minBufferSize = 0x4000; // limit minimum size to 16K
|
||||
//
|
||||
// For 100ms @ 48000:16:2 we have 19.2K
|
||||
// frames: 4800, ms: 100, buffer size: 19200
|
||||
// Apple recommends this as buffer size:
|
||||
// https://developer.apple.com/library/content/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html
|
||||
// static const int maxBufferSize = 0x10000; // limit maximum size to 64K
|
||||
// static const int minBufferSize = 0x4000; // limit minimum size to 16K
|
||||
//
|
||||
// For 100ms @ 48000:16:2 we have 19.2K
|
||||
// frames: 4800, ms: 100, buffer size: 19200
|
||||
frames_ = (sampleFormat.rate * ms_) / 1000;
|
||||
ms_ = frames_ * 1000 / sampleFormat.rate;
|
||||
ms_ = frames_ * 1000 / sampleFormat.rate;
|
||||
buff_size_ = frames_ * sampleFormat.frameSize;
|
||||
logO << "frames: " << frames_ << ", ms: " << ms_ << ", buffer size: " << buff_size_ << "\n";
|
||||
logO << "frames: " << frames_ << ", ms: " << ms_ << ", buffer size: " << buff_size_ << "\n";
|
||||
|
||||
AudioQueueBufferRef buffers[NUM_BUFFERS];
|
||||
for (int i = 0; i < NUM_BUFFERS; i++)
|
||||
{
|
||||
AudioQueueAllocateBuffer(queue, buff_size_, &buffers[i]);
|
||||
buffers[i]->mAudioDataByteSize = buff_size_;
|
||||
callback(this, queue, buffers[i]);
|
||||
}
|
||||
AudioQueueBufferRef buffers[NUM_BUFFERS];
|
||||
for (int i = 0; i < NUM_BUFFERS; i++)
|
||||
{
|
||||
AudioQueueAllocateBuffer(queue, buff_size_, &buffers[i]);
|
||||
buffers[i]->mAudioDataByteSize = buff_size_;
|
||||
callback(this, queue, buffers[i]);
|
||||
}
|
||||
|
||||
logE << "CoreAudioPlayer::worker\n";
|
||||
AudioQueueCreateTimeline(queue, &timeLine);
|
||||
AudioQueueStart(queue, NULL);
|
||||
CFRunLoopRun();
|
||||
logE << "CoreAudioPlayer::worker\n";
|
||||
AudioQueueCreateTimeline(queue, &timeLine_);
|
||||
AudioQueueStart(queue, NULL);
|
||||
CFRunLoopRun();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ public:
|
|||
protected:
|
||||
virtual void worker();
|
||||
|
||||
AudioQueueTimelineRef timeLine;
|
||||
AudioQueueTimelineRef timeLine_;
|
||||
size_t ms_;
|
||||
size_t frames_;
|
||||
size_t buff_size_;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
44
doc/build.md
44
doc/build.md
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -17,28 +18,38 @@ endif
|
|||
|
||||
ifeq ($(TARGET), OPENWRT)
|
||||
|
||||
STRIP = echo
|
||||
CXXFLAGS += -DNO_CPP11_STRING
|
||||
LDFLAGS = -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common -latomic
|
||||
STRIP = echo
|
||||
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
|
||||
LDFLAGS = -lrt -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common -static-libgcc -static-libstdc++
|
||||
CXX = /usr/local/bin/g++
|
||||
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
|
||||
LDFLAGS = -lrt -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common -static-libgcc -static-libstdc++
|
||||
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:
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
17
server/debian/snapserver.plist
Normal file
17
server/debian/snapserver.plist
Normal 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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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,13 +73,13 @@ 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);
|
||||
if (simple_poll)
|
||||
avahi_simple_poll_free(simple_poll);
|
||||
|
||||
avahi_free(name);
|
||||
avahi_free(name);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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_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_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;
|
||||
|
||||
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 */
|
||||
/* 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))
|
||||
/// 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:
|
|
@ -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_;
|
||||
};
|
||||
|
||||
|
159
server/publishZeroConf/publishBonjour.cpp
Normal file
159
server/publishZeroConf/publishBonjour.cpp
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
47
server/publishZeroConf/publishBonjour.h
Normal file
47
server/publishZeroConf/publishBonjour.h
Normal 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
|
||||
|
||||
|
44
server/publishZeroConf/publishmDNS.h
Executable file
44
server/publishZeroConf/publishmDNS.h
Executable 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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue