VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/pulseaudio.c@ 6738

Last change on this file since 6738 was 6234, checked in by vboxsync, 17 years ago

pulseaudio: don't hang if there is no server available

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.2 KB
Line 
1/** @file
2 *
3 * VBox PulseAudio backend
4 */
5
6/*
7 * Copyright (C) 2006-2007 innotek GmbH
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#define LOG_GROUP LOG_GROUP_DEV_AUDIO
23#include <VBox/log.h>
24#include <iprt/mem.h>
25
26#include <pulse/pulseaudio.h>
27#include "pulse_stubs.h"
28
29#include "../../vl_vbox.h"
30#include "audio.h"
31#define AUDIO_CAP "pulse"
32#include "audio_int.h"
33#include <stdio.h>
34
35/*
36 * We use a g_pMainLoop in a separate thread g_pContext. We have to call functions for
37 * manipulating objects either from callback functions or we have to protect
38 * these functions by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock().
39 */
40static struct pa_threaded_mainloop *g_pMainLoop;
41static struct pa_context *g_pContext;
42
43static void pulse_audio_fini (void *);
44
45typedef struct PulseVoice
46{
47 HWVoiceOut hw;
48 void *pPCMBuf;
49 pa_stream *pStream;
50 int fOpSuccess;
51} PulseVoice;
52
53static struct
54{
55 int buffer_msecs_out;
56 int buffer_msecs_in;
57} conf
58=
59{
60 INIT_FIELD (.buffer_msecs_out = ) 100,
61 INIT_FIELD (.buffer_msecs_in = ) 100,
62};
63
64struct pulse_params_req
65{
66 int freq;
67 pa_sample_format_t pa_format;
68 int nchannels;
69};
70
71struct pulse_params_obt
72{
73 int freq;
74 pa_sample_format_t pa_format;
75 int nchannels;
76 unsigned long buffer_size;
77};
78
79static pa_sample_format_t aud_to_pulsefmt (audfmt_e fmt)
80{
81 switch (fmt)
82 {
83 case AUD_FMT_U8:
84 return PA_SAMPLE_U8;
85
86 case AUD_FMT_S16:
87 return PA_SAMPLE_S16LE;
88
89#ifdef PA_SAMPLE_S32LE
90 case AUD_FMT_S32:
91 return PA_SAMPLE_S32LE;
92#endif
93
94 default:
95 dolog ("Bad audio format %d\n", fmt);
96 return PA_SAMPLE_U8;
97 }
98}
99
100
101static int pulse_to_audfmt (pa_sample_format_t pulsefmt, audfmt_e *fmt, int *endianess)
102{
103 switch (pulsefmt)
104 {
105 case PA_SAMPLE_U8:
106 *endianess = 0;
107 *fmt = AUD_FMT_U8;
108 break;
109
110 case PA_SAMPLE_S16LE:
111 *fmt = AUD_FMT_S16;
112 *endianess = 0;
113 break;
114
115 case PA_SAMPLE_S16BE:
116 *fmt = AUD_FMT_S16;
117 *endianess = 1;
118 break;
119
120#ifdef PA_SAMPLE_S32LE
121 case PA_SAMPLE_S32LE:
122 *fmt = AUD_FMT_S32;
123 *endianess = 0;
124 break;
125#endif
126
127#ifdef PA_SAMPLE_S32BE
128 case PA_SAMPLE_S32BE:
129 *fmt = AUD_FMT_S32;
130 *endianess = 1;
131 break;
132#endif
133
134 default:
135 return -1;
136 }
137 return 0;
138}
139
140static void context_state_callback(pa_context *c, void *userdata)
141{
142 switch (pa_context_get_state(c))
143 {
144 case PA_CONTEXT_READY:
145 case PA_CONTEXT_TERMINATED:
146 case PA_CONTEXT_FAILED:
147 pa_threaded_mainloop_signal(g_pMainLoop, 0);
148 break;
149 default:
150 break;
151 }
152}
153
154static void stream_state_callback(pa_stream *s, void *userdata)
155{
156 switch (pa_stream_get_state(s))
157 {
158 case PA_STREAM_READY:
159 case PA_STREAM_FAILED:
160 case PA_STREAM_TERMINATED:
161 pa_threaded_mainloop_signal(g_pMainLoop, 0);
162 break;
163 default:
164 break;
165 }
166}
167
168static void stream_latency_update_callback(pa_stream *s, void *userdata)
169{
170 pa_threaded_mainloop_signal(g_pMainLoop, 0);
171}
172
173static int pulse_open (int fIn, struct pulse_params_req *req,
174 struct pulse_params_obt *obt, pa_stream **ppStream)
175{
176 pa_sample_spec sspec;
177 pa_channel_map cmap;
178 pa_stream *pStream = NULL;
179 pa_buffer_attr bufAttr;
180 const pa_buffer_attr *pBufAttr;
181 const pa_sample_spec *pSampSpec;
182 char achPCMName[64];
183 pa_stream_flags_t flags;
184 int ms = fIn ? conf.buffer_msecs_in : conf.buffer_msecs_out;
185 const char *stream_name = audio_get_stream_name();
186
187 RTStrPrintf(achPCMName, sizeof(achPCMName), "%.32s%s%s%s",
188 stream_name ? stream_name : "",
189 stream_name ? " (" : "",
190 fIn ? "pcm_in" : "pcm_out",
191 stream_name ? ")" : "");
192 sspec.rate = req->freq;
193 sspec.channels = req->nchannels;
194 sspec.format = req->pa_format;
195
196 LogRel(("Pulse: open %s rate=%dHz channels=%d format=%s\n",
197 fIn ? "PCM_IN" : "PCM_OUT", req->freq, req->nchannels,
198 pa_sample_format_to_string(req->pa_format)));
199
200 if (!pa_sample_spec_valid(&sspec))
201 {
202 LogRel(("Pulse: Unsupported sample specification\n"));
203 goto fail;
204 }
205
206 pa_channel_map_init_auto(&cmap, sspec.channels, PA_CHANNEL_MAP_ALSA);
207
208#if 0
209 pa_cvolume_reset(&volume, sspec.channels);
210#endif
211
212 pa_threaded_mainloop_lock(g_pMainLoop);
213
214 if (!(pStream = pa_stream_new(g_pContext, achPCMName, &sspec, &cmap)))
215 {
216 LogRel(("Pulse: Cannot create stream %s\n", achPCMName));
217 goto unlock_and_fail;
218 }
219
220 pSampSpec = pa_stream_get_sample_spec(pStream);
221 obt->pa_format = pSampSpec->format;
222 obt->nchannels = pSampSpec->channels;
223 obt->freq = pSampSpec->rate;
224
225 pa_stream_set_state_callback(pStream, stream_state_callback, NULL);
226 pa_stream_set_latency_update_callback(pStream, stream_latency_update_callback, NULL);
227
228 memset(&bufAttr, 0, sizeof(bufAttr));
229 bufAttr.tlength = (pa_bytes_per_second(pSampSpec) * ms) / 1000;
230 bufAttr.maxlength = (bufAttr.tlength*3) / 2;
231 bufAttr.minreq = pa_bytes_per_second(pSampSpec) / 100; /* 10ms */
232 bufAttr.prebuf = bufAttr.tlength - bufAttr.minreq;
233 bufAttr.fragsize = pa_bytes_per_second(pSampSpec) / 100; /* 10ms */
234
235 flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
236 if (fIn)
237 {
238 if (pa_stream_connect_record(pStream, /*dev=*/NULL, &bufAttr, flags) < 0)
239 {
240 LogRel(("Pulse: Cannot connect record stream : %s\n",
241 pa_strerror(pa_context_errno(g_pContext))));
242 goto disconnect_unlock_and_fail;
243 }
244 }
245 else
246 {
247 if (pa_stream_connect_playback(pStream, /*dev=*/NULL, &bufAttr, flags,
248 NULL, NULL) < 0)
249 {
250 LogRel(("Pulse: Cannot connect playback stream: %s\n",
251 pa_strerror(pa_context_errno(g_pContext))));
252 goto disconnect_unlock_and_fail;
253 }
254 }
255
256 /* Wait until the stream is ready */
257 for (;;)
258 {
259 pa_stream_state_t sstate;
260 pa_threaded_mainloop_wait(g_pMainLoop);
261 sstate = pa_stream_get_state(pStream);
262 if (sstate == PA_STREAM_READY)
263 break;
264 else if (sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED)
265 {
266 LogRel(("Pulse: Failed to initialize stream (state %d)\n", sstate));
267 goto disconnect_unlock_and_fail;
268 }
269 }
270
271 pBufAttr = pa_stream_get_buffer_attr(pStream);
272 obt->buffer_size = pBufAttr->maxlength;
273
274 pa_threaded_mainloop_unlock(g_pMainLoop);
275
276 LogRel(("Pulse: buffer settings: max=%d tlength=%d prebuf=%d minreq=%d\n",
277 pBufAttr->maxlength, pBufAttr->tlength, pBufAttr->prebuf, pBufAttr->minreq));
278
279 *ppStream = pStream;
280 return 0;
281
282disconnect_unlock_and_fail:
283 pa_stream_disconnect(pStream);
284
285unlock_and_fail:
286 pa_threaded_mainloop_unlock(g_pMainLoop);
287
288fail:
289 if (pStream)
290 pa_stream_unref(pStream);
291
292 *ppStream = NULL;
293 return -1;
294}
295
296static int pulse_init_out (HWVoiceOut *hw, audsettings_t *as)
297{
298 PulseVoice *pulse = (PulseVoice *) hw;
299 struct pulse_params_req req;
300 struct pulse_params_obt obt;
301 audfmt_e effective_fmt;
302 int endianness;
303 audsettings_t obt_as;
304
305 req.pa_format = aud_to_pulsefmt (as->fmt);
306 req.freq = as->freq;
307 req.nchannels = as->nchannels;
308
309 if (pulse_open (/*fIn=*/0, &req, &obt, &pulse->pStream))
310 return -1;
311
312 if (pulse_to_audfmt (obt.pa_format, &effective_fmt, &endianness))
313 {
314 LogRel(("Pulse: Cannot find audio format %d\n", obt.pa_format));
315 return -1;
316 }
317
318 obt_as.freq = obt.freq;
319 obt_as.nchannels = obt.nchannels;
320 obt_as.fmt = effective_fmt;
321 obt_as.endianness = endianness;
322
323 audio_pcm_init_info (&hw->info, &obt_as);
324 hw->samples = obt.buffer_size >> hw->info.shift;
325
326 pulse->pPCMBuf = RTMemAllocZ(obt.buffer_size);
327 if (!pulse->pPCMBuf)
328 {
329 LogRel(("Pulse: Could not allocate DAC buffer of %d bytes\n", obt.buffer_size));
330 return -1;
331 }
332
333 return 0;
334}
335
336static void pulse_fini_out (HWVoiceOut *hw)
337{
338 PulseVoice *pulse = (PulseVoice *)hw;
339 if (pulse->pStream)
340 {
341 pa_stream_disconnect(pulse->pStream);
342 pa_stream_unref(pulse->pStream);
343 pulse->pStream = NULL;
344 }
345 if (pulse->pPCMBuf)
346 {
347 RTMemFree (pulse->pPCMBuf);
348 pulse->pPCMBuf = NULL;
349 }
350}
351
352static int pulse_run_out (HWVoiceOut *hw)
353{
354 PulseVoice *pulse = (PulseVoice *) hw;
355 int csLive, csDecr, csSamples, csToWrite, csAvail;
356 size_t cbAvail, cbToWrite;
357 uint8_t *pu8Dst;
358 st_sample_t *psSrc;
359
360 csLive = audio_pcm_hw_get_live_out (hw);
361 if (!csLive)
362 return 0;
363
364 pa_threaded_mainloop_lock(g_pMainLoop);
365
366 cbAvail = pa_stream_writable_size (pulse->pStream);
367 if (cbAvail == -1)
368 {
369 LogRel(("Pulse: Failed to determine the writable size: %s\n",
370 pa_strerror(pa_context_errno(g_pContext))));
371 return 0;
372 }
373
374 csAvail = cbAvail >> hw->info.shift; /* bytes => samples */
375 csDecr = audio_MIN (csLive, csAvail);
376 csSamples = csDecr;
377
378 while (csSamples)
379 {
380 /* split request at the end of our samples buffer */
381 csToWrite = audio_MIN (csSamples, hw->samples - hw->rpos);
382 cbToWrite = csToWrite << hw->info.shift;
383 psSrc = hw->mix_buf + hw->rpos;
384 pu8Dst = advance (pulse->pPCMBuf, hw->rpos << hw->info.shift);
385
386 hw->clip (pu8Dst, psSrc, csToWrite);
387
388 if (pa_stream_write (pulse->pStream, pu8Dst, cbToWrite,
389 /*cleanup_callback=*/NULL, 0, PA_SEEK_RELATIVE) < 0)
390 {
391 LogRel(("Pulse: Failed to write %d samples: %s\n",
392 csToWrite, pa_strerror(pa_context_errno(g_pContext))));
393 break;
394 }
395 hw->rpos = (hw->rpos + csToWrite) % hw->samples;
396 csSamples -= csToWrite;
397 }
398
399 pa_threaded_mainloop_unlock(g_pMainLoop);
400
401 return csDecr;
402}
403
404static int pulse_write (SWVoiceOut *sw, void *buf, int len)
405{
406 return audio_pcm_sw_write (sw, buf, len);
407}
408
409static void stream_success_callback(pa_stream *pStream, int success, void *userdata)
410{
411 PulseVoice *pulse = (PulseVoice *) userdata;
412 pulse->fOpSuccess = success;
413 pa_threaded_mainloop_signal(g_pMainLoop, 0);
414}
415
416typedef enum
417{
418 Unpause = 0,
419 Pause = 1,
420 Flush = 2,
421 Trigger = 3
422} pulse_cmd_t;
423
424static int pulse_ctrl (HWVoiceOut *hw, pulse_cmd_t cmd)
425{
426 PulseVoice *pulse = (PulseVoice *) hw;
427 pa_operation *op = NULL;
428
429 if (!pulse->pStream)
430 return 0;
431
432 pa_threaded_mainloop_lock(g_pMainLoop);
433 switch (cmd)
434 {
435 case Pause:
436 op = pa_stream_cork(pulse->pStream, 1, stream_success_callback, pulse);
437 break;
438 case Unpause:
439 op = pa_stream_cork(pulse->pStream, 0, stream_success_callback, pulse);
440 break;
441 case Flush:
442 op = pa_stream_flush(pulse->pStream, stream_success_callback, pulse);
443 break;
444 case Trigger:
445 op = pa_stream_trigger(pulse->pStream, stream_success_callback, pulse);
446 break;
447 default:
448 goto fail;
449 }
450 if (!op)
451 LogRel(("Pulse: Failed ctrl cmd=%d to stream: %s\n",
452 cmd, pa_strerror(pa_context_errno(g_pContext))));
453 else
454 pa_operation_unref(op);
455
456fail:
457 pa_threaded_mainloop_unlock(g_pMainLoop);
458 return 0;
459}
460
461static int pulse_ctl_out (HWVoiceOut *hw, int cmd, ...)
462{
463 switch (cmd)
464 {
465 case VOICE_ENABLE:
466 pulse_ctrl(hw, Unpause);
467 pulse_ctrl(hw, Trigger);
468 break;
469 case VOICE_DISABLE:
470 pulse_ctrl(hw, Flush);
471 break;
472 default:
473 return -1;
474 }
475 return 0;
476}
477
478static int pulse_init_in (HWVoiceIn *hw, audsettings_t *as)
479{
480 PulseVoice *pulse = (PulseVoice *) hw;
481 struct pulse_params_req req;
482 struct pulse_params_obt obt;
483 audfmt_e effective_fmt;
484 int endianness;
485 audsettings_t obt_as;
486
487 req.pa_format = aud_to_pulsefmt (as->fmt);
488 req.freq = as->freq;
489 req.nchannels = as->nchannels;
490
491 if (pulse_open (/*fIn=*/1, &req, &obt, &pulse->pStream))
492 return -1;
493
494 if (pulse_to_audfmt (obt.pa_format, &effective_fmt, &endianness))
495 {
496 LogRel(("Pulse: Cannot find audio format %d\n", obt.pa_format));
497 return -1;
498 }
499
500 obt_as.freq = obt.freq;
501 obt_as.nchannels = obt.nchannels;
502 obt_as.fmt = effective_fmt;
503 obt_as.endianness = endianness;
504
505 audio_pcm_init_info (&hw->info, &obt_as);
506
507 /* pcm_in: reserve twice as the maximum buffer length because of peek()/drop(). */
508 hw->samples = 2 * (obt.buffer_size >> hw->info.shift);
509
510 /* no buffer for input */
511 pulse->pPCMBuf = NULL;
512
513 return 0;
514}
515
516static void pulse_fini_in (HWVoiceIn *hw)
517{
518 PulseVoice *pulse = (PulseVoice *)hw;
519 if (pulse->pStream)
520 {
521 pa_stream_disconnect(pulse->pStream);
522 pa_stream_unref(pulse->pStream);
523 pulse->pStream = NULL;
524 }
525 if (pulse->pPCMBuf)
526 {
527 RTMemFree (pulse->pPCMBuf);
528 pulse->pPCMBuf = NULL;
529 }
530}
531
532static int pulse_run_in (HWVoiceIn *hw)
533{
534 PulseVoice *pulse = (PulseVoice *) hw;
535 int csDead, csDecr = 0, csSamples, csRead, csAvail;
536 size_t cbAvail;
537 const void *pu8Src;
538 st_sample_t *psDst;
539
540 csDead = hw->samples - audio_pcm_hw_get_live_in (hw);
541
542 if (!csDead)
543 return 0; /* no buffer available */
544
545 pa_threaded_mainloop_lock(g_pMainLoop);
546
547 if (pa_stream_peek(pulse->pStream, &pu8Src, &cbAvail) < 0)
548 {
549 LogRel(("Pulse: Peek failed: %s\n",
550 pa_strerror(pa_context_errno(g_pContext))));
551 goto exit;
552 }
553 if (!pu8Src)
554 goto exit;
555
556 csAvail = cbAvail >> hw->info.shift;
557 csDecr = audio_MIN (csDead, csAvail);
558
559 csSamples = csDecr;
560
561 while (csSamples)
562 {
563 /* split request at the end of our samples buffer */
564 psDst = hw->conv_buf + hw->wpos;
565 csRead = audio_MIN (csSamples, hw->samples - hw->wpos);
566 hw->conv (psDst, pu8Src, csRead, &nominal_volume);
567 hw->wpos = (hw->wpos + csRead) % hw->samples;
568 csSamples -= csRead;
569 pu8Src = (const void*)((uint8_t*)pu8Src + (csRead << hw->info.shift));
570 }
571
572 pa_stream_drop(pulse->pStream);
573
574exit:
575 pa_threaded_mainloop_unlock(g_pMainLoop);
576
577 return csDecr;
578}
579
580static int pulse_read (SWVoiceIn *sw, void *buf, int size)
581{
582 return audio_pcm_sw_read (sw, buf, size);
583}
584
585static int pulse_ctl_in (HWVoiceIn *hw, int cmd, ...)
586{
587 return 0;
588}
589
590static void *pulse_audio_init (void)
591{
592 int rc;
593
594 rc = audioLoadPulseLib();
595 if (RT_FAILURE(rc))
596 {
597 LogRel(("Pulse: Failed to load the PulseAudio shared library! Error %Rrc\n", rc));
598 return NULL;
599 }
600 if (!(g_pMainLoop = pa_threaded_mainloop_new()))
601 {
602 LogRel(("Pulse: Failed to allocate main loop: %s\n",
603 pa_strerror(pa_context_errno(g_pContext))));
604 goto fail;
605 }
606 if (!(g_pContext = pa_context_new(pa_threaded_mainloop_get_api(g_pMainLoop), "VBox")))
607 {
608 LogRel(("Pulse: Failed to allocate context: %s\n",
609 pa_strerror(pa_context_errno(g_pContext))));
610 goto fail;
611 }
612 if (pa_threaded_mainloop_start(g_pMainLoop) < 0)
613 {
614 LogRel(("Pulse: Failed to start threaded mainloop: %s\n",
615 pa_strerror(pa_context_errno(g_pContext))));
616 goto fail;
617 }
618
619 pa_context_set_state_callback(g_pContext, context_state_callback, NULL);
620 pa_threaded_mainloop_lock(g_pMainLoop);
621
622 if (pa_context_connect(g_pContext, /*server=*/NULL, 0, NULL) < 0)
623 {
624 LogRel(("Pulse: Failed to connect to server: %s\n",
625 pa_strerror(pa_context_errno(g_pContext))));
626 goto unlock_and_fail;
627 }
628
629 /* Wait until the g_pContext is ready */
630 for (;;)
631 {
632 pa_context_state_t cstate;
633 pa_threaded_mainloop_wait(g_pMainLoop);
634 cstate = pa_context_get_state(g_pContext);
635 if (cstate == PA_CONTEXT_READY)
636 break;
637 else if (cstate == PA_CONTEXT_TERMINATED || cstate == PA_CONTEXT_FAILED)
638 {
639 LogRel(("Pulse: Failed to initialize context (state %d)\n", cstate));
640 goto unlock_and_fail;
641 }
642 }
643 pa_threaded_mainloop_unlock(g_pMainLoop);
644
645 return &conf;
646
647unlock_and_fail:
648 if (g_pMainLoop)
649 pa_threaded_mainloop_unlock(g_pMainLoop);
650
651fail:
652 if (g_pMainLoop)
653 pa_threaded_mainloop_stop(g_pMainLoop);
654
655 if (g_pContext)
656 {
657 pa_context_disconnect(g_pContext);
658 pa_context_unref(g_pContext);
659 g_pContext = NULL;
660 }
661 if (g_pMainLoop)
662 {
663 pa_threaded_mainloop_free(g_pMainLoop);
664 g_pMainLoop = NULL;
665 }
666 return NULL;
667}
668
669static void pulse_audio_fini (void *opaque)
670{
671 if (g_pMainLoop)
672 pa_threaded_mainloop_stop(g_pMainLoop);
673 if (g_pContext)
674 {
675 pa_context_disconnect(g_pContext);
676 pa_context_unref(g_pContext);
677 g_pContext = NULL;
678 }
679 if (g_pMainLoop)
680 {
681 pa_threaded_mainloop_free(g_pMainLoop);
682 g_pMainLoop = NULL;
683 }
684 (void) opaque;
685}
686
687static struct audio_option pulse_options[] =
688{
689 {"DAC_MS", AUD_OPT_INT, &conf.buffer_msecs_out,
690 "DAC period size in milliseconds", NULL, 0},
691 {"ADC_MS", AUD_OPT_INT, &conf.buffer_msecs_in,
692 "ADC period size in milliseconds", NULL, 0},
693 {NULL, 0, NULL, NULL, NULL, 0}
694};
695
696static struct audio_pcm_ops pulse_pcm_ops =
697{
698 pulse_init_out,
699 pulse_fini_out,
700 pulse_run_out,
701 pulse_write,
702 pulse_ctl_out,
703
704 pulse_init_in,
705 pulse_fini_in,
706 pulse_run_in,
707 pulse_read,
708 pulse_ctl_in
709};
710
711struct audio_driver pulse_audio_driver =
712{
713 INIT_FIELD (name = ) "pulse",
714 INIT_FIELD (descr = ) "PulseAudio http://www.pulseaudio.org",
715 INIT_FIELD (options = ) pulse_options,
716 INIT_FIELD (init = ) pulse_audio_init,
717 INIT_FIELD (fini = ) pulse_audio_fini,
718 INIT_FIELD (pcm_ops = ) &pulse_pcm_ops,
719 INIT_FIELD (can_be_default = ) 1,
720 INIT_FIELD (max_voices_out = ) INT_MAX,
721 INIT_FIELD (max_voices_in = ) INT_MAX,
722 INIT_FIELD (voice_size_out = ) sizeof (PulseVoice),
723 INIT_FIELD (voice_size_in = ) sizeof (PulseVoice)
724};
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette