Skip to content

Commit

Permalink
First implementation of the visualization output plugin.
Browse files Browse the repository at this point in the history
This commit contains the first implementation of an output
plugin that streams sound analysis to clients (presumably
visualizers of some sort).
  • Loading branch information
sp1ff committed Sep 30, 2024
1 parent 124c0e6 commit b69438d
Show file tree
Hide file tree
Showing 23 changed files with 5,033 additions and 3 deletions.
57 changes: 55 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ jobs:
libupnp-dev \
libsqlite3-dev \
libchromaprint-dev \
libgcrypt20-dev
libgcrypt20-dev \
libfftw3-dev
- id: cache-ccache
uses: hendrikmuhs/ccache-action@v1
Expand Down Expand Up @@ -140,8 +141,8 @@ jobs:
-Dfifo=false \
-Dhttpd=false -Dpipe=false -Drecorder=false \
-Dsnapcast=false \
--wrap-mode nofallback \
${{ matrix.meson_options }} \
-Dvisualization=false \
output/mini
- name: Build Mini
Expand All @@ -150,6 +151,57 @@ jobs:
- name: Unit Tests Mini
run: meson test -C output/mini

build-macos:
runs-on: macos-latest
steps:
- id: checkout
uses: actions/checkout@v4

- id: cache-ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: macos

- uses: actions/setup-python@v5
with:
python-version: 3.x

- name: Install dependencies
run: |
brew install \
meson ninja \
fmt \
googletest \
icu4c \
ffmpeg \
libnfs \
yajl \
libupnp \
libid3tag \
chromaprint \
libsamplerate \
libsoxr \
flac \
opus \
libvorbis \
faad2 \
wavpack \
libmpdclient \
fftw
- name: Configure
run: |
meson setup \
-Ddocumentation=disabled \
-Dtest=true \
output
- name: Build
run: meson compile -C output --verbose

- name: Unit Tests
run: meson test -C output

build-msys2:
strategy:
matrix:
Expand All @@ -171,6 +223,7 @@ jobs:
dbus:p
faad2:p
ffmpeg:p
fftw:p
fmt:p
flac:p
gtest:p
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/build_android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ jobs:
with:
key: android

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
ninja-build \
quilt
pip3 install --user meson==1.3.0
# todo: remove once NDK 27 is out of beta
- name: Install Beta NDK
run: |
Expand All @@ -54,7 +62,7 @@ jobs:
cd ./output/android
../../android/build.py $ANDROID_SDK_ROOT $ANDROID_SDK_ROOT/ndk/27.0.12077973 arm64-v8a \
--buildtype=debugoptimized -Db_ndebug=true \
-Dwrap_mode=forcefallback
-Dwrap_mode=forcefallback -Dvisualization=false
cd -
cd ./android
Expand Down
2 changes: 2 additions & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ option('shout', type: 'feature', description: 'Shoutcast streaming support using
option('snapcast', type: 'boolean', value: true, description: 'Snapcast output plugin')
option('sndio', type: 'feature', description: 'sndio output plugin')
option('solaris_output', type: 'feature', description: 'Solaris /dev/audio support')
option('visualization', type: 'boolean', value: true, description: 'Visualization output plugin')
option('fftw3', type: 'feature', description: 'FFTW support')

#
# Misc libraries
Expand Down
22 changes: 22 additions & 0 deletions src/lib/fmt/ThreadIdFormatter.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: BSD-2-Clause
// author: Max Kellermann <[email protected]>

#ifndef THREAD_ID_FORMATTER_HXX
#define THREAD_ID_FORMATTER_HXX

#include <fmt/format.h>
#include <sstream>
#include <thread>

template<>
struct fmt::formatter<std::thread::id> : formatter<string_view>
{
template<typename FormatContext>
auto format(std::thread::id id, FormatContext &ctx) {
std::stringstream stm;
stm << id;
return formatter<string_view>::format(stm.str(), ctx);
}
};

#endif // THREAD_ID_FORMATTER_HXX
4 changes: 4 additions & 0 deletions src/output/Registry.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "plugins/ShoutOutputPlugin.hxx"
#include "plugins/sles/SlesOutputPlugin.hxx"
#include "plugins/SolarisOutputPlugin.hxx"
#include "plugins/visualization/VisualizationOutputPlugin.hxx"
#ifdef ENABLE_WINMM_OUTPUT
#include "plugins/WinmmOutputPlugin.hxx"
#endif
Expand Down Expand Up @@ -85,6 +86,9 @@ constinit const AudioOutputPlugin *const audio_output_plugins[] = {
#endif
#ifdef ENABLE_WASAPI_OUTPUT
&wasapi_output_plugin,
#endif
#ifdef ENABLE_VISUALIZATION_OUTPUT
&visualization_output_plugin,
#endif
nullptr
};
Expand Down
22 changes: 22 additions & 0 deletions src/output/plugins/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,28 @@ else
wasapi_dep = dependency('', required: false)
endif

libfftw3_dep = dependency('fftw3f', version: '>= 3.3.8', required: get_option('fftw3'))
output_features.set('ENABLE_FFTW3', libfftw3_dep.found())

enable_visualization_output = get_option('visualization')
conf.set('ENABLE_VISUALIZATION_OUTPUT', enable_visualization_output)

output_features.set('ENABLE_VISUALIZATION_OUTPUT', get_option('visualization'))
if get_option('visualization')
if not libfftw3_dep.found()
error('libfftw3 not available, but is required for the visualization plugin')
endif
output_plugins_sources += [
'visualization/VisualizationOutputPlugin.cxx',
'visualization/SoundAnalysis.cxx',
'visualization/SoundInfoCache.cxx',
'visualization/VisualizationServer.cxx',
'visualization/VisualizationClient.cxx',
'visualization/Protocol.cxx',
]
output_plugins_deps += [ event_dep, net_dep, libfftw3_dep ]
endif

output_plugins = static_library(
'output_plugins',
output_plugins_sources,
Expand Down
57 changes: 57 additions & 0 deletions src/output/plugins/visualization/LowLevelProtocol.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project

#ifndef LOW_LEVEL_PROTOCOL_HXX_INCLUDED
#define LOW_LEVEL_PROTOCOL_HXX_INCLUDED

#include "util/PackedBigEndian.hxx"

#include <fftw3.h>

#include <algorithm>
#include <cstdint>
#include <limits>

namespace Visualization {

/* Write a uint16_t to an output iterator over byte in wire format; return the
* iterator in its new position
*/
template <typename OutIter>
OutIter
SerializeU16(uint16_t n, OutIter pout) {
auto m = PackedBE16(n);
auto p = (std::byte*)(&m);
return std::copy(p, p + 2, pout);
}

static_assert(std::numeric_limits<float>::is_iec559);

/* Convert an IEEE 754 single-precision floating-point number to wire format;
* write it to an output iterator & return the iterator in its new position
*/
template <typename OutIter>
OutIter
SerializeFloat(float f, OutIter pout) {
auto m = PackedBE32(*(uint32_t*)&f);
auto p = (std::byte*)(&m);
return std::copy(p, p + 4, pout);
}

/* Convert an fftwf_complex to wire format; write it to an output iterator &
* return the iterator in its new position
*/
template <typename OutIter>
OutIter
SerializeComplex(const fftwf_complex c, OutIter pout) {
auto r = PackedBE32(*(const uint32_t*)&(c[0]));
auto i = PackedBE32(*(const uint32_t*)&(c[1]));
auto pr = (std::byte*)(&r);
auto pi = (std::byte*)(&i);
pout = std::copy(pr, pr + 4, pout);
return std::copy(pi, pi + 4, pout);
}

} // namespace Visualization

#endif // LOW_LEVEL_PROTOCOL_HXX_INCLUDED
46 changes: 46 additions & 0 deletions src/output/plugins/visualization/Protocol.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project

#include "Protocol.hxx"

#include "Log.hxx"
#include "util/ByteOrder.hxx"
#include "util/Domain.hxx"

Visualization::ParseResult
Visualization::ParseClihlo(void *data,
size_t length,
ClientHello &clihlo) noexcept {
// CLIHLO payload is 6 bytes, header & footer are five more.
if (length < sizeof(ClientHello) + 5) {
return ParseResult::NEED_MORE_DATA;
}

uint8_t *buf = (uint8_t *)data;

uint16_t msg_type = FromBE16(*(uint16_t *)buf);
if (msg_type != 0) {
return ParseResult::ERROR;
}

buf += 2;
uint16_t payload_len = FromBE16(*(uint16_t *)buf);
if (payload_len != 6) {
return ParseResult::ERROR;
}

buf += 2;
clihlo.major_version = *buf++;
clihlo.minor_version = *buf++;

clihlo.requested_fps = FromBE16(*(uint16_t *)(buf));
buf += 2;
clihlo.tau = FromBE16(*(int16_t *)(buf));
buf += 2;

if (*buf != 0) {
return ParseResult::ERROR;
}

return ParseResult::OK;
}
Loading

0 comments on commit b69438d

Please sign in to comment.