re-added Outurnate's Windows client for master branch

This commit is contained in:
Stijn Van der Borght 2020-03-30 20:43:03 +01:00 committed by Johannes Pohl
parent 6118e1a79d
commit 094ec9b53c
20 changed files with 857 additions and 107 deletions

View file

@ -8,7 +8,10 @@ option(BUILD_SHARED_LIBS "Build snapcast in a shared context" ON)
option(BUILD_STATIC_LIBS "Build snapcast in a static context" ON) option(BUILD_STATIC_LIBS "Build snapcast in a static context" ON)
option(BUILD_TESTS "Build tests (run tests with make test)" ON) option(BUILD_TESTS "Build tests (run tests with make test)" ON)
option(BUILD_SERVER "Build Snapserver" ON) if(NOT WIN32)
option(BUILD_SERVER "Build Snapserver" ON) # no Windows server for now
endif()
option(BUILD_CLIENT "Build Snapclient" ON) option(BUILD_CLIENT "Build Snapclient" ON)
option(BUILD_WITH_FLAC "Build with FLAC support" ON) option(BUILD_WITH_FLAC "Build with FLAC support" ON)
@ -88,110 +91,126 @@ IF(${BIGENDIAN})
ENDIF(${BIGENDIAN}) ENDIF(${BIGENDIAN})
# Check dependencies # Check dependencies
find_package(PkgConfig REQUIRED)
if(NOT WIN32) # no PkgConfig on Windows...
find_package(PkgConfig REQUIRED)
endif()
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
include(CMakePushCheckState) include(CMakePushCheckState)
include(CheckIncludeFileCXX) include(CheckIncludeFileCXX)
if(MACOSX)
set(BONJOUR_FOUND true)
if (BONJOUR_FOUND)
add_definitions(-DHAS_BONJOUR)
endif (BONJOUR_FOUND)
add_definitions(-DFREEBSD -DHAS_DAEMON)
link_directories("/usr/local/lib")
list(APPEND INCLUDE_DIRS "/usr/local/include")
list(APPEND CMAKE_REQUIRED_INCLUDES "${INCLUDE_DIRS}")
elseif(ANDROID)
# add_definitions("-DNO_CPP11_STRING")
else()
if (BUILD_CLIENT)
pkg_search_module(ALSA REQUIRED alsa)
if (ALSA_FOUND)
add_definitions(-DHAS_ALSA)
endif (ALSA_FOUND)
endif()
if(BUILD_WITH_AVAHI)
pkg_search_module(AVAHI avahi-client)
if (AVAHI_FOUND)
add_definitions(-DHAS_AVAHI)
endif (AVAHI_FOUND)
endif(BUILD_WITH_AVAHI)
add_definitions(-DHAS_DAEMON)
if(FREEBSD)
add_definitions(-DFREEBSD)
link_directories("/usr/local/lib")
list(APPEND INCLUDE_DIRS "/usr/local/include")
list(APPEND CMAKE_REQUIRED_INCLUDES "${INCLUDE_DIRS}")
endif()
endif()
include_directories(${INCLUDE_DIRS}) include_directories(${INCLUDE_DIRS})
include(${CMAKE_SOURCE_DIR}/cmake/CheckCXX11StringSupport.cmake) include(${CMAKE_SOURCE_DIR}/cmake/CheckCXX11StringSupport.cmake)
CHECK_CXX11_STRING_SUPPORT(HAS_CXX11_STRING_SUPPORT) CHECK_CXX11_STRING_SUPPORT(HAS_CXX11_STRING_SUPPORT)
if(NOT HAS_CXX11_STRING_SUPPORT) if(NOT HAS_CXX11_STRING_SUPPORT)
add_definitions("-DNO_CPP11_STRING") add_definitions("-DNO_CPP11_STRING")
endif() endif()
if(BUILD_WITH_FLAC)
pkg_search_module(FLAC flac)
if (FLAC_FOUND)
add_definitions("-DHAS_FLAC")
endif (FLAC_FOUND)
endif()
if(BUILD_WITH_VORBIS OR BUILD_WITH_TREMOR) if(NOT WIN32)
pkg_search_module(OGG ogg)
if (OGG_FOUND)
add_definitions("-DHAS_OGG")
endif (OGG_FOUND)
endif()
if(BUILD_WITH_VORBIS) if(MACOSX)
pkg_search_module(VORBIS vorbis) set(BONJOUR_FOUND true)
if (VORBIS_FOUND) if (BONJOUR_FOUND)
add_definitions("-DHAS_VORBIS") add_definitions(-DHAS_BONJOUR)
endif (VORBIS_FOUND) endif (BONJOUR_FOUND)
endif()
if(BUILD_WITH_TREMOR) add_definitions(-DFREEBSD -DHAS_DAEMON)
pkg_search_module(TREMOR vorbisidec) link_directories("/usr/local/lib")
if (TREMOR_FOUND) list(APPEND INCLUDE_DIRS "/usr/local/include")
add_definitions("-DHAS_TREMOR") list(APPEND CMAKE_REQUIRED_INCLUDES "${INCLUDE_DIRS}")
endif (TREMOR_FOUND) elseif(ANDROID)
endif() # add_definitions("-DNO_CPP11_STRING")
else()
if (BUILD_CLIENT)
pkg_search_module(ALSA REQUIRED alsa)
if (ALSA_FOUND)
add_definitions(-DHAS_ALSA)
endif (ALSA_FOUND)
endif()
if(BUILD_WITH_VORBIS) if(BUILD_WITH_AVAHI)
pkg_search_module(VORBISENC vorbisenc) pkg_search_module(AVAHI avahi-client)
if (VORBISENC_FOUND) if (AVAHI_FOUND)
add_definitions("-DHAS_VORBIS_ENC") add_definitions(-DHAS_AVAHI)
endif(VORBISENC_FOUND) endif (AVAHI_FOUND)
endif() endif(BUILD_WITH_AVAHI)
if(BUILD_WITH_OPUS) add_definitions(-DHAS_DAEMON)
pkg_search_module(OPUS opus)
if (OPUS_FOUND)
add_definitions("-DHAS_OPUS")
endif (OPUS_FOUND)
endif()
if(BUILD_WITH_EXPAT) if(FREEBSD)
pkg_search_module(EXPAT expat) add_definitions(-DFREEBSD)
if (EXPAT_FOUND) link_directories("/usr/local/lib")
add_definitions("-DHAS_EXPAT") list(APPEND INCLUDE_DIRS "/usr/local/include")
endif (EXPAT_FOUND) list(APPEND CMAKE_REQUIRED_INCLUDES "${INCLUDE_DIRS}")
endif()
endif()
if(BUILD_WITH_FLAC)
pkg_search_module(FLAC flac)
if (FLAC_FOUND)
add_definitions("-DHAS_FLAC")
endif (FLAC_FOUND)
endif()
if(BUILD_WITH_VORBIS OR BUILD_WITH_TREMOR)
pkg_search_module(OGG ogg)
if (OGG_FOUND)
add_definitions("-DHAS_OGG")
endif (OGG_FOUND)
endif()
if(BUILD_WITH_VORBIS)
pkg_search_module(VORBIS vorbis)
if (VORBIS_FOUND)
add_definitions("-DHAS_VORBIS")
endif (VORBIS_FOUND)
endif()
if(BUILD_WITH_TREMOR)
pkg_search_module(TREMOR vorbisidec)
if (TREMOR_FOUND)
add_definitions("-DHAS_TREMOR")
endif (TREMOR_FOUND)
endif()
if(BUILD_WITH_VORBIS)
pkg_search_module(VORBISENC vorbisenc)
if (VORBISENC_FOUND)
add_definitions("-DHAS_VORBIS_ENC")
endif(VORBISENC_FOUND)
endif()
if(BUILD_WITH_OPUS)
pkg_search_module(OPUS opus)
if (OPUS_FOUND)
add_definitions("-DHAS_OPUS")
endif (OPUS_FOUND)
endif()
if(BUILD_WITH_EXPAT)
pkg_search_module(EXPAT expat)
if (EXPAT_FOUND)
add_definitions("-DHAS_EXPAT")
endif (EXPAT_FOUND)
endif()
endif() endif()
find_package(Boost 1.70 REQUIRED) find_package(Boost 1.70 REQUIRED)
add_definitions("-DBOOST_ERROR_CODE_HEADER_ONLY") add_definitions("-DBOOST_ERROR_CODE_HEADER_ONLY")
if(WIN32)
find_package(FLAC REQUIRED)
find_package(Ogg REQUIRED)
find_package(Vorbis REQUIRED)
add_definitions(-DNTDDI_VERSION=0x06020000 -D_WIN32_WINNT=0x0602 -DWINVER=0x0602 -DWINDOWS -DWIN32_LEAN_AND_MEAN -DUNICODE -D_UNICODE -D_CRT_SECURE_NO_WARNINGS )
add_definitions(-DHAS_OGG -DHAS_VORBIS -DHAS_FLAC -DHAS_VORBIS_ENC -DHAS_WASAPI)
endif()
add_subdirectory(common) add_subdirectory(common)
if (BUILD_SERVER) if (BUILD_SERVER)

View file

@ -14,7 +14,7 @@ set(CLIENT_INCLUDE
${CMAKE_SOURCE_DIR}/client ${CMAKE_SOURCE_DIR}/client
${CMAKE_SOURCE_DIR}/common ${CMAKE_SOURCE_DIR}/common
${ASIO_INCLUDE_DIRS} ${ASIO_INCLUDE_DIRS}
${POPL_INCLUDE_DIRS}) ${POPL_INCLUDE_DIRS})
if(MACOSX) if(MACOSX)
@ -30,6 +30,11 @@ if(MACOSX)
find_library(COREFOUNDATION_LIB CoreFoundation) find_library(COREFOUNDATION_LIB CoreFoundation)
find_library(AUDIOTOOLBOX_LIB AudioToolbox) find_library(AUDIOTOOLBOX_LIB AudioToolbox)
list(APPEND CLIENT_LIBRARIES ${COREAUDIO_LIB} ${COREFOUNDATION_LIB} ${AUDIOTOOLBOX_LIB}) list(APPEND CLIENT_LIBRARIES ${COREAUDIO_LIB} ${COREFOUNDATION_LIB} ${AUDIOTOOLBOX_LIB})
elseif (WIN32)
list(REMOVE_ITEM CLIENT_INCLUDE "common/daemon.cpp")
list(APPEND CLIENT_SOURCES player/wasapi_player.cpp decoder/ogg_decoder.cpp)
list(APPEND CLIENT_LIBRARIES ${FLAC_LIBRARIES} ${OGG_LIBRARIES} ${VORBIS_LIBRARIES} ${OGG_LIBRARIES} wsock32 ws2_32 avrt ksuser iphlpapi)
list(APPEND CLIENT_INCLUDE ${FLAC_INCLUDE_DIRS} ${OGG_INCLUDE_DIRS} ${VORBIS_INCLUDE_DIRS})
else() else()
# Avahi # Avahi
if (AVAHI_FOUND) if (AVAHI_FOUND)

View file

@ -59,7 +59,12 @@ void ClientConnection::socketRead(void* _to, size_t _bytes)
std::string ClientConnection::getMacAddress() std::string ClientConnection::getMacAddress()
{ {
std::string mac = ::getMacAddress(socket_.native_handle()); std::string mac =
#ifndef WINDOWS
::getMacAddress(socket_.native_handle());
#else
::getMacAddress(socket_.local_endpoint().address().to_string());
#endif
if (mac.empty()) if (mac.empty())
mac = "00:00:00:00:00:00"; mac = "00:00:00:00:00:00";
LOG(INFO) << "My MAC: \"" << mac << "\", socket: " << socket_.native_handle() << "\n"; LOG(INFO) << "My MAC: \"" << mac << "\", socket: " << socket_.native_handle() << "\n";

View file

@ -142,6 +142,10 @@ void Controller::onMessageReceived(ClientConnection* /*connection*/, const msg::
#ifdef HAS_COREAUDIO #ifdef HAS_COREAUDIO
if (!player_ && (player_name.empty() || (player_name == "coreaudio"))) if (!player_ && (player_name.empty() || (player_name == "coreaudio")))
player_ = make_unique<CoreAudioPlayer>(pcm_device, stream_); player_ = make_unique<CoreAudioPlayer>(pcm_device, stream_);
#endif
#ifdef HAS_WASAPI
if (!player_ && (player_name.empty() || (player_name == "wasapi")))
player_ = make_unique<WASAPIPlayer>(pcm_device, stream_);
#endif #endif
if (!player_) if (!player_)
throw SnapException("No audio player support"); throw SnapException("No audio player support");

View file

@ -37,6 +37,9 @@
#ifdef HAS_COREAUDIO #ifdef HAS_COREAUDIO
#include "player/coreaudio_player.hpp" #include "player/coreaudio_player.hpp"
#endif #endif
#ifdef WINDOWS
#include "player/wasapi_player.h"
#endif
#include "client_connection.hpp" #include "client_connection.hpp"
#include "client_settings.hpp" #include "client_settings.hpp"
#include "metadata.hpp" #include "metadata.hpp"

View file

@ -0,0 +1,337 @@
#include "wasapi_player.h"
#include <functional>
#include <audioclient.h>
#include <mmdeviceapi.h>
#include <comdef.h>
#include <comip.h>
#include <avrt.h>
#include <ksmedia.h>
#include <chrono>
#include <assert.h>
#include <functiondiscoverykeys_devpkey.h>
#include <codecvt>
#include <locale>
#include "common/snap_exception.hpp"
#include "common/aixlog.hpp"
using namespace std;
using namespace std::chrono;
using namespace std::chrono_literals;
static constexpr auto LOG_TAG = "WASAPI";
template<typename T>
struct COMMemDeleter
{
void operator() (T* obj)
{
if (obj != NULL)
{
CoTaskMemFree(obj);
obj = NULL;
}
}
};
template<typename T>
using com_mem_ptr = unique_ptr<T, COMMemDeleter<T> >;
using com_handle = unique_ptr<void, function<BOOL(HANDLE)> >;
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
const IID IID_IAudioClock = __uuidof(IAudioClock);
_COM_SMARTPTR_TYPEDEF(IMMDevice,__uuidof(IMMDevice));
_COM_SMARTPTR_TYPEDEF(IMMDeviceCollection,__uuidof(IMMDeviceCollection));
_COM_SMARTPTR_TYPEDEF(IMMDeviceEnumerator,__uuidof(IMMDeviceEnumerator));
_COM_SMARTPTR_TYPEDEF(IAudioClient,__uuidof(IAudioClient));
_COM_SMARTPTR_TYPEDEF(IPropertyStore,__uuidof(IPropertyStore));
#define REFTIMES_PER_SEC 10000000
#define REFTIMES_PER_MILLISEC 10000
EXTERN_C const PROPERTYKEY DECLSPEC_SELECTANY PKEY_Device_FriendlyName = { { 0xa45c254e, 0xdf1c, 0x4efd, { 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0 } }, 14 };
#define CHECK_HR(hres) if(FAILED(hres)){stringstream ss;ss<<"HRESULT fault status: "<<hex<<(hres)<<" line "<<dec<<__LINE__<<endl;throw SnapException(ss.str());}
WASAPIPlayer::WASAPIPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream)
: Player(pcmDevice, stream)
{
HRESULT hr = CoInitializeEx(
NULL,
COINIT_MULTITHREADED);
CHECK_HR(hr);
}
WASAPIPlayer::~WASAPIPlayer()
{
WASAPIPlayer::stop();
}
inline PcmDevice convertToDevice(int idx, IMMDevicePtr& device)
{
HRESULT hr;
PcmDevice desc;
LPWSTR id = NULL;
hr = device->GetId(&id);
CHECK_HR(hr);
IPropertyStorePtr properties = nullptr;
hr = device->OpenPropertyStore(STGM_READ, &properties);
PROPVARIANT deviceName;
PropVariantInit(&deviceName);
hr = properties->GetValue(PKEY_Device_FriendlyName, &deviceName);
CHECK_HR(hr);
desc.idx = idx;
desc.name = wstring_convert<codecvt_utf8<wchar_t>, wchar_t>().to_bytes(id);
desc.description = wstring_convert<codecvt_utf8<wchar_t>, wchar_t>().to_bytes(deviceName.pwszVal);
CoTaskMemFree(id);
return desc;
}
vector<PcmDevice> WASAPIPlayer::pcm_list()
{
HRESULT hr;
IMMDeviceCollectionPtr devices = nullptr;
IMMDeviceEnumeratorPtr deviceEnumerator = nullptr;
hr = CoInitializeEx(
NULL,
COINIT_MULTITHREADED);
if (hr != CO_E_ALREADYINITIALIZED)
CHECK_HR(hr);
hr = CoCreateInstance(
CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
(void**)&deviceEnumerator);
CHECK_HR(hr);
hr = deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE | DEVICE_STATE_UNPLUGGED, &devices);
CHECK_HR(hr);
UINT deviceCount;
devices->GetCount(&deviceCount);
if (deviceCount == 0)
throw SnapException("no valid devices");
vector<PcmDevice> deviceList;
{
IMMDevicePtr defaultDevice = nullptr;
hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
CHECK_HR(hr);
auto dev = convertToDevice(0, defaultDevice);
dev.name = "default";
deviceList.push_back(dev);
}
for (UINT i = 0; i < deviceCount; ++i)
{
IMMDevicePtr device = nullptr;
hr = devices->Item(i, &device);
CHECK_HR(hr);
deviceList.push_back(convertToDevice(i + 1, device));
}
return deviceList;
}
void WASAPIPlayer::worker()
{
assert(sizeof(char) == sizeof(BYTE));
HRESULT hr;
// Create the format specifier
com_mem_ptr<WAVEFORMATEX> waveformat((WAVEFORMATEX*)(CoTaskMemAlloc(sizeof(WAVEFORMATEX))));
waveformat->wFormatTag = WAVE_FORMAT_PCM;
waveformat->nChannels = stream_->getFormat().channels();
waveformat->nSamplesPerSec = stream_->getFormat().rate();
waveformat->wBitsPerSample = stream_->getFormat().bits();
waveformat->nBlockAlign = waveformat->nChannels * waveformat->wBitsPerSample / 8;
waveformat->nAvgBytesPerSec = waveformat->nSamplesPerSec * waveformat->nBlockAlign;
waveformat->cbSize = 0;
com_mem_ptr<WAVEFORMATEXTENSIBLE> waveformatExtended((WAVEFORMATEXTENSIBLE*)(CoTaskMemAlloc(sizeof(WAVEFORMATEXTENSIBLE))));
waveformatExtended->Format = *waveformat;
waveformatExtended->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
waveformatExtended->Format.cbSize = 22;
waveformatExtended->Samples.wValidBitsPerSample = waveformat->wBitsPerSample;
waveformatExtended->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
waveformatExtended->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
// Retrieve the device enumerator
IMMDeviceEnumeratorPtr deviceEnumerator = nullptr;
hr = CoCreateInstance(
CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
(void**)&deviceEnumerator);
CHECK_HR(hr);
// Register the default playback device (eRender for playback)
IMMDevicePtr device = nullptr;
if (pcmDevice_.idx == 0)
{
hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
CHECK_HR(hr);
}
else
{
IMMDeviceCollectionPtr devices = nullptr;
hr = deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE | DEVICE_STATE_UNPLUGGED, &devices);
CHECK_HR(hr);
devices->Item(pcmDevice_.idx, &device);
}
// Activate the device
IAudioClientPtr audioClient = nullptr;
hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&audioClient);
CHECK_HR(hr);
hr = audioClient->IsFormatSupported(
AUDCLNT_SHAREMODE_EXCLUSIVE,
&(waveformatExtended->Format),
NULL);
CHECK_HR(hr);
// Get the device period
REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
hr = audioClient->GetDevicePeriod(NULL, &hnsRequestedDuration);
CHECK_HR(hr);
// Initialize the client at minimum latency
hr = audioClient->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
hnsRequestedDuration,
hnsRequestedDuration,
&(waveformatExtended->Format),
NULL);
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
{
UINT32 alignedBufferSize;
hr = audioClient->GetBufferSize(&alignedBufferSize);
CHECK_HR(hr);
audioClient.Attach(NULL, false);
hnsRequestedDuration = (REFERENCE_TIME)((10000.0 * 1000 / waveformatExtended->Format.nSamplesPerSec * alignedBufferSize) + 0.5);
hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&audioClient);
CHECK_HR(hr);
hr = audioClient->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
hnsRequestedDuration,
hnsRequestedDuration,
&(waveformatExtended->Format),
NULL);
}
CHECK_HR(hr);
// Register an event to refill the buffer
com_handle eventHandle(CreateEvent(NULL, FALSE, FALSE, NULL), &::CloseHandle);
if (eventHandle == NULL)
CHECK_HR(E_FAIL);
hr = audioClient->SetEventHandle(HANDLE(eventHandle.get()));
CHECK_HR(hr);
// Get size of buffer
UINT32 bufferFrameCount;
hr = audioClient->GetBufferSize(&bufferFrameCount);
CHECK_HR(hr);
// Get the rendering service
IAudioRenderClient* renderClient = NULL;
hr = audioClient->GetService(
IID_IAudioRenderClient,
(void**)&renderClient);
CHECK_HR(hr);
// Grab the clock service
IAudioClock* clock = NULL;
hr = audioClient->GetService(
IID_IAudioClock,
(void**)&clock);
CHECK_HR(hr);
// Boost our priority
DWORD taskIndex = 0;
com_handle taskHandle(AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex),
&::AvRevertMmThreadCharacteristics);
if (taskHandle == NULL)
CHECK_HR(E_FAIL);
// And, action!
hr = audioClient->Start();
CHECK_HR(hr);
size_t bufferSize = bufferFrameCount * waveformatExtended->Format.nBlockAlign;
BYTE* buffer;
unique_ptr<char[]> queueBuffer(new char[bufferSize]);
UINT64 position = 0, bufferPosition = 0, frequency;
clock->GetFrequency(&frequency);
while (active_)
{
DWORD returnVal = WaitForSingleObject(eventHandle.get(), 2000);
if (returnVal != WAIT_OBJECT_0)
{
stop();
CHECK_HR(ERROR_TIMEOUT);
}
// Thread was sleeping above, double check that we are still running
if (!active_)
break;
clock->GetPosition(&position, NULL);
if (stream_->getPlayerChunk(queueBuffer.get(), microseconds(
((bufferPosition * 1000000) / waveformat->nSamplesPerSec) -
((position * 1000000) / frequency)),
bufferFrameCount))
{
adjustVolume(queueBuffer.get(), bufferFrameCount);
hr = renderClient->GetBuffer(bufferFrameCount, &buffer);
CHECK_HR(hr);
memcpy(buffer, queueBuffer.get(), bufferSize);
hr = renderClient->ReleaseBuffer(bufferFrameCount, 0);
CHECK_HR(hr);
bufferPosition += bufferFrameCount;
}
else
{
std::clog << static_cast<AixLog::Severity>(INFO) << AixLog::Tag(LOG_TAG);
LOG(INFO, LOG_TAG) << "Failed to get chunk\n";
hr = audioClient->Stop();
CHECK_HR(hr);
hr = audioClient->Reset();
CHECK_HR(hr);
while (active_ && !stream_->waitForChunk(std::chrono::milliseconds(100)))
LOG(INFO, LOG_TAG) << "Waiting for chunk\n";
hr = audioClient->Start();
CHECK_HR(hr);
bufferPosition = 0;
}
}
}

View file

@ -0,0 +1,35 @@
/***
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 WASAPI_PLAYER_H
#define WASAPI_PLAYER_H
#include "player.hpp"
class WASAPIPlayer : public Player
{
public:
WASAPIPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream);
virtual ~WASAPIPlayer();
static std::vector<PcmDevice> pcm_list(void);
protected:
virtual void worker();
};
#endif

View file

@ -18,7 +18,9 @@
#include <chrono> #include <chrono>
#include <iostream> #include <iostream>
#ifndef WINDOWS
#include <sys/resource.h> #include <sys/resource.h>
#endif
#include "browseZeroConf/browse_mdns.hpp" #include "browseZeroConf/browse_mdns.hpp"
#include "common/popl.hpp" #include "common/popl.hpp"
@ -46,8 +48,13 @@ using namespace std::chrono_literals;
PcmDevice getPcmDevice(const std::string& soundcard) PcmDevice getPcmDevice(const std::string& soundcard)
{ {
#if defined(HAS_ALSA) || defined(WINDOWS)
vector<PcmDevice> pcmDevices =
#ifdef HAS_ALSA #ifdef HAS_ALSA
vector<PcmDevice> pcmDevices = AlsaPlayer::pcm_list(); AlsaPlayer::pcm_list();
#else
WASAPIPlayer::pcm_list();
#endif
try try
{ {
@ -63,7 +70,6 @@ PcmDevice getPcmDevice(const std::string& soundcard)
for (auto dev : pcmDevices) for (auto dev : pcmDevices)
if (dev.name.find(soundcard) != string::npos) if (dev.name.find(soundcard) != string::npos)
return dev; return dev;
#else
std::ignore = soundcard; std::ignore = soundcard;
#endif #endif
@ -90,7 +96,7 @@ int main(int argc, char** argv)
auto groffSwitch = op.add<Switch, Attribute::hidden>("", "groff", "produce groff message"); auto groffSwitch = op.add<Switch, Attribute::hidden>("", "groff", "produce groff message");
auto debugOption = op.add<Implicit<string>, Attribute::hidden>("", "debug", "enable debug logging", ""); // TODO: &settings.logging.debug); auto debugOption = op.add<Implicit<string>, Attribute::hidden>("", "debug", "enable debug logging", ""); // TODO: &settings.logging.debug);
auto versionSwitch = op.add<Switch>("v", "version", "show version number"); auto versionSwitch = op.add<Switch>("v", "version", "show version number");
#if defined(HAS_ALSA) #if defined(HAS_ALSA) || defined(WINDOWS)
auto listSwitch = op.add<Switch>("l", "list", "list PCM devices"); auto listSwitch = op.add<Switch>("l", "list", "list PCM devices");
/*auto soundcardValue =*/op.add<Value<string>>("s", "soundcard", "index or name of the pcm device", "default", &pcm_device); /*auto soundcardValue =*/op.add<Value<string>>("s", "soundcard", "index or name of the pcm device", "default", &pcm_device);
#endif #endif
@ -132,10 +138,15 @@ int main(int argc, char** argv)
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
} }
#ifdef HAS_ALSA #if defined(HAS_ALSA) || defined(WINDOWS)
if (listSwitch->is_set()) if (listSwitch->is_set())
{ {
vector<PcmDevice> pcmDevices = AlsaPlayer::pcm_list(); vector<PcmDevice> pcmDevices =
#ifdef HAS_ALSA
AlsaPlayer::pcm_list();
#else
WASAPIPlayer::pcm_list();
#endif
for (auto dev : pcmDevices) for (auto dev : pcmDevices)
{ {
cout << dev.idx << ": " << dev.name << "\n" << dev.description << "\n\n"; cout << dev.idx << ": " << dev.name << "\n" << dev.description << "\n\n";
@ -214,6 +225,10 @@ int main(int argc, char** argv)
} }
#endif #endif
#if defined(HAS_WASAPI)
settings.player.player_name = "wasapi";
#endif
#ifdef HAS_SOXR #ifdef HAS_SOXR
if (sample_format->is_set()) if (sample_format->is_set())
{ {
@ -228,7 +243,11 @@ int main(int argc, char** argv)
bool active = true; bool active = true;
std::shared_ptr<Controller> controller; std::shared_ptr<Controller> controller;
auto signal_handler = install_signal_handler({SIGHUP, SIGTERM, SIGINT}, auto signal_handler = install_signal_handler({
#ifndef WINDOWS // no sighup on windows
SIGHUP,
#endif
SIGTERM, SIGINT},
[&active, &controller](int signal, const std::string& strsignal) { [&active, &controller](int signal, const std::string& strsignal) {
SLOG(INFO) << "Received signal " << signal << ": " << strsignal << "\n"; SLOG(INFO) << "Received signal " << signal << ": " << strsignal << "\n";
active = false; active = false;

View file

@ -390,7 +390,7 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
// and can play 30ms of the stream // and can play 30ms of the stream
uint32_t silent_frames = static_cast<size_t>(-chunk_->format.nsRate() * std::chrono::duration_cast<cs::nsec>(age).count()); uint32_t silent_frames = static_cast<size_t>(-chunk_->format.nsRate() * std::chrono::duration_cast<cs::nsec>(age).count());
bool result = (silent_frames <= frames); bool result = (silent_frames <= frames);
silent_frames = std::min(silent_frames, frames); silent_frames = (std::min)(silent_frames, frames);
LOG(DEBUG, LOG_TAG) << "Silent frames: " << silent_frames << ", frames: " << frames LOG(DEBUG, LOG_TAG) << "Silent frames: " << silent_frames << ", frames: " << frames
<< ", age: " << std::chrono::duration_cast<cs::usec>(age).count() / 1000. << "\n"; << ", age: " << std::chrono::duration_cast<cs::usec>(age).count() / 1000. << "\n";
getSilentPlayerChunk(outputBuffer, silent_frames); getSilentPlayerChunk(outputBuffer, silent_frames);
@ -453,7 +453,7 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
if ((cs::usec(shortMedian_) > kCorrectionBegin) && (cs::usec(miniMedian) > cs::usec(50)) && (cs::usec(age) > cs::usec(50))) if ((cs::usec(shortMedian_) > kCorrectionBegin) && (cs::usec(miniMedian) > cs::usec(50)) && (cs::usec(age) > cs::usec(50)))
{ {
double rate = (shortMedian_ / 100) * 0.00005; double rate = (shortMedian_ / 100) * 0.00005;
rate = 1.0 - std::min(rate, 0.0005); rate = 1.0 - (std::min)(rate, 0.0005);
// LOG(INFO, LOG_TAG) << "Rate: " << rate << "\n"; // LOG(INFO, LOG_TAG) << "Rate: " << rate << "\n";
// we are late (age > 0), this means we are not playing fast enough // we are late (age > 0), this means we are not playing fast enough
// => the real sample rate seems to be lower, we have to drop some frames // => the real sample rate seems to be lower, we have to drop some frames
@ -462,7 +462,7 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
else if ((cs::usec(shortMedian_) < -kCorrectionBegin) && (cs::usec(miniMedian) < -cs::usec(50)) && (cs::usec(age) < -cs::usec(50))) else if ((cs::usec(shortMedian_) < -kCorrectionBegin) && (cs::usec(miniMedian) < -cs::usec(50)) && (cs::usec(age) < -cs::usec(50)))
{ {
double rate = (-shortMedian_ / 100) * 0.00005; double rate = (-shortMedian_ / 100) * 0.00005;
rate = 1.0 + std::min(rate, 0.0005); rate = 1.0 + (std::min)(rate, 0.0005);
// LOG(INFO, LOG_TAG) << "Rate: " << rate << "\n"; // LOG(INFO, LOG_TAG) << "Rate: " << rate << "\n";
// we are early (age > 0), this means we are playing too fast // we are early (age > 0), this means we are playing too fast
// => the real sample rate seems to be higher, we have to insert some frames // => the real sample rate seems to be higher, we have to insert some frames

22
cmake/FindFLAC.cmake Normal file
View file

@ -0,0 +1,22 @@
# - Try to find FLAC
# Once done this will define
#
# FLAC_FOUND - system has libFLAC
# FLAC_INCLUDE_DIRS - the libFLAC include directory
# FLAC_LIBRARIES - The libFLAC libraries
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
pkg_check_modules (FLAC flac)
list(APPEND FLAC_INCLUDE_DIRS ${FLAC_INCLUDEDIR})
endif()
if(NOT FLAC_FOUND)
find_path(FLAC_INCLUDE_DIRS FLAC/all.h)
find_library(FLAC_LIBRARIES FLAC)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(FLAC DEFAULT_MSG FLAC_INCLUDE_DIRS FLAC_LIBRARIES)
mark_as_advanced(FLAC_INCLUDE_DIRS FLAC_LIBRARIES)

22
cmake/FindOgg.cmake Normal file
View file

@ -0,0 +1,22 @@
# - Try to find ogg
# Once done this will define
#
# OGG_FOUND - system has ogg
# OGG_INCLUDE_DIRS - the ogg include directory
# OGG_LIBRARIES - The ogg libraries
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
pkg_check_modules (OGG ogg)
list(APPEND OGG_INCLUDE_DIRS ${OGG_INCLUDEDIR})
endif()
if(NOT OGG_FOUND)
find_path(OGG_INCLUDE_DIRS ogg/ogg.h)
find_library(OGG_LIBRARIES ogg)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Ogg DEFAULT_MSG OGG_INCLUDE_DIRS OGG_LIBRARIES)
mark_as_advanced(OGG_INCLUDE_DIRS OGG_LIBRARIES)

22
cmake/FindVorbis.cmake Normal file
View file

@ -0,0 +1,22 @@
# - Try to find vorbis
# Once done this will define
#
# VORBIS_FOUND - system has libvorbis
# VORBIS_INCLUDE_DIRS - the libvorbis include directory
# VORBIS_LIBRARIES - The libvorbis libraries
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
pkg_check_modules (VORBIS vorbis)
list(APPEND VORBIS_INCLUDE_DIRS ${VORBIS_INCLUDEDIR})
endif()
if(NOT VORBIS_FOUND)
find_path(VORBIS_INCLUDE_DIRS vorbis/vorbisenc.h)
find_library(VORBIS_LIBRARIES vorbis)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Vorbis DEFAULT_MSG VORBIS_INCLUDE_DIRS VORBIS_LIBRARIES)
mark_as_advanced(VORBIS_INCLUDE_DIRS VORBIS_LIBRARIES)

17
cmake/FindVorbisEnc.cmake Normal file
View file

@ -0,0 +1,17 @@
# - Try to find vorbisenc
# Once done this will define
#
# VORBISENC_FOUND - system has libvorbisenc
# VORBISENC_INCLUDE_DIRS - the libvorbisenc include directory
# VORBISENC_LIBRARIES - The libvorbisenc libraries
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
pkg_check_modules (VORBISENC vorbisenc)
list(APPEND VORBISENC_INCLUDE_DIRS ${VORBISENC_INCLUDEDIR})
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Vorbis DEFAULT_MSG VORBISENC_INCLUDE_DIRS VORBISENC_LIBRARIES)
mark_as_advanced(VORBISENC_INCLUDE_DIRS VORBISENC_LIBRARIES)

View file

@ -1 +1,5 @@
add_library(common STATIC daemon.cpp sample_format.cpp) if(NOT WIN32)
add_library(common STATIC daemon.cpp sample_format.cpp)
else()
add_library(common STATIC sample_format.cpp)
endif()

View file

@ -108,6 +108,21 @@
#define SPECIAL AixLog::Type::special #define SPECIAL AixLog::Type::special
#define TIMESTAMP AixLog::Timestamp(std::chrono::system_clock::now()) #define TIMESTAMP AixLog::Timestamp(std::chrono::system_clock::now())
// stijnvdb: sorry! :) LOG(SEV, "tag") was not working for Windows and I couldn't figure out how to fix it for windows without potentially breaking everything else...
// https://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros (Jason Deng)
#ifdef WIN32
#define LOG_2(severity, tag) AIXLOG_INTERNAL__LOG_SEVERITY_TAG(severity, tag)
#define LOG_1(severity) AIXLOG_INTERNAL__LOG_SEVERITY(severity)
#define LOG_0() LOG_1(0)
#define FUNC_CHOOSER(_f1, _f2, _f3, ...) _f3
#define FUNC_RECOMPOSER(argsWithParentheses) FUNC_CHOOSER argsWithParentheses
#define CHOOSE_FROM_ARG_COUNT(...) FUNC_RECOMPOSER((__VA_ARGS__, LOG_2, LOG_1, ))
#define MACRO_CHOOSER(...) CHOOSE_FROM_ARG_COUNT(__VA_ARGS__())
#define LOG(...) MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)
#endif
/** /**
* @brief * @brief
* Severity of the log message * Severity of the log message
@ -754,7 +769,8 @@ struct SinkOutputDebugString : public Sink
void log(const Metadata& metadata, const std::string& message) override void log(const Metadata& metadata, const std::string& message) override
{ {
OutputDebugString(message.c_str()); std::wstring wide = std::wstring(message.begin(), message.end());
OutputDebugString(wide.c_str());
} }
}; };
#endif #endif
@ -914,7 +930,8 @@ struct SinkEventLog : public Sink
{ {
SinkEventLog(const std::string& ident, Severity severity, Type type = Type::all) : Sink(severity, type) SinkEventLog(const std::string& ident, Severity severity, Type type = Type::all) : Sink(severity, type)
{ {
event_log = RegisterEventSource(NULL, ident.c_str()); std::wstring wide = std::wstring(ident.begin(), ident.end()); // stijnvdb: RegisterEventSource expands to RegisterEventSourceW which takes wchar_t
event_log = RegisterEventSource(NULL, wide.c_str());
} }
WORD get_type(Severity severity) const WORD get_type(Severity severity) const
@ -940,8 +957,10 @@ struct SinkEventLog : public Sink
void log(const Metadata& metadata, const std::string& message) override void log(const Metadata& metadata, const std::string& message) override
{ {
std::wstring wide = std::wstring(message.begin(), message.end());
// We need this temp variable because we cannot take address of rValue // We need this temp variable because we cannot take address of rValue
const char* c_str = message.c_str(); const wchar_t* c_str = wide.c_str();
ReportEvent(event_log, get_type(metadata.severity), 0, 0, NULL, 1, 0, &c_str, NULL); ReportEvent(event_log, get_type(metadata.severity), 0, 0, NULL, 1, 0, &c_str, NULL);
} }

View file

@ -25,9 +25,36 @@
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include <streambuf> #include <streambuf>
#ifndef WINDOWS
#include <sys/time.h> #include <sys/time.h>
#endif
#include <vector> #include <vector>
#ifdef WINDOWS // Implementation from http://stackoverflow.com/a/26085827/2510022
#include <Windows.h>
#include <stdint.h>
#include <winsock2.h>
inline static int gettimeofday(struct timeval* tp, struct timezone* tzp)
{
// Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's
static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL);
SYSTEMTIME system_time;
FILETIME file_time;
uint64_t time;
GetSystemTime(&system_time);
SystemTimeToFileTime(&system_time, &file_time);
time = ((uint64_t)file_time.dwLowDateTime);
time += ((uint64_t)file_time.dwHighDateTime) << 32;
tp->tv_sec = (long)((time - EPOCH) / 10000000L);
tp->tv_usec = (long)(system_time.wMilliseconds * 1000);
return 0;
}
#endif
/* /*
template<typename CharT, typename TraitsT = std::char_traits<CharT> > template<typename CharT, typename TraitsT = std::char_traits<CharT> >
class vectorwrapbuf : public std::basic_streambuf<CharT, TraitsT> class vectorwrapbuf : public std::basic_streambuf<CharT, TraitsT>
@ -69,7 +96,11 @@ struct tv
tv() tv()
{ {
timeval t; timeval t;
#ifdef WINDOWS
gettimeofday(&t, NULL);
#else
chronos::steadytimeofday(&t); chronos::steadytimeofday(&t);
#endif
sec = t.tv_sec; sec = t.tv_sec;
usec = t.tv_usec; usec = t.tv_usec;
} }

View file

@ -24,6 +24,25 @@
#include <set> #include <set>
#include <signal.h> #include <signal.h>
#ifdef WINDOWS
const char* strsignal(int sig)
{
switch (sig)
{
case SIGTERM:
return "SIGTERM";
case SIGINT:
return "SIGINT";
case SIGBREAK:
return "SIGBREAK";
case SIGABRT:
return "SIGABRT";
default:
return "Unhandled";
}
}
#endif
using signal_callback = std::function<void(int signal, const std::string& name)>; using signal_callback = std::function<void(int signal, const std::string& name)>;
static std::future<int> install_signal_handler(std::set<int> signals, const signal_callback& on_signal = nullptr) static std::future<int> install_signal_handler(std::set<int> signals, const signal_callback& on_signal = nullptr)
@ -49,4 +68,4 @@ static std::future<int> install_signal_handler(std::set<int> signals, const sign
return future; return future;
} }
#endif #endif

View file

@ -20,16 +20,72 @@
#define TIME_DEFS_H #define TIME_DEFS_H
#include <chrono> #include <chrono>
#include <sys/time.h>
#include <thread> #include <thread>
#ifdef MACOS #ifdef MACOS
#include <mach/clock.h> #include <mach/clock.h>
#include <mach/mach.h> #include <mach/mach.h>
#endif #endif
#ifndef WINDOWS
#include <sys/time.h>
#else // from the GNU C library implementation of sys/time.h
#include <winsock2.h>
#define timersub(a, b, result) \
do \
{ \
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
if ((result)->tv_usec < 0) \
{ \
--(result)->tv_sec; \
(result)->tv_usec += 1000000; \
} \
} while (0)
#define CLOCK_MONOTONIC 42 // discarded on windows plaforms
// from http://stackoverflow.com/a/38212960/2510022
#define BILLION (1E9)
static BOOL g_first_time = 1;
static LARGE_INTEGER g_counts_per_sec;
inline static int clock_gettime(int dummy, struct timespec* ct)
{
LARGE_INTEGER count;
if (g_first_time)
{
g_first_time = 0;
if (0 == QueryPerformanceFrequency(&g_counts_per_sec))
{
g_counts_per_sec.QuadPart = 0;
}
}
if ((NULL == ct) || (g_counts_per_sec.QuadPart <= 0) || (0 == QueryPerformanceCounter(&count)))
{
return -1;
}
ct->tv_sec = count.QuadPart / g_counts_per_sec.QuadPart;
ct->tv_nsec = ((count.QuadPart % g_counts_per_sec.QuadPart) * BILLION) / g_counts_per_sec.QuadPart;
return 0;
}
#endif
namespace chronos namespace chronos
{ {
using clk = std::chrono::steady_clock; using clk =
#ifndef WINDOWS
std::chrono::steady_clock;
#else
std::chrono::system_clock;
#endif
using time_point_clk = std::chrono::time_point<clk>; using time_point_clk = std::chrono::time_point<clk>;
using sec = std::chrono::seconds; using sec = std::chrono::seconds;
using msec = std::chrono::milliseconds; using msec = std::chrono::milliseconds;

View file

@ -32,19 +32,21 @@
#include <iterator> #include <iterator>
#include <locale> #include <locale>
#include <memory> #include <memory>
#ifndef WINDOWS
#include <net/if.h> #include <net/if.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/utsname.h>
#endif
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <sys/ioctl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h>
#include <vector> #include <vector>
#ifndef FREEBSD #if !defined(WINDOWS) && !defined(FREEBSD)
#include <sys/sysinfo.h> #include <sys/sysinfo.h>
#endif #endif
#include <sys/utsname.h>
#ifdef MACOS #ifdef MACOS
#include <IOKit/IOCFPlugIn.h> #include <IOKit/IOCFPlugIn.h>
#include <IOKit/IOTypes.h> #include <IOKit/IOTypes.h>
@ -54,12 +56,20 @@
#ifdef ANDROID #ifdef ANDROID
#include <sys/system_properties.h> #include <sys/system_properties.h>
#endif #endif
#ifdef WINDOWS
#include <chrono>
#include <windows.h>
#include <direct.h>
#include <winsock2.h>
#include <iphlpapi.h>
#include <versionhelpers.h>
#endif
namespace strutils = utils::string; namespace strutils = utils::string;
#ifndef WINDOWS
static std::string execGetOutput(const std::string& cmd) static std::string execGetOutput(const std::string& cmd)
{ {
std::shared_ptr<FILE> pipe(popen((cmd + " 2> /dev/null").c_str(), "r"), pclose); std::shared_ptr<FILE> pipe(popen((cmd + " 2> /dev/null").c_str(), "r"), pclose);
@ -74,6 +84,7 @@ static std::string execGetOutput(const std::string& cmd)
} }
return strutils::trim(result); return strutils::trim(result);
} }
#endif
#ifdef ANDROID #ifdef ANDROID
@ -97,11 +108,40 @@ static std::string getOS()
#ifdef ANDROID #ifdef ANDROID
os = strutils::trim_copy("Android " + getProp("ro.build.version.release")); os = strutils::trim_copy("Android " + getProp("ro.build.version.release"));
#elif WINDOWS
if (/*IsWindows10OrGreater()*/ FALSE)
os = "Windows 10";
else if (IsWindows8Point1OrGreater())
os = "Windows 8.1";
else if (IsWindows8OrGreater())
os = "Windows 8";
else if (IsWindows7SP1OrGreater())
os = "Windows 7 SP1";
else if (IsWindows7OrGreater())
os = "Windows 7";
else if (IsWindowsVistaSP2OrGreater())
os = "Windows Vista SP2";
else if (IsWindowsVistaSP1OrGreater())
os = "Windows Vista SP1";
else if (IsWindowsVistaOrGreater())
os = "Windows Vista";
else if (IsWindowsXPSP3OrGreater())
os = "Windows XP SP3";
else if (IsWindowsXPSP2OrGreater())
os = "Windows XP SP2";
else if (IsWindowsXPSP1OrGreater())
os = "Windows XP SP1";
else if (IsWindowsXPOrGreater())
os = "Windows XP";
else
os = "Unknown Windows";
#else #else
os = execGetOutput("lsb_release -d"); os = execGetOutput("lsb_release -d");
if ((os.find(":") != std::string::npos) && (os.find("lsb_release") == std::string::npos)) if ((os.find(":") != std::string::npos) && (os.find("lsb_release") == std::string::npos))
os = strutils::trim_copy(os.substr(os.find(":") + 1)); os = strutils::trim_copy(os.substr(os.find(":") + 1));
#endif #endif
#ifndef WINDOWS
if (os.empty()) if (os.empty())
{ {
os = strutils::trim_copy(execGetOutput("grep /etc/os-release /etc/openwrt_release -e PRETTY_NAME -e DISTRIB_DESCRIPTION")); os = strutils::trim_copy(execGetOutput("grep /etc/os-release /etc/openwrt_release -e PRETTY_NAME -e DISTRIB_DESCRIPTION"));
@ -118,6 +158,7 @@ static std::string getOS()
uname(&u); uname(&u);
os = u.sysname; os = u.sysname;
} }
#endif
strutils::trim(os); strutils::trim(os);
return os; return os;
} }
@ -148,17 +189,46 @@ static std::string getArch()
if (!arch.empty()) if (!arch.empty())
return arch; return arch;
#endif #endif
#ifndef WINDOWS
arch = execGetOutput("arch"); arch = execGetOutput("arch");
if (arch.empty()) if (arch.empty())
arch = execGetOutput("uname -i"); arch = execGetOutput("uname -i");
if (arch.empty() || (arch == "unknown")) if (arch.empty() || (arch == "unknown"))
arch = execGetOutput("uname -m"); arch = execGetOutput("uname -m");
#else
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
switch (sysInfo.wProcessorArchitecture)
{
case PROCESSOR_ARCHITECTURE_AMD64:
arch = "amd64";
break;
case PROCESSOR_ARCHITECTURE_ARM:
arch = "arm";
break;
case PROCESSOR_ARCHITECTURE_IA64:
arch = "ia64";
break;
case PROCESSOR_ARCHITECTURE_INTEL:
arch = "intel";
break;
default:
case PROCESSOR_ARCHITECTURE_UNKNOWN:
arch = "unknown";
break;
}
#endif
return strutils::trim_copy(arch); return strutils::trim_copy(arch);
} }
static long uptime() static long uptime()
{ {
#ifndef WINDOWS
#ifndef FREEBSD #ifndef FREEBSD
struct sysinfo info; struct sysinfo info;
sysinfo(&info); sysinfo(&info);
@ -181,6 +251,9 @@ static long uptime()
} }
return 0; return 0;
#endif #endif
#else
return std::chrono::duration_cast<std::chrono::seconds>(std::chrono::milliseconds(GetTickCount())).count();
#endif
} }
@ -201,6 +274,7 @@ static std::string generateUUID()
} }
#ifndef WINDOWS
/// https://gist.github.com/OrangeTide/909204 /// https://gist.github.com/OrangeTide/909204
static std::string getMacAddress(int sock) static std::string getMacAddress(int sock)
{ {
@ -306,7 +380,42 @@ static std::string getMacAddress(int sock)
#endif #endif
return mac; return mac;
} }
#else
static std::string getMacAddress(const std::string& address)
{
IP_ADAPTER_INFO* first;
IP_ADAPTER_INFO* pos;
ULONG bufferLength = sizeof(IP_ADAPTER_INFO);
first = (IP_ADAPTER_INFO*)malloc(bufferLength);
if (GetAdaptersInfo(first, &bufferLength) == ERROR_BUFFER_OVERFLOW)
{
free(first);
first = (IP_ADAPTER_INFO*)malloc(bufferLength);
}
char mac[19];
if (GetAdaptersInfo(first, &bufferLength) == NO_ERROR)
for (pos = first; pos != NULL; pos = pos->Next)
{
IP_ADDR_STRING* firstAddr = &pos->IpAddressList;
IP_ADDR_STRING* posAddr;
for (posAddr = firstAddr; posAddr != NULL; posAddr = posAddr->Next)
if (_stricmp(posAddr->IpAddress.String, address.c_str()) == 0)
{
sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", pos->Address[0], pos->Address[1], pos->Address[2], pos->Address[3], pos->Address[4],
pos->Address[5]);
free(first);
return mac;
}
}
else
free(first);
return mac;
}
#endif
static std::string getHostId(const std::string defaultId = "") static std::string getHostId(const std::string defaultId = "")
{ {

View file

@ -21,8 +21,10 @@
#include "string_utils.hpp" #include "string_utils.hpp"
#include <fstream> #include <fstream>
#ifndef WINDOWS
#include <grp.h> #include <grp.h>
#include <pwd.h> #include <pwd.h>
#endif
#include <stdexcept> #include <stdexcept>
#include <vector> #include <vector>
@ -39,7 +41,7 @@ static bool exists(const std::string& filename)
return infile.good(); return infile.good();
} }
#ifndef WINDOWS
static void do_chown(const std::string& file_path, const std::string& user_name, const std::string& group_name) static void do_chown(const std::string& file_path, const std::string& user_name, const std::string& group_name)
{ {
if (user_name.empty() && group_name.empty()) if (user_name.empty() && group_name.empty())
@ -88,7 +90,7 @@ static int mkdirRecursive(const char* path, mode_t mode)
} }
return res; return res;
} }
#endif
} // namespace file } // namespace file
} // namespace utils } // namespace utils