X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Faudio_output%2Fdec.c;h=5e5eb99dd069b41d693b06c832aa450edb7b6e20;hb=a308b7a17466bb91908a518c93b36dacb49212e7;hp=cdd1844de9e38fdbfcd4317cec48583a241c1557;hpb=a822a430136503c41d65834df8394a7fcbab4079;p=vlc diff --git a/src/audio_output/dec.c b/src/audio_output/dec.c index cdd1844de9..5e5eb99dd0 100644 --- a/src/audio_output/dec.c +++ b/src/audio_output/dec.c @@ -1,24 +1,24 @@ /***************************************************************************** * dec.c : audio output API towards decoders ***************************************************************************** - * Copyright (C) 2002-2007 the VideoLAN team + * Copyright (C) 2002-2007 VLC authors and VideoLAN * $Id$ * * Authors: Christophe Massiot * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ /***************************************************************************** @@ -31,315 +31,393 @@ #include #include - #include #include #include "aout_internal.h" +#include "libvlc.h" -#undef aout_DecNew /** * Creates an audio output */ -aout_input_t *aout_DecNew( aout_instance_t *p_aout, - audio_sample_format_t *p_format, - const audio_replay_gain_t *p_replay_gain, - const aout_request_vout_t *p_request_vout ) +int aout_DecNew( audio_output_t *p_aout, + const audio_sample_format_t *p_format, + const audio_replay_gain_t *p_replay_gain, + const aout_request_vout_t *p_request_vout ) { - aout_input_t * p_input; - /* Sanitize audio format */ - if( p_format->i_channels > 32 ) - { - msg_Err( p_aout, "too many audio channels (%u)", - p_format->i_channels ); - return NULL; - } - if( p_format->i_channels <= 0 ) - { - msg_Err( p_aout, "no audio channels" ); - return NULL; - } if( p_format->i_channels != aout_FormatNbChannels( p_format ) ) { msg_Err( p_aout, "incompatible audio channels count with layout mask" ); - return NULL; + return -1; } - if( p_format->i_rate > 192000 ) + if( p_format->i_rate > 352800 ) { msg_Err( p_aout, "excessive audio sample frequency (%u)", p_format->i_rate ); - return NULL; + return -1; } if( p_format->i_rate < 4000 ) { msg_Err( p_aout, "too low audio sample frequency (%u)", p_format->i_rate ); - return NULL; + return -1; } - /* We can only be called by the decoder, so no need to lock - * p_input->lock. */ - aout_lock_mixer( p_aout ); - assert( p_aout->i_nb_inputs == 0 ); - - p_input = calloc( 1, sizeof(aout_input_t)); - if( !p_input ) - goto error; + aout_owner_t *owner = aout_owner(p_aout); - vlc_mutex_init( &p_input->lock ); + /* TODO: reduce lock scope depending on decoder's real need */ + aout_OutputLock (p_aout); - p_input->b_error = true; - p_input->b_paused = false; - p_input->i_pause_date = 0; + var_Destroy( p_aout, "stereo-mode" ); - aout_FormatPrepare( p_format ); + /* Create the audio output stream */ + owner->volume = aout_volume_New (p_aout, p_replay_gain); - memcpy( &p_input->input, p_format, - sizeof(audio_sample_format_t) ); - if( p_replay_gain ) - p_input->replay_gain = *p_replay_gain; + atomic_store (&owner->restart, 0); + owner->input_format = *p_format; + owner->mixer_format = owner->input_format; + owner->request_vout = *p_request_vout; - aout_lock_input_fifos( p_aout ); - p_aout->pp_inputs[p_aout->i_nb_inputs] = p_input; - p_aout->i_nb_inputs++; + if (aout_OutputNew (p_aout, &owner->mixer_format)) + goto error; + aout_volume_SetFormat (owner->volume, owner->mixer_format.i_format); - if ( !p_aout->p_mixer ) + /* Create the audio filtering "input" pipeline */ + owner->filters = aout_FiltersNew (p_aout, p_format, &owner->mixer_format, + &owner->request_vout); + if (owner->filters == NULL) { - int i; - - var_Destroy( p_aout, "audio-device" ); - var_Destroy( p_aout, "audio-channels" ); + aout_OutputDelete (p_aout); +error: + aout_volume_Delete (owner->volume); + aout_OutputUnlock (p_aout); + return -1; + } - /* Recreate the output using the new format. */ - if ( aout_OutputNew( p_aout, p_format ) < 0 ) - { - for ( i = 0; i < p_aout->i_nb_inputs - 1; i++ ) - { - aout_lock_input( p_aout, p_aout->pp_inputs[i] ); - aout_InputDelete( p_aout, p_aout->pp_inputs[i] ); - aout_unlock_input( p_aout, p_aout->pp_inputs[i] ); - } - aout_unlock_input_fifos( p_aout ); - aout_unlock_mixer( p_aout ); - return p_input; - } + owner->sync.end = VLC_TS_INVALID; + owner->sync.resamp_type = AOUT_RESAMPLING_NONE; + owner->sync.discontinuity = true; + aout_OutputUnlock (p_aout); - /* Create other input streams. */ - for ( i = 0; i < p_aout->i_nb_inputs - 1; i++ ) - { - aout_input_t *p_input = p_aout->pp_inputs[i]; + atomic_init (&owner->buffers_lost, 0); + return 0; +} - aout_lock_input( p_aout, p_input ); - aout_InputDelete( p_aout, p_input ); - aout_InputNew( p_aout, p_input, &p_input->request_vout ); - aout_unlock_input( p_aout, p_input ); - } - } - else - { - aout_MixerDelete( p_aout ); - } +/** + * Stops all plugins involved in the audio output. + */ +void aout_DecDelete (audio_output_t *aout) +{ + aout_owner_t *owner = aout_owner (aout); - if ( aout_MixerNew( p_aout ) == -1 ) + aout_OutputLock (aout); + if (owner->mixer_format.i_format) { - aout_OutputDelete( p_aout ); - aout_unlock_input_fifos( p_aout ); - goto error; + aout_FiltersDelete (aout, owner->filters); + aout_OutputDelete (aout); } - - aout_InputNew( p_aout, p_input, p_request_vout ); - aout_unlock_input_fifos( p_aout ); - - aout_unlock_mixer( p_aout ); - - return p_input; - -error: - aout_unlock_mixer( p_aout ); - return NULL; + aout_volume_Delete (owner->volume); + aout_OutputUnlock (aout); + var_Destroy (aout, "stereo-mode"); } -/***************************************************************************** - * aout_DecDelete : delete a decoder - *****************************************************************************/ -int aout_DecDelete( aout_instance_t * p_aout, aout_input_t * p_input ) +static int aout_CheckReady (audio_output_t *aout) { - int i_input; + aout_owner_t *owner = aout_owner (aout); - /* This function can only be called by the decoder itself, so no need - * to lock p_input->lock. */ - aout_lock_mixer( p_aout ); - - for ( i_input = 0; i_input < p_aout->i_nb_inputs; i_input++ ) + int restart = atomic_exchange (&owner->restart, 0); + if (unlikely(restart)) { - if ( p_aout->pp_inputs[i_input] == p_input ) - { - break; + if (owner->mixer_format.i_format) + aout_FiltersDelete (aout, owner->filters); + + if (restart & AOUT_RESTART_OUTPUT) + { /* Reinitializes the output */ + msg_Dbg (aout, "restarting output..."); + if (owner->mixer_format.i_format) + aout_OutputDelete (aout); + owner->mixer_format = owner->input_format; + if (aout_OutputNew (aout, &owner->mixer_format)) + owner->mixer_format.i_format = 0; + aout_volume_SetFormat (owner->volume, + owner->mixer_format.i_format); } - } - - if ( i_input == p_aout->i_nb_inputs ) - { - msg_Err( p_aout, "cannot find an input to delete" ); - aout_unlock_mixer( p_aout ); - return -1; - } - - /* Remove the input from the list. */ - p_aout->i_nb_inputs--; - assert( p_aout->i_nb_inputs == 0 ); - - aout_InputDelete( p_aout, p_input ); - vlc_mutex_destroy( &p_input->lock ); - free( p_input ); + msg_Dbg (aout, "restarting filters..."); + owner->sync.end = VLC_TS_INVALID; + owner->sync.resamp_type = AOUT_RESAMPLING_NONE; - if ( !p_aout->i_nb_inputs ) - { - aout_OutputDelete( p_aout ); - aout_MixerDelete( p_aout ); - var_Destroy( p_aout, "audio-device" ); - var_Destroy( p_aout, "audio-channels" ); + if (owner->mixer_format.i_format) + { + owner->filters = aout_FiltersNew (aout, &owner->input_format, + &owner->mixer_format, + &owner->request_vout); + if (owner->filters == NULL) + { + aout_OutputDelete (aout); + owner->mixer_format.i_format = 0; + } + } + /* TODO: This would be a good time to call clean up any video output + * left over by an audio visualization: + input_resource_TerminatVout(MAGIC HERE); */ } - - aout_unlock_mixer( p_aout ); - - return 0; + return (owner->mixer_format.i_format) ? 0 : -1; } +/** + * Marks the audio output for restart, to update any parameter of the output + * plug-in (e.g. output device or channel mapping). + */ +void aout_RequestRestart (audio_output_t *aout, unsigned mode) +{ + aout_owner_t *owner = aout_owner (aout); + atomic_fetch_or (&owner->restart, mode); + msg_Dbg (aout, "restart requested (%u)", mode); +} /* * Buffer management */ -/***************************************************************************** - * aout_DecNewBuffer : ask for a new empty buffer - *****************************************************************************/ -aout_buffer_t * aout_DecNewBuffer( aout_input_t * p_input, - size_t i_nb_samples ) +static void aout_StopResampling (audio_output_t *aout) +{ + aout_owner_t *owner = aout_owner (aout); + + owner->sync.resamp_type = AOUT_RESAMPLING_NONE; + aout_FiltersAdjustResampling (owner->filters, 0); +} + +static void aout_DecSilence (audio_output_t *aout, mtime_t length, mtime_t pts) { + aout_owner_t *owner = aout_owner (aout); + const audio_sample_format_t *fmt = &owner->mixer_format; + size_t frames = (fmt->i_rate * length) / CLOCK_FREQ; block_t *block; - size_t length; - aout_lock_input( NULL, p_input ); + if (AOUT_FMT_SPDIF(fmt)) + block = block_Alloc (4 * frames); + else + block = block_Alloc (frames * fmt->i_bytes_per_frame); + if (unlikely(block == NULL)) + return; /* uho! */ + + msg_Dbg (aout, "inserting %zu zeroes", frames); + memset (block->p_buffer, 0, block->i_buffer); + block->i_nb_samples = frames; + block->i_pts = pts; + block->i_dts = pts; + block->i_length = length; + aout_OutputPlay (aout, block); +} - if ( p_input->b_error ) +static void aout_DecSynchronize (audio_output_t *aout, mtime_t dec_pts, + int input_rate) +{ + aout_owner_t *owner = aout_owner (aout); + mtime_t drift; + + /** + * Depending on the drift between the actual and intended playback times, + * the audio core may ignore the drift, trigger upsampling or downsampling, + * insert silence or even discard samples. + * Future VLC versions may instead adjust the input rate. + * + * The audio output plugin is responsible for estimating its actual + * playback time, or rather the estimated time when the next sample will + * be played. (The actual playback time is always the current time, that is + * to say mdate(). It is not an useful statistic.) + * + * Most audio output plugins can estimate the delay until playback of + * the next sample to be written to the buffer, or equally the time until + * all samples in the buffer will have been played. Then: + * pts = mdate() + delay + */ + if (aout_OutputTimeGet (aout, &drift) != 0) + return; /* nothing can be done if timing is unknown */ + drift += mdate () - dec_pts; + + /* Late audio output. + * This can happen due to insufficient caching, scheduling jitter + * or bug in the decoder. Ideally, the output would seek backward. But that + * is not portable, not supported by some hardware and often unsafe/buggy + * where supported. The other alternative is to flush the buffers + * completely. */ + if (drift > (owner->sync.discontinuity ? 0 + : +3 * input_rate * AOUT_MAX_PTS_DELAY / INPUT_RATE_DEFAULT)) { - aout_unlock_input( NULL, p_input ); - return NULL; + if (!owner->sync.discontinuity) + msg_Warn (aout, "playback way too late (%"PRId64"): " + "flushing buffers", drift); + else + msg_Dbg (aout, "playback too late (%"PRId64"): " + "flushing buffers", drift); + aout_OutputFlush (aout, false); + + aout_StopResampling (aout); + owner->sync.end = VLC_TS_INVALID; + owner->sync.discontinuity = true; + + /* Now the output might be too early... Recheck. */ + if (aout_OutputTimeGet (aout, &drift) != 0) + return; /* nothing can be done if timing is unknown */ + drift += mdate () - dec_pts; } - length = i_nb_samples * p_input->input.i_bytes_per_frame - / p_input->input.i_frame_length; - block = block_Alloc( length ); - - aout_unlock_input( NULL, p_input ); + /* Early audio output. + * This is rare except at startup when the buffers are still empty. */ + if (drift < (owner->sync.discontinuity ? 0 + : -3 * input_rate * AOUT_MAX_PTS_ADVANCE / INPUT_RATE_DEFAULT)) + { + if (!owner->sync.discontinuity) + msg_Warn (aout, "playback way too early (%"PRId64"): " + "playing silence", drift); + aout_DecSilence (aout, -drift, dec_pts); + + aout_StopResampling (aout); + owner->sync.discontinuity = true; + drift = 0; + } - if( likely(block != NULL) ) + /* Resampling */ + if (drift > +AOUT_MAX_PTS_DELAY + && owner->sync.resamp_type != AOUT_RESAMPLING_UP) { - block->i_nb_samples = i_nb_samples; - block->i_pts = block->i_length = 0; + msg_Warn (aout, "playback too late (%"PRId64"): up-sampling", + drift); + owner->sync.resamp_type = AOUT_RESAMPLING_UP; + owner->sync.resamp_start_drift = +drift; + } + if (drift < -AOUT_MAX_PTS_ADVANCE + && owner->sync.resamp_type != AOUT_RESAMPLING_DOWN) + { + msg_Warn (aout, "playback too early (%"PRId64"): down-sampling", + drift); + owner->sync.resamp_type = AOUT_RESAMPLING_DOWN; + owner->sync.resamp_start_drift = -drift; } - return block; -} -/***************************************************************************** - * aout_DecDeleteBuffer : destroy an undecoded buffer - *****************************************************************************/ -void aout_DecDeleteBuffer( aout_instance_t * p_aout, aout_input_t * p_input, - aout_buffer_t * p_buffer ) -{ - (void)p_aout; (void)p_input; - aout_BufferFree( p_buffer ); + if (owner->sync.resamp_type == AOUT_RESAMPLING_NONE) + return; /* Everything is fine. Nothing to do. */ + + if (llabs (drift) > 2 * owner->sync.resamp_start_drift) + { /* If the drift is ever increasing, then something is seriously wrong. + * Cease resampling and hope for the best. */ + msg_Warn (aout, "timing screwed (drift: %"PRId64" us): " + "stopping resampling", drift); + aout_StopResampling (aout); + return; + } + + /* Resampling has been triggered earlier. This checks if it needs to be + * increased or decreased. Resampling rate changes must be kept slow for + * the comfort of listeners. */ + int adj = (owner->sync.resamp_type == AOUT_RESAMPLING_UP) ? +2 : -2; + + if (2 * llabs (drift) <= owner->sync.resamp_start_drift) + /* If the drift has been reduced from more than half its initial + * value, then it is time to switch back the resampling direction. */ + adj *= -1; + + if (!aout_FiltersAdjustResampling (owner->filters, adj)) + { /* Everything is back to normal: stop resampling. */ + owner->sync.resamp_type = AOUT_RESAMPLING_NONE; + msg_Dbg (aout, "resampling stopped (drift: %"PRId64" us)", drift); + } } /***************************************************************************** * aout_DecPlay : filter & mix the decoded buffer *****************************************************************************/ -int aout_DecPlay( aout_instance_t * p_aout, aout_input_t * p_input, - aout_buffer_t * p_buffer, int i_input_rate ) +int aout_DecPlay (audio_output_t *aout, block_t *block, int input_rate) { - assert( i_input_rate >= INPUT_RATE_DEFAULT / AOUT_MAX_INPUT_RATE && - i_input_rate <= INPUT_RATE_DEFAULT * AOUT_MAX_INPUT_RATE ); - - assert( p_buffer->i_pts > 0 ); - - p_buffer->i_length = (mtime_t)p_buffer->i_nb_samples * 1000000 - / p_input->input.i_rate; - - aout_lock_mixer( p_aout ); - aout_lock_input( p_aout, p_input ); - - if( p_input->b_error ) - { - aout_unlock_input( p_aout, p_input ); - aout_unlock_mixer( p_aout ); - - aout_BufferFree( p_buffer ); - return -1; + aout_owner_t *owner = aout_owner (aout); + + assert (input_rate >= INPUT_RATE_DEFAULT / AOUT_MAX_INPUT_RATE); + assert (input_rate <= INPUT_RATE_DEFAULT * AOUT_MAX_INPUT_RATE); + assert (block->i_pts >= VLC_TS_0); + + block->i_length = CLOCK_FREQ * block->i_nb_samples + / owner->input_format.i_rate; + + aout_OutputLock (aout); + if (unlikely(aout_CheckReady (aout))) + goto drop; /* Pipeline is unrecoverably broken :-( */ + + const mtime_t now = mdate (), advance = block->i_pts - now; + if (advance < -AOUT_MAX_PTS_DELAY) + { /* Late buffer can be caused by bugs in the decoder, by scheduling + * latency spikes (excessive load, SIGSTOP, etc.) or if buffering is + * insufficient. We assume the PTS is wrong and play the buffer anyway: + * Hopefully video has encountered a similar PTS problem as audio. */ + msg_Warn (aout, "buffer too late (%"PRId64" us): dropped", advance); + goto drop; } + if (advance > AOUT_MAX_ADVANCE_TIME) + { /* Early buffers can only be caused by bugs in the decoder. */ + msg_Err (aout, "buffer too early (%"PRId64" us): dropped", advance); + goto drop; + } + if (block->i_flags & BLOCK_FLAG_DISCONTINUITY) + owner->sync.discontinuity = true; - aout_InputCheckAndRestart( p_aout, p_input ); - aout_unlock_mixer( p_aout ); - - int i_ret = aout_InputPlay( p_aout, p_input, p_buffer, i_input_rate ); - - aout_unlock_input( p_aout, p_input ); + block = aout_FiltersPlay (owner->filters, block, input_rate); + if (block == NULL) + goto lost; - if( i_ret == -1 ) - return -1; + /* Software volume */ + aout_volume_Amplify (owner->volume, block); - /* Run the mixer if it is able to run. */ - aout_lock_mixer( p_aout ); - aout_MixerRun( p_aout, p_aout->mixer_multiplier ); - aout_unlock_mixer( p_aout ); + /* Drift correction */ + aout_DecSynchronize (aout, block->i_pts, input_rate); + /* Output */ + owner->sync.end = block->i_pts + block->i_length + 1; + owner->sync.discontinuity = false; + aout_OutputPlay (aout, block); +out: + aout_OutputUnlock (aout); return 0; +drop: + owner->sync.discontinuity = true; + block_Release (block); +lost: + atomic_fetch_add(&owner->buffers_lost, 1); + goto out; } -int aout_DecGetResetLost( aout_instance_t *p_aout, aout_input_t *p_input ) +int aout_DecGetResetLost (audio_output_t *aout) { - aout_lock_input( p_aout, p_input ); - int i_value = p_input->i_buffer_lost; - p_input->i_buffer_lost = 0; - aout_unlock_input( p_aout, p_input ); - - return i_value; + aout_owner_t *owner = aout_owner (aout); + return atomic_exchange(&owner->buffers_lost, 0); } -void aout_DecChangePause( aout_instance_t *p_aout, aout_input_t *p_input, bool b_paused, mtime_t i_date ) +void aout_DecChangePause (audio_output_t *aout, bool paused, mtime_t date) { - mtime_t i_duration = 0; - aout_lock_input( p_aout, p_input ); - assert( !p_input->b_paused || !b_paused ); - if( p_input->b_paused ) - { - i_duration = i_date - p_input->i_pause_date; - } - p_input->b_paused = b_paused; - p_input->i_pause_date = i_date; - aout_unlock_input( p_aout, p_input ); + aout_owner_t *owner = aout_owner (aout); - if( i_duration != 0 ) + aout_OutputLock (aout); + if (owner->sync.end != VLC_TS_INVALID) { - aout_lock_mixer( p_aout ); - for( aout_buffer_t *p = p_input->mixer.fifo.p_first; p != NULL; p = p->p_next ) - { - p->i_pts += i_duration; - } - aout_unlock_mixer( p_aout ); + if (paused) + owner->sync.end -= date; + else + owner->sync.end += date; } + if (owner->mixer_format.i_format) + aout_OutputPause (aout, paused, date); + aout_OutputUnlock (aout); } -void aout_DecFlush( aout_instance_t *p_aout, aout_input_t *p_input ) +void aout_DecFlush (audio_output_t *aout, bool wait) { - aout_lock_input_fifos( p_aout ); - - aout_FifoSet( p_aout, &p_input->mixer.fifo, 0 ); - p_input->mixer.begin = NULL; + aout_owner_t *owner = aout_owner (aout); - aout_unlock_input_fifos( p_aout ); + aout_OutputLock (aout); + owner->sync.end = VLC_TS_INVALID; + if (owner->mixer_format.i_format) + aout_OutputFlush (aout, wait); + aout_OutputUnlock (aout); } -