mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-23 14:06:14 +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"
|
applicationId "de.badaix.snapcast"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 23
|
targetSdkVersion 23
|
||||||
versionCode 7
|
versionCode 900
|
||||||
versionName "0.8.0"
|
versionName "0.9.0"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
buildTypes {
|
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);
|
ibMute.setOnClickListener(this);
|
||||||
ibOverflow = (ImageButton) findViewById(R.id.ibOverflow);
|
ibOverflow = (ImageButton) findViewById(R.id.ibOverflow);
|
||||||
ibOverflow.setOnClickListener(this);
|
ibOverflow.setOnClickListener(this);
|
||||||
volumeSeekBar.setMax(100);
|
|
||||||
setClient(client);
|
setClient(client);
|
||||||
volumeSeekBar.setOnSeekBarChangeListener(this);
|
volumeSeekBar.setOnSeekBarChangeListener(this);
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
|
|
@ -67,7 +67,8 @@
|
||||||
android:id="@+id/volumeSeekBar"
|
android:id="@+id/volumeSeekBar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"/>
|
android:layout_gravity="center_vertical"
|
||||||
|
android:max="100"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
@ -86,7 +86,8 @@
|
||||||
android:id="@+id/volumeSeekBar"
|
android:id="@+id/volumeSeekBar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"/>
|
android:layout_gravity="center_vertical"
|
||||||
|
android:max="100"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
@ -5,7 +5,7 @@ buildscript {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
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
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
VERSION = 0.8.0
|
VERSION = 0.9.0
|
||||||
TARGET = snapclient
|
TARGET = snapclient
|
||||||
SHELL = /bin/bash
|
SHELL = /bin/bash
|
||||||
|
|
||||||
|
@ -19,34 +19,34 @@ endif
|
||||||
|
|
||||||
ifeq ($(TARGET), ANDROID)
|
ifeq ($(TARGET), ANDROID)
|
||||||
|
|
||||||
CXX = $(NDK_DIR)/bin/arm-linux-androideabi-g++
|
CXX = $(NDK_DIR)/bin/arm-linux-androideabi-g++
|
||||||
STRIP = $(NDK_DIR)/bin/arm-linux-androideabi-strip
|
STRIP = $(NDK_DIR)/bin/arm-linux-androideabi-strip
|
||||||
CXXFLAGS += -pthread -DANDROID -DNO_CPP11_STRING -fPIC -DHAS_TREMOR -DHAS_OPENSL -I$(NDK_DIR)/include
|
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
|
OBJ += player/openslPlayer.o
|
||||||
|
|
||||||
else ifeq ($(TARGET), OPENWRT)
|
else ifeq ($(TARGET), OPENWRT)
|
||||||
|
|
||||||
STRIP = echo
|
STRIP = echo
|
||||||
CXXFLAGS += -pthread -DNO_CPP11_STRING -DHAS_TREMOR -DHAS_ALSA -DHAS_AVAHI -DHAS_DAEMON
|
CXXFLAGS += -pthread -DNO_CPP11_STRING -DHAS_TREMOR -DHAS_ALSA -DHAS_AVAHI -DHAS_DAEMON
|
||||||
LDFLAGS = -lasound -lvorbisidec -logg -lFLAC -lavahi-client -lavahi-common -latomic
|
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)
|
else ifeq ($(TARGET), MACOS)
|
||||||
|
|
||||||
CXX = /usr/bin/g++
|
CXX = /usr/bin/g++
|
||||||
STRIP = strip
|
STRIP = strip
|
||||||
CXXFLAGS += -DHAS_OGG -DHAS_COREAUDIO -DFREEBSD -DMACOS -DHAS_BONJOUR -I/usr/local/include -Wno-unused-local-typedef -Wno-deprecated
|
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
|
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
|
else
|
||||||
|
|
||||||
CXX = /usr/bin/g++
|
CXX = /usr/bin/g++
|
||||||
STRIP = strip
|
STRIP = strip
|
||||||
CXXFLAGS += -pthread -DHAS_OGG -DHAS_ALSA -DHAS_AVAHI -DHAS_DAEMON
|
CXXFLAGS += -pthread -DHAS_OGG -DHAS_ALSA -DHAS_AVAHI -DHAS_DAEMON
|
||||||
LDFLAGS = -lrt -lasound -logg -lvorbis -lFLAC -lavahi-client -lavahi-common -static-libgcc -static-libstdc++
|
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
|
endif
|
||||||
|
|
||||||
|
@ -77,6 +77,15 @@ ifdef DESTDIR
|
||||||
install:
|
install:
|
||||||
$(MAKE) installfiles
|
$(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
|
else
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
@ -120,6 +129,17 @@ adduser:
|
||||||
adduser --quiet --ingroup audio --system --no-create-home --home /var/lib/snapcast snapcast; \
|
adduser --quiet --ingroup audio --system --no-create-home --home /var/lib/snapcast snapcast; \
|
||||||
fi; \
|
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:
|
uninstall:
|
||||||
rm -f $(TARGET_DIR)/share/man/man1/$(BIN).1
|
rm -f $(TARGET_DIR)/share/man/man1/$(BIN).1
|
||||||
@if [[ `systemctl` =~ -\.mount ]]; then \
|
@if [[ `systemctl` =~ -\.mount ]]; then \
|
||||||
|
@ -134,6 +154,8 @@ uninstall:
|
||||||
rm -rf /var/run/$(BIN)
|
rm -rf /var/run/$(BIN)
|
||||||
$(MAKE) deluser
|
$(MAKE) deluser
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
||||||
uninstallsysv:
|
uninstallsysv:
|
||||||
@/etc/init.d/$(BIN) stop; \
|
@/etc/init.d/$(BIN) stop; \
|
||||||
killall -9 $(BIN); \
|
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
|
snapclient (0.8.0) unstable; urgency=low
|
||||||
|
|
||||||
* Features
|
* 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)
|
void CoreAudioPlayer::playerCallback(AudioQueueRef queue, AudioQueueBufferRef bufferRef)
|
||||||
{
|
{
|
||||||
/// Estimate the playout delay by checking the number of frames left in the buffer
|
/// Estimate the playout delay by checking the number of frames left in the buffer
|
||||||
/// and add ms_ (= complete buffer size). Based on trying.
|
/// and add ms_ (= complete buffer size). Based on trying.
|
||||||
AudioTimeStamp timestamp;
|
AudioTimeStamp timestamp;
|
||||||
AudioQueueGetCurrentTime(queue, timeLine, ×tamp, NULL);
|
AudioQueueGetCurrentTime(queue, timeLine_, ×tamp, NULL);
|
||||||
size_t bufferedFrames = (frames_ - ((uint64_t)timestamp.mSampleTime % frames_)) % frames_;
|
size_t bufferedFrames = (frames_ - ((uint64_t)timestamp.mSampleTime % frames_)) % frames_;
|
||||||
size_t bufferedMs = bufferedFrames * 1000 / pubStream_->getFormat().rate + (ms_ * (NUM_BUFFERS - 1));
|
size_t bufferedMs = bufferedFrames * 1000 / pubStream_->getFormat().rate + (ms_ * (NUM_BUFFERS - 1));
|
||||||
/// 15ms DAC delay. Based on trying.
|
/// 15ms DAC delay. Based on trying.
|
||||||
bufferedMs += 15;
|
bufferedMs += 15;
|
||||||
// logO << "buffered: " << bufferedFrames << ", ms: " << bufferedMs << ", mSampleTime: " << timestamp.mSampleTime << "\n";
|
// 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);
|
chronos::usec delay(bufferedMs * 1000);
|
||||||
char *buffer = (char*)bufferRef->mAudioData;
|
char *buffer = (char*)bufferRef->mAudioData;
|
||||||
if (!pubStream_->getPlayerChunk(buffer, delay, frames_))
|
if (!pubStream_->getPlayerChunk(buffer, delay, frames_))
|
||||||
{
|
{
|
||||||
logO << "Failed to get chunk. Playing silence.\n";
|
logO << "Failed to get chunk. Playing silence.\n";
|
||||||
|
@ -70,60 +71,60 @@ void CoreAudioPlayer::playerCallback(AudioQueueRef queue, AudioQueueBufferRef bu
|
||||||
}
|
}
|
||||||
|
|
||||||
// OSStatus status =
|
// OSStatus status =
|
||||||
AudioQueueEnqueueBuffer(queue, bufferRef, 0, NULL);
|
AudioQueueEnqueueBuffer(queue, bufferRef, 0, NULL);
|
||||||
|
|
||||||
if (!active_)
|
if (!active_)
|
||||||
{
|
{
|
||||||
AudioQueueStop(queue, false);
|
AudioQueueStop(queue, false);
|
||||||
AudioQueueDispose(queue, false);
|
AudioQueueDispose(queue, false);
|
||||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CoreAudioPlayer::worker()
|
void CoreAudioPlayer::worker()
|
||||||
{
|
{
|
||||||
const SampleFormat& sampleFormat = pubStream_->getFormat();
|
const SampleFormat& sampleFormat = pubStream_->getFormat();
|
||||||
|
|
||||||
AudioStreamBasicDescription format;
|
AudioStreamBasicDescription format;
|
||||||
format.mSampleRate = sampleFormat.rate;
|
format.mSampleRate = sampleFormat.rate;
|
||||||
format.mFormatID = kAudioFormatLinearPCM;
|
format.mFormatID = kAudioFormatLinearPCM;
|
||||||
format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;// | kAudioFormatFlagIsPacked;
|
format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;// | kAudioFormatFlagIsPacked;
|
||||||
format.mBitsPerChannel = sampleFormat.bits;
|
format.mBitsPerChannel = sampleFormat.bits;
|
||||||
format.mChannelsPerFrame = sampleFormat.channels;
|
format.mChannelsPerFrame = sampleFormat.channels;
|
||||||
format.mBytesPerFrame = sampleFormat.frameSize;
|
format.mBytesPerFrame = sampleFormat.frameSize;
|
||||||
format.mFramesPerPacket = 1;
|
format.mFramesPerPacket = 1;
|
||||||
format.mBytesPerPacket = format.mBytesPerFrame * format.mFramesPerPacket;
|
format.mBytesPerPacket = format.mBytesPerFrame * format.mFramesPerPacket;
|
||||||
format.mReserved = 0;
|
format.mReserved = 0;
|
||||||
|
|
||||||
AudioQueueRef queue;
|
AudioQueueRef queue;
|
||||||
AudioQueueNewOutput(&format, callback, this, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &queue);
|
AudioQueueNewOutput(&format, callback, this, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &queue);
|
||||||
AudioQueueCreateTimeline(queue, &timeLine);
|
AudioQueueCreateTimeline(queue, &timeLine_);
|
||||||
|
|
||||||
// Apple recommends this as buffer size:
|
// Apple recommends this as buffer size:
|
||||||
// https://developer.apple.com/library/content/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html
|
// 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 maxBufferSize = 0x10000; // limit maximum size to 64K
|
||||||
// static const int minBufferSize = 0x4000; // limit minimum size to 16K
|
// static const int minBufferSize = 0x4000; // limit minimum size to 16K
|
||||||
//
|
//
|
||||||
// For 100ms @ 48000:16:2 we have 19.2K
|
// For 100ms @ 48000:16:2 we have 19.2K
|
||||||
// frames: 4800, ms: 100, buffer size: 19200
|
// frames: 4800, ms: 100, buffer size: 19200
|
||||||
frames_ = (sampleFormat.rate * ms_) / 1000;
|
frames_ = (sampleFormat.rate * ms_) / 1000;
|
||||||
ms_ = frames_ * 1000 / sampleFormat.rate;
|
ms_ = frames_ * 1000 / sampleFormat.rate;
|
||||||
buff_size_ = frames_ * sampleFormat.frameSize;
|
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];
|
AudioQueueBufferRef buffers[NUM_BUFFERS];
|
||||||
for (int i = 0; i < NUM_BUFFERS; i++)
|
for (int i = 0; i < NUM_BUFFERS; i++)
|
||||||
{
|
{
|
||||||
AudioQueueAllocateBuffer(queue, buff_size_, &buffers[i]);
|
AudioQueueAllocateBuffer(queue, buff_size_, &buffers[i]);
|
||||||
buffers[i]->mAudioDataByteSize = buff_size_;
|
buffers[i]->mAudioDataByteSize = buff_size_;
|
||||||
callback(this, queue, buffers[i]);
|
callback(this, queue, buffers[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
logE << "CoreAudioPlayer::worker\n";
|
logE << "CoreAudioPlayer::worker\n";
|
||||||
AudioQueueCreateTimeline(queue, &timeLine);
|
AudioQueueCreateTimeline(queue, &timeLine_);
|
||||||
AudioQueueStart(queue, NULL);
|
AudioQueueStart(queue, NULL);
|
||||||
CFRunLoopRun();
|
CFRunLoopRun();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
virtual void worker();
|
virtual void worker();
|
||||||
|
|
||||||
AudioQueueTimelineRef timeLine;
|
AudioQueueTimelineRef timeLine_;
|
||||||
size_t ms_;
|
size_t ms_;
|
||||||
size_t frames_;
|
size_t frames_;
|
||||||
size_t buff_size_;
|
size_t buff_size_;
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
#include "popl.hpp"
|
#include "popl.hpp"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "browsemDNS.h"
|
#include "browseZeroConf/browsemDNS.h"
|
||||||
|
|
||||||
#ifdef HAS_ALSA
|
#ifdef HAS_ALSA
|
||||||
#include "player/alsaPlayer.h"
|
#include "player/alsaPlayer.h"
|
||||||
|
@ -68,7 +68,7 @@ PcmDevice getPcmDevice(const std::string& soundcard)
|
||||||
int main (int argc, char **argv)
|
int main (int argc, char **argv)
|
||||||
{
|
{
|
||||||
#ifdef MACOS
|
#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
|
#endif
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,6 +21,10 @@
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
#ifdef MACOS
|
||||||
|
#include <mach/clock.h>
|
||||||
|
#include <mach/mach.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace chronos
|
namespace chronos
|
||||||
{
|
{
|
||||||
|
@ -49,9 +53,12 @@ namespace chronos
|
||||||
inline static long getTickCount()
|
inline static long getTickCount()
|
||||||
{
|
{
|
||||||
#ifdef MACOS
|
#ifdef MACOS
|
||||||
struct timeval now;
|
clock_serv_t cclock;
|
||||||
gettimeofday(&now, NULL);
|
mach_timespec_t mts;
|
||||||
return now.tv_sec*1000 + now.tv_usec / 1000;
|
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
|
#else
|
||||||
struct timespec now;
|
struct timespec now;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &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*
|
*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
|
###Build Snapclient
|
||||||
Install the required libs:
|
|
||||||
|
|
||||||
$ brew install flac libvorbis
|
|
||||||
|
|
||||||
`cd` into the Snapclient src-root directory:
|
`cd` into the Snapclient src-root directory:
|
||||||
|
|
||||||
$ cd <snapcast dir>/client
|
$ cd <snapcast dir>/client
|
||||||
$ make TARGET=MACOS
|
$ 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)
|
##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.
|
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
|
###Android NDK setup
|
||||||
http://developer.android.com/ndk/guides/standalone_toolchain.html
|
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`
|
1. Download NDK: `https://dl.google.com/android/repository/android-ndk-r13-linux-x86_64.zip`
|
||||||
2. Extract to: `/SOME/LOCAL/PATH/android-ndk-r12b`
|
2. Extract to: `/SOME/LOCAL/PATH/android-ndk-r13`
|
||||||
3. Setup toolchain somewhere in your home dir (`<android-ndk dir>`):
|
3. Setup toolchain somewhere in your home dir (`<android-ndk dir>`):
|
||||||
|
|
||||||
````
|
````
|
||||||
$ cd /SOME/LOCAL/PATH/android-ndk-r10e/build/tools
|
$ cd /SOME/LOCAL/PATH/android-ndk-r13/build/tools
|
||||||
$ ./make-standalone-toolchain.sh --arch=arm --platform=android-14 --install-dir=<android-ndk dir> --ndk-dir=/SOME/LOCAL/PATH/android-ndk-r12b
|
$ ./make_standalone_toolchain.py --arch arm --api 14 --install-dir <android-ndk dir>
|
||||||
````
|
````
|
||||||
|
|
||||||
###Build Snapclient
|
###Build Snapclient
|
||||||
|
@ -176,7 +196,7 @@ Within the OpenWrt directory create symbolic links to the Snapcast source direct
|
||||||
Build Snapcast:
|
Build Snapcast:
|
||||||
|
|
||||||
$ cd <buildroot dir>
|
$ cd <buildroot dir>
|
||||||
$ make package/sxx/snapcast/clean V=s
|
$ make package/sxx/snapcast/clean
|
||||||
$ make package/sxx/snapcast/compile -j1 V=s
|
$ 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
|
include $(INCLUDE_DIR)/target.mk
|
||||||
|
|
||||||
PKG_NAME := snapcast
|
PKG_NAME := snapcast
|
||||||
PKG_VERSION := 0.8.0
|
PKG_VERSION := 0.9.0
|
||||||
PKG_RELEASE := $(PKG_SOURCE_VERSION)
|
PKG_RELEASE := $(PKG_SOURCE_VERSION)
|
||||||
PKG_USE_MIPS16 := 0
|
PKG_USE_MIPS16 := 0
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
VERSION = 0.8.0
|
VERSION = 0.9.0
|
||||||
TARGET = snapserver
|
TARGET = snapserver
|
||||||
SHELL = /bin/bash
|
SHELL = /bin/bash
|
||||||
|
|
||||||
|
@ -9,7 +9,8 @@ else
|
||||||
TARGET_DIR ?= /usr
|
TARGET_DIR ?= /usr
|
||||||
endif
|
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)
|
ifeq ($(ENDIAN), BIG)
|
||||||
CXXFLAGS += -DIS_BIG_ENDIAN
|
CXXFLAGS += -DIS_BIG_ENDIAN
|
||||||
|
@ -17,28 +18,38 @@ endif
|
||||||
|
|
||||||
ifeq ($(TARGET), OPENWRT)
|
ifeq ($(TARGET), OPENWRT)
|
||||||
|
|
||||||
STRIP = echo
|
STRIP = echo
|
||||||
CXXFLAGS += -DNO_CPP11_STRING
|
CXXFLAGS += -DNO_CPP11_STRING -DHAS_AVAHI -pthread
|
||||||
LDFLAGS = -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common -latomic
|
LDFLAGS = -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common -latomic
|
||||||
|
OBJ += publishZeroConf/publishAvahi.o
|
||||||
|
|
||||||
else ifeq ($(TARGET), FREEBSD)
|
else ifeq ($(TARGET), FREEBSD)
|
||||||
|
|
||||||
SHELL = /usr/local/bin/bash
|
CXX = /usr/local/bin/g++
|
||||||
CXX = /usr/local/bin/g++
|
STRIP = echo
|
||||||
CXXFLAGS += -DNO_CPP11_STRING -DFREEBSD
|
CXXFLAGS += -DNO_CPP11_STRING -DFREEBSD -DHAS_AVAHI -pthread
|
||||||
STRIP = strip
|
LDFLAGS = -lrt -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common -static-libgcc -static-libstdc++
|
||||||
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
|
else
|
||||||
|
|
||||||
CXX = /usr/bin/g++
|
CXX = /usr/bin/g++
|
||||||
STRIP = strip
|
STRIP = strip
|
||||||
LDFLAGS = -lrt -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common -static-libgcc -static-libstdc++
|
CXXFLAGS += -DHAS_AVAHI -pthread
|
||||||
|
LDFLAGS = -lrt -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common -static-libgcc -static-libstdc++
|
||||||
|
OBJ += publishZeroConf/publishAvahi.o
|
||||||
|
|
||||||
endif
|
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
|
BIN = snapserver
|
||||||
|
|
||||||
all: $(TARGET)
|
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 $(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)
|
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
|
else
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
@ -119,6 +139,15 @@ uninstall:
|
||||||
rm -f $(TARGET_DIR)/local/man/man1/$(BIN).1; \
|
rm -f $(TARGET_DIR)/local/man/man1/$(BIN).1; \
|
||||||
rm -f $(TARGET_DIR)/local/etc/rc.d/$(BIN); \
|
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
|
else
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
template<typename T>
|
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
|
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
|
snapserver (0.8.0) unstable; urgency=low
|
||||||
|
|
||||||
* Features
|
* 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;
|
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;
|
double res = 0;
|
||||||
logD << "payload: " << chunk->payloadSize << "\tframes: " << chunk->getFrameCount() << "\tduration: " << chunk->duration<chronos::msec>().count() << "\n";
|
logD << "payload: " << chunk->payloadSize << "\tframes: " << chunk->getFrameCount() << "\tduration: " << chunk->duration<chronos::msec>().count() << "\n";
|
||||||
int frames = chunk->getFrameCount();
|
int frames = chunk->getFrameCount();
|
||||||
float **buffer=vorbis_analysis_buffer(&vd, frames);
|
float **buffer=vorbis_analysis_buffer(&vd_, frames);
|
||||||
|
|
||||||
/* uninterleave samples */
|
/* uninterleave samples */
|
||||||
for (size_t channel = 0; channel < sampleFormat_.channels; ++channel)
|
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 */
|
/* 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);
|
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
|
more involved (potentially parallel) processing. Get a single
|
||||||
block for encoding now */
|
block for encoding now */
|
||||||
size_t pos = 0;
|
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 */
|
/* analysis, assume we want to use bitrate management */
|
||||||
vorbis_analysis(&vb, NULL);
|
vorbis_analysis(&vb_, NULL);
|
||||||
vorbis_bitrate_addblock(&vb);
|
vorbis_bitrate_addblock(&vb_);
|
||||||
|
|
||||||
while (vorbis_bitrate_flushpacket(&vd, &op))
|
while (vorbis_bitrate_flushpacket(&vd_, &op_))
|
||||||
{
|
{
|
||||||
/* weld the packet into the bitstream */
|
/* weld the packet into the bitstream */
|
||||||
ogg_stream_packetin(&os, &op);
|
ogg_stream_packetin(&os_, &op_);
|
||||||
|
|
||||||
/* write out pages (if any) */
|
/* write out pages (if any) */
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int result = ogg_stream_flush(&os, &og);
|
int result = ogg_stream_flush(&os_, &og_);
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
break;
|
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
|
// make chunk larger
|
||||||
if (oggChunk->payloadSize < nextLen)
|
if (oggChunk->payloadSize < nextLen)
|
||||||
oggChunk->payload = (char*)realloc(oggChunk->payload, nextLen);
|
oggChunk->payload = (char*)realloc(oggChunk->payload, nextLen);
|
||||||
|
|
||||||
memcpy(oggChunk->payload + pos, og.header, og.header_len);
|
memcpy(oggChunk->payload + pos, og_.header, og_.header_len);
|
||||||
pos += og.header_len;
|
pos += og_.header_len;
|
||||||
memcpy(oggChunk->payload + pos, og.body, og.body_len);
|
memcpy(oggChunk->payload + pos, og_.body, og_.body_len);
|
||||||
pos += og.body_len;
|
pos += og_.body_len;
|
||||||
|
|
||||||
if (ogg_page_eos(&og))
|
if (ogg_page_eos(&og_))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,7 @@ void OggEncoder::encode(const msg::PcmChunk* chunk)
|
||||||
{
|
{
|
||||||
res /= (sampleFormat_.rate / 1000.);
|
res /= (sampleFormat_.rate / 1000.);
|
||||||
// logO << "res: " << res << "\n";
|
// logO << "res: " << res << "\n";
|
||||||
lastGranulepos = os.granulepos;
|
lastGranulepos_ = os_.granulepos;
|
||||||
// make oggChunk smaller
|
// make oggChunk smaller
|
||||||
oggChunk->payload = (char*)realloc(oggChunk->payload, pos);
|
oggChunk->payload = (char*)realloc(oggChunk->payload, pos);
|
||||||
oggChunk->payloadSize = pos;
|
oggChunk->payloadSize = pos;
|
||||||
|
@ -164,7 +164,7 @@ void OggEncoder::initEncoder()
|
||||||
}
|
}
|
||||||
|
|
||||||
/********** Encode setup ************/
|
/********** Encode setup ************/
|
||||||
vorbis_info_init(&vi);
|
vorbis_info_init(&vi_);
|
||||||
|
|
||||||
/* choose an encoding mode. A few possibilities commented out, one
|
/* choose an encoding mode. A few possibilities commented out, one
|
||||||
actually used: */
|
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
|
/* 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,
|
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");
|
throw SnapException("failed to init encoder");
|
||||||
|
|
||||||
/* add a comment */
|
/* add a comment */
|
||||||
vorbis_comment_init(&vc);
|
vorbis_comment_init(&vc_);
|
||||||
vorbis_comment_add_tag(&vc, "TITLE", "SnapStream");
|
vorbis_comment_add_tag(&vc_, "TITLE", "SnapStream");
|
||||||
vorbis_comment_add_tag(&vc, "VERSION", VERSION);
|
vorbis_comment_add_tag(&vc_, "VERSION", VERSION);
|
||||||
vorbis_comment_add_tag(&vc, "SAMPLE_FORMAT", sampleFormat_.getFormat().c_str());
|
vorbis_comment_add_tag(&vc_, "SAMPLE_FORMAT", sampleFormat_.getFormat().c_str());
|
||||||
|
|
||||||
/* set up the analysis state and auxiliary encoding storage */
|
/* set up the analysis state and auxiliary encoding storage */
|
||||||
vorbis_analysis_init(&vd, &vi);
|
vorbis_analysis_init(&vd_, &vi_);
|
||||||
vorbis_block_init(&vd, &vb);
|
vorbis_block_init(&vd_, &vb_);
|
||||||
|
|
||||||
/* set up our packet->stream encoder */
|
/* set up our packet->stream encoder */
|
||||||
/* pick a random serial number; that way we can more likely build
|
/* pick a random serial number; that way we can more likely build
|
||||||
chained streams just by concatenation */
|
chained streams just by concatenation */
|
||||||
srand(time(NULL));
|
srand(time(NULL));
|
||||||
ogg_stream_init(&os, rand());
|
ogg_stream_init(&os_, rand());
|
||||||
|
|
||||||
/* Vorbis streams begin with three headers; the initial header (with
|
/* Vorbis streams begin with three headers; the initial header (with
|
||||||
most of the codec setup parameters) which is mandated by the Ogg
|
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_comm;
|
||||||
ogg_packet header_code;
|
ogg_packet header_code;
|
||||||
|
|
||||||
vorbis_analysis_headerout(&vd, &vc, &header, &header_comm, &header_code);
|
vorbis_analysis_headerout(&vd_, &vc_, &header, &header_comm, &header_code);
|
||||||
ogg_stream_packetin(&os, &header);
|
ogg_stream_packetin(&os_, &header);
|
||||||
ogg_stream_packetin(&os, &header_comm);
|
ogg_stream_packetin(&os_, &header_comm);
|
||||||
ogg_stream_packetin(&os, &header_code);
|
ogg_stream_packetin(&os_, &header_code);
|
||||||
|
|
||||||
/* This ensures the actual
|
/* This ensures the actual
|
||||||
* audio data will start on a new page, as per spec
|
* audio data will start on a new page, as per spec
|
||||||
|
@ -243,16 +243,16 @@ void OggEncoder::initEncoder()
|
||||||
headerChunk_.reset(new msg::CodecHeader("ogg"));
|
headerChunk_.reset(new msg::CodecHeader("ogg"));
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int result = ogg_stream_flush(&os, &og);
|
int result = ogg_stream_flush(&os_, &og_);
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
break;
|
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);
|
headerChunk_->payload = (char*)realloc(headerChunk_->payload, headerChunk_->payloadSize);
|
||||||
logD << "HeadLen: " << og.header_len << ", bodyLen: " << og.body_len << ", result: " << result << "\n";
|
logD << "HeadLen: " << og_.header_len << ", bodyLen: " << og_.body_len << ", result: " << result << "\n";
|
||||||
memcpy(headerChunk_->payload + pos, og.header, og.header_len);
|
memcpy(headerChunk_->payload + pos, og_.header, og_.header_len);
|
||||||
pos += og.header_len;
|
pos += og_.header_len;
|
||||||
memcpy(headerChunk_->payload + pos, og.body, og.body_len);
|
memcpy(headerChunk_->payload + pos, og_.body, og_.body_len);
|
||||||
pos += og.body_len;
|
pos += og_.body_len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,19 +35,17 @@ protected:
|
||||||
virtual void initEncoder();
|
virtual void initEncoder();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ogg_stream_state os; /* take physical pages, weld into a logical
|
ogg_stream_state os_; /// take physical pages, weld into a logical stream of packets
|
||||||
stream of packets */
|
ogg_page og_; /// one Ogg bitstream page. Vorbis packets are inside
|
||||||
ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */
|
ogg_packet op_; /// one raw packet of data for decode
|
||||||
ogg_packet op; /* one raw packet of data for decode */
|
|
||||||
|
|
||||||
vorbis_info vi; /* struct that stores all the static vorbis bitstream
|
vorbis_info vi_; /// struct that stores all the static vorbis bitstream settings
|
||||||
settings */
|
vorbis_comment vc_; /// struct that stores all the user comments
|
||||||
vorbis_comment vc; /* struct that stores all the user comments */
|
|
||||||
|
|
||||||
vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
|
vorbis_dsp_state vd_; /// central working state for the packet->PCM decoder
|
||||||
vorbis_block vb; /* local working space for packet->PCM decode */
|
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 AvahiSimplePoll *simple_poll;
|
||||||
static char* name;
|
static char* name;
|
||||||
|
|
||||||
PublishAvahi::PublishAvahi(const std::string& serviceName) :
|
PublishAvahi::PublishAvahi(const std::string& serviceName) : PublishmDNS(serviceName),
|
||||||
client(NULL), serviceName_(serviceName), active_(false)
|
client_(NULL), active_(false)
|
||||||
{
|
{
|
||||||
group = NULL;
|
group = NULL;
|
||||||
simple_poll = 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;
|
/// Allocate main loop object
|
||||||
int error;
|
|
||||||
|
|
||||||
/* Allocate main loop object */
|
|
||||||
if (!(simple_poll = avahi_simple_poll_new()))
|
if (!(simple_poll = avahi_simple_poll_new()))
|
||||||
{
|
{
|
||||||
|
///TODO: error handling
|
||||||
logE << "Failed to create simple poll object.\n";
|
logE << "Failed to create simple poll object.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Allocate a new client */
|
/// Allocate a new client
|
||||||
client = avahi_client_new(avahi_simple_poll_get(simple_poll), AVAHI_CLIENT_IGNORE_USER_CONFIG, client_callback, this, &error);
|
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 */
|
/// Check wether creating the client object succeeded
|
||||||
if (!client)
|
if (!client_)
|
||||||
{
|
{
|
||||||
logE << "Failed to create client: " << avahi_strerror(error) << "\n";
|
logE << "Failed to create client: " << avahi_strerror(error) << "\n";
|
||||||
}
|
}
|
||||||
|
@ -74,13 +73,13 @@ PublishAvahi::~PublishAvahi()
|
||||||
active_ = false;
|
active_ = false;
|
||||||
pollThread_.join();
|
pollThread_.join();
|
||||||
|
|
||||||
if (client)
|
if (client_)
|
||||||
avahi_client_free(client);
|
avahi_client_free(client_);
|
||||||
|
|
||||||
if (simple_poll)
|
if (simple_poll)
|
||||||
avahi_simple_poll_free(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);
|
assert(g == group || group == NULL);
|
||||||
group = g;
|
group = g;
|
||||||
|
|
||||||
/* Called whenever the entry group state changes */
|
/// Called whenever the entry group state changes
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case AVAHI_ENTRY_GROUP_ESTABLISHED :
|
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";
|
logO << "Service '" << name << "' successfully established.\n";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AVAHI_ENTRY_GROUP_COLLISION : {
|
case AVAHI_ENTRY_GROUP_COLLISION :
|
||||||
|
{
|
||||||
char *n;
|
char *n;
|
||||||
|
|
||||||
/* A service name collision with a remote service
|
/// A service name collision with a remote service happened. Let's pick a new name
|
||||||
* happened. Let's pick a new name */
|
|
||||||
n = avahi_alternative_service_name(name);
|
n = avahi_alternative_service_name(name);
|
||||||
avahi_free(name);
|
avahi_free(name);
|
||||||
name = n;
|
name = n;
|
||||||
|
|
||||||
logO << "Service name collision, renaming service to '" << 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));
|
static_cast<PublishAvahi*>(userdata)->create_services(avahi_entry_group_get_client(g));
|
||||||
break;
|
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";
|
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);
|
avahi_simple_poll_quit(simple_poll);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -130,13 +128,10 @@ void PublishAvahi::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState
|
||||||
|
|
||||||
void PublishAvahi::create_services(AvahiClient *c)
|
void PublishAvahi::create_services(AvahiClient *c)
|
||||||
{
|
{
|
||||||
char *n, r[128];
|
|
||||||
int ret;
|
|
||||||
assert(c);
|
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)
|
||||||
{
|
{
|
||||||
if (!(group = avahi_entry_group_new(c, entry_group_callback, this)))
|
if (!(group = avahi_entry_group_new(c, entry_group_callback, this)))
|
||||||
|
@ -145,54 +140,34 @@ void PublishAvahi::create_services(AvahiClient *c)
|
||||||
goto fail;
|
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))
|
if (avahi_entry_group_is_empty(group))
|
||||||
{
|
{
|
||||||
logO << "Adding service '" << name << "'\n";
|
logO << "Adding service '" << name << "'\n";
|
||||||
|
|
||||||
/* Create some random TXT data */
|
/// We will now add two services and one subtype to the entry group
|
||||||
snprintf(r, sizeof(r), "random=%i", rand());
|
for (const auto& service: services_)
|
||||||
|
|
||||||
/* 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)
|
|
||||||
{
|
{
|
||||||
|
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)
|
if (ret == AVAHI_ERR_COLLISION)
|
||||||
goto 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;
|
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))
|
/* 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));
|
fprintf(stderr, "Failed to add subtype _magic._sub._printer._tcp: %s\n", avahi_strerror(ret));
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
/* Tell the server to register the service */
|
/// Tell the server to register the service
|
||||||
if ((ret = avahi_entry_group_commit(group)) < 0)
|
if ((ret = avahi_entry_group_commit(group)) < 0)
|
||||||
{
|
{
|
||||||
logE << "Failed to commit entry group: " << avahi_strerror(ret) << "\n";
|
logE << "Failed to commit entry group: " << avahi_strerror(ret) << "\n";
|
||||||
|
@ -204,8 +179,7 @@ void PublishAvahi::create_services(AvahiClient *c)
|
||||||
|
|
||||||
collision:
|
collision:
|
||||||
|
|
||||||
/* A service name collision with a local service happened. Let's
|
/// A service name collision with a local service happened. Let's pick a new name
|
||||||
* pick a new name */
|
|
||||||
n = avahi_alternative_service_name(name);
|
n = avahi_alternative_service_name(name);
|
||||||
avahi_free(name);
|
avahi_free(name);
|
||||||
name = n;
|
name = n;
|
||||||
|
@ -226,14 +200,12 @@ void PublishAvahi::client_callback(AvahiClient *c, AvahiClientState state, AVAHI
|
||||||
{
|
{
|
||||||
assert(c);
|
assert(c);
|
||||||
|
|
||||||
/* Called whenever the client or server state changes */
|
/// Called whenever the client or server state changes
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case AVAHI_CLIENT_S_RUNNING:
|
case AVAHI_CLIENT_S_RUNNING:
|
||||||
|
|
||||||
/* The server has startup successfully and registered its host
|
/// The server has startup successfully and registered its host name on the network, so it's time to create our services
|
||||||
* name on the network, so it's time to create our services */
|
|
||||||
static_cast<PublishAvahi*>(userdata)->create_services(c);
|
static_cast<PublishAvahi*>(userdata)->create_services(c);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -241,25 +213,20 @@ void PublishAvahi::client_callback(AvahiClient *c, AvahiClientState state, AVAHI
|
||||||
|
|
||||||
logE << "Client failure: " << avahi_strerror(avahi_client_errno(c)) << "\n";
|
logE << "Client failure: " << avahi_strerror(avahi_client_errno(c)) << "\n";
|
||||||
avahi_simple_poll_quit(simple_poll);
|
avahi_simple_poll_quit(simple_poll);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AVAHI_CLIENT_S_COLLISION:
|
case AVAHI_CLIENT_S_COLLISION:
|
||||||
|
|
||||||
/* Let's drop our registered services. When the server is back
|
/// Let's drop our registered services. When the server is back
|
||||||
* in AVAHI_SERVER_RUNNING state we will register them
|
/// in AVAHI_SERVER_RUNNING state we will register them again with the new host name.
|
||||||
* again with the new host name. */
|
|
||||||
|
|
||||||
case AVAHI_CLIENT_S_REGISTERING:
|
case AVAHI_CLIENT_S_REGISTERING:
|
||||||
|
|
||||||
/* The server records are now being established. This
|
/// The server records are now being established. This might be caused by a host name change. We need to wait
|
||||||
* 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.
|
||||||
* for our own records to register until the host name is
|
|
||||||
* properly esatblished. */
|
|
||||||
|
|
||||||
if (group)
|
if (group)
|
||||||
avahi_entry_group_reset(group);
|
avahi_entry_group_reset(group);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AVAHI_CLIENT_CONNECTING:
|
case AVAHI_CLIENT_CONNECTING:
|
|
@ -33,36 +33,26 @@
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
|
class PublishAvahi;
|
||||||
|
|
||||||
struct AvahiService
|
#include "publishmDNS.h"
|
||||||
{
|
|
||||||
AvahiService(const std::string& name, size_t port, int proto = AVAHI_PROTO_UNSPEC) : name_(name), port_(port), proto_(proto)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string name_;
|
class PublishAvahi : public PublishmDNS
|
||||||
size_t port_;
|
|
||||||
int proto_;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class PublishAvahi
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PublishAvahi(const std::string& serviceName);
|
PublishAvahi(const std::string& serviceName);
|
||||||
~PublishAvahi();
|
virtual ~PublishAvahi();
|
||||||
void publish(const std::vector<AvahiService>& services);
|
virtual void publish(const std::vector<mDNSService>& services);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata);
|
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);
|
static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata);
|
||||||
void create_services(AvahiClient *c);
|
void create_services(AvahiClient *c);
|
||||||
AvahiClient* client;
|
|
||||||
std::string serviceName_;
|
|
||||||
std::thread pollThread_;
|
|
||||||
void worker();
|
void worker();
|
||||||
|
AvahiClient* client_;
|
||||||
|
std::thread pollThread_;
|
||||||
std::atomic<bool> active_;
|
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 "message/message.h"
|
||||||
#include "encoder/encoderFactory.h"
|
#include "encoder/encoderFactory.h"
|
||||||
#include "streamServer.h"
|
#include "streamServer.h"
|
||||||
#include "publishAvahi.h"
|
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
|
||||||
|
#include "publishZeroConf/publishmDNS.h"
|
||||||
|
#endif
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
|
|
||||||
|
@ -43,6 +45,9 @@ using namespace popl;
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
|
#ifdef MACOS
|
||||||
|
#pragma message "Warning: the macOS support is experimental and might not be maintained"
|
||||||
|
#endif
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
StreamServerSettings settings;
|
StreamServerSettings settings;
|
||||||
|
@ -142,12 +147,10 @@ int main(int argc, char* argv[])
|
||||||
setpriority(PRIO_PROCESS, 0, processPriority);
|
setpriority(PRIO_PROCESS, 0, processPriority);
|
||||||
logS(kLogNotice) << "daemon started" << std::endl;
|
logS(kLogNotice) << "daemon started" << std::endl;
|
||||||
}
|
}
|
||||||
|
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
|
||||||
PublishAvahi publishAvahi("Snapcast");
|
PublishZeroConf publishZeroConfg("Snapcast");
|
||||||
std::vector<AvahiService> services;
|
publishZeroConfg.publish({mDNSService("_snapcast._tcp", settings.port), mDNSService("_snapcast-jsonrpc._tcp", settings.controlPort)});
|
||||||
services.push_back(AvahiService("_snapcast._tcp", settings.port));
|
#endif
|
||||||
services.push_back(AvahiService("_snapcast-jsonrpc._tcp", settings.controlPort));
|
|
||||||
publishAvahi.publish(services);
|
|
||||||
|
|
||||||
if (settings.bufferMs < 400)
|
if (settings.bufferMs < 400)
|
||||||
settings.bufferMs = 400;
|
settings.bufferMs = 400;
|
||||||
|
@ -181,7 +184,3 @@ int main(int argc, char* argv[])
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue