Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
vlc-2-2
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Redmine
Redmine
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
videolan
vlc-2-2
Commits
ed864e7f
Commit
ed864e7f
authored
Apr 02, 2011
by
Rémi Denis-Courmont
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
PulseAudio output: rewrite
This should fix the deadlocks and memory starvation problems.
parent
9d6a0a6a
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
289 additions
and
278 deletions
+289
-278
modules/audio_output/pulse.c
modules/audio_output/pulse.c
+289
-278
No files found.
modules/audio_output/pulse.c
View file @
ed864e7f
...
@@ -21,51 +21,20 @@
...
@@ -21,51 +21,20 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
#ifdef HAVE_CONFIG_H
# include "config.h"
# include "config.h"
#endif
#endif
#include <vlc_common.h>
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_plugin.h>
#include <vlc_aout.h>
#include <vlc_aout.h>
#include <vlc_cpu.h>
#include <vlc_cpu.h>
#include <pulse/pulseaudio.h>
#include <pulse/pulseaudio.h>
/*****************************************************************************
* aout_sys_t: Pulseaudio output method descriptor
*****************************************************************************
* This structure is part of the audio output thread descriptor.
* It describes the specific properties of an audio device.
*****************************************************************************/
struct
aout_sys_t
{
struct
pa_stream
*
stream
;
/**< PulseAudio playback stream object */
struct
pa_context
*
context
;
/**< PulseAudio connection context */
struct
pa_threaded_mainloop
*
mainloop
;
/**< PulseAudio event loop */
size_t
buffer_size
;
mtime_t
start_date
;
};
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static
int
Open
(
vlc_object_t
*
);
static
int
Open
(
vlc_object_t
*
);
static
void
Close
(
vlc_object_t
*
);
static
void
Close
(
vlc_object_t
*
);
static
void
Play
(
aout_instance_t
*
);
static
void
context_state_cb
(
pa_context
*
c
,
void
*
userdata
);
static
void
stream_state_cb
(
pa_stream
*
s
,
void
*
userdata
);
static
void
stream_request_cb
(
pa_stream
*
s
,
size_t
length
,
void
*
userdata
);
static
void
success_cb
(
pa_stream
*
s
,
int
sucess
,
void
*
userdata
);
/*****************************************************************************
* Module descriptor
*****************************************************************************/
vlc_module_begin
()
vlc_module_begin
()
set_shortname
(
"PulseAudio"
)
set_shortname
(
"PulseAudio"
)
set_description
(
N_
(
"Pulseaudio audio output"
)
)
set_description
(
N_
(
"Pulseaudio audio output"
)
)
...
@@ -76,30 +45,221 @@ vlc_module_begin ()
...
@@ -76,30 +45,221 @@ vlc_module_begin ()
set_callbacks
(
Open
,
Close
)
set_callbacks
(
Open
,
Close
)
vlc_module_end
()
vlc_module_end
()
struct
aout_sys_t
{
pa_stream
*
stream
;
/**< PulseAudio playback stream object */
pa_context
*
context
;
/**< PulseAudio connection context */
pa_threaded_mainloop
*
mainloop
;
/**< PulseAudio event loop */
//uint32_t byterate; /**< bytes per second */
};
/* Context helpers */
static
void
context_state_cb
(
pa_context
*
c
,
void
*
userdata
)
{
pa_threaded_mainloop
*
mainloop
=
userdata
;
switch
(
pa_context_get_state
(
c
))
{
case
PA_CONTEXT_READY
:
case
PA_CONTEXT_FAILED
:
case
PA_CONTEXT_TERMINATED
:
pa_threaded_mainloop_signal
(
mainloop
,
0
);
default:
break
;
}
}
static
bool
context_wait
(
pa_threaded_mainloop
*
mainloop
,
pa_context
*
context
)
{
pa_context_state_t
state
;
while
((
state
=
pa_context_get_state
(
context
))
!=
PA_CONTEXT_READY
)
{
if
(
state
==
PA_CONTEXT_FAILED
||
state
==
PA_CONTEXT_TERMINATED
)
return
-
1
;
pa_threaded_mainloop_wait
(
mainloop
);
}
return
0
;
}
static
void
error
(
aout_instance_t
*
aout
,
const
char
*
msg
,
pa_context
*
context
)
{
msg_Err
(
aout
,
"%s: %s"
,
msg
,
pa_strerror
(
pa_context_errno
(
context
)));
}
/* Stream helpers */
static
void
stream_state_cb
(
pa_stream
*
s
,
void
*
userdata
)
{
pa_threaded_mainloop
*
mainloop
=
userdata
;
switch
(
pa_stream_get_state
(
s
))
{
case
PA_STREAM_READY
:
case
PA_STREAM_FAILED
:
case
PA_STREAM_TERMINATED
:
pa_threaded_mainloop_signal
(
mainloop
,
0
);
default:
break
;
}
}
static
void
stream_moved_cb
(
pa_stream
*
s
,
void
*
userdata
)
{
vlc_object_t
*
obj
=
userdata
;
msg_Dbg
(
obj
,
"connected to device %s (%u)"
,
pa_stream_get_device_name
(
s
),
pa_stream_get_device_index
(
s
));
}
static
int
stream_wait
(
pa_threaded_mainloop
*
mainloop
,
pa_stream
*
stream
)
{
pa_stream_state_t
state
;
while
((
state
=
pa_stream_get_state
(
stream
))
!=
PA_STREAM_READY
)
{
if
(
state
==
PA_STREAM_FAILED
||
state
==
PA_STREAM_TERMINATED
)
return
-
1
;
pa_threaded_mainloop_wait
(
mainloop
);
}
return
0
;
}
/* Memory free callback. The block_t address is in front of the data. */
static
void
data_free
(
void
*
data
)
{
block_t
**
pp
=
data
,
*
block
;
memcpy
(
&
block
,
pp
-
1
,
sizeof
(
block
));
block_Release
(
block
);
}
static
void
*
data_convert
(
block_t
**
pp
)
{
block_t
*
block
=
*
pp
;
/* In most cases, there is enough head room, and this is really cheap: */
block
=
block_Realloc
(
block
,
sizeof
(
block
),
block
->
i_buffer
);
*
pp
=
block
;
if
(
unlikely
(
block
==
NULL
))
return
NULL
;
memcpy
(
block
->
p_buffer
,
&
block
,
sizeof
(
block
));
block
->
p_buffer
+=
sizeof
(
block
);
block
->
i_buffer
-=
sizeof
(
block
);
return
block
->
p_buffer
;
}
/*****************************************************************************
* Play: play a sound samples buffer
*****************************************************************************/
static
void
Play
(
aout_instance_t
*
aout
)
{
aout_sys_t
*
sys
=
aout
->
output
.
p_sys
;
pa_stream
*
s
=
sys
->
stream
;
/* Note: The core already holds the output FIFO lock at this point.
* Therefore we must not under any circumstances (try to) acquire the
* output FIFO lock while the PulseAudio threaded main loop lock is held
* (including from PulseAudio stream callbacks). Otherwise lock inversion
* will take place, and sooner or later a deadlock. */
pa_threaded_mainloop_lock
(
sys
->
mainloop
);
if
(
pa_stream_is_corked
(
sys
->
stream
)
>
0
)
{
pa_operation
*
op
=
pa_stream_cork
(
s
,
0
,
NULL
,
NULL
);
if
(
op
!=
NULL
)
pa_operation_unref
(
op
);
msg_Dbg
(
aout
,
"uncorking"
);
}
/* This function should be called by the LibVLC core a header of time,
* but not more than AOUT_MAX_PREPARE. The PulseAudio latency should be
* shorter than that (though it might not be the case with some evil piece
* of audio output hardware). So we need to prepend the buffer with zeroes
* to keep audio and video in sync. */
pa_usec_t
latency
;
int
negative
;
if
(
pa_stream_get_latency
(
s
,
&
latency
,
&
negative
)
<
0
)
{
/* Especially at start of stream, latency may not be known (yet). */
if
(
pa_context_errno
(
sys
->
context
)
!=
PA_ERR_NODATA
)
error
(
aout
,
"cannot determine latency"
,
sys
->
context
);
latency
=
0
;
}
mtime_t
gap
=
aout_FifoFirstDate
(
aout
,
&
aout
->
output
.
fifo
)
-
mdate
()
-
latency
;
if
(
gap
>
AOUT_PTS_TOLERANCE
)
{
#if 0 /* FIXME: we need to take buffer status into account as well... */
size_t len = sys->byterate * gap / CLOCK_FREQ;
void *ptr = calloc(1, len);
msg_Dbg(aout, "buffer too early (%"PRId64" us), stuffing %zu bytes",
gap, len);
if (likely(ptr != NULL))
if (pa_stream_write(sys->stream, ptr, len, free,
0, PA_SEEK_RELATIVE) < 0)
free(ptr);
#endif
msleep
(
gap
);
}
/*else if (latency != 0 && gap < -AOUT_PTS_TOLERANCE)
msg_Err(aout, "buffer too late (%"PRId64" us)", -gap);*/
#if 0 /* Fault injector to test underrun recovery */
static unsigned u = 0;
if ((++u % 500) == 0) {
msg_Err(aout, "fault injection");
msleep(CLOCK_FREQ*2);
}
#endif
/* This function is called exactly once per block in the output FIFO, so
* this for-loop is not necessary.
* If this function is changed to not always dequeue blocks, be sure to
* limit the queue size to a reasonable limit to avoid huge leaks. */
for
(;;)
{
block_t
*
block
=
aout_FifoPop
(
aout
,
&
aout
->
output
.
fifo
);
if
(
block
==
NULL
)
break
;
const
void
*
ptr
=
data_convert
(
&
block
);
if
(
unlikely
(
ptr
==
NULL
))
break
;
size_t
len
=
block
->
i_buffer
;
//mtime_t pts = block->i_pts, duration = block->i_length;
if
(
pa_stream_write
(
s
,
ptr
,
len
,
data_free
,
0
,
PA_SEEK_RELATIVE
)
<
0
)
{
block_Release
(
block
);
msg_Err
(
aout
,
"cannot write: %s"
,
pa_strerror
(
pa_context_errno
(
sys
->
context
)));
}
}
pa_threaded_mainloop_unlock
(
sys
->
mainloop
);
}
/*****************************************************************************
/*****************************************************************************
* Open: open the audio device
* Open: open the audio device
*****************************************************************************/
*****************************************************************************/
static
int
Open
(
vlc_object_t
*
p_this
)
static
int
Open
(
vlc_object_t
*
obj
)
{
{
aout_instance_t
*
p_aout
=
(
aout_instance_t
*
)
p_this
;
aout_instance_t
*
aout
=
(
aout_instance_t
*
)
obj
;
/* Sample format specification */
/* Sample format specification */
struct
pa_sample_spec
ss
;
struct
pa_sample_spec
ss
;
switch
(
p_
aout
->
output
.
output
.
i_format
)
switch
(
aout
->
output
.
output
.
i_format
)
{
{
case
VLC_CODEC_F64B
:
case
VLC_CODEC_F64B
:
p_
aout
->
output
.
output
.
i_format
=
VLC_CODEC_F32B
;
aout
->
output
.
output
.
i_format
=
VLC_CODEC_F32B
;
case
VLC_CODEC_F32B
:
case
VLC_CODEC_F32B
:
ss
.
format
=
PA_SAMPLE_FLOAT32BE
;
ss
.
format
=
PA_SAMPLE_FLOAT32BE
;
break
;
break
;
case
VLC_CODEC_F64L
:
case
VLC_CODEC_F64L
:
p_
aout
->
output
.
output
.
i_format
=
VLC_CODEC_F32L
;
aout
->
output
.
output
.
i_format
=
VLC_CODEC_F32L
;
case
VLC_CODEC_F32L
:
case
VLC_CODEC_F32L
:
ss
.
format
=
PA_SAMPLE_FLOAT32LE
;
ss
.
format
=
PA_SAMPLE_FLOAT32LE
;
break
;
break
;
case
VLC_CODEC_FI32
:
case
VLC_CODEC_FI32
:
p_
aout
->
output
.
output
.
i_format
=
VLC_CODEC_FL32
;
aout
->
output
.
output
.
i_format
=
VLC_CODEC_FL32
;
ss
.
format
=
PA_SAMPLE_FLOAT32NE
;
ss
.
format
=
PA_SAMPLE_FLOAT32NE
;
break
;
break
;
case
VLC_CODEC_S32B
:
case
VLC_CODEC_S32B
:
...
@@ -121,28 +281,28 @@ static int Open ( vlc_object_t *p_this )
...
@@ -121,28 +281,28 @@ static int Open ( vlc_object_t *p_this )
ss
.
format
=
PA_SAMPLE_S16LE
;
ss
.
format
=
PA_SAMPLE_S16LE
;
break
;
break
;
case
VLC_CODEC_S8
:
case
VLC_CODEC_S8
:
p_
aout
->
output
.
output
.
i_format
=
VLC_CODEC_U8
;
aout
->
output
.
output
.
i_format
=
VLC_CODEC_U8
;
case
VLC_CODEC_U8
:
case
VLC_CODEC_U8
:
ss
.
format
=
PA_SAMPLE_U8
;
ss
.
format
=
PA_SAMPLE_U8
;
break
;
break
;
default:
default:
if
(
HAVE_FPU
)
if
(
HAVE_FPU
)
{
{
p_
aout
->
output
.
output
.
i_format
=
VLC_CODEC_FL32
;
aout
->
output
.
output
.
i_format
=
VLC_CODEC_FL32
;
ss
.
format
=
PA_SAMPLE_FLOAT32NE
;
ss
.
format
=
PA_SAMPLE_FLOAT32NE
;
}
}
else
else
{
{
p_
aout
->
output
.
output
.
i_format
=
VLC_CODEC_S16N
;
aout
->
output
.
output
.
i_format
=
VLC_CODEC_S16N
;
ss
.
format
=
PA_SAMPLE_S16NE
;
ss
.
format
=
PA_SAMPLE_S16NE
;
}
}
break
;
break
;
}
}
ss
.
rate
=
p_
aout
->
output
.
output
.
i_rate
;
ss
.
rate
=
aout
->
output
.
output
.
i_rate
;
ss
.
channels
=
aout_FormatNbChannels
(
&
p_
aout
->
output
.
output
);
ss
.
channels
=
aout_FormatNbChannels
(
&
aout
->
output
.
output
);
if
(
!
pa_sample_spec_valid
(
&
ss
))
{
if
(
!
pa_sample_spec_valid
(
&
ss
))
{
msg_Err
(
p_
aout
,
"unsupported sample specification"
);
msg_Err
(
aout
,
"unsupported sample specification"
);
return
VLC_EGENERIC
;
return
VLC_EGENERIC
;
}
}
...
@@ -150,312 +310,163 @@ static int Open ( vlc_object_t *p_this )
...
@@ -150,312 +310,163 @@ static int Open ( vlc_object_t *p_this )
struct
pa_channel_map
map
;
struct
pa_channel_map
map
;
map
.
channels
=
0
;
map
.
channels
=
0
;
if
(
p_
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_CENTER
)
if
(
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_CENTER
)
{
{
if
(
ss
.
channels
==
1
)
if
(
ss
.
channels
==
1
)
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_MONO
;
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_MONO
;
else
else
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_FRONT_CENTER
;
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_FRONT_CENTER
;
}
}
if
(
p_
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_LEFT
)
if
(
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_LEFT
)
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_FRONT_LEFT
;
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_FRONT_LEFT
;
if
(
p_
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_RIGHT
)
if
(
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_RIGHT
)
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_FRONT_RIGHT
;
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_FRONT_RIGHT
;
if
(
p_
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_REARCENTER
)
if
(
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_REARCENTER
)
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_REAR_CENTER
;
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_REAR_CENTER
;
if
(
p_
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_REARLEFT
)
if
(
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_REARLEFT
)
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_REAR_LEFT
;
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_REAR_LEFT
;
if
(
p_
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_REARRIGHT
)
if
(
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_REARRIGHT
)
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_REAR_RIGHT
;
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_REAR_RIGHT
;
if
(
p_
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_MIDDLELEFT
)
if
(
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_MIDDLELEFT
)
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER
;
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER
;
if
(
p_
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_MIDDLERIGHT
)
if
(
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_MIDDLERIGHT
)
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER
;
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER
;
if
(
p_
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_LFE
)
if
(
aout
->
output
.
output
.
i_physical_channels
&
AOUT_CHAN_LFE
)
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_LFE
;
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_LFE
;
for
(
unsigned
i
=
0
;
map
.
channels
<
ss
.
channels
;
i
++
)
{
for
(
unsigned
i
=
0
;
map
.
channels
<
ss
.
channels
;
i
++
)
{
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_AUX0
+
i
;
map
.
map
[
map
.
channels
++
]
=
PA_CHANNEL_POSITION_AUX0
+
i
;
msg_Warn
(
p_
aout
,
"mapping channel %"
PRIu8
" to AUX%u"
,
map
.
channels
,
i
);
msg_Warn
(
aout
,
"mapping channel %"
PRIu8
" to AUX%u"
,
map
.
channels
,
i
);
}
}
if
(
!
pa_channel_map_valid
(
&
map
))
{
if
(
!
pa_channel_map_valid
(
&
map
))
{
msg_Err
(
p_
aout
,
"unsupported channel map"
);
msg_Err
(
aout
,
"unsupported channel map"
);
return
VLC_EGENERIC
;
return
VLC_EGENERIC
;
}
else
{
}
else
{
const
char
*
name
=
pa_channel_map_to_pretty_name
(
&
map
);
const
char
*
name
=
pa_channel_map_to_pretty_name
(
&
map
);
msg_Dbg
(
p_
aout
,
"using %s channel map"
,
(
name
!=
NULL
)
?
name
:
"?"
);
msg_Dbg
(
aout
,
"using %s channel map"
,
(
name
!=
NULL
)
?
name
:
"?"
);
}
}
const
pa_stream_flags_t
flags
=
PA_STREAM_INTERPOLATE_TIMING
const
pa_stream_flags_t
flags
=
PA_STREAM_INTERPOLATE_TIMING
|
PA_STREAM_AUTO_TIMING_UPDATE
|
PA_STREAM_AUTO_TIMING_UPDATE
|
PA_STREAM_ADJUST_LATENCY
;
|
PA_STREAM_ADJUST_LATENCY
|
PA_STREAM_START_CORKED
;
const
uint32_t
byterate
=
pa_bytes_per_second
(
&
ss
);
struct
pa_buffer_attr
attr
;
struct
pa_buffer_attr
attr
;
/* Reduce overall latency to 100mS to reduce audible clicks
/* no point in larger buffers on PA side than VLC */
* Also minreq and internal buffers are now 100ms to reduce resampling.
attr
.
maxlength
=
byterate
*
AOUT_MAX_ADVANCE_TIME
/
CLOCK_FREQ
;
* But it still should not drop samples even with USB sound cards. */
attr
.
tlength
=
byterate
*
AOUT_MAX_PREPARE_TIME
/
CLOCK_FREQ
;
attr
.
tlength
=
pa_bytes_per_second
(
&
ss
)
/
10
;
attr
.
prebuf
=
byterate
*
AOUT_MIN_PREPARE_TIME
/
CLOCK_FREQ
;
attr
.
maxlength
=
attr
.
tlength
*
2
;
attr
.
prebuf
=
-
1
;
attr
.
minreq
=
-
1
;
attr
.
minreq
=
-
1
;
attr
.
fragsize
=
0
;
/* not used for output */
attr
.
fragsize
=
0
;
/* not used for output */
/* Allocate structures */
/* Allocate structures */
struct
aout_sys_t
*
sys
=
malloc
(
sizeof
(
*
sys
));
aout_sys_t
*
sys
=
malloc
(
sizeof
(
*
sys
));
if
(
unlikely
(
sys
==
NULL
))
if
(
unlikely
(
sys
==
NULL
))
return
VLC_ENOMEM
;
return
VLC_ENOMEM
;
sys
->
context
=
NULL
;
sys
->
stream
=
NULL
;
sys
->
stream
=
NULL
;
sys
->
start_date
=
VLC_TS_INVALID
;
//sys->byterate = byterate
;
sys
->
mainloop
=
pa_threaded_mainloop_new
();
/* Allocate threaded main loop */
if
(
unlikely
(
sys
->
mainloop
==
NULL
))
{
pa_threaded_mainloop
*
mainloop
=
pa_threaded_mainloop_new
();
if
(
unlikely
(
mainloop
==
NULL
))
{
free
(
sys
);
free
(
sys
);
return
VLC_ENOMEM
;
return
VLC_ENOMEM
;
}
}
sys
->
mainloop
=
mainloop
;
char
*
ua
=
var_InheritString
(
p_aout
,
"user-agent"
);
if
(
pa_threaded_mainloop_start
(
mainloop
)
<
0
)
{
pa_threaded_mainloop_free
(
mainloop
);
sys
->
context
=
pa_context_new
(
pa_threaded_mainloop_get_api
(
sys
->
mainloop
),
ua
);
free
(
ua
);
if
(
unlikely
(
sys
->
context
==
NULL
))
{
pa_threaded_mainloop_free
(
sys
->
mainloop
);
free
(
sys
);
free
(
sys
);
return
VLC_ENOMEM
;
return
VLC_ENOMEM
;
}
}
pa_threaded_mainloop_lock
(
mainloop
);
p_aout
->
output
.
p_sys
=
sys
;
pa_context_set_state_callback
(
sys
->
context
,
context_state_cb
,
sys
);
/* Connect to PulseAudio server */
if
(
pa_context_connect
(
sys
->
context
,
NULL
,
0
,
NULL
)
<
0
)
{
char
*
user_agent
=
var_InheritString
(
aout
,
"user-agent"
);
connect_fail:
pa_context
*
ctx
=
pa_context_new
(
pa_threaded_mainloop_get_api
(
mainloop
),
msg_Err
(
p_aout
,
"cannot connect to server: %s"
,
user_agent
);
pa_strerror
(
pa_context_errno
(
sys
->
context
)));
free
(
user_agent
);
if
(
unlikely
(
ctx
==
NULL
))
goto
fail
;
goto
fail
;
}
sys
->
context
=
ctx
;
if
(
pa_threaded_mainloop_start
(
sys
->
mainloop
)
<
0
)
{
pa_context_set_state_callback
(
ctx
,
context_state_cb
,
mainloop
);
msg_Err
(
p_aout
,
"cannot start main loop"
);
if
(
pa_context_connect
(
ctx
,
NULL
,
0
,
NULL
)
<
0
||
context_wait
(
mainloop
,
ctx
))
{
error
(
aout
,
"cannot connect to server"
,
ctx
);
goto
fail
;
goto
fail
;
}
}
pa_threaded_mainloop_lock
(
sys
->
mainloop
);
/* Create a playback stream */
/* Wait until the context is ready */
pa_stream
*
s
=
pa_stream_new
(
ctx
,
"audio stream"
,
&
ss
,
&
map
);
while
(
pa_context_get_state
(
sys
->
context
)
!=
PA_CONTEXT_READY
)
{
if
(
s
==
NULL
)
{
if
(
pa_context_get_state
(
sys
->
context
)
==
PA_CONTEXT_FAILED
)
error
(
aout
,
"cannot create stream"
,
ctx
);
goto
connect_fail
;
goto
fail
;
pa_threaded_mainloop_wait
(
sys
->
mainloop
);
}
/* Create a stream */
sys
->
stream
=
pa_stream_new
(
sys
->
context
,
"audio stream"
,
&
ss
,
&
map
);
if
(
sys
->
stream
==
NULL
)
{
msg_Err
(
p_aout
,
"cannot create stream: %s"
,
pa_strerror
(
pa_context_errno
(
sys
->
context
)));
goto
unlock_and_fail
;
}
pa_stream_set_state_callback
(
sys
->
stream
,
stream_state_cb
,
sys
);
pa_stream_set_write_callback
(
sys
->
stream
,
stream_request_cb
,
p_aout
);
if
(
pa_stream_connect_playback
(
sys
->
stream
,
NULL
,
&
attr
,
flags
,
NULL
,
NULL
)
<
0
)
{
stream_fail:
msg_Err
(
p_aout
,
"cannot connect stream: %s"
,
pa_strerror
(
pa_context_errno
(
sys
->
context
)));
goto
unlock_and_fail
;
}
}
sys
->
stream
=
s
;
pa_stream_set_state_callback
(
s
,
stream_state_cb
,
mainloop
);
pa_stream_set_moved_callback
(
s
,
stream_moved_cb
,
aout
);
while
(
pa_stream_get_state
(
sys
->
stream
)
!=
PA_STREAM_READY
)
{
if
(
pa_stream_connect_playback
(
s
,
NULL
,
&
attr
,
flags
,
NULL
,
NULL
)
<
0
if
(
pa_stream_get_state
(
sys
->
stream
)
==
PA_STREAM_FAILED
)
||
stream_wait
(
mainloop
,
s
))
{
goto
stream_fail
;
error
(
aout
,
"cannot connect stream"
,
ctx
)
;
pa_threaded_mainloop_wait
(
sys
->
mainloop
)
;
goto
fail
;
}
}
stream_moved_cb
(
s
,
aout
);
msg_Dbg
(
p_aout
,
"Connected to device %s (%u, %ssuspended)."
,
const
struct
pa_buffer_attr
*
pba
=
pa_stream_get_buffer_attr
(
s
);
pa_stream_get_device_name
(
sys
->
stream
),
msg_Dbg
(
aout
,
"using buffer metrics: maxlength=%u, tlength=%u, "
pa_stream_get_device_index
(
sys
->
stream
),
pa_stream_is_suspended
(
sys
->
stream
)
?
""
:
"not "
);
const
struct
pa_buffer_attr
*
pba
=
pa_stream_get_buffer_attr
(
sys
->
stream
);
p_aout
->
output
.
i_nb_samples
=
pba
->
minreq
/
pa_frame_size
(
&
ss
);
/* Set buffersize from pulseaudio defined minrequest */
sys
->
buffer_size
=
pba
->
minreq
;
msg_Dbg
(
p_aout
,
"using buffer metrics: maxlength=%u, tlength=%u, "
"prebuf=%u, minreq=%u"
,
"prebuf=%u, minreq=%u"
,
pba
->
maxlength
,
pba
->
tlength
,
pba
->
prebuf
,
pba
->
minreq
);
pba
->
maxlength
,
pba
->
tlength
,
pba
->
prebuf
,
pba
->
minreq
);
{
char
sst
[
PA_SAMPLE_SPEC_SNPRINT_MAX
];
msg_Dbg
(
p_aout
,
"using sample specification: %s"
,
pa_sample_spec_snprint
(
sst
,
sizeof
(
sst
),
pa_stream_get_sample_spec
(
sys
->
stream
)));
}
{
char
cmt
[
PA_CHANNEL_MAP_SNPRINT_MAX
];
msg_Dbg
(
p_aout
,
"using channel map: %s"
,
pa_channel_map_snprint
(
cmt
,
sizeof
(
cmt
),
pa_stream_get_channel_map
(
sys
->
stream
)));
}
pa_threaded_mainloop_unlock
(
sys
->
mainloop
);
p_aout
->
output
.
pf_play
=
Play
;
aout
->
output
.
i_nb_samples
=
pba
->
minreq
/
pa_frame_size
(
&
ss
);
aout_VolumeSoftInit
(
p_aout
);
pa_threaded_mainloop_unlock
(
mainloop
);
aout
->
output
.
p_sys
=
sys
;
aout
->
output
.
pf_play
=
Play
;
aout_VolumeSoftInit
(
aout
);
return
VLC_SUCCESS
;
return
VLC_SUCCESS
;
unlock_and_fail:
pa_threaded_mainloop_unlock
(
sys
->
mainloop
);
fail:
fail:
Close
(
p_this
);
pa_threaded_mainloop_unlock
(
mainloop
);
Close
(
obj
);
return
VLC_EGENERIC
;
return
VLC_EGENERIC
;
}
}
/*****************************************************************************
* Play: play a sound samples buffer
*****************************************************************************/
static
void
Play
(
aout_instance_t
*
aout
)
{
struct
aout_sys_t
*
sys
=
aout
->
output
.
p_sys
;
if
(
likely
(
sys
->
start_date
!=
VLC_TS_INVALID
))
return
;
pa_threaded_mainloop_lock
(
sys
->
mainloop
);
sys
->
start_date
=
aout_FifoFirstDate
(
aout
,
&
aout
->
output
.
fifo
);
pa_threaded_mainloop_unlock
(
sys
->
mainloop
);
}
/*****************************************************************************
/*****************************************************************************
* Close: close the audio device
* Close: close the audio device
*****************************************************************************/
*****************************************************************************/
static
void
Close
(
vlc_object_t
*
obj
)
static
void
Close
(
vlc_object_t
*
obj
)
{
{
aout_instance_t
*
aout
=
(
aout_instance_t
*
)
obj
;
aout_instance_t
*
aout
=
(
aout_instance_t
*
)
obj
;
struct
aout_sys_t
*
sys
=
aout
->
output
.
p_sys
;
aout_sys_t
*
sys
=
aout
->
output
.
p_sys
;
pa_threaded_mainloop
*
mainloop
=
sys
->
mainloop
;
if
(
sys
->
stream
)
{
pa_context
*
ctx
=
sys
->
context
;
pa_operation
*
o
;
pa_stream
*
s
=
sys
->
stream
;
pa_threaded_mainloop_lock
(
sys
->
mainloop
);
pa_threaded_mainloop_lock
(
mainloop
);
pa_stream_set_write_callback
(
sys
->
stream
,
NULL
,
NULL
);
if
(
s
!=
NULL
)
{
pa_operation
*
op
;
o
=
pa_stream_flush
(
sys
->
stream
,
success_cb
,
sys
);
if
(
o
!=
NULL
)
{
pa_stream_set_write_callback
(
s
,
NULL
,
NULL
);
while
(
pa_operation_get_state
(
o
)
==
PA_OPERATION_RUNNING
)
op
=
pa_stream_flush
(
s
,
NULL
,
NULL
);
pa_threaded_mainloop_wait
(
sys
->
mainloop
);
if
(
op
!=
NULL
)
pa_operation_unref
(
o
);
pa_operation_unref
(
op
);
}
op
=
pa_stream_drain
(
s
,
NULL
,
NULL
);
if
(
op
!=
NULL
)
o
=
pa_stream_drain
(
sys
->
stream
,
success_cb
,
sys
);
pa_operation_unref
(
op
);
if
(
o
!=
NULL
)
{
pa_stream_disconnect
(
s
);
while
(
pa_operation_get_state
(
o
)
==
PA_OPERATION_RUNNING
)
pa_stream_unref
(
s
);
pa_threaded_mainloop_wait
(
sys
->
mainloop
);
pa_operation_unref
(
o
);
}
pa_threaded_mainloop_unlock
(
sys
->
mainloop
);
}
pa_threaded_mainloop_stop
(
sys
->
mainloop
);
if
(
sys
->
stream
)
{
pa_stream_disconnect
(
sys
->
stream
);
pa_stream_unref
(
sys
->
stream
);
}
}
pa_context_disconnect
(
sys
->
context
);
if
(
ctx
!=
NULL
)
pa_context_unref
(
sys
->
context
);
pa_context_unref
(
ctx
);
pa_threaded_mainloop_free
(
sys
->
mainloop
);
pa_threaded_mainloop_unlock
(
mainloop
);
pa_threaded_mainloop_free
(
mainloop
);
free
(
sys
);
free
(
sys
);
}
}
static
void
context_state_cb
(
pa_context
*
c
,
void
*
userdata
)
{
struct
aout_sys_t
*
sys
=
userdata
;
switch
(
pa_context_get_state
(
c
))
{
case
PA_CONTEXT_READY
:
case
PA_CONTEXT_FAILED
:
pa_threaded_mainloop_signal
(
sys
->
mainloop
,
0
);
default:
break
;
}
}
static
void
stream_state_cb
(
pa_stream
*
s
,
void
*
userdata
)
{
struct
aout_sys_t
*
sys
=
userdata
;
switch
(
pa_stream_get_state
(
s
))
{
case
PA_STREAM_READY
:
case
PA_STREAM_FAILED
:
pa_threaded_mainloop_signal
(
sys
->
mainloop
,
0
);
default:
break
;
}
}
/* Memory free callback. The block_t address is in front of the data. */
static
void
block_free_cb
(
void
*
data
)
{
block_t
**
pp
=
data
,
*
block
;
memcpy
(
&
block
,
pp
-
1
,
sizeof
(
block
));
block_Release
(
block
);
}
static
void
stream_request_cb
(
pa_stream
*
s
,
size_t
length
,
void
*
userdata
)
{
aout_instance_t
*
aout
=
userdata
;
struct
aout_sys_t
*
sys
=
aout
->
output
.
p_sys
;
size_t
buffer_size
=
sys
->
buffer_size
;
do
{
block_t
*
block
=
NULL
;
if
(
sys
->
start_date
!=
VLC_TS_INVALID
)
{
pa_usec_t
latency
;
int
negative
;
if
(
pa_stream_get_latency
(
s
,
&
latency
,
&
negative
)
<
0
){
if
(
pa_context_errno
(
sys
->
context
)
!=
PA_ERR_NODATA
)
{
msg_Err
(
aout
,
"cannot determine latency: %s"
,
pa_strerror
(
pa_context_errno
(
sys
->
context
)));
}
latency
=
0
;
}
//msg_Dbg(p_aout, "latency=%"PRId64, latency);
mtime_t
next_date
=
mdate
()
+
latency
;
if
(
sys
->
start_date
<
next_date
+
AOUT_PTS_TOLERANCE
)
block
=
aout_OutputNextBuffer
(
aout
,
next_date
,
0
);
}
if
(
block
!=
NULL
)
/* PA won't let us pass a reference to the buffer meta data... */
block
=
block_Realloc
(
block
,
sizeof
(
block
),
block
->
i_buffer
);
if
(
block
!=
NULL
)
{
memcpy
(
block
->
p_buffer
,
&
block
,
sizeof
(
block
));
block
->
p_buffer
+=
sizeof
(
block
);
block
->
i_buffer
-=
sizeof
(
block
);
length
-=
block
->
i_buffer
;
pa_stream_write
(
s
,
block
->
p_buffer
,
block
->
i_buffer
,
block_free_cb
,
0
,
PA_SEEK_RELATIVE
);
}
else
{
void
*
data
=
pa_xmalloc
(
length
);
memset
(
data
,
0
,
length
);
pa_stream_write
(
s
,
data
,
length
,
pa_xfree
,
0
,
PA_SEEK_RELATIVE
);
length
=
0
;
}
}
while
(
length
>
buffer_size
);
}
static
void
success_cb
(
pa_stream
*
s
,
int
sucess
,
void
*
userdata
)
{
struct
aout_sys_t
*
sys
=
userdata
;
(
void
)
s
;
(
void
)
sucess
;
pa_threaded_mainloop_signal
(
sys
->
mainloop
,
0
);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment