From 2f9b14916fc2b4f3e505f0512c16d3a6f0fa30fe Mon Sep 17 00:00:00 2001 From: wm4 Date: Mon, 12 Mar 2012 01:14:47 +0100 Subject: ao_dsound: don't repeat parts of the audio buffer when playback ends When layback of a file ends, the audio output doesn't receive new audio data, but the rest of the data must be played properly. ao_dsound.c doesn't handle this properly: DirectSound will continue to play the ringbuffer, even if mplayer doesn't write any data. There's no explicit way to prevent such a buffer underrun. Try to detect it and stop playback. --- libao2/ao_dsound.c | 58 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/libao2/ao_dsound.c b/libao2/ao_dsound.c index b33f2949fd..d21e39df9e 100644 --- a/libao2/ao_dsound.c +++ b/libao2/ao_dsound.c @@ -121,6 +121,7 @@ static int min_free_space = 0; ///if the free space is below this val ///there will always be at least this amout of free space to prevent ///get_space() from returning wrong values when buffer is 100% full. ///will be replaced with nBlockAlign in init() +static int underrun_check = 0; ///0 or last reported free space (underrun detection) static int device_num = 0; ///wanted device number static GUID device; ///guid of the device @@ -314,6 +315,8 @@ static int write_buffer(unsigned char *data, int len) LPVOID lpvPtr2; DWORD dwBytes2; + underrun_check = 0; + // Lock the buffer res = IDirectSoundBuffer_Lock(hdsbuf,write_offset, len, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0); // If the buffer was lost, restore and retry lock. @@ -544,6 +547,7 @@ static void reset(void) // reset directsound buffer IDirectSoundBuffer_SetCurrentPosition(hdsbuf, 0); write_offset=0; + underrun_check=0; } /** @@ -568,22 +572,16 @@ static void audio_resume(void) */ static void uninit(int immed) { - if(immed)reset(); - else{ - DWORD status; - IDirectSoundBuffer_Play(hdsbuf, 0, 0, 0); - while(!IDirectSoundBuffer_GetStatus(hdsbuf,&status) && (status&DSBSTATUS_PLAYING)) - usec_sleep(20000); - } + if (!immed) + usec_sleep(get_delay() * 1000000); + reset(); + DestroyBuffer(); UninitDirectSound(); } -/** -\brief find out how many bytes can be written into the audio buffer without -\return free space in bytes, has to return 0 if the buffer is almost full -*/ -static int get_space(void) +// return exact number of free (safe to write) bytes +static int check_free_buffer_size(void) { int space; DWORD play_offset; @@ -595,6 +593,28 @@ static int get_space(void) // write_cursor is the position after which it is assumed to be save to write data // write_offset is the postion where we actually write the data to if(space > buffer_size)space -= buffer_size; // write_offset < play_offset + // Check for buffer underruns. An underrun happens if DirectSound + // started to play old data beyond the current write_offset. Detect this + // by checking whether the free space shrinks, even though no data was + // written (i.e. no write_buffer). Doesn't always work, but the only + // reason we need this is to deal with the situation when playback ends, + // and the buffer is only half-filled. + if (space < underrun_check) { + // there's no useful data in the buffers + space = buffer_size; + reset(); + } + underrun_check = space; + return space; +} + +/** +\brief find out how many bytes can be written into the audio buffer without +\return free space in bytes, has to return 0 if the buffer is almost full +*/ +static int get_space(void) +{ + int space = check_free_buffer_size(); if(space < min_free_space)return 0; return space-min_free_space; } @@ -608,13 +628,7 @@ static int get_space(void) */ static int play(void* data, int len, int flags) { - DWORD play_offset; - int space; - - // make sure we have enough space to write data - IDirectSoundBuffer_GetCurrentPosition(hdsbuf,&play_offset,NULL); - space=buffer_size-(write_offset-play_offset); - if(space > buffer_size)space -= buffer_size; // write_offset < play_offset + int space = check_free_buffer_size(); if(space < len) len = space; if (!(flags & AOPLAY_FINAL_CHUNK)) @@ -628,10 +642,6 @@ static int play(void* data, int len, int flags) */ static float get_delay(void) { - DWORD play_offset; - int space; - IDirectSoundBuffer_GetCurrentPosition(hdsbuf,&play_offset,NULL); - space=play_offset-write_offset; - if(space <= 0)space += buffer_size; + int space = check_free_buffer_size(); return (float)(buffer_size - space) / (float)ao_data.bps; } -- cgit v1.2.3