mirror of
				https://github.com/gnif/LookingGlass.git
				synced 2025-11-04 06:31:54 +00:00 
			
		
		
		
	[client] audio: initial addition of PipeWire audio support via SPICE
This commit is contained in:
		
							
								
								
									
										1
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@@ -44,6 +44,7 @@ jobs:
 | 
				
			|||||||
          -DCMAKE_LINKER:FILEPATH=/usr/bin/ld \
 | 
					          -DCMAKE_LINKER:FILEPATH=/usr/bin/ld \
 | 
				
			||||||
          -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
 | 
					          -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
 | 
				
			||||||
          -DENABLE_LIBDECOR=${{ matrix.wayland_shell == 'libdecor' }}  \
 | 
					          -DENABLE_LIBDECOR=${{ matrix.wayland_shell == 'libdecor' }}  \
 | 
				
			||||||
 | 
					          -DENABLE_PIPEWIRE=0 \
 | 
				
			||||||
          ..
 | 
					          ..
 | 
				
			||||||
    - name: Build client
 | 
					    - name: Build client
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,6 +51,9 @@ add_feature_info(ENABLE_WAYLAND ENABLE_WAYLAND "Wayland support.")
 | 
				
			|||||||
option(ENABLE_LIBDECOR "Build with libdecor support" OFF)
 | 
					option(ENABLE_LIBDECOR "Build with libdecor support" OFF)
 | 
				
			||||||
add_feature_info(ENABLE_LIBDECOR ENABLE_LIBDECOR "libdecor support.")
 | 
					add_feature_info(ENABLE_LIBDECOR ENABLE_LIBDECOR "libdecor support.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					option(ENABLE_PIPEWIRE "Build with PipeWire audio output support" ON)
 | 
				
			||||||
 | 
					add_feature_info(ENABLE_PIPEWIRE ENABLE_PIPEWIRE "PipeWire audio support.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (NOT ENABLE_X11 AND NOT ENABLE_WAYLAND)
 | 
					if (NOT ENABLE_X11 AND NOT ENABLE_WAYLAND)
 | 
				
			||||||
  message(FATAL_ERROR "Either ENABLE_X11 or ENABLE_WAYLAND must be on")
 | 
					  message(FATAL_ERROR "Either ENABLE_X11 or ENABLE_WAYLAND must be on")
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
@@ -144,6 +147,7 @@ add_subdirectory("${PROJECT_TOP}/repos/cimgui"    "${CMAKE_BINARY_DIR}/cimgui" E
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
add_subdirectory(displayservers)
 | 
					add_subdirectory(displayservers)
 | 
				
			||||||
add_subdirectory(renderers)
 | 
					add_subdirectory(renderers)
 | 
				
			||||||
 | 
					add_subdirectory(audiodevs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_executable(looking-glass-client ${SOURCES})
 | 
					add_executable(looking-glass-client ${SOURCES})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -158,6 +162,7 @@ target_link_libraries(looking-glass-client
 | 
				
			|||||||
	purespice
 | 
						purespice
 | 
				
			||||||
	renderers
 | 
						renderers
 | 
				
			||||||
	cimgui
 | 
						cimgui
 | 
				
			||||||
 | 
						audiodevs
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
install(TARGETS looking-glass-client
 | 
					install(TARGETS looking-glass-client
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										43
									
								
								client/audiodevs/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								client/audiodevs/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					cmake_minimum_required(VERSION 3.0)
 | 
				
			||||||
 | 
					project(audiodevs LANGUAGES C)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set(AUDIODEV_H "${CMAKE_BINARY_DIR}/include/dynamic/audiodev.h")
 | 
				
			||||||
 | 
					set(AUDIODEV_C "${CMAKE_BINARY_DIR}/src/audiodev.c")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					file(WRITE  ${AUDIODEV_H} "#include \"interface/audiodev.h\"\n\n")
 | 
				
			||||||
 | 
					file(APPEND ${AUDIODEV_H} "extern struct LG_AudioDevOps * LG_AudioDevs[];\n\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					file(WRITE  ${AUDIODEV_C} "#include \"interface/audiodev.h\"\n\n")
 | 
				
			||||||
 | 
					file(APPEND ${AUDIODEV_C} "#include <stddef.h>\n\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set(AUDIODEVS "_")
 | 
				
			||||||
 | 
					set(AUDIODEVS_LINK "_")
 | 
				
			||||||
 | 
					function(add_audiodev name)
 | 
				
			||||||
 | 
					  set(AUDIODEVS      "${AUDIODEVS};${name}" PARENT_SCOPE)
 | 
				
			||||||
 | 
					  set(AUDIODEVS_LINK "${AUDIODEVS_LINK};audiodev_${name}" PARENT_SCOPE)
 | 
				
			||||||
 | 
					  add_subdirectory(${name})
 | 
				
			||||||
 | 
					endfunction()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Add/remove audiodevs here!
 | 
				
			||||||
 | 
					if(ENABLE_PIPEWIRE)
 | 
				
			||||||
 | 
					  add_audiodev(PipeWire)
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					list(REMOVE_AT AUDIODEVS      0)
 | 
				
			||||||
 | 
					list(REMOVE_AT AUDIODEVS_LINK 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					list(LENGTH AUDIODEVS AUDIODEV_COUNT)
 | 
				
			||||||
 | 
					file(APPEND ${AUDIODEV_H} "#define LG_AUDIODEV_COUNT ${AUDIODEV_COUNT}\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					foreach(audiodev ${AUDIODEVS})
 | 
				
			||||||
 | 
					  file(APPEND ${AUDIODEV_C} "extern struct LG_AudioDevOps LGAD_${audiodev};\n")
 | 
				
			||||||
 | 
					endforeach()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					file(APPEND ${AUDIODEV_C} "\nconst struct LG_AudioDevOps * LG_AudioDevs[] =\n{\n")
 | 
				
			||||||
 | 
					foreach(audiodev ${AUDIODEVS})
 | 
				
			||||||
 | 
					  file(APPEND ${AUDIODEV_C} "  &LGAD_${audiodev},\n")
 | 
				
			||||||
 | 
					endforeach()
 | 
				
			||||||
 | 
					file(APPEND ${AUDIODEV_C} "  NULL\n};")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_library(audiodevs STATIC ${AUDIODEV_C})
 | 
				
			||||||
 | 
					target_link_libraries(audiodevs ${AUDIODEVS_LINK})
 | 
				
			||||||
							
								
								
									
										21
									
								
								client/audiodevs/PipeWire/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								client/audiodevs/PipeWire/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					cmake_minimum_required(VERSION 3.0)
 | 
				
			||||||
 | 
					project(audiodev_PipeWire LANGUAGES C)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					find_package(PkgConfig)
 | 
				
			||||||
 | 
					pkg_check_modules(AUDIODEV_PipeWire REQUIRED IMPORTED_TARGET
 | 
				
			||||||
 | 
						libpipewire-0.3
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_library(audiodev_PipeWire STATIC
 | 
				
			||||||
 | 
						pipewire.c
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_link_libraries(audiodev_PipeWire
 | 
				
			||||||
 | 
						PkgConfig::AUDIODEV_PipeWire
 | 
				
			||||||
 | 
						lg_common
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_include_directories(audiodev_PipeWire
 | 
				
			||||||
 | 
						PRIVATE
 | 
				
			||||||
 | 
							src
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										220
									
								
								client/audiodevs/PipeWire/pipewire.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								client/audiodevs/PipeWire/pipewire.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,220 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Looking Glass
 | 
				
			||||||
 | 
					 * Copyright © 2017-2021 The Looking Glass Authors
 | 
				
			||||||
 | 
					 * https://looking-glass.io
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 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 2 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, write to the Free Software Foundation, Inc., 59
 | 
				
			||||||
 | 
					 * Temple Place, Suite 330, Boston, MA 02111-1307 USA
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "interface/audiodev.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <spa/param/audio/format-utils.h>
 | 
				
			||||||
 | 
					#include <pipewire/pipewire.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "common/debug.h"
 | 
				
			||||||
 | 
					#include "common/ringbuffer.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PipeWire
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  struct pw_loop        * loop;
 | 
				
			||||||
 | 
					  struct pw_thread_loop * thread;
 | 
				
			||||||
 | 
					  struct pw_stream      * stream;
 | 
				
			||||||
 | 
					  int    channels;
 | 
				
			||||||
 | 
					  int    stride;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  RingBuffer buffer;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct PipeWire pw = {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void pipewire_on_process(void * userdata)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  struct pw_buffer * pbuf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const int avail = ringbuffer_getCount(pw.buffer);
 | 
				
			||||||
 | 
					  if (!avail)
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!(pbuf = pw_stream_dequeue_buffer(pw.stream))) {
 | 
				
			||||||
 | 
					    DEBUG_WARN("out of buffers");
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  struct spa_buffer * sbuf = pbuf->buffer;
 | 
				
			||||||
 | 
					  uint8_t * dst;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!(dst = sbuf->datas[0].data))
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int frames = sbuf->datas[0].maxsize / pw.stride;
 | 
				
			||||||
 | 
					  if (frames > avail)
 | 
				
			||||||
 | 
					    frames = avail;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for(int i = 0; i < frames; ++i)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    ringbuffer_shift(pw.buffer, dst);
 | 
				
			||||||
 | 
					    dst += pw.stride;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  sbuf->datas[0].chunk->offset = 0;
 | 
				
			||||||
 | 
					  sbuf->datas[0].chunk->stride = pw.stride;
 | 
				
			||||||
 | 
					  sbuf->datas[0].chunk->size   = frames * pw.stride;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pw_stream_queue_buffer(pw.stream, pbuf);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool pipewire_init(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  pw_init(NULL, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pw.loop = pw_loop_new(NULL);
 | 
				
			||||||
 | 
					  struct pw_context * context = pw_context_new(pw.loop, NULL, 0);
 | 
				
			||||||
 | 
					  if (!context)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    DEBUG_ERROR("Failed to create a context");
 | 
				
			||||||
 | 
					    goto err;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* this is just to test for PipeWire availabillity */
 | 
				
			||||||
 | 
					  struct pw_core * core = pw_context_connect(context, NULL, 0);
 | 
				
			||||||
 | 
					  if (!core)
 | 
				
			||||||
 | 
					    goto err_context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pw_context_destroy(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* PipeWire is available so create the loop thread and start it */
 | 
				
			||||||
 | 
					  pw.thread = pw_thread_loop_new_full(pw.loop, "Playback", NULL);
 | 
				
			||||||
 | 
					  if (!pw.thread)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    DEBUG_ERROR("Failed to create the thread loop");
 | 
				
			||||||
 | 
					    goto err;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pw_thread_loop_start(pw.thread);
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					err_context:
 | 
				
			||||||
 | 
					  pw_context_destroy(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					err:
 | 
				
			||||||
 | 
					  pw_deinit();
 | 
				
			||||||
 | 
					  return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void pipewire_free(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (pw.thread)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    pw_thread_loop_lock(pw.thread);
 | 
				
			||||||
 | 
					    if (pw.stream)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      pw_stream_destroy(pw.stream);
 | 
				
			||||||
 | 
					      pw.stream = NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pw_thread_loop_signal(pw.thread, true);
 | 
				
			||||||
 | 
					    pw_thread_loop_destroy(pw.thread);
 | 
				
			||||||
 | 
					    pw.loop = NULL;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ringbuffer_free(&pw.buffer);
 | 
				
			||||||
 | 
					  pw_deinit();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void pipewire_start(int channels, int sampleRate)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  const struct spa_pod * params[1];
 | 
				
			||||||
 | 
					  uint8_t buffer[1024];
 | 
				
			||||||
 | 
					  struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
 | 
				
			||||||
 | 
					  static const struct pw_stream_events events =
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    .version = PW_VERSION_STREAM_EVENTS,
 | 
				
			||||||
 | 
					    .process = pipewire_on_process
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pw.channels = channels;
 | 
				
			||||||
 | 
					  pw.stride   = sizeof(uint16_t) * channels;
 | 
				
			||||||
 | 
					  pw.buffer   = ringbuffer_new(sampleRate, channels * sizeof(uint16_t));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pw_thread_loop_lock(pw.thread);
 | 
				
			||||||
 | 
					  pw.stream = pw_stream_new_simple(
 | 
				
			||||||
 | 
					    pw.loop,
 | 
				
			||||||
 | 
					    "LookingGlass",
 | 
				
			||||||
 | 
					    pw_properties_new(
 | 
				
			||||||
 | 
					      PW_KEY_MEDIA_TYPE    , "Audio",
 | 
				
			||||||
 | 
					      PW_KEY_MEDIA_CATEGORY, "Playback",
 | 
				
			||||||
 | 
					      PW_KEY_MEDIA_ROLE    , "Music",
 | 
				
			||||||
 | 
					      NULL
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    &events,
 | 
				
			||||||
 | 
					    NULL
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!pw.stream)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    pw_thread_loop_unlock(pw.thread);
 | 
				
			||||||
 | 
					    DEBUG_ERROR("Failed to create the stream");
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
 | 
				
			||||||
 | 
					      &SPA_AUDIO_INFO_RAW_INIT(
 | 
				
			||||||
 | 
					        .format   = SPA_AUDIO_FORMAT_S16,
 | 
				
			||||||
 | 
					        .channels = channels,
 | 
				
			||||||
 | 
					        .rate     = sampleRate
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pw_stream_connect(
 | 
				
			||||||
 | 
					      pw.stream,
 | 
				
			||||||
 | 
					      PW_DIRECTION_OUTPUT,
 | 
				
			||||||
 | 
					      PW_ID_ANY,
 | 
				
			||||||
 | 
					      PW_STREAM_FLAG_AUTOCONNECT |
 | 
				
			||||||
 | 
					      PW_STREAM_FLAG_MAP_BUFFERS |
 | 
				
			||||||
 | 
					      PW_STREAM_FLAG_RT_PROCESS,
 | 
				
			||||||
 | 
					      params, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pw_thread_loop_unlock(pw.thread);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void pipewire_play(uint8_t * data, int size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (!pw.stream)
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for(int i = 0; i < size; i += pw.stride)
 | 
				
			||||||
 | 
					    ringbuffer_push(pw.buffer, data + i);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void pipewire_stop(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (!pw.stream)
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pw_thread_loop_lock(pw.thread);
 | 
				
			||||||
 | 
					  pw_stream_flush(pw.stream, true);
 | 
				
			||||||
 | 
					  pw_stream_destroy(pw.stream);
 | 
				
			||||||
 | 
					  pw.stream = NULL;
 | 
				
			||||||
 | 
					  pw_thread_loop_unlock(pw.thread);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct LG_AudioDevOps LGAD_PipeWire =
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  .name  = "PipeWire",
 | 
				
			||||||
 | 
					  .init  = pipewire_init,
 | 
				
			||||||
 | 
					  .free  = pipewire_free,
 | 
				
			||||||
 | 
					  .start = pipewire_start,
 | 
				
			||||||
 | 
					  .play  = pipewire_play,
 | 
				
			||||||
 | 
					  .stop  = pipewire_stop
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										63
									
								
								client/include/interface/audiodev.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								client/include/interface/audiodev.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Looking Glass
 | 
				
			||||||
 | 
					 * Copyright © 2017-2021 The Looking Glass Authors
 | 
				
			||||||
 | 
					 * https://looking-glass.io
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 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 2 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, write to the Free Software Foundation, Inc., 59
 | 
				
			||||||
 | 
					 * Temple Place, Suite 330, Boston, MA 02111-1307 USA
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef _H_I_AUDIODEV_
 | 
				
			||||||
 | 
					#define _H_I_AUDIODEV_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					#include <stdint.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct LG_AudioDevOps
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  /* internal name of the audio for debugging */
 | 
				
			||||||
 | 
					  const char * name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* called very early to allow for option registration, optional */
 | 
				
			||||||
 | 
					  void (*earlyInit)(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* called to initialize the audio backend */
 | 
				
			||||||
 | 
					  bool (*init)(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* final free */
 | 
				
			||||||
 | 
					  void (*free)(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* setup the playback audio stream
 | 
				
			||||||
 | 
					   * Note: currently SPICE only supports S16 samples so always assume so
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void (*start)(int channels, int sampleRate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* called for each packet of output audio to play
 | 
				
			||||||
 | 
					   * Note: size is the size of data in bytes, not frames/samples
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void (*play)(uint8_t * data, int size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* called when SPICE reports the audio stream has stopped */
 | 
				
			||||||
 | 
					  void (*stop)(void);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ASSERT_LG_AUDIODEV_VALID(x) \
 | 
				
			||||||
 | 
					  DEBUG_ASSERT((x)->name  ); \
 | 
				
			||||||
 | 
					  DEBUG_ASSERT((x)->init  ); \
 | 
				
			||||||
 | 
					  DEBUG_ASSERT((x)->free  ); \
 | 
				
			||||||
 | 
					  DEBUG_ASSERT((x)->start ); \
 | 
				
			||||||
 | 
					  DEBUG_ASSERT((x)->play  ); \
 | 
				
			||||||
 | 
					  DEBUG_ASSERT((x)->stop  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
@@ -428,6 +428,13 @@ static struct Option options[] =
 | 
				
			|||||||
    .type           = OPTION_TYPE_BOOL,
 | 
					    .type           = OPTION_TYPE_BOOL,
 | 
				
			||||||
    .value.x_bool   = true
 | 
					    .value.x_bool   = true
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    .module         = "spice",
 | 
				
			||||||
 | 
					    .name           = "audio",
 | 
				
			||||||
 | 
					    .description    = "Enable SPICE audio support",
 | 
				
			||||||
 | 
					    .type           = OPTION_TYPE_BOOL,
 | 
				
			||||||
 | 
					    .value.x_bool   = true
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    .module         = "spice",
 | 
					    .module         = "spice",
 | 
				
			||||||
    .name           = "scaleCursor",
 | 
					    .name           = "scaleCursor",
 | 
				
			||||||
@@ -609,6 +616,7 @@ bool config_load(int argc, char * argv[])
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    g_params.useSpiceInput     = option_get_bool("spice", "input"    );
 | 
					    g_params.useSpiceInput     = option_get_bool("spice", "input"    );
 | 
				
			||||||
    g_params.useSpiceClipboard = option_get_bool("spice", "clipboard");
 | 
					    g_params.useSpiceClipboard = option_get_bool("spice", "clipboard");
 | 
				
			||||||
 | 
					    g_params.useSpiceAudio     = option_get_bool("spice", "audio"    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (g_params.useSpiceClipboard)
 | 
					    if (g_params.useSpiceClipboard)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -846,6 +846,66 @@ static void reportBadVersion()
 | 
				
			|||||||
  DEBUG_ERROR("Please install the matching host application for this client");
 | 
					  DEBUG_ERROR("Please install the matching host application for this client");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void audioStart(int channels, int sampleRate, PSAudioFormat format,
 | 
				
			||||||
 | 
					  uint32_t time)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					   * we probe here so that the audiodev is operating in the context of the SPICE
 | 
				
			||||||
 | 
					   * thread/loop to avoid any audio API threading issues
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static int probed = false;
 | 
				
			||||||
 | 
					  if (!probed)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    probed = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // search for the best audiodev to use
 | 
				
			||||||
 | 
					    for(int i = 0; i < LG_AUDIODEV_COUNT; ++i)
 | 
				
			||||||
 | 
					      if (LG_AudioDevs[i]->init())
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        g_state.audioDev = LG_AudioDevs[i];
 | 
				
			||||||
 | 
					        DEBUG_INFO("Using AudioDev: %s", g_state.audioDev->name);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!g_state.audioDev)
 | 
				
			||||||
 | 
					      DEBUG_WARN("Failed to initialize an audio backend");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (g_state.audioDev)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    static int lastChannels   = 0;
 | 
				
			||||||
 | 
					    static int lastSampleRate = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (g_state.audioStarted)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      if (channels != lastChannels || sampleRate != lastSampleRate)
 | 
				
			||||||
 | 
					        g_state.audioDev->stop();
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    lastChannels   = channels;
 | 
				
			||||||
 | 
					    lastSampleRate = sampleRate;
 | 
				
			||||||
 | 
					    g_state.audioStarted = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    DEBUG_INFO("%d channels @ %dHz", channels, sampleRate);
 | 
				
			||||||
 | 
					    g_state.audioDev->start(channels, sampleRate);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void audioStop(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (g_state.audioDev)
 | 
				
			||||||
 | 
					    g_state.audioDev->stop();
 | 
				
			||||||
 | 
					  g_state.audioStarted = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void audioData(uint8_t * data, size_t size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (g_state.audioDev)
 | 
				
			||||||
 | 
					    g_state.audioDev->play(data, size);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int lg_run(void)
 | 
					static int lg_run(void)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  g_cursor.sens = g_params.mouseSens;
 | 
					  g_cursor.sens = g_params.mouseSens;
 | 
				
			||||||
@@ -921,7 +981,9 @@ static int lg_run(void)
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // try to connect to the spice server
 | 
					  // try to connect to the spice server
 | 
				
			||||||
  if (g_params.useSpiceInput || g_params.useSpiceClipboard)
 | 
					  if (g_params.useSpiceInput     ||
 | 
				
			||||||
 | 
					      g_params.useSpiceClipboard ||
 | 
				
			||||||
 | 
					      g_params.useSpiceAudio)
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    if (g_params.useSpiceClipboard)
 | 
					    if (g_params.useSpiceClipboard)
 | 
				
			||||||
      spice_set_clipboard_cb(
 | 
					      spice_set_clipboard_cb(
 | 
				
			||||||
@@ -930,7 +992,14 @@ static int lg_run(void)
 | 
				
			|||||||
          cb_spiceRelease,
 | 
					          cb_spiceRelease,
 | 
				
			||||||
          cb_spiceRequest);
 | 
					          cb_spiceRequest);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!spice_connect(g_params.spiceHost, g_params.spicePort, ""))
 | 
					    if (g_params.useSpiceAudio)
 | 
				
			||||||
 | 
					      spice_set_audio_cb(
 | 
				
			||||||
 | 
					          audioStart,
 | 
				
			||||||
 | 
					          audioStop,
 | 
				
			||||||
 | 
					          audioData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!spice_connect(g_params.spiceHost, g_params.spicePort, "",
 | 
				
			||||||
 | 
					          g_params.useSpiceAudio))
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      DEBUG_ERROR("Failed to connect to spice server");
 | 
					      DEBUG_ERROR("Failed to connect to spice server");
 | 
				
			||||||
      return -1;
 | 
					      return -1;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,6 +24,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "dynamic/displayservers.h"
 | 
					#include "dynamic/displayservers.h"
 | 
				
			||||||
#include "dynamic/renderers.h"
 | 
					#include "dynamic/renderers.h"
 | 
				
			||||||
 | 
					#include "dynamic/audiodev.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "common/thread.h"
 | 
					#include "common/thread.h"
 | 
				
			||||||
#include "common/types.h"
 | 
					#include "common/types.h"
 | 
				
			||||||
@@ -133,6 +134,9 @@ struct AppState
 | 
				
			|||||||
  bool     resizeDone;
 | 
					  bool     resizeDone;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool     autoIdleInhibitState;
 | 
					  bool     autoIdleInhibitState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  struct LG_AudioDevOps * audioDev;
 | 
				
			||||||
 | 
					  bool audioStarted;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct AppParams
 | 
					struct AppParams
 | 
				
			||||||
@@ -154,6 +158,7 @@ struct AppParams
 | 
				
			|||||||
  LG_RendererRotate winRotate;
 | 
					  LG_RendererRotate winRotate;
 | 
				
			||||||
  bool              useSpiceInput;
 | 
					  bool              useSpiceInput;
 | 
				
			||||||
  bool              useSpiceClipboard;
 | 
					  bool              useSpiceClipboard;
 | 
				
			||||||
 | 
					  bool              useSpiceAudio;
 | 
				
			||||||
  const char *      spiceHost;
 | 
					  const char *      spiceHost;
 | 
				
			||||||
  unsigned int      spicePort;
 | 
					  unsigned int      spicePort;
 | 
				
			||||||
  bool              clipboardToVM;
 | 
					  bool              clipboardToVM;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -93,6 +93,9 @@ feature is disabled when running :ref:`cmake <client_building>`.
 | 
				
			|||||||
   -  libwayland-dev
 | 
					   -  libwayland-dev
 | 
				
			||||||
   -  wayland-protocols
 | 
					   -  wayland-protocols
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-  Disable with ``cmake -DENABLE_PIPEWIRE=no ..``
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   -  libpipewire-0.3-dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. _client_deps_recommended:
 | 
					.. _client_deps_recommended:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ cmake
 | 
				
			|||||||
config
 | 
					config
 | 
				
			||||||
dejavu
 | 
					dejavu
 | 
				
			||||||
deuteranope
 | 
					deuteranope
 | 
				
			||||||
 | 
					dev
 | 
				
			||||||
dir
 | 
					dir
 | 
				
			||||||
distros
 | 
					distros
 | 
				
			||||||
dmabuf
 | 
					dmabuf
 | 
				
			||||||
@@ -19,6 +20,7 @@ ini
 | 
				
			|||||||
kvmfr
 | 
					kvmfr
 | 
				
			||||||
laggy
 | 
					laggy
 | 
				
			||||||
libdecor
 | 
					libdecor
 | 
				
			||||||
 | 
					libpipewire
 | 
				
			||||||
libvirt
 | 
					libvirt
 | 
				
			||||||
linux
 | 
					linux
 | 
				
			||||||
LookingGlass
 | 
					LookingGlass
 | 
				
			||||||
 
 | 
				
			|||||||
 Submodule repos/PureSpice updated: 8d8b47454e...3ea156974b
									
								
							
		Reference in New Issue
	
	Block a user