mirror of
https://github.com/xiaoyifang/goldendict-ng.git
synced 2024-11-30 17:24:08 +00:00
Add internal audio player(ffmpeg/libav + libao).
* phonon, bass, playsound are removed.
This commit is contained in:
parent
13654324d1
commit
c4752eb14c
|
@ -18,7 +18,8 @@ This code has been run and tested on Windows XP/Vista/7, Ubuntu Linux, Mac OS X.
|
|||
|
||||
sudo apt-get install pkg-config build-essential qt4-qmake \
|
||||
libvorbis-dev zlib1g-dev libhunspell-dev x11proto-record-dev \
|
||||
qt4-qmake libqt4-dev libxtst-dev libphonon-dev liblzo2-dev libbz2-dev
|
||||
qt4-qmake libqt4-dev libxtst-dev libphonon-dev liblzo2-dev libbz2-dev \
|
||||
libao-dev libavutil-dev libavformat-dev
|
||||
|
||||
## How to build
|
||||
|
||||
|
|
119
articleview.cc
119
articleview.cc
|
@ -17,15 +17,13 @@
|
|||
#include "webmultimediadownload.hh"
|
||||
#include "programs.hh"
|
||||
#include "dprintf.hh"
|
||||
#include "ffmpegaudio.hh"
|
||||
#include <QDebug>
|
||||
#include <QWebElement>
|
||||
#include <QCryptographicHash>
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h> // For PlaySound
|
||||
#include "bass.hh"
|
||||
|
||||
#include "speechclient.hh"
|
||||
|
||||
#include <QPainter>
|
||||
|
@ -33,45 +31,9 @@
|
|||
|
||||
#include <QBuffer>
|
||||
|
||||
// Phonon headers are a mess. How to include them properly? Send patches if you
|
||||
// know.
|
||||
#ifdef __WIN32
|
||||
#include <Phonon/AudioOutput>
|
||||
#include <Phonon/MediaObject>
|
||||
#else
|
||||
#include <phonon/audiooutput.h>
|
||||
#include <phonon/mediaobject.h>
|
||||
#endif
|
||||
|
||||
using std::map;
|
||||
using std::list;
|
||||
|
||||
/// A phonon-based audio player, created on demand
|
||||
struct AudioPlayer
|
||||
{
|
||||
Phonon::AudioOutput output;
|
||||
Phonon::MediaObject object;
|
||||
|
||||
static AudioPlayer & instance();
|
||||
|
||||
private:
|
||||
|
||||
AudioPlayer();
|
||||
};
|
||||
|
||||
AudioPlayer::AudioPlayer():
|
||||
output( Phonon::AccessibilityCategory )
|
||||
{
|
||||
Phonon::createPath( &object, &output );
|
||||
}
|
||||
|
||||
AudioPlayer & AudioPlayer::instance()
|
||||
{
|
||||
static AudioPlayer a;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm,
|
||||
std::vector< sptr< Dictionary::Class > > const & allDictionaries_,
|
||||
|
@ -202,15 +164,6 @@ void ArticleView::setGroupComboBox( GroupComboBox const * g )
|
|||
ArticleView::~ArticleView()
|
||||
{
|
||||
cleanupTemp();
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
if ( winWavData.size() )
|
||||
{
|
||||
// If we were playing some sound some time ago, make sure it stopped
|
||||
// playing before freeing the waveform memory.
|
||||
PlaySoundA( 0, 0, 0 );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ArticleView::showDefinition( QString const & word, unsigned group,
|
||||
|
@ -1581,70 +1534,14 @@ void ArticleView::resourceDownloadFinished()
|
|||
Dictionary::WebMultimediaDownload::isAudioUrl( resourceDownloadUrl ) )
|
||||
{
|
||||
// Audio data
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// If we use Windows PlaySound, use that, not Phonon.
|
||||
if ( !cfg.preferences.useExternalPlayer &&
|
||||
cfg.preferences.useWindowsPlaySound )
|
||||
if ( cfg.preferences.useInternalPlayer )
|
||||
{
|
||||
// Stop any currently playing sound to make sure the previous data
|
||||
// isn't used anymore
|
||||
if ( winWavData.size() )
|
||||
{
|
||||
PlaySoundA( 0, 0, 0 );
|
||||
winWavData.clear();
|
||||
}
|
||||
|
||||
if ( data.size() < 4 || memcmp( data.data(), "RIFF", 4 ) != 0 )
|
||||
{
|
||||
QMessageBox::information( this, tr( "Playing a non-WAV file" ),
|
||||
tr( "To enable playback of files different than WAV, please go "
|
||||
"to Edit|Preferences, choose the Audio tab and select "
|
||||
"\"Play via DirectShow\" there." ) );
|
||||
Ffmpeg::AudioPlayer & player = Ffmpeg::AudioPlayer::instance();
|
||||
connect( &player, SIGNAL( error( QString ) ), this, SLOT( audioPlayerError( QString ) ), Qt::UniqueConnection );
|
||||
player.playMemory( data.data(), data.size() );
|
||||
}
|
||||
else
|
||||
{
|
||||
winWavData = data;
|
||||
PlaySoundA( &winWavData.front(), 0,
|
||||
SND_ASYNC | SND_MEMORY | SND_NODEFAULT | SND_NOWAIT );
|
||||
}
|
||||
}
|
||||
else if ( !cfg.preferences.useExternalPlayer &&
|
||||
cfg.preferences.useBassLibrary )
|
||||
{
|
||||
if( !BassAudioPlayer::instance().canBeUsed() )
|
||||
emit statusBarMessage( tr( "WARNING: %1" ).arg( tr( "Bass library not found." ) ),
|
||||
10000, QPixmap( ":/icons/error.png" ) );
|
||||
else
|
||||
{
|
||||
int bassErrorCode;
|
||||
if( !BassAudioPlayer::instance().playMemory( data.data(), data.size(), &bassErrorCode ) )
|
||||
emit statusBarMessage( tr( "WARNING: %1" ).arg( tr( "Bass library can't play this sound." ) )
|
||||
+ " " + QString( BassAudioPlayer::instance().errorText( bassErrorCode ) ),
|
||||
10000, QPixmap( ":/icons/error.png" ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if ( !cfg.preferences.useExternalPlayer )
|
||||
{
|
||||
// Play via Phonon
|
||||
|
||||
QBuffer * buf = new QBuffer;
|
||||
|
||||
buf->buffer().append( &data.front(), data.size() );
|
||||
|
||||
Phonon::MediaSource source( buf );
|
||||
source.setAutoDelete( true ); // Dispose of our buf when done
|
||||
|
||||
AudioPlayer::instance().object.stop();
|
||||
AudioPlayer::instance().object.clear();
|
||||
AudioPlayer::instance().object.enqueue( source );
|
||||
AudioPlayer::instance().object.play();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Use external viewer to play the file
|
||||
try
|
||||
{
|
||||
|
@ -1712,6 +1609,12 @@ void ArticleView::resourceDownloadFinished()
|
|||
}
|
||||
}
|
||||
|
||||
void ArticleView::audioPlayerError( QString const & message )
|
||||
{
|
||||
emit statusBarMessage( tr( "WARNING: FFmpeg Audio Player: %1" ).arg( message ),
|
||||
10000, QPixmap( ":/icons/error.png" ) );
|
||||
}
|
||||
|
||||
void ArticleView::pasteTriggered()
|
||||
{
|
||||
QString text =
|
||||
|
|
|
@ -34,11 +34,6 @@ class ArticleView: public QFrame
|
|||
QString articleToJump;
|
||||
QString soundScript;
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// Used in Windows only for PlaySound mode
|
||||
vector< char > winWavData;
|
||||
#endif
|
||||
|
||||
/// Any resource we've decided to download off the dictionary gets stored here.
|
||||
/// Full vector capacity is used for search requests, where we have to make
|
||||
/// a multitude of requests.
|
||||
|
@ -265,6 +260,9 @@ private slots:
|
|||
/// Handles the double-click from the definition.
|
||||
void doubleClicked();
|
||||
|
||||
/// Handles audio player error message
|
||||
void audioPlayerError( QString const & message );
|
||||
|
||||
private:
|
||||
|
||||
/// Deduces group from the url. If there doesn't seem to be any group,
|
||||
|
|
201
bass.cc
201
bass.cc
|
@ -1,201 +0,0 @@
|
|||
// Wrapper for bass.dll
|
||||
|
||||
#ifdef __WIN32
|
||||
|
||||
#include <QWidget>
|
||||
#include <QApplication>
|
||||
|
||||
#include <memory.h>
|
||||
#include "bass.hh"
|
||||
|
||||
BassAudioPlayer::BassAudioPlayer() :
|
||||
fBASS_Free( 0 ),
|
||||
currentHandle( 0 ),
|
||||
data( 0 ),
|
||||
hwnd( 0 ),
|
||||
spxPluginHandle( 0 )
|
||||
{
|
||||
Mutex::Lock _( mt );
|
||||
|
||||
if( ( hBass = LoadLibraryW( L"bass.dll" ) ) == 0 )
|
||||
return;
|
||||
for( ; ; )
|
||||
{
|
||||
fBASS_Init = ( pBASS_Init )GetProcAddress( hBass, "BASS_Init" );
|
||||
if( fBASS_Init == 0 )
|
||||
break;
|
||||
|
||||
fBASS_Free = ( pBASS_Free )GetProcAddress( hBass, "BASS_Free" );
|
||||
if( fBASS_Free == 0 )
|
||||
break;
|
||||
|
||||
fBASS_Stop = ( pBASS_Stop )GetProcAddress( hBass, "BASS_Stop" );
|
||||
if( fBASS_Stop == 0 )
|
||||
break;
|
||||
|
||||
fBASS_ErrorGetCode = ( pBASS_ErrorGetCode )GetProcAddress( hBass, "BASS_ErrorGetCode" );
|
||||
if( fBASS_ErrorGetCode == 0 )
|
||||
break;
|
||||
|
||||
fBASS_StreamCreateFile = ( pBASS_StreamCreateFile )GetProcAddress( hBass, "BASS_StreamCreateFile" );
|
||||
if( fBASS_StreamCreateFile == 0 )
|
||||
break;
|
||||
|
||||
fBASS_StreamFree = ( pBASS_StreamFree )GetProcAddress( hBass, "BASS_StreamFree" );
|
||||
if( fBASS_StreamFree == 0 )
|
||||
break;
|
||||
|
||||
fBASS_MusicLoad = ( pBASS_MusicLoad )GetProcAddress( hBass, "BASS_MusicLoad" );
|
||||
if( fBASS_MusicLoad == 0 )
|
||||
break;
|
||||
|
||||
fBASS_MusicFree = ( pBASS_MusicFree )GetProcAddress( hBass, "BASS_MusicFree" );
|
||||
if( fBASS_MusicFree == 0 )
|
||||
break;
|
||||
|
||||
fBASS_ChannelPlay = ( pBASS_ChannelPlay )GetProcAddress( hBass, "BASS_ChannelPlay" );
|
||||
if( fBASS_ChannelPlay == 0 )
|
||||
break;
|
||||
|
||||
fBASS_ChannelStop = ( pBASS_ChannelStop )GetProcAddress( hBass, "BASS_ChannelStop" );
|
||||
if( fBASS_ChannelStop == 0 )
|
||||
break;
|
||||
|
||||
fBASS_PluginLoad = ( pBASS_PluginLoad )GetProcAddress( hBass, "BASS_PluginLoad" );
|
||||
if ( fBASS_PluginLoad == 0 )
|
||||
break;
|
||||
|
||||
fBASS_PluginFree = ( pBASS_PluginFree )GetProcAddress( hBass, "BASS_PluginFree" );
|
||||
if ( fBASS_PluginFree == 0 )
|
||||
break;
|
||||
|
||||
spxPluginHandle = fBASS_PluginLoad( ( const char * )L"bass_spx.dll", BASS_UNICODE );
|
||||
|
||||
return;
|
||||
}
|
||||
FreeLibrary( hBass );
|
||||
hBass = 0;
|
||||
}
|
||||
|
||||
BassAudioPlayer::~BassAudioPlayer()
|
||||
{
|
||||
if( hBass )
|
||||
{
|
||||
if( currentHandle )
|
||||
fBASS_Stop();
|
||||
if ( spxPluginHandle )
|
||||
fBASS_PluginFree( spxPluginHandle );
|
||||
if( fBASS_Free )
|
||||
fBASS_Free();
|
||||
FreeLibrary( hBass );
|
||||
if( data )
|
||||
free( data );
|
||||
}
|
||||
}
|
||||
|
||||
BOOL BassAudioPlayer::playMemory( const void * ptr, size_t size, int * errorCodePtr )
|
||||
{
|
||||
Mutex::Lock _( mt );
|
||||
if( errorCodePtr )
|
||||
*errorCodePtr = -2;
|
||||
|
||||
if( ptr == 0 || size == 0 )
|
||||
return( false );
|
||||
if( !canBeUsed() )
|
||||
return false;
|
||||
|
||||
if( currentHandle )
|
||||
{
|
||||
fBASS_Stop();
|
||||
currentHandle = 0;
|
||||
}
|
||||
|
||||
fBASS_Free();
|
||||
|
||||
if( !fBASS_Init( -1, 44100, 0, hwnd, 0 ) )
|
||||
{
|
||||
if( errorCodePtr )
|
||||
*errorCodePtr = fBASS_ErrorGetCode();
|
||||
return false;
|
||||
}
|
||||
|
||||
if( data )
|
||||
free( data );
|
||||
if( ( data = malloc( size ) ) == 0 )
|
||||
return false;
|
||||
memcpy( data, ptr, size );
|
||||
|
||||
currentType = STREAM;
|
||||
currentHandle = fBASS_StreamCreateFile( TRUE, data, 0, size, BASS_STREAM_PRESCAN );
|
||||
if( currentHandle == 0 )
|
||||
{
|
||||
currentHandle = fBASS_MusicLoad( TRUE, data, 0, size, BASS_STREAM_PRESCAN, 0 );
|
||||
currentType = MUSIC;
|
||||
}
|
||||
|
||||
if( currentHandle )
|
||||
{
|
||||
bool res = fBASS_ChannelPlay( currentHandle, TRUE );
|
||||
if( !res && errorCodePtr != 0 )
|
||||
*errorCodePtr = fBASS_ErrorGetCode();
|
||||
return res;
|
||||
}
|
||||
|
||||
if( errorCodePtr )
|
||||
*errorCodePtr = fBASS_ErrorGetCode();
|
||||
|
||||
return false ;
|
||||
}
|
||||
|
||||
BassAudioPlayer & BassAudioPlayer::instance()
|
||||
{
|
||||
static BassAudioPlayer a;
|
||||
return a;
|
||||
}
|
||||
|
||||
const char * BassAudioPlayer::errorText( int errorCode )
|
||||
{
|
||||
switch( errorCode )
|
||||
{
|
||||
case 0: return( "BASS_OK" );
|
||||
case 1: return( "BASS_ERROR_MEM" );
|
||||
case 2: return( "BASS_ERROR_FILEOPEN" );
|
||||
case 3: return( "BASS_ERROR_DRIVER" );
|
||||
case 4: return( "BASS_ERROR_BUFLOST" );
|
||||
case 5: return( "BASS_ERROR_HANDLE" );
|
||||
case 6: return( "BASS_ERROR_FORMAT" );
|
||||
case 7: return( "BASS_ERROR_POSITION" );
|
||||
case 8: return( "BASS_ERROR_INIT" );
|
||||
case 9: return( "BASS_ERROR_START" );
|
||||
case 14: return( "BASS_ERROR_ALREADY" );
|
||||
case 18: return( "BASS_ERROR_NOCHAN" );
|
||||
case 19: return( "BASS_ERROR_ILLTYPE" );
|
||||
case 20: return( "BASS_ERROR_ILLPARAM" );
|
||||
case 21: return( "BASS_ERROR_NO3D" );
|
||||
case 22: return( "BASS_ERROR_NOEAX" );
|
||||
case 23: return( "BASS_ERROR_DEVICE" );
|
||||
case 24: return( "BASS_ERROR_NOPLAY" );
|
||||
case 25: return( "BASS_ERROR_FREQ" );
|
||||
case 27: return( "BASS_ERROR_NOTFILE" );
|
||||
case 29: return( "BASS_ERROR_NOHW" );
|
||||
case 31: return( "BASS_ERROR_EMPTY" );
|
||||
case 32: return( "BASS_ERROR_NONET" );
|
||||
case 33: return( "BASS_ERROR_CREATE" );
|
||||
case 34: return( "BASS_ERROR_NOFX" );
|
||||
case 37: return( "BASS_ERROR_NOTAVAIL");
|
||||
case 38: return( "BASS_ERROR_DECODE" );
|
||||
case 39: return( "BASS_ERROR_DX" );
|
||||
case 40: return( "BASS_ERROR_TIMEOUT" );
|
||||
case 41: return( "BASS_ERROR_FILEFORM" );
|
||||
case 42: return( "BASS_ERROR_SPEAKER" );
|
||||
case 43: return( "BASS_ERROR_VERSION" );
|
||||
case 44: return( "BASS_ERROR_CODEC" );
|
||||
case 45: return( "BASS_ERROR_ENDED" );
|
||||
case 46: return( "BASS_ERROR_BUSY" );
|
||||
case -1: return( "BASS_ERROR_UNKNOWN" );
|
||||
}
|
||||
return "Unknown error";
|
||||
}
|
||||
|
||||
#endif
|
||||
|
72
bass.hh
72
bass.hh
|
@ -1,72 +0,0 @@
|
|||
#ifndef __BASS_HH_INCLUDED__
|
||||
#define __BASS_HH_INCLUDED__
|
||||
|
||||
#ifdef __WIN32
|
||||
|
||||
#include <windows.h>
|
||||
#include "bass.h"
|
||||
#include "mutex.hh"
|
||||
|
||||
// Used bass.dll functions
|
||||
|
||||
class BassAudioPlayer
|
||||
{
|
||||
public:
|
||||
bool canBeUsed()
|
||||
{ return hBass != 0; }
|
||||
|
||||
BOOL playMemory( const void * ptr, size_t size, int *errorCodePtr = 0 );
|
||||
const char * errorText( int errorCode );
|
||||
void setMainWindow( HWND hwnd_ )
|
||||
{ hwnd = hwnd_; }
|
||||
|
||||
static BassAudioPlayer & instance();
|
||||
|
||||
private:
|
||||
|
||||
BassAudioPlayer();
|
||||
~BassAudioPlayer();
|
||||
|
||||
HMODULE hBass;
|
||||
|
||||
// Some bass.dll functions
|
||||
|
||||
typedef BOOL BASSDEF( ( *pBASS_Init ) )( int , DWORD, DWORD, HWND, GUID * );
|
||||
typedef BOOL BASSDEF( ( *pBASS_Free ) )();
|
||||
typedef BOOL BASSDEF( ( *pBASS_Stop ) )();
|
||||
typedef int BASSDEF( ( *pBASS_ErrorGetCode ) )();
|
||||
typedef HSTREAM BASSDEF( ( *pBASS_StreamCreateFile ) )( BOOL, void *, QWORD, QWORD, DWORD );
|
||||
typedef BOOL BASSDEF( ( *pBASS_StreamFree ) )( HSTREAM );
|
||||
typedef HMUSIC BASSDEF( ( *pBASS_MusicLoad ) )( BOOL, void *, QWORD, DWORD, DWORD, DWORD );
|
||||
typedef BOOL BASSDEF( ( *pBASS_MusicFree ) )( HMUSIC );
|
||||
typedef BOOL BASSDEF( ( *pBASS_ChannelPlay ) )( DWORD, BOOL );
|
||||
typedef BOOL BASSDEF( ( *pBASS_ChannelStop ) )( DWORD );
|
||||
typedef BOOL BASSDEF( ( *pBASS_PluginLoad ) )( const char *, DWORD );
|
||||
typedef BOOL BASSDEF( ( *pBASS_PluginFree ) )( HPLUGIN );
|
||||
|
||||
pBASS_Init fBASS_Init;
|
||||
pBASS_Free fBASS_Free;
|
||||
pBASS_Stop fBASS_Stop;
|
||||
pBASS_StreamCreateFile fBASS_StreamCreateFile;
|
||||
pBASS_ErrorGetCode fBASS_ErrorGetCode;
|
||||
pBASS_StreamFree fBASS_StreamFree;
|
||||
pBASS_MusicLoad fBASS_MusicLoad;
|
||||
pBASS_MusicFree fBASS_MusicFree;
|
||||
pBASS_ChannelPlay fBASS_ChannelPlay;
|
||||
pBASS_ChannelStop fBASS_ChannelStop;
|
||||
pBASS_PluginLoad fBASS_PluginLoad;
|
||||
pBASS_PluginFree fBASS_PluginFree;
|
||||
|
||||
DWORD currentHandle;
|
||||
void * data;
|
||||
HWND hwnd;
|
||||
HPLUGIN spxPluginHandle;
|
||||
|
||||
Mutex mt;
|
||||
|
||||
enum SoundType { STREAM, MUSIC } currentType;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif // __BASS_HH_INCLUDED__
|
23
config.cc
23
config.cc
|
@ -114,14 +114,8 @@ Preferences::Preferences():
|
|||
scanToMainWindow( false ),
|
||||
pronounceOnLoadMain( false ),
|
||||
pronounceOnLoadPopup( false ),
|
||||
#ifdef Q_WS_WIN
|
||||
useExternalPlayer( false ),
|
||||
useWindowsPlaySound( true ),
|
||||
#else
|
||||
useExternalPlayer( true ), // Phonon on Linux still feels quite buggy
|
||||
useWindowsPlaySound( false ),
|
||||
#endif
|
||||
useBassLibrary( false ),
|
||||
useInternalPlayer( true ),
|
||||
checkForNewReleases( true ),
|
||||
disallowContentFromOtherSites( false ),
|
||||
enableWebPlugins( false ),
|
||||
|
@ -688,11 +682,8 @@ Class load() throw( exError )
|
|||
if ( !preferences.namedItem( "useExternalPlayer" ).isNull() )
|
||||
c.preferences.useExternalPlayer = ( preferences.namedItem( "useExternalPlayer" ).toElement().text() == "1" );
|
||||
|
||||
if ( !preferences.namedItem( "useWindowsPlaySound" ).isNull() )
|
||||
c.preferences.useWindowsPlaySound = ( preferences.namedItem( "useWindowsPlaySound" ).toElement().text() == "1" );
|
||||
|
||||
if ( !preferences.namedItem( "useBassLibrary" ).isNull() )
|
||||
c.preferences.useBassLibrary = ( preferences.namedItem( "useBassLibrary" ).toElement().text() == "1" );
|
||||
if ( !preferences.namedItem( "useInternalPlayer" ).isNull() )
|
||||
c.preferences.useInternalPlayer = ( preferences.namedItem( "useInternalPlayer" ).toElement().text() == "1" );
|
||||
|
||||
if ( !preferences.namedItem( "audioPlaybackProgram" ).isNull() )
|
||||
c.preferences.audioPlaybackProgram = preferences.namedItem( "audioPlaybackProgram" ).toElement().text();
|
||||
|
@ -1370,12 +1361,8 @@ void save( Class const & c ) throw( exError )
|
|||
opt.appendChild( dd.createTextNode( c.preferences.useExternalPlayer ? "1" : "0" ) );
|
||||
preferences.appendChild( opt );
|
||||
|
||||
opt = dd.createElement( "useWindowsPlaySound" );
|
||||
opt.appendChild( dd.createTextNode( c.preferences.useWindowsPlaySound ? "1" : "0" ) );
|
||||
preferences.appendChild( opt );
|
||||
|
||||
opt = dd.createElement( "useBassLibrary" );
|
||||
opt.appendChild( dd.createTextNode( c.preferences.useBassLibrary ? "1" : "0" ) );
|
||||
opt = dd.createElement( "useInternalPlayer" );
|
||||
opt.appendChild( dd.createTextNode( c.preferences.useInternalPlayer ? "1" : "0" ) );
|
||||
preferences.appendChild( opt );
|
||||
|
||||
opt = dd.createElement( "audioPlaybackProgram" );
|
||||
|
|
|
@ -189,8 +189,7 @@ struct Preferences
|
|||
bool pronounceOnLoadMain, pronounceOnLoadPopup;
|
||||
QString audioPlaybackProgram;
|
||||
bool useExternalPlayer;
|
||||
bool useWindowsPlaySound;
|
||||
bool useBassLibrary;
|
||||
bool useInternalPlayer;
|
||||
|
||||
ProxyServer proxyServer;
|
||||
|
||||
|
|
537
ffmpegaudio.cc
Normal file
537
ffmpegaudio.cc
Normal file
|
@ -0,0 +1,537 @@
|
|||
#include "ffmpegaudio.hh"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#ifndef INT64_C
|
||||
#define INT64_C(c) (c ## LL)
|
||||
#endif
|
||||
|
||||
#ifndef UINT64_C
|
||||
#define UINT64_C(c) (c ## ULL)
|
||||
#endif
|
||||
|
||||
#include <ao/ao.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/avutil.h>
|
||||
}
|
||||
|
||||
#include <QString>
|
||||
#include <QDataStream>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using std::vector;
|
||||
|
||||
namespace Ffmpeg
|
||||
{
|
||||
|
||||
QMutex DecoderThread::deviceMutex_;
|
||||
|
||||
static inline QString avErrorString( int errnum )
|
||||
{
|
||||
char buf[64];
|
||||
av_strerror( errnum, buf, 64 );
|
||||
return QString::fromLatin1( buf );
|
||||
}
|
||||
|
||||
AudioPlayer & AudioPlayer::instance()
|
||||
{
|
||||
static AudioPlayer a;
|
||||
return a;
|
||||
}
|
||||
|
||||
AudioPlayer::AudioPlayer()
|
||||
{
|
||||
av_register_all();
|
||||
ao_initialize();
|
||||
}
|
||||
|
||||
AudioPlayer::~AudioPlayer()
|
||||
{
|
||||
emit cancelPlaying( true );
|
||||
ao_shutdown();
|
||||
}
|
||||
|
||||
void AudioPlayer::playMemory( const void * ptr, int size )
|
||||
{
|
||||
emit cancelPlaying( false );
|
||||
QByteArray audioData( ( char * )ptr, size );
|
||||
DecoderThread * thread = new DecoderThread( audioData, this );
|
||||
|
||||
connect( thread, SIGNAL( error( QString ) ), this, SIGNAL( error( QString ) ) );
|
||||
connect( this, SIGNAL( cancelPlaying( bool ) ), thread, SLOT( cancel( bool ) ), Qt::DirectConnection );
|
||||
connect( thread, SIGNAL( finished() ), thread, SLOT( deleteLater() ) );
|
||||
|
||||
thread->start();
|
||||
}
|
||||
|
||||
struct DecoderContext
|
||||
{
|
||||
enum
|
||||
{
|
||||
kBufferSize = 32768
|
||||
};
|
||||
|
||||
static QMutex deviceMutex_;
|
||||
QAtomicInt & isCancelled_;
|
||||
QByteArray audioData_;
|
||||
QDataStream audioDataStream_;
|
||||
AVFormatContext * formatContext_;
|
||||
AVCodecContext * codecContext_;
|
||||
AVIOContext * avioContext_;
|
||||
AVStream * audioStream_;
|
||||
ao_device * aoDevice_;
|
||||
bool avformatOpened_;
|
||||
|
||||
DecoderContext( QByteArray const & audioData, QAtomicInt & isCancelled );
|
||||
~DecoderContext();
|
||||
|
||||
bool openCodec( QString & errorString );
|
||||
void closeCodec();
|
||||
bool openOutputDevice( QString & errorString );
|
||||
void closeOutputDevice();
|
||||
bool play( QString & errorString );
|
||||
bool normalizeAudio( AVFrame * frame, vector<char> & samples );
|
||||
void playFrame( AVFrame * frame );
|
||||
};
|
||||
|
||||
DecoderContext::DecoderContext( QByteArray const & audioData, QAtomicInt & isCancelled ):
|
||||
isCancelled_( isCancelled ),
|
||||
audioData_( audioData ),
|
||||
audioDataStream_( audioData_ ),
|
||||
formatContext_( NULL ),
|
||||
codecContext_( NULL ),
|
||||
avioContext_( NULL ),
|
||||
audioStream_( NULL ),
|
||||
aoDevice_( NULL ),
|
||||
avformatOpened_( false )
|
||||
{
|
||||
}
|
||||
|
||||
DecoderContext::~DecoderContext()
|
||||
{
|
||||
closeOutputDevice();
|
||||
closeCodec();
|
||||
}
|
||||
|
||||
static int readAudioData( void * opaque, unsigned char * buffer, int bufferSize )
|
||||
{
|
||||
QDataStream * pStream = ( QDataStream * )opaque;
|
||||
return pStream->readRawData( ( char * )buffer, bufferSize );
|
||||
}
|
||||
|
||||
bool DecoderContext::openCodec( QString & errorString )
|
||||
{
|
||||
formatContext_ = avformat_alloc_context();
|
||||
if ( !formatContext_ )
|
||||
{
|
||||
errorString = "avformat_alloc_context() failed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char * avioBuffer = ( unsigned char * )av_malloc( kBufferSize + FF_INPUT_BUFFER_PADDING_SIZE );
|
||||
if ( !avioBuffer )
|
||||
{
|
||||
errorString = "av_malloc() failed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't free buffer allocated here (if succeeded), it will be cleaned up automatically.
|
||||
avioContext_ = avio_alloc_context( avioBuffer, kBufferSize, 0, &audioDataStream_, readAudioData, NULL, NULL );
|
||||
if ( !avioContext_ )
|
||||
{
|
||||
av_free( avioBuffer );
|
||||
errorString = "avio_alloc_context() failed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
avioContext_->seekable = 0;
|
||||
avioContext_->write_flag = 0;
|
||||
|
||||
// If pb not set, avformat_open_input() simply crash.
|
||||
formatContext_->pb = avioContext_;
|
||||
formatContext_->flags |= AVFMT_FLAG_CUSTOM_IO;
|
||||
|
||||
int ret = 0;
|
||||
avformatOpened_ = true;
|
||||
|
||||
ret = avformat_open_input( &formatContext_, "_STREAM_", NULL, NULL );
|
||||
if ( ret < 0 )
|
||||
{
|
||||
errorString = QString( "avformat_open_input() failed: %1." ).arg( avErrorString( ret ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = avformat_find_stream_info( formatContext_, NULL );
|
||||
if ( ret < 0 )
|
||||
{
|
||||
errorString = QString( "avformat_find_stream_info() failed: %1." ).arg( avErrorString( ret ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find audio stream, use the first audio stream if available
|
||||
for ( unsigned i = 0; i < formatContext_->nb_streams; i++ )
|
||||
{
|
||||
if ( formatContext_->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO )
|
||||
{
|
||||
audioStream_ = formatContext_->streams[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !audioStream_ )
|
||||
{
|
||||
errorString = "Could not find audio stream.";
|
||||
return false;
|
||||
}
|
||||
|
||||
codecContext_ = audioStream_->codec;
|
||||
AVCodec * codec = avcodec_find_decoder( codecContext_->codec_id );
|
||||
if ( !codec )
|
||||
{
|
||||
errorString = QString( "Codec [id: %d] not found." ).arg( codecContext_->codec_id );
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = avcodec_open2( codecContext_, codec, NULL );
|
||||
if ( ret < 0 )
|
||||
{
|
||||
errorString = QString( "avcodec_open2() failed: %1." ).arg( avErrorString( ret ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
av_log( NULL, AV_LOG_INFO, "Codec open: %s: channels: %d, rate: %d, format: %s\n", codec->long_name,
|
||||
codecContext_->channels, codecContext_->sample_rate, av_get_sample_fmt_name( codecContext_->sample_fmt ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
void DecoderContext::closeCodec()
|
||||
{
|
||||
if ( !formatContext_ )
|
||||
{
|
||||
if ( avioContext_ )
|
||||
{
|
||||
av_free( avioContext_->buffer );
|
||||
avioContext_ = NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// avformat_open_input() is not called, just free the buffer associated with
|
||||
// the AVIOContext, and the AVFormatContext
|
||||
if ( !avformatOpened_ )
|
||||
{
|
||||
if ( formatContext_ )
|
||||
{
|
||||
avformat_free_context( formatContext_ );
|
||||
formatContext_ = NULL;
|
||||
}
|
||||
|
||||
if ( avioContext_ )
|
||||
{
|
||||
av_free( avioContext_->buffer );
|
||||
avioContext_ = NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
avformatOpened_ = false;
|
||||
|
||||
// Closing a codec context without prior avcodec_open2() will result in
|
||||
// a crash in ffmpeg
|
||||
if ( audioStream_ && audioStream_->codec && audioStream_->codec->codec )
|
||||
{
|
||||
audioStream_->discard = AVDISCARD_ALL;
|
||||
avcodec_close( audioStream_->codec );
|
||||
}
|
||||
|
||||
avformat_close_input( &formatContext_ );
|
||||
av_free( avioContext_->buffer );
|
||||
}
|
||||
|
||||
bool DecoderContext::openOutputDevice( QString & errorString )
|
||||
{
|
||||
// Prepare for audio output
|
||||
int aoDriverId = ao_default_driver_id();
|
||||
if ( aoDriverId == -1 )
|
||||
{
|
||||
errorString = "Cannot find usable audio output device.";
|
||||
return false;
|
||||
}
|
||||
|
||||
ao_sample_format aoSampleFormat;
|
||||
aoSampleFormat.channels = codecContext_->channels;
|
||||
aoSampleFormat.rate = codecContext_->sample_rate;
|
||||
aoSampleFormat.byte_format = AO_FMT_NATIVE;
|
||||
aoSampleFormat.matrix = 0;
|
||||
aoSampleFormat.bits = qMin( 32, av_get_bytes_per_sample( codecContext_->sample_fmt ) << 3 );
|
||||
|
||||
if ( aoSampleFormat.bits == 0 )
|
||||
{
|
||||
errorString = "Unsupported sample format.";
|
||||
return false;
|
||||
}
|
||||
|
||||
aoDevice_ = ao_open_live( aoDriverId, &aoSampleFormat, NULL );
|
||||
if ( !aoDevice_ )
|
||||
{
|
||||
errorString = "ao_open_live() failed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
ao_info * aoDriverInfo = ao_driver_info( aoDriverId );
|
||||
if ( aoDriverInfo )
|
||||
{
|
||||
|
||||
av_log( NULL, AV_LOG_INFO, "ao_open_live(): %s: channels: %d, rate: %d, bits: %d\n", aoDriverInfo->name,
|
||||
aoSampleFormat.channels, aoSampleFormat.rate, aoSampleFormat.bits );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DecoderContext::closeOutputDevice()
|
||||
{
|
||||
// ao_close() is synchronous, it will wait until all audio streams flushed
|
||||
if ( aoDevice_ )
|
||||
{
|
||||
ao_close( aoDevice_ );
|
||||
aoDevice_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool DecoderContext::play( QString & errorString )
|
||||
{
|
||||
AVFrame * frame = avcodec_alloc_frame();
|
||||
if ( !frame )
|
||||
{
|
||||
errorString = "avcodec_alloc_frame() failed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
AVPacket packet;
|
||||
av_init_packet( &packet );
|
||||
|
||||
while ( !isCancelled_ && av_read_frame( formatContext_, &packet ) >= 0 )
|
||||
{
|
||||
if ( packet.stream_index == audioStream_->index )
|
||||
{
|
||||
int gotFrame = 0;
|
||||
avcodec_decode_audio4( codecContext_, frame, &gotFrame, &packet );
|
||||
if ( !isCancelled_ && gotFrame )
|
||||
{
|
||||
playFrame( frame );
|
||||
}
|
||||
}
|
||||
// av_free_packet() must be called after each call to av_read_frame()
|
||||
av_free_packet( &packet );
|
||||
}
|
||||
|
||||
if ( codecContext_->codec->capabilities & CODEC_CAP_DELAY )
|
||||
{
|
||||
av_init_packet( &packet );
|
||||
int gotFrame = 0;
|
||||
while ( avcodec_decode_audio4( codecContext_, frame, &gotFrame, &packet ) >= 0 && gotFrame )
|
||||
{
|
||||
if ( isCancelled_ )
|
||||
break;
|
||||
playFrame( frame );
|
||||
}
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_MAJOR < 54
|
||||
av_free( frame );
|
||||
#else
|
||||
avcodec_free_frame( &frame );
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline int32_t toInt32( double v )
|
||||
{
|
||||
if ( v >= 1.0 )
|
||||
return 0x7fffffffL;
|
||||
else if ( v <= -1.0 )
|
||||
return 0x80000000L;
|
||||
return floor( v * 2147483648.0 );
|
||||
}
|
||||
|
||||
bool DecoderContext::normalizeAudio( AVFrame * frame, vector<char> & samples )
|
||||
{
|
||||
int lineSize = 0;
|
||||
int dataSize = av_samples_get_buffer_size( &lineSize, codecContext_->channels,
|
||||
frame->nb_samples, codecContext_->sample_fmt, 1 );
|
||||
|
||||
// Portions from: https://code.google.com/p/lavfilters/source/browse/decoder/LAVAudio/LAVAudio.cpp
|
||||
// But this one use 8, 16, 32 bits integer, respectively.
|
||||
switch ( codecContext_->sample_fmt )
|
||||
{
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
{
|
||||
samples.resize( dataSize );
|
||||
memcpy( &samples.front(), frame->extended_data[0], lineSize );
|
||||
}
|
||||
break;
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
{
|
||||
samples.resize( dataSize );
|
||||
|
||||
int32_t * out = ( int32_t * )&samples.front();
|
||||
for ( int i = 0; i < dataSize; i += sizeof( float ) )
|
||||
{
|
||||
*out++ = toInt32( *( float * )frame->extended_data[i] );
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AV_SAMPLE_FMT_DBL:
|
||||
{
|
||||
samples.resize( dataSize / 2 );
|
||||
|
||||
int32_t * out = ( int32_t * )&samples.front();
|
||||
for ( int i = 0; i < dataSize; i += sizeof( double ) )
|
||||
{
|
||||
*out++ = toInt32( *( double * )frame->extended_data[i] );
|
||||
}
|
||||
}
|
||||
break;
|
||||
// Planar
|
||||
case AV_SAMPLE_FMT_U8P:
|
||||
{
|
||||
samples.resize( dataSize );
|
||||
|
||||
uint8_t * out = ( uint8_t * )&samples.front();
|
||||
for ( int i = 0; i < frame->nb_samples; i++ )
|
||||
{
|
||||
for ( int ch = 0; ch < codecContext_->channels; ch++ )
|
||||
{
|
||||
*out++ = ( ( uint8_t * )frame->extended_data[ch] )[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S16P:
|
||||
{
|
||||
samples.resize( dataSize );
|
||||
|
||||
int16_t * out = ( int16_t * )&samples.front();
|
||||
for ( int i = 0; i < frame->nb_samples; i++ )
|
||||
{
|
||||
for ( int ch = 0; ch < codecContext_->channels; ch++ )
|
||||
{
|
||||
*out++ = ( ( int16_t * )frame->extended_data[ch] )[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S32P:
|
||||
{
|
||||
samples.resize( dataSize );
|
||||
|
||||
int32_t * out = ( int32_t * )&samples.front();
|
||||
for ( int i = 0; i < frame->nb_samples; i++ )
|
||||
{
|
||||
for ( int ch = 0; ch < codecContext_->channels; ch++ )
|
||||
{
|
||||
*out++ = ( ( int32_t * )frame->extended_data[ch] )[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AV_SAMPLE_FMT_FLTP:
|
||||
{
|
||||
samples.resize( dataSize );
|
||||
|
||||
float ** data = ( float ** )frame->extended_data;
|
||||
int32_t * out = ( int32_t * )&samples.front();
|
||||
for ( int i = 0; i < frame->nb_samples; i++ )
|
||||
{
|
||||
for ( int ch = 0; ch < codecContext_->channels; ch++ )
|
||||
{
|
||||
*out++ = toInt32( data[ch][i] );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AV_SAMPLE_FMT_DBLP:
|
||||
{
|
||||
samples.resize( dataSize / 2 );
|
||||
|
||||
double ** data = ( double ** )frame->extended_data;
|
||||
int32_t * out = ( int32_t * )&samples.front();
|
||||
for ( int i = 0; i < frame->nb_samples; i++ )
|
||||
{
|
||||
for ( int ch = 0; ch < codecContext_->channels; ch++ )
|
||||
{
|
||||
*out++ = toInt32( data[ch][i] );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DecoderContext::playFrame( AVFrame * frame )
|
||||
{
|
||||
if ( !frame )
|
||||
return;
|
||||
|
||||
vector<char> samples;
|
||||
if ( normalizeAudio( frame, samples ) )
|
||||
ao_play( aoDevice_, &samples.front(), samples.size() );
|
||||
}
|
||||
|
||||
DecoderThread::DecoderThread( QByteArray const & audioData, QObject * parent ) :
|
||||
QThread( parent ),
|
||||
isCancelled_( 0 ),
|
||||
audioData_( audioData )
|
||||
{
|
||||
}
|
||||
|
||||
DecoderThread::~DecoderThread()
|
||||
{
|
||||
isCancelled_.ref();
|
||||
}
|
||||
|
||||
void DecoderThread::run()
|
||||
{
|
||||
QString errorString;
|
||||
DecoderContext d( audioData_, isCancelled_ );
|
||||
|
||||
if ( !d.openCodec( errorString ) )
|
||||
{
|
||||
emit error( errorString );
|
||||
return;
|
||||
}
|
||||
|
||||
while ( !deviceMutex_.tryLock( 100 ) )
|
||||
{
|
||||
if ( isCancelled_ )
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !d.openOutputDevice( errorString ) )
|
||||
emit error( errorString );
|
||||
else if ( !d.play( errorString ) )
|
||||
emit error( errorString );
|
||||
|
||||
d.closeOutputDevice();
|
||||
deviceMutex_.unlock();
|
||||
}
|
||||
|
||||
void DecoderThread::cancel( bool waitUntilFinished )
|
||||
{
|
||||
isCancelled_.ref();
|
||||
if ( waitUntilFinished )
|
||||
this->wait();
|
||||
}
|
||||
|
||||
}
|
54
ffmpegaudio.hh
Normal file
54
ffmpegaudio.hh
Normal file
|
@ -0,0 +1,54 @@
|
|||
#ifndef __FFMPEGAUDIO_HH_INCLUDED__
|
||||
#define __FFMPEGAUDIO_HH_INCLUDED__
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
#include <QAtomicInt>
|
||||
#include <QByteArray>
|
||||
#include <QThread>
|
||||
|
||||
namespace Ffmpeg
|
||||
{
|
||||
|
||||
class AudioPlayer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static AudioPlayer & instance();
|
||||
void playMemory( const void * ptr, int size );
|
||||
|
||||
signals:
|
||||
void cancelPlaying( bool waitUntilFinished );
|
||||
void error( QString const & message );
|
||||
|
||||
private:
|
||||
AudioPlayer();
|
||||
~AudioPlayer();
|
||||
AudioPlayer( AudioPlayer const & );
|
||||
AudioPlayer & operator=( AudioPlayer const & );
|
||||
};
|
||||
|
||||
class DecoderThread: public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
static QMutex deviceMutex_;
|
||||
QAtomicInt isCancelled_;
|
||||
QByteArray audioData_;
|
||||
|
||||
public:
|
||||
DecoderThread( QByteArray const & audioData, QObject * parent );
|
||||
virtual ~DecoderThread();
|
||||
|
||||
public slots:
|
||||
void run();
|
||||
void cancel( bool waitUntilFinished );
|
||||
|
||||
signals:
|
||||
void error( QString const & message );
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // __FFMPEGAUDIO_HH_INCLUDED__
|
|
@ -54,6 +54,7 @@ bool isNameOfSound( string const & name )
|
|||
endsWith( s, ".ogg" ) ||
|
||||
endsWith( s, ".mp3" ) ||
|
||||
endsWith( s, ".mp4" ) ||
|
||||
endsWith( s, ".m4a") ||
|
||||
endsWith( s, ".aac" ) ||
|
||||
endsWith( s, ".flac" ) ||
|
||||
endsWith( s, ".mid" ) ||
|
||||
|
|
|
@ -22,7 +22,6 @@ INCLUDEPATH += .
|
|||
QT += webkit
|
||||
QT += xml
|
||||
QT += network
|
||||
QT += phonon
|
||||
CONFIG += exceptions \
|
||||
rtti \
|
||||
stl
|
||||
|
@ -47,7 +46,11 @@ win32 {
|
|||
LIBS += -lvorbisfile \
|
||||
-lvorbis \
|
||||
-logg \
|
||||
-lhunspell-1.3.2
|
||||
-lhunspell-1.3.2 \
|
||||
-lao \
|
||||
-lavutil-gd \
|
||||
-lavformat-gd \
|
||||
-lavcodec-gd
|
||||
RC_FILE = goldendict.rc
|
||||
INCLUDEPATH += winlibs/include
|
||||
LIBS += -L$${PWD}/winlibs/lib
|
||||
|
@ -70,7 +73,11 @@ unix:!mac {
|
|||
PKGCONFIG += vorbisfile \
|
||||
vorbis \
|
||||
ogg \
|
||||
hunspell
|
||||
hunspell \
|
||||
ao \
|
||||
libavutil \
|
||||
libavformat \
|
||||
libavcodec
|
||||
arm {
|
||||
LIBS += -liconv
|
||||
} else {
|
||||
|
@ -222,7 +229,8 @@ HEADERS += folding.hh \
|
|||
wordlist.hh \
|
||||
mdictparser.hh \
|
||||
mdx.hh \
|
||||
voiceengines.hh
|
||||
voiceengines.hh \
|
||||
ffmpegaudio.hh
|
||||
|
||||
FORMS += groups.ui \
|
||||
dictgroupwidget.ui \
|
||||
|
@ -328,7 +336,8 @@ SOURCES += folding.cc \
|
|||
wordlist.cc \
|
||||
mdictparser.cc \
|
||||
mdx.cc \
|
||||
voiceengines.cc
|
||||
voiceengines.cc \
|
||||
ffmpegaudio.cc
|
||||
|
||||
win32 {
|
||||
FORMS += texttospeechsource.ui
|
||||
|
@ -336,7 +345,6 @@ win32 {
|
|||
wordbyauto.cc \
|
||||
guids.c \
|
||||
x64.cc \
|
||||
bass.cc \
|
||||
speechclient_win.cc \
|
||||
texttospeechsource.cc \
|
||||
speechhlp.cc
|
||||
|
@ -344,7 +352,6 @@ win32 {
|
|||
wordbyauto.hh \
|
||||
uiauto.hh \
|
||||
x64.hh \
|
||||
bass.hh \
|
||||
texttospeechsource.hh \
|
||||
sapi.hh \
|
||||
sphelper.hh \
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
#include "fsencoding.hh"
|
||||
#include <QProcess>
|
||||
#include "historypanewidget.hh"
|
||||
#include "bass.hh"
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include "lionsupport.h"
|
||||
|
@ -744,7 +743,6 @@ MainWindow::MainWindow( Config::Class & cfg_ ):
|
|||
#endif
|
||||
#ifdef Q_OS_WIN32
|
||||
gdAskMessage = RegisterWindowMessage( GD_MESSAGE_NAME );
|
||||
BassAudioPlayer::instance().setMainWindow( winId() );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -1967,12 +1965,6 @@ bool MainWindow::handleBackForwardMouseButtons ( QMouseEvent * event) {
|
|||
|
||||
bool MainWindow::eventFilter( QObject * obj, QEvent * ev )
|
||||
{
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
if( ev->type() == QEvent::WinIdChange )
|
||||
BassAudioPlayer::instance().setMainWindow( this->internalWinId() );
|
||||
#endif
|
||||
|
||||
if ( ev->type() == QEvent::ShortcutOverride ) {
|
||||
// Handle Ctrl+H to show the History Pane.
|
||||
QKeyEvent * ke = static_cast<QKeyEvent*>( ev );
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "langcoder.hh"
|
||||
#include <QMessageBox>
|
||||
#include "broken_xrecord.hh"
|
||||
#include "bass.hh"
|
||||
|
||||
|
||||
Preferences::Preferences( QWidget * parent, Config::Preferences const & p ):
|
||||
QDialog( parent ), prevInterfaceLanguage( 0 )
|
||||
|
@ -157,28 +157,14 @@ Preferences::Preferences( QWidget * parent, Config::Preferences const & p ):
|
|||
|
||||
// Sound
|
||||
|
||||
#ifdef Q_WS_WIN
|
||||
// Since there's only one Phonon backend under Windows, be more precise
|
||||
ui.playViaPhonon->setText( tr( "Play via DirectShow" ) );
|
||||
ui.playViaBass->setEnabled( BassAudioPlayer::instance().canBeUsed() );
|
||||
#else
|
||||
// This setting is Windows-specific
|
||||
ui.useWindowsPlaySound->hide();
|
||||
ui.playViaBass->hide();
|
||||
#endif
|
||||
|
||||
ui.pronounceOnLoadMain->setChecked( p.pronounceOnLoadMain );
|
||||
ui.pronounceOnLoadPopup->setChecked( p.pronounceOnLoadPopup );
|
||||
|
||||
if ( p.useInternalPlayer )
|
||||
ui.useInternalPlayer->setChecked( true );
|
||||
else
|
||||
ui.useExternalPlayer->setChecked( p.useExternalPlayer );
|
||||
|
||||
#ifdef Q_WS_WIN
|
||||
if ( p.useWindowsPlaySound && !p.useExternalPlayer )
|
||||
ui.useWindowsPlaySound->setChecked( true );
|
||||
else if( p.useBassLibrary && !p.useExternalPlayer && BassAudioPlayer::instance().canBeUsed() )
|
||||
ui.playViaBass->setChecked( true );
|
||||
#endif
|
||||
|
||||
ui.audioPlaybackProgram->setText( p.audioPlaybackProgram );
|
||||
|
||||
if ( !isRECORDBroken() )
|
||||
|
@ -268,10 +254,7 @@ Config::Preferences Preferences::getPreferences()
|
|||
p.pronounceOnLoadMain = ui.pronounceOnLoadMain->isChecked();
|
||||
p.pronounceOnLoadPopup = ui.pronounceOnLoadPopup->isChecked();
|
||||
p.useExternalPlayer = ui.useExternalPlayer->isChecked();
|
||||
#ifdef Q_WS_WIN
|
||||
p.useWindowsPlaySound = ui.useWindowsPlaySound->isChecked();
|
||||
p.useBassLibrary = ui.playViaBass->isChecked();
|
||||
#endif
|
||||
p.useInternalPlayer = ui.useInternalPlayer->isChecked();
|
||||
p.audioPlaybackProgram = ui.audioPlaybackProgram->text();
|
||||
|
||||
p.proxyServer.enabled = ui.useProxyServer->isChecked();
|
||||
|
|
|
@ -798,38 +798,12 @@ p, li { white-space: pre-wrap; }
|
|||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_16">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="useWindowsPlaySound">
|
||||
<widget class="QRadioButton" name="useInternalPlayer">
|
||||
<property name="toolTip">
|
||||
<string>Use Windows native playback API. Limited to .wav files only,
|
||||
but works very well.</string>
|
||||
<string>Play audio files via FFmpeg(libav) and libao</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Play via Windows native API</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="playViaPhonon">
|
||||
<property name="toolTip">
|
||||
<string>Play audio via Phonon framework. May be somewhat unstable,
|
||||
but should support most audio file formats.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Play via Phonon</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="playViaBass">
|
||||
<property name="toolTip">
|
||||
<string>Play audio via Bass library. Optimal choice. To use this mode
|
||||
you must place bass.dll (http://www.un4seen.com) into GoldenDict folder.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Play via Bass library</string>
|
||||
<string>Use internal player</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
Loading…
Reference in a new issue