2010-02-05 16:13:11 +01:00
// Copyright (c) 2008, 2010, Niels Martin Hansen
2008-11-27 19:35:26 +01:00
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
2009-07-29 07:43:02 +02:00
// Aegisub Project http://www.aegisub.org/
/// @file audio_player_dsound2.cpp
/// @brief New DirectSound-based audio output
/// @ingroup audio_output
///
2008-11-27 19:35:26 +01:00
# ifdef WITH_DIRECTSOUND
2014-03-24 15:54:22 +01:00
# include "include/aegisub/audio_player.h"
2011-11-16 20:55:31 +01:00
2012-01-08 02:33:39 +01:00
# include "audio_controller.h"
2010-12-08 04:36:10 +01:00
# include "frame_main.h"
2013-01-07 02:50:09 +01:00
# include "options.h"
2009-09-10 15:06:40 +02:00
# include "utils.h"
2008-11-27 19:35:26 +01:00
2014-07-09 16:22:49 +02:00
# include <libaegisub/audio/provider.h>
2013-06-08 06:19:40 +02:00
# include <libaegisub/scoped_ptr.h>
# include <libaegisub/log.h>
2014-04-23 22:53:24 +02:00
# include <libaegisub/make_unique.h>
2013-06-08 06:19:40 +02:00
# include <mmsystem.h>
# include <process.h>
# include <dsound.h>
2014-03-24 15:54:22 +01:00
namespace {
class DirectSoundPlayer2Thread ;
/// @class DirectSoundPlayer2
/// @brief New implementation of DirectSound-based audio player
///
/// The core design idea is to have a playback thread that owns the DirectSound COM objects
/// and performs all playback operations, and use the player object as a proxy to
/// send commands to the playback thread.
class DirectSoundPlayer2 final : public AudioPlayer {
/// The playback thread
std : : unique_ptr < DirectSoundPlayer2Thread > thread ;
/// Desired length in milliseconds to write ahead of the playback cursor
int WantedLatency ;
/// Multiplier for WantedLatency to get total buffer length
int BufferLength ;
/// @brief Tell whether playback thread is alive
/// @return True if there is a playback thread and it's ready
bool IsThreadAlive ( ) ;
public :
/// @brief Constructor
2014-07-09 16:22:49 +02:00
DirectSoundPlayer2 ( agi : : AudioProvider * provider , wxWindow * parent ) ;
2014-03-24 15:54:22 +01:00
/// @brief Destructor
~ DirectSoundPlayer2 ( ) ;
/// @brief Start playback
/// @param start First audio frame to play
/// @param count Number of audio frames to play
void Play ( int64_t start , int64_t count ) ;
/// @brief Stop audio playback
/// @param timerToo Whether to also stop the playback update timer
void Stop ( ) ;
/// @brief Tell whether playback is active
/// @return True if audio is playing back
bool IsPlaying ( ) ;
/// @brief Get playback end position
/// @return Audio frame index
///
/// Returns 0 if playback is stopped or there is no playback thread
int64_t GetEndPosition ( ) ;
/// @brief Get approximate playback position
/// @return Index of audio frame user is currently hearing
///
/// Returns 0 if playback is stopped or there is no playback thread
int64_t GetCurrentPosition ( ) ;
/// @brief Change playback end position
/// @param pos New end position
void SetEndPosition ( int64_t pos ) ;
/// @brief Change playback volume
/// @param vol Amplification factor
void SetVolume ( double vol ) ;
} ;
2010-02-05 15:51:12 +01:00
/// @brief RAII support class to init and de-init the COM library
2008-11-27 19:35:26 +01:00
struct COMInitialization {
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
2010-02-05 15:51:12 +01:00
/// Flag set if an inited COM library is managed
2014-04-25 19:01:07 +02:00
bool inited = false ;
2008-11-27 19:35:26 +01:00
2010-02-05 15:51:12 +01:00
/// @brief Destructor, de-inits COM if it is inited
2008-11-27 19:35:26 +01:00
~ COMInitialization ( )
{
if ( inited ) CoUninitialize ( ) ;
}
2010-02-05 15:51:12 +01:00
/// @brief Initialise the COM library as single-threaded apartment if isn't already inited by us
2014-12-12 17:19:10 +01:00
bool Init ( )
2008-11-27 19:35:26 +01:00
{
if ( ! inited )
{
2014-12-12 17:19:10 +01:00
if ( SUCCEEDED ( CoInitialize ( nullptr ) ) )
inited = true ;
2008-11-27 19:35:26 +01:00
}
2014-12-12 17:19:10 +01:00
return inited ;
2008-11-27 19:35:26 +01:00
}
} ;
2014-03-25 17:51:38 +01:00
struct ReleaseCOMObject {
void operator ( ) ( IUnknown * obj ) {
2008-11-27 19:35:26 +01:00
if ( obj ) obj - > Release ( ) ;
}
} ;
2014-03-25 17:51:38 +01:00
template < typename T >
using COMObjectRetainer = std : : unique_ptr < T , ReleaseCOMObject > ;
2010-02-05 16:13:11 +01:00
/// @brief RAII wrapper around Win32 HANDLE type
2014-03-13 02:39:07 +01:00
struct Win32KernelHandle final : public agi : : scoped_holder < HANDLE , BOOL ( __stdcall * ) ( HANDLE ) > {
2010-02-05 16:13:11 +01:00
/// @brief Create with a managed handle
/// @param handle Win32 handle to manage
Win32KernelHandle ( HANDLE handle = 0 )
2012-02-20 19:22:12 +01:00
: scoped_holder ( handle , CloseHandle )
2010-02-05 16:13:11 +01:00
{
}
2012-02-20 19:22:12 +01:00
Win32KernelHandle & operator = ( HANDLE new_handle )
2010-02-05 16:13:11 +01:00
{
2012-02-20 19:22:12 +01:00
scoped_holder : : operator = ( new_handle ) ;
return * this ;
2010-02-05 16:13:11 +01:00
}
} ;
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
/// @class DirectSoundPlayer2Thread
2010-02-05 15:51:12 +01:00
/// @brief Playback thread class for DirectSoundPlayer2
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
///
2010-02-05 15:51:12 +01:00
/// Not based on wxThread, but uses Win32 threads directly
2008-11-27 19:35:26 +01:00
class DirectSoundPlayer2Thread {
2011-11-16 20:54:55 +01:00
/// @brief Win32 thread entry point
/// @param parameter Pointer to our thread object
/// @return Thread return value, always 0 here
2008-11-27 19:35:26 +01:00
static unsigned int __stdcall ThreadProc ( void * parameter ) ;
2011-11-16 20:54:55 +01:00
/// @brief Thread entry point
2008-11-27 19:35:26 +01:00
void Run ( ) ;
2011-11-16 20:54:55 +01:00
/// @brief Fill audio data into a locked buffer-pair and unlock the buffers
/// @param buf1 First buffer in pair
/// @param buf1sz Byte-size of first buffer in pair
/// @param buf2 Second buffer in pair, or null
/// @param buf2sz Byte-size of second buffer in pair
/// @param input_frame First audio frame to fill into buffers
/// @param bfr DirectSound buffer object owning the buffer pair
/// @return Number of bytes written
2008-11-27 19:35:26 +01:00
DWORD FillAndUnlockBuffers ( void * buf1 , DWORD buf1sz , void * buf2 , DWORD buf2sz , int64_t & input_frame , IDirectSoundBuffer8 * bfr ) ;
2011-11-16 20:54:55 +01:00
/// @brief Check for error state and throw exception if one occurred
2008-11-27 19:35:26 +01:00
void CheckError ( ) ;
2014-03-25 17:51:38 +01:00
HWND parent ;
2010-02-05 15:51:12 +01:00
/// Win32 handle to the thread
2010-02-05 16:13:11 +01:00
Win32KernelHandle thread_handle ;
2008-11-27 19:35:26 +01:00
2010-02-05 15:51:12 +01:00
/// Event object, world to thread, set to start playback
2010-02-05 16:13:11 +01:00
Win32KernelHandle event_start_playback ;
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
2010-02-05 15:51:12 +01:00
/// Event object, world to thread, set to stop playback
2010-02-05 16:13:11 +01:00
Win32KernelHandle event_stop_playback ;
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
2010-02-05 15:51:12 +01:00
/// Event object, world to thread, set if playback end time was updated
2010-02-05 16:13:11 +01:00
Win32KernelHandle event_update_end_time ;
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
2010-02-05 15:51:12 +01:00
/// Event object, world to thread, set if the volume was changed
2010-02-05 16:13:11 +01:00
Win32KernelHandle event_set_volume ;
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
2010-02-05 15:51:12 +01:00
/// Event object, world to thread, set if the thread should end as soon as possible
2010-02-05 16:13:11 +01:00
Win32KernelHandle event_kill_self ;
2008-11-27 19:35:26 +01:00
2010-02-05 15:51:12 +01:00
/// Event object, thread to world, set when the thread has entered its main loop
2010-02-05 16:13:11 +01:00
Win32KernelHandle thread_running ;
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
2010-02-05 15:51:12 +01:00
/// Event object, thread to world, set when playback is ongoing
2010-02-05 16:13:11 +01:00
Win32KernelHandle is_playing ;
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
2010-02-05 15:51:12 +01:00
/// Event object, thread to world, set if an error state has occurred (implies thread is dying)
2010-02-05 16:13:11 +01:00
Win32KernelHandle error_happened ;
2008-11-27 19:35:26 +01:00
2010-02-05 15:51:12 +01:00
/// Statically allocated error message text describing reason for error_happened being set
2014-03-24 15:54:22 +01:00
const char * error_message = nullptr ;
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
2010-02-05 15:51:12 +01:00
/// Playback volume, 1.0 is "unchanged"
2014-03-24 15:54:22 +01:00
double volume = 1.0 ;
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
2010-02-05 15:51:12 +01:00
/// Audio frame to start playback at
2014-03-24 15:54:22 +01:00
int64_t start_frame = 0 ;
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
2010-02-05 15:51:12 +01:00
/// Audio frame to end playback at
2014-03-24 15:54:22 +01:00
int64_t end_frame = 0 ;
2008-11-27 19:35:26 +01:00
2010-02-05 15:51:12 +01:00
/// Desired length in milliseconds to write ahead of the playback cursor
2009-05-07 16:47:36 +02:00
int wanted_latency ;
Note: This was done using a script! it's far from perfect but 95% of the work has been done already formatting-wise.
Document all functions, class, struct, union, enum, macro, variable, typedefs. This isn't the actual document in itself but empty documentation using any old documentation if it was there.
This was done using exuberant ctags to get tag info, then a TCL script to parse/remove old comments and convert them into Doxygen-style.
Some notes:
* Anything labeled 'DOCME' needs to be documented, @param and @return have been left blank as it would be annoying to delete the 'DOCME' from every one of those.
* Some multiline comments may have been munged into single line comments
* Leave the /// comments above global variables with a space, if they're harder to read then we'll be less likey to use them.
* Enum comments can go after the enumeration itself '[value] /// comment'
* include/aegisub/*.h haven't been converted yet, this will be done in a later commit
* Some documentation blocks are in the wrong place, in the .h when it should be in the .cpp, or vice versa.
See http://devel.aegisub.org/wiki/Doxygen for some details on Doxygen and a 'style guide'.
Originally committed to SVN as r3312.
2009-07-30 00:59:22 +02:00
2010-02-05 15:51:12 +01:00
/// Multiplier for WantedLatency to get total buffer length
2009-05-07 16:47:36 +02:00
int buffer_length ;
2010-02-05 15:51:12 +01:00
/// System millisecond timestamp of last playback start, used to calculate playback position
2008-11-27 19:35:26 +01:00
DWORD last_playback_restart ;
2010-02-05 15:51:12 +01:00
/// Audio provider to take sample data from
2014-07-09 16:22:49 +02:00
agi : : AudioProvider * provider ;
2008-11-27 19:35:26 +01:00
public :
2011-11-16 20:54:55 +01:00
/// @brief Constructor, creates and starts playback thread
/// @param provider Audio provider to take sample data from
/// @param WantedLatency Desired length in milliseconds to write ahead of the playback cursor
/// @param BufferLength Multiplier for WantedLatency to get total buffer length
2014-07-09 16:22:49 +02:00
DirectSoundPlayer2Thread ( agi : : AudioProvider * provider , int WantedLatency , int BufferLength , wxWindow * parent ) ;
2011-11-16 20:54:55 +01:00
/// @brief Destructor, waits for thread to have died
2008-11-27 19:35:26 +01:00
~ DirectSoundPlayer2Thread ( ) ;
2011-11-16 20:54:55 +01:00
/// @brief Start audio playback
/// @param start Audio frame to start playback at
/// @param count Number of audio frames to play
2008-11-27 19:35:26 +01:00
void Play ( int64_t start , int64_t count ) ;
2011-11-16 20:54:55 +01:00
/// @brief Stop audio playback
2008-11-27 19:35:26 +01:00
void Stop ( ) ;
2011-11-16 20:54:55 +01:00
/// @brief Change audio playback end point
/// @param new_end_frame New last audio frame to play
///
/// Playback stops instantly if new_end_frame is before the current playback position
2008-11-27 19:35:26 +01:00
void SetEndFrame ( int64_t new_end_frame ) ;
2011-11-16 20:54:55 +01:00
/// @brief Change audio playback volume
/// @param new_volume New playback amplification factor, 1.0 is "unchanged"
2008-11-27 19:35:26 +01:00
void SetVolume ( double new_volume ) ;
2011-11-16 20:54:55 +01:00
/// @brief Tell whether audio playback is active
/// @return True if audio is being played back, false if it is not
2008-11-27 19:35:26 +01:00
bool IsPlaying ( ) ;
2011-11-16 20:54:55 +01:00
/// @brief Get approximate current audio frame being heard by the user
/// @return Audio frame index
///
/// Returns 0 if not playing
2008-11-27 19:35:26 +01:00
int64_t GetCurrentFrame ( ) ;
2011-11-16 20:54:55 +01:00
/// @brief Get audio playback end point
/// @return Audio frame index
2008-11-27 19:35:26 +01:00
int64_t GetEndFrame ( ) ;
2011-11-16 20:54:55 +01:00
/// @brief Tell whether playback thread has died
/// @return True if thread is no longer running
2009-05-22 03:41:31 +02:00
bool IsDead ( ) ;
2008-11-27 19:35:26 +01:00
} ;
unsigned int __stdcall DirectSoundPlayer2Thread : : ThreadProc ( void * parameter )
{
static_cast < DirectSoundPlayer2Thread * > ( parameter ) - > Run ( ) ;
return 0 ;
}
2010-02-05 15:51:12 +01:00
/// Macro used to set error_message, error_happened and end the thread
2011-11-16 20:55:31 +01:00
# define REPORT_ERROR(msg) \
{ \
ResetEvent ( is_playing ) ; \
error_message = " DirectSoundPlayer2Thread: " msg ; \
SetEvent ( error_happened ) ; \
return ; \
}
2008-11-27 19:35:26 +01:00
2011-11-16 20:54:55 +01:00
void DirectSoundPlayer2Thread : : Run ( )
{
2008-11-27 19:35:26 +01:00
COMInitialization COM_library ;
2014-12-12 17:19:10 +01:00
if ( ! COM_library . Init ( ) )
2008-11-27 19:35:26 +01:00
REPORT_ERROR ( " Could not initialise COM " )
// Create DirectSound object
2014-03-25 17:51:38 +01:00
IDirectSound8 * ds_raw = nullptr ;
if ( FAILED ( DirectSoundCreate8 ( & DSDEVID_DefaultPlayback , & ds_raw , nullptr ) ) )
2008-11-27 19:35:26 +01:00
REPORT_ERROR ( " Cound not create DirectSound object " )
2014-03-25 17:51:38 +01:00
COMObjectRetainer < IDirectSound8 > ds ( ds_raw ) ;
2008-11-27 19:35:26 +01:00
// Ensure we can get interesting wave formats (unless we have PRIORITY we can only use a standard 8 bit format)
2014-03-25 17:51:38 +01:00
ds - > SetCooperativeLevel ( parent , DSSCL_PRIORITY ) ;
2008-11-27 19:35:26 +01:00
// Describe the wave format
WAVEFORMATEX waveFormat ;
waveFormat . nSamplesPerSec = provider - > GetSampleRate ( ) ;
2022-08-11 06:28:35 +02:00
waveFormat . cbSize = 0 ;
waveFormat . wFormatTag = provider - > AreSamplesFloat ( ) ? 3 : WAVE_FORMAT_PCM ; // Eh fuck it.
waveFormat . nChannels = provider - > GetChannels ( ) ;
waveFormat . wBitsPerSample = provider - > GetBytesPerSample ( ) * 8 ;
2008-11-27 19:35:26 +01:00
waveFormat . nBlockAlign = waveFormat . nChannels * waveFormat . wBitsPerSample / 8 ;
waveFormat . nAvgBytesPerSec = waveFormat . nSamplesPerSec * waveFormat . nBlockAlign ;
2022-08-11 06:28:35 +02:00
//waveFormat.cbSize = sizeof(waveFormat);
2008-11-27 19:35:26 +01:00
// And the buffer itself
2009-05-07 16:47:36 +02:00
int aim = waveFormat . nAvgBytesPerSec * ( wanted_latency * buffer_length ) / 1000 ;
2008-11-27 19:35:26 +01:00
int min = DSBSIZE_MIN ;
int max = DSBSIZE_MAX ;
2011-11-16 20:54:55 +01:00
DWORD bufSize = mid ( min , aim , max ) ; // size of entire playback buffer
2008-11-27 19:35:26 +01:00
DSBUFFERDESC desc ;
desc . dwSize = sizeof ( DSBUFFERDESC ) ;
2022-08-11 06:28:35 +02:00
desc . dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS ;
2008-11-27 19:35:26 +01:00
desc . dwBufferBytes = bufSize ;
desc . dwReserved = 0 ;
desc . lpwfxFormat = & waveFormat ;
desc . guid3DAlgorithm = GUID_NULL ;
// And then create the buffer
IDirectSoundBuffer * bfr7 = 0 ;
if FAILED ( ds - > CreateSoundBuffer ( & desc , & bfr7 , 0 ) )
REPORT_ERROR ( " Could not create buffer " )
// But it's an old version interface we get, query it for the DSound8 interface
2014-03-25 17:51:38 +01:00
IDirectSoundBuffer8 * bfr_raw = nullptr ;
if ( FAILED ( bfr7 - > QueryInterface ( IID_IDirectSoundBuffer8 , ( LPVOID * ) & bfr_raw ) ) )
2008-11-27 19:35:26 +01:00
REPORT_ERROR ( " Buffer doesn't support version 8 interface " )
2014-03-25 17:51:38 +01:00
COMObjectRetainer < IDirectSoundBuffer8 > bfr ( bfr_raw ) ;
2008-11-27 19:35:26 +01:00
bfr7 - > Release ( ) ;
bfr7 = 0 ;
2011-09-28 21:43:11 +02:00
//wx Log Debug("DirectSoundPlayer2: Created buffer of %d bytes, supposed to be %d milliseconds or %d frames", bufSize, WANTED_LATENCY*BUFFER_LENGTH, bufSize/provider->GetBytesPerSample());
2008-11-27 19:35:26 +01:00
// Now we're ready to roll!
SetEvent ( thread_running ) ;
bool running = true ;
HANDLE events_to_wait [ ] = {
event_start_playback ,
event_stop_playback ,
event_update_end_time ,
event_set_volume ,
event_kill_self
} ;
int64_t next_input_frame = 0 ;
DWORD buffer_offset = 0 ;
bool playback_should_be_running = false ;
2009-06-01 16:53:18 +02:00
int current_latency = wanted_latency ;
2022-08-11 06:28:35 +02:00
const DWORD wanted_latency_bytes = wanted_latency * waveFormat . nSamplesPerSec * provider - > GetBytesPerSample ( ) / 1000 ;
2008-11-27 19:35:26 +01:00
while ( running )
{
2009-06-01 16:53:18 +02:00
DWORD wait_result = WaitForMultipleObjects ( sizeof ( events_to_wait ) / sizeof ( HANDLE ) , events_to_wait , FALSE , current_latency ) ;
2008-11-27 19:35:26 +01:00
switch ( wait_result )
{
case WAIT_OBJECT_0 + 0 :
{
// Start or restart playback
bfr - > Stop ( ) ;
next_input_frame = start_frame ;
DWORD buf_size ; // size of buffer locked for filling
void * buf ;
buffer_offset = 0 ;
2009-06-01 16:53:18 +02:00
if ( FAILED ( bfr - > SetCurrentPosition ( 0 ) ) )
REPORT_ERROR ( " Could not reset playback buffer cursor before filling first buffer. " )
2008-11-27 19:35:26 +01:00
HRESULT res = bfr - > Lock ( buffer_offset , 0 , & buf , & buf_size , 0 , 0 , DSBLOCK_ENTIREBUFFER ) ;
2010-02-05 17:06:37 +01:00
if ( FAILED ( res ) )
2008-11-27 19:35:26 +01:00
{
if ( res = = DSERR_BUFFERLOST )
{
// Try to regain the buffer
2010-02-05 17:06:37 +01:00
if ( FAILED ( bfr - > Restore ( ) ) | |
FAILED ( bfr - > Lock ( buffer_offset , 0 , & buf , & buf_size , 0 , 0 , DSBLOCK_ENTIREBUFFER ) ) )
2008-11-27 19:35:26 +01:00
{
2010-02-05 17:06:37 +01:00
REPORT_ERROR ( " Lost buffer and could not restore it. " )
2008-11-27 19:35:26 +01:00
}
}
2010-02-05 17:06:37 +01:00
else
{
REPORT_ERROR ( " Could not lock buffer for playback. " )
}
2008-11-27 19:35:26 +01:00
}
2009-06-01 16:53:18 +02:00
// Clear the buffer in case we can't fill it completely
memset ( buf , 0 , buf_size ) ;
2014-03-25 17:51:38 +01:00
DWORD bytes_filled = FillAndUnlockBuffers ( buf , buf_size , 0 , 0 , next_input_frame , bfr . get ( ) ) ;
2009-06-01 16:53:18 +02:00
buffer_offset + = bytes_filled ;
2008-11-27 19:35:26 +01:00
if ( buffer_offset > = bufSize ) buffer_offset - = bufSize ;
if ( FAILED ( bfr - > SetCurrentPosition ( 0 ) ) )
REPORT_ERROR ( " Could not reset playback buffer cursor before playback. " )
2009-06-01 16:53:18 +02:00
if ( bytes_filled < wanted_latency_bytes )
{
// Very short playback length, do without streaming playback
2022-08-11 06:28:35 +02:00
current_latency = ( bytes_filled * 1000 ) / ( waveFormat . nSamplesPerSec * provider - > GetBytesPerSample ( ) ) ;
2009-06-01 16:53:18 +02:00
if ( FAILED ( bfr - > Play ( 0 , 0 , 0 ) ) )
REPORT_ERROR ( " Could not start single-buffer playback. " )
}
else
{
// We filled the entire buffer so there's reason to do streaming playback
current_latency = wanted_latency ;
if ( FAILED ( bfr - > Play ( 0 , 0 , DSBPLAY_LOOPING ) ) )
REPORT_ERROR ( " Could not start looping playback. " )
}
2008-11-27 19:35:26 +01:00
SetEvent ( is_playing ) ;
playback_should_be_running = true ;
break ;
}
case WAIT_OBJECT_0 + 1 :
2011-11-16 20:55:31 +01:00
stop_playback :
// Stop playing
bfr - > Stop ( ) ;
ResetEvent ( is_playing ) ;
playback_should_be_running = false ;
break ;
2008-11-27 19:35:26 +01:00
case WAIT_OBJECT_0 + 2 :
2011-11-16 20:55:31 +01:00
// Set end frame
if ( end_frame < = next_input_frame )
2008-11-27 19:35:26 +01:00
{
2011-11-16 20:55:31 +01:00
goto stop_playback ;
2008-11-27 19:35:26 +01:00
}
2011-11-16 20:55:31 +01:00
// If the user is dragging the start or end point in the audio display
// the set end frame events might come in faster than the timeouts happen
// and then new data never get filled into the buffer. See bug #915.
goto do_fill_buffer ;
2008-11-27 19:35:26 +01:00
case WAIT_OBJECT_0 + 3 :
2022-08-11 06:28:35 +02:00
{
LONG invert_volume = ( LONG ) ( ( this - > volume - 1.0 ) * 5000.0 ) ; // Hrmm weirdly it's half?
// Look, I would have used a min max but it just errored out for me lol.
if ( invert_volume > DSBVOLUME_MAX )
invert_volume = DSBVOLUME_MAX ;
else if ( invert_volume < DSBVOLUME_MIN / 2 )
invert_volume = DSBVOLUME_MIN / 2 ;
bfr - > SetVolume ( invert_volume ) ;
}
2011-11-16 20:55:31 +01:00
// Change volume
// We aren't thread safe right now, filling the buffers grabs volume directly
// from the field set by the controlling thread, but it shouldn't be a major
// problem if race conditions do occur, just some momentary distortion.
goto do_fill_buffer ;
2008-11-27 19:35:26 +01:00
case WAIT_OBJECT_0 + 4 :
2011-11-16 20:55:31 +01:00
// Perform suicide
running = false ;
goto stop_playback ;
2008-11-27 19:35:26 +01:00
case WAIT_TIMEOUT :
2009-10-27 00:37:47 +01:00
do_fill_buffer :
2008-11-27 19:35:26 +01:00
{
// Time to fill more into buffer
if ( ! playback_should_be_running )
break ;
DWORD status ;
if ( FAILED ( bfr - > GetStatus ( & status ) ) )
REPORT_ERROR ( " Could not get playback buffer status " )
if ( ! ( status & DSBSTATUS_LOOPING ) )
{
2009-06-01 16:53:18 +02:00
// Not looping playback...
// hopefully we only triggered timeout after being done with the buffer
2011-11-16 20:55:31 +01:00
goto stop_playback ;
2008-11-27 19:35:26 +01:00
}
DWORD play_cursor ;
if ( FAILED ( bfr - > GetCurrentPosition ( & play_cursor , 0 ) ) )
REPORT_ERROR ( " Could not get play cursor position for filling buffer. " )
int bytes_needed = ( int ) play_cursor - ( int ) buffer_offset ;
if ( bytes_needed < 0 ) bytes_needed + = ( int ) bufSize ;
2009-10-31 01:54:42 +01:00
// Requesting zero buffer makes Windows cry, and zero buffer seemed to be
// a common request on Windows 7. (Maybe related to the new timer coalescing?)
// We'll probably get non-zero bytes requested on the next iteration.
if ( bytes_needed = = 0 )
break ;
2008-11-27 19:35:26 +01:00
DWORD buf1sz , buf2sz ;
void * buf1 , * buf2 ;
2009-11-04 05:16:25 +01:00
assert ( bytes_needed > 0 ) ;
2009-10-31 01:54:42 +01:00
assert ( buffer_offset < bufSize ) ;
2009-11-04 05:16:25 +01:00
assert ( ( DWORD ) bytes_needed < = bufSize ) ;
2009-10-31 01:54:42 +01:00
2008-11-27 19:35:26 +01:00
HRESULT res = bfr - > Lock ( buffer_offset , bytes_needed , & buf1 , & buf1sz , & buf2 , & buf2sz , 0 ) ;
2009-10-31 01:54:42 +01:00
switch ( res )
2008-11-27 19:35:26 +01:00
{
2009-10-31 01:54:42 +01:00
case DSERR_BUFFERLOST :
// Try to regain the buffer
// When the buffer was lost the entire contents was lost too, so we have to start over
if ( SUCCEEDED ( bfr - > Restore ( ) ) & &
SUCCEEDED ( bfr - > Lock ( 0 , bufSize , & buf1 , & buf1sz , & buf2 , & buf2sz , 0 ) ) & &
SUCCEEDED ( bfr - > Play ( 0 , 0 , DSBPLAY_LOOPING ) ) )
2008-11-27 19:35:26 +01:00
{
2010-06-09 01:21:39 +02:00
LOG_D ( " audio/player/dsound " ) < < " Lost and restored buffer " ;
2009-10-31 01:54:42 +01:00
break ;
2008-11-27 19:35:26 +01:00
}
2009-10-31 01:54:42 +01:00
REPORT_ERROR ( " Lost buffer and could not restore it. " )
case DSERR_INVALIDPARAM :
REPORT_ERROR ( " Invalid parameters to IDirectSoundBuffer8::Lock(). " )
2008-11-27 19:35:26 +01:00
2009-10-31 01:54:42 +01:00
case DSERR_INVALIDCALL :
REPORT_ERROR ( " Invalid call to IDirectSoundBuffer8::Lock(). " )
case DSERR_PRIOLEVELNEEDED :
REPORT_ERROR ( " Incorrect priority level set on DirectSoundBuffer8 object. " )
default :
if ( FAILED ( res ) )
REPORT_ERROR ( " Could not lock audio buffer, unknown error. " )
break ;
2008-11-27 19:35:26 +01:00
}
2014-03-25 17:51:38 +01:00
DWORD bytes_filled = FillAndUnlockBuffers ( buf1 , buf1sz , buf2 , buf2sz , next_input_frame , bfr . get ( ) ) ;
2009-06-01 16:53:18 +02:00
buffer_offset + = bytes_filled ;
2008-11-27 19:35:26 +01:00
if ( buffer_offset > = bufSize ) buffer_offset - = bufSize ;
2009-06-01 16:53:18 +02:00
if ( bytes_filled < 1024 )
{
// Arbitrary low number, we filled in very little so better get back to filling in the rest with silence
// really fast... set latency to zero in this case.
current_latency = 0 ;
}
else if ( bytes_filled < wanted_latency_bytes )
{
// Didn't fill as much as we wanted to, let's get back to filling sooner than normal
2022-08-11 06:28:35 +02:00
current_latency = ( bytes_filled * 1000 ) / ( waveFormat . nSamplesPerSec * provider - > GetBytesPerSample ( ) ) ;
2009-06-01 16:53:18 +02:00
}
else
{
// Plenty filled in, do regular latency
current_latency = wanted_latency ;
}
2008-11-27 19:35:26 +01:00
break ;
}
default :
REPORT_ERROR ( " Something bad happened while waiting on events in playback loop, either the wait failed or an event object was abandoned. " )
break ;
}
}
}
2011-11-16 20:54:55 +01:00
# undef REPORT_ERROR
2008-11-27 19:35:26 +01:00
DWORD DirectSoundPlayer2Thread : : FillAndUnlockBuffers ( void * buf1 , DWORD buf1sz , void * buf2 , DWORD buf2sz , int64_t & input_frame , IDirectSoundBuffer8 * bfr )
{
// Assume buffers have been locked and are ready to be filled
2022-08-11 06:28:35 +02:00
DWORD bytes_per_frame = provider - > GetChannels ( ) * provider - > GetBytesPerSample ( ) ;
2008-11-27 19:35:26 +01:00
DWORD buf1szf = buf1sz / bytes_per_frame ;
DWORD buf2szf = buf2sz / bytes_per_frame ;
if ( input_frame > = end_frame )
{
// Silence
if ( buf1 )
memset ( buf1 , 0 , buf1sz ) ;
if ( buf2 )
memset ( buf2 , 0 , buf2sz ) ;
input_frame + = buf1szf + buf2szf ;
bfr - > Unlock ( buf1 , buf1sz , buf2 , buf2sz ) ; // should be checking for success
return buf1sz + buf2sz ;
}
if ( buf1 & & buf1sz )
{
if ( buf1szf + input_frame > end_frame )
{
buf1szf = end_frame - input_frame ;
buf1sz = buf1szf * bytes_per_frame ;
buf2szf = 0 ;
buf2sz = 0 ;
}
2022-08-11 06:28:35 +02:00
provider - > GetAudio ( buf1 , input_frame , buf1szf ) ;
2008-11-27 19:35:26 +01:00
input_frame + = buf1szf ;
}
if ( buf2 & & buf2sz )
{
if ( buf2szf + input_frame > end_frame )
{
buf2szf = end_frame - input_frame ;
buf2sz = buf2szf * bytes_per_frame ;
}
2022-08-11 06:28:35 +02:00
provider - > GetAudio ( buf2 , input_frame , buf2szf ) ;
2008-11-27 19:35:26 +01:00
input_frame + = buf2szf ;
}
bfr - > Unlock ( buf1 , buf1sz , buf2 , buf2sz ) ; // bad? should check for success
return buf1sz + buf2sz ;
}
void DirectSoundPlayer2Thread : : CheckError ( )
{
2009-05-22 03:41:31 +02:00
try
2008-11-27 19:35:26 +01:00
{
2009-05-22 03:41:31 +02:00
switch ( WaitForSingleObject ( error_happened , 0 ) )
{
case WAIT_OBJECT_0 :
throw error_message ;
2008-11-27 19:35:26 +01:00
2009-05-22 03:41:31 +02:00
case WAIT_ABANDONED :
2011-09-28 21:43:11 +02:00
throw " The DirectShowPlayer2Thread error signal event was abandoned, somehow. This should not happen. " ;
2008-11-27 19:35:26 +01:00
2009-05-22 03:41:31 +02:00
case WAIT_FAILED :
2011-09-28 21:43:11 +02:00
throw " Failed checking state of DirectShowPlayer2Thread error signal event. " ;
2008-11-27 19:35:26 +01:00
2009-05-22 03:41:31 +02:00
case WAIT_TIMEOUT :
default :
return ;
}
}
catch ( . . . )
{
ResetEvent ( is_playing ) ;
ResetEvent ( thread_running ) ;
throw ;
2008-11-27 19:35:26 +01:00
}
}
2014-07-09 16:22:49 +02:00
DirectSoundPlayer2Thread : : DirectSoundPlayer2Thread ( agi : : AudioProvider * provider , int WantedLatency , int BufferLength , wxWindow * parent )
2014-03-25 17:51:38 +01:00
: parent ( ( HWND ) parent - > GetHandle ( ) )
, event_start_playback ( CreateEvent ( 0 , FALSE , FALSE , 0 ) )
2010-02-05 16:13:11 +01:00
, event_stop_playback ( CreateEvent ( 0 , FALSE , FALSE , 0 ) )
, event_update_end_time ( CreateEvent ( 0 , FALSE , FALSE , 0 ) )
, event_set_volume ( CreateEvent ( 0 , FALSE , FALSE , 0 ) )
, event_kill_self ( CreateEvent ( 0 , FALSE , FALSE , 0 ) )
, thread_running ( CreateEvent ( 0 , TRUE , FALSE , 0 ) )
, is_playing ( CreateEvent ( 0 , TRUE , FALSE , 0 ) )
, error_happened ( CreateEvent ( 0 , FALSE , FALSE , 0 ) )
2011-11-16 20:54:55 +01:00
, wanted_latency ( WantedLatency )
, buffer_length ( BufferLength )
, provider ( provider )
2008-11-27 19:35:26 +01:00
{
2012-02-20 19:22:12 +01:00
thread_handle = ( HANDLE ) _beginthreadex ( 0 , 0 , ThreadProc , this , 0 , 0 ) ;
2008-11-27 19:35:26 +01:00
if ( ! thread_handle )
2014-07-09 16:22:49 +02:00
throw AudioPlayerOpenError ( " Failed creating playback thread in DirectSoundPlayer2. This is bad. " ) ;
2008-11-27 19:35:26 +01:00
2010-02-05 14:22:43 +01:00
HANDLE running_or_error [ ] = { thread_running , error_happened } ;
switch ( WaitForMultipleObjects ( 2 , running_or_error , FALSE , INFINITE ) )
{
case WAIT_OBJECT_0 :
// running, all good
return ;
case WAIT_OBJECT_0 + 1 :
// error happened, we fail
2014-07-09 16:22:49 +02:00
throw AudioPlayerOpenError ( error_message ) ;
2008-11-27 19:35:26 +01:00
2010-02-05 14:22:43 +01:00
default :
2014-07-09 16:22:49 +02:00
throw AudioPlayerOpenError ( " Failed wait for thread start or thread error in DirectSoundPlayer2. This is bad. " ) ;
2010-02-05 14:22:43 +01:00
}
2008-11-27 19:35:26 +01:00
}
DirectSoundPlayer2Thread : : ~ DirectSoundPlayer2Thread ( )
{
SetEvent ( event_kill_self ) ;
WaitForSingleObject ( thread_handle , INFINITE ) ;
}
void DirectSoundPlayer2Thread : : Play ( int64_t start , int64_t count )
{
CheckError ( ) ;
start_frame = start ;
end_frame = start + count ;
SetEvent ( event_start_playback ) ;
last_playback_restart = GetTickCount ( ) ;
2011-11-16 20:55:31 +01:00
// Block until playback actually begins to avoid race conditions with
// checking if playback is in progress
HANDLE events_to_wait [ ] = { is_playing , error_happened } ;
switch ( WaitForMultipleObjects ( 2 , events_to_wait , FALSE , INFINITE ) )
{
case WAIT_OBJECT_0 + 0 : // Playing
LOG_D ( " audio/player/dsound " ) < < " Playback begun " ;
break ;
case WAIT_OBJECT_0 + 1 : // Error
throw error_message ;
default :
2014-05-29 14:57:27 +02:00
throw agi : : InternalError ( " Unexpected result from WaitForMultipleObjects in DirectSoundPlayer2Thread::Play " ) ;
2011-11-16 20:55:31 +01:00
}
2008-11-27 19:35:26 +01:00
}
void DirectSoundPlayer2Thread : : Stop ( )
{
CheckError ( ) ;
SetEvent ( event_stop_playback ) ;
}
void DirectSoundPlayer2Thread : : SetEndFrame ( int64_t new_end_frame )
{
CheckError ( ) ;
end_frame = new_end_frame ;
SetEvent ( event_update_end_time ) ;
}
void DirectSoundPlayer2Thread : : SetVolume ( double new_volume )
{
CheckError ( ) ;
volume = new_volume ;
SetEvent ( event_set_volume ) ;
}
bool DirectSoundPlayer2Thread : : IsPlaying ( )
{
CheckError ( ) ;
switch ( WaitForSingleObject ( is_playing , 0 ) )
{
case WAIT_ABANDONED :
2011-09-28 21:43:11 +02:00
throw " The DirectShowPlayer2Thread playback state event was abandoned, somehow. This should not happen. " ;
2008-11-27 19:35:26 +01:00
case WAIT_FAILED :
2011-09-28 21:43:11 +02:00
throw " Failed checking state of DirectShowPlayer2Thread playback state event. " ;
2008-11-27 19:35:26 +01:00
case WAIT_OBJECT_0 :
return true ;
case WAIT_TIMEOUT :
default :
return false ;
}
}
int64_t DirectSoundPlayer2Thread : : GetCurrentFrame ( )
{
CheckError ( ) ;
if ( ! IsPlaying ( ) ) return 0 ;
2011-12-27 02:38:14 +01:00
int64_t milliseconds_elapsed = GetTickCount ( ) - last_playback_restart ;
2008-11-27 19:35:26 +01:00
return start_frame + milliseconds_elapsed * provider - > GetSampleRate ( ) / 1000 ;
}
int64_t DirectSoundPlayer2Thread : : GetEndFrame ( )
{
CheckError ( ) ;
return end_frame ;
}
2009-05-22 03:41:31 +02:00
bool DirectSoundPlayer2Thread : : IsDead ( )
{
switch ( WaitForSingleObject ( thread_running , 0 ) )
{
case WAIT_OBJECT_0 :
return false ;
default :
return true ;
}
}
2014-07-09 16:22:49 +02:00
DirectSoundPlayer2 : : DirectSoundPlayer2 ( agi : : AudioProvider * provider , wxWindow * parent )
2012-03-20 00:31:33 +01:00
: AudioPlayer ( provider )
2008-11-27 19:35:26 +01:00
{
2009-05-07 16:47:36 +02:00
// The buffer will hold BufferLength times WantedLatency milliseconds of audio
2010-05-21 03:13:36 +02:00
WantedLatency = OPT_GET ( " Player/Audio/DirectSound/Buffer Latency " ) - > GetInt ( ) ;
BufferLength = OPT_GET ( " Player/Audio/DirectSound/Buffer Length " ) - > GetInt ( ) ;
2009-05-07 16:47:36 +02:00
// sanity checking
if ( WantedLatency < = 0 )
WantedLatency = 100 ;
if ( BufferLength < = 0 )
BufferLength = 5 ;
2008-11-27 21:39:36 +01:00
try
{
2014-04-23 22:53:24 +02:00
thread = agi : : make_unique < DirectSoundPlayer2Thread > ( provider , WantedLatency , BufferLength , parent ) ;
2008-11-27 21:39:36 +01:00
}
2011-09-28 21:43:11 +02:00
catch ( const char * msg )
2008-11-27 21:39:36 +01:00
{
2010-06-09 01:21:39 +02:00
LOG_E ( " audio/player/dsound " ) < < msg ;
2014-07-09 16:22:49 +02:00
throw AudioPlayerOpenError ( msg ) ;
2008-11-27 21:39:36 +01:00
}
2008-11-27 19:35:26 +01:00
}
2012-03-20 00:31:33 +01:00
DirectSoundPlayer2 : : ~ DirectSoundPlayer2 ( )
2008-11-27 19:35:26 +01:00
{
}
2012-03-20 00:31:33 +01:00
bool DirectSoundPlayer2 : : IsThreadAlive ( )
2008-11-27 19:35:26 +01:00
{
2012-03-20 00:31:33 +01:00
if ( thread & & thread - > IsDead ( ) )
2008-11-27 21:39:36 +01:00
{
2012-03-20 00:31:33 +01:00
thread . reset ( ) ;
2008-11-27 21:39:36 +01:00
}
2012-03-20 00:31:33 +01:00
2013-12-11 21:30:27 +01:00
return ! ! thread ;
2008-11-27 19:35:26 +01:00
}
void DirectSoundPlayer2 : : Play ( int64_t start , int64_t count )
{
2008-11-27 21:39:36 +01:00
try
{
thread - > Play ( start , count ) ;
}
2011-09-28 21:43:11 +02:00
catch ( const char * msg )
2008-11-27 21:39:36 +01:00
{
2010-06-09 01:21:39 +02:00
LOG_E ( " audio/player/dsound " ) < < msg ;
2008-11-27 21:39:36 +01:00
}
2008-11-27 19:35:26 +01:00
}
2012-03-25 06:05:44 +02:00
void DirectSoundPlayer2 : : Stop ( )
2008-11-27 19:35:26 +01:00
{
2008-11-27 21:39:36 +01:00
try
{
2009-05-22 03:41:31 +02:00
if ( IsThreadAlive ( ) ) thread - > Stop ( ) ;
2008-11-27 21:39:36 +01:00
}
2011-09-28 21:43:11 +02:00
catch ( const char * msg )
2008-11-27 21:39:36 +01:00
{
2010-06-09 01:21:39 +02:00
LOG_E ( " audio/player/dsound " ) < < msg ;
2008-11-27 19:35:26 +01:00
}
}
bool DirectSoundPlayer2 : : IsPlaying ( )
{
2008-11-27 21:39:36 +01:00
try
{
2009-05-22 03:41:31 +02:00
if ( ! IsThreadAlive ( ) ) return false ;
2008-11-27 21:39:36 +01:00
return thread - > IsPlaying ( ) ;
}
2011-09-28 21:43:11 +02:00
catch ( const char * msg )
2008-11-27 21:39:36 +01:00
{
2010-06-09 01:21:39 +02:00
LOG_E ( " audio/player/dsound " ) < < msg ;
2008-11-27 21:39:36 +01:00
return false ;
}
2008-11-27 19:35:26 +01:00
}
int64_t DirectSoundPlayer2 : : GetEndPosition ( )
{
2008-11-27 21:39:36 +01:00
try
{
2009-05-22 03:41:31 +02:00
if ( ! IsThreadAlive ( ) ) return 0 ;
2008-11-27 21:39:36 +01:00
return thread - > GetEndFrame ( ) ;
}
2011-09-28 21:43:11 +02:00
catch ( const char * msg )
2008-11-27 21:39:36 +01:00
{
2010-06-09 01:21:39 +02:00
LOG_E ( " audio/player/dsound " ) < < msg ;
2008-11-27 21:39:36 +01:00
return 0 ;
}
2008-11-27 19:35:26 +01:00
}
int64_t DirectSoundPlayer2 : : GetCurrentPosition ( )
{
2008-11-27 21:39:36 +01:00
try
{
2009-05-22 03:41:31 +02:00
if ( ! IsThreadAlive ( ) ) return 0 ;
2008-11-27 21:39:36 +01:00
return thread - > GetCurrentFrame ( ) ;
}
2011-09-28 21:43:11 +02:00
catch ( const char * msg )
2008-11-27 21:39:36 +01:00
{
2010-06-09 01:21:39 +02:00
LOG_E ( " audio/player/dsound " ) < < msg ;
2008-11-27 21:39:36 +01:00
return 0 ;
}
2008-11-27 19:35:26 +01:00
}
void DirectSoundPlayer2 : : SetEndPosition ( int64_t pos )
{
2008-11-27 21:39:36 +01:00
try
{
2009-05-22 03:41:31 +02:00
if ( IsThreadAlive ( ) ) thread - > SetEndFrame ( pos ) ;
2008-11-27 21:39:36 +01:00
}
2011-09-28 21:43:11 +02:00
catch ( const char * msg )
2008-11-27 21:39:36 +01:00
{
2010-06-09 01:21:39 +02:00
LOG_E ( " audio/player/dsound " ) < < msg ;
2008-11-27 21:39:36 +01:00
}
2008-11-27 19:35:26 +01:00
}
void DirectSoundPlayer2 : : SetVolume ( double vol )
{
2008-11-27 21:39:36 +01:00
try
{
2009-05-22 03:41:31 +02:00
if ( IsThreadAlive ( ) ) thread - > SetVolume ( vol ) ;
2008-11-27 21:39:36 +01:00
}
2011-09-28 21:43:11 +02:00
catch ( const char * msg )
2008-11-27 21:39:36 +01:00
{
2010-06-09 01:21:39 +02:00
LOG_E ( " audio/player/dsound " ) < < msg ;
2008-11-27 21:39:36 +01:00
}
2008-11-27 19:35:26 +01:00
}
2014-03-24 15:54:22 +01:00
}
2014-07-09 16:22:49 +02:00
std : : unique_ptr < AudioPlayer > CreateDirectSound2Player ( agi : : AudioProvider * provider , wxWindow * parent ) {
2014-04-23 22:53:24 +02:00
return agi : : make_unique < DirectSoundPlayer2 > ( provider , parent ) ;
2014-03-24 15:54:22 +01:00
}
2008-11-27 19:35:26 +01:00
2022-08-10 15:09:41 +02:00
# endif // WITH_DIRECTSOUND