2018-03-29 17:00:53 +00:00
|
|
|
#ifdef MAKE_FFMPEG_PLAYER
|
2013-06-18 01:11:21 +00:00
|
|
|
|
2023-04-17 20:12:27 +00:00
|
|
|
#include "audiooutput.hh"
|
2013-05-05 10:22:12 +00:00
|
|
|
#include "ffmpegaudio.hh"
|
|
|
|
|
|
|
|
#include <math.h>
|
2013-05-14 14:24:19 +00:00
|
|
|
#include <errno.h>
|
2013-05-05 10:22:12 +00:00
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
#include <libavcodec/avcodec.h>
|
|
|
|
#include <libavformat/avformat.h>
|
|
|
|
#include <libavutil/avutil.h>
|
2020-07-18 12:42:29 +00:00
|
|
|
#include "libswresample/swresample.h"
|
2013-05-05 10:22:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#include <QString>
|
|
|
|
#include <QDataStream>
|
|
|
|
|
|
|
|
#include <vector>
|
2022-11-05 05:44:10 +00:00
|
|
|
#if ( QT_VERSION >= QT_VERSION_CHECK( 6, 2, 0 ) )
|
|
|
|
#include <QMediaDevices>
|
2013-05-05 10:22:12 +00:00
|
|
|
|
2022-11-05 05:44:10 +00:00
|
|
|
#include <QAudioDevice>
|
|
|
|
#endif
|
2022-06-21 13:29:05 +00:00
|
|
|
#include "gddebug.hh"
|
2021-11-27 07:17:33 +00:00
|
|
|
#include "utils.hh"
|
2013-05-30 13:24:21 +00:00
|
|
|
|
2013-05-05 10:22:12 +00:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
2018-03-21 17:49:34 +00:00
|
|
|
AudioService & AudioService::instance()
|
2013-05-05 10:22:12 +00:00
|
|
|
{
|
2018-03-21 17:49:34 +00:00
|
|
|
static AudioService a;
|
2013-05-05 10:22:12 +00:00
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
2018-03-21 17:49:34 +00:00
|
|
|
AudioService::AudioService()
|
2013-05-05 10:22:12 +00:00
|
|
|
{
|
2022-11-05 05:44:10 +00:00
|
|
|
// ao_initialize();
|
2013-05-05 10:22:12 +00:00
|
|
|
}
|
|
|
|
|
2018-03-21 17:49:34 +00:00
|
|
|
AudioService::~AudioService()
|
2013-05-05 10:22:12 +00:00
|
|
|
{
|
|
|
|
emit cancelPlaying( true );
|
2022-11-05 05:44:10 +00:00
|
|
|
// ao_shutdown();
|
2013-05-05 10:22:12 +00:00
|
|
|
}
|
|
|
|
|
2018-03-21 17:49:34 +00:00
|
|
|
void AudioService::playMemory( const char * ptr, int size )
|
2013-05-05 10:22:12 +00:00
|
|
|
{
|
|
|
|
emit cancelPlaying( false );
|
2018-03-21 16:51:18 +00:00
|
|
|
QByteArray audioData( ptr, size );
|
2013-05-05 10:22:12 +00:00
|
|
|
DecoderThread * thread = new DecoderThread( audioData, this );
|
|
|
|
|
2022-12-26 02:08:17 +00:00
|
|
|
connect( thread, &DecoderThread::error, this, &AudioService::error );
|
|
|
|
connect( this, &AudioService::cancelPlaying, thread, &DecoderThread::cancel, Qt::DirectConnection );
|
|
|
|
connect( thread, &QThread::finished, thread, &QObject::deleteLater );
|
2013-05-05 10:22:12 +00:00
|
|
|
|
|
|
|
thread->start();
|
|
|
|
}
|
|
|
|
|
2018-03-21 17:49:34 +00:00
|
|
|
void AudioService::stop()
|
2013-06-23 09:53:10 +00:00
|
|
|
{
|
|
|
|
emit cancelPlaying( false );
|
|
|
|
}
|
|
|
|
|
2013-05-05 10:22:12 +00:00
|
|
|
struct DecoderContext
|
|
|
|
{
|
|
|
|
enum {
|
|
|
|
kBufferSize = 32768
|
|
|
|
};
|
|
|
|
|
|
|
|
static QMutex deviceMutex_;
|
|
|
|
QAtomicInt & isCancelled_;
|
|
|
|
QByteArray audioData_;
|
|
|
|
QDataStream audioDataStream_;
|
|
|
|
AVFormatContext * formatContext_;
|
2022-02-20 04:28:05 +00:00
|
|
|
#if LIBAVCODEC_VERSION_MAJOR < 59
|
2018-03-30 19:53:24 +00:00
|
|
|
AVCodec * codec_;
|
2022-02-20 04:28:05 +00:00
|
|
|
#else
|
|
|
|
const AVCodec * codec_;
|
|
|
|
#endif
|
2013-05-05 10:22:12 +00:00
|
|
|
AVCodecContext * codecContext_;
|
|
|
|
AVIOContext * avioContext_;
|
|
|
|
AVStream * audioStream_;
|
2022-11-05 05:44:10 +00:00
|
|
|
// ao_device * aoDevice_;
|
|
|
|
AudioOutput * audioOutput;
|
2013-05-05 10:22:12 +00:00
|
|
|
bool avformatOpened_;
|
|
|
|
|
2020-07-18 12:42:29 +00:00
|
|
|
SwrContext * swr_;
|
|
|
|
|
2013-05-05 10:22:12 +00:00
|
|
|
DecoderContext( QByteArray const & audioData, QAtomicInt & isCancelled );
|
|
|
|
~DecoderContext();
|
|
|
|
|
|
|
|
bool openCodec( QString & errorString );
|
|
|
|
void closeCodec();
|
|
|
|
bool openOutputDevice( QString & errorString );
|
|
|
|
void closeOutputDevice();
|
|
|
|
bool play( QString & errorString );
|
2022-11-05 05:44:10 +00:00
|
|
|
bool normalizeAudio( AVFrame * frame, vector< uint8_t > & samples );
|
2013-05-05 10:22:12 +00:00
|
|
|
void playFrame( AVFrame * frame );
|
|
|
|
};
|
|
|
|
|
|
|
|
DecoderContext::DecoderContext( QByteArray const & audioData, QAtomicInt & isCancelled ):
|
|
|
|
isCancelled_( isCancelled ),
|
|
|
|
audioData_( audioData ),
|
|
|
|
audioDataStream_( audioData_ ),
|
|
|
|
formatContext_( NULL ),
|
2018-03-30 19:53:24 +00:00
|
|
|
codec_( NULL ),
|
2013-05-05 10:22:12 +00:00
|
|
|
codecContext_( NULL ),
|
|
|
|
avioContext_( NULL ),
|
|
|
|
audioStream_( NULL ),
|
2022-11-05 05:44:10 +00:00
|
|
|
audioOutput( new AudioOutput ),
|
2020-07-18 12:42:29 +00:00
|
|
|
avformatOpened_( false ),
|
|
|
|
swr_( NULL )
|
2013-05-05 10:22:12 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
DecoderContext::~DecoderContext()
|
|
|
|
{
|
|
|
|
closeOutputDevice();
|
|
|
|
closeCodec();
|
|
|
|
}
|
|
|
|
|
|
|
|
static int readAudioData( void * opaque, unsigned char * buffer, int bufferSize )
|
|
|
|
{
|
|
|
|
QDataStream * pStream = (QDataStream *)opaque;
|
2022-06-21 13:29:05 +00:00
|
|
|
// This function is passed as the read_packet callback into avio_alloc_context().
|
|
|
|
// The documentation for this callback parameter states:
|
|
|
|
// For stream protocols, must never return 0 but rather a proper AVERROR code.
|
|
|
|
if ( pStream->atEnd() )
|
|
|
|
return AVERROR_EOF;
|
|
|
|
const int bytesRead = pStream->readRawData( (char *)buffer, bufferSize );
|
|
|
|
// QDataStream::readRawData() returns 0 at EOF => return AVERROR_EOF in this case.
|
|
|
|
// An error is unlikely here, so just print a warning and return AVERROR_EOF too.
|
|
|
|
if ( bytesRead < 0 )
|
|
|
|
gdWarning( "readAudioData: error while reading raw data." );
|
|
|
|
return bytesRead > 0 ? bytesRead : AVERROR_EOF;
|
2013-05-05 10:22:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool DecoderContext::openCodec( QString & errorString )
|
|
|
|
{
|
|
|
|
formatContext_ = avformat_alloc_context();
|
|
|
|
if ( !formatContext_ ) {
|
2023-05-07 12:47:41 +00:00
|
|
|
errorString = "avformat_alloc_context() failed.";
|
2013-05-05 10:22:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-03-30 19:53:24 +00:00
|
|
|
unsigned char * avioBuffer = (unsigned char *)av_malloc( kBufferSize + AV_INPUT_BUFFER_PADDING_SIZE );
|
2022-02-07 13:06:49 +00:00
|
|
|
|
2013-05-05 10:22:12 +00:00
|
|
|
if ( !avioBuffer ) {
|
2023-05-07 12:47:41 +00:00
|
|
|
errorString = "av_malloc() failed.";
|
2013-05-05 10:22:12 +00:00
|
|
|
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 );
|
2023-05-07 12:47:41 +00:00
|
|
|
errorString = "avio_alloc_context() failed.";
|
2013-05-05 10:22:12 +00:00
|
|
|
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;
|
|
|
|
|
2022-12-09 07:37:33 +00:00
|
|
|
ret = avformat_open_input( &formatContext_, NULL, NULL, NULL );
|
2013-05-05 10:22:12 +00:00
|
|
|
if ( ret < 0 ) {
|
2023-05-07 12:47:41 +00:00
|
|
|
errorString = QString( "avformat_open_input() failed: %1." ).arg( avErrorString( ret ) );
|
2013-05-05 10:22:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = avformat_find_stream_info( formatContext_, NULL );
|
|
|
|
if ( ret < 0 ) {
|
2023-05-07 12:47:41 +00:00
|
|
|
errorString = QString( "avformat_find_stream_info() failed: %1." ).arg( avErrorString( ret ) );
|
2013-05-05 10:22:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find audio stream, use the first audio stream if available
|
|
|
|
for ( unsigned i = 0; i < formatContext_->nb_streams; i++ ) {
|
2022-02-07 13:06:49 +00:00
|
|
|
if ( formatContext_->streams[ i ]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO ) {
|
2013-05-05 10:22:12 +00:00
|
|
|
audioStream_ = formatContext_->streams[ i ];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( !audioStream_ ) {
|
2023-05-07 12:47:41 +00:00
|
|
|
errorString = QString( "Could not find audio stream." );
|
2013-05-05 10:22:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-03-30 19:53:24 +00:00
|
|
|
codec_ = avcodec_find_decoder( audioStream_->codecpar->codec_id );
|
|
|
|
if ( !codec_ ) {
|
2023-05-07 12:47:41 +00:00
|
|
|
errorString = QString( "Codec [id: %1] not found." ).arg( audioStream_->codecpar->codec_id );
|
2018-03-30 19:53:24 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
codecContext_ = avcodec_alloc_context3( codec_ );
|
|
|
|
if ( !codecContext_ ) {
|
2023-05-07 12:47:41 +00:00
|
|
|
errorString = QString( "avcodec_alloc_context3() failed." );
|
2018-03-30 19:53:24 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
avcodec_parameters_to_context( codecContext_, audioStream_->codecpar );
|
2013-05-05 10:22:12 +00:00
|
|
|
|
2018-03-30 19:53:24 +00:00
|
|
|
ret = avcodec_open2( codecContext_, codec_, NULL );
|
2013-05-05 10:22:12 +00:00
|
|
|
if ( ret < 0 ) {
|
2023-05-07 12:47:41 +00:00
|
|
|
errorString = QString( "avcodec_open2() failed: %1." ).arg( avErrorString( ret ) );
|
2013-05-05 10:22:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-11-04 15:38:58 +00:00
|
|
|
gdDebug( "Codec open: %s: channels: %d, rate: %d, format: %s\n",
|
|
|
|
codec_->long_name,
|
2013-05-05 10:22:12 +00:00
|
|
|
codecContext_->channels,
|
|
|
|
codecContext_->sample_rate,
|
|
|
|
av_get_sample_fmt_name( codecContext_->sample_fmt ) );
|
2020-07-18 12:42:29 +00:00
|
|
|
|
2022-11-28 12:35:30 +00:00
|
|
|
auto layout = codecContext_->channel_layout;
|
2022-12-05 01:56:45 +00:00
|
|
|
if ( !layout ) {
|
|
|
|
layout = av_get_default_channel_layout( codecContext_->channels );
|
2022-11-28 12:35:30 +00:00
|
|
|
codecContext_->channel_layout = layout;
|
|
|
|
}
|
|
|
|
|
2022-12-05 01:56:45 +00:00
|
|
|
swr_ = swr_alloc_set_opts( NULL,
|
2022-11-28 12:35:30 +00:00
|
|
|
layout,
|
|
|
|
AV_SAMPLE_FMT_S16,
|
|
|
|
44100,
|
|
|
|
layout,
|
|
|
|
codecContext_->sample_fmt,
|
|
|
|
codecContext_->sample_rate,
|
|
|
|
0,
|
|
|
|
NULL );
|
2023-07-20 08:02:22 +00:00
|
|
|
|
2022-11-28 12:35:30 +00:00
|
|
|
if ( !swr_ || swr_init( swr_ ) < 0 ) {
|
|
|
|
av_log( NULL, AV_LOG_ERROR, "Cannot create sample rate converter \n" );
|
|
|
|
swr_free( &swr_ );
|
|
|
|
return false;
|
2020-07-18 12:42:29 +00:00
|
|
|
}
|
|
|
|
|
2013-05-05 10:22:12 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DecoderContext::closeCodec()
|
|
|
|
{
|
2020-07-18 12:42:29 +00:00
|
|
|
if ( swr_ ) {
|
|
|
|
swr_free( &swr_ );
|
|
|
|
}
|
|
|
|
|
2013-05-05 10:22:12 +00:00
|
|
|
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
|
2018-03-30 19:53:24 +00:00
|
|
|
if ( audioStream_ && codecContext_ && codec_ ) {
|
2013-05-05 10:22:12 +00:00
|
|
|
audioStream_->discard = AVDISCARD_ALL;
|
2018-03-30 19:53:24 +00:00
|
|
|
avcodec_close( codecContext_ );
|
|
|
|
avcodec_free_context( &codecContext_ );
|
2013-05-05 10:22:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
avformat_close_input( &formatContext_ );
|
|
|
|
av_free( avioContext_->buffer );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DecoderContext::openOutputDevice( QString & errorString )
|
|
|
|
{
|
2022-11-05 05:44:10 +00:00
|
|
|
// only check device when qt version is greater than 6.2
|
|
|
|
#if ( QT_VERSION >= QT_VERSION_CHECK( 6, 2, 0 ) )
|
|
|
|
QAudioDevice m_outputDevice = QMediaDevices::defaultAudioOutput();
|
|
|
|
if ( m_outputDevice.isNull() ) {
|
2023-05-07 12:47:41 +00:00
|
|
|
errorString += QString( "Can not found default audio output device" );
|
2013-05-14 14:24:19 +00:00
|
|
|
return false;
|
2013-05-05 10:22:12 +00:00
|
|
|
}
|
2022-11-05 05:44:10 +00:00
|
|
|
#endif
|
2013-05-05 10:22:12 +00:00
|
|
|
|
2022-11-28 12:35:30 +00:00
|
|
|
audioOutput->setAudioFormat( 44100, codecContext_->channels );
|
2013-05-05 10:22:12 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DecoderContext::closeOutputDevice() {}
|
|
|
|
|
|
|
|
bool DecoderContext::play( QString & errorString )
|
|
|
|
{
|
2016-02-17 14:37:23 +00:00
|
|
|
AVFrame * frame = av_frame_alloc();
|
2013-05-05 10:22:12 +00:00
|
|
|
if ( !frame ) {
|
2023-05-07 12:47:41 +00:00
|
|
|
errorString = QString( "avcodec_alloc_frame() failed." );
|
2013-05-05 10:22:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-11-28 23:32:51 +00:00
|
|
|
AVPacket * packet = av_packet_alloc();
|
2013-05-05 10:22:12 +00:00
|
|
|
|
2021-11-27 07:17:33 +00:00
|
|
|
while ( !Utils::AtomicInt::loadAcquire( isCancelled_ ) && av_read_frame( formatContext_, packet ) >= 0 ) {
|
2022-11-28 23:32:51 +00:00
|
|
|
if ( packet->stream_index == audioStream_->index ) {
|
|
|
|
int ret = avcodec_send_packet( codecContext_, packet );
|
2018-03-30 19:53:24 +00:00
|
|
|
/* read all the output frames (in general there may be any number of them) */
|
|
|
|
while ( ret >= 0 ) {
|
|
|
|
ret = avcodec_receive_frame( codecContext_, frame );
|
|
|
|
|
2021-11-27 07:17:33 +00:00
|
|
|
if ( Utils::AtomicInt::loadAcquire( isCancelled_ ) || ret < 0 )
|
2018-03-30 19:53:24 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
playFrame( frame );
|
|
|
|
}
|
2013-05-05 10:22:12 +00:00
|
|
|
}
|
2022-02-07 13:06:49 +00:00
|
|
|
|
2022-11-28 23:32:51 +00:00
|
|
|
av_packet_unref( packet );
|
2013-05-05 10:22:12 +00:00
|
|
|
}
|
2022-02-07 13:06:49 +00:00
|
|
|
|
2018-03-30 19:53:24 +00:00
|
|
|
/* flush the decoder */
|
2022-11-28 23:32:51 +00:00
|
|
|
packet->data = NULL;
|
|
|
|
packet->size = 0;
|
|
|
|
int ret = avcodec_send_packet( codecContext_, packet );
|
2018-03-30 19:53:24 +00:00
|
|
|
while ( ret >= 0 ) {
|
|
|
|
ret = avcodec_receive_frame( codecContext_, frame );
|
2021-11-27 07:17:33 +00:00
|
|
|
if ( Utils::AtomicInt::loadAcquire( isCancelled_ ) || ret < 0 )
|
2018-03-30 19:53:24 +00:00
|
|
|
break;
|
|
|
|
playFrame( frame );
|
|
|
|
}
|
2016-02-17 14:37:23 +00:00
|
|
|
av_frame_free( &frame );
|
2022-11-28 23:32:51 +00:00
|
|
|
av_packet_free( &packet );
|
2013-05-05 10:22:12 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-11-05 05:44:10 +00:00
|
|
|
bool DecoderContext::normalizeAudio( AVFrame * frame, vector< uint8_t > & samples )
|
2013-05-05 10:22:12 +00:00
|
|
|
{
|
2022-11-28 12:35:30 +00:00
|
|
|
auto dst_freq = 44100;
|
|
|
|
auto dst_channels = codecContext_->channels;
|
|
|
|
int out_count = (int64_t)frame->nb_samples * dst_freq / frame->sample_rate + 256;
|
2022-11-29 13:19:31 +00:00
|
|
|
int out_size = av_samples_get_buffer_size( NULL, dst_channels, out_count, AV_SAMPLE_FMT_S16, 1 );
|
2022-11-28 12:35:30 +00:00
|
|
|
samples.resize( out_size );
|
|
|
|
uint8_t * data[ 2 ] = { 0 };
|
|
|
|
data[ 0 ] = &samples.front();
|
2022-11-15 11:54:34 +00:00
|
|
|
|
2022-11-28 12:35:30 +00:00
|
|
|
auto out_nb_samples = swr_convert( swr_, data, out_count, (const uint8_t **)frame->extended_data, frame->nb_samples );
|
2022-11-15 11:54:34 +00:00
|
|
|
|
2022-11-28 12:35:30 +00:00
|
|
|
if ( out_nb_samples < 0 ) {
|
|
|
|
av_log( NULL, AV_LOG_ERROR, "converte fail \n" );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// qDebug( "out_count:%d, out_nb_samples:%d, frame->nb_samples:%d \n", out_count, out_nb_samples, frame->nb_samples );
|
2022-11-15 11:54:34 +00:00
|
|
|
}
|
2013-05-05 10:22:12 +00:00
|
|
|
|
2022-11-29 13:19:31 +00:00
|
|
|
int actual_size = av_samples_get_buffer_size( NULL, dst_channels, out_nb_samples, AV_SAMPLE_FMT_S16, 1 );
|
2022-11-28 12:35:30 +00:00
|
|
|
samples.resize( actual_size );
|
2013-05-05 10:22:12 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DecoderContext::playFrame( AVFrame * frame )
|
|
|
|
{
|
|
|
|
if ( !frame )
|
|
|
|
return;
|
|
|
|
|
2022-11-05 05:44:10 +00:00
|
|
|
vector< uint8_t > samples;
|
2013-05-05 10:22:12 +00:00
|
|
|
if ( normalizeAudio( frame, samples ) ) {
|
2022-11-05 05:44:10 +00:00
|
|
|
audioOutput->play( &samples.front(), samples.size() );
|
|
|
|
}
|
2013-05-05 10:22:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 ) ) {
|
2021-11-27 07:17:33 +00:00
|
|
|
if ( Utils::AtomicInt::loadAcquire( isCancelled_ ) )
|
2013-05-05 10:22:12 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Ffmpeg
|
2013-06-18 01:11:21 +00:00
|
|
|
|
2018-03-29 17:00:53 +00:00
|
|
|
#endif // MAKE_FFMPEG_PLAYER
|