| | 1 | /***************************************************************************** |
|---|
| | 2 | * pulse.c : Pulseaudio output plugin for vlc |
|---|
| | 3 | ***************************************************************************** |
|---|
| | 4 | * Copyright (C) 2000-2002 the VideoLAN team |
|---|
| | 5 | * |
|---|
| | 6 | * Authors: Martin Hamrle <hamrle@post.cz> |
|---|
| | 7 | * |
|---|
| | 8 | * This program is free software; you can redistribute it and/or modify |
|---|
| | 9 | * it under the terms of the GNU General Public License as published by |
|---|
| | 10 | * the Free Software Foundation; either version 2 of the License, or |
|---|
| | 11 | * (at your option) any later version. |
|---|
| | 12 | * |
|---|
| | 13 | * This program is distributed in the hope that it will be useful, |
|---|
| | 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| | 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| | 16 | * GNU General Public License for more details. |
|---|
| | 17 | * |
|---|
| | 18 | * You should have received a copy of the GNU General Public License |
|---|
| | 19 | * along with this program; if not, write to the Free Software |
|---|
| | 20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
|---|
| | 21 | *****************************************************************************/ |
|---|
| | 22 | |
|---|
| | 23 | /***************************************************************************** |
|---|
| | 24 | * Preamble |
|---|
| | 25 | *****************************************************************************/ |
|---|
| | 26 | #ifdef HAVE_CONFIG_H |
|---|
| | 27 | # include "config.h" |
|---|
| | 28 | #endif |
|---|
| | 29 | |
|---|
| | 30 | #include <vlc/vlc.h> |
|---|
| | 31 | #include <vlc_aout.h> |
|---|
| | 32 | |
|---|
| | 33 | #include <pulse/pulseaudio.h> |
|---|
| | 34 | |
|---|
| | 35 | /***************************************************************************** |
|---|
| | 36 | * aout_sys_t: Pulseaudio output method descriptor |
|---|
| | 37 | ***************************************************************************** |
|---|
| | 38 | * This structure is part of the audio output thread descriptor. |
|---|
| | 39 | * It describes the specific properties of an audio device. |
|---|
| | 40 | *****************************************************************************/ |
|---|
| | 41 | struct aout_sys_t |
|---|
| | 42 | { |
|---|
| | 43 | /** PulseAudio playback stream object */ |
|---|
| | 44 | struct pa_stream *stream; |
|---|
| | 45 | |
|---|
| | 46 | /** PulseAudio connection context */ |
|---|
| | 47 | struct pa_context *context; |
|---|
| | 48 | |
|---|
| | 49 | /** Main event loop object */ |
|---|
| | 50 | struct pa_threaded_mainloop *mainloop; |
|---|
| | 51 | |
|---|
| | 52 | int started; |
|---|
| | 53 | size_t buffer_size; |
|---|
| | 54 | mtime_t start_date; |
|---|
| | 55 | }; |
|---|
| | 56 | |
|---|
| | 57 | #define PULSE_CLIENT_NAME "vlc" |
|---|
| | 58 | |
|---|
| | 59 | #define CHECK_DEAD_GOTO(label) do { \ |
|---|
| | 60 | if (!p_sys->context || pa_context_get_state(p_sys->context) != PA_CONTEXT_READY || \ |
|---|
| | 61 | !p_sys->stream || pa_stream_get_state(p_sys->stream) != PA_STREAM_READY) { \ |
|---|
| | 62 | msg_Err(p_aout, "Connection died: %s\n", p_sys->context ? pa_strerror(pa_context_errno(p_sys->context)) : "NULL"); \ |
|---|
| | 63 | goto label; \ |
|---|
| | 64 | } \ |
|---|
| | 65 | } while(0); |
|---|
| | 66 | /***************************************************************************** |
|---|
| | 67 | * Local prototypes |
|---|
| | 68 | *****************************************************************************/ |
|---|
| | 69 | static int Open ( vlc_object_t * ); |
|---|
| | 70 | static void Close ( vlc_object_t * ); |
|---|
| | 71 | static void Play ( aout_instance_t * ); |
|---|
| | 72 | |
|---|
| | 73 | static void context_state_cb(pa_context *c, void *userdata); |
|---|
| | 74 | static void stream_state_cb(pa_stream *s, void * userdata); |
|---|
| | 75 | static void stream_request_cb(pa_stream *s, size_t length, void *userdata); |
|---|
| | 76 | static void stream_latency_update_cb(pa_stream *s, void *userdata); |
|---|
| | 77 | static void success_cb(pa_stream *s, int success, void *userdata); |
|---|
| | 78 | static void uninit(aout_instance_t *p_aout); |
|---|
| | 79 | /***************************************************************************** |
|---|
| | 80 | * Module descriptor |
|---|
| | 81 | *****************************************************************************/ |
|---|
| | 82 | vlc_module_begin(); |
|---|
| | 83 | set_shortname( "pulse" ); |
|---|
| | 84 | set_description( _("Pulseaudio audio output") ); |
|---|
| | 85 | set_capability( "audio output", 40 ); |
|---|
| | 86 | set_category( CAT_AUDIO ); |
|---|
| | 87 | set_subcategory( SUBCAT_AUDIO_AOUT ); |
|---|
| | 88 | add_shortcut( "pulse" ); |
|---|
| | 89 | set_callbacks( Open, Close ); |
|---|
| | 90 | vlc_module_end(); |
|---|
| | 91 | |
|---|
| | 92 | /***************************************************************************** |
|---|
| | 93 | * Open: open the audio device |
|---|
| | 94 | *****************************************************************************/ |
|---|
| | 95 | static int Open ( vlc_object_t *p_this ) |
|---|
| | 96 | { |
|---|
| | 97 | aout_instance_t *p_aout = (aout_instance_t *)p_this; |
|---|
| | 98 | struct aout_sys_t * p_sys; |
|---|
| | 99 | struct pa_sample_spec ss; |
|---|
| | 100 | const struct pa_buffer_attr *buffer_attr; |
|---|
| | 101 | struct pa_buffer_attr a; |
|---|
| | 102 | struct pa_channel_map map; |
|---|
| | 103 | |
|---|
| | 104 | /* Allocate structures */ |
|---|
| | 105 | p_aout->output.p_sys = p_sys = malloc( sizeof( aout_sys_t ) ); |
|---|
| | 106 | if( p_sys == NULL ) |
|---|
| | 107 | { |
|---|
| | 108 | msg_Err( p_aout, "out of memory" ); |
|---|
| | 109 | return VLC_ENOMEM; |
|---|
| | 110 | } |
|---|
| | 111 | p_sys->started = 0; |
|---|
| | 112 | p_sys->stream = NULL; |
|---|
| | 113 | p_sys->mainloop = NULL; |
|---|
| | 114 | p_sys->context = NULL; |
|---|
| | 115 | |
|---|
| | 116 | msg_Dbg(p_aout, "Pulse start initialization\n"); |
|---|
| | 117 | |
|---|
| | 118 | ss.rate = p_aout->output.output.i_rate; |
|---|
| | 119 | ss.channels = 2; |
|---|
| | 120 | |
|---|
| | 121 | ss.format = PA_SAMPLE_S16LE; |
|---|
| | 122 | p_aout->output.output.i_physical_channels = |
|---|
| | 123 | AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT; |
|---|
| | 124 | p_aout->output.output.i_format = AOUT_FMT_S16_NE; |
|---|
| | 125 | |
|---|
| | 126 | if (!pa_sample_spec_valid(&ss)) { |
|---|
| | 127 | msg_Err(p_aout,"Invalid sample spec\n"); |
|---|
| | 128 | goto fail; |
|---|
| | 129 | } |
|---|
| | 130 | |
|---|
| | 131 | a.maxlength = pa_bytes_per_second(&ss)/4/pa_frame_size(&ss); |
|---|
| | 132 | a.tlength = a.maxlength*9/10; |
|---|
| | 133 | a.prebuf = a.tlength/2; |
|---|
| | 134 | a.minreq = a.tlength/10; |
|---|
| | 135 | |
|---|
| | 136 | a.maxlength *= pa_frame_size(&ss); |
|---|
| | 137 | a.tlength *= pa_frame_size(&ss); |
|---|
| | 138 | a.prebuf *= pa_frame_size(&ss); |
|---|
| | 139 | a.minreq *= pa_frame_size(&ss); |
|---|
| | 140 | |
|---|
| | 141 | p_sys->buffer_size = a.minreq; |
|---|
| | 142 | |
|---|
| | 143 | pa_channel_map_init_stereo(&map); |
|---|
| | 144 | |
|---|
| | 145 | |
|---|
| | 146 | if (!(p_sys->mainloop = pa_threaded_mainloop_new())) { |
|---|
| | 147 | msg_Err(p_aout, "Failed to allocate main loop\n"); |
|---|
| | 148 | goto fail; |
|---|
| | 149 | } |
|---|
| | 150 | |
|---|
| | 151 | if (!(p_sys->context = pa_context_new(pa_threaded_mainloop_get_api(p_sys->mainloop), PULSE_CLIENT_NAME))) { |
|---|
| | 152 | msg_Err(p_aout, "Failed to allocate context\n"); |
|---|
| | 153 | goto fail; |
|---|
| | 154 | } |
|---|
| | 155 | |
|---|
| | 156 | pa_context_set_state_callback(p_sys->context, context_state_cb, p_aout); |
|---|
| | 157 | |
|---|
| | 158 | msg_Dbg(p_aout, "Pulse before context connect\n"); |
|---|
| | 159 | |
|---|
| | 160 | if (pa_context_connect(p_sys->context, NULL, 0, NULL) < 0) { |
|---|
| | 161 | msg_Err(p_aout, "Failed to connect to server: %s\n", pa_strerror(pa_context_errno(p_sys->context))); |
|---|
| | 162 | goto fail; |
|---|
| | 163 | } |
|---|
| | 164 | |
|---|
| | 165 | msg_Dbg(p_aout, "Pulse after context connect\n"); |
|---|
| | 166 | |
|---|
| | 167 | pa_threaded_mainloop_lock(p_sys->mainloop); |
|---|
| | 168 | |
|---|
| | 169 | if (pa_threaded_mainloop_start(p_sys->mainloop) < 0) { |
|---|
| | 170 | msg_Err(p_aout, "Failed to start main loop\n"); |
|---|
| | 171 | goto unlock_and_fail; |
|---|
| | 172 | } |
|---|
| | 173 | |
|---|
| | 174 | msg_Dbg(p_aout, "Pulse mainloop started\n"); |
|---|
| | 175 | |
|---|
| | 176 | /* Wait until the context is ready */ |
|---|
| | 177 | pa_threaded_mainloop_wait(p_sys->mainloop); |
|---|
| | 178 | |
|---|
| | 179 | if (pa_context_get_state(p_sys->context) != PA_CONTEXT_READY) { |
|---|
| | 180 | msg_Err(p_aout, "Failed to connect to server: %s\n", pa_strerror(pa_context_errno(p_sys->context))); |
|---|
| | 181 | goto unlock_and_fail; |
|---|
| | 182 | } |
|---|
| | 183 | |
|---|
| | 184 | if (!(p_sys->stream = pa_stream_new(p_sys->context, "audio stream", &ss, &map))) { |
|---|
| | 185 | msg_Err(p_aout, "Failed to create stream: %s\n", pa_strerror(pa_context_errno(p_sys->context))); |
|---|
| | 186 | goto unlock_and_fail; |
|---|
| | 187 | } |
|---|
| | 188 | |
|---|
| | 189 | msg_Dbg(p_aout, "Pulse after new stream\n"); |
|---|
| | 190 | |
|---|
| | 191 | pa_stream_set_state_callback(p_sys->stream, stream_state_cb, p_aout); |
|---|
| | 192 | pa_stream_set_write_callback(p_sys->stream, stream_request_cb, p_aout); |
|---|
| | 193 | pa_stream_set_latency_update_callback(p_sys->stream, stream_latency_update_cb, p_aout); |
|---|
| | 194 | |
|---|
| | 195 | if (pa_stream_connect_playback(p_sys->stream, NULL, &a, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL) < 0) { |
|---|
| | 196 | msg_Err(p_aout, "Failed to connect stream: %s\n", pa_strerror(pa_context_errno(p_sys->context))); |
|---|
| | 197 | goto unlock_and_fail; |
|---|
| | 198 | } |
|---|
| | 199 | |
|---|
| | 200 | msg_Dbg(p_aout, "Pulse stream connect\n"); |
|---|
| | 201 | |
|---|
| | 202 | /* Wait until the stream is ready */ |
|---|
| | 203 | pa_threaded_mainloop_wait(p_sys->mainloop); |
|---|
| | 204 | |
|---|
| | 205 | msg_Dbg(p_aout, "Pulse stream connected\n"); |
|---|
| | 206 | |
|---|
| | 207 | if (pa_stream_get_state(p_sys->stream) != PA_STREAM_READY) { |
|---|
| | 208 | msg_Err(p_aout, "Failed to connect to server: %s\n", pa_strerror(pa_context_errno(p_sys->context))); |
|---|
| | 209 | goto unlock_and_fail; |
|---|
| | 210 | } |
|---|
| | 211 | |
|---|
| | 212 | |
|---|
| | 213 | msg_Dbg(p_aout, "Pulse after stream get status\n"); |
|---|
| | 214 | |
|---|
| | 215 | pa_threaded_mainloop_unlock(p_sys->mainloop); |
|---|
| | 216 | |
|---|
| | 217 | buffer_attr = pa_stream_get_buffer_attr(p_sys->stream); |
|---|
| | 218 | p_aout->output.i_nb_samples = buffer_attr->minreq / pa_frame_size(&ss); |
|---|
| | 219 | p_aout->output.pf_play = Play; |
|---|
| | 220 | msg_Dbg(p_aout, "Pulse initialized successfully\n"); |
|---|
| | 221 | { |
|---|
| | 222 | char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX]; |
|---|
| | 223 | |
|---|
| | 224 | msg_Dbg(p_aout, "Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u\n", buffer_attr->maxlength, buffer_attr->tlength, buffer_attr->prebuf, buffer_attr->minreq); |
|---|
| | 225 | msg_Dbg(p_aout, "Using sample spec '%s', channel map '%s'.\n", |
|---|
| | 226 | pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(p_sys->stream)), |
|---|
| | 227 | pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(p_sys->stream))); |
|---|
| | 228 | |
|---|
| | 229 | msg_Dbg(p_aout, "Connected to device %s (%u, %ssuspended).\n", |
|---|
| | 230 | pa_stream_get_device_name(p_sys->stream), |
|---|
| | 231 | pa_stream_get_device_index(p_sys->stream), |
|---|
| | 232 | pa_stream_is_suspended(p_sys->stream) ? "" : "not "); |
|---|
| | 233 | } |
|---|
| | 234 | |
|---|
| | 235 | return VLC_SUCCESS; |
|---|
| | 236 | |
|---|
| | 237 | unlock_and_fail: |
|---|
| | 238 | msg_Dbg(p_aout, "Pulse initialization unlock and fail\n"); |
|---|
| | 239 | |
|---|
| | 240 | if (p_sys->mainloop) |
|---|
| | 241 | pa_threaded_mainloop_unlock(p_sys->mainloop); |
|---|
| | 242 | fail: |
|---|
| | 243 | msg_Err(p_aout, "Pulse initialization failed\n"); |
|---|
| | 244 | uninit(p_aout); |
|---|
| | 245 | return VLC_EGENERIC; |
|---|
| | 246 | } |
|---|
| | 247 | |
|---|
| | 248 | /***************************************************************************** |
|---|
| | 249 | * Play: play a sound samples buffer |
|---|
| | 250 | *****************************************************************************/ |
|---|
| | 251 | static void Play( aout_instance_t * p_aout ) |
|---|
| | 252 | { |
|---|
| | 253 | struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; |
|---|
| | 254 | |
|---|
| | 255 | pa_operation *o; |
|---|
| | 256 | |
|---|
| | 257 | if(!p_sys->started){ |
|---|
| | 258 | msg_Dbg(p_aout, "Pulse stream started\n"); |
|---|
| | 259 | p_sys->start_date = |
|---|
| | 260 | aout_FifoFirstDate( p_aout, &p_aout->output.fifo ); |
|---|
| | 261 | p_sys->started = 1; |
|---|
| | 262 | |
|---|
| | 263 | |
|---|
| | 264 | pa_threaded_mainloop_lock(p_sys->mainloop); |
|---|
| | 265 | if((o = pa_stream_flush(p_sys->stream, success_cb, p_aout))){ |
|---|
| | 266 | pa_operation_unref(o); |
|---|
| | 267 | } |
|---|
| | 268 | pa_threaded_mainloop_unlock(p_sys->mainloop); |
|---|
| | 269 | |
|---|
| | 270 | pa_threaded_mainloop_signal(p_sys->mainloop, 0); |
|---|
| | 271 | } |
|---|
| | 272 | } |
|---|
| | 273 | |
|---|
| | 274 | /***************************************************************************** |
|---|
| | 275 | * Close: close the audio device |
|---|
| | 276 | *****************************************************************************/ |
|---|
| | 277 | static void Close ( vlc_object_t *p_this ) |
|---|
| | 278 | { |
|---|
| | 279 | aout_instance_t *p_aout = (aout_instance_t *)p_this; |
|---|
| | 280 | struct aout_sys_t * p_sys = p_aout->output.p_sys; |
|---|
| | 281 | |
|---|
| | 282 | msg_Dbg(p_aout, "Pulse Close\n"); |
|---|
| | 283 | |
|---|
| | 284 | if(p_sys->stream){ |
|---|
| | 285 | pa_operation *o; |
|---|
| | 286 | pa_threaded_mainloop_lock(p_sys->mainloop); |
|---|
| | 287 | pa_stream_set_write_callback(p_sys->stream, NULL, NULL); |
|---|
| | 288 | |
|---|
| | 289 | if((o = pa_stream_drain(p_sys->stream, success_cb, p_aout))){ |
|---|
| | 290 | while (pa_operation_get_state(o) != PA_OPERATION_DONE) { |
|---|
| | 291 | CHECK_DEAD_GOTO(fail); |
|---|
| | 292 | pa_threaded_mainloop_wait(p_sys->mainloop); |
|---|
| | 293 | } |
|---|
| | 294 | |
|---|
| | 295 | fail: |
|---|
| | 296 | |
|---|
| | 297 | pa_operation_unref(o); |
|---|
| | 298 | } |
|---|
| | 299 | |
|---|
| | 300 | pa_threaded_mainloop_unlock(p_sys->mainloop); |
|---|
| | 301 | } |
|---|
| | 302 | uninit(p_aout); |
|---|
| | 303 | } |
|---|
| | 304 | |
|---|
| | 305 | static void uninit(aout_instance_t *p_aout){ |
|---|
| | 306 | struct aout_sys_t * p_sys = p_aout->output.p_sys; |
|---|
| | 307 | |
|---|
| | 308 | if (p_sys->mainloop) |
|---|
| | 309 | pa_threaded_mainloop_stop(p_sys->mainloop); |
|---|
| | 310 | |
|---|
| | 311 | if (p_sys->stream) { |
|---|
| | 312 | pa_stream_disconnect(p_sys->stream); |
|---|
| | 313 | pa_stream_unref(p_sys->stream); |
|---|
| | 314 | p_sys->stream = NULL; |
|---|
| | 315 | } |
|---|
| | 316 | |
|---|
| | 317 | if (p_sys->context) { |
|---|
| | 318 | pa_context_disconnect(p_sys->context); |
|---|
| | 319 | pa_context_unref(p_sys->context); |
|---|
| | 320 | p_sys->context = NULL; |
|---|
| | 321 | } |
|---|
| | 322 | |
|---|
| | 323 | if (p_sys->mainloop) { |
|---|
| | 324 | pa_threaded_mainloop_free(p_sys->mainloop); |
|---|
| | 325 | p_sys->mainloop = NULL; |
|---|
| | 326 | } |
|---|
| | 327 | |
|---|
| | 328 | free(p_sys); |
|---|
| | 329 | p_aout->output.p_sys = NULL; |
|---|
| | 330 | } |
|---|
| | 331 | |
|---|
| | 332 | static void context_state_cb(pa_context *c, void *userdata) { |
|---|
| | 333 | aout_instance_t *p_aout = (aout_instance_t *)userdata; |
|---|
| | 334 | struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; |
|---|
| | 335 | |
|---|
| | 336 | assert(c); |
|---|
| | 337 | |
|---|
| | 338 | msg_Dbg(p_aout, "Pulse context state changed\n"); |
|---|
| | 339 | |
|---|
| | 340 | switch (pa_context_get_state(c)) { |
|---|
| | 341 | case PA_CONTEXT_READY: |
|---|
| | 342 | case PA_CONTEXT_TERMINATED: |
|---|
| | 343 | case PA_CONTEXT_FAILED: |
|---|
| | 344 | msg_Dbg(p_aout, "Pulse context state changed signal\n"); |
|---|
| | 345 | pa_threaded_mainloop_signal(p_sys->mainloop, 0); |
|---|
| | 346 | break; |
|---|
| | 347 | |
|---|
| | 348 | case PA_CONTEXT_UNCONNECTED: |
|---|
| | 349 | case PA_CONTEXT_CONNECTING: |
|---|
| | 350 | case PA_CONTEXT_AUTHORIZING: |
|---|
| | 351 | case PA_CONTEXT_SETTING_NAME: |
|---|
| | 352 | msg_Dbg(p_aout, "Pulse context state changed no signal\n"); |
|---|
| | 353 | break; |
|---|
| | 354 | } |
|---|
| | 355 | } |
|---|
| | 356 | |
|---|
| | 357 | static void stream_state_cb(pa_stream *s, void * userdata) { |
|---|
| | 358 | aout_instance_t *p_aout = (aout_instance_t *)userdata; |
|---|
| | 359 | struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; |
|---|
| | 360 | |
|---|
| | 361 | assert(s); |
|---|
| | 362 | |
|---|
| | 363 | msg_Dbg(p_aout, "Pulse stream state changed\n"); |
|---|
| | 364 | |
|---|
| | 365 | switch (pa_stream_get_state(s)) { |
|---|
| | 366 | |
|---|
| | 367 | case PA_STREAM_READY: |
|---|
| | 368 | case PA_STREAM_FAILED: |
|---|
| | 369 | case PA_STREAM_TERMINATED: |
|---|
| | 370 | pa_threaded_mainloop_signal(p_sys->mainloop, 0); |
|---|
| | 371 | break; |
|---|
| | 372 | |
|---|
| | 373 | case PA_STREAM_UNCONNECTED: |
|---|
| | 374 | case PA_STREAM_CREATING: |
|---|
| | 375 | break; |
|---|
| | 376 | } |
|---|
| | 377 | } |
|---|
| | 378 | |
|---|
| | 379 | static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { |
|---|
| | 380 | aout_instance_t *p_aout = (aout_instance_t *)userdata; |
|---|
| | 381 | struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; |
|---|
| | 382 | mtime_t next_date; |
|---|
| | 383 | |
|---|
| | 384 | assert(s); |
|---|
| | 385 | assert(p_sys); |
|---|
| | 386 | |
|---|
| | 387 | size_t buffer_size = p_sys->buffer_size; |
|---|
| | 388 | |
|---|
| | 389 | msg_Dbg(p_aout, "Pulse stream request %d\n", length); |
|---|
| | 390 | |
|---|
| | 391 | do{ |
|---|
| | 392 | aout_buffer_t * p_buffer = NULL; |
|---|
| | 393 | if(p_sys->started){ |
|---|
| | 394 | pa_usec_t latency; |
|---|
| | 395 | int negative; |
|---|
| | 396 | if(pa_stream_get_latency(p_sys->stream, &latency, &negative)<0){ |
|---|
| | 397 | if (pa_context_errno(p_sys->context) != PA_ERR_NODATA) { |
|---|
| | 398 | msg_Err(p_aout, "pa_stream_get_latency() failed: %s\n", pa_strerror(pa_context_errno(p_sys->context))); |
|---|
| | 399 | } |
|---|
| | 400 | latency = 0; |
|---|
| | 401 | |
|---|
| | 402 | } |
|---|
| | 403 | msg_Dbg(p_aout, "Pulse stream request latency="I64Fd"\n", latency); |
|---|
| | 404 | next_date = mdate() + latency; |
|---|
| | 405 | |
|---|
| | 406 | |
|---|
| | 407 | if(p_sys->start_date < next_date + AOUT_PTS_TOLERANCE ){ |
|---|
| | 408 | /* |
|---|
| | 409 | vlc_mutex_lock( &p_aout->output_fifo_lock ); |
|---|
| | 410 | p_buffer = aout_FifoPop( p_aout, &p_aout->output.fifo ); |
|---|
| | 411 | vlc_mutex_unlock( &p_aout->output_fifo_lock ); |
|---|
| | 412 | */ |
|---|
| | 413 | p_buffer = aout_OutputNextBuffer( p_aout, next_date, 0); |
|---|
| | 414 | } |
|---|
| | 415 | } |
|---|
| | 416 | |
|---|
| | 417 | if ( p_buffer != NULL ) |
|---|
| | 418 | { |
|---|
| | 419 | msg_Dbg(p_aout, "Pulse stream request write buffer %d\n", p_buffer->i_nb_bytes); |
|---|
| | 420 | pa_stream_write(p_sys->stream, p_buffer->p_buffer, p_buffer->i_nb_bytes, NULL, 0, PA_SEEK_RELATIVE); |
|---|
| | 421 | length -= p_buffer->i_nb_bytes; |
|---|
| | 422 | aout_BufferFree( p_buffer ); |
|---|
| | 423 | } |
|---|
| | 424 | else |
|---|
| | 425 | { |
|---|
| | 426 | msg_Dbg(p_aout, "Pulse stream request write zeroes\n"); |
|---|
| | 427 | void *data = pa_xmalloc(buffer_size); |
|---|
| | 428 | bzero(data, buffer_size); |
|---|
| | 429 | pa_stream_write(p_sys->stream, data, buffer_size, pa_xfree, 0, PA_SEEK_RELATIVE); |
|---|
| | 430 | length -= buffer_size; |
|---|
| | 431 | } |
|---|
| | 432 | }while(length > buffer_size); |
|---|
| | 433 | |
|---|
| | 434 | pa_threaded_mainloop_signal(p_sys->mainloop, 0); |
|---|
| | 435 | } |
|---|
| | 436 | |
|---|
| | 437 | static void stream_latency_update_cb(pa_stream *s, void *userdata) { |
|---|
| | 438 | aout_instance_t *p_aout = (aout_instance_t *)userdata; |
|---|
| | 439 | struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; |
|---|
| | 440 | |
|---|
| | 441 | assert(s); |
|---|
| | 442 | |
|---|
| | 443 | msg_Dbg(p_aout, "Pulse stream latency update\n"); |
|---|
| | 444 | |
|---|
| | 445 | pa_threaded_mainloop_signal(p_sys->mainloop, 0); |
|---|
| | 446 | } |
|---|
| | 447 | |
|---|
| | 448 | static void success_cb(pa_stream *s, int success, void *userdata) { |
|---|
| | 449 | aout_instance_t *p_aout = (aout_instance_t *)userdata; |
|---|
| | 450 | struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; |
|---|
| | 451 | assert(s); |
|---|
| | 452 | |
|---|
| | 453 | pa_threaded_mainloop_signal(p_sys->mainloop, 0); |
|---|
| | 454 | } |
|---|
| | 455 | |