Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,25 @@ if(ENABLE_H264)
endif()
endif()

trioption(ENABLE_AUDIO "Enable audio playback in the viewer")
if(ENABLE_AUDIO)
if(WIN32)
# waveOut comes with the OS
set(HAVE_AUDIO 1)
else()
if(ENABLE_AUDIO STREQUAL "AUTO")
find_package(Pulse)
else()
find_package(Pulse REQUIRED)
endif()
if(PULSE_FOUND)
set(HAVE_AUDIO 1)
else()
message(WARNING "PulseAudio NOT found. Audio playback disabled.")
endif()
endif()
endif()

# Check for libjpeg
find_package(JPEG REQUIRED)

Expand Down
47 changes: 47 additions & 0 deletions cmake/Modules/FindPulse.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#[=======================================================================[.rst:
FindPulse
---------

Find the PulseAudio client library

Result variables
^^^^^^^^^^^^^^^^

This module will set the following variables if found:

``PULSE_INCLUDE_DIRS``
where to find pulse/pulseaudio.h, etc.
``PULSE_LIBRARIES``
the libraries to link against to use libpulse.
``PULSE_FOUND``
TRUE if found

#]=======================================================================]

find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(PC_Pulse QUIET libpulse)
endif()

find_path(Pulse_INCLUDE_DIR NAMES pulse/pulseaudio.h
HINTS
${PC_Pulse_INCLUDE_DIRS}
)
mark_as_advanced(Pulse_INCLUDE_DIR)

find_library(Pulse_LIBRARY NAMES pulse
HINTS
${PC_Pulse_LIBRARY_DIRS}
)
mark_as_advanced(Pulse_LIBRARY)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Pulse
REQUIRED_VARS
Pulse_LIBRARY Pulse_INCLUDE_DIR
)

if(Pulse_FOUND)
set(PULSE_INCLUDE_DIRS ${Pulse_INCLUDE_DIR})
set(PULSE_LIBRARIES ${Pulse_LIBRARY})
endif()
90 changes: 89 additions & 1 deletion common/rfb/CConnection.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <rfb/Exception.h>
#include <rfb/clipboardTypes.h>
#include <rfb/fenceTypes.h>
#include <rfb/qemuTypes.h>
#include <rfb/screenTypes.h>
#include <rfb/CMsgReader.h>
#include <rfb/CMsgWriter.h>
Expand Down Expand Up @@ -65,6 +66,7 @@ CConnection::CConnection()
: csecurity(nullptr),
supportsLocalCursor(false), supportsCursorPosition(false),
supportsDesktopResize(false), supportsLEDState(false),
supportsAudio(false),
is(nullptr), os(nullptr), reader_(nullptr), writer_(nullptr),
shared(false),
state_(RFBSTATE_UNINITIALISED),
Expand All @@ -74,7 +76,8 @@ CConnection::CConnection()
firstUpdate(true), pendingUpdate(false), continuousUpdates(false),
forceNonincremental(true),
framebuffer(nullptr), decoder(this),
hasRemoteClipboard(false), hasLocalClipboard(false)
hasRemoteClipboard(false), hasLocalClipboard(false),
audioRequested(false)
{
}

Expand Down Expand Up @@ -514,6 +517,11 @@ void CConnection::supportsQEMUKeyEvent()
server.supportsQEMUKeyEvent = true;
}

void CConnection::supportsQEMUAudio()
{
server.supportsQEMUAudio = true;
}

void CConnection::supportsExtendedMouseButtons()
{
server.supportsExtendedMouseButtons = true;
Expand Down Expand Up @@ -589,6 +597,63 @@ void CConnection::framebufferUpdateEnd()

firstUpdate = false;
}

// The server tells us it can send audio by sending us a rectangle
// with that pseudo encoding, so this is the earliest point at which
// we can safely ask for it
if (server.supportsQEMUAudio && !audioRequested)
requestAudio();
}

void CConnection::requestAudio()
{
uint8_t sampleFormat, channels;
uint32_t frequency;

assert(!audioRequested);

// Only ask once, whatever the answer. A server that offered audio
// and then had it declined will not offer it again.
audioRequested = true;

if (!getAudioFormat(&sampleFormat, &channels, &frequency))
return;

vlog.info(_("Requesting audio (format %d, %d channels, %d Hz)"),
(int)sampleFormat, (int)channels, (int)frequency);

writer()->writeQEMUAudioSetFormat(sampleFormat, channels, frequency);
writer()->writeQEMUAudioEnable(true);
}

void CConnection::handleQEMUServerMessage(uint8_t submessage,
uint16_t operation,
const uint8_t* data,
size_t length)
{
if (submessage != qemuAudio) {
vlog.debug("Ignoring unknown QEMU submessage %d", (int)submessage);
return;
}

// A server that never got a request for audio has no business
// sending any
if (!server.supportsQEMUAudio)
throw protocol_error(_("Unexpected audio message"));

switch (operation) {
case msgFromQemuAudioBegin:
handleAudioBegin();
break;
case msgFromQemuAudioEnd:
handleAudioEnd();
break;
case msgFromQemuAudioData:
handleAudioData(data, length);
break;
default:
vlog.debug("Ignoring unknown QEMU audio operation %d", (int)operation);
}
}

bool CConnection::dataRect(const core::Rect& r, int encoding)
Expand Down Expand Up @@ -742,6 +807,26 @@ void CConnection::handleClipboardData(const char* /*data*/)
{
}

bool CConnection::getAudioFormat(uint8_t* /*sampleFormat*/,
uint8_t* /*channels*/,
uint32_t* /*frequency*/)
{
return false;
}

void CConnection::handleAudioBegin()
{
}

void CConnection::handleAudioEnd()
{
}

void CConnection::handleAudioData(const uint8_t* /*data*/,
size_t /*length*/)
{
}

void CConnection::requestClipboard()
{
if (hasRemoteClipboard) {
Expand Down Expand Up @@ -1021,6 +1106,9 @@ void CConnection::updateEncodings()
encodings.push_back(pseudoEncodingLEDState);
encodings.push_back(pseudoEncodingVMwareLEDState);
}
if (supportsAudio) {
encodings.push_back(pseudoEncodingQEMUAudio);
}

encodings.push_back(pseudoEncodingDesktopName);
encodings.push_back(pseudoEncodingLastRect);
Expand Down
34 changes: 34 additions & 0 deletions common/rfb/CConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ namespace rfb {

void supportsQEMUKeyEvent() override;

void supportsQEMUAudio() override;

void supportsExtendedMouseButtons() override;

void serverInit(int width, int height, const PixelFormat& pf,
Expand Down Expand Up @@ -244,6 +246,10 @@ namespace rfb {
void handleClipboardProvide(uint32_t flags, const size_t* lengths,
const uint8_t* const* data) override;

void handleQEMUServerMessage(uint8_t submessage, uint16_t operation,
const uint8_t* data,
size_t length) override;


// Methods to be overridden in a derived class

Expand Down Expand Up @@ -278,6 +284,26 @@ namespace rfb {
// server received the request.
virtual void handleClipboardData(const char* data);

// getAudioFormat() is called once the server has indicated that it
// can send audio, to determine the format to ask for. It should
// return false if the client cannot play audio after all, in which
// case none will be requested. The sample format is one of the
// qemuAudioFormat* constants.
virtual bool getAudioFormat(uint8_t* sampleFormat, uint8_t* channels,
uint32_t* frequency);

// handleAudioBegin() and handleAudioEnd() are called when the
// server starts and stops sending audio. There may be many such
// periods in a single session, as the server only sends audio
// whilst something is playing.
virtual void handleAudioBegin();
virtual void handleAudioEnd();

// handleAudioData() is called with a chunk of samples, in the
// format previously agreed. Note that a chunk is not aligned to
// anything in particular, and may even be empty.
virtual void handleAudioData(const uint8_t* data, size_t length);

protected:
CSecurity *csecurity;
SecurityClient security;
Expand All @@ -297,8 +323,14 @@ namespace rfb {
bool supportsCursorPosition;
bool supportsDesktopResize;
bool supportsLEDState;
// Unlike the others, leaving this off is not just a matter of
// ignoring something the server sends anyway. The server will
// encode and send audio purely because we asked for it, so do not
// ask unless there is somewhere for it to go.
bool supportsAudio;

private:
void requestAudio();
bool processVersionMsg();
bool processSecurityTypesMsg();
bool processSecurityMsg();
Expand Down Expand Up @@ -346,6 +378,8 @@ namespace rfb {
bool hasLocalClipboard;
bool unsolicitedClipboardAttempt;

bool audioRequested;

struct DownKey {
uint32_t keyCode;
uint32_t keySym;
Expand Down
11 changes: 11 additions & 0 deletions common/rfb/CMsgHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ namespace rfb {
const uint8_t data[]) = 0;
virtual void endOfContinuousUpdates() = 0;
virtual void supportsQEMUKeyEvent() = 0;
virtual void supportsQEMUAudio() = 0;
virtual void supportsExtendedMouseButtons() = 0;
virtual void serverInit(int width, int height,
const PixelFormat& pf,
Expand Down Expand Up @@ -84,6 +85,16 @@ namespace rfb {
const size_t* lengths,
const uint8_t* const* data) = 0;

// A submessage of QEMU's vendor extension, with the payload that
// follows the operation code, if any. The submessage is left
// undecoded here as QEMU multiplexes several unrelated things on
// this message type, and only some of them concern any given
// handler.
virtual void handleQEMUServerMessage(uint8_t submessage,
uint16_t operation,
const uint8_t* data,
size_t length) = 0;

ServerParams server;
};
}
Expand Down
Loading