Added rdpsnd latency calculation.

This commit is contained in:
Armin Novak 2019-08-22 09:07:33 +02:00
parent a298a3d8d5
commit 52af56d956

View File

@ -30,6 +30,7 @@
#include <string.h> #include <string.h>
#include <winpr/crt.h> #include <winpr/crt.h>
#include <winpr/sysinfo.h>
#include <freerdp/types.h> #include <freerdp/types.h>
@ -50,6 +51,7 @@ struct rdpsnd_mac_plugin
AVAudioEngine* engine; AVAudioEngine* engine;
AVAudioPlayerNode* player; AVAudioPlayerNode* player;
UINT64 diff;
}; };
typedef struct rdpsnd_mac_plugin rdpsndMacPlugin; typedef struct rdpsnd_mac_plugin rdpsndMacPlugin;
@ -124,12 +126,21 @@ static char* FormatError(OSStatus st)
} }
} }
static void rdpsnd_mac_release(rdpsndMacPlugin* mac)
{
if (mac->player)
[mac->player release];
mac->player = NULL;
if (mac->engine)
[mac->engine release];
mac->engine = NULL;
}
static BOOL rdpsnd_mac_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency) static BOOL rdpsnd_mac_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency)
{ {
OSStatus err; OSStatus err;
NSError* error; NSError* error;
AudioDeviceID outputDeviceID;
UInt32 propertySize;
AudioObjectPropertyAddress propertyAddress = { AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyDefaultSystemOutputDevice, kAudioHardwarePropertyDefaultSystemOutputDevice,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeGlobal,
@ -142,37 +153,25 @@ static BOOL rdpsnd_mac_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* form
if (!rdpsnd_mac_set_format(device, format, latency)) if (!rdpsnd_mac_set_format(device, format, latency))
return FALSE; return FALSE;
propertySize = sizeof(outputDeviceID);
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propertySize, &outputDeviceID);
if (err)
{
WLog_ERR(TAG, "AudioHardwareGetProperty: %s", FormatError(err));
return FALSE;
}
mac->engine = [[AVAudioEngine alloc] init]; mac->engine = [[AVAudioEngine alloc] init];
if (!mac->engine) if (!mac->engine)
return FALSE; return FALSE;
err = AudioUnitSetProperty(mac->engine.outputNode.audioUnit, kAudioOutputUnitProperty_CurrentDevice, mac->player = [[AVAudioPlayerNode alloc] init];
kAudioUnitScope_Global, 0, &outputDeviceID, sizeof(outputDeviceID)); if (!mac->player)
if (err)
{ {
[mac->engine release]; rdpsnd_mac_release(mac);
WLog_ERR(TAG, "AudioUnitSetProperty: %s", FormatError(err)); WLog_ERR(TAG, "AVAudioPlayerNode::init() failed");
return FALSE; return FALSE;
} }
mac->player = [[AVAudioPlayerNode alloc] init];
[mac->engine attachNode: mac->player]; [mac->engine attachNode: mac->player];
[mac->engine connect: mac->player to: mac->engine.mainMixerNode format: nil]; [mac->engine connect: mac->player to: mac->engine.mainMixerNode format: nil];
if (![mac->engine startAndReturnError: &error]) if (![mac->engine startAndReturnError: &error])
{ {
[mac->player release]; device->Close(device);
[mac->engine release];
WLog_ERR(TAG, "Failed to start audio player %s", [error UTF8String]); WLog_ERR(TAG, "Failed to start audio player %s", [error UTF8String]);
return FALSE; return FALSE;
} }
@ -185,21 +184,19 @@ static void rdpsnd_mac_close(rdpsndDevicePlugin* device)
{ {
rdpsndMacPlugin* mac = (rdpsndMacPlugin*) device; rdpsndMacPlugin* mac = (rdpsndMacPlugin*) device;
if (mac->isOpen)
{
mac->isOpen = FALSE;
// TODO: Close
if (mac->isPlaying) if (mac->isPlaying)
{
[mac->player stop]; [mac->player stop];
[mac->engine stop];
[mac->engine disconnectNodeInput: mac->player];
[mac->engine detachNode: mac->player];
[mac->player release];
[mac->engine release];
mac->isPlaying = FALSE; mac->isPlaying = FALSE;
} }
if (mac->isOpen)
{
[mac->engine stop];
mac->isOpen = FALSE;
}
rdpsnd_mac_release(mac);
} }
static void rdpsnd_mac_free(rdpsndDevicePlugin* device) static void rdpsnd_mac_free(rdpsndDevicePlugin* device)
@ -252,6 +249,7 @@ static void rdpsnd_mac_start(rdpsndDevicePlugin* device)
[mac->player play]; [mac->player play];
mac->isPlaying = TRUE; mac->isPlaying = TRUE;
mac->diff = 100; /* Initial latency, corrected after first sample is played. */
} }
} }
@ -263,16 +261,28 @@ static UINT rdpsnd_mac_play(rdpsndDevicePlugin* device, const BYTE* data, size_t
float * const * db; float * const * db;
size_t pos, step, x; size_t pos, step, x;
AVAudioFrameCount count; AVAudioFrameCount count;
UINT64 start = GetTickCount64();
if (!mac->isOpen) if (!mac->isOpen)
return 0; return 0;
step = 2 * mac->format.nChannels; step = 2 * mac->format.nChannels;
rdpsnd_mac_start(device);
count = size / step; count = size / step;
format = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:mac->format.nSamplesPerSec channels:mac->format.nChannels interleaved:NO]; format = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:mac->format.nSamplesPerSec channels:mac->format.nChannels interleaved:NO];
if (!format)
{
WLog_WARN(TAG, "AVAudioFormat::init() failed");
return 0;
}
buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format frameCapacity: count]; buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format frameCapacity: count];
if (!buffer)
{
[format release];
WLog_WARN(TAG, "AVAudioPCMBuffer::init() failed");
return 0;
}
buffer.frameLength = buffer.frameCapacity; buffer.frameLength = buffer.frameCapacity;
db = buffer.floatChannelData; db = buffer.floatChannelData;
@ -287,9 +297,17 @@ static UINT rdpsnd_mac_play(rdpsndDevicePlugin* device, const BYTE* data, size_t
} }
} }
[mac->player scheduleBuffer: buffer completionHandler:nil]; rdpsnd_mac_start(device);
return 10; /* TODO: Get real latencry in [ms] */ [mac->player scheduleBuffer: buffer completionHandler:^{
UINT64 stop = GetTickCount64();
if (start > stop)
mac->diff = 0;
else
mac->diff = stop - start;
}];
return mac->diff > UINT_MAX ? UINT_MAX : mac->diff;
} }
#ifdef BUILTIN_CHANNELS #ifdef BUILTIN_CHANNELS