mirror of
https://github.com/xiaoyifang/goldendict-ng.git
synced 2024-11-27 19: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 \
|
sudo apt-get install pkg-config build-essential qt4-qmake \
|
||||||
libvorbis-dev zlib1g-dev libhunspell-dev x11proto-record-dev \
|
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
|
## How to build
|
||||||
|
|
||||||
|
|
119
articleview.cc
119
articleview.cc
|
@ -17,15 +17,13 @@
|
||||||
#include "webmultimediadownload.hh"
|
#include "webmultimediadownload.hh"
|
||||||
#include "programs.hh"
|
#include "programs.hh"
|
||||||
#include "dprintf.hh"
|
#include "dprintf.hh"
|
||||||
|
#include "ffmpegaudio.hh"
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QWebElement>
|
#include <QWebElement>
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <mmsystem.h> // For PlaySound
|
|
||||||
#include "bass.hh"
|
|
||||||
|
|
||||||
#include "speechclient.hh"
|
#include "speechclient.hh"
|
||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
@ -33,45 +31,9 @@
|
||||||
|
|
||||||
#include <QBuffer>
|
#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::map;
|
||||||
using std::list;
|
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,
|
ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm,
|
||||||
std::vector< sptr< Dictionary::Class > > const & allDictionaries_,
|
std::vector< sptr< Dictionary::Class > > const & allDictionaries_,
|
||||||
|
@ -202,15 +164,6 @@ void ArticleView::setGroupComboBox( GroupComboBox const * g )
|
||||||
ArticleView::~ArticleView()
|
ArticleView::~ArticleView()
|
||||||
{
|
{
|
||||||
cleanupTemp();
|
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,
|
void ArticleView::showDefinition( QString const & word, unsigned group,
|
||||||
|
@ -1581,70 +1534,14 @@ void ArticleView::resourceDownloadFinished()
|
||||||
Dictionary::WebMultimediaDownload::isAudioUrl( resourceDownloadUrl ) )
|
Dictionary::WebMultimediaDownload::isAudioUrl( resourceDownloadUrl ) )
|
||||||
{
|
{
|
||||||
// Audio data
|
// Audio data
|
||||||
|
if ( cfg.preferences.useInternalPlayer )
|
||||||
#ifdef Q_OS_WIN32
|
|
||||||
// If we use Windows PlaySound, use that, not Phonon.
|
|
||||||
if ( !cfg.preferences.useExternalPlayer &&
|
|
||||||
cfg.preferences.useWindowsPlaySound )
|
|
||||||
{
|
{
|
||||||
// Stop any currently playing sound to make sure the previous data
|
Ffmpeg::AudioPlayer & player = Ffmpeg::AudioPlayer::instance();
|
||||||
// isn't used anymore
|
connect( &player, SIGNAL( error( QString ) ), this, SLOT( audioPlayerError( QString ) ), Qt::UniqueConnection );
|
||||||
if ( winWavData.size() )
|
player.playMemory( data.data(), data.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." ) );
|
|
||||||
}
|
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
// Use external viewer to play the file
|
// Use external viewer to play the file
|
||||||
try
|
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()
|
void ArticleView::pasteTriggered()
|
||||||
{
|
{
|
||||||
QString text =
|
QString text =
|
||||||
|
|
|
@ -34,11 +34,6 @@ class ArticleView: public QFrame
|
||||||
QString articleToJump;
|
QString articleToJump;
|
||||||
QString soundScript;
|
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.
|
/// 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
|
/// Full vector capacity is used for search requests, where we have to make
|
||||||
/// a multitude of requests.
|
/// a multitude of requests.
|
||||||
|
@ -265,6 +260,9 @@ private slots:
|
||||||
/// Handles the double-click from the definition.
|
/// Handles the double-click from the definition.
|
||||||
void doubleClicked();
|
void doubleClicked();
|
||||||
|
|
||||||
|
/// Handles audio player error message
|
||||||
|
void audioPlayerError( QString const & message );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/// Deduces group from the url. If there doesn't seem to be any group,
|
/// 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 ),
|
scanToMainWindow( false ),
|
||||||
pronounceOnLoadMain( false ),
|
pronounceOnLoadMain( false ),
|
||||||
pronounceOnLoadPopup( false ),
|
pronounceOnLoadPopup( false ),
|
||||||
#ifdef Q_WS_WIN
|
|
||||||
useExternalPlayer( false ),
|
useExternalPlayer( false ),
|
||||||
useWindowsPlaySound( true ),
|
useInternalPlayer( true ),
|
||||||
#else
|
|
||||||
useExternalPlayer( true ), // Phonon on Linux still feels quite buggy
|
|
||||||
useWindowsPlaySound( false ),
|
|
||||||
#endif
|
|
||||||
useBassLibrary( false ),
|
|
||||||
checkForNewReleases( true ),
|
checkForNewReleases( true ),
|
||||||
disallowContentFromOtherSites( false ),
|
disallowContentFromOtherSites( false ),
|
||||||
enableWebPlugins( false ),
|
enableWebPlugins( false ),
|
||||||
|
@ -688,11 +682,8 @@ Class load() throw( exError )
|
||||||
if ( !preferences.namedItem( "useExternalPlayer" ).isNull() )
|
if ( !preferences.namedItem( "useExternalPlayer" ).isNull() )
|
||||||
c.preferences.useExternalPlayer = ( preferences.namedItem( "useExternalPlayer" ).toElement().text() == "1" );
|
c.preferences.useExternalPlayer = ( preferences.namedItem( "useExternalPlayer" ).toElement().text() == "1" );
|
||||||
|
|
||||||
if ( !preferences.namedItem( "useWindowsPlaySound" ).isNull() )
|
if ( !preferences.namedItem( "useInternalPlayer" ).isNull() )
|
||||||
c.preferences.useWindowsPlaySound = ( preferences.namedItem( "useWindowsPlaySound" ).toElement().text() == "1" );
|
c.preferences.useInternalPlayer = ( preferences.namedItem( "useInternalPlayer" ).toElement().text() == "1" );
|
||||||
|
|
||||||
if ( !preferences.namedItem( "useBassLibrary" ).isNull() )
|
|
||||||
c.preferences.useBassLibrary = ( preferences.namedItem( "useBassLibrary" ).toElement().text() == "1" );
|
|
||||||
|
|
||||||
if ( !preferences.namedItem( "audioPlaybackProgram" ).isNull() )
|
if ( !preferences.namedItem( "audioPlaybackProgram" ).isNull() )
|
||||||
c.preferences.audioPlaybackProgram = preferences.namedItem( "audioPlaybackProgram" ).toElement().text();
|
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" ) );
|
opt.appendChild( dd.createTextNode( c.preferences.useExternalPlayer ? "1" : "0" ) );
|
||||||
preferences.appendChild( opt );
|
preferences.appendChild( opt );
|
||||||
|
|
||||||
opt = dd.createElement( "useWindowsPlaySound" );
|
opt = dd.createElement( "useInternalPlayer" );
|
||||||
opt.appendChild( dd.createTextNode( c.preferences.useWindowsPlaySound ? "1" : "0" ) );
|
opt.appendChild( dd.createTextNode( c.preferences.useInternalPlayer ? "1" : "0" ) );
|
||||||
preferences.appendChild( opt );
|
|
||||||
|
|
||||||
opt = dd.createElement( "useBassLibrary" );
|
|
||||||
opt.appendChild( dd.createTextNode( c.preferences.useBassLibrary ? "1" : "0" ) );
|
|
||||||
preferences.appendChild( opt );
|
preferences.appendChild( opt );
|
||||||
|
|
||||||
opt = dd.createElement( "audioPlaybackProgram" );
|
opt = dd.createElement( "audioPlaybackProgram" );
|
||||||
|
|
|
@ -189,8 +189,7 @@ struct Preferences
|
||||||
bool pronounceOnLoadMain, pronounceOnLoadPopup;
|
bool pronounceOnLoadMain, pronounceOnLoadPopup;
|
||||||
QString audioPlaybackProgram;
|
QString audioPlaybackProgram;
|
||||||
bool useExternalPlayer;
|
bool useExternalPlayer;
|
||||||
bool useWindowsPlaySound;
|
bool useInternalPlayer;
|
||||||
bool useBassLibrary;
|
|
||||||
|
|
||||||
ProxyServer proxyServer;
|
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, ".ogg" ) ||
|
||||||
endsWith( s, ".mp3" ) ||
|
endsWith( s, ".mp3" ) ||
|
||||||
endsWith( s, ".mp4" ) ||
|
endsWith( s, ".mp4" ) ||
|
||||||
|
endsWith( s, ".m4a") ||
|
||||||
endsWith( s, ".aac" ) ||
|
endsWith( s, ".aac" ) ||
|
||||||
endsWith( s, ".flac" ) ||
|
endsWith( s, ".flac" ) ||
|
||||||
endsWith( s, ".mid" ) ||
|
endsWith( s, ".mid" ) ||
|
||||||
|
|
|
@ -22,7 +22,6 @@ INCLUDEPATH += .
|
||||||
QT += webkit
|
QT += webkit
|
||||||
QT += xml
|
QT += xml
|
||||||
QT += network
|
QT += network
|
||||||
QT += phonon
|
|
||||||
CONFIG += exceptions \
|
CONFIG += exceptions \
|
||||||
rtti \
|
rtti \
|
||||||
stl
|
stl
|
||||||
|
@ -47,7 +46,11 @@ win32 {
|
||||||
LIBS += -lvorbisfile \
|
LIBS += -lvorbisfile \
|
||||||
-lvorbis \
|
-lvorbis \
|
||||||
-logg \
|
-logg \
|
||||||
-lhunspell-1.3.2
|
-lhunspell-1.3.2 \
|
||||||
|
-lao \
|
||||||
|
-lavutil-gd \
|
||||||
|
-lavformat-gd \
|
||||||
|
-lavcodec-gd
|
||||||
RC_FILE = goldendict.rc
|
RC_FILE = goldendict.rc
|
||||||
INCLUDEPATH += winlibs/include
|
INCLUDEPATH += winlibs/include
|
||||||
LIBS += -L$${PWD}/winlibs/lib
|
LIBS += -L$${PWD}/winlibs/lib
|
||||||
|
@ -69,8 +72,12 @@ unix:!mac {
|
||||||
CONFIG += link_pkgconfig
|
CONFIG += link_pkgconfig
|
||||||
PKGCONFIG += vorbisfile \
|
PKGCONFIG += vorbisfile \
|
||||||
vorbis \
|
vorbis \
|
||||||
ogg \
|
ogg \
|
||||||
hunspell
|
hunspell \
|
||||||
|
ao \
|
||||||
|
libavutil \
|
||||||
|
libavformat \
|
||||||
|
libavcodec
|
||||||
arm {
|
arm {
|
||||||
LIBS += -liconv
|
LIBS += -liconv
|
||||||
} else {
|
} else {
|
||||||
|
@ -222,7 +229,8 @@ HEADERS += folding.hh \
|
||||||
wordlist.hh \
|
wordlist.hh \
|
||||||
mdictparser.hh \
|
mdictparser.hh \
|
||||||
mdx.hh \
|
mdx.hh \
|
||||||
voiceengines.hh
|
voiceengines.hh \
|
||||||
|
ffmpegaudio.hh
|
||||||
|
|
||||||
FORMS += groups.ui \
|
FORMS += groups.ui \
|
||||||
dictgroupwidget.ui \
|
dictgroupwidget.ui \
|
||||||
|
@ -328,7 +336,8 @@ SOURCES += folding.cc \
|
||||||
wordlist.cc \
|
wordlist.cc \
|
||||||
mdictparser.cc \
|
mdictparser.cc \
|
||||||
mdx.cc \
|
mdx.cc \
|
||||||
voiceengines.cc
|
voiceengines.cc \
|
||||||
|
ffmpegaudio.cc
|
||||||
|
|
||||||
win32 {
|
win32 {
|
||||||
FORMS += texttospeechsource.ui
|
FORMS += texttospeechsource.ui
|
||||||
|
@ -336,7 +345,6 @@ win32 {
|
||||||
wordbyauto.cc \
|
wordbyauto.cc \
|
||||||
guids.c \
|
guids.c \
|
||||||
x64.cc \
|
x64.cc \
|
||||||
bass.cc \
|
|
||||||
speechclient_win.cc \
|
speechclient_win.cc \
|
||||||
texttospeechsource.cc \
|
texttospeechsource.cc \
|
||||||
speechhlp.cc
|
speechhlp.cc
|
||||||
|
@ -344,7 +352,6 @@ win32 {
|
||||||
wordbyauto.hh \
|
wordbyauto.hh \
|
||||||
uiauto.hh \
|
uiauto.hh \
|
||||||
x64.hh \
|
x64.hh \
|
||||||
bass.hh \
|
|
||||||
texttospeechsource.hh \
|
texttospeechsource.hh \
|
||||||
sapi.hh \
|
sapi.hh \
|
||||||
sphelper.hh \
|
sphelper.hh \
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
#include "fsencoding.hh"
|
#include "fsencoding.hh"
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include "historypanewidget.hh"
|
#include "historypanewidget.hh"
|
||||||
#include "bass.hh"
|
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
#include "lionsupport.h"
|
#include "lionsupport.h"
|
||||||
|
@ -744,7 +743,6 @@ MainWindow::MainWindow( Config::Class & cfg_ ):
|
||||||
#endif
|
#endif
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
gdAskMessage = RegisterWindowMessage( GD_MESSAGE_NAME );
|
gdAskMessage = RegisterWindowMessage( GD_MESSAGE_NAME );
|
||||||
BassAudioPlayer::instance().setMainWindow( winId() );
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1967,12 +1965,6 @@ bool MainWindow::handleBackForwardMouseButtons ( QMouseEvent * event) {
|
||||||
|
|
||||||
bool MainWindow::eventFilter( QObject * obj, QEvent * ev )
|
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 ) {
|
if ( ev->type() == QEvent::ShortcutOverride ) {
|
||||||
// Handle Ctrl+H to show the History Pane.
|
// Handle Ctrl+H to show the History Pane.
|
||||||
QKeyEvent * ke = static_cast<QKeyEvent*>( ev );
|
QKeyEvent * ke = static_cast<QKeyEvent*>( ev );
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#include "langcoder.hh"
|
#include "langcoder.hh"
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include "broken_xrecord.hh"
|
#include "broken_xrecord.hh"
|
||||||
#include "bass.hh"
|
|
||||||
|
|
||||||
Preferences::Preferences( QWidget * parent, Config::Preferences const & p ):
|
Preferences::Preferences( QWidget * parent, Config::Preferences const & p ):
|
||||||
QDialog( parent ), prevInterfaceLanguage( 0 )
|
QDialog( parent ), prevInterfaceLanguage( 0 )
|
||||||
|
@ -157,27 +157,13 @@ Preferences::Preferences( QWidget * parent, Config::Preferences const & p ):
|
||||||
|
|
||||||
// Sound
|
// 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.pronounceOnLoadMain->setChecked( p.pronounceOnLoadMain );
|
||||||
ui.pronounceOnLoadPopup->setChecked( p.pronounceOnLoadPopup );
|
ui.pronounceOnLoadPopup->setChecked( p.pronounceOnLoadPopup );
|
||||||
|
|
||||||
ui.useExternalPlayer->setChecked( p.useExternalPlayer );
|
if ( p.useInternalPlayer )
|
||||||
|
ui.useInternalPlayer->setChecked( true );
|
||||||
#ifdef Q_WS_WIN
|
else
|
||||||
if ( p.useWindowsPlaySound && !p.useExternalPlayer )
|
ui.useExternalPlayer->setChecked( 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 );
|
ui.audioPlaybackProgram->setText( p.audioPlaybackProgram );
|
||||||
|
|
||||||
|
@ -268,10 +254,7 @@ Config::Preferences Preferences::getPreferences()
|
||||||
p.pronounceOnLoadMain = ui.pronounceOnLoadMain->isChecked();
|
p.pronounceOnLoadMain = ui.pronounceOnLoadMain->isChecked();
|
||||||
p.pronounceOnLoadPopup = ui.pronounceOnLoadPopup->isChecked();
|
p.pronounceOnLoadPopup = ui.pronounceOnLoadPopup->isChecked();
|
||||||
p.useExternalPlayer = ui.useExternalPlayer->isChecked();
|
p.useExternalPlayer = ui.useExternalPlayer->isChecked();
|
||||||
#ifdef Q_WS_WIN
|
p.useInternalPlayer = ui.useInternalPlayer->isChecked();
|
||||||
p.useWindowsPlaySound = ui.useWindowsPlaySound->isChecked();
|
|
||||||
p.useBassLibrary = ui.playViaBass->isChecked();
|
|
||||||
#endif
|
|
||||||
p.audioPlaybackProgram = ui.audioPlaybackProgram->text();
|
p.audioPlaybackProgram = ui.audioPlaybackProgram->text();
|
||||||
|
|
||||||
p.proxyServer.enabled = ui.useProxyServer->isChecked();
|
p.proxyServer.enabled = ui.useProxyServer->isChecked();
|
||||||
|
|
|
@ -798,38 +798,12 @@ p, li { white-space: pre-wrap; }
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_16">
|
<layout class="QVBoxLayout" name="verticalLayout_16">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QRadioButton" name="useWindowsPlaySound">
|
<widget class="QRadioButton" name="useInternalPlayer">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Use Windows native playback API. Limited to .wav files only,
|
<string>Play audio files via FFmpeg(libav) and libao</string>
|
||||||
but works very well.</string>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Play via Windows native API</string>
|
<string>Use internal player</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>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
Loading…
Reference in a new issue