pulse.c (10910B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "config.h" 21 22#include "pulse/pulse.h" 23 24#include <guacamole/audio.h> 25#include <guacamole/mem.h> 26#include <guacamole/client.h> 27#include <guacamole/user.h> 28#include <pulse/pulseaudio.h> 29 30/** 31 * Returns whether the given buffer contains only silence (only null bytes). 32 * 33 * @param buffer 34 * The audio buffer to check. 35 * 36 * @param length 37 * The length of the buffer to check. 38 * 39 * @return 40 * Non-zero if the audio buffer contains silence, zero otherwise. 41 */ 42static int guac_pa_is_silence(const void* buffer, size_t length) { 43 44 int i; 45 46 const unsigned char* current = (const unsigned char*) buffer; 47 48 /* For each byte in buffer */ 49 for (i = 0; i < length; i++) { 50 51 /* If current value non-zero, then not silence */ 52 if (*(current++)) 53 return 0; 54 55 } 56 57 /* Otherwise, the buffer contains 100% silence */ 58 return 1; 59 60} 61 62/** 63 * Callback invoked by PulseAudio when PCM data is available for reading 64 * from the given stream. The PCM data can be read using pa_stream_peek(). 65 * 66 * @param stream 67 * The PulseAudio stream which has PCM data available. 68 * 69 * @param length 70 * The number of bytes of PCM data available on the given stream. 71 * 72 * @param data 73 * A pointer to the guac_pa_stream structure associated with the Guacamole 74 * stream receiving audio data from PulseAudio. 75 */ 76static void __stream_read_callback(pa_stream* stream, size_t length, 77 void* data) { 78 79 guac_pa_stream* guac_stream = (guac_pa_stream*) data; 80 guac_audio_stream* audio = guac_stream->audio; 81 82 const void* buffer; 83 84 /* Read data */ 85 pa_stream_peek(stream, &buffer, &length); 86 87 /* Continuously write received PCM data */ 88 if (!guac_pa_is_silence(buffer, length)) 89 guac_audio_stream_write_pcm(audio, buffer, length); 90 91 /* Flush upon silence */ 92 else 93 guac_audio_stream_flush(audio); 94 95 /* Advance buffer */ 96 pa_stream_drop(stream); 97 98} 99 100/** 101 * Callback invoked by PulseAudio when the audio stream has changed state. The 102 * new state can be retrieved using pa_stream_get_state(). 103 * 104 * @param stream 105 * The PulseAudio stream which has changed state. 106 * 107 * @param data 108 * A pointer to the guac_pa_stream structure associated with the Guacamole 109 * stream receiving audio data from PulseAudio. 110 */ 111static void __stream_state_callback(pa_stream* stream, void* data) { 112 113 guac_pa_stream* guac_stream = (guac_pa_stream*) data; 114 guac_client* client = guac_stream->client; 115 116 switch (pa_stream_get_state(stream)) { 117 118 case PA_STREAM_UNCONNECTED: 119 guac_client_log(client, GUAC_LOG_INFO, 120 "PulseAudio stream currently unconnected"); 121 break; 122 123 case PA_STREAM_CREATING: 124 guac_client_log(client, GUAC_LOG_INFO, "PulseAudio stream being created..."); 125 break; 126 127 case PA_STREAM_READY: 128 guac_client_log(client, GUAC_LOG_INFO, "PulseAudio stream now ready"); 129 break; 130 131 case PA_STREAM_FAILED: 132 guac_client_log(client, GUAC_LOG_INFO, "PulseAudio stream connection failed"); 133 break; 134 135 case PA_STREAM_TERMINATED: 136 guac_client_log(client, GUAC_LOG_INFO, "PulseAudio stream terminated"); 137 break; 138 139 default: 140 guac_client_log(client, GUAC_LOG_INFO, 141 "Unknown PulseAudio stream state: 0x%x", 142 pa_stream_get_state(stream)); 143 144 } 145 146} 147 148/** 149 * Callback invoked by PulseAudio when the audio sink information has been 150 * retrieved. The callback is invoked repeatedly, once for each available 151 * sink, followed by one final invocation with the is_last flag set. The final 152 * invocation (with is_last set) does not describe a sink; it serves as a 153 * terminator only. 154 * 155 * @param context 156 * The PulseAudio context which is providing the sink information. 157 * 158 * @param info 159 * Information describing an available audio sink. 160 * 161 * @param is_last 162 * Non-zero if this invocation is the final invocation of this callback for 163 * all currently-available sinks (and this invocation does not describe a 164 * sink), zero otherwise. 165 * 166 * @param data 167 * A pointer to the guac_pa_stream structure associated with the Guacamole 168 * stream receiving audio data from PulseAudio. 169 */ 170static void __context_get_sink_info_callback(pa_context* context, 171 const pa_sink_info* info, int is_last, void* data) { 172 173 guac_pa_stream* guac_stream = (guac_pa_stream*) data; 174 guac_client* client = guac_stream->client; 175 176 pa_stream* stream; 177 pa_sample_spec spec; 178 pa_buffer_attr attr; 179 180 /* Stop if end of list reached */ 181 if (is_last) 182 return; 183 184 guac_client_log(client, GUAC_LOG_INFO, "Starting streaming from \"%s\"", 185 info->description); 186 187 /* Set format */ 188 spec.format = PA_SAMPLE_S16LE; 189 spec.rate = GUAC_PULSE_AUDIO_RATE; 190 spec.channels = GUAC_PULSE_AUDIO_CHANNELS; 191 192 attr.maxlength = -1; 193 attr.fragsize = GUAC_PULSE_AUDIO_FRAGMENT_SIZE; 194 195 /* Create stream */ 196 stream = pa_stream_new(context, "Guacamole Audio", &spec, NULL); 197 198 /* Set stream callbacks */ 199 pa_stream_set_state_callback(stream, __stream_state_callback, guac_stream); 200 pa_stream_set_read_callback(stream, __stream_read_callback, guac_stream); 201 202 /* Start stream */ 203 pa_stream_connect_record(stream, info->monitor_source_name, &attr, 204 PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND 205 | PA_STREAM_ADJUST_LATENCY); 206 207} 208 209/** 210 * Callback invoked by PulseAudio when server information has been retrieved. 211 * 212 * @param context 213 * The PulseAudio context which is providing the sink information. 214 * 215 * @param info 216 * Information describing the PulseAudio server. 217 * 218 * @param data 219 * A pointer to the guac_pa_stream structure associated with the Guacamole 220 * stream receiving audio data from PulseAudio. 221 */ 222static void __context_get_server_info_callback(pa_context* context, 223 const pa_server_info* info, void* data) { 224 225 guac_pa_stream* guac_stream = (guac_pa_stream*) data; 226 guac_client* client = guac_stream->client; 227 228 /* If no default sink, cannot continue */ 229 if (info->default_sink_name == NULL) { 230 guac_client_log(client, GUAC_LOG_ERROR, "No default sink. Cannot stream audio."); 231 return; 232 } 233 234 guac_client_log(client, GUAC_LOG_INFO, "Will use default sink: \"%s\"", 235 info->default_sink_name); 236 237 /* Wait for default sink information */ 238 pa_operation_unref( 239 pa_context_get_sink_info_by_name(context, 240 info->default_sink_name, __context_get_sink_info_callback, 241 guac_stream)); 242 243} 244 245/** 246 * Callback invoked by PulseAudio when the overall audio context has changed 247 * state. The new state can be retrieved using pa_context_get_state(). 248 * 249 * @param context 250 * The PulseAudio context which has changed state. 251 * 252 * @param data 253 * A pointer to the guac_pa_stream structure associated with the Guacamole 254 * stream receiving audio data from PulseAudio. 255 */ 256static void __context_state_callback(pa_context* context, void* data) { 257 258 guac_pa_stream* guac_stream = (guac_pa_stream*) data; 259 guac_client* client = guac_stream->client; 260 261 switch (pa_context_get_state(context)) { 262 263 case PA_CONTEXT_UNCONNECTED: 264 guac_client_log(client, GUAC_LOG_INFO, 265 "PulseAudio reports it is unconnected"); 266 break; 267 268 case PA_CONTEXT_CONNECTING: 269 guac_client_log(client, GUAC_LOG_INFO, "Connecting to PulseAudio..."); 270 break; 271 272 case PA_CONTEXT_AUTHORIZING: 273 guac_client_log(client, GUAC_LOG_INFO, 274 "Authorizing PulseAudio connection..."); 275 break; 276 277 case PA_CONTEXT_SETTING_NAME: 278 guac_client_log(client, GUAC_LOG_INFO, "Sending client name..."); 279 break; 280 281 case PA_CONTEXT_READY: 282 guac_client_log(client, GUAC_LOG_INFO, "PulseAudio now ready"); 283 pa_operation_unref(pa_context_get_server_info(context, 284 __context_get_server_info_callback, guac_stream)); 285 break; 286 287 case PA_CONTEXT_FAILED: 288 guac_client_log(client, GUAC_LOG_INFO, "PulseAudio connection failed"); 289 break; 290 291 case PA_CONTEXT_TERMINATED: 292 guac_client_log(client, GUAC_LOG_INFO, "PulseAudio connection terminated"); 293 break; 294 295 default: 296 guac_client_log(client, GUAC_LOG_INFO, 297 "Unknown PulseAudio context state: 0x%x", 298 pa_context_get_state(context)); 299 300 } 301 302} 303 304guac_pa_stream* guac_pa_stream_alloc(guac_client* client, 305 const char* server_name) { 306 307 guac_audio_stream* audio = guac_audio_stream_alloc(client, NULL, 308 GUAC_PULSE_AUDIO_RATE, GUAC_PULSE_AUDIO_CHANNELS, 309 GUAC_PULSE_AUDIO_BPS); 310 311 /* Abort if audio stream cannot be created */ 312 if (audio == NULL) 313 return NULL; 314 315 /* Init main loop */ 316 guac_pa_stream* stream = guac_mem_alloc(sizeof(guac_pa_stream)); 317 stream->client = client; 318 stream->audio = audio; 319 stream->pa_mainloop = pa_threaded_mainloop_new(); 320 321 /* Create context */ 322 pa_context* context = pa_context_new( 323 pa_threaded_mainloop_get_api(stream->pa_mainloop), 324 "Guacamole Audio"); 325 326 /* Set up context */ 327 pa_context_set_state_callback(context, __context_state_callback, stream); 328 pa_context_connect(context, server_name, PA_CONTEXT_NOAUTOSPAWN, NULL); 329 330 /* Start loop */ 331 pa_threaded_mainloop_start(stream->pa_mainloop); 332 333 return stream; 334 335} 336 337void guac_pa_stream_add_user(guac_pa_stream* stream, guac_user* user) { 338 guac_audio_stream_add_user(stream->audio, user); 339} 340 341void guac_pa_stream_free(guac_pa_stream* stream) { 342 343 /* Stop loop */ 344 pa_threaded_mainloop_stop(stream->pa_mainloop); 345 346 /* Free underlying audio stream */ 347 guac_audio_stream_free(stream->audio); 348 349 /* Stream now ended */ 350 guac_client_log(stream->client, GUAC_LOG_INFO, "Audio stream finished"); 351 guac_mem_free(stream); 352 353} 354