Add internal audio player(ffmpeg/libav + libao).

* phonon, bass, playsound are removed.
This commit is contained in:
Timon Wong 2013-05-05 18:22:12 +08:00
parent 13654324d1
commit c4752eb14c
14 changed files with 638 additions and 475 deletions

View file

@ -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

View file

@ -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 =

View file

@ -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
View file

@ -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
View file

@ -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__

View file

@ -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" );

View file

@ -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
View 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
View 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__

View file

@ -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" ) ||

View file

@ -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 \

View file

@ -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 );

View file

@ -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();

View file

@ -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>