first commit
This commit is contained in:
275
project/fm_viewer/fav/AVClock.cpp
Normal file
275
project/fm_viewer/fav/AVClock.cpp
Normal file
@@ -0,0 +1,275 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
#include "AVClock.h"
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QTimerEvent>
|
||||
#include <QtCore/QDateTime>
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
enum {
|
||||
kRunning,
|
||||
kPaused,
|
||||
kStopped
|
||||
};
|
||||
AVClock::AVClock(AVClock::ClockType c, QObject *parent):
|
||||
QObject(parent)
|
||||
, auto_clock(true)
|
||||
, m_state(kStopped)
|
||||
, clock_type(c)
|
||||
, mSpeed(1.0)
|
||||
, value0(0)
|
||||
, avg_err(0)
|
||||
, nb_restarted(0)
|
||||
, nb_sync(0)
|
||||
, sync_id(0)
|
||||
{
|
||||
last_pts = pts_ = pts_v = delay_ = 0;
|
||||
}
|
||||
|
||||
AVClock::AVClock(QObject *parent):
|
||||
QObject(parent)
|
||||
, auto_clock(true)
|
||||
, m_state(kStopped)
|
||||
, clock_type(AudioClock)
|
||||
, mSpeed(1.0)
|
||||
, value0(0)
|
||||
, avg_err(0)
|
||||
, nb_restarted(0)
|
||||
, nb_sync(0)
|
||||
, sync_id(0)
|
||||
{
|
||||
last_pts = pts_ = pts_v = delay_ = 0;
|
||||
}
|
||||
|
||||
void AVClock::setClockType(ClockType ct)
|
||||
{
|
||||
if (clock_type == ct)
|
||||
return;
|
||||
clock_type = ct;
|
||||
QTimer::singleShot(0, this, SLOT(restartCorrectionTimer()));
|
||||
}
|
||||
|
||||
AVClock::ClockType AVClock::clockType() const
|
||||
{
|
||||
return clock_type;
|
||||
}
|
||||
|
||||
bool AVClock::isActive() const
|
||||
{
|
||||
return clock_type == AudioClock || timer.isValid();
|
||||
}
|
||||
|
||||
void AVClock::setInitialValue(double v)
|
||||
{
|
||||
value0 = v;
|
||||
qDebug("Clock initial value: %f", v);
|
||||
}
|
||||
|
||||
double AVClock::initialValue() const
|
||||
{
|
||||
return value0;
|
||||
}
|
||||
|
||||
void AVClock::setClockAuto(bool a)
|
||||
{
|
||||
auto_clock = a;
|
||||
}
|
||||
|
||||
bool AVClock::isClockAuto() const
|
||||
{
|
||||
return auto_clock;
|
||||
}
|
||||
|
||||
void AVClock::updateExternalClock(qint64 msecs)
|
||||
{
|
||||
if (clock_type == AudioClock)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#if !(OFF_OTHER_DEBUG)
|
||||
qDebug("External clock change: %f ==> %f", value(), double(msecs) * kThousandth);
|
||||
#endif
|
||||
pts_ = double(msecs) * kThousandth; //can not use msec/1000.
|
||||
if (!isPaused())
|
||||
{
|
||||
timer.restart();
|
||||
}
|
||||
|
||||
last_pts = pts_;
|
||||
t = QDateTime::currentMSecsSinceEpoch();
|
||||
if (clockType() == VideoClock)
|
||||
{
|
||||
pts_v = pts_;
|
||||
}
|
||||
}
|
||||
|
||||
void AVClock::updateExternalClock(const AVClock &clock)
|
||||
{
|
||||
if (clock_type != ExternalClock)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#if !(OFF_OTHER_DEBUG)
|
||||
qDebug("External clock change: %f ==> %f", value(), clock.value());
|
||||
#endif
|
||||
pts_ = clock.value();
|
||||
|
||||
// qInfo() << "C:" << pts_;
|
||||
if (!isPaused())
|
||||
timer.restart();
|
||||
|
||||
last_pts = pts_;
|
||||
t = QDateTime::currentMSecsSinceEpoch();
|
||||
}
|
||||
|
||||
void AVClock::setSpeed(qreal speed)
|
||||
{
|
||||
mSpeed = speed;
|
||||
}
|
||||
|
||||
bool AVClock::isPaused() const
|
||||
{
|
||||
return m_state == kPaused;
|
||||
}
|
||||
|
||||
// SYNC START 처리하면
|
||||
//
|
||||
int AVClock::syncStart(int count)
|
||||
{
|
||||
static int sId = 0;
|
||||
nb_sync = count;
|
||||
if (sId == -1) {
|
||||
sId = 0;
|
||||
}
|
||||
sync_id = ++sId;
|
||||
return sId;
|
||||
}
|
||||
|
||||
bool AVClock::syncEndOnce(int id)
|
||||
{
|
||||
if (id != sync_id)
|
||||
{
|
||||
qWarning("bad sync id: %d, current: %d", id, sync_id);
|
||||
return true;
|
||||
}
|
||||
if (!nb_sync.deref()) {
|
||||
sync_id = 0;
|
||||
}
|
||||
return sync_id;
|
||||
}
|
||||
|
||||
void AVClock::start()
|
||||
{
|
||||
m_state = kRunning;
|
||||
qDebug("AVClock started!!!!!!!!");
|
||||
timer.start();
|
||||
QTimer::singleShot(0, this, SLOT(restartCorrectionTimer()));
|
||||
Q_EMIT started();
|
||||
}
|
||||
//remember last value because we don't reset pts_, pts_v, delay_
|
||||
void AVClock::pause(bool p)
|
||||
{
|
||||
if (isPaused() == p)
|
||||
return;
|
||||
if (clock_type == AudioClock)
|
||||
return;
|
||||
m_state = p ? kPaused : kRunning;
|
||||
if (p) {
|
||||
QTimer::singleShot(0, this, SLOT(stopCorrectionTimer()));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
|
||||
timer.invalidate();
|
||||
#else
|
||||
timer.stop();
|
||||
#endif //QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
|
||||
Q_EMIT paused();
|
||||
} else {
|
||||
timer.start();
|
||||
QTimer::singleShot(0, this, SLOT(restartCorrectionTimer()));
|
||||
Q_EMIT resumed();
|
||||
}
|
||||
t = QDateTime::currentMSecsSinceEpoch();
|
||||
Q_EMIT paused(p);
|
||||
}
|
||||
|
||||
void AVClock::reset()
|
||||
{
|
||||
nb_sync = 0;
|
||||
sync_id = 0;
|
||||
// keep mSpeed
|
||||
m_state = kStopped;
|
||||
value0 = 0;
|
||||
pts_ = pts_v = delay_ = 0;
|
||||
QTimer::singleShot(0, this, SLOT(stopCorrectionTimer()));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
|
||||
timer.invalidate();
|
||||
#else
|
||||
timer.stop();
|
||||
#endif //QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
|
||||
t = QDateTime::currentMSecsSinceEpoch();
|
||||
Q_EMIT resetted();
|
||||
}
|
||||
|
||||
void AVClock::timerEvent(QTimerEvent *event)
|
||||
{
|
||||
Q_ASSERT_X(clockType() != AudioClock, "AVClock::timerEvent", "Internal error. AudioClock can not call this");
|
||||
if (event->timerId() != correction_schedule_timer.timerId())
|
||||
return;
|
||||
if (isPaused())
|
||||
return;
|
||||
const double delta_pts = (value() - last_pts)/speed();
|
||||
//const double err = double(correction_timer.restart()) * kThousandth - delta_pts;
|
||||
const qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
const double err = double(now - t) * kThousandth - delta_pts;
|
||||
t = now;
|
||||
// FIXME: avfoundation camera error is large (about -0.6s)
|
||||
if (qAbs(err*10.0) < kCorrectionInterval || clock_type == VideoClock) {
|
||||
avg_err += err/(nb_restarted+1);
|
||||
}
|
||||
//qDebug("correction timer event. error = %f, avg_err=%f, nb_restarted=%d", err, avg_err, nb_restarted);
|
||||
last_pts = value();
|
||||
nb_restarted = 0;
|
||||
}
|
||||
|
||||
void AVClock::restartCorrectionTimer()
|
||||
{
|
||||
nb_restarted = 0;
|
||||
avg_err = 0;
|
||||
correction_schedule_timer.stop();
|
||||
if (clockType() == AudioClock) // TODO: for all clock type
|
||||
return;
|
||||
// parameters are reset. do not start correction timer if not running
|
||||
if (m_state != kRunning)
|
||||
return;
|
||||
// timer is always started in AVClock::start()
|
||||
if (!timer.isValid())
|
||||
return;
|
||||
t = QDateTime::currentMSecsSinceEpoch();
|
||||
correction_schedule_timer.start(kCorrectionInterval*1000, this);
|
||||
}
|
||||
|
||||
void AVClock::stopCorrectionTimer()
|
||||
{
|
||||
nb_restarted = 0;
|
||||
avg_err = 0;
|
||||
correction_schedule_timer.stop();
|
||||
}
|
||||
} //namespace FAV
|
||||
221
project/fm_viewer/fav/AVClock.h
Normal file
221
project/fm_viewer/fav/AVClock.h
Normal file
@@ -0,0 +1,221 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AVCLOCK_H
|
||||
#define QTAV_AVCLOCK_H
|
||||
|
||||
#include "_fav_constants.h"
|
||||
#include <QtCore/QAtomicInt>
|
||||
#include <QtCore/QBasicTimer>
|
||||
#include <QtCore/QObject>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#else
|
||||
#include <QtCore/QTime>
|
||||
typedef QTime QElapsedTimer;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* AVClock is created by AVPlayer. The only way to access AVClock is through AVPlayer::masterClock()
|
||||
* The default clock type is Audio's clock, i.e. vedio synchronizes to audio. If audio stream is not
|
||||
* detected, then the clock will set to External clock automatically.
|
||||
* I name it ExternalClock because the clock can be corrected outside, though it is a clock inside AVClock
|
||||
*/
|
||||
namespace FAV {
|
||||
|
||||
static const double kThousandth = 0.001;
|
||||
|
||||
class Q_AV_EXPORT AVClock : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
typedef enum {
|
||||
AudioClock = 0,
|
||||
ExternalClock = 1,
|
||||
VideoClock = 2, //sync to video timestamp
|
||||
} ClockType;
|
||||
|
||||
AVClock(ClockType c, QObject* parent = 0);
|
||||
AVClock(QObject* parent = 0);
|
||||
void setClockType(ClockType ct);
|
||||
ClockType clockType() const;
|
||||
bool isActive() const;
|
||||
/*!
|
||||
* \brief setInitialValue
|
||||
* Usually for ExternalClock. For example, media start time is not 0, clock have to set initial value as media start time
|
||||
*/
|
||||
void setInitialValue(double v);
|
||||
double initialValue() const;
|
||||
/*
|
||||
* auto clock: use audio clock if audio stream found, otherwise use external clock
|
||||
*/
|
||||
void setClockAuto(bool a);
|
||||
bool isClockAuto() const;
|
||||
/*in seconds*/
|
||||
inline double pts() const;
|
||||
/*!
|
||||
* \brief value
|
||||
* the real timestamp in seconds: pts + delay
|
||||
* \return
|
||||
*/
|
||||
inline double value() const;
|
||||
inline void updateValue(double pts); //update the pts
|
||||
/*used when seeking and correcting from external*/
|
||||
void updateExternalClock(qint64 msecs);
|
||||
/*external clock outside still running, so it's more accurate for syncing multiple clocks serially*/
|
||||
void updateExternalClock(const AVClock& clock);
|
||||
|
||||
inline void updateVideoTime(double pts);
|
||||
inline double videoTime() const;
|
||||
inline double delay() const; //playing audio spends some time
|
||||
inline void updateDelay(double delay);
|
||||
inline qreal diff() const;
|
||||
|
||||
void setSpeed(qreal speed);
|
||||
inline qreal speed() const;
|
||||
|
||||
bool isPaused() const;
|
||||
|
||||
/*!
|
||||
* \brief syncStart
|
||||
* For internal use now
|
||||
* Start to sync "count" objects. Call syncEndOnce(id) "count" times to end sync.
|
||||
* \param count Number of objects to sync. Each one should call syncEndOnce(int id)
|
||||
* \return an id
|
||||
*/
|
||||
int syncStart(int count);
|
||||
int syncId() const {return sync_id;}
|
||||
/*!
|
||||
* \brief syncEndOnce
|
||||
* Decrease sync objects count if id is current sync id.
|
||||
* \return true if sync is end for id or id is not current sync id
|
||||
*/
|
||||
bool syncEndOnce(int id);
|
||||
|
||||
Q_SIGNALS:
|
||||
void paused(bool);
|
||||
void paused(); //equals to paused(true)
|
||||
void resumed();//equals to paused(false)
|
||||
void started();
|
||||
void resetted();
|
||||
public Q_SLOTS:
|
||||
//these slots are not frequently used. so not inline
|
||||
/*start the external clock*/
|
||||
void start();
|
||||
/*pause external clock*/
|
||||
void pause(bool p);
|
||||
/*reset clock intial value and external clock parameters (and stop timer). keep speed() and isClockAuto()*/
|
||||
void reset();
|
||||
|
||||
protected:
|
||||
virtual void timerEvent(QTimerEvent *event);
|
||||
private Q_SLOTS:
|
||||
/// make sure QBasic timer start/stop in a right thread
|
||||
void restartCorrectionTimer();
|
||||
void stopCorrectionTimer();
|
||||
private:
|
||||
bool auto_clock;
|
||||
int m_state;
|
||||
ClockType clock_type;
|
||||
mutable double pts_;
|
||||
mutable double pts_v;
|
||||
double delay_;
|
||||
mutable QElapsedTimer timer;
|
||||
qreal mSpeed;
|
||||
double value0;
|
||||
/*!
|
||||
* \brief correction_schedule_timer
|
||||
* accumulative error is too large using QElapsedTimer.restart() frequently.
|
||||
* we periodically correct value() to keep the error always less
|
||||
* than the error of calling QElapsedTimer.restart() once
|
||||
* see github issue 46, 307 etc
|
||||
*/
|
||||
QBasicTimer correction_schedule_timer;
|
||||
qint64 t; // absolute time for elapsed timer correction
|
||||
static const int kCorrectionInterval = 1; // 1000ms
|
||||
double last_pts;
|
||||
double avg_err; // average error of restart()
|
||||
mutable int nb_restarted;
|
||||
QAtomicInt nb_sync;
|
||||
int sync_id;
|
||||
};
|
||||
|
||||
double AVClock::value() const
|
||||
{
|
||||
if (clock_type == AudioClock) {
|
||||
// TODO: audio clock need a timer too
|
||||
// timestamp from media stream is >= value0
|
||||
return pts_ == 0 ? value0 : pts_ + delay_;
|
||||
} else if (clock_type == ExternalClock) {
|
||||
if (timer.isValid()) {
|
||||
++nb_restarted;
|
||||
pts_ += (double(timer.restart()) * kThousandth + avg_err)* speed();
|
||||
} else {//timer is paused
|
||||
//qDebug("clock is paused. return the last value %f", pts_);
|
||||
}
|
||||
return pts_ + value0;
|
||||
} else {
|
||||
return pts_v; // value0 is 1st video pts_v already
|
||||
}
|
||||
}
|
||||
|
||||
void AVClock::updateValue(double pts)
|
||||
{
|
||||
if (clock_type == AudioClock)
|
||||
{
|
||||
pts_ = pts;
|
||||
}
|
||||
}
|
||||
|
||||
void AVClock::updateVideoTime(double pts)
|
||||
{
|
||||
pts_v = pts;
|
||||
if (clock_type == VideoClock)
|
||||
timer.restart();
|
||||
}
|
||||
|
||||
double AVClock::videoTime() const
|
||||
{
|
||||
return pts_v;
|
||||
}
|
||||
|
||||
double AVClock::delay() const
|
||||
{
|
||||
return delay_;
|
||||
}
|
||||
|
||||
void AVClock::updateDelay(double delay)
|
||||
{
|
||||
delay_ = delay;
|
||||
}
|
||||
|
||||
qreal AVClock::diff() const
|
||||
{
|
||||
return value() - videoTime();
|
||||
}
|
||||
|
||||
qreal AVClock::speed() const
|
||||
{
|
||||
return mSpeed;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QTAV_AVCLOCK_H
|
||||
415
project/fm_viewer/fav/AVCompat.cpp
Normal file
415
project/fm_viewer/fav/AVCompat.cpp
Normal file
@@ -0,0 +1,415 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AVCompat.h"
|
||||
#include "version.h"
|
||||
|
||||
#if !FFMPEG_MODULE_CHECK(LIBAVFORMAT, 56, 4, 101)
|
||||
int avio_feof(AVIOContext *s)
|
||||
{
|
||||
#if QTAV_USE_FFMPEG(LIBAVFORMAT)
|
||||
return url_feof(s);
|
||||
#else
|
||||
return s && s->eof_reached;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#if QTAV_USE_LIBAV(LIBAVFORMAT)
|
||||
int avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename)
|
||||
{
|
||||
AVFormatContext *s = avformat_alloc_context();
|
||||
int ret = 0;
|
||||
|
||||
*avctx = NULL;
|
||||
if (!s)
|
||||
goto nomem;
|
||||
|
||||
if (!oformat) {
|
||||
if (format) {
|
||||
oformat = av_guess_format(format, NULL, NULL);
|
||||
if (!oformat) {
|
||||
av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
oformat = av_guess_format(NULL, filename, NULL);
|
||||
if (!oformat) {
|
||||
ret = AVERROR(EINVAL);
|
||||
av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n",
|
||||
filename);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s->oformat = oformat;
|
||||
if (s->oformat->priv_data_size > 0) {
|
||||
s->priv_data = av_mallocz(s->oformat->priv_data_size);
|
||||
if (!s->priv_data)
|
||||
goto nomem;
|
||||
if (s->oformat->priv_class) {
|
||||
*(const AVClass**)s->priv_data= s->oformat->priv_class;
|
||||
av_opt_set_defaults(s->priv_data);
|
||||
}
|
||||
} else
|
||||
s->priv_data = NULL;
|
||||
|
||||
if (filename)
|
||||
av_strlcpy(s->filename, filename, sizeof(s->filename));
|
||||
*avctx = s;
|
||||
return 0;
|
||||
nomem:
|
||||
av_log(s, AV_LOG_ERROR, "Out of memory\n");
|
||||
ret = AVERROR(ENOMEM);
|
||||
error:
|
||||
avformat_free_context(s);
|
||||
return ret;
|
||||
}
|
||||
#endif //QTAV_USE_LIBAV(LIBAVFORMAT)
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51, 32, 0)
|
||||
static const struct {
|
||||
const char *name;
|
||||
int nb_channels;
|
||||
uint64_t layout;
|
||||
} channel_layout_map[] = {
|
||||
{ "mono", 1, AV_CH_LAYOUT_MONO },
|
||||
{ "stereo", 2, AV_CH_LAYOUT_STEREO },
|
||||
{ "4.0", 4, AV_CH_LAYOUT_4POINT0 },
|
||||
{ "quad", 4, AV_CH_LAYOUT_QUAD },
|
||||
{ "5.0", 5, AV_CH_LAYOUT_5POINT0 },
|
||||
{ "5.0", 5, AV_CH_LAYOUT_5POINT0_BACK },
|
||||
{ "5.1", 6, AV_CH_LAYOUT_5POINT1 },
|
||||
{ "5.1", 6, AV_CH_LAYOUT_5POINT1_BACK },
|
||||
{ "5.1+downmix", 8, AV_CH_LAYOUT_5POINT1|AV_CH_LAYOUT_STEREO_DOWNMIX, },
|
||||
{ "7.1", 8, AV_CH_LAYOUT_7POINT1 },
|
||||
{ "7.1(wide)", 8, AV_CH_LAYOUT_7POINT1_WIDE },
|
||||
{ "7.1+downmix", 10, AV_CH_LAYOUT_7POINT1|AV_CH_LAYOUT_STEREO_DOWNMIX, },
|
||||
{ 0 }
|
||||
};
|
||||
int64_t av_get_default_channel_layout(int nb_channels) {
|
||||
int i;
|
||||
for (i = 0; i < FF_ARRAY_ELEMS(channel_layout_map); i++)
|
||||
if (nb_channels == channel_layout_map[i].nb_channels)
|
||||
return channel_layout_map[i].layout;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* always need this function if avresample available
|
||||
* use AVAudioResampleContext to avoid func type confliction when swr is also available
|
||||
*/
|
||||
#if QTAV_HAVE(AVRESAMPLE)
|
||||
AVAudioResampleContext *swr_alloc_set_opts(AVAudioResampleContext *s
|
||||
, int64_t out_ch_layout
|
||||
, enum AVSampleFormat out_sample_fmt
|
||||
, int out_sample_rate
|
||||
, int64_t in_ch_layout
|
||||
, enum AVSampleFormat in_sample_fmt
|
||||
, int in_sample_rate
|
||||
, int log_offset, void *log_ctx)
|
||||
{
|
||||
//DO NOT use swr_alloc() because it's not defined as a macro in QtAV_Compat.h
|
||||
if (!s)
|
||||
s = avresample_alloc_context();
|
||||
if (!s)
|
||||
return 0;
|
||||
|
||||
Q_UNUSED(log_offset);
|
||||
Q_UNUSED(log_ctx);
|
||||
|
||||
av_opt_set_int(s, "out_channel_layout", out_ch_layout , 0);
|
||||
av_opt_set_int(s, "out_sample_fmt" , out_sample_fmt , 0);
|
||||
av_opt_set_int(s, "out_sample_rate" , out_sample_rate, 0);
|
||||
av_opt_set_int(s, "in_channel_layout" , in_ch_layout , 0);
|
||||
av_opt_set_int(s, "in_sample_fmt" , in_sample_fmt , 0);
|
||||
av_opt_set_int(s, "in_sample_rate" , in_sample_rate , 0);
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !AV_MODULE_CHECK(LIBAVUTIL, 52, 3, 0, 13, 100)
|
||||
extern const AVPixFmtDescriptor av_pix_fmt_descriptors[];
|
||||
const AVPixFmtDescriptor *av_pix_fmt_desc_get(AVPixelFormat pix_fmt)
|
||||
{
|
||||
if (pix_fmt < 0 || pix_fmt >= QTAV_PIX_FMT_C(NB))
|
||||
return NULL;
|
||||
return &av_pix_fmt_descriptors[pix_fmt];
|
||||
}
|
||||
|
||||
const AVPixFmtDescriptor *av_pix_fmt_desc_next(const AVPixFmtDescriptor *prev)
|
||||
{
|
||||
if (!prev)
|
||||
return &av_pix_fmt_descriptors[0];
|
||||
// can not use sizeof(av_pix_fmt_descriptors)
|
||||
while (prev - av_pix_fmt_descriptors < QTAV_PIX_FMT_C(NB) - 1) {
|
||||
prev++;
|
||||
if (prev->name)
|
||||
return prev;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AVPixelFormat av_pix_fmt_desc_get_id(const AVPixFmtDescriptor *desc)
|
||||
{
|
||||
if (desc < av_pix_fmt_descriptors ||
|
||||
desc >= av_pix_fmt_descriptors + QTAV_PIX_FMT_C(NB))
|
||||
return QTAV_PIX_FMT_C(NONE);
|
||||
|
||||
return AVPixelFormat(desc - av_pix_fmt_descriptors);
|
||||
}
|
||||
#endif // !AV_MODULE_CHECK(LIBAVUTIL, 52, 3, 0, 13, 100)
|
||||
#if !FFMPEG_MODULE_CHECK(LIBAVUTIL, 52, 48, 101)
|
||||
enum AVColorSpace av_frame_get_colorspace(const AVFrame *frame)
|
||||
{
|
||||
if (!frame)
|
||||
return AVCOL_SPC_NB;
|
||||
#if LIBAV_MODULE_CHECK(LIBAVUTIL, 53, 16, 0) //8c02adc
|
||||
return frame->colorspace;
|
||||
#endif
|
||||
return AVCOL_SPC_NB;
|
||||
}
|
||||
|
||||
enum AVColorRange av_frame_get_color_range(const AVFrame *frame)
|
||||
{
|
||||
if (!frame)
|
||||
return AVCOL_RANGE_UNSPECIFIED;
|
||||
#if LIBAV_MODULE_CHECK(LIBAVUTIL, 53, 16, 0) //8c02adc
|
||||
return frame->color_range;
|
||||
#endif
|
||||
return AVCOL_RANGE_UNSPECIFIED;
|
||||
}
|
||||
#endif //!FFMPEG_MODULE_CHECK(LIBAVUTIL, 52, 28, 101)
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 38, 100)
|
||||
int av_pix_fmt_count_planes(AVPixelFormat pix_fmt)
|
||||
{
|
||||
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
|
||||
int i, planes[4] = { 0 }, ret = 0;
|
||||
|
||||
if (!desc)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
for (i = 0; i < desc->nb_components; i++)
|
||||
planes[desc->comp[i].plane] = 1;
|
||||
for (i = 0; i < (int)FF_ARRAY_ELEMS(planes); i++)
|
||||
ret += planes[i];
|
||||
return ret;
|
||||
}
|
||||
#endif //AV_VERSION_INT(52, 38, 100)
|
||||
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51, 73, 101)
|
||||
int av_samples_copy(uint8_t **dst, uint8_t * const *src, int dst_offset,
|
||||
int src_offset, int nb_samples, int nb_channels,
|
||||
enum AVSampleFormat sample_fmt)
|
||||
{
|
||||
int planar = av_sample_fmt_is_planar(sample_fmt);
|
||||
int planes = planar ? nb_channels : 1;
|
||||
int block_align = av_get_bytes_per_sample(sample_fmt) * (planar ? 1 : nb_channels);
|
||||
int data_size = nb_samples * block_align;
|
||||
int i;
|
||||
|
||||
dst_offset *= block_align;
|
||||
src_offset *= block_align;
|
||||
|
||||
if((dst[0] < src[0] ? src[0] - dst[0] : dst[0] - src[0]) >= data_size) {
|
||||
for (i = 0; i < planes; i++)
|
||||
memcpy(dst[i] + dst_offset, src[i] + src_offset, data_size);
|
||||
} else {
|
||||
for (i = 0; i < planes; i++)
|
||||
memmove(dst[i] + dst_offset, src[i] + src_offset, data_size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif //AV_VERSION_INT(51, 73, 101)
|
||||
|
||||
#if QTAV_USE_LIBAV(LIBAVCODEC)
|
||||
const char *avcodec_get_name(enum AVCodecID id)
|
||||
{
|
||||
const AVCodecDescriptor *cd;
|
||||
AVCodec *codec;
|
||||
|
||||
if (id == AV_CODEC_ID_NONE)
|
||||
return "none";
|
||||
cd = avcodec_descriptor_get(id);
|
||||
if (cd)
|
||||
return cd->name;
|
||||
av_log(NULL, AV_LOG_WARNING, "Codec 0x%x is not in the full list.\n", id);
|
||||
codec = avcodec_find_decoder(id);
|
||||
if (codec)
|
||||
return codec->name;
|
||||
codec = avcodec_find_encoder(id);
|
||||
if (codec)
|
||||
return codec->name;
|
||||
return "unknown_codec";
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !AV_MODULE_CHECK(LIBAVCODEC, 55, 55, 0, 68, 100)
|
||||
void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb)
|
||||
{
|
||||
if (pkt->pts != (int64_t)AV_NOPTS_VALUE)
|
||||
pkt->pts = av_rescale_q(pkt->pts, src_tb, dst_tb);
|
||||
if (pkt->dts != (int64_t)AV_NOPTS_VALUE)
|
||||
pkt->dts = av_rescale_q(pkt->dts, src_tb, dst_tb);
|
||||
if (pkt->duration > 0)
|
||||
pkt->duration = av_rescale_q(pkt->duration, src_tb, dst_tb);
|
||||
if (pkt->convergence_duration > 0)
|
||||
pkt->convergence_duration = av_rescale_q(pkt->convergence_duration, src_tb, dst_tb);
|
||||
}
|
||||
#endif
|
||||
// since libav-11, ffmpeg-2.1
|
||||
#if !LIBAV_MODULE_CHECK(LIBAVCODEC, 56, 1, 0) && !FFMPEG_MODULE_CHECK(LIBAVCODEC, 55, 39, 100)
|
||||
int av_packet_copy_props(AVPacket *dst, const AVPacket *src)
|
||||
{
|
||||
dst->pts = src->pts;
|
||||
dst->dts = src->dts;
|
||||
dst->pos = src->pos;
|
||||
dst->duration = src->duration;
|
||||
dst->convergence_duration = src->convergence_duration;
|
||||
dst->flags = src->flags;
|
||||
dst->stream_index = src->stream_index;
|
||||
|
||||
for (int i = 0; i < src->side_data_elems; i++) {
|
||||
enum AVPacketSideDataType type = src->side_data[i].type;
|
||||
int size = src->side_data[i].size;
|
||||
uint8_t *src_data = src->side_data[i].data;
|
||||
uint8_t *dst_data = av_packet_new_side_data(dst, type, size);
|
||||
|
||||
if (!dst_data) {
|
||||
av_packet_free_side_data(dst);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
memcpy(dst_data, src_data, size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
// since libav-10, ffmpeg-2.1
|
||||
#if !LIBAV_MODULE_CHECK(LIBAVCODEC, 55, 34, 1) && !FFMPEG_MODULE_CHECK(LIBAVCODEC, 55, 39, 100)
|
||||
void av_packet_free_side_data(AVPacket *pkt)
|
||||
{
|
||||
for (int i = 0; i < pkt->side_data_elems; ++i)
|
||||
av_freep(&pkt->side_data[i].data);
|
||||
av_freep(&pkt->side_data);
|
||||
pkt->side_data_elems = 0;
|
||||
}
|
||||
#endif
|
||||
#if !AV_MODULE_CHECK(LIBAVCODEC, 55, 34, 1, 39, 101)
|
||||
int av_packet_ref(AVPacket *dst, const AVPacket *src)
|
||||
{
|
||||
#if QTAV_USE_FFMPEG(LIBAVCODEC)
|
||||
return av_copy_packet(dst, const_cast<AVPacket*>(src)); // not const in these versions
|
||||
#else // libav <=11 has no av_copy_packet
|
||||
#define DUP_DATA(dst, src, size, padding) \
|
||||
do { \
|
||||
void *data; \
|
||||
if (padding) { \
|
||||
if ((unsigned)(size) > \
|
||||
(unsigned)(size) + FF_INPUT_BUFFER_PADDING_SIZE) \
|
||||
goto failed_alloc; \
|
||||
data = av_malloc(size + FF_INPUT_BUFFER_PADDING_SIZE); \
|
||||
} else { \
|
||||
data = av_malloc(size); \
|
||||
} \
|
||||
if (!data) \
|
||||
goto failed_alloc; \
|
||||
memcpy(data, src, size); \
|
||||
if (padding) \
|
||||
memset((uint8_t*)data + size, 0, \
|
||||
FF_INPUT_BUFFER_PADDING_SIZE); \
|
||||
*((void**)&dst) = data; \
|
||||
} while (0)
|
||||
|
||||
*dst = *src;
|
||||
dst->data = NULL;
|
||||
dst->side_data = NULL;
|
||||
DUP_DATA(dst->data, src->data, dst->size, 1);
|
||||
dst->destruct = av_destruct_packet;
|
||||
if (dst->side_data_elems) {
|
||||
int i;
|
||||
DUP_DATA(dst->side_data, src->side_data,
|
||||
dst->side_data_elems * sizeof(*dst->side_data), 0);
|
||||
memset(dst->side_data, 0,
|
||||
dst->side_data_elems * sizeof(*dst->side_data));
|
||||
for (i = 0; i < dst->side_data_elems; i++) {
|
||||
DUP_DATA(dst->side_data[i].data, src->side_data[i].data, src->side_data[i].size, 1);
|
||||
dst->side_data[i].size = src->side_data[i].size;
|
||||
dst->side_data[i].type = src->side_data[i].type;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
failed_alloc:
|
||||
av_destruct_packet(dst);
|
||||
return AVERROR(ENOMEM);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#if !AV_MODULE_CHECK(LIBAVCODEC, 55, 52, 0, 63, 100)
|
||||
void avcodec_free_context(AVCodecContext **pavctx)
|
||||
{
|
||||
|
||||
AVCodecContext *avctx = *pavctx;
|
||||
if (!avctx)
|
||||
return;
|
||||
avcodec_close(avctx);
|
||||
av_freep(&avctx->extradata);
|
||||
av_freep(&avctx->subtitle_header);
|
||||
av_freep(&avctx->intra_matrix);
|
||||
av_freep(&avctx->inter_matrix);
|
||||
av_freep(&avctx->rc_override);
|
||||
av_freep(pavctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
const char *get_codec_long_name(enum AVCodecID id)
|
||||
{
|
||||
if (id == AV_CODEC_ID_NONE)
|
||||
return "none";
|
||||
const AVCodecDescriptor *cd = avcodec_descriptor_get(id);
|
||||
if (cd)
|
||||
return cd->long_name;
|
||||
av_log(NULL, AV_LOG_WARNING, "Codec 0x%x is not in the full list.\n", id);
|
||||
AVCodec *codec = avcodec_find_decoder(id);
|
||||
if (codec)
|
||||
return codec->long_name;
|
||||
codec = avcodec_find_encoder(id);
|
||||
if (codec)
|
||||
return codec->long_name;
|
||||
return "unknown_codec";
|
||||
}
|
||||
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
#if !AV_MODULE_CHECK(LIBAVFILTER, 2, 22, 0, 79, 100) //FF_API_AVFILTERPAD_PUBLIC
|
||||
const char *avfilter_pad_get_name(const AVFilterPad *pads, int pad_idx)
|
||||
{
|
||||
return pads[pad_idx].name;
|
||||
}
|
||||
|
||||
enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx)
|
||||
{
|
||||
return pads[pad_idx].type;
|
||||
}
|
||||
#endif
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
460
project/fm_viewer/fav/AVCompat.h
Normal file
460
project/fm_viewer/fav/AVCompat.h
Normal file
@@ -0,0 +1,460 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
solve the version problem and diffirent api in FFmpeg and libav
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
#ifndef QTAV_COMPAT_H
|
||||
#define QTAV_COMPAT_H
|
||||
|
||||
/*!
|
||||
NOTE: include this at last
|
||||
*/
|
||||
#define QTAV_USE_FFMPEG(MODULE) (MODULE##_VERSION_MICRO >= 100)
|
||||
#define QTAV_USE_LIBAV(MODULE) !QTAV_USE_FFMPEG(MODULE)
|
||||
#define FFMPEG_MODULE_CHECK(MODULE, MAJOR, MINOR, MICRO) \
|
||||
(QTAV_USE_FFMPEG(MODULE) && MODULE##_VERSION_INT >= AV_VERSION_INT(MAJOR, MINOR, MICRO))
|
||||
#define LIBAV_MODULE_CHECK(MODULE, MAJOR, MINOR, MICRO) \
|
||||
(QTAV_USE_LIBAV(MODULE) && MODULE##_VERSION_INT >= AV_VERSION_INT(MAJOR, MINOR, MICRO))
|
||||
#define AV_MODULE_CHECK(MODULE, MAJOR, MINOR, MICRO, MINOR2, MICRO2) \
|
||||
(LIBAV_MODULE_CHECK(MODULE, MAJOR, MINOR, MICRO) || FFMPEG_MODULE_CHECK(MODULE, MAJOR, MINOR2, MICRO2))
|
||||
/// example: AV_ENSURE(avcodec_close(avctx), false) will print error and return false if failed. AV_WARN just prints error.
|
||||
#define AV_ENSURE_OK(FUNC, ...) AV_RUN_CHECK(FUNC, return, __VA_ARGS__)
|
||||
#define AV_ENSURE(FUNC, ...) AV_RUN_CHECK(FUNC, return, __VA_ARGS__)
|
||||
#define AV_WARN(FUNC) AV_RUN_CHECK(FUNC, void)
|
||||
|
||||
#include "_fav_constants.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
/*UINT64_C: C99 math features, need -D__STDC_CONSTANT_MACROS in CXXFLAGS*/
|
||||
#endif /*__cplusplus*/
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/avstring.h>
|
||||
#include <libavutil/dict.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/log.h>
|
||||
#include <libavutil/mathematics.h> //AV_ROUND_UP, av_rescale_rnd for libav
|
||||
#include <libavutil/cpu.h>
|
||||
#include <libavutil/error.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/parseutils.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#include <libavutil/avstring.h>
|
||||
|
||||
#if !FFMPEG_MODULE_CHECK(LIBAVUTIL, 51, 73, 101)
|
||||
#include <libavutil/channel_layout.h>
|
||||
#endif
|
||||
|
||||
/* TODO: how to check whether we have swresample or not? how to check avresample?*/
|
||||
#include <libavutil/samplefmt.h>
|
||||
#if QTAV_HAVE(SWRESAMPLE)
|
||||
#include <libswresample/swresample.h>
|
||||
#ifndef LIBSWRESAMPLE_VERSION_INT //ffmpeg 0.9, swr 0.5
|
||||
#define LIBSWRESAMPLE_VERSION_INT AV_VERSION_INT(LIBSWRESAMPLE_VERSION_MAJOR, LIBSWRESAMPLE_VERSION_MINOR, LIBSWRESAMPLE_VERSION_MICRO)
|
||||
#endif //LIBSWRESAMPLE_VERSION_INT
|
||||
//ffmpeg >= 0.11.x. swr0.6.100: ffmpeg-0.10.x
|
||||
#define HAVE_SWR_GET_DELAY (LIBSWRESAMPLE_VERSION_INT > AV_VERSION_INT(0, 6, 100))
|
||||
#endif //QTAV_HAVE(SWRESAMPLE)
|
||||
#if QTAV_HAVE(AVRESAMPLE)
|
||||
#include <libavresample/avresample.h>
|
||||
#endif //QTAV_HAVE(AVRESAMPLE)
|
||||
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
#if !(USE_FFMPEG4)
|
||||
#include <libavfilter/avfiltergraph.h> //code is here for old version
|
||||
#endif // USE_FFMPEG4
|
||||
#include <libavfilter/avfilter.h>
|
||||
#include <libavfilter/buffersink.h>
|
||||
#include <libavfilter/buffersrc.h>
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
|
||||
#if QTAV_HAVE(AVDEVICE)
|
||||
#include <libavdevice/avdevice.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /*__cplusplus*/
|
||||
|
||||
/*!
|
||||
* Guide to uniform the api for different FFmpeg version(or other libraries)
|
||||
* We use the existing old api to simulater .
|
||||
* 1. The old version does not have this api: Just add it.
|
||||
* 2. The old version has similar api: Try using macro.
|
||||
* e.g. the old is bool my_play(char* data, size_t size)
|
||||
* the new is bool my_play2(const ByteArray& data)
|
||||
* change:
|
||||
* #define my_play2(data) my_play(data.data(), data.size());
|
||||
*
|
||||
* 3. The old version api is conflicted with the latest's. We can redefine the api
|
||||
* e.g. the old is bool my_play(char* data, size_t size)
|
||||
* the new is bool my_play(const ByteArray& data)
|
||||
* change:
|
||||
* typedef bool (*my_play_t)(const ByteArray&);
|
||||
* static my_play_t my_play_ptr = my_play; //using the existing my_play(char*, size_t)
|
||||
* #define my_play my_play_compat
|
||||
* inline bool my_play_compat(const ByteArray& data)
|
||||
* {
|
||||
* return my_play_ptr(data.data(), data.size());
|
||||
* }
|
||||
* 4. conflict macros
|
||||
* see av_err2str
|
||||
*/
|
||||
|
||||
#ifndef AV_VERSION_INT
|
||||
#define AV_VERSION_INT(a, b, c) (a<<16 | b<<8 | c)
|
||||
#endif /*AV_VERSION_INT*/
|
||||
|
||||
void ffmpeg_version_print();
|
||||
|
||||
#if !FFMPEG_MODULE_CHECK(LIBAVFORMAT, 56, 4, 101)
|
||||
int avio_feof(AVIOContext *s);
|
||||
#endif
|
||||
#if QTAV_USE_LIBAV(LIBAVFORMAT)
|
||||
int avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename);
|
||||
#endif
|
||||
//TODO: always inline
|
||||
/* --gnu option of the RVCT compiler also defines __GNUC__ */
|
||||
#if defined(__GNUC__) && !(defined(__ARMCC__) || defined(__CC_ARM))
|
||||
#define GCC_VERSION_AT_LEAST(major, minor, patch) \
|
||||
(__GNUC__ > major || (__GNUC__ == major && (__GNUC_MINOR__ > minor \
|
||||
|| (__GNUC_MINOR__ == minor && __GNUC_PATCHLEVEL__ >= patch))))
|
||||
#else
|
||||
/* Define this for !GCC compilers, just so we can write things like GCC_VERSION_AT_LEAST(4, 1, 0). */
|
||||
#define GCC_VERSION_AT_LEAST(major, minor, patch) 0
|
||||
#endif
|
||||
|
||||
//FFmpeg2.0, Libav10 2013-03-08 - Reference counted buffers - lavu 52.19.100/52.8.0, lavc 55.0.100 / 55.0.0, lavf 55.0.100 / 55.0.0, lavd 54.4.100 / 54.0.0, lavfi 3.5.0
|
||||
#define QTAV_HAVE_AVBUFREF AV_MODULE_CHECK(LIBAVUTIL, 52, 8, 0, 19, 100)
|
||||
|
||||
#if defined(_MSC_VER) || !defined(av_err2str) || (GCC_VERSION_AT_LEAST(4, 7, 0) && __cplusplus)
|
||||
#ifdef av_err2str
|
||||
#undef av_err2str
|
||||
/*#define av_make_error_string qtav_make_error_string*/
|
||||
#else
|
||||
/**
|
||||
* Fill the provided buffer with a string containing an error string
|
||||
* corresponding to the AVERROR code errnum.
|
||||
*
|
||||
* @param errbuf a buffer
|
||||
* @param errbuf_size size in bytes of errbuf
|
||||
* @param errnum error code to describe
|
||||
* @return the buffer in input, filled with the error description
|
||||
* @see av_strerror()
|
||||
*/
|
||||
static av_always_inline char *av_make_error_string(char *errbuf, size_t errbuf_size, int errnum)
|
||||
{
|
||||
av_strerror(errnum, errbuf, errbuf_size);
|
||||
return errbuf;
|
||||
}
|
||||
#endif /*av_err2str*/
|
||||
|
||||
#define AV_ERROR_MAX_STRING_SIZE 64
|
||||
#ifdef QT_CORE_LIB
|
||||
#include <QtCore/QSharedPointer>
|
||||
#define av_err2str(e) av_err2str_qsp(e).data()
|
||||
av_always_inline QSharedPointer<char> av_err2str_qsp(int errnum)
|
||||
{
|
||||
QSharedPointer<char> str((char*)calloc(AV_ERROR_MAX_STRING_SIZE, 1), ::free);
|
||||
av_strerror(errnum, str.data(), AV_ERROR_MAX_STRING_SIZE);
|
||||
return str;
|
||||
}
|
||||
#else
|
||||
av_always_inline char* av_err2str(int errnum)
|
||||
{
|
||||
static char str[AV_ERROR_MAX_STRING_SIZE];
|
||||
memset(str, 0, sizeof(str));
|
||||
return av_make_error_string(str, AV_ERROR_MAX_STRING_SIZE, errnum);
|
||||
}
|
||||
#endif /* QT_CORE_LIB */
|
||||
#endif /*!defined(av_err2str) || GCC_VERSION_AT_LEAST(4, 7, 2)*/
|
||||
|
||||
#if (LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(52,23,0))
|
||||
#define avcodec_decode_audio3(avctx, samples, frame_size_ptr, avpkt) \
|
||||
avcodec_decode_audio2(avctx, samples, frame_size_ptr, (*avpkt).data, (*avpkt).size);
|
||||
|
||||
#endif /*AV_VERSION_INT(52,23,0)*/
|
||||
|
||||
#if (LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(52,101,0))
|
||||
#define av_dump_format(...) dump_format(__VA_ARGS__)
|
||||
#endif /*AV_VERSION_INT(52,101,0)*/
|
||||
|
||||
#if QTAV_HAVE(SWRESAMPLE) && (LIBSWRESAMPLE_VERSION_INT <= AV_VERSION_INT(0, 5, 0))
|
||||
#define swresample_version() LIBSWRESAMPLE_VERSION_INT //we can not know the runtime version, so just use build time version
|
||||
#define swresample_configuration() "Not available."
|
||||
#define swresample_license() "Not available."
|
||||
#endif
|
||||
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51, 32, 0)
|
||||
int64_t av_get_default_channel_layout(int nb_channels);
|
||||
#endif
|
||||
/*
|
||||
* mapping avresample to swresample
|
||||
* https://github.com/xbmc/xbmc/commit/274679d
|
||||
*/
|
||||
#if (QTAV_HAVE(SWR_AVR_MAP) || !QTAV_HAVE(SWRESAMPLE)) && QTAV_HAVE(AVRESAMPLE)
|
||||
#ifndef SWR_CH_MAX
|
||||
#ifdef AVRESAMPLE_MAX_CHANNELS
|
||||
#define SWR_CH_MAX AVRESAMPLE_MAX_CHANNELS
|
||||
#else
|
||||
#define SWR_CH_MAX 64
|
||||
#endif //AVRESAMPLE_MAX_CHANNELS
|
||||
#endif //SWR_CH_MAX
|
||||
#define SwrContext AVAudioResampleContext
|
||||
#define swr_init(ctx) avresample_open(ctx)
|
||||
//free context and set pointer to null. see swresample
|
||||
#define swr_free(ctx) \
|
||||
if (ctx && *ctx) { \
|
||||
avresample_close(*ctx); \
|
||||
*ctx = 0; \
|
||||
}
|
||||
#define swr_get_class() avresample_get_class()
|
||||
#define swr_alloc() avresample_alloc_context()
|
||||
//#define swr_next_pts()
|
||||
#define swr_set_compensation() avresample_set_compensation()
|
||||
#define swr_set_channel_mapping(ctx, map) avresample_set_channel_mapping(ctx, map)
|
||||
#define swr_set_matrix(ctx, matrix, stride) avresample_set_matrix(ctx, matrix, stride)
|
||||
//#define swr_drop_output(ctx, count)
|
||||
//#define swr_inject_silence(ctx, count)
|
||||
#define swr_get_delay(ctx, ...) avresample_get_delay(ctx)
|
||||
#if LIBAVRESAMPLE_VERSION_INT >= AV_VERSION_INT(1, 0, 0) //ffmpeg >= 1.1
|
||||
#define swr_convert(ctx, out, out_count, in, in_count) \
|
||||
avresample_convert(ctx, out, 0, out_count, const_cast<uint8_t**>(in), 0, in_count)
|
||||
#else
|
||||
#define swr_convert(ctx, out, out_count, in, in_count) \
|
||||
avresample_convert(ctx, (void**)out, 0, out_count, (void**)in, 0, in_count)
|
||||
#define HAVE_SWR_GET_DELAY 1
|
||||
#define swr_get_delay(ctx, ...) avresample_get_delay(ctx)
|
||||
#endif
|
||||
struct SwrContext *swr_alloc_set_opts(struct SwrContext *s, int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate, int64_t in_ch_layout, enum AVSampleFormat in_sample_fmt, int in_sample_rate, int log_offset, void *log_ctx);
|
||||
#define swresample_version() avresample_version()
|
||||
#define swresample_configuration() avresample_configuration()
|
||||
#define swresample_license() avresample_license()
|
||||
#endif //MAP_SWR_AVR
|
||||
|
||||
|
||||
/* For FFmpeg < 2.0
|
||||
* FF_API_PIX_FMT macro?
|
||||
* 51.42.0: PIX_FMT_* -> AV_PIX_FMT_*, PixelFormat -> AVPixelFormat
|
||||
* so I introduce QTAV_PIX_FMT_C(X) for internal use
|
||||
* FFmpeg n1.1 AVPixelFormat
|
||||
*/
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 13, 100) //(51, 42, 0)
|
||||
typedef enum PixelFormat AVPixelFormat; // so we must avoid using enum AVPixelFormat
|
||||
#define QTAV_PIX_FMT_C(X) PIX_FMT_##X
|
||||
#else //FFmpeg >= 2.0
|
||||
typedef enum AVPixelFormat AVPixelFormat;
|
||||
#define QTAV_PIX_FMT_C(X) AV_PIX_FMT_##X
|
||||
#endif //AV_VERSION_INT(51, 42, 0)
|
||||
// FF_API_PIX_FMT
|
||||
#ifdef PixelFormat
|
||||
#undef PixelFormat
|
||||
#endif
|
||||
|
||||
// AV_PIX_FMT_FLAG_XXX was PIX_FMT_XXX before FFmpeg 2.0
|
||||
// AV_PIX_FMT_FLAG_ALPHA was added at 52.2.0. but version.h not changed
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 2, 1) //git cbe5a60c9d495df0fb4775b064f06719b70b9952
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51, 22, 1) //git 38d553322891c8e47182f05199d19888422167dc
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51, 19, 0) //git 6b0768e2021b90215a2ab55ed427bce91d148148
|
||||
#define PIX_FMT_PLANAR 16 ///< At least one pixel component is not in the first data plane
|
||||
#define PIX_FMT_RGB 32 ///< The pixel format contains RGB-like data (as opposed to YUV/grayscale)
|
||||
#endif //AV_VERSION_INT(51, 19, 0)
|
||||
#define PIX_FMT_PSEUDOPAL 64 //why not defined in FFmpeg 0.9 lavu51.32.0 but git log says 51.22.1 defined it?
|
||||
#endif //AV_VERSION_INT(51, 22, 1)
|
||||
#define PIX_FMT_ALPHA 128 ///< The pixel format has an alpha channel
|
||||
#endif //AV_VERSION_INT(52, 2, 1)
|
||||
|
||||
#ifndef PIX_FMT_PLANAR
|
||||
#define PIX_FMT_PLANAR 16
|
||||
#endif //PIX_FMT_PLANAR
|
||||
#ifndef PIX_FMT_RGB
|
||||
#define PIX_FMT_RGB 32
|
||||
#endif //PIX_FMT_RGB
|
||||
#ifndef PIX_FMT_PSEUDOPAL
|
||||
#define PIX_FMT_PSEUDOPAL 64
|
||||
#endif //PIX_FMT_PSEUDOPAL
|
||||
#ifndef PIX_FMT_ALPHA
|
||||
#define PIX_FMT_ALPHA 128
|
||||
#endif //PIX_FMT_ALPHA
|
||||
|
||||
/*
|
||||
* rename PIX_FMT_* flags to AV_PIX_FMT_FLAG_*. git e6c4ac7b5f038be56dfbb0171f5dd0cb850d9b28
|
||||
*/
|
||||
//#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 11, 0)
|
||||
#ifndef AV_PIX_FMT_FLAG_BE
|
||||
#define AV_PIX_FMT_FLAG_BE PIX_FMT_BE
|
||||
#define AV_PIX_FMT_FLAG_PAL PIX_FMT_PAL
|
||||
#define AV_PIX_FMT_FLAG_BITSTREAM PIX_FMT_BITSTREAM
|
||||
#define AV_PIX_FMT_FLAG_HWACCEL PIX_FMT_HWACCEL
|
||||
|
||||
// FFmpeg >= 0.9, libav >= 0.8.8(51,22,1)
|
||||
#define AV_PIX_FMT_FLAG_PLANAR PIX_FMT_PLANAR
|
||||
#define AV_PIX_FMT_FLAG_RGB PIX_FMT_RGB
|
||||
|
||||
// FFmpeg >= 1.0, libav >= 9.7
|
||||
#define AV_PIX_FMT_FLAG_PSEUDOPAL PIX_FMT_PSEUDOPAL
|
||||
// FFmpeg >= 1.1, libav >= 9.7
|
||||
#define AV_PIX_FMT_FLAG_ALPHA PIX_FMT_ALPHA
|
||||
#endif //AV_PIX_FMT_FLAG_BE
|
||||
//#endif //AV_VERSION_INT(52, 11, 0)
|
||||
// FFmpeg >= 1.1, but use internal av_pix_fmt_descriptors. FFmpeg < 1.1 has extern av_pix_fmt_descriptors
|
||||
// used by av_pix_fmt_count_planes
|
||||
#if !AV_MODULE_CHECK(LIBAVUTIL, 52, 3, 0, 13, 100)
|
||||
const AVPixFmtDescriptor *av_pix_fmt_desc_get(AVPixelFormat pix_fmt);
|
||||
const AVPixFmtDescriptor *av_pix_fmt_desc_next(const AVPixFmtDescriptor *prev);
|
||||
AVPixelFormat av_pix_fmt_desc_get_id(const AVPixFmtDescriptor *desc);
|
||||
#endif // !AV_MODULE_CHECK(LIBAVUTIL, 52, 3, 0, 13, 100)
|
||||
#if !FFMPEG_MODULE_CHECK(LIBAVUTIL, 52, 48, 101) // since ffmpeg2.1, libavutil53.16.0 (FF_API_AVFRAME_COLORSPACE), git 8c02adc
|
||||
enum AVColorSpace av_frame_get_colorspace(const AVFrame *frame);
|
||||
enum AVColorRange av_frame_get_color_range(const AVFrame *frame);
|
||||
#endif
|
||||
/*
|
||||
* lavu 52.9.0 git 2c328a907978b61949fd20f7c991803174337855
|
||||
* FFmpeg >= 2.0.
|
||||
*/
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 38, 100)
|
||||
int av_pix_fmt_count_planes(AVPixelFormat pix_fmt);
|
||||
#endif //AV_VERSION_INT(52, 38, 100)
|
||||
|
||||
// FFmpeg < 1.0 has no av_samples_copy
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51, 73, 101)
|
||||
/**
|
||||
* Copy samples from src to dst.
|
||||
*
|
||||
* @param dst destination array of pointers to data planes
|
||||
* @param src source array of pointers to data planes
|
||||
* @param dst_offset offset in samples at which the data will be written to dst
|
||||
* @param src_offset offset in samples at which the data will be read from src
|
||||
* @param nb_samples number of samples to be copied
|
||||
* @param nb_channels number of audio channels
|
||||
* @param sample_fmt audio sample format
|
||||
*/
|
||||
int av_samples_copy(uint8_t **dst, uint8_t * const *src, int dst_offset,
|
||||
int src_offset, int nb_samples, int nb_channels,
|
||||
enum AVSampleFormat sample_fmt);
|
||||
#endif //AV_VERSION_INT(51, 73, 101)
|
||||
|
||||
// < ffmpeg 1.0
|
||||
//#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 59, 100)
|
||||
#if AV_MODULE_CHECK(LIBAVCODEC, 54, 25, 0, 51, 100)
|
||||
#define QTAV_CODEC_ID(X) AV_CODEC_ID_##X
|
||||
#else
|
||||
typedef enum CodecID AVCodecID;
|
||||
#define QTAV_CODEC_ID(X) CODEC_ID_##X
|
||||
#endif
|
||||
|
||||
/* av_frame_alloc
|
||||
* since FFmpeg2.0: 2.0.4 avcodec-55.18.102, avutil-52.38.100 (1.2.7 avcodec-54.92.100,avutil-52.18.100)
|
||||
* since libav10.0: 10.2 avcodec55.34.1, avutil-53.3.0
|
||||
* the same as avcodec_alloc_frame() (deprecated since 2.2). AVFrame was in avcodec.h, now in avutil/frame.h
|
||||
*/
|
||||
#if !AV_MODULE_CHECK(LIBAVCODEC, 55, 34, 0, 18, 100)
|
||||
#define av_frame_alloc() avcodec_alloc_frame()
|
||||
#if QTAV_USE_LIBAV(LIBAVCODEC) || FFMPEG_MODULE_CHECK(LIBAVCODEC, 54, 59, 100)
|
||||
#define av_frame_free(f) avcodec_free_frame(f)
|
||||
#else
|
||||
#define av_frame_free(f) av_free(f)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if QTAV_USE_LIBAV(LIBAVCODEC)
|
||||
const char *avcodec_get_name(enum AVCodecID id);
|
||||
#endif
|
||||
#if !AV_MODULE_CHECK(LIBAVCODEC, 55, 55, 0, 68, 100)
|
||||
void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb);
|
||||
#endif
|
||||
// since libav-11, ffmpeg-2.1
|
||||
#if !LIBAV_MODULE_CHECK(LIBAVCODEC, 56, 1, 0) && !FFMPEG_MODULE_CHECK(LIBAVCODEC, 55, 39, 100)
|
||||
int av_packet_copy_props(AVPacket *dst, const AVPacket *src);
|
||||
#endif
|
||||
// since libav-10, ffmpeg-2.1
|
||||
#if !LIBAV_MODULE_CHECK(LIBAVCODEC, 55, 34, 1) && !FFMPEG_MODULE_CHECK(LIBAVCODEC, 55, 39, 100)
|
||||
void av_packet_free_side_data(AVPacket *pkt);
|
||||
#endif
|
||||
//ffmpeg2.1 libav10
|
||||
#if !AV_MODULE_CHECK(LIBAVCODEC, 55, 34, 1, 39, 101)
|
||||
int av_packet_ref(AVPacket *dst, const AVPacket *src);
|
||||
#define av_packet_unref(pkt) av_free_packet(pkt)
|
||||
#endif
|
||||
|
||||
#if !AV_MODULE_CHECK(LIBAVCODEC, 55, 52, 0, 63, 100)
|
||||
void avcodec_free_context(AVCodecContext **pavctx);
|
||||
#endif
|
||||
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
// ffmpeg2.0 2013-07-03 - 838bd73 - lavfi 3.78.100 - avfilter.h
|
||||
#if QTAV_USE_LIBAV(LIBAVFILTER)
|
||||
#define avfilter_graph_parse_ptr(pGraph, pFilters, ppInputs, ppOutputs, pLog) avfilter_graph_parse(pGraph, pFilters, *ppInputs, *ppOutputs, pLog)
|
||||
#elif !FFMPEG_MODULE_CHECK(LIBAVFILTER, 3, 78, 100)
|
||||
#define avfilter_graph_parse_ptr(pGraph, pFilters, ppInputs, ppOutputs, pLog) avfilter_graph_parse(pGraph, pFilters, ppInputs, ppOutputs, pLog)
|
||||
#endif //QTAV_USE_LIBAV(LIBAVFILTER)
|
||||
|
||||
//ffmpeg1.0 2012-06-12 - c7b9eab / 84b9fbe - lavfi 2.79.100 / 2.22.0 - avfilter.h
|
||||
#if !AV_MODULE_CHECK(LIBAVFILTER, 2, 22, 0, 79, 100) //FF_API_AVFILTERPAD_PUBLIC
|
||||
const char *avfilter_pad_get_name(const AVFilterPad *pads, int pad_idx);
|
||||
enum AVMediaType avfilter_pad_get_type(const AVFilterPad *pads, int pad_idx);
|
||||
#endif
|
||||
///ffmpeg1.0 lavfi 2.74.100 / 2.17.0. was in ffmpeg <libavfilter/avcodec.h> in old ffmpeg and now are in avfilter.h and deprecated. declare here to avoid version check
|
||||
#if QTAV_USE_FFMPEG(LIBAVFILTER)
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
struct AVFilterBufferRef;
|
||||
int avfilter_copy_buf_props(AVFrame *dst, const AVFilterBufferRef *src);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
|
||||
/* helper functions */
|
||||
const char *get_codec_long_name(AVCodecID id);
|
||||
|
||||
// AV_CODEC_ID_H265 is a macro defined as AV_CODEC_ID_HEVC in ffmpeg but not in libav. so we can use FF_PROFILE_HEVC_MAIN to avoid libavcodec version check. (from ffmpeg 2.1)
|
||||
#ifndef FF_PROFILE_HEVC_MAIN //libav does not define it
|
||||
#define AV_CODEC_ID_HEVC ((AVCodecID)0) //QTAV_CODEC_ID(NONE)
|
||||
#define CODEC_ID_HEVC ((AVCodecID)0) //QTAV_CODEC_ID(NONE)
|
||||
#define FF_PROFILE_HEVC_MAIN -1
|
||||
#define FF_PROFILE_HEVC_MAIN_10 -1
|
||||
#endif
|
||||
#if !FFMPEG_MODULE_CHECK(LIBAVCODEC, 54, 92, 100) && !LIBAV_MODULE_CHECK(LIBAVCODEC, 55, 34, 1) //ffmpeg1.2 libav10
|
||||
#define AV_CODEC_ID_VP9 ((AVCodecID)0) //QTAV_CODEC_ID(NONE)
|
||||
#define CODEC_ID_VP9 ((AVCodecID)0) //QTAV_CODEC_ID(NONE)
|
||||
#endif
|
||||
#ifndef FF_PROFILE_VP9_0
|
||||
#define FF_PROFILE_VP9_0 0
|
||||
#define FF_PROFILE_VP9_1 1
|
||||
#define FF_PROFILE_VP9_2 2
|
||||
#define FF_PROFILE_VP9_3 3
|
||||
#endif
|
||||
|
||||
#define AV_RUN_CHECK(FUNC, RETURN, ...) do { \
|
||||
int ret = FUNC; \
|
||||
if (ret < 0) { \
|
||||
char str[AV_ERROR_MAX_STRING_SIZE]; \
|
||||
memset(str, 0, sizeof(str)); \
|
||||
av_strerror(ret, str, sizeof(str)); \
|
||||
av_log(NULL, AV_LOG_WARNING, "Error " #FUNC " @%d " __FILE__ ": (%#x) %s\n", __LINE__, ret, str); \
|
||||
RETURN __VA_ARGS__; \
|
||||
} } while(0)
|
||||
|
||||
#endif //QTAV_COMPAT_H
|
||||
287
project/fm_viewer/fav/AVDecoder.cpp
Normal file
287
project/fm_viewer/fav/AVDecoder.cpp
Normal file
@@ -0,0 +1,287 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AVDecoder.h"
|
||||
#include "AVDecoder_p.h"
|
||||
#include "version.h"
|
||||
#include "internal.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
static AVCodec* get_codec(const QString &name, const QString& hwa, AVCodecID cid)
|
||||
{
|
||||
QString fullname(name);
|
||||
if (name.isEmpty()) {
|
||||
if (hwa.isEmpty())
|
||||
return avcodec_find_decoder(cid);
|
||||
fullname = QString("%1_%2").arg(avcodec_get_name(cid)).arg(hwa);
|
||||
}
|
||||
AVCodec *codec = avcodec_find_decoder_by_name(fullname.toUtf8().constData());
|
||||
if (codec)
|
||||
return codec;
|
||||
const AVCodecDescriptor* cd = avcodec_descriptor_get_by_name(fullname.toUtf8().constData());
|
||||
if (cd)
|
||||
return avcodec_find_decoder(cd->id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AVDecoder::AVDecoder(AVDecoderPrivate &d)
|
||||
:DPTR_INIT(&d)
|
||||
{
|
||||
avcodec_register_all(); // avcodec_find_decoder will always be used
|
||||
}
|
||||
|
||||
AVDecoder::~AVDecoder()
|
||||
{
|
||||
setCodecContext(0); // FIXME: will call virtual
|
||||
}
|
||||
|
||||
QString AVDecoder::name() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString AVDecoder::description() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool AVDecoder::open()
|
||||
{
|
||||
DPTR_D(AVDecoder);
|
||||
// codec_ctx can't be null for none-ffmpeg based decoders because we may use it's properties in those decoders
|
||||
if (!d.codec_ctx) {
|
||||
qWarning("FFmpeg codec context not ready");
|
||||
return false;
|
||||
}
|
||||
const QString hwa = property("hwaccel").toString();
|
||||
AVCodec* codec = get_codec(codecName(), hwa, d.codec_ctx->codec_id);
|
||||
if (!codec) { // TODO: can be null for none-ffmpeg based decoders
|
||||
QString es("No codec could be found for '%1'");
|
||||
if (d.codec_name.isEmpty()) {
|
||||
es = es.arg(QLatin1String(avcodec_get_name(d.codec_ctx->codec_id)));
|
||||
if (!hwa.isEmpty())
|
||||
es.append('_').append(hwa);
|
||||
} else {
|
||||
es = es.arg(d.codec_name);
|
||||
}
|
||||
qWarning() << es;
|
||||
AVError::ErrorCode ec(AVError::CodecError);
|
||||
switch (d.codec_ctx->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
ec = AVError::VideoCodecNotFound;
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
ec = AVError::AudioCodecNotFound;
|
||||
break;
|
||||
case AVMEDIA_TYPE_SUBTITLE:
|
||||
ec = AVError::SubtitleCodecNotFound;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Q_EMIT error(AVError(ec, es));
|
||||
return false;
|
||||
}
|
||||
// hwa extra init can be here
|
||||
if (!d.open()) {
|
||||
d.close();
|
||||
return false;
|
||||
}
|
||||
// CODEC_FLAG_OUTPUT_CORRUPT, CODEC_FLAG2_SHOW_ALL?
|
||||
// TODO: skip for none-ffmpeg based decoders
|
||||
d.applyOptionsForDict();
|
||||
|
||||
av_opt_set_int(d.codec_ctx, "refcounted_frames", d.enableFrameRef(), 0); // why dict may have no effect?
|
||||
|
||||
d.codec_ctx->thread_count = 1;
|
||||
d.codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||
d.codec_ctx->flags2 |= AV_CODEC_FLAG2_CHUNKS;
|
||||
|
||||
// TODO: only open for ff decoders
|
||||
//av_dict_set(&d.dict, "lowres", "1", 0);
|
||||
// dict is used for a specified AVCodec options (priv_class), av_opt_set_xxx(avctx) is only for avctx
|
||||
AV_ENSURE_OK(avcodec_open2(d.codec_ctx, codec, d.options.isEmpty() ? NULL : &d.dict), false);
|
||||
d.is_open = true;
|
||||
static const char* thread_name[] = { "Single", "Frame", "Slice"};
|
||||
qDebug("%s thread type: %s, count: %d", metaObject()->className(), thread_name[d.codec_ctx->active_thread_type], d.codec_ctx->thread_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AVDecoder::close()
|
||||
{
|
||||
if (!isOpen()) {
|
||||
return true;
|
||||
}
|
||||
DPTR_D(AVDecoder);
|
||||
d.is_open = false;
|
||||
// hwa extra finalize can be here
|
||||
flush();
|
||||
d.close();
|
||||
// TODO: reset config?
|
||||
if (d.codec_ctx) {
|
||||
AV_ENSURE_OK(avcodec_close(d.codec_ctx), false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AVDecoder::isOpen() const
|
||||
{
|
||||
return d_func().is_open;
|
||||
}
|
||||
|
||||
void AVDecoder::flush()
|
||||
{
|
||||
if (!isAvailable())
|
||||
return;
|
||||
if (!isOpen())
|
||||
return;
|
||||
avcodec_flush_buffers(d_func().codec_ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
* do nothing if equal
|
||||
* close the old one. the codec context can not be shared in more than 1 decoder.
|
||||
*/
|
||||
void AVDecoder::setCodecContext(void *codecCtx)
|
||||
{
|
||||
DPTR_D(AVDecoder);
|
||||
AVCodecContext *ctx = (AVCodecContext*)codecCtx;
|
||||
if (ctx == NULL || d.codec_ctx == ctx)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (isOpen())
|
||||
{
|
||||
#if !(OFF_OTHER_DEBUG)
|
||||
qWarning("Can not copy codec properties when it's open");
|
||||
#endif
|
||||
close(); //
|
||||
}
|
||||
d.is_open = false;
|
||||
if (!ctx) {
|
||||
avcodec_free_context(&d.codec_ctx);
|
||||
d.codec_ctx = 0;
|
||||
return;
|
||||
}
|
||||
if (!d.codec_ctx)
|
||||
d.codec_ctx = avcodec_alloc_context3(NULL);
|
||||
// avcodec_alloc_context3(codec) equals to avcodec_alloc_context3(NULL) + avcodec_get_context_defaults3(codec), codec specified private data is initialized
|
||||
if (!d.codec_ctx) {
|
||||
qWarning("avcodec_alloc_context3 failed");
|
||||
return;
|
||||
}
|
||||
AV_ENSURE_OK(avcodec_copy_context(d.codec_ctx, ctx));
|
||||
}
|
||||
|
||||
//TODO: reset other parameters?
|
||||
void* AVDecoder::codecContext() const
|
||||
{
|
||||
return d_func().codec_ctx;
|
||||
}
|
||||
|
||||
void AVDecoder::setCodecName(const QString &name)
|
||||
{
|
||||
DPTR_D(AVDecoder);
|
||||
if (d.codec_name == name)
|
||||
return;
|
||||
d.codec_name = name;
|
||||
Q_EMIT codecNameChanged();
|
||||
}
|
||||
|
||||
QString AVDecoder::codecName() const
|
||||
{
|
||||
DPTR_D(const AVDecoder);
|
||||
return d.codec_name;
|
||||
}
|
||||
|
||||
bool AVDecoder::isAvailable() const
|
||||
{
|
||||
return d_func().codec_ctx != 0;
|
||||
}
|
||||
|
||||
int AVDecoder::undecodedSize() const
|
||||
{
|
||||
return d_func().undecoded_size;
|
||||
}
|
||||
|
||||
void AVDecoder::setOptions(const QVariantHash &dict)
|
||||
{
|
||||
DPTR_D(AVDecoder);
|
||||
d.options = dict;
|
||||
// if dict is empty, can not return here, default options will be set for AVCodecContext
|
||||
// apply to AVCodecContext
|
||||
d.applyOptionsForContext();
|
||||
/* set AVDecoder meta properties.
|
||||
* we do not check whether the property exists thus we can set dynamic properties.
|
||||
*/
|
||||
if (dict.isEmpty())
|
||||
return;
|
||||
if (name() == QLatin1String("avcodec"))
|
||||
return;
|
||||
QVariant opt(dict);
|
||||
if (dict.contains(name()))
|
||||
opt = dict.value(name());
|
||||
else if (dict.contains(name().toLower()))
|
||||
opt = dict.value(name().toLower());
|
||||
Internal::setOptionsForQObject(opt, this);
|
||||
}
|
||||
|
||||
QVariantHash AVDecoder::options() const
|
||||
{
|
||||
return d_func().options;
|
||||
}
|
||||
|
||||
void AVDecoderPrivate::applyOptionsForDict()
|
||||
{
|
||||
if (dict) {
|
||||
av_dict_free(&dict);
|
||||
dict = 0; //aready 0 in av_free
|
||||
}
|
||||
// enable ref if possible
|
||||
av_dict_set(&dict, "refcounted_frames", enableFrameRef() ? "1" : "0", 0);
|
||||
if (options.isEmpty())
|
||||
return;
|
||||
// TODO: use QVariantMap only
|
||||
if (!options.contains(QStringLiteral("avcodec")))
|
||||
return;
|
||||
qDebug("set AVCodecContext dict:");
|
||||
// workaround for VideoDecoderFFmpeg. now it does not call av_opt_set_xxx, so set here in dict
|
||||
// TODO: wrong if opt is empty
|
||||
Internal::setOptionsToDict(options.value(QStringLiteral("avcodec")), &dict);
|
||||
}
|
||||
|
||||
void AVDecoderPrivate::applyOptionsForContext()
|
||||
{
|
||||
if (!codec_ctx)
|
||||
return;
|
||||
if (options.isEmpty()) {
|
||||
// av_opt_set_defaults(codec_ctx); //can't set default values! result maybe unexpected
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options.contains(QStringLiteral("avcodec")))
|
||||
return;
|
||||
// workaround for VideoDecoderFFmpeg. now it does not call av_opt_set_xxx, so set here in dict
|
||||
// TODO: wrong if opt is empty
|
||||
Internal::setOptionsToFFmpegObj(options.value(QStringLiteral("avcodec")), codec_ctx);
|
||||
}
|
||||
} //namespace FAV
|
||||
97
project/fm_viewer/fav/AVDecoder.h
Normal file
97
project/fm_viewer/fav/AVDecoder.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QAV_DECODER_H
|
||||
#define QAV_DECODER_H
|
||||
|
||||
#include "AVError.h"
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class Packet;
|
||||
class AVDecoderPrivate;
|
||||
class Q_AV_EXPORT AVDecoder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
DPTR_DECLARE_PRIVATE(AVDecoder)
|
||||
//Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
|
||||
public:
|
||||
virtual ~AVDecoder();
|
||||
virtual QString name() const;
|
||||
virtual QString description() const;
|
||||
/*!
|
||||
* default is open FFmpeg codec context
|
||||
* codec config must be done before open
|
||||
* NOTE: open() and close() are not thread safe. You'd better call them in the same thread.
|
||||
*/
|
||||
virtual bool open();
|
||||
virtual bool close();
|
||||
bool isOpen() const;
|
||||
virtual void flush();
|
||||
void setCodecContext(void* codecCtx); //protected
|
||||
void* codecContext() const;
|
||||
/*not available if AVCodecContext == 0*/
|
||||
bool isAvailable() const;
|
||||
#if (PLAY_SYNC_FIX2)
|
||||
virtual bool send(const Packet* packet) = 0;
|
||||
virtual bool receive() = 0;
|
||||
#endif // PLAY_SYNC_FIX2
|
||||
virtual bool decode(const Packet& packet) = 0;
|
||||
|
||||
|
||||
int undecodedSize() const; //TODO: remove. always decode whole input data completely
|
||||
|
||||
// avcodec_open2
|
||||
/*!
|
||||
* \brief setOptions
|
||||
* 1. If has key "avcodec", it's value (suboption, a hash or map) will be used to set AVCodecContext use av_opt_set and av_dict_set. A value of hash type is ignored.
|
||||
* we can ignore the flags used in av_dict_xxx because we can use hash api.
|
||||
* empty value does nothing to current context if it is open, but will clear AVDictionary in the next open.
|
||||
* AVDictionary is used in avcodec_open2() and will not change unless user call setOptions().
|
||||
* 2. Set QObject properties for AVDecoder. Use AVDecoder::name() or lower case as a key to set properties. If key not found, assume key is "avcodec"
|
||||
* 3. If no ket AVDecoder::name() found in the option, set key-value pairs as QObject property-value pairs.
|
||||
* \param dict
|
||||
* example:
|
||||
* "avcodec": {"vismv":"pf"}, "vaapi":{"display":"DRM"}, "copyMode": "ZeroCopy"
|
||||
* means set avcodec context option vismv=>pf, VA-API display (qt property) to DRM when using VA-API, set copyMode (GPU decoders) property to ZeroCopy
|
||||
*/
|
||||
void setOptions(const QVariantHash &dict);
|
||||
QVariantHash options() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void error(const FAV::AVError& e); //explictly use FAV::AVError in connection for Qt4 syntax
|
||||
void descriptionChanged();
|
||||
protected:
|
||||
AVDecoder(AVDecoderPrivate& d);
|
||||
DPTR_DECLARE(AVDecoder)
|
||||
// force a codec. only used by avcodec sw decoders. TODO: move to public? profile set?
|
||||
void setCodecName(const QString& name);
|
||||
QString codecName() const;
|
||||
virtual void codecNameChanged() {}//signals can not be decared virtual (winrt)
|
||||
private:
|
||||
Q_DISABLE_COPY(AVDecoder)
|
||||
AVDecoder(); // base class, not direct create. only final class has is enough
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QAV_DECODER_H
|
||||
147
project/fm_viewer/fav/AVDecoder_p.h
Normal file
147
project/fm_viewer/fav/AVDecoder_p.h
Normal file
@@ -0,0 +1,147 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2015 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AVDECODER_P_H
|
||||
#define QTAV_AVDECODER_P_H
|
||||
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QVector>
|
||||
#include "_fav_constants.h"
|
||||
#include "AVCompat.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
// always define the class to avoid macro check when using it
|
||||
class AVFrameBuffers {
|
||||
#if QTAV_HAVE(AVBUFREF)
|
||||
QVector<AVBufferRef*> buf;
|
||||
#endif
|
||||
public:
|
||||
AVFrameBuffers(AVFrame* frame) {
|
||||
Q_UNUSED(frame);
|
||||
#if QTAV_HAVE(AVBUFREF)
|
||||
if (!frame->buf[0]) { //not ref counted. duplicate data?
|
||||
return;
|
||||
}
|
||||
|
||||
buf.reserve(frame->nb_extended_buf + FF_ARRAY_ELEMS(frame->buf));
|
||||
buf.resize(frame->nb_extended_buf + FF_ARRAY_ELEMS(frame->buf));
|
||||
for (int i = 0; i < (int)FF_ARRAY_ELEMS(frame->buf); ++i) {
|
||||
if (!frame->buf[i]) //so not use planes + nb_extended_buf!
|
||||
continue;
|
||||
buf[i] = av_buffer_ref(frame->buf[i]);
|
||||
if (!buf[i]) {
|
||||
qWarning("av_buffer_ref(frame->buf[%d]) error", i);
|
||||
}
|
||||
}
|
||||
if (!frame->extended_buf)
|
||||
return;
|
||||
for (int i = 0; i < frame->nb_extended_buf; ++i) {
|
||||
const int k = buf.size() + i - frame->nb_extended_buf;
|
||||
buf[k] = av_buffer_ref(frame->extended_buf[i]);
|
||||
if (!buf[k]) {
|
||||
qWarning("av_buffer_ref(frame->extended_buf[%d]) error", i);
|
||||
}
|
||||
}
|
||||
#endif //QTAV_HAVE(AVBUFREF)
|
||||
}
|
||||
~AVFrameBuffers() {
|
||||
#if QTAV_HAVE(AVBUFREF)
|
||||
foreach (AVBufferRef* b, buf) {
|
||||
av_buffer_unref(&b);
|
||||
}
|
||||
#endif //QTAV_HAVE(AVBUFREF)
|
||||
}
|
||||
};
|
||||
typedef QSharedPointer<AVFrameBuffers> AVFrameBuffersRef;
|
||||
|
||||
class Q_AV_PRIVATE_EXPORT AVDecoderPrivate : public DPtrPrivate<AVDecoder>
|
||||
{
|
||||
public:
|
||||
static const char* getProfileName(AVCodecID id, int profile) {
|
||||
AVCodec *c = avcodec_find_decoder(id);
|
||||
if (!c)
|
||||
return "Unknow";
|
||||
return av_get_profile_name(c, profile);
|
||||
}
|
||||
static const char* getProfileName(const AVCodecContext* ctx) {
|
||||
if (ctx->codec)
|
||||
return av_get_profile_name(ctx->codec, ctx->profile);
|
||||
return getProfileName(ctx->codec_id, ctx->profile);
|
||||
}
|
||||
|
||||
AVDecoderPrivate():
|
||||
codec_ctx(0)
|
||||
, available(true)
|
||||
, is_open(false)
|
||||
, undecoded_size(0)
|
||||
, dict(0)
|
||||
{
|
||||
codec_ctx = avcodec_alloc_context3(NULL);
|
||||
}
|
||||
virtual ~AVDecoderPrivate() {
|
||||
if (dict) {
|
||||
av_dict_free(&dict);
|
||||
}
|
||||
if (codec_ctx) {
|
||||
avcodec_free_context(&codec_ctx);
|
||||
}
|
||||
}
|
||||
virtual bool open() {return true;}
|
||||
virtual void close() {}
|
||||
virtual bool enableFrameRef() const { return true;}
|
||||
void applyOptionsForDict();
|
||||
void applyOptionsForContext();
|
||||
|
||||
AVCodecContext *codec_ctx; //set once and not change
|
||||
bool available; //TODO: true only when context(and hw ctx) is ready
|
||||
bool is_open;
|
||||
int undecoded_size;
|
||||
QString codec_name;
|
||||
QVariantHash options;
|
||||
AVDictionary *dict;
|
||||
};
|
||||
|
||||
class AudioResampler;
|
||||
class AudioDecoderPrivate : public AVDecoderPrivate
|
||||
{
|
||||
public:
|
||||
AudioDecoderPrivate();
|
||||
virtual ~AudioDecoderPrivate();
|
||||
|
||||
AudioResampler *resampler;
|
||||
QByteArray decoded;
|
||||
};
|
||||
|
||||
class Q_AV_PRIVATE_EXPORT VideoDecoderPrivate : public AVDecoderPrivate
|
||||
{
|
||||
public:
|
||||
VideoDecoderPrivate():
|
||||
AVDecoderPrivate()
|
||||
{}
|
||||
virtual ~VideoDecoderPrivate() {}
|
||||
};
|
||||
} //namespace FAV
|
||||
|
||||
Q_DECLARE_METATYPE(FAV::AVFrameBuffersRef)
|
||||
|
||||
#endif // QTAV_AVDECODER_P_H
|
||||
1049
project/fm_viewer/fav/AVDemuxThread.cpp
Normal file
1049
project/fm_viewer/fav/AVDemuxThread.cpp
Normal file
File diff suppressed because it is too large
Load Diff
129
project/fm_viewer/fav/AVDemuxThread.h
Normal file
129
project/fm_viewer/fav/AVDemuxThread.h
Normal file
@@ -0,0 +1,129 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QAV_DEMUXTHREAD_H
|
||||
#define QAV_DEMUXTHREAD_H
|
||||
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QSemaphore>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QRunnable>
|
||||
#include "PacketBuffer.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class AVDemuxer;
|
||||
class AVThread;
|
||||
class AVDemuxThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AVDemuxThread(QObject *parent = 0);
|
||||
explicit AVDemuxThread(AVDemuxer *dmx, QObject *parent = 0);
|
||||
void setDemuxer(AVDemuxer *dmx);
|
||||
void setAudioDemuxer(AVDemuxer *demuxer); //not thread safe
|
||||
void setAudioThread(AVThread *thread);
|
||||
AVThread* audioThread();
|
||||
void setVideoThread(AVThread *thread);
|
||||
AVThread* videoThread();
|
||||
// void stepForward(); // show next video frame and pause
|
||||
// void stepBackward();
|
||||
void seek(qint64 pos, SeekType type); //ms
|
||||
//AVDemuxer* demuxer
|
||||
bool isPaused() const;
|
||||
bool isEnd() const;
|
||||
bool atEndOfMedia() const;
|
||||
PacketBuffer* buffer();
|
||||
void updateBufferState();
|
||||
void stop(); //TODO: remove it?
|
||||
void pause(bool p, bool wait = false);
|
||||
int playerID;
|
||||
#if (REAR_SYNC_FRONT)
|
||||
double rear_delay;
|
||||
#endif
|
||||
|
||||
|
||||
bool seek_exist()
|
||||
{
|
||||
return seek_tasks.size() > 0;
|
||||
}
|
||||
|
||||
MediaEndAction mediaEndAction() const;
|
||||
void setMediaEndAction(MediaEndAction value);
|
||||
bool waitForStarted(int msec = -1);
|
||||
Q_SIGNALS:
|
||||
void requestClockPause(bool value);
|
||||
void mediaStatusChanged(FAV::MediaStatus);
|
||||
void bufferProgressChanged(qreal);
|
||||
#if (PLAY_SYNC_FIX2)
|
||||
void seekFinished(qint64 timestamp,qint64 requested);
|
||||
#else
|
||||
void seekFinished(qint64 timestamp);
|
||||
#endif
|
||||
void stepFinished();
|
||||
#if !(DO_NOT_USE_SUBTITLE)
|
||||
void internalSubtitlePacketRead(int index, const FAV::Packet& packet);
|
||||
#endif
|
||||
|
||||
private slots:
|
||||
void seekOnPauseFinished();
|
||||
//void frameDeliveredOnStepForward();
|
||||
//void eofDecodedOnStepForward();
|
||||
void onAVThreadQuit();
|
||||
|
||||
protected:
|
||||
virtual void run();
|
||||
/*
|
||||
* If the pause state is true setted by pause(true), then block the thread and wait for pause state changed, i.e. pause(false)
|
||||
* and return true. Otherwise, return false immediatly.
|
||||
*/
|
||||
bool tryPause(unsigned long timeout = 100);
|
||||
|
||||
private:
|
||||
void setAVThread(AVThread *&pOld, AVThread* pNew);
|
||||
void newSeekRequest(QRunnable *r);
|
||||
void processNextSeekTask();
|
||||
void seekInternal(qint64 pos, SeekType type); //must call in AVDemuxThread
|
||||
void pauseInternal(bool value);
|
||||
|
||||
bool paused;
|
||||
bool user_paused;
|
||||
volatile bool end;
|
||||
MediaEndAction end_action;
|
||||
bool m_buffering;
|
||||
PacketBuffer *m_buffer;
|
||||
AVDemuxer *demuxer;
|
||||
AVDemuxer *ademuxer;
|
||||
AVThread *audio_thread, *video_thread;
|
||||
int audio_stream, video_stream;
|
||||
QMutex buffer_mutex;
|
||||
QWaitCondition cond;
|
||||
BlockingQueue<QRunnable*> seek_tasks;
|
||||
|
||||
QSemaphore sem;
|
||||
QMutex next_frame_mutex;
|
||||
int clock_type; // change happens in different threads(direct connection)
|
||||
friend class SeekTask;
|
||||
friend class stepBackwardTask;
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QAV_DEMUXTHREAD_H
|
||||
1942
project/fm_viewer/fav/AVDemuxer.cpp
Normal file
1942
project/fm_viewer/fav/AVDemuxer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
249
project/fm_viewer/fav/AVDemuxer.h
Normal file
249
project/fm_viewer/fav/AVDemuxer.h
Normal file
@@ -0,0 +1,249 @@
|
||||
/******************************************************************************
|
||||
QtAV: Media play library based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QAV_DEMUXER_H
|
||||
#define QAV_DEMUXER_H
|
||||
|
||||
#include "AVError.h"
|
||||
#include "Packet.h"
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QScopedPointer>
|
||||
|
||||
struct AVFormatContext;
|
||||
struct AVCodecContext;
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QIODevice;
|
||||
QT_END_NAMESPACE
|
||||
// TODO: force codec name. clean code
|
||||
namespace FAV {
|
||||
class AVError;
|
||||
class MediaIO;
|
||||
class Q_AV_EXPORT AVDemuxer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum StreamType { //TODO: move to common MediaType
|
||||
AudioStream,
|
||||
VideoStream,
|
||||
#if !(DO_NOT_USE_SUBTITLE)
|
||||
SubtitleStream,
|
||||
#endif
|
||||
};
|
||||
static const QStringList& supportedFormats();
|
||||
static const QStringList& supportedExtensions();
|
||||
/// Supported ffmpeg/libav input protocols(not complete). A static string list
|
||||
static const QStringList& supportedProtocols();
|
||||
|
||||
AVDemuxer(QObject *parent = 0);
|
||||
~AVDemuxer();
|
||||
|
||||
int playerID;
|
||||
#if (REAR_SYNC_FRONT)
|
||||
double rear_delay;
|
||||
#endif
|
||||
|
||||
qint64 _lastDuration;
|
||||
#if (FIXED_FPS_DURATION)
|
||||
qint64 durationFixed;
|
||||
qint32 frameCount;
|
||||
#if (FORCE_BREAK_EOF)
|
||||
qint64 endFilePosition; // 시간(DTS/PTS)으로 EOF 처리 불가능함 -> 파일 포지션으로 처리
|
||||
#endif // FORCE_BREAK_EOF
|
||||
#if (PREVENT_OVER_DURATION_RENDER)
|
||||
qint64 originalDuraiont; // VideoThread 에 전달하여 원 재생시간 이상의 경우 DRAW 하지 않도록 변경
|
||||
#endif // PREVENT_OVER_DURATION_RENDER
|
||||
#endif //FIXED_FPS_DURATION
|
||||
|
||||
qint32 realVideoStreamCount; ///!< 실제(프레임이 존재하는) 비디오 스트림 개수
|
||||
|
||||
#if (PLAY_SYNC_FIX2)
|
||||
qint64 frameDuration; // 각 프레임당 길이(SEEK 및 SLIDER BAR 계산시 durationFixed - frameDuration 으로 사용)
|
||||
#endif // PLAY_SYNC_FIX2
|
||||
|
||||
MediaStatus mediaStatus() const;
|
||||
bool atEnd() const;
|
||||
QString fileName() const;
|
||||
QIODevice* ioDevice() const;
|
||||
/// not null for QIODevice, custom protocols
|
||||
MediaIO* mediaIO() const;
|
||||
/*!
|
||||
* \brief setMedia
|
||||
* \return whether the media source is changed
|
||||
*/
|
||||
bool setMedia(const QString& fileName);
|
||||
bool setMedia(QIODevice* dev);
|
||||
bool setMedia(MediaIO* in);
|
||||
/*!
|
||||
* \brief setFormat
|
||||
* Force the input format. Useful if input stream is a raw video stream(fmt="rawvideo).
|
||||
* formatForced() is reset if media changed. So you have to call setFormat() for every media
|
||||
* you want to force the format.
|
||||
* If AVFormatContext.format_whitelist contains only 1 format, then that format will be forced.
|
||||
* For example, setOptions({"format_whitelist": "rawvideo"})
|
||||
*/
|
||||
void setFormat(const QString& fmt);
|
||||
QString formatForced() const;
|
||||
bool load();
|
||||
bool unload();
|
||||
bool isLoaded() const;
|
||||
/*!
|
||||
* \brief readFrame
|
||||
* Read a packet from 1 of the streams. use packet() to get the result packet. packet() returns last valid packet.
|
||||
* So do not use packet() if readFrame() failed.
|
||||
* Call readFrame() and seek() in the same thread.
|
||||
* \return true if no error. false if error occurs, eof reaches, interrupted by user or time out(getInterruptTimeout())
|
||||
*/
|
||||
bool readFrame(); // TODO: rename int readPacket(), return stream number
|
||||
/*!
|
||||
* \brief packet
|
||||
* return the packet read by demuxer. packet is invalid if readFrame() returns false.
|
||||
*/
|
||||
Packet packet() const;
|
||||
/*!
|
||||
* \brief stream
|
||||
* Current readFrame() readed stream index.
|
||||
*/
|
||||
int stream() const;
|
||||
|
||||
bool isSeekable() const; // TODO: change in unload?
|
||||
void setSeekUnit(SeekUnit unit);
|
||||
SeekUnit seekUnit() const;
|
||||
void setSeekType(SeekType target);
|
||||
SeekType seekType() const;
|
||||
/*!
|
||||
* \brief seek
|
||||
* seek to a given position. Only support timestamp seek now.
|
||||
* Experiment: if pos is out of range (>duration()), do nothing unless a seekable and variableSize MediaIO is used.
|
||||
* \return false if fail
|
||||
*/
|
||||
bool seek(qint64 pos); //pos: ms
|
||||
/*!
|
||||
* \brief seek
|
||||
* Percentage seek. duration() must be >0LL
|
||||
* \param q [0, 1]
|
||||
* TODO: what if duration() is not valid but size is known?
|
||||
*/
|
||||
bool seek(qreal q);
|
||||
AVFormatContext* formatContext();
|
||||
QString formatName() const;
|
||||
QString formatLongName() const;
|
||||
// TODO: rename startPosition()
|
||||
qint64 startTime() const; //ms, AVFormatContext::start_time/1000
|
||||
qint64 duration() const; //ms, AVFormatContext::duration/1000
|
||||
qint64 startTimeUs() const; //us, AVFormatContext::start_time
|
||||
qint64 durationUs() const; //us, AVFormatContext::duration
|
||||
qint64 durationUSAudio() const; // Audio duration
|
||||
//total bit rate
|
||||
int bitRate() const; //AVFormatContext::bit_rate
|
||||
qreal frameRate() const; //deprecated AVStream::avg_frame_rate
|
||||
// if stream is -1, return the current video(or audio if no video) stream.
|
||||
// TODO: audio/videoFrames?
|
||||
qint64 frames(int stream = -1) const; //AVFormatContext::nb_frames
|
||||
bool hasAttacedPicture() const;
|
||||
/*!
|
||||
* \brief setStreamIndex
|
||||
* Set stream by index in stream list. call it after loaded.
|
||||
* Stream/index will not change in next load() unless media source changed
|
||||
* index < 0 is invalid
|
||||
*/
|
||||
bool setStreamIndex(StreamType st, int index);
|
||||
// current open stream
|
||||
int currentStream(StreamType st) const;
|
||||
QList<int> streams(StreamType st) const;
|
||||
// TODO: stream(StreamType), streams(StreamType)
|
||||
// current open stream
|
||||
int audioStream() const;
|
||||
QList<int> audioStreams() const;
|
||||
int videoStream() const;
|
||||
QList<int> videoStreams() const;
|
||||
int subtitleStream() const;
|
||||
QList<int> subtitleStreams() const;
|
||||
//codec. stream < 0: the stream going to play (or the stream set by setStreamIndex())
|
||||
AVCodecContext* audioCodecContext(int stream = -1) const;
|
||||
AVCodecContext* videoCodecContext(int stream = -1) const;
|
||||
AVCodecContext* subtitleCodecContext(int stream = -1) const;
|
||||
/**
|
||||
* @brief getInterruptTimeout return the interrupt timeout
|
||||
*/
|
||||
qint64 getInterruptTimeout() const;
|
||||
/**
|
||||
* @brief setInterruptTimeout set the interrupt timeout
|
||||
* @param timeout in ms
|
||||
*/
|
||||
void setInterruptTimeout(qint64 timeout);
|
||||
bool isInterruptOnTimeout() const;
|
||||
void setInterruptOnTimeout(bool value);
|
||||
/**
|
||||
* @brief getInterruptStatus return the interrupt status.
|
||||
* \return -1: interrupted by user
|
||||
* 0: not interrupted
|
||||
* >0: timeout value of AVError::ErrorCode
|
||||
*/
|
||||
int getInterruptStatus() const;
|
||||
/**
|
||||
* @brief setInterruptStatus set the interrupt status
|
||||
* @param interrupt <0: abort current operation like loading and reading packets.
|
||||
* 0: no interrupt
|
||||
*/
|
||||
void setInterruptStatus(int interrupt);
|
||||
/*!
|
||||
* \brief setOptions
|
||||
* libav's AVDictionary. we can ignore the flags used in av_dict_xxx because we can use hash api.
|
||||
* empty value does nothing to current context if it is open, but will change AVDictionary options to null in next open.
|
||||
* AVDictionary is used in avformat_open_input() and will not change unless user call setOptions()
|
||||
* If an option is not found
|
||||
*/
|
||||
void setOptions(const QVariantHash &dict);
|
||||
QVariantHash options() const;
|
||||
Q_SIGNALS:
|
||||
void unloaded();
|
||||
void userInterrupted(); // NO direct connection because it's emit before interrupted happens
|
||||
void loaded();
|
||||
// emit when the first frame is read
|
||||
void started();
|
||||
void finished(); //end of file
|
||||
void error(const FAV::AVError& e); //explictly use FAV::AVError in connection for Qt4 syntax
|
||||
void mediaStatusChanged(FAV::MediaStatus status);
|
||||
void seekableChanged();
|
||||
#if (FIXED_FPS_DURATION)
|
||||
void mediaEnded();
|
||||
#endif
|
||||
|
||||
private:
|
||||
void setMediaStatus(MediaStatus status);
|
||||
// error code (errorCode) and message (msg) may be modified internally
|
||||
void handleError(int averr, AVError::ErrorCode* errorCode, QString& msg);
|
||||
|
||||
class Private;
|
||||
QScopedPointer<Private> d;
|
||||
class InterruptHandler;
|
||||
friend class InterruptHandler;
|
||||
|
||||
#if (FIXED_FPS_DURATION)
|
||||
|
||||
void _loadDuration();
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QAV_DEMUXER_H
|
||||
218
project/fm_viewer/fav/AVEncoder.cpp
Normal file
218
project/fm_viewer/fav/AVEncoder.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AVEncoder.h"
|
||||
#if !(REMOVE_AV_ENCODER)
|
||||
|
||||
#include "AVEncoder_p.h"
|
||||
#include "version.h"
|
||||
#include "internal.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
AVEncoder::AVEncoder(AVEncoderPrivate &d)
|
||||
:DPTR_INIT(&d)
|
||||
{
|
||||
}
|
||||
|
||||
AVEncoder::~AVEncoder()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
QString AVEncoder::description() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
void AVEncoder::setCodecName(const QString &name)
|
||||
{
|
||||
DPTR_D(AVEncoder);
|
||||
if (d.codec_name == name)
|
||||
return;
|
||||
d.codec_name = name;
|
||||
Q_EMIT codecNameChanged();
|
||||
}
|
||||
|
||||
QString AVEncoder::codecName() const
|
||||
{
|
||||
DPTR_D(const AVEncoder);
|
||||
if (!d.codec_name.isEmpty())
|
||||
return d.codec_name;
|
||||
if (d.avctx)
|
||||
return QLatin1String(avcodec_get_name(d.avctx->codec_id));
|
||||
return QString();
|
||||
}
|
||||
|
||||
void AVEncoder::setBitRate(int value)
|
||||
{
|
||||
DPTR_D(AVEncoder);
|
||||
if (d.bit_rate == value)
|
||||
return;
|
||||
d.bit_rate = value;
|
||||
emit bitRateChanged();
|
||||
}
|
||||
|
||||
int AVEncoder::bitRate() const
|
||||
{
|
||||
return d_func().bit_rate;
|
||||
}
|
||||
|
||||
AVEncoder::TimestampMode AVEncoder::timestampMode() const
|
||||
{
|
||||
return TimestampMode(d_func().timestamp_mode);
|
||||
}
|
||||
|
||||
void AVEncoder::setTimestampMode(TimestampMode value)
|
||||
{
|
||||
DPTR_D(AVEncoder);
|
||||
if (d.timestamp_mode == (int)value)
|
||||
return;
|
||||
d.timestamp_mode = (int)value;
|
||||
Q_EMIT timestampModeChanged(value);
|
||||
}
|
||||
|
||||
bool AVEncoder::open()
|
||||
{
|
||||
DPTR_D(AVEncoder);
|
||||
if (d.avctx) {
|
||||
d.applyOptionsForDict();
|
||||
}
|
||||
if (!d.open()) {
|
||||
d.close();
|
||||
return false;
|
||||
}
|
||||
d.is_open = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AVEncoder::close()
|
||||
{
|
||||
if (!isOpen()) {
|
||||
return true;
|
||||
}
|
||||
DPTR_D(AVEncoder);
|
||||
d.is_open = false;
|
||||
// hwa extra finalize can be here
|
||||
d.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AVEncoder::isOpen() const
|
||||
{
|
||||
return d_func().is_open;
|
||||
}
|
||||
|
||||
void AVEncoder::flush()
|
||||
{
|
||||
if (!isOpen())
|
||||
return;
|
||||
if (d_func().avctx)
|
||||
avcodec_flush_buffers(d_func().avctx);
|
||||
}
|
||||
|
||||
Packet AVEncoder::encoded() const
|
||||
{
|
||||
return d_func().packet;
|
||||
}
|
||||
|
||||
void* AVEncoder::codecContext() const
|
||||
{
|
||||
return d_func().avctx;
|
||||
}
|
||||
|
||||
void AVEncoder::copyAVCodecContext(void* ctx)
|
||||
{
|
||||
if (!ctx)
|
||||
return;
|
||||
DPTR_D(AVEncoder);
|
||||
AVCodecContext* c = static_cast<AVCodecContext*>(ctx);
|
||||
if (d.avctx) {
|
||||
// dest should be avcodec_alloc_context3(NULL)
|
||||
AV_ENSURE_OK(avcodec_copy_context(d.avctx, c));
|
||||
d.is_open = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void AVEncoder::setOptions(const QVariantHash &dict)
|
||||
{
|
||||
DPTR_D(AVEncoder);
|
||||
d.options = dict;
|
||||
// if dict is empty, can not return here, default options will be set for AVCodecContext
|
||||
// apply to AVCodecContext
|
||||
d.applyOptionsForContext();
|
||||
/* set AVEncoder meta properties.
|
||||
* we do not check whether the property exists thus we can set dynamic properties.
|
||||
*/
|
||||
if (dict.isEmpty())
|
||||
return;
|
||||
if (name() == QLatin1String("avcodec"))
|
||||
return;
|
||||
QVariant opt(dict);
|
||||
if (dict.contains(name()))
|
||||
opt = dict.value(name());
|
||||
else if (dict.contains(name().toLower()))
|
||||
opt = dict.value(name().toLower());
|
||||
Internal::setOptionsForQObject(opt, this);
|
||||
}
|
||||
|
||||
QVariantHash AVEncoder::options() const
|
||||
{
|
||||
return d_func().options;
|
||||
}
|
||||
|
||||
void AVEncoderPrivate::applyOptionsForDict()
|
||||
{
|
||||
if (dict) {
|
||||
av_dict_free(&dict);
|
||||
dict = 0; //aready 0 in av_free
|
||||
}
|
||||
if (options.isEmpty())
|
||||
return;
|
||||
qDebug("set AVCodecContext dict:");
|
||||
// TODO: use QVariantMap only
|
||||
if (!options.contains(QStringLiteral("avcodec")))
|
||||
return;
|
||||
// workaround for VideoDecoderFFmpeg. now it does not call av_opt_set_xxx, so set here in dict
|
||||
// TODO: wrong if opt is empty
|
||||
Internal::setOptionsToDict(options.value(QStringLiteral("avcodec")), &dict);
|
||||
}
|
||||
|
||||
void AVEncoderPrivate::applyOptionsForContext()
|
||||
{
|
||||
if (!avctx)
|
||||
return;
|
||||
if (options.isEmpty()) {
|
||||
// av_opt_set_defaults(avctx); //can't set default values! result maybe unexpected
|
||||
return;
|
||||
}
|
||||
// TODO: use QVariantMap only
|
||||
if (!options.contains(QStringLiteral("avcodec")))
|
||||
return;
|
||||
// workaround for VideoDecoderFFmpeg. now it does not call av_opt_set_xxx, so set here in dict
|
||||
// TODO: wrong if opt is empty
|
||||
Internal::setOptionsToFFmpegObj(options.value(QStringLiteral("avcodec")), avctx);
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
#endif // #if !(REMOVE_AV_ENCODER)
|
||||
106
project/fm_viewer/fav/AVEncoder.h
Normal file
106
project/fm_viewer/fav/AVEncoder.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QAV_ENCODER_H
|
||||
#define QAV_ENCODER_H
|
||||
|
||||
#define REMOVE_AV_ENCODER 1
|
||||
#if !(REMOVE_AV_ENCODER)
|
||||
|
||||
#include "_fav_constants.h"
|
||||
#include "AVError.h"
|
||||
#include "Packet.h"
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
namespace FAV {
|
||||
class AVEncoderPrivate;
|
||||
class Q_AV_EXPORT AVEncoder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
DPTR_DECLARE_PRIVATE(AVEncoder)
|
||||
Q_PROPERTY(int bitRate READ bitRate WRITE setBitRate NOTIFY bitRateChanged)
|
||||
Q_PROPERTY(QString codecName READ codecName WRITE setCodecName NOTIFY codecNameChanged)
|
||||
Q_PROPERTY(TimestampMode timestampMode READ timestampMode WRITE setTimestampMode NOTIFY timestampModeChanged)
|
||||
Q_ENUMS(TimestampMode)
|
||||
public:
|
||||
enum TimestampMode {
|
||||
TimestampMonotonic,
|
||||
TimestampCopy,
|
||||
};
|
||||
|
||||
virtual ~AVEncoder();
|
||||
virtual QString name() const = 0;
|
||||
virtual QString description() const;
|
||||
/*!
|
||||
* \brief setCodecName
|
||||
* An encoder can support more than 1 codec.
|
||||
*/
|
||||
void setCodecName(const QString& name);
|
||||
QString codecName() const;
|
||||
bool open();
|
||||
bool close();
|
||||
bool isOpen() const;
|
||||
virtual void flush();
|
||||
Packet encoded() const;
|
||||
|
||||
/*!
|
||||
* used by ff muxer. Be sure all parameters are set. (open?)
|
||||
*/
|
||||
virtual void copyAVCodecContext(void* ctx);
|
||||
void* codecContext() const; // TODO: always have a avctx like decoder?
|
||||
/*!
|
||||
* \brief setBitRate
|
||||
* Higher bit rate result in better quality.
|
||||
* Default for video: 400000, audio: 64000
|
||||
*/
|
||||
void setBitRate(int value);
|
||||
int bitRate() const;
|
||||
|
||||
TimestampMode timestampMode() const;
|
||||
void setTimestampMode(TimestampMode value);
|
||||
/*!
|
||||
* \brief setOptions
|
||||
* 1. If has key "avcodec", it's value (suboption, a hash or map) will be used to set AVCodecContext use av_opt_set and av_dict_set. A value of hash type is ignored.
|
||||
* we can ignore the flags used in av_dict_xxx because we can use hash api.
|
||||
* empty value does nothing to current context if it is open, but will clear AVDictionary in the next open.
|
||||
* AVDictionary is used in avcodec_open2() and will not change unless user call setOptions().
|
||||
* 2. Set QObject properties for AVEncoder. Use AVEncoder::name() or lower case as a key to set properties. If key not found, assume key is "avcodec"
|
||||
* 3. If no ket AVEncoder::name() found in the option, set key-value pairs as QObject property-value pairs.
|
||||
*/
|
||||
void setOptions(const QVariantHash &dict);
|
||||
QVariantHash options() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void error(const FAV::AVError& e); //explictly use FAV::AVError in connection for Qt4 syntax
|
||||
void codecNameChanged();
|
||||
void bitRateChanged();
|
||||
void timestampModeChanged(TimestampMode mode);
|
||||
protected:
|
||||
AVEncoder(AVEncoderPrivate& d);
|
||||
DPTR_DECLARE(AVEncoder)
|
||||
private:
|
||||
Q_DISABLE_COPY(AVEncoder)
|
||||
AVEncoder(); // base class, not direct create. only final class has is enough
|
||||
};
|
||||
} //namespace FAV
|
||||
#endif // QAV_ENCODER_H
|
||||
#endif // #if !(REMOVE_AV_ENCODER)
|
||||
108
project/fm_viewer/fav/AVEncoder_p.h
Normal file
108
project/fm_viewer/fav/AVEncoder_p.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AVENCODER_P_H
|
||||
#define QTAV_AVENCODER_P_H
|
||||
|
||||
#define REMOVE_AV_ENCODER_P 1
|
||||
#if !(REMOVE_AV_ENCODER_P)
|
||||
|
||||
#include <QtCore/QVariant>
|
||||
#include "AudioFormat.h"
|
||||
#include "Packet.h"
|
||||
#include "VideoFormat.h"
|
||||
#include "AVCompat.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class Q_AV_PRIVATE_EXPORT AVEncoderPrivate : public DPtrPrivate<AVEncoder>
|
||||
{
|
||||
public:
|
||||
AVEncoderPrivate():
|
||||
avctx(0)
|
||||
, is_open(false)
|
||||
, bit_rate(0)
|
||||
, timestamp_mode(0)
|
||||
, dict(0)
|
||||
{
|
||||
}
|
||||
virtual ~AVEncoderPrivate() {
|
||||
if (dict) {
|
||||
av_dict_free(&dict);
|
||||
}
|
||||
if (avctx) {
|
||||
avcodec_free_context(&avctx);
|
||||
}
|
||||
}
|
||||
virtual bool open() {return true;}
|
||||
virtual bool close() {return true;}
|
||||
// used iff avctx != null
|
||||
void applyOptionsForDict();
|
||||
void applyOptionsForContext();
|
||||
|
||||
AVCodecContext *avctx; // null if not avcodec. allocated in ffmpeg based encoders
|
||||
bool is_open;
|
||||
int bit_rate;
|
||||
int timestamp_mode;
|
||||
QString codec_name;
|
||||
QVariantHash options;
|
||||
AVDictionary *dict; // null if not avcodec
|
||||
Packet packet;
|
||||
};
|
||||
|
||||
class AudioResampler;
|
||||
class AudioEncoderPrivate : public AVEncoderPrivate
|
||||
{
|
||||
public:
|
||||
AudioEncoderPrivate()
|
||||
: AVEncoderPrivate()
|
||||
{
|
||||
bit_rate = 64000;
|
||||
}
|
||||
|
||||
virtual ~AudioEncoderPrivate() {}
|
||||
|
||||
AudioResampler *resampler;
|
||||
AudioFormat format, format_used;
|
||||
};
|
||||
|
||||
class Q_AV_PRIVATE_EXPORT VideoEncoderPrivate : public AVEncoderPrivate
|
||||
{
|
||||
public:
|
||||
VideoEncoderPrivate():
|
||||
AVEncoderPrivate()
|
||||
, width(0)
|
||||
, height(0)
|
||||
, frame_rate(-1)
|
||||
, format_used(VideoFormat::Format_Invalid)
|
||||
, format(format_used)
|
||||
{
|
||||
bit_rate = 400000;
|
||||
}
|
||||
virtual ~VideoEncoderPrivate() {}
|
||||
int width, height;
|
||||
qreal frame_rate;
|
||||
VideoFormat::PixelFormat format_used;
|
||||
VideoFormat format;
|
||||
};
|
||||
} //namespace FAV
|
||||
#endif // QTAV_AVENCODER_P_H
|
||||
#endif // #if !(REMOVE_AV_ENCODER_P)
|
||||
230
project/fm_viewer/fav/AVError.cpp
Normal file
230
project/fm_viewer/fav/AVError.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
#include "AVError.h"
|
||||
#include "AVCompat.h"
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
#include <QtCore/qdebug.h>
|
||||
#endif
|
||||
|
||||
namespace FAV {
|
||||
|
||||
namespace
|
||||
{
|
||||
class RegisterMetaTypes
|
||||
{
|
||||
public:
|
||||
RegisterMetaTypes()
|
||||
{
|
||||
qRegisterMetaType<FAV::AVError>("FAV::AVError");
|
||||
}
|
||||
} _registerMetaTypes;
|
||||
}
|
||||
|
||||
static AVError::ErrorCode errorFromFFmpeg(int fe)
|
||||
{
|
||||
typedef struct {
|
||||
int ff;
|
||||
AVError::ErrorCode e;
|
||||
} err_entry;
|
||||
static const err_entry err_map[] = {
|
||||
{ AVERROR_BSF_NOT_FOUND, AVError::FormatError },
|
||||
#ifdef AVERROR_BUFFER_TOO_SMALL
|
||||
{ AVERROR_BUFFER_TOO_SMALL, AVError::ResourceError },
|
||||
#endif //AVERROR_BUFFER_TOO_SMALL
|
||||
{ AVERROR_DECODER_NOT_FOUND, AVError::CodecError },
|
||||
{ AVERROR_ENCODER_NOT_FOUND, AVError::CodecError },
|
||||
{ AVERROR_DEMUXER_NOT_FOUND, AVError::FormatError },
|
||||
{ AVERROR_MUXER_NOT_FOUND, AVError::FormatError },
|
||||
{ AVERROR_PROTOCOL_NOT_FOUND, AVError::ResourceError },
|
||||
{ AVERROR_STREAM_NOT_FOUND, AVError::ResourceError },
|
||||
{ 0, AVError::UnknowError }
|
||||
};
|
||||
for (int i = 0; err_map[i].ff; ++i) {
|
||||
if (err_map[i].ff == fe)
|
||||
return err_map[i].e;
|
||||
}
|
||||
return AVError::UnknowError;
|
||||
}
|
||||
|
||||
/*
|
||||
* correct wrong AVError to a right category by ffmpeg error code
|
||||
* does nothing if ffmpeg error code is 0
|
||||
*/
|
||||
static void correct_error_by_ffmpeg(AVError::ErrorCode *e, int fe)
|
||||
{
|
||||
if (!fe || !e)
|
||||
return;
|
||||
const AVError::ErrorCode ec = errorFromFFmpeg(fe);
|
||||
if (*e > ec)
|
||||
*e = ec;
|
||||
}
|
||||
|
||||
AVError::AVError()
|
||||
: mError(NoError)
|
||||
, mFFmpegError(0)
|
||||
{
|
||||
}
|
||||
|
||||
AVError::AVError(ErrorCode code, int ffmpegError)
|
||||
: mError(code)
|
||||
, mFFmpegError(ffmpegError)
|
||||
{
|
||||
correct_error_by_ffmpeg(&mError, mFFmpegError);
|
||||
}
|
||||
|
||||
AVError::AVError(ErrorCode code, const QString &detail, int ffmpegError)
|
||||
: mError(code)
|
||||
, mFFmpegError(ffmpegError)
|
||||
, mDetail(detail)
|
||||
{
|
||||
correct_error_by_ffmpeg(&mError, mFFmpegError);
|
||||
}
|
||||
|
||||
AVError::AVError(const AVError& other)
|
||||
: mError(other.mError)
|
||||
, mFFmpegError(other.mFFmpegError)
|
||||
, mDetail(other.mDetail)
|
||||
{
|
||||
}
|
||||
|
||||
AVError& AVError::operator=(const AVError& other)
|
||||
{
|
||||
mError = other.mError;
|
||||
mFFmpegError = other.mFFmpegError;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool AVError::operator==(const AVError& other) const
|
||||
{
|
||||
return (mError == other.mError && mFFmpegError == other.mFFmpegError);
|
||||
}
|
||||
|
||||
void AVError::setError(ErrorCode ec)
|
||||
{
|
||||
mError = ec;
|
||||
}
|
||||
|
||||
AVError::ErrorCode AVError::error() const
|
||||
{
|
||||
return mError;
|
||||
}
|
||||
|
||||
QString AVError::string() const
|
||||
{
|
||||
QString errStr(mDetail);
|
||||
if (errStr.isEmpty()) {
|
||||
switch (mError) {
|
||||
case NoError:
|
||||
errStr = "No error";
|
||||
break;
|
||||
case OpenError:
|
||||
errStr = "Open error";
|
||||
break;
|
||||
case OpenTimedout:
|
||||
errStr = "Open timed out";
|
||||
break;
|
||||
case ParseStreamTimedOut:
|
||||
errStr = "Parse stream timed out";
|
||||
break;
|
||||
case ParseStreamError:
|
||||
errStr = "Parse stream error";
|
||||
break;
|
||||
case StreamNotFound:
|
||||
errStr = "Stream not found";
|
||||
break;
|
||||
case ReadTimedout:
|
||||
errStr = "Read packet timed out";
|
||||
break;
|
||||
case ReadError:
|
||||
errStr = "Read error";
|
||||
break;
|
||||
case SeekError:
|
||||
errStr = "Seek error";
|
||||
break;
|
||||
case ResourceError:
|
||||
errStr = "Resource error";
|
||||
break;
|
||||
|
||||
case OpenCodecError:
|
||||
errStr = "Open codec error";
|
||||
break;
|
||||
case CloseCodecError:
|
||||
errStr = "Close codec error";
|
||||
break;
|
||||
case VideoCodecNotFound:
|
||||
errStr = "Video codec not found";
|
||||
break;
|
||||
case AudioCodecNotFound:
|
||||
errStr = "Audio codec not found";
|
||||
break;
|
||||
case SubtitleCodecNotFound:
|
||||
errStr = "Subtitle codec not found";
|
||||
break;
|
||||
case CodecError:
|
||||
errStr = "Codec error";
|
||||
break;
|
||||
|
||||
case FormatError:
|
||||
errStr = "Format error";
|
||||
break;
|
||||
|
||||
case NetworkError:
|
||||
errStr = "Network error";
|
||||
break;
|
||||
|
||||
case AccessDenied:
|
||||
errStr = "Access denied";
|
||||
break;
|
||||
|
||||
default:
|
||||
errStr = "Unknow error";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mFFmpegError != 0) {
|
||||
errStr += QStringLiteral("\n(FFmpeg %1: %2)").arg(mFFmpegError, 0, 16).arg(ffmpegErrorString());
|
||||
}
|
||||
return errStr;
|
||||
}
|
||||
|
||||
int AVError::ffmpegErrorCode() const
|
||||
{
|
||||
return mFFmpegError;
|
||||
}
|
||||
|
||||
QString AVError::ffmpegErrorString() const
|
||||
{
|
||||
if (mFFmpegError == 0)
|
||||
return QString();
|
||||
return QString::fromUtf8(av_err2str(mFFmpegError));
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
//class QDebug;
|
||||
QDebug operator<<(QDebug debug, const FAV::AVError &error)
|
||||
{
|
||||
debug << error.string();
|
||||
return debug;
|
||||
}
|
||||
#endif
|
||||
105
project/fm_viewer/fav/AVError.h
Normal file
105
project/fm_viewer/fav/AVError.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
#ifndef QTAV_AVERROR_H
|
||||
#define QTAV_AVERROR_H
|
||||
|
||||
#include "_fav_constants.h"
|
||||
#include <QtCore/QString>
|
||||
|
||||
namespace FAV {
|
||||
class Q_AV_EXPORT AVError
|
||||
{
|
||||
public:
|
||||
enum ErrorCode {
|
||||
NoError,
|
||||
|
||||
//open/read/seek network stream error. value must be less then ResourceError because of correct_error_by_ffmpeg
|
||||
NetworkError, // all above and before NoError are NetworkError
|
||||
|
||||
OpenTimedout,
|
||||
OpenError,
|
||||
ParseStreamTimedOut,
|
||||
FindStreamInfoTimedout = ParseStreamTimedOut,
|
||||
ParseStreamError,
|
||||
FindStreamInfoError = ParseStreamError,
|
||||
StreamNotFound, //a,v,s?
|
||||
ReadTimedout,
|
||||
ReadError,
|
||||
SeekError,
|
||||
ResourceError, // all above and before NetworkError are ResourceError
|
||||
|
||||
OpenCodecError,
|
||||
CloseCodecError,
|
||||
AudioCodecNotFound,
|
||||
VideoCodecNotFound,
|
||||
SubtitleCodecNotFound,
|
||||
CodecError, // all above and before NoError are CodecError
|
||||
|
||||
FormatError, // all above and before CodecError are FormatError
|
||||
|
||||
// decrypt error. Not implemented
|
||||
AccessDenied, // all above and before NetworkError are AccessDenied
|
||||
|
||||
UnknowError
|
||||
};
|
||||
|
||||
AVError();
|
||||
AVError(ErrorCode code, int ffmpegError = 0);
|
||||
/*!
|
||||
* \brief AVError
|
||||
* string() will be detail. If ffmpeg error not 0, also contains ffmpegErrorString()
|
||||
* \param code ErrorCode value
|
||||
* \param detail ErrorCode string will be overrided by detail.
|
||||
* \param ffmpegError ffmpeg error code. If not 0, string() will contains ffmpeg error string.
|
||||
*/
|
||||
AVError(ErrorCode code, const QString& detail, int ffmpegError = 0);
|
||||
AVError(const AVError& other);
|
||||
|
||||
AVError &operator=(const AVError &other);
|
||||
bool operator==(const AVError &other) const;
|
||||
inline bool operator!=(const AVError &other) const
|
||||
{ return !(*this == other); }
|
||||
|
||||
void setError(ErrorCode ec);
|
||||
ErrorCode error() const;
|
||||
QString string() const;
|
||||
|
||||
int ffmpegErrorCode() const;
|
||||
QString ffmpegErrorString() const;
|
||||
|
||||
private:
|
||||
ErrorCode mError;
|
||||
int mFFmpegError;
|
||||
QString mDetail;
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
Q_DECLARE_METATYPE(FAV::AVError)
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QDebug;
|
||||
QT_END_NAMESPACE
|
||||
Q_AV_EXPORT QDebug operator<<(QDebug debug, const FAV::AVError &error);
|
||||
#endif
|
||||
|
||||
#endif // QTAV_AVERROR_H
|
||||
531
project/fm_viewer/fav/AVMuxer.cpp
Normal file
531
project/fm_viewer/fav/AVMuxer.cpp
Normal file
@@ -0,0 +1,531 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AVMuxer.h"
|
||||
|
||||
#if !(REMOVE_AV_MUXER)
|
||||
|
||||
#include "AVCompat.h"
|
||||
#include "MediaIO.h"
|
||||
#include "VideoEncoder.h"
|
||||
#include "AudioEncoder.h"
|
||||
#include "internal.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
static const char kFileScheme[] = "file:";
|
||||
#define CHAR_COUNT(s) (sizeof(s) - 1) // tail '\0'
|
||||
|
||||
// Packet::asAVPacket() assumes time base is 0.001
|
||||
static const AVRational kTB = {1, 1000};
|
||||
|
||||
class AVMuxer::Private
|
||||
{
|
||||
public:
|
||||
Private()
|
||||
: seekable(false)
|
||||
, network(false)
|
||||
, started(false)
|
||||
, eof(false)
|
||||
, media_changed(true)
|
||||
, format_ctx(0)
|
||||
, format(0)
|
||||
, io(0)
|
||||
, dict(0)
|
||||
, aenc(0)
|
||||
, venc(0)
|
||||
{
|
||||
av_register_all();
|
||||
}
|
||||
~Private() {
|
||||
//delete interrupt_hanlder;
|
||||
if (dict) {
|
||||
av_dict_free(&dict);
|
||||
dict = 0;
|
||||
}
|
||||
if (io) {
|
||||
delete io;
|
||||
io = 0;
|
||||
}
|
||||
}
|
||||
AVStream* addStream(AVFormatContext* ctx, const QString& codecName, AVCodecID codecId);
|
||||
bool prepareStreams();
|
||||
void applyOptionsForDict();
|
||||
void applyOptionsForContext();
|
||||
|
||||
bool seekable;
|
||||
bool network;
|
||||
bool started;
|
||||
bool eof;
|
||||
bool media_changed;
|
||||
AVFormatContext *format_ctx;
|
||||
//copy the info, not parse the file when constructed, then need member vars
|
||||
QString file;
|
||||
QString file_orig;
|
||||
AVOutputFormat *format;
|
||||
QString format_forced;
|
||||
MediaIO *io;
|
||||
|
||||
AVDictionary *dict;
|
||||
QVariantHash options;
|
||||
QList<int> audio_streams, video_streams, subtitle_streams;
|
||||
AudioEncoder *aenc; // not owner
|
||||
VideoEncoder *venc; // not owner
|
||||
};
|
||||
|
||||
AVStream *AVMuxer::Private::addStream(AVFormatContext* ctx, const QString &codecName, AVCodecID codecId)
|
||||
{
|
||||
AVCodec *codec = NULL;
|
||||
if (!codecName.isEmpty()) {
|
||||
codec = avcodec_find_encoder_by_name(codecName.toUtf8().constData());
|
||||
if (!codec) {
|
||||
const AVCodecDescriptor* cd = avcodec_descriptor_get_by_name(codecName.toUtf8().constData());
|
||||
if (cd) {
|
||||
codec = avcodec_find_encoder(cd->id);
|
||||
}
|
||||
}
|
||||
if (!codec)
|
||||
qWarning("Can not find encoder for %s", codecName.toUtf8().constData());
|
||||
} else if (codecId != QTAV_CODEC_ID(NONE)) {
|
||||
codec = avcodec_find_encoder(codecId);
|
||||
if (!codec)
|
||||
qWarning("Can not find encoder for %s", avcodec_get_name(codecId));
|
||||
}
|
||||
if (!codec)
|
||||
return 0;
|
||||
AVStream *s = avformat_new_stream(ctx, codec);
|
||||
if (!s) {
|
||||
qWarning("Can not allocate stream");
|
||||
return 0;
|
||||
}
|
||||
// set by avformat if unset
|
||||
s->id = ctx->nb_streams - 1;
|
||||
s->time_base = kTB;
|
||||
AVCodecContext *c = s->codec;
|
||||
c->codec_id = codec->id;
|
||||
// Using codec->time_base is deprecated, but needed for older lavf.
|
||||
c->time_base = s->time_base;
|
||||
/* Some formats want stream headers to be separate. */
|
||||
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
c->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
// expose avctx to encoder and set properties in encoder?
|
||||
// list codecs for a given format in ui
|
||||
return s;
|
||||
}
|
||||
|
||||
bool AVMuxer::Private::prepareStreams()
|
||||
{
|
||||
audio_streams.clear();
|
||||
video_streams.clear();
|
||||
subtitle_streams.clear();
|
||||
AVOutputFormat* fmt = format_ctx->oformat;
|
||||
if (venc) {
|
||||
AVStream *s = addStream(format_ctx, venc->codecName(), fmt->video_codec);
|
||||
if (s) {
|
||||
AVCodecContext *c = s->codec;
|
||||
c->bit_rate = venc->bitRate();
|
||||
c->width = venc->width();
|
||||
c->height = venc->height();
|
||||
/// MUST set after encoder is open to ensure format is valid and the same
|
||||
c->pix_fmt = (AVPixelFormat)VideoFormat::pixelFormatToFFmpeg(venc->pixelFormat());
|
||||
video_streams.push_back(s->id);
|
||||
}
|
||||
}
|
||||
if (aenc) {
|
||||
AVStream *s = addStream(format_ctx, aenc->codecName(), fmt->audio_codec);
|
||||
if (s) {
|
||||
AVCodecContext *c = s->codec;
|
||||
c->bit_rate = aenc->bitRate();
|
||||
/// MUST set after encoder is open to ensure format is valid and the same
|
||||
c->sample_rate = aenc->audioFormat().sampleRate();
|
||||
c->sample_fmt = (AVSampleFormat)aenc->audioFormat().sampleFormatFFmpeg();
|
||||
c->channel_layout = aenc->audioFormat().channelLayoutFFmpeg();
|
||||
c->channels = aenc->audioFormat().channels();
|
||||
c->bits_per_raw_sample = aenc->audioFormat().bytesPerSample()*8; // need??
|
||||
audio_streams.push_back(s->id);
|
||||
}
|
||||
}
|
||||
return !(audio_streams.isEmpty() && video_streams.isEmpty() && subtitle_streams.isEmpty());
|
||||
}
|
||||
|
||||
static void getFFmpegOutputFormats(QStringList* formats, QStringList* extensions)
|
||||
{
|
||||
static QStringList exts;
|
||||
static QStringList fmts;
|
||||
if (exts.isEmpty() && fmts.isEmpty()) {
|
||||
av_register_all(); // MUST register all input/output formats
|
||||
AVOutputFormat *o = NULL;
|
||||
QStringList e, f;
|
||||
while ((o = av_oformat_next(o))) {
|
||||
if (o->extensions)
|
||||
e << QString::fromLatin1(o->extensions).split(QLatin1Char(','), QString::SkipEmptyParts);
|
||||
if (o->name)
|
||||
f << QString::fromLatin1(o->name).split(QLatin1Char(','), QString::SkipEmptyParts);
|
||||
}
|
||||
foreach (const QString& v, e) {
|
||||
exts.append(v.trimmed());
|
||||
}
|
||||
foreach (const QString& v, f) {
|
||||
fmts.append(v.trimmed());
|
||||
}
|
||||
exts.removeDuplicates();
|
||||
fmts.removeDuplicates();
|
||||
}
|
||||
if (formats)
|
||||
*formats = fmts;
|
||||
if (extensions)
|
||||
*extensions = exts;
|
||||
}
|
||||
|
||||
const QStringList& AVMuxer::supportedFormats()
|
||||
{
|
||||
static QStringList fmts;
|
||||
if (fmts.isEmpty())
|
||||
getFFmpegOutputFormats(&fmts, NULL);
|
||||
return fmts;
|
||||
}
|
||||
|
||||
const QStringList& AVMuxer::supportedExtensions()
|
||||
{
|
||||
static QStringList exts;
|
||||
if (exts.isEmpty())
|
||||
getFFmpegOutputFormats(NULL, &exts);
|
||||
return exts;
|
||||
}
|
||||
|
||||
// TODO: move to FAV::supportedFormats(bool out). custom protols?
|
||||
const QStringList &AVMuxer::supportedProtocols()
|
||||
{
|
||||
static bool called = false;
|
||||
static QStringList protocols;
|
||||
if (called)
|
||||
return protocols;
|
||||
called = true;
|
||||
if (!protocols.isEmpty())
|
||||
return protocols;
|
||||
#if QTAV_HAVE(AVDEVICE)
|
||||
protocols << QStringLiteral("avdevice");
|
||||
#endif
|
||||
av_register_all(); // MUST register all input/output formats
|
||||
void* opq = 0;
|
||||
const char* protocol = avio_enum_protocols(&opq, 1);
|
||||
while (protocol) {
|
||||
// static string, no deep copy needed. but QByteArray::fromRawData(data,size) assumes data is not null terminated and we must give a size
|
||||
protocols.append(QString::fromUtf8(protocol));
|
||||
protocol = avio_enum_protocols(&opq, 1);
|
||||
}
|
||||
return protocols;
|
||||
}
|
||||
|
||||
AVMuxer::AVMuxer(QObject *parent)
|
||||
: QObject(parent)
|
||||
, d(new Private())
|
||||
{
|
||||
}
|
||||
|
||||
AVMuxer::~AVMuxer()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
QString AVMuxer::fileName() const
|
||||
{
|
||||
return d->file_orig;
|
||||
}
|
||||
|
||||
QIODevice* AVMuxer::ioDevice() const
|
||||
{
|
||||
if (!d->io)
|
||||
return 0;
|
||||
if (d->io->name() != QLatin1String("QIODevice"))
|
||||
return 0;
|
||||
return d->io->property("device").value<QIODevice*>();
|
||||
}
|
||||
|
||||
MediaIO* AVMuxer::mediaIO() const
|
||||
{
|
||||
return d->io;
|
||||
}
|
||||
|
||||
bool AVMuxer::setMedia(const QString &fileName)
|
||||
{
|
||||
if (d->io) {
|
||||
delete d->io;
|
||||
d->io = 0;
|
||||
}
|
||||
d->file_orig = fileName;
|
||||
const QString url_old(d->file);
|
||||
d->file = fileName.trimmed();
|
||||
if (d->file.startsWith(QLatin1String("mms:")))
|
||||
d->file.insert(3, QLatin1Char('h'));
|
||||
else if (d->file.startsWith(QLatin1String(kFileScheme)))
|
||||
d->file = Internal::Path::toLocal(d->file);
|
||||
int colon = d->file.indexOf(QLatin1Char(':'));
|
||||
if (colon == 1) {
|
||||
#ifdef Q_OS_WINRT
|
||||
d->file.prepend(QStringLiteral("qfile:"));
|
||||
#endif
|
||||
}
|
||||
d->media_changed = url_old != d->file;
|
||||
if (d->media_changed) {
|
||||
d->format_forced.clear();
|
||||
}
|
||||
// a local file. return here to avoid protocol checking. If path contains ":", protocol checking will fail
|
||||
if (d->file.startsWith(QLatin1Char('/')))
|
||||
return d->media_changed;
|
||||
// use MediaIO to support protocols not supported by ffmpeg
|
||||
colon = d->file.indexOf(QLatin1Char(':'));
|
||||
if (colon >= 0) {
|
||||
#ifdef Q_OS_WIN
|
||||
if (colon == 1 && d->file.at(0).isLetter())
|
||||
return d->media_changed;
|
||||
#endif
|
||||
const QString scheme = colon == 0 ? QStringLiteral("qrc") : d->file.left(colon);
|
||||
// supportedProtocols() is not complete. so try MediaIO 1st, if not found, fallback to libavformat
|
||||
d->io = MediaIO::createForProtocol(scheme);
|
||||
if (d->io) {
|
||||
d->io->setUrl(d->file);
|
||||
}
|
||||
}
|
||||
return d->media_changed;
|
||||
}
|
||||
|
||||
bool AVMuxer::setMedia(QIODevice* device)
|
||||
{
|
||||
d->file = QString();
|
||||
d->file_orig = QString();
|
||||
if (d->io) {
|
||||
if (d->io->name() != QLatin1String("QIODevice")) {
|
||||
delete d->io;
|
||||
d->io = 0;
|
||||
}
|
||||
}
|
||||
if (!d->io)
|
||||
d->io = MediaIO::create("QIODevice");
|
||||
QIODevice* old_dev = d->io->property("device").value<QIODevice*>();
|
||||
d->media_changed = old_dev != device;
|
||||
if (d->media_changed) {
|
||||
d->format_forced.clear();
|
||||
}
|
||||
d->io->setProperty("device", QVariant::fromValue(device)); //open outside?
|
||||
return d->media_changed;
|
||||
}
|
||||
|
||||
bool AVMuxer::setMedia(MediaIO *in)
|
||||
{
|
||||
d->media_changed = in != d->io;
|
||||
if (d->media_changed) {
|
||||
d->format_forced.clear();
|
||||
}
|
||||
d->file = QString();
|
||||
d->file_orig = QString();
|
||||
if (!d->io)
|
||||
d->io = in;
|
||||
if (d->io != in) {
|
||||
delete d->io;
|
||||
d->io = in;
|
||||
}
|
||||
return d->media_changed;
|
||||
}
|
||||
|
||||
void AVMuxer::setFormat(const QString &fmt)
|
||||
{
|
||||
d->format_forced = fmt;
|
||||
}
|
||||
|
||||
QString AVMuxer::formatForced() const
|
||||
{
|
||||
return d->format_forced;
|
||||
}
|
||||
|
||||
bool AVMuxer::open()
|
||||
{
|
||||
// avformatcontext will be allocated in avformat_alloc_output_context2()
|
||||
//d->format_ctx->interrupt_callback = *d->interrupt_hanlder;
|
||||
|
||||
d->applyOptionsForDict();
|
||||
// check special dict keys
|
||||
// d->format_forced can be set from AVFormatContext.format_whitelist
|
||||
if (!d->format_forced.isEmpty()) {
|
||||
d->format = av_guess_format(d->format_forced.toUtf8().constData(), NULL, NULL);
|
||||
qDebug() << "force format: " << d->format_forced;
|
||||
}
|
||||
|
||||
//d->interrupt_hanlder->begin(InterruptHandler::Open);
|
||||
if (d->io) {
|
||||
if (d->io->accessMode() == MediaIO::Read) {
|
||||
qWarning("wrong MediaIO accessMode. MUST be Write");
|
||||
}
|
||||
AV_ENSURE_OK(avformat_alloc_output_context2(&d->format_ctx, d->format, d->format_forced.isEmpty() ? 0 : d->format_forced.toUtf8().constData(), ""), false);
|
||||
d->format_ctx->pb = (AVIOContext*)d->io->avioContext();
|
||||
d->format_ctx->flags |= AVFMT_FLAG_CUSTOM_IO;
|
||||
//d->format_ctx->flags |= AVFMT_FLAG_GENPTS;
|
||||
} else {
|
||||
AV_ENSURE_OK(avformat_alloc_output_context2(&d->format_ctx, d->format, d->format_forced.isEmpty() ? 0 : d->format_forced.toUtf8().constData(), fileName().toUtf8().constData()), false);
|
||||
}
|
||||
//d->interrupt_hanlder->end();
|
||||
|
||||
if (!d->prepareStreams()) {
|
||||
return false;
|
||||
}
|
||||
// TODO: AVFMT_NOFILE ? examples/muxing.c only check AVFMT_NOFILE
|
||||
// a custome io does not need avio_open. it open resource in it's own way, e.g. QIODevice.open
|
||||
if (!(d->format_ctx->oformat->flags & AVFMT_NOFILE) && !(d->format_ctx->flags & AVFMT_FLAG_CUSTOM_IO)) {
|
||||
// avio_open2?
|
||||
AV_ENSURE_OK(avio_open(&d->format_ctx->pb, fileName().toUtf8().constData(), AVIO_FLAG_WRITE), false);
|
||||
}
|
||||
// d->format_ctx->start_time_realtime
|
||||
AV_ENSURE_OK(avformat_write_header(d->format_ctx, &d->dict), false);
|
||||
d->started = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AVMuxer::close()
|
||||
{
|
||||
if (!isOpen())
|
||||
return true;
|
||||
av_write_trailer(d->format_ctx);
|
||||
// close AVCodecContext* in encoder
|
||||
// custom io will call avio_close in ~MediaIO()
|
||||
if (!(d->format_ctx->oformat->flags & AVFMT_NOFILE) && !(d->format_ctx->flags & AVFMT_FLAG_CUSTOM_IO)) {
|
||||
if (d->format_ctx->pb) {
|
||||
avio_flush(d->format_ctx->pb);
|
||||
avio_close(d->format_ctx->pb);
|
||||
d->format_ctx->pb = 0;
|
||||
}
|
||||
}
|
||||
avformat_free_context(d->format_ctx);
|
||||
d->format_ctx = 0;
|
||||
d->audio_streams.clear();
|
||||
d->video_streams.clear();
|
||||
d->subtitle_streams.clear();
|
||||
d->started = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AVMuxer::isOpen() const
|
||||
{
|
||||
return d->format_ctx;
|
||||
}
|
||||
|
||||
bool AVMuxer::writeAudio(const FAV::Packet& packet)
|
||||
{
|
||||
AVPacket *pkt = (AVPacket*)packet.asAVPacket(); //FIXME
|
||||
pkt->stream_index = d->audio_streams[0]; //FIXME
|
||||
AVStream *s = d->format_ctx->streams[pkt->stream_index];
|
||||
// stream.time_base is set in avformat_write_header
|
||||
av_packet_rescale_ts(pkt, kTB, s->time_base);
|
||||
av_interleaved_write_frame(d->format_ctx, pkt);
|
||||
|
||||
d->started = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AVMuxer::writeVideo(const FAV::Packet& packet)
|
||||
{
|
||||
AVPacket *pkt = (AVPacket*)packet.asAVPacket();
|
||||
pkt->stream_index = d->video_streams[0];
|
||||
AVStream *s = d->format_ctx->streams[pkt->stream_index];
|
||||
// stream.time_base is set in avformat_write_header
|
||||
av_packet_rescale_ts(pkt, kTB, s->time_base);
|
||||
//av_write_frame
|
||||
av_interleaved_write_frame(d->format_ctx, pkt);
|
||||
#if 0
|
||||
qDebug("mux packet.pts: %.3f dts:%.3f duration: %.3f, avpkt.pts: %lld,dts:%lld,duration:%lld"
|
||||
, packet.pts, packet.dts, packet.duration
|
||||
, pkt->pts, pkt->dts, pkt->duration);
|
||||
qDebug("stream: %d duration: %lld, end: %lld. tb:{%d/%d}"
|
||||
, pkt->stream_index, s->duration
|
||||
, av_stream_get_end_pts(s)
|
||||
, s->time_base.num, s->time_base.den
|
||||
);
|
||||
#endif
|
||||
d->started = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AVMuxer::copyProperties(VideoEncoder *enc)
|
||||
{
|
||||
d->venc = enc;
|
||||
}
|
||||
|
||||
void AVMuxer::copyProperties(AudioEncoder *enc)
|
||||
{
|
||||
d->aenc = enc;
|
||||
}
|
||||
|
||||
void AVMuxer::setOptions(const QVariantHash &dict)
|
||||
{
|
||||
d->options = dict;
|
||||
d->applyOptionsForContext(); // apply even if avformat context is open
|
||||
}
|
||||
|
||||
QVariantHash AVMuxer::options() const
|
||||
{
|
||||
return d->options;
|
||||
}
|
||||
|
||||
void AVMuxer::Private::applyOptionsForDict()
|
||||
{
|
||||
if (dict) {
|
||||
av_dict_free(&dict);
|
||||
dict = 0; //aready 0 in av_free
|
||||
}
|
||||
if (options.isEmpty())
|
||||
return;
|
||||
QVariant opt(options);
|
||||
if (options.contains(QStringLiteral("avformat")))
|
||||
opt = options.value(QStringLiteral("avformat"));
|
||||
Internal::setOptionsToDict(opt, &dict);
|
||||
|
||||
if (opt.type() == QVariant::Map) {
|
||||
QVariantMap avformat_dict(opt.toMap());
|
||||
if (avformat_dict.contains(QStringLiteral("format_whitelist"))) {
|
||||
const QString fmts(avformat_dict[QStringLiteral("format_whitelist")].toString());
|
||||
if (!fmts.contains(QLatin1Char(',')) && !fmts.isEmpty())
|
||||
format_forced = fmts; // reset when media changed
|
||||
}
|
||||
} else if (opt.type() == QVariant::Hash) {
|
||||
QVariantHash avformat_dict(opt.toHash());
|
||||
if (avformat_dict.contains(QStringLiteral("format_whitelist"))) {
|
||||
const QString fmts(avformat_dict[QStringLiteral("format_whitelist")].toString());
|
||||
if (!fmts.contains(QLatin1Char(',')) && !fmts.isEmpty())
|
||||
format_forced = fmts; // reset when media changed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AVMuxer::Private::applyOptionsForContext()
|
||||
{
|
||||
if (!format_ctx)
|
||||
return;
|
||||
if (options.isEmpty()) {
|
||||
//av_opt_set_defaults(format_ctx); //can't set default values! result maybe unexpected
|
||||
return;
|
||||
}
|
||||
QVariant opt(options);
|
||||
if (options.contains(QStringLiteral("avformat")))
|
||||
opt = options.value(QStringLiteral("avformat"));
|
||||
Internal::setOptionsToFFmpegObj(opt, format_ctx);
|
||||
}
|
||||
} //namespace FAV
|
||||
#endif // #if !(REMOVE_AV_MUXER)
|
||||
98
project/fm_viewer/fav/AVMuxer.h
Normal file
98
project/fm_viewer/fav/AVMuxer.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QAV_AVMUXER_H
|
||||
#define QAV_AVMUXER_H
|
||||
|
||||
#define REMOVE_AV_MUXER 1
|
||||
#if !(REMOVE_AV_MUXER)
|
||||
|
||||
#include "AVError.h"
|
||||
#include "Packet.h"
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QIODevice>
|
||||
#include <QtCore/QScopedPointer>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QVariant>
|
||||
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class MediaIO;
|
||||
class VideoEncoder;
|
||||
class AudioEncoder;
|
||||
class Q_AV_EXPORT AVMuxer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static const QStringList& supportedFormats();
|
||||
static const QStringList& supportedExtensions();
|
||||
/// Supported ffmpeg/libav input protocols(not complete). A static string list
|
||||
static const QStringList& supportedProtocols();
|
||||
|
||||
AVMuxer(QObject *parent = 0);
|
||||
~AVMuxer();
|
||||
QString fileName() const;
|
||||
QIODevice* ioDevice() const;
|
||||
/// not null for QIODevice, custom protocols
|
||||
MediaIO *mediaIO() const;
|
||||
/*!
|
||||
* \brief setMedia
|
||||
* \return whether the media is changed
|
||||
*/
|
||||
bool setMedia(const QString& fileName);
|
||||
bool setMedia(QIODevice* dev);
|
||||
bool setMedia(MediaIO* io);
|
||||
/*!
|
||||
* \brief setFormat
|
||||
* Force the output format.
|
||||
* formatForced() is reset if media changed. So you have to call setFormat() for every media
|
||||
* you want to force the format.
|
||||
* Also useful for custom io
|
||||
*/
|
||||
void setFormat(const QString& fmt);
|
||||
QString formatForced() const;
|
||||
|
||||
bool open();
|
||||
bool close();
|
||||
bool isOpen() const;
|
||||
|
||||
// TODO: copyAudioContext(void* avctx) for copy encoding without decoding
|
||||
void copyProperties(VideoEncoder* enc); //rename to setEncoder
|
||||
void copyProperties(AudioEncoder* enc);
|
||||
|
||||
void setOptions(const QVariantHash &dict);
|
||||
QVariantHash options() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
// TODO: multiple streams. Packet.type,stream
|
||||
bool writeAudio(const FAV::Packet& packet);
|
||||
bool writeVideo(const FAV::Packet& packet);
|
||||
|
||||
//void writeHeader();
|
||||
//void writeTrailer();
|
||||
private:
|
||||
class Private;
|
||||
QScopedPointer<Private> d;
|
||||
};
|
||||
} //namespace FAV
|
||||
#endif //QAV_AVMUXER_H
|
||||
#endif //#if !(REMOVE_AV_MUXER)
|
||||
189
project/fm_viewer/fav/AVOutput.cpp
Normal file
189
project/fm_viewer/fav/AVOutput.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AVOutput.h"
|
||||
#include "AVOutput_p.h"
|
||||
#include "Filter.h"
|
||||
#include "FilterContext.h"
|
||||
#include "FilterManager.h"
|
||||
#include "OutputSet.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
AVOutputPrivate::~AVOutputPrivate() {
|
||||
cond.wakeAll(); //WHY: failed to wake up
|
||||
}
|
||||
|
||||
AVOutput::AVOutput()
|
||||
{
|
||||
}
|
||||
|
||||
AVOutput::AVOutput(AVOutputPrivate &d)
|
||||
:DPTR_INIT(&d)
|
||||
{
|
||||
}
|
||||
|
||||
AVOutput::~AVOutput()
|
||||
{
|
||||
pause(false); //Does not work. cond may still waiting when destroyed
|
||||
detach();
|
||||
DPTR_D(AVOutput);
|
||||
if (d.filter_context) {
|
||||
delete d.filter_context;
|
||||
d.filter_context = 0;
|
||||
}
|
||||
foreach (Filter *f, d.pending_uninstall_filters) {
|
||||
d.filters.removeAll(f);
|
||||
}
|
||||
QList<Filter*>::iterator it = d.filters.begin();
|
||||
while (it != d.filters.end()) {
|
||||
// if not uninstall here, if AVOutput is also an QObject (for example, widget based renderers)
|
||||
// then qobject children filters will be deleted when parent is destroying and call FilterManager::uninstallFilter()
|
||||
// and FilterManager::instance().unregisterFilter(filter, this) too late that AVOutput is almost be destroyed
|
||||
uninstallFilter(*it);
|
||||
// 1 filter has 1 target. so if has output filter in manager, the output is this output
|
||||
/*FilterManager::instance().hasOutputFilter(*it) && */
|
||||
if ((*it)->isOwnedByTarget() && !(*it)->parent())
|
||||
delete *it;
|
||||
++it;
|
||||
}
|
||||
d.filters.clear();
|
||||
}
|
||||
|
||||
bool AVOutput::isAvailable() const
|
||||
{
|
||||
return d_func().available;
|
||||
}
|
||||
|
||||
void AVOutput::pause(bool p)
|
||||
{
|
||||
DPTR_D(AVOutput);
|
||||
if (d.paused == p)
|
||||
return;
|
||||
d.paused = p;
|
||||
}
|
||||
|
||||
bool AVOutput::isPaused() const
|
||||
{
|
||||
return d_func().paused;
|
||||
}
|
||||
|
||||
//TODO: how to call this automatically before write()?
|
||||
bool AVOutput::tryPause()
|
||||
{
|
||||
DPTR_D(AVOutput);
|
||||
if (!d.paused)
|
||||
return false;
|
||||
QMutexLocker lock(&d.mutex);
|
||||
Q_UNUSED(lock);
|
||||
d.cond.wait(&d.mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AVOutput::addOutputSet(OutputSet *set)
|
||||
{
|
||||
d_func().output_sets.append(set);
|
||||
}
|
||||
|
||||
void AVOutput::removeOutputSet(OutputSet *set)
|
||||
{
|
||||
d_func().output_sets.removeAll(set);
|
||||
}
|
||||
|
||||
void AVOutput::attach(OutputSet *set)
|
||||
{
|
||||
set->addOutput(this);
|
||||
}
|
||||
|
||||
void AVOutput::detach(OutputSet *set)
|
||||
{
|
||||
DPTR_D(AVOutput);
|
||||
if (set) {
|
||||
set->removeOutput(this);
|
||||
return;
|
||||
}
|
||||
foreach(OutputSet *set, d.output_sets) {
|
||||
set->removeOutput(this);
|
||||
}
|
||||
}
|
||||
|
||||
QList<Filter*>& AVOutput::filters()
|
||||
{
|
||||
return d_func().filters;
|
||||
}
|
||||
|
||||
void AVOutput::setStatistics(Statistics *statistics)
|
||||
{
|
||||
DPTR_D(AVOutput);
|
||||
d.statistics = statistics;
|
||||
}
|
||||
|
||||
bool AVOutput::installFilter(Filter *filter, int index)
|
||||
{
|
||||
return onInstallFilter(filter, index);
|
||||
}
|
||||
|
||||
bool AVOutput::onInstallFilter(Filter *filter, int index)
|
||||
{
|
||||
if (!FilterManager::instance().registerFilter(filter, this, index)) {
|
||||
return false;
|
||||
}
|
||||
DPTR_D(AVOutput);
|
||||
d.filters = FilterManager::instance().outputFilters(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME: how to ensure thread safe using mutex etc? for a video filter, both are in main thread.
|
||||
* an audio filter on audio output may be in audio thread
|
||||
*/
|
||||
bool AVOutput::uninstallFilter(Filter *filter)
|
||||
{
|
||||
return onUninstallFilter(filter);
|
||||
}
|
||||
|
||||
bool AVOutput::onUninstallFilter(Filter *filter)
|
||||
{
|
||||
DPTR_D(AVOutput);
|
||||
FilterManager::instance().unregisterFilter(filter, this);
|
||||
d.pending_uninstall_filters.push_back(filter);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AVOutput::hanlePendingTasks()
|
||||
{
|
||||
onHanlePendingTasks();
|
||||
}
|
||||
|
||||
bool AVOutput::onHanlePendingTasks()
|
||||
{
|
||||
DPTR_D(AVOutput);
|
||||
if (d.pending_uninstall_filters.isEmpty())
|
||||
return false;
|
||||
foreach (Filter *filter, d.pending_uninstall_filters) {
|
||||
d.filters.removeAll(filter);
|
||||
}
|
||||
d.pending_uninstall_filters.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
94
project/fm_viewer/fav/AVOutput.h
Normal file
94
project/fm_viewer/fav/AVOutput.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QAV_AVOUTPUT_H
|
||||
#define QAV_AVOUTPUT_H
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include "_fav_constants.h"
|
||||
|
||||
/*!
|
||||
* TODO: add api id(), name(), detail()
|
||||
*/
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class AVDecoder;
|
||||
class AVOutputPrivate;
|
||||
class Filter;
|
||||
class Statistics;
|
||||
class OutputSet;
|
||||
class Q_AV_EXPORT AVOutput
|
||||
{
|
||||
DPTR_DECLARE_PRIVATE(AVOutput)
|
||||
public:
|
||||
AVOutput();
|
||||
virtual ~AVOutput();
|
||||
bool isAvailable() const;
|
||||
|
||||
//void addSource(AVPlayer* player); //call player.addVideoRenderer(this)
|
||||
//void removeSource(AVPlayer* player);
|
||||
//Demuxer thread automatically paused because packets will be full
|
||||
//only pause the renderering, the thread going on. If all outputs are paused, then pause the thread(OutputSet.tryPause)
|
||||
//TODO: what about audio's pause api?
|
||||
void pause(bool p); //processEvents when waiting?
|
||||
bool isPaused() const;
|
||||
QList<Filter*>& filters();
|
||||
/*!
|
||||
* \brief installFilter
|
||||
* Insert a filter at position 'index' of current filter list.
|
||||
* If the filter is already installed, it will move to the correct index.
|
||||
* \param index A nagative index == size() + index. If index >= size(), append at last
|
||||
* \return false if already installed
|
||||
*/
|
||||
bool installFilter(Filter *filter, int index = 0x7fffffff);
|
||||
bool uninstallFilter(Filter *filter);
|
||||
protected:
|
||||
AVOutput(AVOutputPrivate& d);
|
||||
/*
|
||||
* If the pause state is true setted by pause(true), then block the thread and wait for pause state changed, i.e. pause(false)
|
||||
* and return true. Otherwise, return false immediatly.
|
||||
*/
|
||||
Q_DECL_DEPRECATED bool tryPause(); //move to OutputSet
|
||||
//TODO: we need an active set
|
||||
void addOutputSet(OutputSet *set);
|
||||
void removeOutputSet(OutputSet *set);
|
||||
void attach(OutputSet *set); //add this to set
|
||||
void detach(OutputSet *set = 0); //detatch from (all, if 0) output set(s)
|
||||
// for thread safe
|
||||
void hanlePendingTasks();
|
||||
|
||||
DPTR_DECLARE(AVOutput)
|
||||
|
||||
private:
|
||||
// for proxy VideoOutput
|
||||
virtual void setStatistics(Statistics* statistics); //called by friend AVPlayer
|
||||
virtual bool onInstallFilter(Filter *filter, int index);
|
||||
virtual bool onUninstallFilter(Filter *filter);
|
||||
// only called in handlePaintEvent. But filters may change. so required by proxy to update it's filters
|
||||
virtual bool onHanlePendingTasks(); //return true: proxy update filters
|
||||
friend class AVPlayer;
|
||||
friend class OutputSet;
|
||||
friend class VideoOutput;
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif //QAV_AVOUTPUT_H
|
||||
64
project/fm_viewer/fav/AVOutput_p.h
Normal file
64
project/fm_viewer/fav/AVOutput_p.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2013 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AVOUTPUT_P_H
|
||||
#define QTAV_AVOUTPUT_P_H
|
||||
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QWaitCondition>
|
||||
#include "_fav_constants.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class AVOutput;
|
||||
class AVDecoder;
|
||||
class Filter;
|
||||
class VideoFilterContext;
|
||||
class Statistics;
|
||||
class OutputSet;
|
||||
class Q_AV_PRIVATE_EXPORT AVOutputPrivate : public DPtrPrivate<AVOutput>
|
||||
{
|
||||
public:
|
||||
AVOutputPrivate():
|
||||
paused(false)
|
||||
, available(true)
|
||||
, statistics(0)
|
||||
, filter_context(0)
|
||||
{}
|
||||
virtual ~AVOutputPrivate();
|
||||
|
||||
bool paused;
|
||||
bool available;
|
||||
QMutex mutex; //pause
|
||||
QWaitCondition cond; //pause
|
||||
|
||||
//paintEvent is in main thread, copy it(only dynamic information) is better.
|
||||
//the static data are copied from AVPlayer when open
|
||||
Statistics *statistics; //do not own the ptr. just use AVPlayer's statistics ptr
|
||||
VideoFilterContext *filter_context; //create internally by the renderer with correct type
|
||||
QList<Filter*> filters;
|
||||
QList<Filter*> pending_uninstall_filters;
|
||||
QList<OutputSet*> output_sets;
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QTAV_AVOUTPUT_P_H
|
||||
2055
project/fm_viewer/fav/AVPlayer.cpp
Normal file
2055
project/fm_viewer/fav/AVPlayer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
698
project/fm_viewer/fav/AVPlayer.h
Normal file
698
project/fm_viewer/fav/AVPlayer.h
Normal file
@@ -0,0 +1,698 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AVPLAYER_H
|
||||
#define QTAV_AVPLAYER_H
|
||||
|
||||
#include <limits>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QScopedPointer>
|
||||
#include "AudioOutput.h"
|
||||
#include "AVClock.h"
|
||||
#include "Statistics.h"
|
||||
#include "VideoDecoder.h"
|
||||
#include "AVError.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QIODevice;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class MediaIO;
|
||||
class AudioOutput;
|
||||
class VideoRenderer;
|
||||
class Filter;
|
||||
class AudioFilter;
|
||||
class VideoFilter;
|
||||
class VideoCapture;
|
||||
/*!
|
||||
* \brief The AVPlayer class
|
||||
* Preload:
|
||||
* \code
|
||||
* player->setFile(...);
|
||||
* player->load();
|
||||
* do some thing...
|
||||
* player->play();
|
||||
* \endcode
|
||||
* No preload:
|
||||
* \code
|
||||
* player->setFile(...);
|
||||
* player->play();
|
||||
* \endcode
|
||||
*/
|
||||
class Q_AV_EXPORT AVPlayer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool relativeTimeMode READ relativeTimeMode WRITE setRelativeTimeMode NOTIFY relativeTimeModeChanged)
|
||||
Q_PROPERTY(bool autoLoad READ isAutoLoad WRITE setAutoLoad NOTIFY autoLoadChanged)
|
||||
Q_PROPERTY(bool asyncLoad READ isAsyncLoad WRITE setAsyncLoad NOTIFY asyncLoadChanged)
|
||||
Q_PROPERTY(qreal bufferProgress READ bufferProgress NOTIFY bufferProgressChanged)
|
||||
Q_PROPERTY(bool seekable READ isSeekable NOTIFY seekableChanged)
|
||||
Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged)
|
||||
Q_PROPERTY(qint64 position READ position WRITE setPosition NOTIFY positionChanged)
|
||||
Q_PROPERTY(qint64 startPosition READ startPosition WRITE setStartPosition NOTIFY startPositionChanged)
|
||||
Q_PROPERTY(qint64 stopPosition READ stopPosition WRITE setStopPosition NOTIFY stopPositionChanged)
|
||||
Q_PROPERTY(qint64 repeat READ repeat WRITE setRepeat NOTIFY repeatChanged)
|
||||
Q_PROPERTY(int currentRepeat READ currentRepeat NOTIFY currentRepeatChanged)
|
||||
Q_PROPERTY(qint64 interruptTimeout READ interruptTimeout WRITE setInterruptTimeout NOTIFY interruptTimeoutChanged)
|
||||
Q_PROPERTY(bool interruptOnTimeout READ isInterruptOnTimeout WRITE setInterruptOnTimeout NOTIFY interruptOnTimeoutChanged)
|
||||
Q_PROPERTY(int notifyInterval READ notifyInterval WRITE setNotifyInterval NOTIFY notifyIntervalChanged)
|
||||
Q_PROPERTY(int brightness READ brightness WRITE setBrightness NOTIFY brightnessChanged)
|
||||
Q_PROPERTY(int contrast READ contrast WRITE setContrast NOTIFY contrastChanged)
|
||||
Q_PROPERTY(int saturation READ saturation WRITE setSaturation NOTIFY saturationChanged)
|
||||
Q_PROPERTY(State state READ state WRITE setState NOTIFY stateChanged)
|
||||
Q_PROPERTY(FAV::MediaStatus mediaStatus READ mediaStatus NOTIFY mediaStatusChanged)
|
||||
Q_PROPERTY(FAV::MediaEndAction mediaEndAction READ mediaEndAction WRITE setMediaEndAction NOTIFY mediaEndActionChanged)
|
||||
Q_ENUMS(State)
|
||||
public:
|
||||
/*!
|
||||
* \brief The State enum
|
||||
* The playback state. It's different from MediaStatus. MediaStatus indicates media stream state
|
||||
*/
|
||||
enum State {
|
||||
StoppedState,
|
||||
PlayingState, /// Start to play if it was stopped, or resume if it was paused
|
||||
PausedState
|
||||
};
|
||||
|
||||
|
||||
/// Supported input protocols. A static string list
|
||||
static const QStringList& supportedProtocols();
|
||||
|
||||
explicit AVPlayer(QObject *parent = 0);
|
||||
~AVPlayer();
|
||||
|
||||
#if (REAR_SYNC_FRONT)
|
||||
void applyRearDuration(qint64 frontDuraiton);
|
||||
#endif
|
||||
#if (USE_FFMPEG_PW)
|
||||
static void setPassword(char* pw);
|
||||
static char* getPassword();
|
||||
#endif // USE_FFMPEG_PW
|
||||
|
||||
bool seek_exist();
|
||||
|
||||
#if (USE_SKIP_COUNT)
|
||||
void setSkipCount(int nCount);
|
||||
#endif
|
||||
|
||||
#if (SKIP_FIRST_CORRUPT_FRAME)
|
||||
void setSkipFrameThreadWait(int mSleep);
|
||||
#endif
|
||||
#if (USE_AUDIO_DISABLE)
|
||||
void setAudioDisable(bool disable);
|
||||
#endif
|
||||
|
||||
qint32 realVideoStreamCount();
|
||||
|
||||
/*!
|
||||
* \brief masterClock
|
||||
* setClockType() should call when playback started.
|
||||
* \return
|
||||
*/
|
||||
AVClock* masterClock();
|
||||
// If path is different from previous one, the stream to play will be reset to default.
|
||||
/*!
|
||||
* \brief setFile
|
||||
* TODO: Set current media source if current media is invalid or auto load is enabled.
|
||||
* Otherwise set as the pendding media and it becomes the current media if the next
|
||||
* load(), play() is called
|
||||
* \param path
|
||||
*/
|
||||
void setFile(const QString& path);
|
||||
QString file() const;
|
||||
/*!
|
||||
* \brief setIODevice
|
||||
* Play media stream from QIODevice. AVPlayer does not take the ownership. You have to manage device lifetime.
|
||||
*/
|
||||
void setIODevice(QIODevice* device);
|
||||
/*!
|
||||
* \brief setInput
|
||||
* Play media stream from custom MediaIO. AVPlayer's demuxer takes the ownership. Call it when player is stopped.
|
||||
*/
|
||||
void setInput(MediaIO* in);
|
||||
MediaIO* input() const;
|
||||
|
||||
bool isLoaded() const;
|
||||
/*!
|
||||
* \brief setAsyncLoad
|
||||
* async load is enabled by default
|
||||
*/
|
||||
void setAsyncLoad(bool value = true);
|
||||
bool isAsyncLoad() const;
|
||||
/*!
|
||||
* \brief setAutoLoad
|
||||
* true: current media source changed immediatly and stop current playback if new media source is set.
|
||||
* status becomes LoadingMedia=>LoadedMedia before play( and BufferedMedia when playing?)
|
||||
* false:
|
||||
* Default is false
|
||||
*/
|
||||
void setAutoLoad(bool value = true); // NOT implemented
|
||||
bool isAutoLoad() const; // NOT implemented
|
||||
|
||||
MediaStatus mediaStatus() const;
|
||||
// TODO: add hasAudio, hasVideo, isMusic(has pic)
|
||||
/*!
|
||||
* \brief relativeTimeMode
|
||||
* true (default): mediaStartPosition() is always 0. All time related API, for example setPosition(), position() and positionChanged()
|
||||
* use relative time instead of real pts
|
||||
* false: mediaStartPosition() is from media stream itself, same as absoluteMediaStartPosition()
|
||||
* To get real start time, use statistics().start_time. Or setRelativeTimeMode(false) first but may affect playback when playing.
|
||||
*/
|
||||
bool relativeTimeMode() const;
|
||||
/// Media stream property. The first timestamp in the media
|
||||
qint64 absoluteMediaStartPosition() const;
|
||||
qreal durationF() const; //unit: s, This function may be removed in the future.
|
||||
qint64 duration() const; //unit: ms. media duration. network stream may be very small, why?
|
||||
#if (PLAY_SYNC_FIX2)
|
||||
qint64 frameDuration() const;
|
||||
#endif // PLAY_SYNC_FIX2
|
||||
/*!
|
||||
* \brief mediaStartPosition
|
||||
* If relativeTimeMode() is true (default), it's 0. Otherwise is the same as absoluteMediaStartPosition()
|
||||
*/
|
||||
qint64 mediaStartPosition() const;
|
||||
/// mediaStartPosition() + duration().
|
||||
qint64 mediaStopPosition() const;
|
||||
qreal mediaStartPositionF() const; //unit: s
|
||||
qreal mediaStopPositionF() const; //unit: s
|
||||
// can set by user. may be not the real media start position.
|
||||
qint64 startPosition() const;
|
||||
/*!
|
||||
* \brief stopPosition: the position at which player should stop playing
|
||||
* \return
|
||||
* If media stream is not a local file, stopPosition()==max value of qint64
|
||||
*/
|
||||
qint64 stopPosition() const; //unit: ms
|
||||
qint64 position() const; //unit: ms
|
||||
//0: play once. N: play N+1 times. <0: infinity
|
||||
int repeat() const; //or repeatMax()?
|
||||
/*!
|
||||
* \brief currentRepeat
|
||||
* \return -1 if not playback is stopped, otherwise (Playback times - 1)
|
||||
*/
|
||||
int currentRepeat() const;
|
||||
/*!
|
||||
* \brief setExternalAudio
|
||||
* set audio track from an external audio stream. this will try to load the external audio and
|
||||
* select the 1st audio stream. If no error happens, the external audio stream will be set to
|
||||
* current audio track.
|
||||
* If external audio stream <0 before play, stream is auto selected
|
||||
* You have to manually empty value to unload the external audio!
|
||||
* \param file external audio file path. Set empty to use internal audio tracks. TODO: reset stream number if switch to internal
|
||||
* \return true if no error happens
|
||||
*/
|
||||
bool setExternalAudio(const QString& file);
|
||||
QString externalAudio() const;
|
||||
/*!
|
||||
* \brief externalAudioTracks
|
||||
* A list of QVariantMap. Using QVariantMap and QVariantList is mainly for QML support.
|
||||
* [ {id: 0, file: abc.dts, language: eng, title: xyz}, ...]
|
||||
* id: used for setAudioStream(id)
|
||||
* \sa externalAudioTracksChanged
|
||||
*/
|
||||
const QVariantList& externalAudioTracks() const;
|
||||
const QVariantList& internalAudioTracks() const;
|
||||
/*!
|
||||
* \brief setAudioStream
|
||||
* set an external audio file and stream number as audio track
|
||||
* \param file external audio file. set empty to use internal audio tracks
|
||||
* \param n audio stream number n=0, 1, .... n<0: disable audio thread
|
||||
* \return false if fail
|
||||
*/
|
||||
bool setAudioStream(const QString& file, int n = 0);
|
||||
/*!
|
||||
* set audio/video/subtitle stream to n. n=0, 1, 2..., means the 1st, 2nd, 3rd audio/video/subtitle stream
|
||||
* If n < 0, there will be no audio thread and sound/
|
||||
* If a new file is set(except the first time) then a best stream will be selected. If the file not changed,
|
||||
* e.g. replay, then the stream not change
|
||||
* return: false if stream not changed, not valid
|
||||
* TODO: rename to track instead of stream
|
||||
*/
|
||||
/*!
|
||||
* \brief setAudioStream
|
||||
* Set audio stream number in current media or external audio file
|
||||
*/
|
||||
bool setAudioStream(int n);
|
||||
//TODO: n<0, no video thread
|
||||
bool setVideoStream(int n);
|
||||
#if (AVFORMAT_FILTER_SPEED)
|
||||
void setAVFormatFilter(QString& filter);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief internalAudioTracks
|
||||
* A list of QVariantMap. Using QVariantMap and QVariantList is mainly for QML support.
|
||||
* [ {id: 0, file: abc.dts, language: eng, title: xyz}, ...]
|
||||
* id: used for setSubtitleStream(id)
|
||||
* \sa internalSubtitleTracksChanged;
|
||||
* Different with external audio tracks, the external subtitle is supported by class Subtitle.
|
||||
*/
|
||||
#if !(DO_NOT_USE_SUBTITLE)
|
||||
const QVariantList& internalSubtitleTracks() const;
|
||||
bool setSubtitleStream(int n);
|
||||
#endif
|
||||
int currentAudioStream() const;
|
||||
int currentVideoStream() const;
|
||||
#if !(DO_NOT_USE_SUBTITLE)
|
||||
int currentSubtitleStream() const;
|
||||
#endif
|
||||
int audioStreamCount() const;
|
||||
int videoStreamCount() const;
|
||||
#if !(DO_NOT_USE_SUBTITLE)
|
||||
int subtitleStreamCount() const;
|
||||
#endif
|
||||
|
||||
// 화면해상도 가져오기
|
||||
QSize videoResolution() const;
|
||||
/*!
|
||||
* \brief videoCapture
|
||||
* Capture the current frame using videoCapture()->capture()
|
||||
* \sa VideoCapture
|
||||
*/
|
||||
VideoCapture *videoCapture() const;
|
||||
//TODO: no replay, replay without parsing the stream if it's already loaded. (not implemented). to force reload the stream, unload() then play()
|
||||
/*!
|
||||
* \brief play
|
||||
* If isAsyncLoad() is true (default), play() will return immediately. Signals started() and stateChanged() will be emitted if media is loaded and playback starts.
|
||||
*/
|
||||
void play(const QString& path);
|
||||
bool isPlaying() const;
|
||||
bool isPaused() const;
|
||||
/*!
|
||||
* \brief state
|
||||
* Player's playback state. Default is StoppedState.
|
||||
* setState() is a replacement of play(), stop(), pause(bool)
|
||||
* \return
|
||||
*/
|
||||
State state() const;
|
||||
void setState(State value);
|
||||
|
||||
// TODO: use id as parameter and return ptr?
|
||||
void addVideoRenderer(VideoRenderer *renderer);
|
||||
void removeVideoRenderer(VideoRenderer *renderer);
|
||||
void clearVideoRenderers();
|
||||
void setRenderer(VideoRenderer* renderer, bool updateCustomRatio = true);
|
||||
VideoRenderer* renderer();
|
||||
QList<VideoRenderer*> videoOutputs();
|
||||
/*!
|
||||
* \brief audio
|
||||
* AVPlayer always has an AudioOutput instance. You can access or control audio output properties through audio().
|
||||
* To disable audio output, set audio()->setBackends(QStringList() << "null") before starting playback
|
||||
* \return
|
||||
*/
|
||||
AudioOutput* audio();
|
||||
/*!
|
||||
* \brief setSpeed
|
||||
* Set playback speed.
|
||||
* \param speed speed > 0. 1.0: normal speed
|
||||
* TODO: playbackRate
|
||||
*/
|
||||
void setSpeed(qreal speed);
|
||||
qreal speed() const;
|
||||
|
||||
/*!
|
||||
* \brief setInterruptTimeout
|
||||
* Emit error(usually network error) if open/read spends too much time.
|
||||
* If isInterruptOnTimeout() is true, abort current operation and stop playback
|
||||
* \param ms milliseconds. <0: never interrupt.
|
||||
*/
|
||||
/// TODO: rename to timeout
|
||||
void setInterruptTimeout(qint64 ms);
|
||||
qint64 interruptTimeout() const;
|
||||
/*!
|
||||
* \brief setInterruptOnTimeout
|
||||
* \param value
|
||||
*/
|
||||
void setInterruptOnTimeout(bool value);
|
||||
bool isInterruptOnTimeout() const;
|
||||
/*!
|
||||
* \brief setFrameRate
|
||||
* Force the (video) frame rate to a given value.
|
||||
* AVClock::ClockType and autoClock will be changed internally.
|
||||
* \param value <=0: use previous playback speed.
|
||||
* >0: force to a given (video) frame rate
|
||||
*/
|
||||
void setFrameRate(qreal value);
|
||||
qreal forcedFrameRate() const;
|
||||
//Statistics& statistics();
|
||||
const Statistics& statistics() const;
|
||||
/*!
|
||||
* \brief installFilter
|
||||
* Insert a filter at position 'index' of current filter list.
|
||||
* If the filter is already installed, it will move to the correct index.
|
||||
* \param index A nagative index == size() + index. If index >= size(), append at last
|
||||
* \return false if audio/video thread is not ready. But the filter will be installed when thread is ready.
|
||||
* false if already installed.
|
||||
*/
|
||||
bool installFilter(AudioFilter* filter, int index = 0x7FFFFFFF);
|
||||
bool installFilter(VideoFilter* filter, int index = 0x7FFFFFFF);
|
||||
bool uninstallFilter(AudioFilter* filter);
|
||||
bool uninstallFilter(VideoFilter* filter);
|
||||
QList<Filter*> audioFilters() const;
|
||||
QList<Filter*> videoFilters() const;
|
||||
/*!
|
||||
* \brief setPriority
|
||||
* A suitable decoder will be applied when video is playing. The decoder does not change in current playback if no decoder is found.
|
||||
* If not playing or no decoder found, the decoder will be changed at the next playback
|
||||
* \param ids
|
||||
*/
|
||||
void setPriority(const QVector<VideoDecoderId>& ids);
|
||||
/*!
|
||||
* \brief setVideoDecoderPriority
|
||||
* also can set in opt.priority
|
||||
* \param names the video decoder name list in priority order. Name can be "FFmpeg", "CUDA", "DXVA", "D3D11", "VAAPI", "VDA", "VideoToolbox", "MediaCodec", "MMAL", "QSV", "CrystalHD", case insensitive
|
||||
*/
|
||||
void setVideoDecoderPriority(const QStringList& names);
|
||||
QStringList videoDecoderPriority() const;
|
||||
//void setPriority(const QVector<AudioOutputId>& ids);
|
||||
/*!
|
||||
* below APIs are deprecated.
|
||||
* TODO: setValue("key", value) or setOption("key", value) ?
|
||||
* enum OptionKey { Brightness, ... VideoCodec, FilterOptions...}
|
||||
* or use QString as keys?
|
||||
*/
|
||||
int brightness() const;
|
||||
int contrast() const;
|
||||
int hue() const; //not implemented
|
||||
int saturation() const;
|
||||
/*!
|
||||
* \sa AVDemuxer::setOptions()
|
||||
* example:
|
||||
* QVariantHash opt;
|
||||
* opt["rtsp_transport"] = "tcp"
|
||||
* player->setOptionsForFormat(opt);
|
||||
*/
|
||||
// avformat_open_input
|
||||
void setOptionsForFormat(const QVariantHash &dict);
|
||||
QVariantHash optionsForFormat() const;
|
||||
// avcodec_open2. TODO: the same for audio/video codec?
|
||||
/*!
|
||||
* \sa AVDecoder::setOptions()
|
||||
* example:
|
||||
* QVariantHash opt, vaopt, ffopt;
|
||||
* vaopt["display"] = "X11";
|
||||
* opt["vaapi"] = vaopt; // only apply for va-api decoder
|
||||
* ffopt["vismv"] = "pf";
|
||||
* opt["ffmpeg"] = ffopt; // only apply for ffmpeg software decoder
|
||||
* player->setOptionsForVideoCodec(opt);
|
||||
*/
|
||||
// QVariantHash deprecated, use QVariantMap to get better js compatibility
|
||||
void setOptionsForAudioCodec(const QVariantHash &dict);
|
||||
QVariantHash optionsForAudioCodec() const;
|
||||
void setOptionsForVideoCodec(const QVariantHash& dict);
|
||||
QVariantHash optionsForVideoCodec() const;
|
||||
|
||||
/*!
|
||||
* \brief mediaEndAction
|
||||
* The action at the end of media or when playback is stopped. Default is quit threads and clear video renderers.
|
||||
* If the flag MediaEndAction_KeepDisplay is set, the last video frame will keep displaying in video renderers.
|
||||
* If MediaEndAction_Pause is set, you can still seek and resume the playback because no thread exits.
|
||||
*/
|
||||
MediaEndAction mediaEndAction() const;
|
||||
void setMediaEndAction(MediaEndAction value);
|
||||
|
||||
public Q_SLOTS:
|
||||
/*!
|
||||
* \brief load
|
||||
* Load the current media set by setFile(); Can be used to reload a media and call play() later. If already loaded, does nothing and return true.
|
||||
* If async load, mediaStatus() becomes LoadingMedia and user should connect signal loaded()
|
||||
* or mediaStatusChanged(FAV::LoadedMedia) to a slot
|
||||
* \return true if success or already loaded.
|
||||
*/
|
||||
bool load();
|
||||
|
||||
void togglePause();
|
||||
void pause(bool p = true);
|
||||
/*!
|
||||
* \brief play
|
||||
* Load media and start playback. If current media is playing and media source is not changed, nothing to do. If media source is not changed, try to load (not in LoadingStatus or LoadedStatus) and start playback. If media source changed, reload and start playback.
|
||||
*/
|
||||
void play();
|
||||
/*!
|
||||
* \brief stop
|
||||
* Stop playback. It blocks current thread until the playback is stopped. Will emit signal stopped(). startPosition(), stopPosition(), repeat() are reset
|
||||
*/
|
||||
void stop();
|
||||
// /*!
|
||||
// * \brief stepForward
|
||||
// * Play the next frame and pause
|
||||
// */
|
||||
// void stepForward();
|
||||
// /*!
|
||||
// * \brief stepBackward
|
||||
// * Play the previous frame and pause. Currently only support the previous decoded frames
|
||||
// */
|
||||
// void stepBackward();
|
||||
|
||||
void setRelativeTimeMode(bool value);
|
||||
/*!
|
||||
* \brief setRepeat
|
||||
* Repeat max times between startPosition() and endPosition(). It's reset if playback is stopped.
|
||||
* max==0: no repeat
|
||||
* max<0: infinity. std::numeric_limits<int>::max();
|
||||
* \param max
|
||||
*/
|
||||
void setRepeat(int max);
|
||||
/*!
|
||||
* \brief startPosition
|
||||
* Used to repeat from startPosition() to endPosition().
|
||||
* You can also start to play at a given position
|
||||
* \code
|
||||
* player->setStartPosition();
|
||||
* player->play("some video");
|
||||
* \endcode
|
||||
* pos < 0: equals duration()+pos
|
||||
* pos == 0, means start at the beginning of media stream
|
||||
* pos > media end position, or pos > normalized stopPosition(): undefined
|
||||
*/
|
||||
void setStartPosition(qint64 pos);
|
||||
/*!
|
||||
* \brief stopPosition
|
||||
* pos > mediaStopPosition(): mediaStopPosition()
|
||||
* pos < 0: duration() + pos
|
||||
* With the default value, the playback will not stop until the end of media (including dynamically changed media duration, e.g. recording video)
|
||||
*/
|
||||
void setStopPosition(qint64 pos = std::numeric_limits<qint64>::max());
|
||||
/*!
|
||||
* \brief setTimeRange
|
||||
* Set startPosition and stopPosition. Make sure start <= stop.
|
||||
*/
|
||||
void setTimeRange(qint64 start, qint64 stop = std::numeric_limits<qint64>::max());
|
||||
|
||||
bool isSeekable() const;
|
||||
/*!
|
||||
* \brief setPosition equals to seek(qreal)
|
||||
* position < 0: 0
|
||||
* \param position in ms
|
||||
*/
|
||||
void setPosition(qint64 position);
|
||||
void seek(qreal r); // r: [0, 1]
|
||||
void seek(qint64 pos); //ms. same as setPosition(pos)
|
||||
void seekForward();
|
||||
void seekBackward();
|
||||
void setSeekType(SeekType type);
|
||||
SeekType seekType() const;
|
||||
|
||||
/*!
|
||||
* \brief bufferProgress
|
||||
* How much the data buffer is currently filled. From 0.0 to 1.0.
|
||||
* Playback can start or resume only when the buffer is entirely filled.
|
||||
*/
|
||||
qreal bufferProgress() const;
|
||||
/*!
|
||||
* \brief bufferSpeed
|
||||
* Bytes/s
|
||||
* \return 0 if not buffering. >= 0 if buffering
|
||||
*/
|
||||
qreal bufferSpeed() const;
|
||||
/*!
|
||||
* \brief buffered
|
||||
* Current buffered value in msecs, bytes or packet count depending on bufferMode()
|
||||
*/
|
||||
qint64 buffered() const;
|
||||
void setBufferMode(BufferMode mode);
|
||||
BufferMode bufferMode() const;
|
||||
/*!
|
||||
* \brief setBufferValue
|
||||
* Ensure the buffered msecs/bytes/packets in queue is at least the given value before playback starts.
|
||||
* Set before playback starts.
|
||||
* \param value <0: auto; BufferBytes: bytes, BufferTime: msecs, BufferPackets: packets count
|
||||
*/
|
||||
void setBufferValue(qint64 value);
|
||||
int bufferValue() const;
|
||||
|
||||
/*!
|
||||
* \brief setNotifyInterval
|
||||
* The interval at which progress will update
|
||||
* \param msec <=0: auto and compute internally depending on duration and fps
|
||||
*/
|
||||
void setNotifyInterval(int msec);
|
||||
/// The real notify interval. Always > 0
|
||||
int notifyInterval() const;
|
||||
void updateClock(qint64 msecs); //update AVClock's external clock
|
||||
// for all renderers. val: [-100, 100]. other value changes nothing
|
||||
void setBrightness(int val);
|
||||
void setContrast(int val);
|
||||
void setHue(int val); //not implemented
|
||||
void setSaturation(int val);
|
||||
|
||||
#if (FIXED_FPS_DURATION)
|
||||
void setFixedDuration(quint64 duration);
|
||||
#endif
|
||||
|
||||
#if (RM_TESTING)
|
||||
int displyedFrameCount();
|
||||
#endif
|
||||
|
||||
Q_SIGNALS:
|
||||
void bufferProgressChanged(qreal);
|
||||
void relativeTimeModeChanged();
|
||||
void autoLoadChanged();
|
||||
void asyncLoadChanged();
|
||||
void muteChanged();
|
||||
void sourceChanged();
|
||||
void loaded(); // == mediaStatusChanged(FAV::LoadedMedia)
|
||||
void mediaStatusChanged(FAV::MediaStatus status); //explictly use FAV::MediaStatus
|
||||
void mediaEndActionChanged(FAV::MediaEndAction action);
|
||||
/*!
|
||||
* \brief durationChanged emit when media is loaded/unloaded
|
||||
*/
|
||||
void durationChanged(qint64);
|
||||
void error(const FAV::AVError& e); //explictly use FAV::AVError in connection for Qt4 syntax
|
||||
void paused(bool p);
|
||||
/*!
|
||||
* \brief started
|
||||
* Emitted when playback is started. Some functions that control playback should be called after playback is started, otherwise they won't work, e.g. setPosition(), pause(). stop() can be called at any time.
|
||||
*/
|
||||
void started();
|
||||
void stopped();
|
||||
#if (FIX_PLAYER_END_CLIP)
|
||||
void ended();
|
||||
#endif
|
||||
void stoppedAt(qint64 position);
|
||||
void stateChanged(FAV::AVPlayer::State state);
|
||||
void speedChanged(qreal speed);
|
||||
void repeatChanged(int r);
|
||||
void currentRepeatChanged(int r);
|
||||
void startPositionChanged(qint64 position);
|
||||
void stopPositionChanged(qint64 position);
|
||||
void seekableChanged();
|
||||
/*!
|
||||
* \brief seekFinished
|
||||
* If there is a video stream currently playing, emitted when video seek is finished. If only an audio stream is playing, emitted when audio seek is finished. The position() is the master clock value, It can be very different from video timestamp at this time.
|
||||
* \param position The video or audio timestamp when seek is finished
|
||||
*/
|
||||
#if (PLAY_SYNC_FIX2)
|
||||
void seekFinished(qint64 position);
|
||||
#else
|
||||
void seekFinished(qint64 position);
|
||||
#endif
|
||||
void positionChanged(qint64 position);
|
||||
void interruptTimeoutChanged();
|
||||
void interruptOnTimeoutChanged();
|
||||
void notifyIntervalChanged();
|
||||
void brightnessChanged(int val);
|
||||
void contrastChanged(int val);
|
||||
void hueChanged(int val);
|
||||
void saturationChanged(int val);
|
||||
void subtitleStreamChanged(int value);
|
||||
/*!
|
||||
* \brief internalAudioTracksChanged
|
||||
* Emitted when media is loaded. \sa internalAudioTracks
|
||||
*/
|
||||
void internalAudioTracksChanged(const QVariantList& tracks);
|
||||
void externalAudioTracksChanged(const QVariantList& tracks);
|
||||
void internalSubtitleTracksChanged(const QVariantList& tracks);
|
||||
/*!
|
||||
* \brief internalSubtitleHeaderRead
|
||||
* Emitted when internal subtitle is loaded. Empty data if no data.
|
||||
* codec is used by subtitle processors
|
||||
*/
|
||||
void internalSubtitleHeaderRead(const QByteArray& codec, const QByteArray& data);
|
||||
void internalSubtitlePacketRead(int track, const FAV::Packet& packet);
|
||||
#if (FIXED_FPS_DURATION)
|
||||
void mediaEnded();
|
||||
#endif
|
||||
|
||||
#if (FIRST_FRAME_NOTIFY)
|
||||
void firstFrameNotify(qreal pts);
|
||||
#endif
|
||||
|
||||
#if (FIXED_FPS_DURATION)
|
||||
void frameEnded();
|
||||
#endif
|
||||
|
||||
private Q_SLOTS:
|
||||
void loadInternal(); // simply load
|
||||
void playInternal(); // simply play
|
||||
void stopFromDemuxerThread();
|
||||
void aboutToQuitApp();
|
||||
// start/stop notify timer in this thread. use QMetaObject::invokeMethod
|
||||
void startNotifyTimer();
|
||||
void stopNotifyTimer();
|
||||
void onStarted();
|
||||
void updateMediaStatus(FAV::MediaStatus status);
|
||||
#if (PLAY_SYNC_FIX2)
|
||||
void onSeekFinished(qint64 value,qint64 requested);
|
||||
#else
|
||||
void onSeekFinished(qint64 value);
|
||||
#endif
|
||||
void tryClearVideoRenderers();
|
||||
|
||||
#if (FIRST_FRAME_NOTIFY)
|
||||
void onFirstFrameNotify(qreal pts);
|
||||
#endif
|
||||
|
||||
#if (FIXED_FPS_DURATION)
|
||||
void onFrameEnded();
|
||||
#endif
|
||||
|
||||
#if (FIX_PLAYER_END_CLIP)
|
||||
void onEnded();
|
||||
#endif
|
||||
|
||||
|
||||
protected:
|
||||
// TODO: set position check timer interval
|
||||
virtual void timerEvent(QTimerEvent *);
|
||||
private:
|
||||
/*!
|
||||
* \brief unload
|
||||
* If the media is loading or loaded but not playing, unload it. Internall use only.
|
||||
*/
|
||||
void unload(); //TODO: private. call in stop() if not load() by user? or always unload() in stop()?
|
||||
qint64 normalizedPosition(qint64 pos);
|
||||
class Private;
|
||||
QScopedPointer<Private> d;
|
||||
|
||||
public:
|
||||
int playerID;
|
||||
#if (DEBUG_PLAYER)
|
||||
void debugInfo();
|
||||
#endif
|
||||
void setPlayerID(int id);
|
||||
|
||||
};
|
||||
} //namespace FAV
|
||||
Q_DECLARE_METATYPE(FAV::AVPlayer::State)
|
||||
#endif // QTAV_AVPLAYER_H
|
||||
824
project/fm_viewer/fav/AVPlayerPrivate.cpp
Normal file
824
project/fm_viewer/fav/AVPlayerPrivate.cpp
Normal file
@@ -0,0 +1,824 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2014)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AVPlayerPrivate.h"
|
||||
#include "FilterManager.h"
|
||||
#include "OutputSet.h"
|
||||
#include "AudioDecoder.h"
|
||||
#include "AudioFormat.h"
|
||||
#include "AudioResampler.h"
|
||||
#include "MediaIO.h"
|
||||
#include "VideoCapture.h"
|
||||
#include "AVCompat.h"
|
||||
#include "Logger.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
namespace FAV {
|
||||
|
||||
namespace Internal {
|
||||
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
#include <QElapsedTimer>
|
||||
QElapsedTimer gPlayerPrivateTimer;
|
||||
#endif
|
||||
|
||||
int computeNotifyPrecision(qint64 duration, qreal fps)
|
||||
{
|
||||
#if (FIXED_PLAYER_NOTIFY_INTERVAL)
|
||||
Q_UNUSED(duration)
|
||||
Q_UNUSED(fps)
|
||||
return 200;
|
||||
#else
|
||||
if (duration <= 0 || duration > 60*1000) // no duration or 10min
|
||||
return 500;
|
||||
if (duration > 20*1000)
|
||||
return 250;
|
||||
int dt = 500;
|
||||
if (fps > 1)
|
||||
{
|
||||
dt = qMin(250, int(qreal(dt*2)/fps));
|
||||
}
|
||||
else
|
||||
{
|
||||
dt = duration / 80; //<= 250
|
||||
}
|
||||
return qMax(20, dt);
|
||||
#endif
|
||||
}
|
||||
} // namespace Internal
|
||||
|
||||
static bool correct_audio_channels(AVCodecContext *ctx)
|
||||
{
|
||||
if (ctx->channels <= 0)
|
||||
{
|
||||
if (ctx->channel_layout)
|
||||
{
|
||||
ctx->channels = av_get_channel_layout_nb_channels(ctx->channel_layout);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ctx->channel_layout)
|
||||
{
|
||||
ctx->channel_layout = av_get_default_channel_layout(ctx->channels);
|
||||
}
|
||||
}
|
||||
return ctx->channel_layout > 0 && ctx->channels > 0;
|
||||
}
|
||||
|
||||
AVPlayer::Private::Private()
|
||||
: auto_load(false)
|
||||
, async_load(true)
|
||||
, loaded(false)
|
||||
, relative_time_mode(true)
|
||||
, media_start_pts(0)
|
||||
, media_end(kInvalidPosition)
|
||||
, reset_state(true)
|
||||
, start_position(0)
|
||||
, stop_position(kInvalidPosition)
|
||||
, start_position_norm(0)
|
||||
, stop_position_norm(kInvalidPosition)
|
||||
, repeat_max(0)
|
||||
, repeat_current(-1)
|
||||
, timer_id(-1)
|
||||
, audio_track(0)
|
||||
, video_track(0)
|
||||
#if !(DO_NOT_USE_SUBTITLE)
|
||||
, subtitle_track(0)
|
||||
#endif
|
||||
, buffer_mode(BufferPackets)
|
||||
, buffer_value(-1)
|
||||
, read_thread(0)
|
||||
, clock(new AVClock(AVClock::AudioClock))
|
||||
, vo(0)
|
||||
, ao(new AudioOutput())
|
||||
, adec(0)
|
||||
, vdec(0)
|
||||
, athread(0)
|
||||
, vthread(0)
|
||||
, vcapture(0)
|
||||
, speed(1.0)
|
||||
, vos(0)
|
||||
, aos(0)
|
||||
, brightness(0)
|
||||
, contrast(0)
|
||||
, saturation(0)
|
||||
, seeking(false)
|
||||
, seek_type(AccurateSeek)
|
||||
, interrupt_timeout(30000)
|
||||
, force_fps(0)
|
||||
, notify_interval(-500)
|
||||
, status(NoMedia)
|
||||
, state(AVPlayer::StoppedState)
|
||||
, end_action(MediaEndAction_Default)
|
||||
{
|
||||
//nSkipCount = 0;
|
||||
#if (USE_AUDIO_DISABLE)
|
||||
audio_disabled = false;
|
||||
#endif
|
||||
|
||||
demuxer.setInterruptTimeout(interrupt_timeout);
|
||||
/*
|
||||
* reset_state = true;
|
||||
* must be the same value at the end of stop(), and must be different from value in
|
||||
* stopFromDemuxerThread()(which is false), so the initial value must be true
|
||||
*/
|
||||
|
||||
vc_ids
|
||||
#if QTAV_HAVE(DXVA)
|
||||
//<< VideoDecoderId_DXVA
|
||||
#endif //QTAV_HAVE(DXVA)
|
||||
#if QTAV_HAVE(VAAPI)
|
||||
//<< VideoDecoderId_VAAPI
|
||||
#endif //QTAV_HAVE(VAAPI)
|
||||
#if QTAV_HAVE(CEDARV)
|
||||
<< VideoDecoderId_Cedarv
|
||||
#endif //QTAV_HAVE(CEDARV)
|
||||
<< VideoDecoderId_FFmpeg;
|
||||
}
|
||||
AVPlayer::Private::~Private() {
|
||||
// TODO: scoped ptr
|
||||
if (ao) {
|
||||
delete ao;
|
||||
ao = 0;
|
||||
}
|
||||
if (adec) {
|
||||
delete adec;
|
||||
adec = 0;
|
||||
}
|
||||
if (vdec) {
|
||||
delete vdec;
|
||||
vdec = 0;
|
||||
}
|
||||
if (vos) {
|
||||
vos->clearOutputs();
|
||||
delete vos;
|
||||
vos = 0;
|
||||
}
|
||||
if (aos) {
|
||||
aos->clearOutputs();
|
||||
delete aos;
|
||||
aos = 0;
|
||||
}
|
||||
if (vcapture) {
|
||||
delete vcapture;
|
||||
vcapture = 0;
|
||||
}
|
||||
if (clock) {
|
||||
delete clock;
|
||||
clock = 0;
|
||||
}
|
||||
if (read_thread) {
|
||||
delete read_thread;
|
||||
read_thread = 0;
|
||||
}
|
||||
}
|
||||
#if (USE_AUDIO_DISABLE)
|
||||
void AVPlayer::Private::setAudioDisable(bool disable)
|
||||
{
|
||||
audio_disabled = disable;
|
||||
}
|
||||
#endif
|
||||
bool AVPlayer::Private::checkSourceChange()
|
||||
{
|
||||
if (current_source.type() == QVariant::String)
|
||||
{
|
||||
return demuxer.fileName() != current_source.toString();
|
||||
}
|
||||
if (current_source.canConvert<QIODevice*>())
|
||||
{
|
||||
return demuxer.ioDevice() != current_source.value<QIODevice*>();
|
||||
}
|
||||
return demuxer.mediaIO() != current_source.value<FAV::MediaIO*>();
|
||||
}
|
||||
|
||||
|
||||
void AVPlayer::Private::updateNotifyInterval()
|
||||
{
|
||||
if (notify_interval <= 0) {
|
||||
notify_interval = -Internal::computeNotifyPrecision(demuxer.duration(), demuxer.frameRate());
|
||||
}
|
||||
qDebug("notify_interval: %d", qAbs(notify_interval));
|
||||
}
|
||||
void AVPlayer::Private::applyFrameRate()
|
||||
{
|
||||
qreal vfps = force_fps;
|
||||
bool force = vfps > 0;
|
||||
const bool ao_null = ao && ao->backend().toLower() == QLatin1String("null");
|
||||
if (athread && !ao_null) { // TODO: no null ao check. null ao block internally
|
||||
force = vfps > 0 && !!vthread;
|
||||
} else if (!force) {
|
||||
force = !!vthread;
|
||||
vfps = statistics.video.frame_rate > 0 ? statistics.video.frame_rate : 25;
|
||||
// vfps<0: try to use pts (ExternalClock). if no pts (raw codec), try the default fps(VideoClock)
|
||||
vfps = -vfps;
|
||||
}
|
||||
qreal r = speed;
|
||||
if (force) {
|
||||
clock->setClockAuto(false);
|
||||
// vfps>0: force video fps to vfps. clock must be external
|
||||
clock->setClockType(vfps > 0 ? AVClock::VideoClock : AVClock::ExternalClock);
|
||||
vthread->setFrameRate(vfps);
|
||||
if (statistics.video.frame_rate > 0)
|
||||
r = qAbs(qreal(vfps))/statistics.video.frame_rate;
|
||||
} else {
|
||||
clock->setClockAuto(true);
|
||||
clock->setClockType(athread && ao->isOpen() ? AVClock::AudioClock : AVClock::ExternalClock);
|
||||
if (vthread)
|
||||
vthread->setFrameRate(0.0);
|
||||
if(ao)
|
||||
{
|
||||
ao->setSpeed(1);
|
||||
}
|
||||
clock->setSpeed(1);
|
||||
}
|
||||
if(ao)
|
||||
{
|
||||
ao->setSpeed(r);
|
||||
}
|
||||
clock->setSpeed(r);
|
||||
}
|
||||
|
||||
void AVPlayer::Private::initStatistics()
|
||||
{
|
||||
initBaseStatistics();
|
||||
initAudioStatistics(demuxer.audioStream());
|
||||
initVideoStatistics(demuxer.videoStream());
|
||||
#if !(DO_NOT_USE_SUBTITLE)
|
||||
initSubtitleStatistics(demuxer.subtitleStream());
|
||||
#endif
|
||||
}
|
||||
|
||||
//TODO: av_guess_frame_rate in latest ffmpeg
|
||||
void AVPlayer::Private::initBaseStatistics()
|
||||
{
|
||||
statistics.reset();
|
||||
statistics.url = current_source.type() == QVariant::String ? current_source.toString() : QString();
|
||||
statistics.start_time = QTime(0, 0, 0).addMSecs(int(demuxer.startTime()));
|
||||
statistics.duration = QTime(0, 0, 0).addMSecs((int)demuxer.duration());
|
||||
AVFormatContext *fmt_ctx = demuxer.formatContext();
|
||||
if (!fmt_ctx) {
|
||||
qWarning("demuxer.formatContext()==null. internal error");
|
||||
updateNotifyInterval();
|
||||
return;
|
||||
}
|
||||
statistics.bit_rate = fmt_ctx->bit_rate;
|
||||
statistics.format = QString().sprintf("%s - %s", fmt_ctx->iformat->name, fmt_ctx->iformat->long_name);
|
||||
//AV_TIME_BASE_Q: msvc error C2143
|
||||
//fmt_ctx->duration may be AV_NOPTS_VALUE. AVDemuxer.duration deals with this case
|
||||
AVDictionaryEntry *tag = NULL;
|
||||
while ((tag = av_dict_get(fmt_ctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
|
||||
statistics.metadata.insert(QString::fromUtf8(tag->key), QString::fromUtf8(tag->value));
|
||||
}
|
||||
updateNotifyInterval();
|
||||
}
|
||||
|
||||
void AVPlayer::Private::initCommonStatistics(int s, Statistics::Common *st, AVCodecContext *avctx)
|
||||
{
|
||||
AVFormatContext *fmt_ctx = demuxer.formatContext();
|
||||
if (!fmt_ctx) {
|
||||
qWarning("demuxer.formatContext()==null. internal error");
|
||||
return;
|
||||
}
|
||||
AVStream *stream = fmt_ctx->streams[s];
|
||||
qDebug("stream: %d, duration=%lld (%lld ms), time_base=%f", s, stream->duration, qint64(qreal(stream->duration)*av_q2d(stream->time_base)*1000.0), av_q2d(stream->time_base));
|
||||
// AVCodecContext.codec_name is deprecated. use avcodec_get_name. check null avctx->codec?
|
||||
st->codec = QLatin1String(avcodec_get_name(avctx->codec_id));
|
||||
st->codec_long = QLatin1String(get_codec_long_name(avctx->codec_id));
|
||||
st->total_time = QTime(0, 0, 0).addMSecs(stream->duration == (qint64)AV_NOPTS_VALUE ? 0 : int(qreal(stream->duration)*av_q2d(stream->time_base)*1000.0));
|
||||
st->start_time = QTime(0, 0, 0).addMSecs(stream->start_time == (qint64)AV_NOPTS_VALUE ? 0 : int(qreal(stream->start_time)*av_q2d(stream->time_base)*1000.0));
|
||||
qDebug("codec: %s(%s)", qPrintable(st->codec), qPrintable(st->codec_long));
|
||||
st->bit_rate = avctx->bit_rate; //fmt_ctx
|
||||
st->frames = stream->nb_frames;
|
||||
if (stream->avg_frame_rate.den && stream->avg_frame_rate.num)
|
||||
st->frame_rate = av_q2d(stream->avg_frame_rate);
|
||||
#if (defined FF_API_R_FRAME_RATE && FF_API_R_FRAME_RATE) //removed in libav10
|
||||
//FIXME: which 1 should we choose? avg_frame_rate may be nan, r_frame_rate may be wrong(guessed value)
|
||||
else if (stream->r_frame_rate.den && stream->r_frame_rate.num) {
|
||||
st->frame_rate = av_q2d(stream->r_frame_rate);
|
||||
qDebug("%d/%d", stream->r_frame_rate.num, stream->r_frame_rate.den);
|
||||
}
|
||||
#endif //FF_API_R_FRAME_RATE
|
||||
//http://ffmpeg.org/faq.html#AVStream_002er_005fframe_005frate-is-wrong_002c-it-is-much-larger-than-the-frame-rate_002e
|
||||
//http://libav-users.943685.n4.nabble.com/Libav-user-Reading-correct-frame-rate-fps-of-input-video-td4657666.html
|
||||
//qDebug("time: %f~%f, nb_frames=%lld", st->start_time, st->total_time, stream->nb_frames); //why crash on mac? av_q2d({0,0})?
|
||||
AVDictionaryEntry *tag = NULL;
|
||||
while ((tag = av_dict_get(stream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
|
||||
st->metadata.insert(QString::fromUtf8(tag->key), QString::fromUtf8(tag->value));
|
||||
}
|
||||
}
|
||||
|
||||
void AVPlayer::Private::initAudioStatistics(int s)
|
||||
{
|
||||
AVCodecContext *avctx = demuxer.audioCodecContext();
|
||||
statistics.audio = Statistics::Common();
|
||||
statistics.audio_only = Statistics::AudioOnly();
|
||||
if (!avctx)
|
||||
return;
|
||||
statistics.audio.available = s == demuxer.audioStream();
|
||||
initCommonStatistics(s, &statistics.audio, avctx);
|
||||
if (adec) {
|
||||
statistics.audio.decoder = adec->name();
|
||||
statistics.audio.decoder_detail = adec->description();
|
||||
}
|
||||
correct_audio_channels(avctx);
|
||||
statistics.audio_only.block_align = avctx->block_align;
|
||||
statistics.audio_only.channels = avctx->channels;
|
||||
char cl[128]; //
|
||||
// nb_channels -1: will use av_get_channel_layout_nb_channels
|
||||
av_get_channel_layout_string(cl, sizeof(cl), avctx->channels, avctx->channel_layout);
|
||||
statistics.audio_only.channel_layout = QLatin1String(cl);
|
||||
statistics.audio_only.sample_fmt = QLatin1String(av_get_sample_fmt_name(avctx->sample_fmt));
|
||||
statistics.audio_only.frame_size = avctx->frame_size;
|
||||
statistics.audio_only.sample_rate = avctx->sample_rate;
|
||||
}
|
||||
|
||||
void AVPlayer::Private::initVideoStatistics(int s)
|
||||
{
|
||||
AVCodecContext *avctx = demuxer.videoCodecContext();
|
||||
statistics.video = Statistics::Common();
|
||||
statistics.video_only = Statistics::VideoOnly();
|
||||
if (!avctx)
|
||||
{
|
||||
return;
|
||||
}
|
||||
statistics.video.available = s == demuxer.videoStream();
|
||||
initCommonStatistics(s, &statistics.video, avctx);
|
||||
if (vdec)
|
||||
{
|
||||
statistics.video.decoder = vdec->name();
|
||||
statistics.video.decoder_detail = vdec->description();
|
||||
}
|
||||
statistics.video_only.coded_height = avctx->coded_height;
|
||||
statistics.video_only.coded_width = avctx->coded_width;
|
||||
statistics.video_only.gop_size = avctx->gop_size;
|
||||
statistics.video_only.pix_fmt = QLatin1String(av_get_pix_fmt_name(avctx->pix_fmt));
|
||||
statistics.video_only.height = avctx->height;
|
||||
statistics.video_only.width = avctx->width;
|
||||
}
|
||||
// notify statistics change after audio/video thread is set
|
||||
bool AVPlayer::Private::setupAudioThread(AVPlayer *player)
|
||||
{
|
||||
#if (DISABLE_AUDIO_THREAD)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
#if (USE_AUDIO_DISABLE)
|
||||
if(audio_disabled == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
Internal::gPlayerPrivateTimer.start();
|
||||
#endif
|
||||
|
||||
AVDemuxer *ademuxer = &demuxer;
|
||||
if (!external_audio.isEmpty())
|
||||
{
|
||||
ademuxer = &audio_demuxer;
|
||||
}
|
||||
ademuxer->setStreamIndex(AVDemuxer::AudioStream, audio_track);
|
||||
// pause demuxer, clear queues, set demuxer stream, set decoder, set ao, resume
|
||||
// clear packets before stream changed
|
||||
if (athread)
|
||||
{
|
||||
athread->packetQueue()->clear();
|
||||
athread->setDecoder(0);
|
||||
athread->setOutput(0);
|
||||
}
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
|
||||
AVCodecContext *avctx = ademuxer->audioCodecContext();
|
||||
if (!avctx)
|
||||
{
|
||||
// TODO: close ao? //TODO: check pulseaudio perapp control if closed
|
||||
return false;
|
||||
}
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
|
||||
qDebug("has audio");
|
||||
// TODO: no delete, just reset avctx and reopen
|
||||
if (adec) {
|
||||
adec->disconnect();
|
||||
delete adec;
|
||||
adec = 0;
|
||||
}
|
||||
adec = AudioDecoder::create();
|
||||
if (!adec)
|
||||
{
|
||||
qWarning("failed to create audio decoder");
|
||||
return false;
|
||||
}
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
|
||||
QObject::connect(adec, SIGNAL(error(FAV::AVError)), player, SIGNAL(error(FAV::AVError)));
|
||||
adec->setCodecContext(avctx);
|
||||
adec->setOptions(ac_opt);
|
||||
if (!adec->open()) {
|
||||
AVError e(AVError::AudioCodecNotFound);
|
||||
qWarning() << e.string();
|
||||
emit player->error(e);
|
||||
return false;
|
||||
}
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
|
||||
correct_audio_channels(avctx);
|
||||
AudioFormat af;
|
||||
af.setSampleRate(avctx->sample_rate);
|
||||
af.setSampleFormatFFmpeg(avctx->sample_fmt);
|
||||
af.setChannelLayoutFFmpeg(avctx->channel_layout);
|
||||
if (!af.isValid()) {
|
||||
qWarning("invalid audio format. audio stream will be disabled");
|
||||
return false;
|
||||
}
|
||||
//af.setChannels(avctx->channels);
|
||||
// always reopen to ensure internal buffer queue inside audio backend(openal) is clear. also make it possible to change backend when replay.
|
||||
//if (ao->audioFormat() != af) {
|
||||
//qDebug("ao audio format is changed. reopen ao");
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
ao->setAudioFormat(af); /// set before close to workaround OpenAL context lost
|
||||
ao->close();
|
||||
#if !(OFF_OTHER_DEBUG)
|
||||
qDebug() << "AudioOutput format: " << ao->audioFormat() << "; requested: " << ao->requestedFormat();
|
||||
#endif
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
// PORT AUDIO 1초 걸림...
|
||||
if (!ao->open()) {
|
||||
return false;
|
||||
}
|
||||
//}
|
||||
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
adec->resampler()->setOutAudioFormat(ao->audioFormat());
|
||||
// no need to set resampler if AudioFrame is used
|
||||
#if !USE_AUDIO_FRAME
|
||||
adec->resampler()->inAudioFormat().setSampleFormatFFmpeg(avctx->sample_fmt);
|
||||
adec->resampler()->inAudioFormat().setSampleRate(avctx->sample_rate);
|
||||
adec->resampler()->inAudioFormat().setChannels(avctx->channels);
|
||||
adec->resampler()->inAudioFormat().setChannelLayoutFFmpeg(avctx->channel_layout);
|
||||
#endif
|
||||
if (audio_track < 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << "ELAPSED:" << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
if (!athread)
|
||||
{
|
||||
qDebug("new audio thread");
|
||||
athread = new AudioThread(player);
|
||||
athread->playerID = player->playerID;
|
||||
athread->setClock(clock);
|
||||
athread->setStatistics(&statistics);
|
||||
athread->setOutputSet(aos);
|
||||
qDebug("demux thread setAudioThread");
|
||||
read_thread->setAudioThread(athread);
|
||||
//reconnect if disconnected
|
||||
QList<Filter*> filters = FilterManager::instance().audioFilters(player);
|
||||
//TODO: isEmpty()==false but size() == 0 in debug mode, it's a Qt bug? we can not just foreach without check empty in debug mode
|
||||
if (filters.size() > 0) {
|
||||
foreach (Filter *filter, filters) {
|
||||
athread->installFilter(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
#if (FIXED_FPS_DURATION)
|
||||
athread->stopPosition = demuxer.durationFixed;
|
||||
#if (PREVENT_OVER_DURATION_RENDER)
|
||||
athread->duration = ((qreal)demuxer.originalDuraiont) / 1000000.0;
|
||||
//qInfo() << athread->playerID << athread->duration << demuxer.originalDuraiont << __FUNCTION__;
|
||||
#endif // PREVENT_OVER_DURATION_RENDER
|
||||
#endif // PREVENT_OVER_DURATION_RENDER
|
||||
|
||||
athread->setDecoder(adec);
|
||||
setAVOutput(ao, ao, athread);
|
||||
updateBufferValue(athread->packetQueue());
|
||||
initAudioStatistics(ademuxer->audioStream());
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << Internal::gPlayerPrivateTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantList AVPlayer::Private::getTracksInfo(AVDemuxer *demuxer, AVDemuxer::StreamType st)
|
||||
{
|
||||
QVariantList info;
|
||||
if (!demuxer)
|
||||
return info;
|
||||
QList<int> streams;
|
||||
switch (st) {
|
||||
case AVDemuxer::AudioStream:
|
||||
streams = demuxer->audioStreams();
|
||||
break;
|
||||
#if !(DO_NOT_USE_SUBTITLE)
|
||||
case AVDemuxer::SubtitleStream:
|
||||
streams = demuxer->subtitleStreams();
|
||||
break;
|
||||
#endif
|
||||
case AVDemuxer::VideoStream:
|
||||
streams = demuxer->videoStreams();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (streams.isEmpty())
|
||||
return info;
|
||||
foreach (int s, streams) {
|
||||
QVariantMap t;
|
||||
t[QStringLiteral("id")] = info.size();
|
||||
t[QStringLiteral("file")] = demuxer->fileName();
|
||||
AVStream *stream = demuxer->formatContext()->streams[s];
|
||||
AVCodecContext *ctx = stream->codec;
|
||||
if (ctx) {
|
||||
t[QStringLiteral("codec")] = QByteArray(avcodec_descriptor_get(ctx->codec_id)->name);
|
||||
if (ctx->extradata)
|
||||
t[QStringLiteral("extra")] = QByteArray((const char*)ctx->extradata, ctx->extradata_size);
|
||||
}
|
||||
AVDictionaryEntry *tag = av_dict_get(stream->metadata, "language", NULL, 0);
|
||||
if (!tag)
|
||||
tag = av_dict_get(stream->metadata, "lang", NULL, 0);
|
||||
if (tag) {
|
||||
t[QStringLiteral("language")] = QString::fromUtf8(tag->value);
|
||||
}
|
||||
tag = av_dict_get(stream->metadata, "title", NULL, 0);
|
||||
if (tag) {
|
||||
t[QStringLiteral("title")] = QString::fromUtf8(tag->value);
|
||||
}
|
||||
info.push_back(t);
|
||||
}
|
||||
//QVariantMap t;
|
||||
//t[QStringLiteral("id")] = -1;
|
||||
//info.prepend(t);
|
||||
return info;
|
||||
}
|
||||
#if !(DO_NOT_USE_SUBTITLE)
|
||||
bool AVPlayer::Private::applySubtitleStream(int n, AVPlayer *player)
|
||||
{
|
||||
if (!demuxer.setStreamIndex(AVDemuxer::SubtitleStream, n))
|
||||
return false;
|
||||
AVCodecContext *ctx = demuxer.subtitleCodecContext();
|
||||
if (!ctx)
|
||||
return false;
|
||||
// FIXME: AVCodecDescriptor.name and AVCodec.name are different!
|
||||
const AVCodecDescriptor *codec_desc = avcodec_descriptor_get(ctx->codec_id);
|
||||
QByteArray codec(codec_desc->name);
|
||||
if (ctx->extradata)
|
||||
Q_EMIT player->internalSubtitleHeaderRead(codec, QByteArray((const char*)ctx->extradata, ctx->extradata_size));
|
||||
else
|
||||
Q_EMIT player->internalSubtitleHeaderRead(codec, QByteArray());
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
bool AVPlayer::Private::tryApplyDecoderPriority(AVPlayer *player)
|
||||
{
|
||||
// TODO: add an option to apply the new decoder even if not available
|
||||
qint64 pos = player->position();
|
||||
VideoDecoder *vd = NULL;
|
||||
AVCodecContext *avctx = demuxer.videoCodecContext();
|
||||
foreach(VideoDecoderId vid, vc_ids)
|
||||
{
|
||||
qDebug("**********trying video decoder: %s...", VideoDecoder::name(vid));
|
||||
vd = VideoDecoder::create(vid);
|
||||
if (!vd)
|
||||
continue;
|
||||
vd->setCodecContext(avctx); // It's fine because AVDecoder copy the avctx properties
|
||||
vd->setOptions(vc_opt);
|
||||
if (vd->open()) {
|
||||
qDebug("**************Video decoder found:%p", vd);
|
||||
break;
|
||||
}
|
||||
delete vd;
|
||||
vd = 0;
|
||||
}
|
||||
qDebug("**************set new decoder:%p -> %p", vdec, vd);
|
||||
if (!vd)
|
||||
{
|
||||
Q_EMIT player->error(AVError(AVError::VideoCodecNotFound));
|
||||
return false;
|
||||
}
|
||||
if (vd->id() == vdec->id()
|
||||
&& vd->options() == vdec->options())
|
||||
{
|
||||
qDebug("Video decoder does not change");
|
||||
delete vd;
|
||||
return true;
|
||||
}
|
||||
vthread->packetQueue()->clear();
|
||||
vthread->setDecoder(vd);
|
||||
// MUST delete decoder after video thread set the decoder to ensure the deleted vdec will not be used in vthread!
|
||||
if (vdec)
|
||||
{
|
||||
delete vdec;
|
||||
}
|
||||
vdec = vd;
|
||||
QObject::connect(vdec, SIGNAL(error(FAV::AVError)), player, SIGNAL(error(FAV::AVError)));
|
||||
initVideoStatistics(demuxer.videoStream());
|
||||
// If no seek, drop packets until a key frame packet is found. But we may drop too many packets, and also a/v sync is a problem.
|
||||
player->setPosition(pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if (FIX_PLAYER_END_CLIP)
|
||||
void AVPlayer::Private::exitThreads()
|
||||
{
|
||||
if(vthread) {
|
||||
vthread->endStopBy();
|
||||
}
|
||||
if(athread) {
|
||||
athread->endStopBy();
|
||||
}
|
||||
if(read_thread) {
|
||||
read_thread->stop();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool AVPlayer::Private::setupVideoThread(AVPlayer *player)
|
||||
{
|
||||
demuxer.setStreamIndex(AVDemuxer::VideoStream, video_track);
|
||||
// pause demuxer, clear queues, set demuxer stream, set decoder, set ao, resume
|
||||
// clear packets before stream changed
|
||||
if (vthread)
|
||||
{
|
||||
vthread->packetQueue()->clear();
|
||||
vthread->setDecoder(0);
|
||||
#if (WAIT_CHANNEL_CHANGE)
|
||||
QThread::msleep(500);
|
||||
#endif
|
||||
//qInfo() << "3.1.1 decoder deleted";
|
||||
}
|
||||
AVCodecContext *avctx = demuxer.videoCodecContext();
|
||||
if (!avctx) {
|
||||
return false;
|
||||
}
|
||||
if (vdec) {
|
||||
vdec->disconnect();
|
||||
delete vdec;
|
||||
vdec = 0;
|
||||
}
|
||||
foreach(VideoDecoderId vid, vc_ids) {
|
||||
qDebug("**********trying video decoder: %s...", VideoDecoder::name(vid));
|
||||
VideoDecoder *vd = VideoDecoder::create(vid);
|
||||
if (!vd) {
|
||||
continue;
|
||||
}
|
||||
//vd->isAvailable() //TODO: the value is wrong now
|
||||
vd->setCodecContext(avctx);
|
||||
vd->setOptions(vc_opt);
|
||||
if (vd->open()) {
|
||||
vdec = vd;
|
||||
// qInfo() << "3.1.2 decoder created";
|
||||
|
||||
qDebug("**************Video decoder found:%p", vdec);
|
||||
break;
|
||||
}
|
||||
delete vd;
|
||||
}
|
||||
if (!vdec) {
|
||||
// DO NOT emit error signals in VideoDecoder::open(). 1 signal is enough
|
||||
AVError e(AVError::VideoCodecNotFound);
|
||||
qWarning() << e.string();
|
||||
emit player->error(e);
|
||||
return false;
|
||||
}
|
||||
QObject::connect(vdec, SIGNAL(error(FAV::AVError)), player, SIGNAL(error(FAV::AVError)));
|
||||
|
||||
if (!vthread)
|
||||
{
|
||||
vthread = new VideoThread(player);
|
||||
vthread->playerID = player->playerID;
|
||||
#if (FIXED_FPS_DURATION)
|
||||
vthread->stopPosition = demuxer.durationFixed;
|
||||
//vthread->setPriority(QThread::TimeCriticalPriority);
|
||||
#endif
|
||||
vthread->setClock(clock);
|
||||
vthread->setStatistics(&statistics);
|
||||
vthread->setVideoCapture(vcapture);
|
||||
vthread->setOutputSet(vos);
|
||||
read_thread->setVideoThread(vthread);
|
||||
#if (FIRST_FRAME_NOTIFY)
|
||||
QObject::connect(vthread,SIGNAL(firstFrameNotify(qreal)),player,SLOT(onFirstFrameNotify(qreal)));
|
||||
#endif
|
||||
|
||||
#if (FIXED_FPS_DURATION)
|
||||
QObject::connect(vthread,SIGNAL(frameEnded()),player,SLOT(onFrameEnded()),Qt::DirectConnection);
|
||||
#endif
|
||||
|
||||
QList<Filter*> filters = FilterManager::instance().videoFilters(player);
|
||||
if (filters.size() > 0) {
|
||||
foreach (Filter *filter, filters) {
|
||||
vthread->installFilter(filter);
|
||||
}
|
||||
}
|
||||
QObject::connect(vthread, SIGNAL(finished()), player, SLOT(tryClearVideoRenderers()), Qt::DirectConnection);
|
||||
}
|
||||
vthread->setDecoder(vdec);
|
||||
|
||||
// 항상실행
|
||||
#if (FIXED_FPS_DURATION)
|
||||
vthread->stopPosition = demuxer.durationFixed;
|
||||
#if (PREVENT_OVER_DURATION_RENDER)
|
||||
vthread->duration = ((qreal)demuxer.originalDuraiont) / 1000000.0;
|
||||
#endif // PREVENT_OVER_DURATION_RENDER
|
||||
#endif // PREVENT_OVER_DURATION_RENDER
|
||||
|
||||
#if (PLAY_SYNC_FIX2)
|
||||
// SEEK 문제없도록 LASTSEEK PTS 추가
|
||||
//qInfo() << "EOF_PACKET_PTS" << demuxer.durationFixed << (demuxer.frameDuration * 1000) << __FUNCTION__;
|
||||
vthread->EOFPacketPTS = ((double)(demuxer.durationFixed - (demuxer.frameDuration * 1000)) / 1000000);
|
||||
|
||||
// if(demuxer.frameCount > 100) {
|
||||
// vthread->EOFPacketPTS -= 1;
|
||||
// }
|
||||
#endif //
|
||||
vthread->setBrightness(brightness);
|
||||
vthread->setContrast(contrast);
|
||||
vthread->setSaturation(saturation);
|
||||
updateBufferValue(vthread->packetQueue());
|
||||
initVideoStatistics(demuxer.videoStream());
|
||||
// qInfo() << "3.2 setupVideoThread done";
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: set to a lower value when buffering
|
||||
void AVPlayer::Private::updateBufferValue(PacketBuffer* buf)
|
||||
{
|
||||
const bool video = vthread && buf == vthread->packetQueue();
|
||||
const qreal fps = qMax<qreal>(24.0, statistics.video.frame_rate);
|
||||
qint64 bv = 0.5*fps;
|
||||
if (!video)
|
||||
{
|
||||
// if has video, then audio buffer should not block the video buffer (bufferValue == 1, modified in AVDemuxThread)
|
||||
bv = statistics.audio.frame_rate > 0 && statistics.audio.frame_rate < 60 ?
|
||||
statistics.audio.frame_rate : 3LL;
|
||||
}
|
||||
if (buffer_mode == BufferTime)
|
||||
{
|
||||
bv = 600LL; //ms
|
||||
}
|
||||
else if (buffer_mode == BufferBytes)
|
||||
{
|
||||
bv = 1024LL;
|
||||
}
|
||||
// no block for music with cover
|
||||
if (video)
|
||||
{
|
||||
if (demuxer.hasAttacedPicture() || (statistics.video.frames > 0 && statistics.video.frames < bv))
|
||||
{
|
||||
bv = qMax<qint64>(1LL, statistics.video.frames);
|
||||
}
|
||||
}
|
||||
buf->setBufferMode(buffer_mode);
|
||||
buf->setBufferValue(buffer_value < 0LL ? bv : buffer_value);
|
||||
}
|
||||
|
||||
void AVPlayer::Private::updateBufferValue()
|
||||
{
|
||||
if (athread)
|
||||
{
|
||||
updateBufferValue(athread->packetQueue());
|
||||
}
|
||||
if (vthread)
|
||||
{
|
||||
updateBufferValue(vthread->packetQueue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} //namespace FAV
|
||||
178
project/fm_viewer/fav/AVPlayerPrivate.h
Normal file
178
project/fm_viewer/fav/AVPlayerPrivate.h
Normal file
@@ -0,0 +1,178 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2014-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AVPLAYER_PRIVATE_H
|
||||
#define QTAV_AVPLAYER_PRIVATE_H
|
||||
|
||||
#include "AVDemuxer.h"
|
||||
#include "AVPlayer.h"
|
||||
#include "AudioThread.h"
|
||||
#include "VideoThread.h"
|
||||
#include "AVDemuxThread.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
static const qint64 kInvalidPosition = std::numeric_limits<qint64>::max();
|
||||
class AVPlayer::Private
|
||||
{
|
||||
public:
|
||||
Private();
|
||||
~Private();
|
||||
|
||||
bool checkSourceChange();
|
||||
void updateNotifyInterval();
|
||||
void applyFrameRate();
|
||||
void initStatistics();
|
||||
void initBaseStatistics();
|
||||
void initCommonStatistics(int s, Statistics::Common* st, AVCodecContext* avctx);
|
||||
void initAudioStatistics(int s);
|
||||
void initVideoStatistics(int s);
|
||||
#if (USE_AUDIO_DISABLE)
|
||||
void setAudioDisable(bool disable);
|
||||
#endif
|
||||
|
||||
#if (FIX_PLAYER_END_CLIP)
|
||||
void exitThreads();
|
||||
#endif
|
||||
|
||||
#if !(DO_NOT_USE_SUBTITLE)
|
||||
void initSubtitleStatistics(int s);
|
||||
#endif
|
||||
QVariantList getTracksInfo(AVDemuxer* demuxer, AVDemuxer::StreamType st);
|
||||
|
||||
#if !(DO_NOT_USE_SUBTITLE)
|
||||
bool applySubtitleStream(int n, AVPlayer *player);
|
||||
#endif
|
||||
bool setupAudioThread(AVPlayer *player);
|
||||
bool setupVideoThread(AVPlayer *player);
|
||||
bool tryApplyDecoderPriority(AVPlayer *player);
|
||||
// TODO: what if buffer mode changed during playback?
|
||||
void updateBufferValue(PacketBuffer *buf);
|
||||
void updateBufferValue();
|
||||
//TODO: addAVOutput()
|
||||
template<class Out>
|
||||
void setAVOutput(Out *&pOut, Out *pNew, AVThread *thread) {
|
||||
Out *old = pOut;
|
||||
bool delete_old = false;
|
||||
if (pOut == pNew) {
|
||||
qDebug("output not changed: %p", pOut);
|
||||
if (thread && thread->output() == pNew) {//avthread already set that output
|
||||
qDebug("avthread already set that output");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
pOut = pNew;
|
||||
delete_old = true;
|
||||
}
|
||||
if (!thread) {
|
||||
qDebug("avthread not ready. can not set output.");
|
||||
//no avthread, we can delete it safely
|
||||
//AVOutput must be allocated in heap. Just like QObject's children.
|
||||
if (delete_old) {
|
||||
delete old;
|
||||
old = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
//FIXME: what if isPaused()==false but pause(true) in another thread?
|
||||
//bool need_lock = isPlaying() && !thread->isPaused();
|
||||
//if (need_lock)
|
||||
// thread->lock();
|
||||
qDebug("set AVThread output");
|
||||
thread->setOutput(pOut);
|
||||
if (pOut) {
|
||||
pOut->setStatistics(&statistics);
|
||||
//if (need_lock)
|
||||
// thread->unlock(); //??why here?
|
||||
}
|
||||
//now the old avoutput is not used by avthread, we can delete it safely
|
||||
//AVOutput must be allocated in heap. Just like QObject's children.
|
||||
if (delete_old) {
|
||||
delete old;
|
||||
old = 0;
|
||||
}
|
||||
}
|
||||
#if (USE_AUDIO_DISABLE)
|
||||
bool audio_disabled;
|
||||
#endif
|
||||
bool auto_load;
|
||||
bool async_load;
|
||||
// can be QString, QIODevice*
|
||||
QVariant current_source, pendding_source;
|
||||
bool loaded; // for current source
|
||||
bool relative_time_mode;
|
||||
qint64 media_start_pts; // read from media stream
|
||||
qint64 media_end;
|
||||
bool reset_state;
|
||||
qint64 start_position, stop_position;
|
||||
qint64 start_position_norm, stop_position_norm; // real position
|
||||
int repeat_max, repeat_current;
|
||||
int timer_id; //notify position change and check AB repeat range. active when playing
|
||||
|
||||
int audio_track, video_track;
|
||||
|
||||
#if !(DO_NOT_USE_SUBTITLE)
|
||||
int subtitle_track;
|
||||
QVariantList subtitle_tracks;
|
||||
#endif
|
||||
QString external_audio;
|
||||
AVDemuxer audio_demuxer;
|
||||
QVariantList audio_tracks, external_audio_tracks;
|
||||
BufferMode buffer_mode;
|
||||
qint64 buffer_value;
|
||||
//the following things are required and must be set not null
|
||||
AVDemuxer demuxer;
|
||||
AVDemuxThread *read_thread;
|
||||
AVClock *clock;
|
||||
VideoRenderer *vo; //list? // TODO: remove
|
||||
AudioOutput *ao; // TODO: remove
|
||||
AudioDecoder *adec;
|
||||
VideoDecoder *vdec;
|
||||
AudioThread *athread;
|
||||
VideoThread *vthread;
|
||||
|
||||
VideoCapture *vcapture;
|
||||
Statistics statistics;
|
||||
qreal speed;
|
||||
OutputSet *vos, *aos;
|
||||
QVector<VideoDecoderId> vc_ids;
|
||||
int brightness, contrast, saturation;
|
||||
|
||||
QVariantHash ac_opt, vc_opt;
|
||||
|
||||
bool seeking;
|
||||
SeekType seek_type;
|
||||
qint64 interrupt_timeout;
|
||||
|
||||
qreal force_fps;
|
||||
// timerEvent interval in ms. can divide 1000. depends on media duration, fps etc.
|
||||
// <0: auto compute internally, |notify_interval| is the real interval
|
||||
int notify_interval;
|
||||
MediaStatus status; // status changes can be from demuxer or demux thread
|
||||
AVPlayer::State state;
|
||||
MediaEndAction end_action;
|
||||
QMutex load_mutex;
|
||||
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QTAV_AVPLAYER_PRIVATE_H
|
||||
452
project/fm_viewer/fav/AVThread.cpp
Normal file
452
project/fm_viewer/fav/AVThread.cpp
Normal file
@@ -0,0 +1,452 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AVThread.h"
|
||||
#include <limits>
|
||||
#include "AVThread_p.h"
|
||||
#include "AVClock.h"
|
||||
#include "AVDecoder.h"
|
||||
#include "AVOutput.h"
|
||||
#include "Filter.h"
|
||||
#include "OutputSet.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
QVariantHash AVThreadPrivate::dec_opt_framedrop;
|
||||
QVariantHash AVThreadPrivate::dec_opt_normal;
|
||||
|
||||
AVThreadPrivate::~AVThreadPrivate() {
|
||||
|
||||
stop = true;
|
||||
if (!paused) {
|
||||
qDebug("~AVThreadPrivate wake up paused thread");
|
||||
paused = false;
|
||||
next_pause = false;
|
||||
cond.wakeAll();
|
||||
}
|
||||
packets.setBlocking(true); //???
|
||||
packets.clear();
|
||||
QList<Filter*>::iterator it = filters.begin();
|
||||
while (it != filters.end()) {
|
||||
if ((*it)->isOwnedByTarget() && !(*it)->parent())
|
||||
delete *it;
|
||||
++it;
|
||||
}
|
||||
filters.clear();
|
||||
}
|
||||
|
||||
AVThread::AVThread(QObject *parent) :
|
||||
QThread(parent)
|
||||
{
|
||||
playerID = -1;
|
||||
#if (USE_DURATION_IN_THREAD)
|
||||
durationSec = 0;
|
||||
#endif
|
||||
#if (PREVENT_OVER_DURATION_RENDER)
|
||||
duration = 0.0;
|
||||
#endif
|
||||
|
||||
connect(this, SIGNAL(started()), SLOT(onStarted()), Qt::DirectConnection);
|
||||
connect(this, SIGNAL(finished()), SLOT(onFinished()), Qt::DirectConnection);
|
||||
}
|
||||
|
||||
AVThread::AVThread(AVThreadPrivate &d, QObject *parent)
|
||||
:QThread(parent),DPTR_INIT(&d)
|
||||
{
|
||||
#if (USE_DURATION_IN_THREAD)
|
||||
durationSec = 0;
|
||||
#endif
|
||||
|
||||
#if (FIX_PLAYER_END_CLIP)
|
||||
endStop = false;
|
||||
#endif
|
||||
connect(this, SIGNAL(started()), SLOT(onStarted()), Qt::DirectConnection);
|
||||
connect(this, SIGNAL(finished()), SLOT(onFinished()), Qt::DirectConnection);
|
||||
}
|
||||
|
||||
AVThread::~AVThread()
|
||||
{
|
||||
//d_ptr destroyed automatically
|
||||
}
|
||||
|
||||
bool AVThread::isPaused() const
|
||||
{
|
||||
DPTR_D(const AVThread);
|
||||
//if d.next_pause is true, the thread will pause soon, may happens before you can handle the result
|
||||
return d.paused || d.next_pause;
|
||||
}
|
||||
|
||||
bool AVThread::installFilter(Filter *filter, int index, bool lock)
|
||||
{
|
||||
DPTR_D(AVThread);
|
||||
int p = index;
|
||||
if (p < 0)
|
||||
p += d.filters.size();
|
||||
if (p < 0)
|
||||
p = 0;
|
||||
if (p > d.filters.size())
|
||||
p = d.filters.size();
|
||||
const int old = d.filters.indexOf(filter);
|
||||
// already installed at desired position
|
||||
if (p == old)
|
||||
return true;
|
||||
if (lock) {
|
||||
QMutexLocker locker(&d.mutex);
|
||||
if (p >= 0)
|
||||
d.filters.removeAt(p);
|
||||
d.filters.insert(p, filter);
|
||||
} else {
|
||||
if (p >= 0)
|
||||
d.filters.removeAt(p);
|
||||
d.filters.insert(p, filter);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AVThread::uninstallFilter(Filter *filter, bool lock)
|
||||
{
|
||||
DPTR_D(AVThread);
|
||||
if (lock) {
|
||||
QMutexLocker locker(&d.mutex);
|
||||
return d.filters.removeOne(filter);
|
||||
}
|
||||
return d.filters.removeOne(filter);
|
||||
}
|
||||
|
||||
const QList<Filter*>& AVThread::filters() const
|
||||
{
|
||||
return d_func().filters;
|
||||
}
|
||||
|
||||
void AVThread::scheduleTask(QRunnable *task)
|
||||
{
|
||||
d_func().tasks.put(task);
|
||||
}
|
||||
|
||||
void AVThread::requestSeek()
|
||||
{
|
||||
class SeekPTS : public QRunnable {
|
||||
AVThread *self;
|
||||
public:
|
||||
SeekPTS(AVThread* thread) : self(thread) {}
|
||||
void run() Q_DECL_OVERRIDE {
|
||||
self->d_func().seek_requested = true;
|
||||
}
|
||||
};
|
||||
scheduleTask(new SeekPTS(this));
|
||||
}
|
||||
|
||||
void AVThread::scheduleFrameDrop(bool value)
|
||||
{
|
||||
class FrameDropTask : public QRunnable {
|
||||
AVDecoder *decoder;
|
||||
bool drop;
|
||||
public:
|
||||
FrameDropTask(AVDecoder *dec, bool value) : decoder(dec), drop(value) {}
|
||||
void run() Q_DECL_OVERRIDE {
|
||||
if (!decoder)
|
||||
return;
|
||||
if (drop)
|
||||
decoder->setOptions(AVThreadPrivate::dec_opt_framedrop);
|
||||
else
|
||||
decoder->setOptions(AVThreadPrivate::dec_opt_normal);
|
||||
}
|
||||
};
|
||||
scheduleTask(new FrameDropTask(decoder(), value));
|
||||
}
|
||||
|
||||
qreal AVThread::previousHistoryPts() const
|
||||
{
|
||||
DPTR_D(const AVThread);
|
||||
if (d.pts_history.empty()) {
|
||||
qDebug("pts history is EMPTY");
|
||||
return 0;
|
||||
}
|
||||
if (d.pts_history.size() == 1)
|
||||
return -d.pts_history.back();
|
||||
const qreal current_pts = d.pts_history.back();
|
||||
for (int i = d.pts_history.size() - 2; i > 0; --i) {
|
||||
if (d.pts_history.at(i) < current_pts)
|
||||
return d.pts_history.at(i);
|
||||
}
|
||||
return -d.pts_history.front();
|
||||
}
|
||||
|
||||
qreal AVThread::decodeFrameRate() const
|
||||
{
|
||||
DPTR_D(const AVThread);
|
||||
if (d.pts_history.size() <= 1)
|
||||
return 0;
|
||||
const qreal dt = d.pts_history.back() - d.pts_history.front();
|
||||
if (dt <= 0)
|
||||
return 0;
|
||||
return d.pts_history.size()/dt;
|
||||
}
|
||||
|
||||
void AVThread::setDropFrameOnSeek(bool value)
|
||||
{
|
||||
d_func().drop_frame_seek = value;
|
||||
}
|
||||
|
||||
// TODO: shall we close decoder here?
|
||||
void AVThread::stop()
|
||||
{
|
||||
DPTR_D(AVThread);
|
||||
d.stop = true; //stop as soon as possible
|
||||
QMutexLocker locker(&d.mutex);
|
||||
Q_UNUSED(locker);
|
||||
d.packets.setBlocking(false); //stop blocking take()
|
||||
d.packets.clear();
|
||||
pause(false);
|
||||
//terminate();
|
||||
}
|
||||
|
||||
// 짧은 동영상 후반부 SEEK 처리시 PAUSED 가 풀림 ->
|
||||
// UI 에서 SLIDER MOUSE PRESS 시 PAUSED 상태를 확인할때
|
||||
// 이전에 이미 SEEK 가 처리되고 있으면 PAUSED 가 아닌 PLAY 상태로 인식되어
|
||||
// SLIDER RELESE 후 다시 PAUSE(false) 처리한 문제였음
|
||||
void AVThread::pause(bool p)
|
||||
{
|
||||
DPTR_D(AVThread);
|
||||
if (d.paused == p)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 왜 동일한 PAUSE 가 두번씩 호출되는지 확인
|
||||
// RMPlayer::onSliderPress 에서 강제 pause 1차 요청함
|
||||
PLAYER_DEBUG_V2("!!!!PAUSE:",50,p,this);
|
||||
// if(playerID == 0 && p == true) {
|
||||
// int i = 0;
|
||||
// qInfo() << i << __FUNCTION__;
|
||||
// }
|
||||
|
||||
d.paused = p;
|
||||
if (!d.paused) {
|
||||
qDebug("wake up paused thread");
|
||||
d.next_pause = false;
|
||||
d.cond.wakeAll();
|
||||
}
|
||||
}
|
||||
|
||||
void AVThread::nextAndPause()
|
||||
{
|
||||
DPTR_D(AVThread);
|
||||
d.next_pause = true;
|
||||
d.paused = true;
|
||||
d.cond.wakeAll();
|
||||
}
|
||||
|
||||
void AVThread::lock()
|
||||
{
|
||||
d_func().mutex.lock();
|
||||
}
|
||||
|
||||
void AVThread::unlock()
|
||||
{
|
||||
d_func().mutex.unlock();
|
||||
}
|
||||
|
||||
void AVThread::setClock(AVClock *clock)
|
||||
{
|
||||
d_func().clock = clock;
|
||||
}
|
||||
|
||||
AVClock* AVThread::clock() const
|
||||
{
|
||||
return d_func().clock;
|
||||
}
|
||||
|
||||
PacketBuffer* AVThread::packetQueue() const
|
||||
{
|
||||
return const_cast<PacketBuffer*>(&d_func().packets);
|
||||
}
|
||||
|
||||
void AVThread::setDecoder(AVDecoder *decoder)
|
||||
{
|
||||
DPTR_D(AVThread);
|
||||
QMutexLocker lock(&d.mutex);
|
||||
Q_UNUSED(lock);
|
||||
d.dec = decoder;
|
||||
}
|
||||
|
||||
AVDecoder* AVThread::decoder() const
|
||||
{
|
||||
return d_func().dec;
|
||||
}
|
||||
|
||||
void AVThread::setOutput(AVOutput *out)
|
||||
{
|
||||
DPTR_D(AVThread);
|
||||
QMutexLocker lock(&d.mutex);
|
||||
Q_UNUSED(lock);
|
||||
if (!d.outputSet)
|
||||
return;
|
||||
if (!out) {
|
||||
d.outputSet->clearOutputs();
|
||||
return;
|
||||
}
|
||||
d.outputSet->addOutput(out);
|
||||
}
|
||||
|
||||
AVOutput* AVThread::output() const
|
||||
{
|
||||
DPTR_D(const AVThread);
|
||||
if (!d.outputSet || d.outputSet->outputs().isEmpty())
|
||||
return 0;
|
||||
return d.outputSet->outputs().first();
|
||||
}
|
||||
|
||||
// TODO: remove?
|
||||
void AVThread::setOutputSet(OutputSet *set)
|
||||
{
|
||||
d_func().outputSet = set;
|
||||
}
|
||||
|
||||
OutputSet* AVThread::outputSet() const
|
||||
{
|
||||
return d_func().outputSet;
|
||||
}
|
||||
|
||||
void AVThread::onStarted()
|
||||
{
|
||||
d_func().sem.release();
|
||||
}
|
||||
|
||||
void AVThread::onFinished()
|
||||
{
|
||||
if (d_func().sem.available() > 0)
|
||||
d_func().sem.acquire(d_func().sem.available());
|
||||
}
|
||||
|
||||
void AVThread::resetState()
|
||||
{
|
||||
DPTR_D(AVThread);
|
||||
pause(false);
|
||||
d.pts_history = ring<qreal>(d.pts_history.capacity());
|
||||
d.tasks.clear();
|
||||
d.render_pts0 = -1;
|
||||
d.stop = false;
|
||||
d.packets.setBlocking(true);
|
||||
d.packets.clear();
|
||||
d.wait_err = 0;
|
||||
d.wait_timer.invalidate();
|
||||
}
|
||||
|
||||
bool AVThread::tryPause(unsigned long timeout)
|
||||
{
|
||||
DPTR_D(AVThread);
|
||||
if (!isPaused())
|
||||
return false;
|
||||
QMutexLocker lock(&d.mutex);
|
||||
Q_UNUSED(lock);
|
||||
return d.cond.wait(&d.mutex, timeout);
|
||||
qDebug("paused thread waked up!!!");
|
||||
return true;
|
||||
}
|
||||
#if (FIX_PLAYER_END_CLIP)
|
||||
void AVThread::endStopBy()
|
||||
{
|
||||
DPTR_D(AVThread);
|
||||
QMutexLocker lock(&d.mutex);
|
||||
endStop = true;
|
||||
}
|
||||
#endif
|
||||
bool AVThread::processNextTask()
|
||||
{
|
||||
DPTR_D(AVThread);
|
||||
if (d.tasks.isEmpty())
|
||||
return true;
|
||||
QRunnable *task = d.tasks.take();
|
||||
task->run();
|
||||
if (task->autoDelete()) {
|
||||
delete task;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AVThread::setStatistics(Statistics *statistics)
|
||||
{
|
||||
DPTR_D(AVThread);
|
||||
d.statistics = statistics;
|
||||
}
|
||||
|
||||
bool AVThread::waitForStarted(int msec)
|
||||
{
|
||||
if (!d_func().sem.tryAcquire(1, msec > 0 ? msec : std::numeric_limits<int>::max()))
|
||||
return false;
|
||||
d_func().sem.release(1); //ensure another waitForStarted() continues
|
||||
return true;
|
||||
}
|
||||
|
||||
void AVThread::waitAndCheck(ulong value, qreal pts)
|
||||
{
|
||||
DPTR_D(AVThread);
|
||||
if (value <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(value > 1000) {
|
||||
qInfo() << __FUNCTION__ << __LINE__<< value << "WAIT>>>>>>>";
|
||||
}
|
||||
|
||||
value += d.wait_err;
|
||||
d.wait_timer.restart();
|
||||
//qDebug("wating for %lu msecs", value);
|
||||
ulong us = value * 1000UL;
|
||||
const ulong ms = value;
|
||||
static const ulong kWaitSlice = K_WAIT_SLICE * 1000UL; //20ms
|
||||
while (us > kWaitSlice)
|
||||
{
|
||||
usleep(kWaitSlice);
|
||||
if (d.stop)
|
||||
us = 0;
|
||||
else
|
||||
us -= kWaitSlice;
|
||||
if (pts > 0) {
|
||||
us = qMin(us, ulong((double)(qMax<qreal>(0, pts - d.clock->value()))*1000000.0));
|
||||
}
|
||||
//qDebug("us: %lu/%lu, pts: %f, clock: %f", us, ms-et.elapsed(), pts, d.clock->value());
|
||||
processNextTask();
|
||||
us = qMin<ulong>(us, (ms-d.wait_timer.elapsed())*1000);
|
||||
}
|
||||
if (us > 0)
|
||||
{
|
||||
usleep(us);
|
||||
// if(us > 100000) {
|
||||
// qInfo() << "USLEEP!_____________" << us << __FUNCTION__ << __LINE__;
|
||||
// }
|
||||
}
|
||||
//qDebug("wait elapsed: %lu %d/%lld", us, ms, et.elapsed());
|
||||
const int de = ((ms-d.wait_timer.elapsed()) - d.wait_err);
|
||||
if (de > -3 && de < 3)
|
||||
{
|
||||
d.wait_err += de;
|
||||
}
|
||||
else
|
||||
{
|
||||
d.wait_err += de > 0 ? 1 : -1;
|
||||
}
|
||||
//qDebug("err: %lld", d.wait_err);
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
145
project/fm_viewer/fav/AVThread.h
Normal file
145
project/fm_viewer/fav/AVThread.h
Normal file
@@ -0,0 +1,145 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AVTHREAD_H
|
||||
#define QTAV_AVTHREAD_H
|
||||
|
||||
#include <QtCore/QRunnable>
|
||||
#include <QtCore/QScopedPointer>
|
||||
#include <QtCore/QThread>
|
||||
#include "PacketBuffer.h"
|
||||
//TODO: pause functions. AVOutput may be null, use AVThread's pause state
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class AVDecoder;
|
||||
class AVThreadPrivate;
|
||||
class AVOutput;
|
||||
class AVClock;
|
||||
class Filter;
|
||||
class Statistics;
|
||||
class OutputSet;
|
||||
class AVThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
DPTR_DECLARE_PRIVATE(AVThread)
|
||||
public:
|
||||
explicit AVThread(QObject *parent = 0);
|
||||
virtual ~AVThread();
|
||||
|
||||
int playerID;
|
||||
#if (FORCE_BREAK_EOF)
|
||||
bool forceEnd;
|
||||
bool aboutToEnd; // 종료전 queue 를 비우고 있는 상태
|
||||
#endif
|
||||
|
||||
#if (FIX_PLAYER_END_CLIP)
|
||||
bool endStop;
|
||||
#endif//
|
||||
|
||||
#if (PREVENT_OVER_DURATION_RENDER)
|
||||
qreal duration;
|
||||
#endif
|
||||
//used for changing some components when running
|
||||
Q_DECL_DEPRECATED void lock();
|
||||
Q_DECL_DEPRECATED void unlock();
|
||||
|
||||
void setClock(AVClock *clock);
|
||||
AVClock* clock() const;
|
||||
|
||||
PacketBuffer* packetQueue() const;
|
||||
|
||||
void setDecoder(AVDecoder *decoder);
|
||||
AVDecoder *decoder() const;
|
||||
|
||||
void setOutput(AVOutput *out); //Q_DECL_DEPRECATED
|
||||
AVOutput* output() const; //Q_DECL_DEPRECATED
|
||||
|
||||
void setOutputSet(OutputSet *set);
|
||||
OutputSet* outputSet() const;
|
||||
|
||||
void setDemuxEnded(bool ended);
|
||||
|
||||
bool isPaused() const;
|
||||
|
||||
bool waitForStarted(int msec = -1);
|
||||
|
||||
bool installFilter(Filter *filter, int index = 0x7FFFFFFF, bool lock = true);
|
||||
bool uninstallFilter(Filter *filter, bool lock = true);
|
||||
const QList<Filter *> &filters() const;
|
||||
|
||||
// TODO: resample, resize task etc.
|
||||
void scheduleTask(QRunnable *task);
|
||||
void requestSeek();
|
||||
void scheduleFrameDrop(bool value = true);
|
||||
qreal previousHistoryPts() const; //move to statistics?
|
||||
qreal decodeFrameRate() const; //move to statistics?
|
||||
void setDropFrameOnSeek(bool value);
|
||||
|
||||
#if (FIX_PLAYER_END_CLIP)
|
||||
void endStopBy();
|
||||
#endif
|
||||
|
||||
#if (USE_DURATION_IN_THREAD)
|
||||
double durationSec; // 시간 확인해서 decode thread 종료 void AVDemuxThread::run() 에서 video 만 설정
|
||||
#endif
|
||||
public slots:
|
||||
virtual void stop();
|
||||
/*change pause state. the pause/continue action will do in the next loop*/
|
||||
void pause(bool p); //processEvents when waiting?
|
||||
void nextAndPause(); //process 1 frame and pause
|
||||
|
||||
Q_SIGNALS:
|
||||
void frameDelivered();
|
||||
/*!
|
||||
* \brief seekFinished
|
||||
* \param timestamp the frame pts after seek
|
||||
*/
|
||||
#if (PLAY_SYNC_FIX2)
|
||||
void seekFinished(qint64 timestamp,qint64 requested);
|
||||
#else // PLAY_SYNC_FIX2
|
||||
void seekFinished(qint64 timestamp);
|
||||
#endif // PLAY_SYNC_FIX2
|
||||
void eofDecoded();
|
||||
private Q_SLOTS:
|
||||
void onStarted();
|
||||
void onFinished();
|
||||
protected:
|
||||
AVThread(AVThreadPrivate& d, QObject *parent = 0);
|
||||
void resetState();
|
||||
/*
|
||||
* If the pause state is true setted by pause(true), then block the thread and wait for pause state changed, i.e. pause(false)
|
||||
* and return true. Otherwise, return false immediatly.
|
||||
*/
|
||||
// has timeout so that the pending tasks can be processed
|
||||
bool tryPause(unsigned long timeout = 100);
|
||||
bool processNextTask(); //in AVThread
|
||||
// pts > 0: compare pts and clock when waiting
|
||||
void waitAndCheck(ulong value, qreal pts);
|
||||
|
||||
DPTR_DECLARE(AVThread)
|
||||
private:
|
||||
void setStatistics(Statistics* statistics);
|
||||
friend class AVPlayer;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // QTAV_AVTHREAD_H
|
||||
106
project/fm_viewer/fav/AVThread_p.h
Normal file
106
project/fm_viewer/fav/AVThread_p.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/******************************************************************************
|
||||
QtAV: Media play library based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AVTHREAD_P_H
|
||||
#define QTAV_AVTHREAD_P_H
|
||||
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QMutexLocker>
|
||||
#include <QtCore/QSemaphore>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QWaitCondition>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#else
|
||||
#include <QtCore/QTime>
|
||||
typedef QTime QElapsedTimer;
|
||||
#endif
|
||||
|
||||
#include "PacketBuffer.h"
|
||||
#include "ring.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QRunnable;
|
||||
QT_END_NAMESPACE
|
||||
namespace FAV {
|
||||
|
||||
const double kSyncThreshold = 0.2; // 200 ms
|
||||
class AVDecoder;
|
||||
class AVOutput;
|
||||
class AVClock;
|
||||
class Filter;
|
||||
class Statistics;
|
||||
class OutputSet;
|
||||
class AVThreadPrivate : public DPtrPrivate<AVThread>
|
||||
{
|
||||
public:
|
||||
AVThreadPrivate():
|
||||
paused(false)
|
||||
, next_pause(false)
|
||||
, stop(false)
|
||||
, clock(0)
|
||||
, dec(0)
|
||||
, outputSet(0)
|
||||
, delay(0)
|
||||
, statistics(0)
|
||||
, seek_requested(false)
|
||||
, render_pts0(-1)
|
||||
, drop_frame_seek(true)
|
||||
, pts_history(30)
|
||||
, wait_err(0)
|
||||
{
|
||||
tasks.blockFull(false);
|
||||
|
||||
QVariantHash opt;
|
||||
opt[QString::fromLatin1("skip_frame")] = 8; // 8 for "avcodec", "NoRef" for "FFmpeg". see AVDiscard
|
||||
dec_opt_framedrop[QString::fromLatin1("avcodec")] = opt;
|
||||
opt[QString::fromLatin1("skip_frame")] = 0; // 0 for "avcodec", "Default" for "FFmpeg". see AVDiscard
|
||||
dec_opt_normal[QString::fromLatin1("avcodec")] = opt; // avcodec need correct string or value in libavcodec
|
||||
}
|
||||
virtual ~AVThreadPrivate();
|
||||
|
||||
bool paused, next_pause;
|
||||
volatile bool stop; //true when packets is empty and demux is end.
|
||||
AVClock *clock;
|
||||
PacketBuffer packets;
|
||||
AVDecoder *dec;
|
||||
OutputSet *outputSet;
|
||||
QMutex mutex;
|
||||
QWaitCondition cond; //pause
|
||||
qreal delay;
|
||||
QList<Filter*> filters;
|
||||
Statistics *statistics; //not obj. Statistics is unique for the player, which is in AVPlayer
|
||||
BlockingQueue<QRunnable*> tasks;
|
||||
QSemaphore sem;
|
||||
bool seek_requested;
|
||||
//only decode video without display or skip decode audio until pts reaches
|
||||
qreal render_pts0; // SEEK 용도?
|
||||
|
||||
static QVariantHash dec_opt_framedrop, dec_opt_normal;
|
||||
bool drop_frame_seek;
|
||||
ring<qreal> pts_history;
|
||||
|
||||
qint64 wait_err;
|
||||
QElapsedTimer wait_timer;
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QTAV_AVTHREAD_P_H
|
||||
384
project/fm_viewer/fav/AVTranscoder.cpp
Normal file
384
project/fm_viewer/fav/AVTranscoder.cpp
Normal file
@@ -0,0 +1,384 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AVTranscoder.h"
|
||||
#if !(REMOVE_AV_ENCODER)
|
||||
#include "AVPlayer.h"
|
||||
#include "AVMuxer.h"
|
||||
#include "EncodeFilter.h"
|
||||
#include "Statistics.h"
|
||||
#include "BlockingQueue.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class AVTranscoder::Private
|
||||
{
|
||||
public:
|
||||
Private()
|
||||
: started(false)
|
||||
, async(false)
|
||||
, encoded_frames(0)
|
||||
, start_time(0)
|
||||
, source_player(0)
|
||||
, afilter(0)
|
||||
, vfilter(0)
|
||||
{}
|
||||
|
||||
~Private() {
|
||||
muxer.close();
|
||||
if (afilter) {
|
||||
delete afilter;
|
||||
}
|
||||
if (vfilter) {
|
||||
delete vfilter;
|
||||
}
|
||||
}
|
||||
|
||||
bool started;
|
||||
bool async;
|
||||
int encoded_frames;
|
||||
qint64 start_time;
|
||||
AVPlayer *source_player;
|
||||
AudioEncodeFilter *afilter;
|
||||
VideoEncodeFilter *vfilter;
|
||||
//BlockingQueue<Packet> aqueue, vqueue; // TODO: 1 queue if packet.mediaType is enabled
|
||||
AVMuxer muxer;
|
||||
QString format;
|
||||
QVector<Filter*> filters;
|
||||
};
|
||||
|
||||
AVTranscoder::AVTranscoder(QObject *parent)
|
||||
: QObject(parent)
|
||||
, d(new Private())
|
||||
{
|
||||
}
|
||||
|
||||
AVTranscoder::~AVTranscoder()
|
||||
{
|
||||
stop();
|
||||
//TODO: wait for stopped()
|
||||
}
|
||||
|
||||
void AVTranscoder::setAsync(bool value)
|
||||
{
|
||||
if (d->async == value)
|
||||
return;
|
||||
d->async = value;
|
||||
Q_EMIT asyncChanged();
|
||||
if (d->afilter) {
|
||||
d->afilter->setAsync(value);
|
||||
}
|
||||
if (d->vfilter) {
|
||||
d->vfilter->setAsync(value);
|
||||
}
|
||||
}
|
||||
|
||||
bool AVTranscoder::isAsync() const
|
||||
{
|
||||
return d->async;
|
||||
}
|
||||
|
||||
void AVTranscoder::setMediaSource(AVPlayer *player)
|
||||
{
|
||||
if (d->source_player) {
|
||||
if (d->afilter)
|
||||
disconnect(d->source_player, SIGNAL(stopped()), d->afilter, SLOT(finish()));
|
||||
if (d->vfilter)
|
||||
disconnect(d->source_player, SIGNAL(stopped()), d->vfilter, SLOT(finish()));
|
||||
disconnect(d->source_player, SIGNAL(started()), this, SLOT(onSourceStarted()));
|
||||
}
|
||||
d->source_player = player;
|
||||
// direct connect to ensure it's called before encoders open in filters
|
||||
connect(d->source_player, SIGNAL(started()), this, SLOT(onSourceStarted()), Qt::DirectConnection);
|
||||
}
|
||||
|
||||
AVPlayer* AVTranscoder::sourcePlayer() const
|
||||
{
|
||||
return d->source_player;
|
||||
}
|
||||
|
||||
QString AVTranscoder::outputFile() const
|
||||
{
|
||||
return d->muxer.fileName();
|
||||
}
|
||||
|
||||
QIODevice* AVTranscoder::outputDevice() const
|
||||
{
|
||||
return d->muxer.ioDevice();
|
||||
}
|
||||
|
||||
MediaIO* AVTranscoder::outputMediaIO() const
|
||||
{
|
||||
return d->muxer.mediaIO();
|
||||
}
|
||||
|
||||
void AVTranscoder::setOutputMedia(const QString &fileName)
|
||||
{
|
||||
d->muxer.setMedia(fileName);
|
||||
}
|
||||
|
||||
void AVTranscoder::setOutputMedia(QIODevice *dev)
|
||||
{
|
||||
d->muxer.setMedia(dev);
|
||||
}
|
||||
|
||||
void AVTranscoder::setOutputMedia(MediaIO *io)
|
||||
{
|
||||
d->muxer.setMedia(io);
|
||||
}
|
||||
|
||||
void AVTranscoder::setOutputFormat(const QString &fmt)
|
||||
{
|
||||
d->format = fmt;
|
||||
d->muxer.setFormat(fmt);
|
||||
}
|
||||
|
||||
QString AVTranscoder::outputFormatForced() const
|
||||
{
|
||||
return d->format;
|
||||
}
|
||||
|
||||
void AVTranscoder::setOutputOptions(const QVariantHash &dict)
|
||||
{
|
||||
d->muxer.setOptions(dict);
|
||||
}
|
||||
|
||||
QVariantHash AVTranscoder::outputOptions() const
|
||||
{
|
||||
return d->muxer.options();
|
||||
}
|
||||
|
||||
bool AVTranscoder::createVideoEncoder(const QString &name)
|
||||
{
|
||||
if (!d->vfilter) {
|
||||
d->vfilter = new VideoEncodeFilter();
|
||||
d->vfilter->setAsync(isAsync());
|
||||
// BlockingQueuedConnection: ensure muxer open()/close() in the same thread, and is open when packet is encoded
|
||||
connect(d->vfilter, SIGNAL(readyToEncode()), SLOT(prepareMuxer()), Qt::BlockingQueuedConnection);
|
||||
// direct: can ensure delayed frames (when stop()) are written at last
|
||||
connect(d->vfilter, SIGNAL(frameEncoded(FAV::Packet)), SLOT(writeVideo(FAV::Packet)), Qt::DirectConnection);
|
||||
connect(d->vfilter, SIGNAL(finished()), SLOT(tryFinish()));
|
||||
}
|
||||
return !!d->vfilter->createEncoder(name);
|
||||
}
|
||||
|
||||
VideoEncoder* AVTranscoder::videoEncoder() const
|
||||
{
|
||||
if (!d->vfilter)
|
||||
return 0;
|
||||
return d->vfilter->encoder();
|
||||
}
|
||||
|
||||
bool AVTranscoder::createAudioEncoder(const QString &name)
|
||||
{
|
||||
if (!d->afilter) {
|
||||
d->afilter = new AudioEncodeFilter();
|
||||
d->afilter->setAsync(isAsync());
|
||||
// BlockingQueuedConnection: ensure muxer open()/close() in the same thread, and is open when packet is encoded
|
||||
connect(d->afilter, SIGNAL(readyToEncode()), SLOT(prepareMuxer()), Qt::BlockingQueuedConnection);
|
||||
// direct: can ensure delayed frames (when stop()) are written at last
|
||||
connect(d->afilter, SIGNAL(frameEncoded(FAV::Packet)), SLOT(writeAudio(FAV::Packet)), Qt::DirectConnection);
|
||||
connect(d->afilter, SIGNAL(finished()), SLOT(tryFinish()));
|
||||
}
|
||||
return !!d->afilter->createEncoder(name);
|
||||
}
|
||||
|
||||
AudioEncoder* AVTranscoder::audioEncoder() const
|
||||
{
|
||||
if (!d->afilter)
|
||||
return 0;
|
||||
return d->afilter->encoder();
|
||||
}
|
||||
|
||||
bool AVTranscoder::isRunning() const
|
||||
{
|
||||
return d->started;
|
||||
}
|
||||
|
||||
bool AVTranscoder::isPaused() const
|
||||
{
|
||||
if (d->vfilter) {
|
||||
if (d->vfilter->isEnabled())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
if (d->afilter) {
|
||||
if (d->afilter->isEnabled())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return false; //stopped
|
||||
}
|
||||
|
||||
qint64 AVTranscoder::startTime() const
|
||||
{
|
||||
return d->start_time;
|
||||
}
|
||||
|
||||
void AVTranscoder::setStartTime(qint64 ms)
|
||||
{
|
||||
if (d->start_time == ms)
|
||||
return;
|
||||
d->start_time = ms;
|
||||
Q_EMIT startTimeChanged(ms);
|
||||
if (d->afilter)
|
||||
d->afilter->setStartTime(startTime());
|
||||
if (d->vfilter)
|
||||
d->vfilter->setStartTime(startTime());
|
||||
}
|
||||
|
||||
void AVTranscoder::start()
|
||||
{
|
||||
if (!videoEncoder())
|
||||
return;
|
||||
if (!sourcePlayer())
|
||||
return;
|
||||
d->encoded_frames = 0;
|
||||
d->started = true;
|
||||
d->filters.clear();
|
||||
if (sourcePlayer()) {
|
||||
if (d->afilter) {
|
||||
d->filters.append(d->afilter);
|
||||
d->afilter->setStartTime(startTime());
|
||||
sourcePlayer()->installFilter(d->afilter);
|
||||
disconnect(sourcePlayer(), SIGNAL(stopped()), d->afilter, SLOT(finish()));
|
||||
connect(sourcePlayer(), SIGNAL(stopped()), d->afilter, SLOT(finish()), Qt::DirectConnection);
|
||||
}
|
||||
if (d->vfilter) {
|
||||
d->filters.append(d->vfilter);
|
||||
d->vfilter->setStartTime(startTime());
|
||||
qDebug("framerate: %.3f/%.3f", videoEncoder()->frameRate(), sourcePlayer()->statistics().video.frame_rate);
|
||||
if (videoEncoder()->frameRate() <= 0) { // use source frame rate. set before install filter (so before open)
|
||||
videoEncoder()->setFrameRate(sourcePlayer()->statistics().video.frame_rate);
|
||||
}
|
||||
sourcePlayer()->installFilter(d->vfilter);
|
||||
disconnect(sourcePlayer(), SIGNAL(stopped()), d->vfilter, SLOT(finish()));
|
||||
connect(sourcePlayer(), SIGNAL(stopped()), d->vfilter, SLOT(finish()), Qt::DirectConnection);
|
||||
}
|
||||
}
|
||||
Q_EMIT started();
|
||||
}
|
||||
|
||||
void AVTranscoder::stop()
|
||||
{
|
||||
if (!isRunning())
|
||||
return;
|
||||
if (!d->muxer.isOpen())
|
||||
return;
|
||||
// uninstall encoder filters first then encoders can be closed safely
|
||||
if (sourcePlayer()) {
|
||||
sourcePlayer()->uninstallFilter(d->afilter);
|
||||
sourcePlayer()->uninstallFilter(d->vfilter);
|
||||
}
|
||||
if (d->afilter)
|
||||
d->afilter->finish(); //FIXME: thread of sync mode
|
||||
if (d->vfilter)
|
||||
d->vfilter->finish();
|
||||
}
|
||||
|
||||
void AVTranscoder::stopInternal()
|
||||
{
|
||||
d->muxer.close();
|
||||
d->started = false;
|
||||
Q_EMIT stopped();
|
||||
qDebug("AVTranscoder stopped");
|
||||
}
|
||||
|
||||
void AVTranscoder::pause(bool value)
|
||||
{
|
||||
if (d->vfilter)
|
||||
d->vfilter->setEnabled(!value);
|
||||
if (d->afilter)
|
||||
d->afilter->setEnabled(!value);
|
||||
Q_EMIT paused(value);
|
||||
}
|
||||
|
||||
void AVTranscoder::onSourceStarted()
|
||||
{
|
||||
if (d->vfilter) {
|
||||
qDebug("onSourceStarted framerate: %.3f/%.3f", videoEncoder()->frameRate(), sourcePlayer()->statistics().video.frame_rate);
|
||||
if (videoEncoder()->frameRate() <= 0) { // use source frame rate. set before install filter (so before open)
|
||||
videoEncoder()->setFrameRate(sourcePlayer()->statistics().video.frame_rate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AVTranscoder::prepareMuxer()
|
||||
{
|
||||
// TODO: lock here?
|
||||
// open muxer only if all encoders are open
|
||||
if (audioEncoder() && videoEncoder()) {
|
||||
if (!audioEncoder()->isOpen() || !videoEncoder()->isOpen()) {
|
||||
qDebug("encoders are not readly a:%d v:%d", audioEncoder()->isOpen(), videoEncoder()->isOpen());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (audioEncoder())
|
||||
d->muxer.copyProperties(audioEncoder());
|
||||
if (videoEncoder())
|
||||
d->muxer.copyProperties(videoEncoder());
|
||||
if (!d->format.isEmpty())
|
||||
d->muxer.setFormat(d->format); // clear when media changed
|
||||
if (!d->muxer.open()) {
|
||||
qWarning("Failed to open muxer");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void AVTranscoder::writeAudio(const FAV::Packet &packet)
|
||||
{
|
||||
// TODO: muxer maybe is not open. queue the packet
|
||||
if (!d->muxer.isOpen()) {
|
||||
//d->aqueue.put(packet);
|
||||
return;
|
||||
}
|
||||
d->muxer.writeAudio(packet);
|
||||
Q_EMIT audioFrameEncoded(packet.pts);
|
||||
|
||||
if (d->vfilter)
|
||||
return;
|
||||
// TODO: startpts, duration, encoded size
|
||||
d->encoded_frames++;
|
||||
//qDebug("encoded frames: %d, pos: %lld", d->encoded_frames, packet.position);
|
||||
}
|
||||
|
||||
void AVTranscoder::writeVideo(const FAV::Packet &packet)
|
||||
{
|
||||
// TODO: muxer maybe is not open. queue the packet
|
||||
if (!d->muxer.isOpen())
|
||||
return;
|
||||
d->muxer.writeVideo(packet);
|
||||
Q_EMIT videoFrameEncoded(packet.pts);
|
||||
// TODO: startpts, duration, encoded size
|
||||
d->encoded_frames++;
|
||||
printf("encoded frames: %d, @%.3f pos: %lld\r", d->encoded_frames, packet.pts, packet.position);fflush(0);
|
||||
}
|
||||
|
||||
void AVTranscoder::tryFinish()
|
||||
{
|
||||
Filter* f = qobject_cast<Filter*>(sender());
|
||||
d->filters.remove(d->filters.indexOf(f));
|
||||
if (d->filters.isEmpty())
|
||||
stopInternal();
|
||||
}
|
||||
} //namespace FAV
|
||||
#endif // #if !(REMOVE_AV_ENCODER)
|
||||
156
project/fm_viewer/fav/AVTranscoder.h
Normal file
156
project/fm_viewer/fav/AVTranscoder.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) (2012-2016) Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AVTRANSCODE_H
|
||||
#define QTAV_AVTRANSCODE_H
|
||||
|
||||
#include "MediaIO.h"
|
||||
#include "AudioEncoder.h"
|
||||
#include "VideoEncoder.h"
|
||||
|
||||
#if !(REMOVE_AV_ENCODER)
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class AVPlayer;
|
||||
class Q_AV_EXPORT AVTranscoder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AVTranscoder(QObject* parent = 0);
|
||||
~AVTranscoder();
|
||||
|
||||
// TODO: other source (more operations needed, e.g. seek)?
|
||||
void setMediaSource(AVPlayer* player);
|
||||
AVPlayer* sourcePlayer() const;
|
||||
|
||||
QString outputFile() const;
|
||||
QIODevice* outputDevice() const;
|
||||
MediaIO* outputMediaIO() const;
|
||||
/*!
|
||||
* \brief setOutputMedia
|
||||
*/
|
||||
void setOutputMedia(const QString& fileName);
|
||||
void setOutputMedia(QIODevice* dev);
|
||||
void setOutputMedia(MediaIO* io);
|
||||
/*!
|
||||
* \brief setOutputFormat
|
||||
* Force the output format. Useful for custom io
|
||||
*/
|
||||
void setOutputFormat(const QString& fmt);
|
||||
QString outputFormatForced() const;
|
||||
|
||||
void setOutputOptions(const QVariantHash &dict);
|
||||
QVariantHash outputOptions() const;
|
||||
|
||||
/*!
|
||||
* \brief setAsync
|
||||
* Enable async encoding. Default is disabled.
|
||||
*/
|
||||
void setAsync(bool value = true);
|
||||
bool isAsync() const;
|
||||
/*!
|
||||
* \brief createEncoder
|
||||
* Destroy old encoder and create a new one in filter chain. Filter has the ownership. You shall not manually open it. Transcoder will set the missing parameters open it.
|
||||
* \param name registered encoder name, for example "FFmpeg"
|
||||
* \return false if failed
|
||||
*/
|
||||
bool createVideoEncoder(const QString& name = QLatin1String("FFmpeg"));
|
||||
/*!
|
||||
* \brief encoder
|
||||
* Use this to set encoder properties and options.
|
||||
* If frameRate is not set, source frame rate will be set if it's valid, otherwise VideoEncoder::defaultFrameRate() will be used internally
|
||||
* Do not call open()/close() manually
|
||||
* \return Encoder instance or null if createVideoEncoder failed
|
||||
*/
|
||||
VideoEncoder* videoEncoder() const;
|
||||
/*!
|
||||
* \brief createEncoder
|
||||
* Destroy old encoder and create a new one in filter chain. Filter has the ownership. You shall not manually open it. Transcoder will set the missing parameters open it.
|
||||
* \param name registered encoder name, for example "FFmpeg"
|
||||
* \return false if failed
|
||||
*/
|
||||
bool createAudioEncoder(const QString& name = QLatin1String("FFmpeg"));
|
||||
/*!
|
||||
* \brief encoder
|
||||
* Use this to set encoder properties and options.
|
||||
* Do not call open()/close() manually
|
||||
* \return Encoder instance or null if createAudioEncoder failed
|
||||
*/
|
||||
AudioEncoder* audioEncoder() const;
|
||||
/*!
|
||||
* \brief isRunning
|
||||
* \return true if encoding started
|
||||
*/
|
||||
bool isRunning() const;
|
||||
bool isPaused() const;
|
||||
qint64 encodedSize() const;
|
||||
qreal startTimestamp() const;
|
||||
qreal encodedDuration() const;
|
||||
|
||||
/*!
|
||||
* \brief startTime
|
||||
* Start to encode after startTime(). To decode from a given time, call sourcePlayer()->setPosition()
|
||||
*/
|
||||
qint64 startTime() const;
|
||||
void setStartTime(qint64 ms);
|
||||
|
||||
Q_SIGNALS:
|
||||
void videoFrameEncoded(qreal timestamp);
|
||||
void audioFrameEncoded(qreal timestamp);
|
||||
void started();
|
||||
void stopped();
|
||||
void paused(bool value);
|
||||
void startTimeChanged(qint64 ms);
|
||||
void asyncChanged();
|
||||
|
||||
public Q_SLOTS:
|
||||
void start();
|
||||
/*!
|
||||
* \brief stop
|
||||
* Call stop() to encode delayed frames remains in encoder and then stop encoding.
|
||||
* It's called internally when sourcePlayer() is stopped
|
||||
*/
|
||||
void stop();
|
||||
/*!
|
||||
* \brief pause
|
||||
* pause the encoders
|
||||
* \param value
|
||||
*/
|
||||
void pause(bool value);
|
||||
|
||||
private Q_SLOTS:
|
||||
void onSourceStarted();
|
||||
void prepareMuxer();
|
||||
void writeAudio(const FAV::Packet& packet);
|
||||
void writeVideo(const FAV::Packet& packet);
|
||||
void tryFinish();
|
||||
|
||||
private:
|
||||
void stopInternal();
|
||||
class Private;
|
||||
QScopedPointer<Private> d;
|
||||
};
|
||||
} //namespace FAV
|
||||
#endif // QTAV_AVTRANSCODE_H
|
||||
|
||||
#endif // #if !(REMOVE_AV_ENCODER)
|
||||
|
||||
95
project/fm_viewer/fav/AudioDecoder.cpp
Normal file
95
project/fm_viewer/fav/AudioDecoder.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AudioDecoder.h"
|
||||
#include "AVDecoder_p.h"
|
||||
#include "AVCompat.h"
|
||||
#include "AudioResampler.h"
|
||||
#include "factory.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
FACTORY_DEFINE(AudioDecoder)
|
||||
// TODO: why vc can not declare extern func in a class member? resolved as &func@@YAXXZ
|
||||
extern bool RegisterAudioDecoderFFmpeg_Man();
|
||||
void AudioDecoder::registerAll() {
|
||||
static bool done = false;
|
||||
if (done)
|
||||
return;
|
||||
done = true;
|
||||
RegisterAudioDecoderFFmpeg_Man();
|
||||
}
|
||||
|
||||
QStringList AudioDecoder::supportedCodecs()
|
||||
{
|
||||
static QStringList codecs;
|
||||
if (!codecs.isEmpty())
|
||||
return codecs;
|
||||
avcodec_register_all();
|
||||
AVCodec* c = NULL;
|
||||
while ((c=av_codec_next(c))) {
|
||||
if (!av_codec_is_decoder(c) || c->type != AVMEDIA_TYPE_AUDIO)
|
||||
continue;
|
||||
codecs.append(QString::fromLatin1(c->name));
|
||||
}
|
||||
return codecs;
|
||||
}
|
||||
|
||||
AudioDecoderPrivate::AudioDecoderPrivate()
|
||||
: AVDecoderPrivate()
|
||||
, resampler(0)
|
||||
{
|
||||
resampler = AudioResampler::create(AudioResamplerId_FF);
|
||||
if (!resampler)
|
||||
resampler = AudioResampler::create(AudioResamplerId_Libav);
|
||||
if (resampler)
|
||||
resampler->setOutSampleFormat(AV_SAMPLE_FMT_FLT);
|
||||
}
|
||||
|
||||
AudioDecoderPrivate::~AudioDecoderPrivate()
|
||||
{
|
||||
if (resampler) {
|
||||
delete resampler;
|
||||
resampler = 0;
|
||||
}
|
||||
}
|
||||
|
||||
AudioDecoder::AudioDecoder(AudioDecoderPrivate &d):
|
||||
AVDecoder(d)
|
||||
{
|
||||
}
|
||||
|
||||
QString AudioDecoder::name() const
|
||||
{
|
||||
return QLatin1String(AudioDecoder::name(id()));
|
||||
}
|
||||
|
||||
QByteArray AudioDecoder::data() const
|
||||
{
|
||||
return d_func().decoded;
|
||||
}
|
||||
|
||||
AudioResampler* AudioDecoder::resampler()
|
||||
{
|
||||
return d_func().resampler;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
80
project/fm_viewer/fav/AudioDecoder.h
Normal file
80
project/fm_viewer/fav/AudioDecoder.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QAV_AUDIODECODER_H
|
||||
#define QAV_AUDIODECODER_H
|
||||
|
||||
#include "AVDecoder.h"
|
||||
#include "AudioFrame.h"
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
#define USE_AUDIO_FRAME 1
|
||||
//TODO: decoder.in/outAudioFormat()?
|
||||
namespace FAV {
|
||||
typedef int AudioDecoderId;
|
||||
// built-in decoders
|
||||
extern Q_AV_EXPORT AudioDecoderId AudioDecoderId_FFmpeg;
|
||||
|
||||
class AudioResampler;
|
||||
class AudioDecoderPrivate;
|
||||
class Q_AV_EXPORT AudioDecoder : public AVDecoder
|
||||
{
|
||||
Q_DISABLE_COPY(AudioDecoder)
|
||||
DPTR_DECLARE_PRIVATE(AudioDecoder)
|
||||
public:
|
||||
static QStringList supportedCodecs();
|
||||
static AudioDecoder* create(AudioDecoderId id);
|
||||
/*!
|
||||
* \brief create
|
||||
* create a decoder from registered name. FFmpeg decoder will be created for empty name
|
||||
* \param name can be "FFmpeg"
|
||||
* \return 0 if not registered
|
||||
*/
|
||||
static AudioDecoder* create(const char* name = "FFmpeg");
|
||||
virtual AudioDecoderId id() const = 0;
|
||||
QString name() const; //name from factory
|
||||
virtual QByteArray data() const; //decoded data
|
||||
virtual AudioFrame frame() = 0;
|
||||
AudioResampler *resampler(); //TODO: remove. can not share the same resampler for multiple frames
|
||||
public:
|
||||
template<class C> static bool Register(AudioDecoderId id, const char* name) { return Register(id, create<C>, name);}
|
||||
/*!
|
||||
* \brief next
|
||||
* \param id NULL to get the first id address
|
||||
* \return address of id or NULL if not found/end
|
||||
*/
|
||||
static AudioDecoderId* next(AudioDecoderId* id = 0);
|
||||
static const char* name(AudioDecoderId id);
|
||||
static AudioDecoderId id(const char* name);
|
||||
private:
|
||||
// if QtAV is static linked (ios for example), components may be not automatically registered. Add registerAll() to workaround
|
||||
static void registerAll();
|
||||
template<class C> static AudioDecoder* create() { return new C();}
|
||||
typedef AudioDecoder* (*AudioDecoderCreator)();
|
||||
static bool Register(AudioDecoderId id, AudioDecoderCreator, const char *name);
|
||||
protected:
|
||||
AudioDecoder(AudioDecoderPrivate& d);
|
||||
private:
|
||||
AudioDecoder();
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QAV_AUDIODECODER_H
|
||||
164
project/fm_viewer/fav/AudioDecoderFFmpeg.cpp
Normal file
164
project/fm_viewer/fav/AudioDecoderFFmpeg.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AudioDecoder.h"
|
||||
#include "AudioResampler.h"
|
||||
#include "Packet.h"
|
||||
#include "AVDecoder_p.h"
|
||||
#include "AVCompat.h"
|
||||
#include "mkid.h"
|
||||
#include "factory.h"
|
||||
#include "version.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class AudioDecoderFFmpegPrivate;
|
||||
class AudioDecoderFFmpeg : public AudioDecoder
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(AudioDecoderFFmpeg)
|
||||
DPTR_DECLARE_PRIVATE(AudioDecoderFFmpeg)
|
||||
Q_PROPERTY(QString codecName READ codecName WRITE setCodecName NOTIFY codecNameChanged)
|
||||
public:
|
||||
AudioDecoderFFmpeg();
|
||||
AudioDecoderId id() const Q_DECL_OVERRIDE Q_DECL_FINAL;
|
||||
virtual QString description() const Q_DECL_OVERRIDE Q_DECL_FINAL {
|
||||
const int patch = QTAV_VERSION_PATCH(avcodec_version());
|
||||
return QStringLiteral("%1 avcodec %2.%3.%4")
|
||||
.arg(patch>=100?QStringLiteral("FFmpeg"):QStringLiteral("Libav"))
|
||||
.arg(QTAV_VERSION_MAJOR(avcodec_version())).arg(QTAV_VERSION_MINOR(avcodec_version())).arg(patch);
|
||||
}
|
||||
#if (PLAY_SYNC_FIX2)
|
||||
virtual bool send(const Packet* packet) Q_DECL_OVERRIDE Q_DECL_FINAL;
|
||||
virtual bool receive() Q_DECL_OVERRIDE Q_DECL_FINAL;
|
||||
#endif // PLAY_SYNC_FIX2
|
||||
bool decode(const Packet& packet) Q_DECL_OVERRIDE Q_DECL_FINAL;
|
||||
AudioFrame frame() Q_DECL_OVERRIDE Q_DECL_FINAL;
|
||||
Q_SIGNALS:
|
||||
void codecNameChanged() Q_DECL_OVERRIDE Q_DECL_FINAL;
|
||||
};
|
||||
|
||||
AudioDecoderId AudioDecoderId_FFmpeg = mkid::id32base36_6<'F','F','m','p','e','g'>::value;
|
||||
FACTORY_REGISTER(AudioDecoder, FFmpeg, "FFmpeg")
|
||||
|
||||
class AudioDecoderFFmpegPrivate Q_DECL_FINAL: public AudioDecoderPrivate
|
||||
{
|
||||
public:
|
||||
AudioDecoderFFmpegPrivate()
|
||||
: AudioDecoderPrivate()
|
||||
, frame(av_frame_alloc())
|
||||
{
|
||||
avcodec_register_all();
|
||||
}
|
||||
~AudioDecoderFFmpegPrivate() {
|
||||
if (frame) {
|
||||
av_frame_free(&frame);
|
||||
frame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
AVFrame *frame; //set once and not change
|
||||
};
|
||||
|
||||
AudioDecoderId AudioDecoderFFmpeg::id() const
|
||||
{
|
||||
return AudioDecoderId_FFmpeg;
|
||||
}
|
||||
|
||||
AudioDecoderFFmpeg::AudioDecoderFFmpeg()
|
||||
: AudioDecoder(*new AudioDecoderFFmpegPrivate())
|
||||
{
|
||||
}
|
||||
#if (PLAY_SYNC_FIX2)
|
||||
bool AudioDecoderFFmpeg::send(const Packet* packet) {
|
||||
return true;
|
||||
}
|
||||
bool AudioDecoderFFmpeg::receive() {
|
||||
return true;
|
||||
}
|
||||
#endif // PLAY_SYNC_FIX2
|
||||
bool AudioDecoderFFmpeg::decode(const Packet &packet)
|
||||
{
|
||||
if (!isAvailable())
|
||||
return false;
|
||||
DPTR_D(AudioDecoderFFmpeg);
|
||||
d.decoded.clear();
|
||||
int got_frame_ptr = 0;
|
||||
int ret = 0;
|
||||
if (packet.isEOF()) {
|
||||
AVPacket eofpkt;
|
||||
av_init_packet(&eofpkt);
|
||||
eofpkt.data = NULL;
|
||||
eofpkt.size = 0;
|
||||
ret = avcodec_decode_audio4(d.codec_ctx, d.frame, &got_frame_ptr, &eofpkt);
|
||||
} else {
|
||||
// const AVPacket*: ffmpeg >= 1.0. no libav
|
||||
ret = avcodec_decode_audio4(d.codec_ctx, d.frame, &got_frame_ptr, (AVPacket*)packet.asAVPacket());
|
||||
}
|
||||
d.undecoded_size = qMin(packet.data.size() - ret, packet.data.size());
|
||||
if (ret == AVERROR(EAGAIN)) {
|
||||
return false;
|
||||
}
|
||||
if (ret < 0) {
|
||||
qWarning("[AudioDecoder] %s", av_err2str(ret));
|
||||
return false;
|
||||
}
|
||||
if (!got_frame_ptr) {
|
||||
qWarning("[AudioDecoder] got_frame_ptr=false. decoded: %d, un: %d", ret, d.undecoded_size);
|
||||
return !packet.isEOF();
|
||||
}
|
||||
#if USE_AUDIO_FRAME
|
||||
return true;
|
||||
#endif
|
||||
d.resampler->setInSampesPerChannel(d.frame->nb_samples);
|
||||
if (!d.resampler->convert((const quint8**)d.frame->extended_data)) {
|
||||
return false;
|
||||
}
|
||||
d.decoded = d.resampler->outData();
|
||||
return true;
|
||||
return !d.decoded.isEmpty();
|
||||
}
|
||||
|
||||
AudioFrame AudioDecoderFFmpeg::frame()
|
||||
{
|
||||
DPTR_D(AudioDecoderFFmpeg);
|
||||
AudioFormat fmt;
|
||||
fmt.setSampleFormatFFmpeg(d.frame->format);
|
||||
fmt.setChannelLayoutFFmpeg(d.frame->channel_layout);
|
||||
fmt.setSampleRate(d.frame->sample_rate);
|
||||
if (!fmt.isValid()) {// need more data to decode to get a frame
|
||||
return AudioFrame();
|
||||
}
|
||||
AudioFrame f(fmt);
|
||||
//av_frame_get_pkt_duration ffmpeg
|
||||
f.setBits(d.frame->extended_data); // TODO: ref
|
||||
f.setBytesPerLine(d.frame->linesize[0], 0); // for correct alignment
|
||||
f.setSamplesPerChannel(d.frame->nb_samples);
|
||||
// TODO: ffplay check AVFrame.pts, pkt_pts, last_pts+nb_samples. move to AudioFrame::from(AVFrame*)
|
||||
//f.setTimestamp((double)d.frame->pkt_pts/1000.0);
|
||||
f.setPTS((double)d.frame->pkt_pts/1000.0);
|
||||
f.setAudioResampler(d.resampler); // TODO: remove. it's not safe if frame is shared. use a pool or detach if ref >1
|
||||
return f;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
#include "AudioDecoderFFmpeg.moc"
|
||||
85
project/fm_viewer/fav/AudioEncoder.cpp
Normal file
85
project/fm_viewer/fav/AudioEncoder.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AudioEncoder.h"
|
||||
#if !(REMOVE_AV_ENCODER)
|
||||
#include "AVEncoder_p.h"
|
||||
#include "factory.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
FACTORY_DEFINE(AudioEncoder)
|
||||
|
||||
void AudioEncoder_RegisterAll()
|
||||
{
|
||||
static bool called = false;
|
||||
if (called)
|
||||
return;
|
||||
called = true;
|
||||
// factory.h does not check whether an id is registered
|
||||
if (AudioEncoder::id("FFmpeg")) //registered on load
|
||||
return;
|
||||
extern bool RegisterAudioEncoderFFmpeg_Man();
|
||||
RegisterAudioEncoderFFmpeg_Man();
|
||||
}
|
||||
|
||||
QStringList AudioEncoder::supportedCodecs()
|
||||
{
|
||||
static QStringList codecs;
|
||||
if (!codecs.isEmpty())
|
||||
return codecs;
|
||||
avcodec_register_all();
|
||||
AVCodec* c = NULL;
|
||||
while ((c=av_codec_next(c))) {
|
||||
if (!av_codec_is_encoder(c) || c->type != AVMEDIA_TYPE_AUDIO)
|
||||
continue;
|
||||
codecs.append(QString::fromLatin1(c->name));
|
||||
}
|
||||
return codecs;
|
||||
}
|
||||
|
||||
AudioEncoder::AudioEncoder(AudioEncoderPrivate &d):
|
||||
AVEncoder(d)
|
||||
{
|
||||
}
|
||||
|
||||
QString AudioEncoder::name() const
|
||||
{
|
||||
return QLatin1String(AudioEncoder::name(id()));
|
||||
}
|
||||
|
||||
void AudioEncoder::setAudioFormat(const AudioFormat& format)
|
||||
{
|
||||
DPTR_D(AudioEncoder);
|
||||
if (d.format == format)
|
||||
return;
|
||||
d.format = format;
|
||||
d.format_used = format;
|
||||
Q_EMIT audioFormatChanged();
|
||||
}
|
||||
|
||||
const AudioFormat& AudioEncoder::audioFormat() const
|
||||
{
|
||||
return d_func().format_used;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
#endif //#if !(REMOVE_AV_ENCODER)
|
||||
92
project/fm_viewer/fav/AudioEncoder.h
Normal file
92
project/fm_viewer/fav/AudioEncoder.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AUDIOENCODER_H
|
||||
#define QTAV_AUDIOENCODER_H
|
||||
|
||||
#include "AVEncoder.h"
|
||||
#if !(REMOVE_AV_ENCODER)
|
||||
#include "AudioFrame.h"
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
namespace FAV {
|
||||
typedef int AudioEncoderId;
|
||||
class AudioEncoderPrivate;
|
||||
class Q_AV_EXPORT AudioEncoder : public AVEncoder
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(FAV::AudioFormat audioFormat READ audioFormat WRITE setAudioFormat NOTIFY audioFormatChanged)
|
||||
Q_DISABLE_COPY(AudioEncoder)
|
||||
DPTR_DECLARE_PRIVATE(AudioEncoder)
|
||||
public:
|
||||
static QStringList supportedCodecs();
|
||||
static AudioEncoder* create(AudioEncoderId id);
|
||||
/*!
|
||||
* \brief create
|
||||
* create an encoder from registered names
|
||||
* \param name can be "FFmpeg". FFmpeg encoder will be created for empty name
|
||||
* \return 0 if not registered
|
||||
*/
|
||||
static AudioEncoder* create(const char* name = "FFmpeg");
|
||||
virtual AudioEncoderId id() const = 0;
|
||||
QString name() const Q_DECL_OVERRIDE; //name from factory
|
||||
/*!
|
||||
* \brief encode
|
||||
* encode a audio frame to a Packet
|
||||
* \param frame pass an invalid frame to get delayed frames
|
||||
* \return
|
||||
*/
|
||||
virtual bool encode(const AudioFrame& frame = AudioFrame()) = 0;
|
||||
|
||||
/// output parameters
|
||||
/*!
|
||||
* \brief audioFormat
|
||||
* If not set or set to an invalid format, a supported format will be used and audioFormat() will be that format after open()
|
||||
* \return
|
||||
* TODO: check supported formats
|
||||
*/
|
||||
const AudioFormat& audioFormat() const;
|
||||
void setAudioFormat(const AudioFormat& format);
|
||||
Q_SIGNALS:
|
||||
void audioFormatChanged();
|
||||
public:
|
||||
template<class C> static bool Register(AudioEncoderId id, const char* name) { return Register(id, create<C>, name);}
|
||||
/*!
|
||||
* \brief next
|
||||
* \param id NULL to get the first id address
|
||||
* \return address of id or NULL if not found/end
|
||||
*/
|
||||
static AudioEncoderId* next(AudioEncoderId* id = 0);
|
||||
static const char* name(AudioEncoderId id);
|
||||
static AudioEncoderId id(const char* name);
|
||||
private:
|
||||
template<class C> static AudioEncoder* create() { return new C();}
|
||||
typedef AudioEncoder* (*AudioEncoderCreator)();
|
||||
static bool Register(AudioEncoderId id, AudioEncoderCreator, const char *name);
|
||||
protected:
|
||||
AudioEncoder(AudioEncoderPrivate& d);
|
||||
private:
|
||||
AudioEncoder();
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QTAV_AUDIOENCODER_H
|
||||
#endif // #if !(REMOVE_AV_ENCODER)
|
||||
231
project/fm_viewer/fav/AudioEncoderFFmpeg.cpp
Normal file
231
project/fm_viewer/fav/AudioEncoderFFmpeg.cpp
Normal file
@@ -0,0 +1,231 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AudioEncoder.h"
|
||||
#if !(REMOVE_AV_ENCODER)
|
||||
#include "AVEncoder_p.h"
|
||||
#include "AVCompat.h"
|
||||
#include "mkid.h"
|
||||
#include "factory.h"
|
||||
#include "version.h"
|
||||
#include "Logger.h"
|
||||
|
||||
/*!
|
||||
* options (properties) are from libavcodec/options_table.h
|
||||
* enum name here must convert to lower case to fit the names in avcodec. done in AVEncoder.setOptions()
|
||||
* Don't use lower case here because the value name may be "default" in avcodec which is a keyword of C++
|
||||
*/
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class AudioEncoderFFmpegPrivate;
|
||||
class AudioEncoderFFmpeg Q_DECL_FINAL: public AudioEncoder
|
||||
{
|
||||
DPTR_DECLARE_PRIVATE(AudioEncoderFFmpeg)
|
||||
public:
|
||||
AudioEncoderFFmpeg();
|
||||
AudioEncoderId id() const Q_DECL_OVERRIDE;
|
||||
bool encode(const AudioFrame &frame = AudioFrame()) Q_DECL_OVERRIDE;
|
||||
};
|
||||
|
||||
static const AudioEncoderId AudioEncoderId_FFmpeg = mkid::id32base36_6<'F', 'F', 'm', 'p', 'e', 'g'>::value;
|
||||
FACTORY_REGISTER(AudioEncoder, FFmpeg, "FFmpeg")
|
||||
|
||||
class AudioEncoderFFmpegPrivate Q_DECL_FINAL: public AudioEncoderPrivate
|
||||
{
|
||||
public:
|
||||
AudioEncoderFFmpegPrivate()
|
||||
: AudioEncoderPrivate()
|
||||
, frame_size(0)
|
||||
{
|
||||
avcodec_register_all();
|
||||
// NULL: codec-specific defaults won't be initialized, which may result in suboptimal default settings (this is important mainly for encoders, e.g. libx264).
|
||||
avctx = avcodec_alloc_context3(NULL);
|
||||
}
|
||||
bool open() Q_DECL_OVERRIDE;
|
||||
bool close() Q_DECL_OVERRIDE;
|
||||
|
||||
int frame_size; // used if avctx->frame_size == 0
|
||||
QByteArray buffer;
|
||||
};
|
||||
|
||||
bool AudioEncoderFFmpegPrivate::open()
|
||||
{
|
||||
if (codec_name.isEmpty()) {
|
||||
// copy ctx from muxer by copyAVCodecContext
|
||||
AVCodec *codec = avcodec_find_encoder(avctx->codec_id);
|
||||
AV_ENSURE_OK(avcodec_open2(avctx, codec, &dict), false);
|
||||
return true;
|
||||
}
|
||||
AVCodec *codec = avcodec_find_encoder_by_name(codec_name.toUtf8().constData());
|
||||
if (!codec) {
|
||||
const AVCodecDescriptor* cd = avcodec_descriptor_get_by_name(codec_name.toUtf8().constData());
|
||||
if (cd) {
|
||||
codec = avcodec_find_encoder(cd->id);
|
||||
}
|
||||
}
|
||||
if (!codec) {
|
||||
qWarning() << "Can not find encoder for codec " << codec_name;
|
||||
return false;
|
||||
}
|
||||
if (avctx) {
|
||||
avcodec_free_context(&avctx);
|
||||
avctx = 0;
|
||||
}
|
||||
avctx = avcodec_alloc_context3(codec);
|
||||
|
||||
// reset format_used to user defined format. important to update default format if format is invalid
|
||||
format_used = format;
|
||||
if (format.sampleRate() <= 0) {
|
||||
if (codec->supported_samplerates) {
|
||||
qDebug("use first supported sample rate: %d", codec->supported_samplerates[0]);
|
||||
format_used.setSampleRate(codec->supported_samplerates[0]);
|
||||
} else {
|
||||
qWarning("sample rate and supported sample rate are not set. use 44100");
|
||||
format_used.setSampleRate(44100);
|
||||
}
|
||||
}
|
||||
if (format.sampleFormat() == AudioFormat::SampleFormat_Unknown) {
|
||||
if (codec->sample_fmts) {
|
||||
qDebug("use first supported sample format: %d", codec->sample_fmts[0]);
|
||||
format_used.setSampleFormatFFmpeg((int)codec->sample_fmts[0]);
|
||||
} else {
|
||||
qWarning("sample format and supported sample format are not set. use s16");
|
||||
format_used.setSampleFormat(AudioFormat::SampleFormat_Signed16);
|
||||
}
|
||||
}
|
||||
if (format.channelLayout() == AudioFormat::ChannelLayout_Unsupported) {
|
||||
if (codec->channel_layouts) {
|
||||
char cl[128];
|
||||
av_get_channel_layout_string(cl, sizeof(cl), -1, codec->channel_layouts[0]); //TODO: ff version
|
||||
qDebug("use first supported channel layout: %s", cl);
|
||||
format_used.setChannelLayoutFFmpeg((qint64)codec->channel_layouts[0]);
|
||||
} else {
|
||||
qWarning("channel layout and supported channel layout are not set. use stereo");
|
||||
format_used.setChannelLayout(AudioFormat::ChannelLayout_Stereo);
|
||||
}
|
||||
}
|
||||
avctx->sample_fmt = (AVSampleFormat)format_used.sampleFormatFFmpeg();
|
||||
avctx->channel_layout = format_used.channelLayoutFFmpeg();
|
||||
avctx->channels = format_used.channels();
|
||||
avctx->sample_rate = format_used.sampleRate();
|
||||
avctx->bits_per_raw_sample = format_used.bytesPerSample()*8;
|
||||
|
||||
/// set the time base. TODO
|
||||
avctx->time_base.num = 1;
|
||||
avctx->time_base.den = format_used.sampleRate();
|
||||
|
||||
avctx->bit_rate = bit_rate;
|
||||
qDebug() << format_used;
|
||||
|
||||
/** Allow the use of the experimental AAC encoder */
|
||||
avctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
|
||||
av_dict_set(&dict, "strict", "-2", 0); //aac, vorbis
|
||||
applyOptionsForContext();
|
||||
// avctx->frame_size will be set in avcodec_open2
|
||||
AV_ENSURE_OK(avcodec_open2(avctx, codec, &dict), false);
|
||||
// from mpv ao_lavc
|
||||
int pcm_hack = 0;
|
||||
int buffer_size = 0;
|
||||
frame_size = avctx->frame_size;
|
||||
if (frame_size <= 1)
|
||||
pcm_hack = av_get_bits_per_sample(avctx->codec_id)/8;
|
||||
if (pcm_hack) {
|
||||
frame_size = 16384; // "enough"
|
||||
buffer_size = frame_size*pcm_hack*format_used.channels()*2+200;
|
||||
} else {
|
||||
buffer_size = frame_size*format_used.bytesPerSample()*format_used.channels()*2+200;
|
||||
}
|
||||
if (buffer_size < FF_MIN_BUFFER_SIZE)
|
||||
buffer_size = FF_MIN_BUFFER_SIZE;
|
||||
buffer.resize(buffer_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioEncoderFFmpegPrivate::close()
|
||||
{
|
||||
AV_ENSURE_OK(avcodec_close(avctx), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
AudioEncoderFFmpeg::AudioEncoderFFmpeg()
|
||||
: AudioEncoder(*new AudioEncoderFFmpegPrivate())
|
||||
{
|
||||
}
|
||||
|
||||
AudioEncoderId AudioEncoderFFmpeg::id() const
|
||||
{
|
||||
return AudioEncoderId_FFmpeg;
|
||||
}
|
||||
|
||||
bool AudioEncoderFFmpeg::encode(const AudioFrame &frame)
|
||||
{
|
||||
DPTR_D(AudioEncoderFFmpeg);
|
||||
AVFrame *f = NULL;
|
||||
if (frame.isValid()) {
|
||||
f = av_frame_alloc();
|
||||
const AudioFormat fmt(frame.format());
|
||||
f->format = fmt.sampleFormatFFmpeg();
|
||||
f->channel_layout = fmt.channelLayoutFFmpeg();
|
||||
// f->channels = fmt.channels(); //remove? not availale in libav9
|
||||
// must be (not the last frame) exactly frame_size unless CODEC_CAP_VARIABLE_FRAME_SIZE is set (frame_size==0)
|
||||
// TODO: mpv use pcmhack for avctx.frame_size==0. can we use input frame.samplesPerChannel?
|
||||
f->nb_samples = d.frame_size;
|
||||
/// f->quality = d.avctx->global_quality; //TODO
|
||||
// TODO: record last pts. mpv compute pts internally and also use playback time
|
||||
f->pts = int64_t(frame.timestamp()*fmt.sampleRate()); // TODO
|
||||
// pts is set in muxer
|
||||
const int nb_planes = frame.planeCount();
|
||||
// bytes between 2 samples on a plane. TODO: add to AudioFormat? what about bytesPerFrame?
|
||||
const int sample_stride = fmt.isPlanar() ? fmt.bytesPerSample() : fmt.bytesPerSample()*fmt.channels();
|
||||
for (int i = 0; i < nb_planes; ++i) {
|
||||
f->linesize[i] = f->nb_samples * sample_stride;// frame.bytesPerLine(i); //
|
||||
f->extended_data[i] = (uint8_t*)frame.constBits(i);
|
||||
}
|
||||
}
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.data = (uint8_t*)d.buffer.constData(); //NULL
|
||||
pkt.size = d.buffer.size(); //0
|
||||
int got_packet = 0;
|
||||
int ret = avcodec_encode_audio2(d.avctx, &pkt, f, &got_packet);
|
||||
av_frame_free(&f);
|
||||
if (ret < 0) {
|
||||
//qWarning("error avcodec_encode_audio2: %s" ,av_err2str(ret));
|
||||
//av_packet_unref(&pkt); //FIXME
|
||||
return false; //false
|
||||
}
|
||||
if (!got_packet) {
|
||||
qWarning("no packet got");
|
||||
d.packet = Packet();
|
||||
// invalid frame means eof
|
||||
return frame.isValid();
|
||||
}
|
||||
// qDebug("enc avpkt.pts: %lld, dts: %lld.", pkt.pts, pkt.dts);
|
||||
d.packet = Packet::fromAVPacket(&pkt, av_q2d(d.avctx->time_base));
|
||||
// qDebug("enc packet.pts: %.3f, dts: %.3f.", d.packet.pts, d.packet.dts);
|
||||
return true;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
#endif // #if !(REMOVE_AV_ENCODER)
|
||||
502
project/fm_viewer/fav/AudioFormat.cpp
Normal file
502
project/fm_viewer/fav/AudioFormat.cpp
Normal file
@@ -0,0 +1,502 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AudioFormat.h"
|
||||
#include "AVCompat.h"
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
#include <QtDebug>
|
||||
#endif //QT_NO_DEBUG_STREAM
|
||||
//FF_API_OLD_SAMPLE_FMT. e.g. FFmpeg 0.9
|
||||
#ifdef SampleFormat
|
||||
#undef SampleFormat
|
||||
#endif
|
||||
|
||||
namespace FAV {
|
||||
|
||||
const qint64 kHz = 1000000LL;
|
||||
|
||||
typedef struct {
|
||||
AVSampleFormat avfmt;
|
||||
AudioFormat::SampleFormat fmt;
|
||||
const char* name;
|
||||
} sample_fmt_entry;
|
||||
static const sample_fmt_entry samplefmts[] = {
|
||||
{ AV_SAMPLE_FMT_U8, AudioFormat::SampleFormat_Unsigned8, "u8" },
|
||||
{ AV_SAMPLE_FMT_S16, AudioFormat::SampleFormat_Signed16, "s16" },
|
||||
{ AV_SAMPLE_FMT_S32, AudioFormat::SampleFormat_Signed32, "s32" },
|
||||
{ AV_SAMPLE_FMT_FLT, AudioFormat::SampleFormat_Float, "float" },
|
||||
{ AV_SAMPLE_FMT_DBL, AudioFormat::SampleFormat_Double, "double" },
|
||||
{ AV_SAMPLE_FMT_U8P, AudioFormat::SampleFormat_Unsigned8Planar, "u8p" },
|
||||
{ AV_SAMPLE_FMT_S16P, AudioFormat::SampleFormat_Signed16Planar, "s16p" },
|
||||
{ AV_SAMPLE_FMT_S32P, AudioFormat::SampleFormat_Signed32Planar, "s32p" },
|
||||
{ AV_SAMPLE_FMT_FLTP, AudioFormat::SampleFormat_FloatPlanar, "floatp" },
|
||||
{ AV_SAMPLE_FMT_DBLP, AudioFormat::SampleFormat_DoublePlanar, "doublep" },
|
||||
{ AV_SAMPLE_FMT_NONE, AudioFormat::SampleFormat_Unknown, "unknown" }
|
||||
};
|
||||
|
||||
AudioFormat::SampleFormat AudioFormat::sampleFormatFromFFmpeg(int fffmt)
|
||||
{
|
||||
for (int i = 0; samplefmts[i].fmt != AudioFormat::SampleFormat_Unknown; ++i) {
|
||||
if ((int)samplefmts[i].avfmt == fffmt)
|
||||
return samplefmts[i].fmt;
|
||||
}
|
||||
return AudioFormat::SampleFormat_Unknown;
|
||||
}
|
||||
|
||||
int AudioFormat::sampleFormatToFFmpeg(AudioFormat::SampleFormat fmt)
|
||||
{
|
||||
for (int i = 0; samplefmts[i].fmt != AudioFormat::SampleFormat_Unknown; ++i) {
|
||||
if (samplefmts[i].fmt == fmt)
|
||||
return (int)samplefmts[i].avfmt;
|
||||
}
|
||||
return (int)AV_SAMPLE_FMT_NONE;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
qint64 ff;
|
||||
AudioFormat::ChannelLayout cl;
|
||||
} ChannelLayoutMap;
|
||||
|
||||
static const ChannelLayoutMap kChannelLayoutMap[] = {
|
||||
{ AV_CH_FRONT_LEFT, AudioFormat::ChannelLayout_Left },
|
||||
{ AV_CH_FRONT_RIGHT, AudioFormat::ChannelLayout_Right },
|
||||
{ AV_CH_FRONT_CENTER, AudioFormat::ChannelLayout_Center },
|
||||
{ AV_CH_LAYOUT_MONO, AudioFormat::ChannelLayout_Mono },
|
||||
{ AV_CH_LAYOUT_STEREO, AudioFormat::ChannelLayout_Stereo },
|
||||
{ 0, AudioFormat::ChannelLayout_Unsupported}
|
||||
};
|
||||
|
||||
AudioFormat::ChannelLayout AudioFormat::channelLayoutFromFFmpeg(qint64 clff)
|
||||
{
|
||||
for (unsigned int i = 0; i < sizeof(kChannelLayoutMap)/sizeof(ChannelLayoutMap); ++i) {
|
||||
if (kChannelLayoutMap[i].ff == clff)
|
||||
return kChannelLayoutMap[i].cl;
|
||||
}
|
||||
return AudioFormat::ChannelLayout_Unsupported;
|
||||
}
|
||||
|
||||
qint64 AudioFormat::channelLayoutToFFmpeg(AudioFormat::ChannelLayout cl)
|
||||
{
|
||||
for (unsigned int i = 0; i < sizeof(kChannelLayoutMap)/sizeof(ChannelLayoutMap); ++i) {
|
||||
if (kChannelLayoutMap[i].cl == cl)
|
||||
return kChannelLayoutMap[i].ff;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
class AudioFormatPrivate : public QSharedData
|
||||
{
|
||||
public:
|
||||
AudioFormatPrivate()
|
||||
: sample_fmt(AudioFormat::SampleFormat_Input)
|
||||
, av_sample_fmt(AV_SAMPLE_FMT_NONE)
|
||||
, channels(0)
|
||||
, sample_rate(0)
|
||||
, channel_layout(AudioFormat::ChannelLayout_Unsupported)
|
||||
, channel_layout_ff(0)
|
||||
{}
|
||||
void setChannels(int cs) {
|
||||
channels = cs;
|
||||
if (av_get_channel_layout_nb_channels(channel_layout_ff) != channels) {
|
||||
channel_layout_ff = av_get_default_channel_layout(channels);
|
||||
channel_layout = AudioFormat::channelLayoutFromFFmpeg(channel_layout_ff);
|
||||
}
|
||||
}
|
||||
void setChannelLayoutFF(qint64 clff) {
|
||||
channel_layout_ff = clff;
|
||||
if (av_get_channel_layout_nb_channels(channel_layout_ff) != channels) {
|
||||
channels = av_get_channel_layout_nb_channels(channel_layout_ff);
|
||||
}
|
||||
}
|
||||
|
||||
AudioFormat::SampleFormat sample_fmt;
|
||||
AVSampleFormat av_sample_fmt;
|
||||
int channels;
|
||||
int sample_rate;
|
||||
AudioFormat::ChannelLayout channel_layout;
|
||||
qint64 channel_layout_ff;
|
||||
};
|
||||
|
||||
AudioFormat::SampleFormat AudioFormat::make(int bytesPerSample, bool isFloat, bool isUnsigned, bool isPlanar)
|
||||
{
|
||||
int f = bytesPerSample;
|
||||
if (isFloat)
|
||||
f |= kFloat;
|
||||
if (isUnsigned)
|
||||
f |= kUnsigned;
|
||||
if (isPlanar)
|
||||
f |= kPlanar;
|
||||
return SampleFormat(f);
|
||||
}
|
||||
|
||||
AudioFormat::AudioFormat():
|
||||
d(new AudioFormatPrivate())
|
||||
{
|
||||
}
|
||||
|
||||
AudioFormat::AudioFormat(const AudioFormat &other):
|
||||
d(other.d)
|
||||
{
|
||||
}
|
||||
|
||||
AudioFormat::~AudioFormat()
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Assigns \a other to this AudioFormat implementation.
|
||||
*/
|
||||
AudioFormat& AudioFormat::operator=(const AudioFormat &other)
|
||||
{
|
||||
d = other.d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns true if this AudioFormat is equal to the \a other
|
||||
AudioFormat; otherwise returns false.
|
||||
|
||||
All elements of AudioFormat are used for the comparison.
|
||||
*/
|
||||
bool AudioFormat::operator==(const AudioFormat &other) const
|
||||
{
|
||||
return d->sample_rate == other.d->sample_rate &&
|
||||
//compare channel layout first because it determines channel count
|
||||
d->channel_layout_ff == other.d->channel_layout_ff &&
|
||||
d->channel_layout == other.d->channel_layout &&
|
||||
d->channels == other.d->channels &&
|
||||
d->sample_fmt == other.d->sample_fmt;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns true if this AudioFormat is not equal to the \a other
|
||||
AudioFormat; otherwise returns false.
|
||||
|
||||
All elements of AudioFormat are used for the comparison.
|
||||
*/
|
||||
bool AudioFormat::operator!=(const AudioFormat& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns true if all of the parameters are valid->
|
||||
*/
|
||||
bool AudioFormat::isValid() const
|
||||
{
|
||||
return d->sample_rate > 0 && (d->channels > 0 || d->channel_layout > 0) &&
|
||||
d->sample_fmt != AudioFormat::SampleFormat_Unknown;
|
||||
}
|
||||
|
||||
bool AudioFormat::isFloat() const
|
||||
{
|
||||
return d->sample_fmt & kFloat;
|
||||
}
|
||||
|
||||
bool AudioFormat::isUnsigned() const
|
||||
{
|
||||
return IsUnsigned(d->sample_fmt);
|
||||
}
|
||||
|
||||
bool AudioFormat::isPlanar() const
|
||||
{
|
||||
return IsPlanar(d->sample_fmt);
|
||||
}
|
||||
|
||||
int AudioFormat::planeCount() const
|
||||
{
|
||||
return isPlanar() ? channels() : 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the sample rate to \a samplerate Hertz.
|
||||
|
||||
*/
|
||||
void AudioFormat::setSampleRate(int sampleRate)
|
||||
{
|
||||
d->sample_rate = sampleRate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current sample rate in Hertz.
|
||||
|
||||
*/
|
||||
int AudioFormat::sampleRate() const
|
||||
{
|
||||
return d->sample_rate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the channel layout to \a layout. Currently use FFmpeg's. see avutil/channel_layout.h
|
||||
*/
|
||||
void AudioFormat::setChannelLayoutFFmpeg(qint64 layout)
|
||||
{
|
||||
//FFmpeg channel layout is more complete, so we just it
|
||||
d->channel_layout = channelLayoutFromFFmpeg(layout);
|
||||
d->setChannelLayoutFF(layout);
|
||||
}
|
||||
|
||||
qint64 AudioFormat::channelLayoutFFmpeg() const
|
||||
{
|
||||
return d->channel_layout_ff;
|
||||
}
|
||||
|
||||
void AudioFormat::setChannelLayout(ChannelLayout layout)
|
||||
{
|
||||
qint64 clff = channelLayoutToFFmpeg(layout);
|
||||
d->channel_layout = layout;
|
||||
//TODO: shall we set ffmpeg channel layout to 0(not valid value)?
|
||||
if (!clff)
|
||||
return;
|
||||
d->setChannelLayoutFF(clff);
|
||||
}
|
||||
|
||||
AudioFormat::ChannelLayout AudioFormat::channelLayout() const
|
||||
{
|
||||
return d->channel_layout;
|
||||
}
|
||||
|
||||
QString AudioFormat::channelLayoutName() const
|
||||
{
|
||||
char cl[128];
|
||||
av_get_channel_layout_string(cl, sizeof(cl), -1, channelLayoutFFmpeg()); //TODO: ff version
|
||||
return QLatin1String(cl);
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the channel count to \a channels.
|
||||
|
||||
*/
|
||||
void AudioFormat::setChannels(int channels)
|
||||
{
|
||||
d->setChannels(channels);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current channel count value.
|
||||
|
||||
*/
|
||||
int AudioFormat::channels() const
|
||||
{
|
||||
return d->channels;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the sampleFormat to \a sampleFormat.
|
||||
*/
|
||||
void AudioFormat::setSampleFormat(AudioFormat::SampleFormat sampleFormat)
|
||||
{
|
||||
d->sample_fmt = sampleFormat;
|
||||
d->av_sample_fmt = (AVSampleFormat)AudioFormat::sampleFormatToFFmpeg(sampleFormat);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current SampleFormat value.
|
||||
*/
|
||||
AudioFormat::SampleFormat AudioFormat::sampleFormat() const
|
||||
{
|
||||
return d->sample_fmt;
|
||||
}
|
||||
|
||||
void AudioFormat::setSampleFormatFFmpeg(int ffSampleFormat)
|
||||
{
|
||||
d->sample_fmt = AudioFormat::sampleFormatFromFFmpeg(ffSampleFormat);
|
||||
d->av_sample_fmt = (AVSampleFormat)ffSampleFormat;
|
||||
}
|
||||
|
||||
int AudioFormat::sampleFormatFFmpeg() const
|
||||
{
|
||||
return d->av_sample_fmt;
|
||||
}
|
||||
|
||||
QString AudioFormat::sampleFormatName() const
|
||||
{
|
||||
if (d->av_sample_fmt == AV_SAMPLE_FMT_NONE) {
|
||||
for (int i = 0; samplefmts[i].fmt != SampleFormat_Unknown; ++i) {
|
||||
if (samplefmts[i].fmt == d->sample_fmt)
|
||||
return QLatin1String(samplefmts[i].name);
|
||||
}
|
||||
}
|
||||
return QLatin1String(av_get_sample_fmt_name((AVSampleFormat)sampleFormatFFmpeg()));
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Returns the number of bytes required for this audio format for \a duration microseconds.
|
||||
|
||||
Returns 0 if this format is not valid->
|
||||
|
||||
Note that some rounding may occur if \a duration is not an exact fraction of the
|
||||
sampleRate().
|
||||
|
||||
\sa durationForBytes()
|
||||
*/
|
||||
qint32 AudioFormat::bytesForDuration(qint64 duration) const
|
||||
{
|
||||
return bytesPerFrame() * framesForDuration(duration);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the number of microseconds represented by \a bytes in this format.
|
||||
|
||||
Returns 0 if this format is not valid->
|
||||
|
||||
Note that some rounding may occur if \a bytes is not an exact multiple
|
||||
of the number of bytes per frame.
|
||||
|
||||
\sa bytesForDuration()
|
||||
*/
|
||||
qint64 AudioFormat::durationForBytes(qint32 bytes) const
|
||||
{
|
||||
if (!isValid() || bytes <= 0)
|
||||
return 0;
|
||||
|
||||
// We round the byte count to ensure whole frames
|
||||
return qint64(kHz * (bytes / bytesPerFrame())) / sampleRate();
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the number of bytes required for \a frameCount frames of this format.
|
||||
|
||||
Returns 0 if this format is not valid->
|
||||
|
||||
\sa bytesForDuration()
|
||||
*/
|
||||
qint32 AudioFormat::bytesForFrames(qint32 frameCount) const
|
||||
{
|
||||
return frameCount * bytesPerFrame();
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the number of frames represented by \a byteCount in this format.
|
||||
|
||||
Note that some rounding may occur if \a byteCount is not an exact multiple
|
||||
of the number of bytes per frame.
|
||||
|
||||
Each frame has one sample per channel.
|
||||
|
||||
\sa framesForDuration()
|
||||
*/
|
||||
qint32 AudioFormat::framesForBytes(qint32 byteCount) const
|
||||
{
|
||||
int size = bytesPerFrame();
|
||||
if (size > 0)
|
||||
return byteCount / size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the number of frames required to represent \a duration microseconds in this format.
|
||||
|
||||
Note that some rounding may occur if \a duration is not an exact fraction of the
|
||||
\l sampleRate().
|
||||
*/
|
||||
qint32 AudioFormat::framesForDuration(qint64 duration) const
|
||||
{
|
||||
if (!isValid())
|
||||
return 0;
|
||||
|
||||
return qint32((duration * sampleRate()) / kHz);
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the number of microseconds represented by \a frameCount frames in this format.
|
||||
*/
|
||||
qint64 AudioFormat::durationForFrames(qint32 frameCount) const
|
||||
{
|
||||
if (!isValid() || frameCount <= 0)
|
||||
return 0;
|
||||
|
||||
return (frameCount * kHz) / sampleRate();
|
||||
}
|
||||
|
||||
int AudioFormat::bytesPerFrame() const
|
||||
{
|
||||
if (!isValid())
|
||||
return 0;
|
||||
|
||||
return bytesPerSample() * channels();
|
||||
}
|
||||
|
||||
// kSize: assume 12 bytes(long double) at most
|
||||
int AudioFormat::bytesPerSample() const
|
||||
{
|
||||
return d->sample_fmt & ((1<<(kSize+1)) - 1);
|
||||
}
|
||||
|
||||
int AudioFormat::sampleSize() const
|
||||
{
|
||||
return bytesPerSample();
|
||||
}
|
||||
|
||||
int AudioFormat::bitRate() const
|
||||
{
|
||||
return bytesPerSecond() << 3;
|
||||
}
|
||||
|
||||
int AudioFormat::bytesPerSecond() const
|
||||
{
|
||||
return channels() * bytesPerSample() * sampleRate();
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
QDebug operator<<(QDebug dbg, const FAV::AudioFormat &fmt)
|
||||
{
|
||||
dbg.nospace() << "FAV::AudioFormat(" << fmt.sampleRate();
|
||||
dbg.nospace() << "Hz, " << fmt.bytesPerSample();
|
||||
dbg.nospace() << "Bytes, channelCount:" << fmt.channels();
|
||||
dbg.nospace() << ", channelLayout: " << fmt.channelLayoutName();
|
||||
dbg.nospace() << ", sampleFormat: " << fmt.sampleFormatName();
|
||||
dbg.nospace() << ")";
|
||||
|
||||
return dbg.space();
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug dbg, FAV::AudioFormat::SampleFormat sampleFormat)
|
||||
{
|
||||
dbg.nospace() << av_get_sample_fmt_name((AVSampleFormat)AudioFormat::sampleFormatToFFmpeg(sampleFormat));
|
||||
return dbg.space();
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug dbg, FAV::AudioFormat::ChannelLayout channelLayout)
|
||||
{
|
||||
char cl[128];
|
||||
av_get_channel_layout_string(cl, sizeof(cl), -1, AudioFormat::channelLayoutToFFmpeg(channelLayout)); //TODO: ff version
|
||||
dbg.nospace() << cl;
|
||||
return dbg.space();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
namespace {
|
||||
class AudioFormatPrivateRegisterMetaTypes
|
||||
{
|
||||
public:
|
||||
AudioFormatPrivateRegisterMetaTypes()
|
||||
{
|
||||
qRegisterMetaType<FAV::AudioFormat>();
|
||||
qRegisterMetaType<FAV::AudioFormat::SampleFormat>();
|
||||
qRegisterMetaType<FAV::AudioFormat::ChannelLayout>();
|
||||
}
|
||||
} _registerMetaTypes;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
170
project/fm_viewer/fav/AudioFormat.h
Normal file
170
project/fm_viewer/fav/AudioFormat.h
Normal file
@@ -0,0 +1,170 @@
|
||||
/******************************************************************************
|
||||
QtAV: Media play library based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AUDIOFORMAT_H
|
||||
#define QTAV_AUDIOFORMAT_H
|
||||
|
||||
#include <QtCore/QSharedDataPointer>
|
||||
#include <QtCore/QString>
|
||||
#include "_fav_constants.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QDebug;
|
||||
QT_END_NAMESPACE
|
||||
namespace FAV {
|
||||
|
||||
class AudioFormatPrivate;
|
||||
class Q_AV_EXPORT AudioFormat
|
||||
{
|
||||
enum { kSize = 12, kFloat = 1<<(kSize+1), kUnsigned = 1<<(kSize+2), kPlanar = 1<<(kSize+3), kByteOrder = 1<<(kSize+4) };
|
||||
public:
|
||||
/*!
|
||||
* \brief The SampleFormat enum
|
||||
* s8, u16, u24, s24, u32 are not listed in ffmpeg sample format and have not planar format.
|
||||
* pcm_s24le will be decoded as s32-24bit in ffmpeg, it's encoded as 32 bits, but raw sample has 24 bits
|
||||
*/
|
||||
enum SampleFormat {
|
||||
SampleFormat_Unknown = 0,
|
||||
SampleFormat_Input = SampleFormat_Unknown,
|
||||
SampleFormat_Unsigned8 = 1 | kUnsigned,
|
||||
SampleFormat_Signed8 = 1,
|
||||
SampleFormat_Unigned16 = 2 | kUnsigned,
|
||||
SampleFormat_Signed16 = 2,
|
||||
SampleFormat_Unsigned24 = 3 | kUnsigned,
|
||||
SampleFormat_Signed24 = 3,
|
||||
SampleFormat_Unsigned32 = 4 | kUnsigned,
|
||||
SampleFormat_Signed32 = 4,
|
||||
SampleFormat_Float = 4 | kFloat,
|
||||
SampleFormat_Double = 8 | kFloat,
|
||||
SampleFormat_Unsigned8Planar = SampleFormat_Unsigned8 | kPlanar,
|
||||
SampleFormat_Signed16Planar = SampleFormat_Signed16 | kPlanar,
|
||||
SampleFormat_Signed32Planar = SampleFormat_Signed32 | kPlanar,
|
||||
SampleFormat_FloatPlanar = SampleFormat_Float | kPlanar,
|
||||
SampleFormat_DoublePlanar = SampleFormat_Double | kPlanar
|
||||
};
|
||||
enum ChannelLayout {
|
||||
ChannelLayout_Left,
|
||||
ChannelLayout_Right,
|
||||
ChannelLayout_Center,
|
||||
ChannelLayout_Mono = ChannelLayout_Center,
|
||||
ChannelLayout_Stereo,
|
||||
ChannelLayout_Unsupported //ok. now it's not complete
|
||||
};
|
||||
//typedef qint64 ChannelLayout; //currently use latest FFmpeg's
|
||||
// TODO: constexpr
|
||||
friend int RawSampleSize(SampleFormat fmt) { return fmt & ((1<<(kSize+1)) - 1); }
|
||||
friend bool IsFloat(SampleFormat fmt) { return !!(fmt & kFloat);}
|
||||
friend bool IsPlanar(SampleFormat format) { return !!(format & kPlanar);}
|
||||
friend bool IsUnsigned(SampleFormat format) {return !!(format & kUnsigned);}
|
||||
friend SampleFormat ToPlanar(SampleFormat fmt) { return IsPlanar(fmt) ? fmt : SampleFormat((int)fmt|kPlanar);}
|
||||
friend SampleFormat ToPacked(SampleFormat fmt) { return IsPlanar(fmt) ? SampleFormat((int)fmt^kPlanar) : fmt;}
|
||||
|
||||
static ChannelLayout channelLayoutFromFFmpeg(qint64 clff);
|
||||
static qint64 channelLayoutToFFmpeg(ChannelLayout cl);
|
||||
static SampleFormat sampleFormatFromFFmpeg(int fffmt);
|
||||
static int sampleFormatToFFmpeg(SampleFormat fmt);
|
||||
static SampleFormat make(int bytesPerSample, bool isFloat, bool isUnsigned, bool isPlanar);
|
||||
AudioFormat();
|
||||
AudioFormat(const AudioFormat &other);
|
||||
~AudioFormat();
|
||||
|
||||
AudioFormat& operator=(const AudioFormat &other);
|
||||
bool operator==(const AudioFormat &other) const;
|
||||
bool operator!=(const AudioFormat &other) const;
|
||||
|
||||
bool isValid() const;
|
||||
bool isFloat() const;
|
||||
bool isUnsigned() const;
|
||||
bool isPlanar() const;
|
||||
int planeCount() const;
|
||||
|
||||
void setSampleRate(int sampleRate);
|
||||
int sampleRate() const;
|
||||
|
||||
/*!
|
||||
* setChannelLayout and setChannelLayoutFFmpeg also sets the correct channels if channels does not match.
|
||||
*/
|
||||
void setChannelLayoutFFmpeg(qint64 layout);
|
||||
qint64 channelLayoutFFmpeg() const;
|
||||
//currently a limitted set of channel layout is supported. call setChannelLayoutFFmpeg is recommended
|
||||
void setChannelLayout(ChannelLayout layout);
|
||||
ChannelLayout channelLayout() const;
|
||||
QString channelLayoutName() const;
|
||||
/*!
|
||||
* setChannels also sets the default layout for this channels if channels does not match.
|
||||
*/
|
||||
void setChannels(int channels);
|
||||
/*!
|
||||
* \brief channels
|
||||
* For planar format, channel count == plane count. For packed format, plane count is 1
|
||||
* \return
|
||||
*/
|
||||
int channels() const;
|
||||
|
||||
void setSampleFormat(SampleFormat sampleFormat);
|
||||
SampleFormat sampleFormat() const;
|
||||
void setSampleFormatFFmpeg(int ffSampleFormat);
|
||||
int sampleFormatFFmpeg() const;
|
||||
QString sampleFormatName() const;
|
||||
|
||||
// Helper functions
|
||||
// in microseconds
|
||||
qint32 bytesForDuration(qint64 duration) const;
|
||||
qint64 durationForBytes(qint32 byteCount) const;
|
||||
|
||||
qint32 bytesForFrames(qint32 frameCount) const;
|
||||
qint32 framesForBytes(qint32 byteCount) const;
|
||||
|
||||
// in microseconds
|
||||
qint32 framesForDuration(qint64 duration) const;
|
||||
qint64 durationForFrames(qint32 frameCount) const;
|
||||
|
||||
// 1 frame = 1 sample with channels
|
||||
/*!
|
||||
Returns the number of bytes required to represent one frame (a sample in each channel) in this format.
|
||||
Returns 0 if this format is invalid.
|
||||
*/
|
||||
int bytesPerFrame() const;
|
||||
/*!
|
||||
Returns the current sample size value, in bytes.
|
||||
\sa bytesPerFrame()
|
||||
*/
|
||||
int bytesPerSample() const;
|
||||
int sampleSize() const; // the same as bytesPerSample()
|
||||
int bitRate() const; //bits per second
|
||||
int bytesPerSecond() const;
|
||||
private:
|
||||
QSharedDataPointer<AudioFormatPrivate> d;
|
||||
};
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
Q_AV_EXPORT QDebug operator<<(QDebug debug, const AudioFormat &fmt);
|
||||
Q_AV_EXPORT QDebug operator<<(QDebug debug, AudioFormat::SampleFormat sampleFormat);
|
||||
Q_AV_EXPORT QDebug operator<<(QDebug debug, AudioFormat::ChannelLayout channelLayout);
|
||||
#endif
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
Q_DECLARE_METATYPE(FAV::AudioFormat)
|
||||
Q_DECLARE_METATYPE(FAV::AudioFormat::SampleFormat)
|
||||
Q_DECLARE_METATYPE(FAV::AudioFormat::ChannelLayout)
|
||||
|
||||
#endif // QTAV_AUDIOFORMAT_H
|
||||
238
project/fm_viewer/fav/AudioFrame.cpp
Normal file
238
project/fm_viewer/fav/AudioFrame.cpp
Normal file
@@ -0,0 +1,238 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AudioFrame.h"
|
||||
#include "Frame_p.h"
|
||||
#include "AudioResampler.h"
|
||||
#include "AVCompat.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
namespace{
|
||||
static const struct RegisterMetaTypes
|
||||
{
|
||||
inline RegisterMetaTypes() {
|
||||
qRegisterMetaType<FAV::AudioFrame>("FAV::AudioFrame");
|
||||
}
|
||||
} _registerMetaTypes;
|
||||
}
|
||||
|
||||
class AudioFramePrivate : public FramePrivate
|
||||
{
|
||||
public:
|
||||
AudioFramePrivate(const AudioFormat& fmt)
|
||||
: FramePrivate()
|
||||
, format(fmt)
|
||||
, samples_per_ch(0)
|
||||
, conv(0)
|
||||
{
|
||||
if (!format.isValid())
|
||||
return;
|
||||
const int nb_planes(format.planeCount());
|
||||
planes.reserve(nb_planes);
|
||||
planes.resize(nb_planes);
|
||||
line_sizes.reserve(nb_planes);
|
||||
line_sizes.resize(nb_planes);
|
||||
}
|
||||
|
||||
AudioFormat format;
|
||||
int samples_per_ch;
|
||||
AudioResampler *conv;
|
||||
};
|
||||
|
||||
/*!
|
||||
Constructs a shallow copy of \a other. Since AudioFrame is
|
||||
explicitly shared, these two instances will reflect the same frame.
|
||||
|
||||
*/
|
||||
AudioFrame::AudioFrame(const AudioFrame &other)
|
||||
: Frame(other)
|
||||
{
|
||||
}
|
||||
|
||||
AudioFrame::AudioFrame(const AudioFormat &format, const QByteArray& data)
|
||||
: Frame(new AudioFramePrivate(format))
|
||||
{
|
||||
if (data.isEmpty())
|
||||
return;
|
||||
Q_D(AudioFrame);
|
||||
d->format = format;
|
||||
d->data = data;
|
||||
if (!d->format.isValid())
|
||||
return;
|
||||
if (d->data.isEmpty())
|
||||
return;
|
||||
d->samples_per_ch = data.size() / d->format.channels() / d->format.bytesPerSample();
|
||||
const int nb_planes(d->format.planeCount());
|
||||
const int bpl(d->data.size()/nb_planes);
|
||||
for (int i = 0; i < nb_planes; ++i) {
|
||||
setBytesPerLine(bpl, i);
|
||||
setBits((uchar*)d->data.constData() + i*bpl, i);
|
||||
}
|
||||
//init();
|
||||
}
|
||||
|
||||
/*!
|
||||
Assigns the contents of \a other to this video frame. Since AudioFrame is
|
||||
explicitly shared, these two instances will reflect the same frame.
|
||||
|
||||
*/
|
||||
AudioFrame &AudioFrame::operator =(const AudioFrame &other)
|
||||
{
|
||||
d_ptr = other.d_ptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AudioFrame::~AudioFrame()
|
||||
{
|
||||
}
|
||||
|
||||
bool AudioFrame::isValid() const
|
||||
{
|
||||
Q_D(const AudioFrame);
|
||||
return d->samples_per_ch > 0 && d->format.isValid();
|
||||
}
|
||||
|
||||
QByteArray AudioFrame::data()
|
||||
{
|
||||
if (!isValid())
|
||||
return QByteArray();
|
||||
Q_D(AudioFrame);
|
||||
if (d->data.isEmpty()) {
|
||||
AudioFrame a(clone());
|
||||
d->data = a.data();
|
||||
}
|
||||
return d->data;
|
||||
}
|
||||
|
||||
int AudioFrame::channelCount() const
|
||||
{
|
||||
Q_D(const AudioFrame);
|
||||
if (!d->format.isValid())
|
||||
return 0;
|
||||
return d->format.channels();
|
||||
}
|
||||
|
||||
AudioFrame AudioFrame::clone() const
|
||||
{
|
||||
Q_D(const AudioFrame);
|
||||
if (d->format.sampleFormatFFmpeg() == AV_SAMPLE_FMT_NONE
|
||||
|| d->format.channels() <= 0)
|
||||
return AudioFrame();
|
||||
if (d->samples_per_ch <= 0 || bytesPerLine(0) <= 0)
|
||||
return AudioFrame(format());
|
||||
QByteArray buf(bytesPerLine()*planeCount(), 0);
|
||||
char *dst = buf.data(); //must before buf is shared, otherwise data will be detached.
|
||||
for (int i = 0; i < planeCount(); ++i) {
|
||||
const int plane_size = bytesPerLine(i);
|
||||
memcpy(dst, constBits(i), plane_size);
|
||||
dst += plane_size;
|
||||
}
|
||||
AudioFrame f(d->format, buf);
|
||||
f.setSamplesPerChannel(samplesPerChannel());
|
||||
f.setPTS(pts()); // timestamp()
|
||||
f.setDuration(duration());
|
||||
// meta data?
|
||||
return f;
|
||||
}
|
||||
|
||||
AudioFormat AudioFrame::format() const
|
||||
{
|
||||
return d_func()->format;
|
||||
}
|
||||
|
||||
void AudioFrame::setSamplesPerChannel(int samples)
|
||||
{
|
||||
Q_D(AudioFrame);
|
||||
if (!d->format.isValid()) {
|
||||
qWarning() << "can not set spc for an invalid format: " << d->format;
|
||||
return;
|
||||
}
|
||||
d->samples_per_ch = samples;
|
||||
const int nb_planes = d->format.planeCount();
|
||||
const int bpl(d->line_sizes[0] > 0 ? d->line_sizes[0] : d->samples_per_ch*d->format.bytesPerSample() * (d->format.isPlanar() ? 1 : d->format.channels()));
|
||||
for (int i = 0; i < nb_planes; ++i) {
|
||||
setBytesPerLine(bpl, i);
|
||||
}
|
||||
if (d->data.isEmpty())
|
||||
return;
|
||||
if (!constBits(0)) {
|
||||
setBits((quint8*)d->data.constData(), 0);
|
||||
}
|
||||
for (int i = 1; i < nb_planes; ++i) {
|
||||
if (!constBits(i)) {
|
||||
setBits((uchar*)constBits(i-1) + bpl, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int AudioFrame::samplesPerChannel() const
|
||||
{
|
||||
return d_func()->samples_per_ch;
|
||||
}
|
||||
|
||||
void AudioFrame::setAudioResampler(AudioResampler *conv)
|
||||
{
|
||||
d_func()->conv = conv;
|
||||
}
|
||||
|
||||
qint64 AudioFrame::duration() const
|
||||
{
|
||||
Q_D(const AudioFrame);
|
||||
return d->format.durationForBytes(d->data.size());
|
||||
}
|
||||
|
||||
AudioFrame AudioFrame::to(const AudioFormat &fmt) const
|
||||
{
|
||||
if (!isValid() || !constBits(0))
|
||||
return AudioFrame();
|
||||
//if (fmt == format())
|
||||
// return clone(); //FIXME: clone a frame from ffmpeg is not enough?
|
||||
Q_D(const AudioFrame);
|
||||
// TODO: use a pool
|
||||
AudioResampler *conv = d->conv;
|
||||
QScopedPointer<AudioResampler> c;
|
||||
if (!conv) {
|
||||
conv = AudioResampler::create(AudioResamplerId_FF);
|
||||
if (!conv)
|
||||
conv = AudioResampler::create(AudioResamplerId_Libav);
|
||||
if (!conv) {
|
||||
qWarning("no audio resampler is available");
|
||||
return AudioFrame();
|
||||
}
|
||||
c.reset(conv);
|
||||
}
|
||||
conv->setInAudioFormat(format());
|
||||
conv->setOutAudioFormat(fmt);
|
||||
//conv->prepare(); // already called in setIn/OutFormat
|
||||
conv->setInSampesPerChannel(samplesPerChannel()); //TODO
|
||||
if (!conv->convert((const quint8**)d->planes.constData())) {
|
||||
qWarning() << "AudioFrame::to error: " << format() << "=>" << fmt;
|
||||
return AudioFrame();
|
||||
}
|
||||
AudioFrame f(fmt, conv->outData());
|
||||
f.setSamplesPerChannel(conv->outSamplesPerChannel());
|
||||
f.setPTS(pts()); // (timestamp()
|
||||
f.setDuration(duration());
|
||||
f.d_ptr->metadata = d->metadata; // need metadata?
|
||||
return f;
|
||||
}
|
||||
} //namespace FAV
|
||||
77
project/fm_viewer/fav/AudioFrame.h
Normal file
77
project/fm_viewer/fav/AudioFrame.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AUDIOFRAME_H
|
||||
#define QTAV_AUDIOFRAME_H
|
||||
|
||||
#include "Frame.h"
|
||||
#include "AudioFormat.h"
|
||||
|
||||
namespace FAV {
|
||||
class AudioResampler;
|
||||
class AudioFramePrivate;
|
||||
class Q_AV_EXPORT AudioFrame : public Frame
|
||||
{
|
||||
Q_DECLARE_PRIVATE(AudioFrame)
|
||||
public:
|
||||
//data must be complete
|
||||
/*!
|
||||
* \brief AudioFrame
|
||||
* construct an audio frame from a given buffer and format
|
||||
*/
|
||||
AudioFrame(const AudioFormat& format = AudioFormat(), const QByteArray& data = QByteArray());
|
||||
AudioFrame(const AudioFrame &other);
|
||||
virtual ~AudioFrame();
|
||||
AudioFrame &operator =(const AudioFrame &other);
|
||||
|
||||
bool isValid() const;
|
||||
operator bool() const { return isValid();}
|
||||
|
||||
/*!
|
||||
* \brief data
|
||||
* Audio data. clone is called if frame is not constructed with a QByteArray.
|
||||
* \return
|
||||
*/
|
||||
QByteArray data();
|
||||
virtual int channelCount() const;
|
||||
/*!
|
||||
* Deep copy. If you want to copy data from somewhere, knowing the format, width and height,
|
||||
* then you can use clone().
|
||||
*/
|
||||
AudioFrame clone() const;
|
||||
AudioFormat format() const;
|
||||
void setSamplesPerChannel(int samples);
|
||||
// may change after resampling
|
||||
int samplesPerChannel() const;
|
||||
AudioFrame to(const AudioFormat& fmt) const;
|
||||
//AudioResamplerId
|
||||
void setAudioResampler(AudioResampler *conv); //TODO: remove
|
||||
/*!
|
||||
Returns the number of microseconds represented by \a bytes in this format.
|
||||
Returns 0 if this format is not valid.
|
||||
Note that some rounding may occur if \a bytes is not an exact multiple
|
||||
of the number of bytes per frame.
|
||||
*/
|
||||
qint64 duration() const;
|
||||
};
|
||||
} //namespace FAV
|
||||
Q_DECLARE_METATYPE(FAV::AudioFrame)
|
||||
#endif // QTAV_AUDIOFRAME_H
|
||||
847
project/fm_viewer/fav/AudioOutput.cpp
Normal file
847
project/fm_viewer/fav/AudioOutput.cpp
Normal file
@@ -0,0 +1,847 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AudioOutput.h"
|
||||
#include "AVOutput_p.h"
|
||||
#include "AudioOutputBackend.h"
|
||||
#include "AVCompat.h"
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#else
|
||||
#include <QtCore/QTime>
|
||||
typedef QTime QElapsedTimer;
|
||||
#endif
|
||||
#include "ring.h"
|
||||
#include "Logger.h"
|
||||
|
||||
#define AO_USE_TIMER 1
|
||||
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
#include <QElapsedTimer>
|
||||
QElapsedTimer gAOLoadingTimer;
|
||||
#endif
|
||||
|
||||
namespace FAV {
|
||||
|
||||
// chunk
|
||||
static const int kBufferSamples = 512;
|
||||
static const int kBufferCount = 8*2; // may wait too long at the beginning (oal) if too large. if buffer count is too small, can not play for high sample rate audio.
|
||||
|
||||
typedef void (*scale_samples_func)(quint8 *dst, const quint8 *src, int nb_samples, int volume, float volumef);
|
||||
|
||||
/// from libavfilter/af_volume begin
|
||||
static inline void scale_samples_u8(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
|
||||
{
|
||||
for (int i = 0; i < nb_samples; i++)
|
||||
dst[i] = av_clip_uint8(((((qint64)src[i] - 128) * volume + 128) >> 8) + 128);
|
||||
}
|
||||
|
||||
static inline void scale_samples_u8_small(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
|
||||
{
|
||||
for (int i = 0; i < nb_samples; i++)
|
||||
dst[i] = av_clip_uint8((((src[i] - 128) * volume + 128) >> 8) + 128);
|
||||
}
|
||||
|
||||
static inline void scale_samples_s16(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
|
||||
{
|
||||
int16_t *smp_dst = (int16_t *)dst;
|
||||
const int16_t *smp_src = (const int16_t *)src;
|
||||
for (int i = 0; i < nb_samples; i++)
|
||||
smp_dst[i] = av_clip_int16(((qint64)smp_src[i] * volume + 128) >> 8);
|
||||
}
|
||||
|
||||
static inline void scale_samples_s16_small(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
|
||||
{
|
||||
int16_t *smp_dst = (int16_t *)dst;
|
||||
const int16_t *smp_src = (const int16_t *)src;
|
||||
for (int i = 0; i < nb_samples; i++)
|
||||
smp_dst[i] = av_clip_int16((smp_src[i] * volume + 128) >> 8);
|
||||
}
|
||||
|
||||
static inline void scale_samples_s32(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
|
||||
{
|
||||
qint32 *smp_dst = (qint32 *)dst;
|
||||
const qint32 *smp_src = (const qint32 *)src;
|
||||
for (int i = 0; i < nb_samples; i++)
|
||||
smp_dst[i] = av_clipl_int32((((qint64)smp_src[i] * volume + 128) >> 8));
|
||||
}
|
||||
/// from libavfilter/af_volume end
|
||||
|
||||
//TODO: simd
|
||||
template<typename T>
|
||||
static inline void scale_samples(quint8 *dst, const quint8 *src, int nb_samples, int, float volume)
|
||||
{
|
||||
T *smp_dst = (T *)dst;
|
||||
const T *smp_src = (const T *)src;
|
||||
for (int i = 0; i < nb_samples; ++i)
|
||||
smp_dst[i] = smp_src[i] * (T)volume;
|
||||
}
|
||||
|
||||
scale_samples_func get_scaler(AudioFormat::SampleFormat fmt, qreal vol, int* voli)
|
||||
{
|
||||
int v = (int)(vol * 256.0 + 0.5);
|
||||
if (voli)
|
||||
*voli = v;
|
||||
switch (fmt) {
|
||||
case AudioFormat::SampleFormat_Unsigned8:
|
||||
case AudioFormat::SampleFormat_Unsigned8Planar:
|
||||
return v < 0x1000000 ? scale_samples_u8_small : scale_samples_u8;
|
||||
case AudioFormat::SampleFormat_Signed16:
|
||||
case AudioFormat::SampleFormat_Signed16Planar:
|
||||
return v < 0x10000 ? scale_samples_s16_small : scale_samples_s16;
|
||||
case AudioFormat::SampleFormat_Signed32:
|
||||
case AudioFormat::SampleFormat_Signed32Planar:
|
||||
return scale_samples_s32;
|
||||
case AudioFormat::SampleFormat_Float:
|
||||
case AudioFormat::SampleFormat_FloatPlanar:
|
||||
return scale_samples<float>;
|
||||
case AudioFormat::SampleFormat_Double:
|
||||
case AudioFormat::SampleFormat_DoublePlanar:
|
||||
return scale_samples<double>;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class AudioOutputPrivate : public AVOutputPrivate
|
||||
{
|
||||
public:
|
||||
AudioOutputPrivate():
|
||||
mute(false)
|
||||
, sw_volume(true)
|
||||
, sw_mute(true)
|
||||
, volume_i(256)
|
||||
, vol(1)
|
||||
, speed(1.0)
|
||||
, nb_buffers(kBufferCount)
|
||||
, buffer_samples(kBufferSamples)
|
||||
, features(0)
|
||||
, play_pos(0)
|
||||
, processed_remain(0)
|
||||
, msecs_ahead(0)
|
||||
, scale_samples(0)
|
||||
, backend(0)
|
||||
, update_backend(true)
|
||||
, index_enqueue(-1)
|
||||
, index_deuqueue(-1)
|
||||
, frame_infos(ring<FrameInfo>(nb_buffers))
|
||||
{
|
||||
available = false;
|
||||
}
|
||||
virtual ~AudioOutputPrivate();
|
||||
|
||||
void playInitialData(); //required by some backends, e.g. openal
|
||||
void onCallback() { cond.wakeAll();}
|
||||
virtual void uwait(qint64 us) {
|
||||
QMutexLocker lock(&mutex);
|
||||
Q_UNUSED(lock);
|
||||
cond.wait(&mutex, (us+500LL)/1000LL);
|
||||
}
|
||||
|
||||
struct FrameInfo {
|
||||
FrameInfo(const QByteArray& d = QByteArray(), qreal t = 0, int us = 0) : timestamp(t), duration(us), data(d) {}
|
||||
qreal timestamp;
|
||||
int duration; // in us
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
void resetStatus() {
|
||||
play_pos = 0;
|
||||
processed_remain = 0;
|
||||
msecs_ahead = 0;
|
||||
#if AO_USE_TIMER
|
||||
timer.invalidate();
|
||||
#endif
|
||||
frame_infos = ring<FrameInfo>(nb_buffers);
|
||||
}
|
||||
/// call this if sample format or volume is changed
|
||||
void updateSampleScaleFunc();
|
||||
void tryVolume(qreal value);
|
||||
void tryMute(bool value);
|
||||
|
||||
bool mute;
|
||||
bool sw_volume, sw_mute;
|
||||
int volume_i;
|
||||
qreal vol;
|
||||
qreal speed;
|
||||
AudioFormat format;
|
||||
AudioFormat requested;
|
||||
//AudioFrame audio_frame;
|
||||
quint32 nb_buffers;
|
||||
qint32 buffer_samples;
|
||||
int features;
|
||||
int play_pos; // index or bytes
|
||||
int processed_remain;
|
||||
int msecs_ahead;
|
||||
#if AO_USE_TIMER
|
||||
QElapsedTimer timer;
|
||||
#endif
|
||||
scale_samples_func scale_samples;
|
||||
AudioOutputBackend *backend;
|
||||
bool update_backend;
|
||||
QStringList backends;
|
||||
//private:
|
||||
// the index of current enqueue/dequeue
|
||||
int index_enqueue, index_deuqueue;
|
||||
ring<FrameInfo> frame_infos;
|
||||
};
|
||||
|
||||
void AudioOutputPrivate::updateSampleScaleFunc()
|
||||
{
|
||||
scale_samples = get_scaler(format.sampleFormat(), vol, &volume_i);
|
||||
}
|
||||
|
||||
AudioOutputPrivate::~AudioOutputPrivate()
|
||||
{
|
||||
if (backend) {
|
||||
backend->close();
|
||||
delete backend;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputPrivate::playInitialData()
|
||||
{
|
||||
const char c = (format.sampleFormat() == AudioFormat::SampleFormat_Unsigned8
|
||||
|| format.sampleFormat() == AudioFormat::SampleFormat_Unsigned8Planar)
|
||||
? 0x80 : 0;
|
||||
//qInfo() << "AO ELAPSED nb_buffers:" << nb_buffers;
|
||||
for (quint32 i = 0; i < nb_buffers; ++i)
|
||||
{
|
||||
const QByteArray data(backend->buffer_size, c);
|
||||
backend->write(data); // fill silence byte, not always 0. AudioFormat.silenceByte
|
||||
frame_infos.push_back(FrameInfo(data, 0, 0)); // initial data can be small (1 instead of buffer_samples)
|
||||
}
|
||||
//qInfo() << "AO ELAPSED END nb_buffers:" << nb_buffers;
|
||||
backend->play();
|
||||
}
|
||||
|
||||
void AudioOutputPrivate::tryVolume(qreal value)
|
||||
{
|
||||
// if not open, try later
|
||||
if (!available)
|
||||
return;
|
||||
if (features & AudioOutput::SetVolume) {
|
||||
sw_volume = !backend->setVolume(value);
|
||||
//if (!qFuzzyCompare(backend->volume(), value))
|
||||
// sw_volume = true;
|
||||
if (sw_volume)
|
||||
backend->setVolume(1.0); // TODO: partial software?
|
||||
} else {
|
||||
sw_volume = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputPrivate::tryMute(bool value)
|
||||
{
|
||||
// if not open, try later
|
||||
if (!available)
|
||||
return;
|
||||
if ((features & AudioOutput::SetMute) && backend)
|
||||
sw_mute = !backend->setMute(value);
|
||||
else
|
||||
sw_mute = true;
|
||||
}
|
||||
|
||||
AudioOutput::AudioOutput(QObject* parent)
|
||||
: QObject(parent)
|
||||
, AVOutput(*new AudioOutputPrivate())
|
||||
{
|
||||
#if !(OFF_OTHER_DEBUG)
|
||||
qDebug() << "Registered audio backends: " << AudioOutput::backendsAvailable(); // call this to register
|
||||
#endif
|
||||
setBackends(AudioOutputBackend::defaultPriority()); //ensure a backend is available
|
||||
}
|
||||
|
||||
AudioOutput::~AudioOutput()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
extern void AudioOutput_RegisterAll(); //why vc link error if put in the following a exported class member function?
|
||||
QStringList AudioOutput::backendsAvailable()
|
||||
{
|
||||
AudioOutput_RegisterAll();
|
||||
static QStringList all;
|
||||
if (!all.isEmpty())
|
||||
return all;
|
||||
AudioOutputBackendId* i = NULL;
|
||||
while ((i = AudioOutputBackend::next(i)) != NULL) {
|
||||
all.append(AudioOutputBackend::name(*i));
|
||||
}
|
||||
all = AudioOutputBackend::defaultPriority() << all;
|
||||
all.removeDuplicates();
|
||||
return all;
|
||||
}
|
||||
|
||||
void AudioOutput::setBackends(const QStringList &backendNames)
|
||||
{
|
||||
DPTR_D(AudioOutput);
|
||||
if (d.backends == backendNames)
|
||||
return;
|
||||
d.update_backend = true;
|
||||
d.backends = backendNames;
|
||||
// create backend here because we have to check format support before open which needs a backend
|
||||
d.update_backend = false;
|
||||
if (d.backend) {
|
||||
d.backend->close();
|
||||
delete d.backend;
|
||||
d.backend = 0;
|
||||
}
|
||||
// TODO: empty backends use dummy backend
|
||||
if (!d.backends.isEmpty()) {
|
||||
foreach (const QString& b, d.backends) {
|
||||
d.backend = AudioOutputBackend::create(b.toLatin1().constData());
|
||||
if (!d.backend)
|
||||
continue;
|
||||
if (d.backend->available)
|
||||
break;
|
||||
delete d.backend;
|
||||
d.backend = NULL;
|
||||
}
|
||||
}
|
||||
if (d.backend) {
|
||||
// default: set all features when backend is ready
|
||||
setDeviceFeatures(d.backend->supportedFeatures());
|
||||
// connect volumeReported
|
||||
connect(d.backend, SIGNAL(volumeReported(qreal)), SLOT(reportVolume(qreal)));
|
||||
connect(d.backend, SIGNAL(muteReported(bool)), SLOT(reportMute(bool)));
|
||||
}
|
||||
|
||||
Q_EMIT backendsChanged();
|
||||
}
|
||||
|
||||
QStringList AudioOutput::backends() const
|
||||
{
|
||||
return d_func().backends;
|
||||
}
|
||||
|
||||
QString AudioOutput::backend() const
|
||||
{
|
||||
DPTR_D(const AudioOutput);
|
||||
if (d.backend)
|
||||
return d.backend->name();
|
||||
return QString();
|
||||
}
|
||||
QString AudioOutput::status() const
|
||||
{
|
||||
DPTR_D(const AudioOutput);
|
||||
if (d.backend)
|
||||
return d.backend->status();
|
||||
return QString();
|
||||
}
|
||||
|
||||
void AudioOutput::flush()
|
||||
{
|
||||
DPTR_D(AudioOutput);
|
||||
while (!d.frame_infos.empty()) {
|
||||
if (d.backend)
|
||||
d.backend->flush();
|
||||
waitForNextBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutput::clear()
|
||||
{
|
||||
DPTR_D(AudioOutput);
|
||||
if (!d.backend || !d.backend->clear())
|
||||
flush();
|
||||
d.resetStatus();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool AudioOutput::open()
|
||||
{
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
gAOLoadingTimer.start();
|
||||
#endif
|
||||
|
||||
DPTR_D(AudioOutput);
|
||||
QMutexLocker lock(&d.mutex);
|
||||
Q_UNUSED(lock);
|
||||
d.available = false;
|
||||
d.paused = false;
|
||||
d.resetStatus();
|
||||
if (!d.backend)
|
||||
return false;
|
||||
d.backend->audio = this;
|
||||
d.backend->buffer_size = bufferSize();
|
||||
d.backend->buffer_count = bufferCount();
|
||||
d.backend->format = audioFormat();
|
||||
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << "AO ELAPSED:" << gAOLoadingTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
|
||||
// TODO: open next backend if fail and emit backendChanged()
|
||||
if (!d.backend->open())
|
||||
return false;
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << "AO ELAPSED:" << gAOLoadingTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
d.available = true;
|
||||
d.tryVolume(volume());
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << "AO ELAPSED:" << gAOLoadingTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
d.tryMute(isMute());
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << "AO ELAPSED:" << gAOLoadingTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
#if !defined(_WIN64)
|
||||
d.playInitialData(); // ?????
|
||||
#endif
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
qInfo() << "AO ELAPSED:" << gAOLoadingTimer.elapsed() << __FUNCTION__ << __LINE__;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutput::close()
|
||||
{
|
||||
DPTR_D(AudioOutput);
|
||||
QMutexLocker lock(&d.mutex);
|
||||
Q_UNUSED(lock);
|
||||
d.available = false;
|
||||
d.paused = false;
|
||||
d.resetStatus();
|
||||
if (!d.backend)
|
||||
return false;
|
||||
// TODO: drain() before close
|
||||
d.backend->audio = 0;
|
||||
return d.backend->close();
|
||||
}
|
||||
|
||||
bool AudioOutput::isOpen() const
|
||||
{
|
||||
return d_func().available;
|
||||
}
|
||||
|
||||
bool AudioOutput::play(const QByteArray &data, qreal pts)
|
||||
{
|
||||
DPTR_D(AudioOutput);
|
||||
if (!d.backend)
|
||||
return false;
|
||||
if (!receiveData(data, pts))
|
||||
return false;
|
||||
return d.backend->play();
|
||||
}
|
||||
|
||||
void AudioOutput::pause(bool value)
|
||||
{
|
||||
DPTR_D(AudioOutput);
|
||||
d.paused = value;
|
||||
// backend pause? Without backend pause, the buffered data will be played
|
||||
}
|
||||
|
||||
bool AudioOutput::isPaused() const
|
||||
{
|
||||
return d_func().paused;
|
||||
}
|
||||
|
||||
bool AudioOutput::receiveData(const QByteArray &data, qreal pts)
|
||||
{
|
||||
DPTR_D(AudioOutput);
|
||||
if (isPaused())
|
||||
return false;
|
||||
QByteArray queue_data(data);
|
||||
if (isMute() && d.sw_mute) {
|
||||
char s = 0;
|
||||
if (d.format.isUnsigned() && !d.format.isFloat())
|
||||
s = 1<<((d.format.bytesPerSample() << 3)-1);
|
||||
queue_data.fill(s);
|
||||
} else {
|
||||
if (!qFuzzyCompare(volume(), (qreal)1.0)
|
||||
&& d.sw_volume
|
||||
&& d.scale_samples
|
||||
) {
|
||||
// TODO: af_volume needs samples_align to get nb_samples
|
||||
const int nb_samples = queue_data.size()/d.format.bytesPerSample();
|
||||
quint8 *dst = (quint8*)queue_data.constData();
|
||||
d.scale_samples(dst, dst, nb_samples, d.volume_i, volume());
|
||||
}
|
||||
}
|
||||
// wait after all data processing finished to reduce time error
|
||||
if (!waitForNextBuffer()) { // TODO: wait or not parameter, set by user (async)
|
||||
qWarning("ao backend maybe not open");
|
||||
d.resetStatus();
|
||||
return false;
|
||||
}
|
||||
d.frame_infos.push_back(AudioOutputPrivate::FrameInfo(queue_data, pts, d.format.durationForBytes(queue_data.size())));
|
||||
return d.backend->write(queue_data); // backend is not null here
|
||||
}
|
||||
|
||||
AudioFormat AudioOutput::setAudioFormat(const AudioFormat& format)
|
||||
{
|
||||
DPTR_D(AudioOutput);
|
||||
// no support check because that may require an open device(AL) while this function is called before ao.open()
|
||||
if (d.format == format)
|
||||
return format;
|
||||
d.requested = format;
|
||||
if (!d.backend) {
|
||||
d.format = AudioFormat();
|
||||
d.scale_samples = NULL;
|
||||
return AudioFormat();
|
||||
}
|
||||
if (d.backend->isSupported(format)) {
|
||||
d.format = format;
|
||||
d.updateSampleScaleFunc();
|
||||
return format;
|
||||
}
|
||||
AudioFormat af(format);
|
||||
// set channel layout first so that isSupported(AudioFormat) will not always false
|
||||
if (!d.backend->isSupported(format.channelLayout()))
|
||||
af.setChannelLayout(AudioFormat::ChannelLayout_Stereo); // assume stereo is supported
|
||||
bool check_up = af.bytesPerSample() == 1;
|
||||
while (!d.backend->isSupported(af) && !d.backend->isSupported(af.sampleFormat())) {
|
||||
if (af.isPlanar()) {
|
||||
af.setSampleFormat(ToPacked(af.sampleFormat()));
|
||||
continue;
|
||||
}
|
||||
if (af.isFloat()) {
|
||||
if (af.bytesPerSample() == 8)
|
||||
af.setSampleFormat(AudioFormat::SampleFormat_Float);
|
||||
else
|
||||
af.setSampleFormat(AudioFormat::SampleFormat_Signed32);
|
||||
} else {
|
||||
af.setSampleFormat(AudioFormat::make(af.bytesPerSample()/2, false, (af.bytesPerSample() == 2) | af.isUnsigned() /* U8, no S8 */, false));
|
||||
}
|
||||
if (af.bytesPerSample() < 1) {
|
||||
if (!check_up) {
|
||||
qWarning("No sample format found");
|
||||
break;
|
||||
}
|
||||
af.setSampleFormat(AudioFormat::SampleFormat_Float);
|
||||
check_up = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
d.format = af;
|
||||
d.updateSampleScaleFunc();
|
||||
return af;
|
||||
}
|
||||
|
||||
const AudioFormat& AudioOutput::requestedFormat() const
|
||||
{
|
||||
return d_func().requested;
|
||||
}
|
||||
|
||||
const AudioFormat& AudioOutput::audioFormat() const
|
||||
{
|
||||
return d_func().format;
|
||||
}
|
||||
|
||||
void AudioOutput::setVolume(qreal value)
|
||||
{
|
||||
DPTR_D(AudioOutput);
|
||||
if (value < 0.0)
|
||||
return;
|
||||
if (d.vol == value) //fuzzy compare?
|
||||
return;
|
||||
d.vol = value;
|
||||
Q_EMIT volumeChanged(value);
|
||||
d.updateSampleScaleFunc();
|
||||
d.tryVolume(value);
|
||||
}
|
||||
|
||||
qreal AudioOutput::volume() const
|
||||
{
|
||||
return qMax<qreal>(d_func().vol, 0);
|
||||
}
|
||||
|
||||
void AudioOutput::setMute(bool value)
|
||||
{
|
||||
DPTR_D(AudioOutput);
|
||||
if (d.mute == value)
|
||||
return;
|
||||
d.mute = value;
|
||||
Q_EMIT muteChanged(value);
|
||||
d.tryMute(value);
|
||||
}
|
||||
|
||||
bool AudioOutput::isMute() const
|
||||
{
|
||||
return d_func().mute;
|
||||
}
|
||||
|
||||
void AudioOutput::setSpeed(qreal speed)
|
||||
{
|
||||
d_func().speed = speed;
|
||||
}
|
||||
|
||||
qreal AudioOutput::speed() const
|
||||
{
|
||||
return d_func().speed;
|
||||
}
|
||||
|
||||
bool AudioOutput::isSupported(const AudioFormat &format) const
|
||||
{
|
||||
DPTR_D(const AudioOutput);
|
||||
if (!d.backend)
|
||||
return false;
|
||||
return d.backend->isSupported(format);
|
||||
}
|
||||
|
||||
int AudioOutput::bufferSize() const
|
||||
{
|
||||
return bufferSamples() * d_func().format.bytesPerSample();
|
||||
}
|
||||
|
||||
int AudioOutput::bufferSamples() const
|
||||
{
|
||||
return d_func().buffer_samples;
|
||||
}
|
||||
|
||||
void AudioOutput::setBufferSamples(int value)
|
||||
{
|
||||
d_func().buffer_samples = value;
|
||||
}
|
||||
|
||||
int AudioOutput::bufferCount() const
|
||||
{
|
||||
return d_func().nb_buffers;
|
||||
}
|
||||
|
||||
void AudioOutput::setBufferCount(int value)
|
||||
{
|
||||
d_func().nb_buffers = value;
|
||||
}
|
||||
|
||||
// no virtual functions inside because it can be called in ctor
|
||||
void AudioOutput::setDeviceFeatures(DeviceFeatures value)
|
||||
{
|
||||
DPTR_D(AudioOutput);
|
||||
//Qt5: QFlags::Int (int or uint)
|
||||
const int s(supportedDeviceFeatures());
|
||||
const int f(value);
|
||||
if (d.features == (f & s))
|
||||
return;
|
||||
d.features = (f & s);
|
||||
emit deviceFeaturesChanged();
|
||||
}
|
||||
|
||||
AudioOutput::DeviceFeatures AudioOutput::deviceFeatures() const
|
||||
{
|
||||
return (DeviceFeature)d_func().features;
|
||||
}
|
||||
|
||||
AudioOutput::DeviceFeatures AudioOutput::supportedDeviceFeatures() const
|
||||
{
|
||||
DPTR_D(const AudioOutput);
|
||||
if (!d.backend)
|
||||
return NoFeature;
|
||||
return d.backend->supportedFeatures();
|
||||
}
|
||||
|
||||
bool AudioOutput::waitForNextBuffer() // parameter bool wait: if no wait and no next buffer, return false
|
||||
{
|
||||
DPTR_D(AudioOutput);
|
||||
if (d.frame_infos.empty())
|
||||
return true;
|
||||
//don't return even if we can add buffer because we don't know when a buffer is processed and we have /to update dequeue index
|
||||
// openal need enqueue to a dequeued buffer! why sl crash
|
||||
bool no_wait = false;//d.canAddBuffer();
|
||||
const AudioOutputBackend::BufferControl f = d.backend->bufferControl();
|
||||
int remove = 0;
|
||||
const AudioOutputPrivate::FrameInfo &fi(d.frame_infos.front());
|
||||
if (f & AudioOutputBackend::Blocking) {
|
||||
remove = 1;
|
||||
} else if (f & AudioOutputBackend::CountCallback) {
|
||||
d.backend->acquireNextBuffer();
|
||||
remove = 1;
|
||||
} else if (f & AudioOutputBackend::BytesCallback) {
|
||||
#if AO_USE_TIMER
|
||||
d.timer.restart();
|
||||
#endif //AO_USE_TIMER
|
||||
int processed = d.processed_remain;
|
||||
d.processed_remain = d.backend->getWritableBytes();
|
||||
if (d.processed_remain < 0)
|
||||
return false;
|
||||
const int next = fi.data.size();
|
||||
//qDebug("remain: %d-%d, size: %d, next: %d", processed, d.processed_remain, d.data.size(), next);
|
||||
qint64 last_wait = 0LL;
|
||||
while (d.processed_remain - processed < next || d.processed_remain < fi.data.size()) { //implies next > 0
|
||||
const qint64 us = d.format.durationForBytes(next - (d.processed_remain - processed));
|
||||
d.uwait(us);
|
||||
d.processed_remain = d.backend->getWritableBytes();
|
||||
if (d.processed_remain < 0)
|
||||
return false;
|
||||
#if AO_USE_TIMER
|
||||
if (!d.timer.isValid()) {
|
||||
qWarning("invalid timer. closed in another thread");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (us >= last_wait
|
||||
#if AO_USE_TIMER
|
||||
&& d.timer.elapsed() > 1000
|
||||
#endif //AO_USE_TIMER
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
last_wait = us;
|
||||
}
|
||||
processed = d.processed_remain - processed;
|
||||
d.processed_remain -= fi.data.size(); //ensure d.processed_remain later is greater
|
||||
remove = -processed; // processed_this_period
|
||||
} else if (f & AudioOutputBackend::PlayedBytes) {
|
||||
d.processed_remain = d.backend->getPlayedBytes();
|
||||
const int next = fi.data.size();
|
||||
// TODO: avoid always 0
|
||||
// TODO: compare processed_remain with fi.data.size because input chuncks can be in different sizes
|
||||
while (!no_wait && d.processed_remain < next) {
|
||||
const qint64 us = d.format.durationForBytes(next - d.processed_remain);
|
||||
if (us < 1000LL)
|
||||
d.uwait(1000LL);
|
||||
else
|
||||
d.uwait(us);
|
||||
d.processed_remain = d.backend->getPlayedBytes();
|
||||
// what if s always 0?
|
||||
}
|
||||
remove = -d.processed_remain;
|
||||
} else if (f & AudioOutputBackend::PlayedCount) {
|
||||
#if AO_USE_TIMER
|
||||
if (!d.timer.isValid())
|
||||
d.timer.start();
|
||||
qint64 elapsed = 0;
|
||||
#endif //AO_USE_TIMER
|
||||
int c = d.backend->getPlayedCount();
|
||||
// TODO: avoid always 0
|
||||
qint64 us = 0;
|
||||
while (!no_wait && c < 1) {
|
||||
if (us <= 0)
|
||||
us = fi.duration;
|
||||
#if AO_USE_TIMER
|
||||
elapsed = d.timer.restart();
|
||||
if (elapsed > 0 && us > elapsed*1000LL)
|
||||
us -= elapsed*1000LL;
|
||||
if (us < 1000LL)
|
||||
us = 1000LL; //opensl crash if 1ms
|
||||
#endif //AO_USE_TIMER
|
||||
d.uwait(us);
|
||||
c = d.backend->getPlayedCount();
|
||||
}
|
||||
// what if c always 0?
|
||||
remove = c;
|
||||
} else if (f & AudioOutputBackend::OffsetBytes) { //TODO: similar to Callback+getWritableBytes()
|
||||
int s = d.backend->getOffsetByBytes();
|
||||
int processed = s - d.play_pos;
|
||||
//qDebug("s: %d, play_pos: %d, processed: %d, bufferSizeTotal: %d", s, d.play_pos, processed, bufferSizeTotal());
|
||||
if (processed < 0)
|
||||
processed += bufferSizeTotal();
|
||||
d.play_pos = s;
|
||||
const int next = fi.data.size();
|
||||
int writable_size = d.processed_remain + processed;
|
||||
while (!no_wait && (/*processed < next ||*/ writable_size < fi.data.size()) && next > 0) {
|
||||
const qint64 us = d.format.durationForBytes(next - writable_size);
|
||||
d.uwait(us);
|
||||
s = d.backend->getOffsetByBytes();
|
||||
processed += s - d.play_pos;
|
||||
if (processed < 0)
|
||||
processed += bufferSizeTotal();
|
||||
writable_size = d.processed_remain + processed;
|
||||
d.play_pos = s;
|
||||
}
|
||||
d.processed_remain += processed;
|
||||
d.processed_remain -= fi.data.size(); //ensure d.processed_remain later is greater
|
||||
remove = -processed;
|
||||
} else if (f & AudioOutputBackend::OffsetIndex) {
|
||||
int n = d.backend->getOffset();
|
||||
int processed = n - d.play_pos;
|
||||
if (processed < 0)
|
||||
processed += bufferCount();
|
||||
d.play_pos = n;
|
||||
// TODO: timer
|
||||
// TODO: avoid always 0
|
||||
while (!no_wait && processed < 1) {
|
||||
d.uwait(fi.duration);
|
||||
n = d.backend->getOffset();
|
||||
processed = n - d.play_pos;
|
||||
if (processed < 0)
|
||||
processed += bufferCount();
|
||||
d.play_pos = n;
|
||||
}
|
||||
remove = processed;
|
||||
} else {
|
||||
qFatal("User defined waitForNextBuffer() not implemented!");
|
||||
return false;
|
||||
}
|
||||
if (remove < 0) {
|
||||
int next = fi.data.size();
|
||||
int free_bytes = -remove;//d.processed_remain;
|
||||
while (free_bytes >= next && next > 0) {
|
||||
free_bytes -= next;
|
||||
if (d.frame_infos.empty()) {
|
||||
// qWarning("buffer queue empty");
|
||||
break;
|
||||
}
|
||||
d.frame_infos.pop_front();
|
||||
next = d.frame_infos.front().data.size();
|
||||
}
|
||||
//qDebug("remove: %d, unremoved bytes < %d, writable_bytes: %d", remove, free_bytes, d.processed_remain);
|
||||
return true;
|
||||
}
|
||||
//qDebug("remove count: %d", remove);
|
||||
while (remove-- > 0) {
|
||||
if (d.frame_infos.empty()) {
|
||||
// qWarning("empty. can not pop!");
|
||||
break;
|
||||
}
|
||||
d.frame_infos.pop_front();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
qreal AudioOutput::timestamp() const
|
||||
{
|
||||
DPTR_D(const AudioOutput);
|
||||
return d.frame_infos.front().timestamp;
|
||||
}
|
||||
|
||||
void AudioOutput::reportVolume(qreal value)
|
||||
{
|
||||
if (qFuzzyCompare(value + 1.0, volume() + 1.0))
|
||||
return;
|
||||
DPTR_D(AudioOutput);
|
||||
d.vol = value;
|
||||
Q_EMIT volumeChanged(value);
|
||||
// skip sw sample scale
|
||||
d.sw_volume = false;
|
||||
}
|
||||
|
||||
void AudioOutput::reportMute(bool value)
|
||||
{
|
||||
if (value == isMute())
|
||||
return;
|
||||
DPTR_D(AudioOutput);
|
||||
d.mute = value;
|
||||
Q_EMIT muteChanged(value);
|
||||
// skip sw sample scale
|
||||
d.sw_mute = false;
|
||||
}
|
||||
|
||||
void AudioOutput::onCallback()
|
||||
{
|
||||
d_func().onCallback();
|
||||
}
|
||||
} //namespace FAV
|
||||
233
project/fm_viewer/fav/AudioOutput.h
Normal file
233
project/fm_viewer/fav/AudioOutput.h
Normal file
@@ -0,0 +1,233 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QAV_AUDIOOUTPUT_H
|
||||
#define QAV_AUDIOOUTPUT_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QStringList>
|
||||
#include "AVOutput.h"
|
||||
#include "AudioFrame.h"
|
||||
|
||||
/*!
|
||||
* AudioOutput ao;
|
||||
* ao.setAudioFormat(fmt);
|
||||
* ao.open();
|
||||
* while (has_data) {
|
||||
* data = read_data(ao->bufferSize());
|
||||
* ao->play(data, pts);
|
||||
* }
|
||||
* ao->close();
|
||||
* See QtAV/tests/ao/main.cpp for detail
|
||||
*/
|
||||
namespace FAV {
|
||||
|
||||
class AudioFormat;
|
||||
class AudioOutputPrivate;
|
||||
class Q_AV_EXPORT AudioOutput : public QObject, public AVOutput
|
||||
{
|
||||
Q_OBJECT
|
||||
DPTR_DECLARE_PRIVATE(AudioOutput)
|
||||
Q_ENUMS(DeviceFeature)
|
||||
Q_FLAGS(DeviceFeatures)
|
||||
Q_PROPERTY(qreal volume READ volume WRITE setVolume NOTIFY volumeChanged)
|
||||
Q_PROPERTY(bool mute READ isMute WRITE setMute NOTIFY muteChanged)
|
||||
Q_PROPERTY(DeviceFeatures deviceFeatures READ deviceFeatures WRITE setDeviceFeatures NOTIFY deviceFeaturesChanged)
|
||||
Q_PROPERTY(QStringList backends READ backends WRITE setBackends NOTIFY backendsChanged)
|
||||
public:
|
||||
/*!
|
||||
* \brief DeviceFeature Feature enum
|
||||
* Features supported by the audio playback api (we call device or backend here)
|
||||
* If a feature is not supported, e.g. SetVolume, then a software implementation is used.
|
||||
*/
|
||||
enum DeviceFeature {
|
||||
NoFeature = 0,
|
||||
SetVolume = 1, /// Use backend volume control api rather than software scale. Ignore if backend does not support.
|
||||
SetMute = 1 << 1,
|
||||
SetSampleRate = 1 << 2, /// NOT IMPLEMENTED
|
||||
SetSpeed = 1 << 3, /// NOT IMPLEMENTED
|
||||
};
|
||||
Q_DECLARE_FLAGS(DeviceFeatures, DeviceFeature)
|
||||
/*!
|
||||
* \brief backendsAvailable
|
||||
* All registered backends in default priority order
|
||||
* \return
|
||||
*/
|
||||
static QStringList backendsAvailable();
|
||||
/*!
|
||||
* \brief AudioOutput
|
||||
* Audio format set to preferred sample format and channel layout
|
||||
*/
|
||||
AudioOutput(QObject *parent = 0);
|
||||
~AudioOutput();
|
||||
/*!
|
||||
* \brief setBackends
|
||||
* set the given backends. Old backend instance and backend() is updated soon if backendsChanged.
|
||||
* It is called internally with a default backend names when AudioOutput is created.
|
||||
*/
|
||||
void setBackends(const QStringList &backendNames = QStringList());
|
||||
QStringList backends() const;
|
||||
/*!
|
||||
* \brief backend
|
||||
* backend name currently in use
|
||||
*/
|
||||
QString backend() const;
|
||||
|
||||
|
||||
QString status() const;
|
||||
/*!
|
||||
* \brief flush
|
||||
* Play the buffered audio data
|
||||
* \return
|
||||
*/
|
||||
void flush();
|
||||
/*!
|
||||
* \brief clear
|
||||
* Clear audio buffers and set time to 0. The default behavior is flush and reset time
|
||||
*/
|
||||
void clear();
|
||||
bool open();
|
||||
bool close();
|
||||
bool isOpen() const;
|
||||
/*!
|
||||
* \brief play
|
||||
* Play out the given audio data. It may block current thread until the data can be written to audio device
|
||||
* for async playback backend, or until the data is completely played for blocking playback backend.
|
||||
* \param data Audio data to play
|
||||
* \param pts Timestamp for this data. Useful if need A/V sync. Ignore it if only play audio
|
||||
* \return false if currently isPaused(), no backend is available or backend failed to play
|
||||
*/
|
||||
bool play(const QByteArray& data, qreal pts = 0.0);
|
||||
/*!
|
||||
* \brief pause
|
||||
* Pause audio rendering. play() will fail.
|
||||
*/
|
||||
void pause(bool value);
|
||||
bool isPaused() const;
|
||||
/*!
|
||||
* \brief setAudioFormat
|
||||
* Set/Request to use the given \l format. If it's not supported, an preferred format will be used.
|
||||
* \param format requested format
|
||||
* \return actual format to use. Invalid format if backend is not available
|
||||
* NOTE: Check format support may fail for some backends (OpenAL) if it's closed.
|
||||
*/
|
||||
AudioFormat setAudioFormat(const AudioFormat& format);
|
||||
const AudioFormat& requestedFormat() const;
|
||||
/*!
|
||||
* \brief audioFormat
|
||||
* \return actual format for requested format
|
||||
*/
|
||||
const AudioFormat& audioFormat() const;
|
||||
/*!
|
||||
* \brief setVolume
|
||||
* Set volume level.
|
||||
* If SetVolume feature is not set or not supported, software implementation will be used.
|
||||
* Call this after open(), because it will call backend api if SetVolume feature is enabled
|
||||
* \param volume linear. 1.0: original volume.
|
||||
*/
|
||||
void setVolume(qreal value);
|
||||
qreal volume() const;
|
||||
/*!
|
||||
* \brief setMute
|
||||
* If SetMute feature is not set or not supported, software implementation will be used.
|
||||
* Call this after open(), because it will call backend api if SetMute feature is enabled
|
||||
*/
|
||||
void setMute(bool value = true);
|
||||
bool isMute() const;
|
||||
/*!
|
||||
* \brief setSpeed set audio playing speed
|
||||
* Currently only store the value and does nothing else in audio output. You may change sample rate to get the same effect.
|
||||
* The speed affects the playing only if audio is available and clock type is
|
||||
* audio clock. For example, play a video contains audio without special configurations.
|
||||
* To change the playing speed in other cases, use AVPlayer::setSpeed(qreal)
|
||||
* \param speed linear. > 0
|
||||
* TODO: resample internally
|
||||
*/
|
||||
void setSpeed(qreal speed);
|
||||
qreal speed() const;
|
||||
/*!
|
||||
* \brief isSupported
|
||||
* check \a isSupported(format.sampleFormat()) and \a isSupported(format.channelLayout())
|
||||
* \param format
|
||||
* \return true if \a format is supported. default is true
|
||||
* NOTE: may fail for some backends if it's closed, for example OpenAL
|
||||
*/
|
||||
bool isSupported(const AudioFormat& format) const;
|
||||
/*!
|
||||
* \brief bufferSamples
|
||||
* Number of samples that audio output accept in 1 buffer. Feed the audio output this size of data every time.
|
||||
* Smaller buffer samples gives more buffers for a given data to avoid stutter. But if it's too small, the duration of 1 buffer will be too small to play, for example 1ms. Currently the default value is 512.
|
||||
* Some backends(OpenAL) are affected significantly by this property
|
||||
*/
|
||||
int bufferSamples() const;
|
||||
void setBufferSamples(int value);
|
||||
int bufferSize() const; /// bufferSamples()*bytesPerSample
|
||||
/*!
|
||||
* \brief bufferCount
|
||||
* Total buffer count. If it's not large enough, playing high sample rate audio may be poor.
|
||||
* The default value is 16. TODO: depending on audio format(sample rate?)
|
||||
* Some backends(OpenAL) are affected significantly by this property
|
||||
*/
|
||||
int bufferCount() const;
|
||||
void setBufferCount(int value);
|
||||
int bufferSizeTotal() const { return bufferCount() * bufferSize();}
|
||||
/*!
|
||||
* \brief setDeviceFeatures
|
||||
* Unsupported features will not be set.
|
||||
* You can call this in a backend ctor.
|
||||
*/
|
||||
void setDeviceFeatures(DeviceFeatures value);
|
||||
/*!
|
||||
* \brief deviceFeatures
|
||||
* \return features set by setFeatures() excluding unsupported features
|
||||
*/
|
||||
DeviceFeatures deviceFeatures() const;
|
||||
/*!
|
||||
* \brief supportedDeviceFeatures
|
||||
* Supported features of the backend, defined by AudioOutput(DeviceFeatures,AudioOutput&,QObject*) in a backend ctor
|
||||
*/
|
||||
DeviceFeatures supportedDeviceFeatures() const;
|
||||
qreal timestamp() const;
|
||||
// timestamp of current playing data
|
||||
Q_SIGNALS:
|
||||
void volumeChanged(qreal);
|
||||
void muteChanged(bool);
|
||||
void deviceFeaturesChanged();
|
||||
void backendsChanged();
|
||||
protected:
|
||||
// Store and fill data to audio buffers
|
||||
bool receiveData(const QByteArray &data, qreal pts = 0.0);
|
||||
/*!
|
||||
* \brief waitForNextBuffer
|
||||
* wait until you can feed more data
|
||||
*/
|
||||
bool waitForNextBuffer();
|
||||
private Q_SLOTS:
|
||||
void reportVolume(qreal value);
|
||||
void reportMute(bool value);
|
||||
private:
|
||||
void onCallback();
|
||||
friend class AudioOutputBackend;
|
||||
Q_DISABLE_COPY(AudioOutput)
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QAV_AUDIOOUTPUT_H
|
||||
246
project/fm_viewer/fav/AudioOutputAudioToolbox.cpp
Normal file
246
project/fm_viewer/fav/AudioOutputAudioToolbox.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2016-02-11)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#define REMOVE_AUDIO_TOOLBOX 1
|
||||
|
||||
#if !(REMOVE_AUDIO_TOOLBOX)
|
||||
|
||||
// FIXME: pause=>resume error
|
||||
#include "AudioOutputBackend.h"
|
||||
#include <QtCore/QQueue>
|
||||
#include <QtCore/QSemaphore>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QMutex> //qt4
|
||||
#include <QtCore/QWaitCondition>
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include "mkid.h"
|
||||
#include "factory.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
static const char kName[] = "AudioToolbox";
|
||||
class AudioOutputAudioToolbox Q_DECL_FINAL: public AudioOutputBackend
|
||||
{
|
||||
public:
|
||||
AudioOutputAudioToolbox(QObject *parent = 0);
|
||||
QString name() const Q_DECL_OVERRIDE { return QLatin1String(kName);}
|
||||
bool isSupported(AudioFormat::SampleFormat smpfmt) const Q_DECL_OVERRIDE;
|
||||
bool open() Q_DECL_OVERRIDE;
|
||||
bool close() Q_DECL_OVERRIDE;
|
||||
//bool flush() Q_DECL_OVERRIDE;
|
||||
BufferControl bufferControl() const Q_DECL_OVERRIDE;
|
||||
void onCallback() Q_DECL_OVERRIDE;
|
||||
bool write(const QByteArray& data) Q_DECL_OVERRIDE;
|
||||
bool play() Q_DECL_OVERRIDE;
|
||||
bool setVolume(qreal value) override;
|
||||
private:
|
||||
static void outCallback(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);
|
||||
void tryPauseTimeline();
|
||||
|
||||
QVector<AudioQueueBufferRef> m_buffer;
|
||||
QVector<AudioQueueBufferRef> m_buffer_fill;
|
||||
AudioQueueRef m_queue;
|
||||
AudioStreamBasicDescription m_desc;
|
||||
|
||||
bool m_waiting;
|
||||
QMutex m_mutex;
|
||||
QWaitCondition m_cond;
|
||||
QSemaphore sem;
|
||||
};
|
||||
|
||||
typedef AudioOutputAudioToolbox AudioOutputBackendAudioToolbox;
|
||||
static const AudioOutputBackendId AudioOutputBackendId_AudioToolbox = mkid::id32base36_2<'A', 'T'>::value;
|
||||
FACTORY_REGISTER(AudioOutputBackend, AudioToolbox, kName)
|
||||
|
||||
#define AT_ENSURE(FUNC, ...) AQ_RUN_CHECK(FUNC, return __VA_ARGS__)
|
||||
#define AT_WARN(FUNC, ...) AQ_RUN_CHECK(FUNC)
|
||||
#define AQ_RUN_CHECK(FUNC, ...) \
|
||||
do { \
|
||||
OSStatus ret = FUNC; \
|
||||
if (ret != noErr) { \
|
||||
qWarning("AudioBackendAudioQueue Error>>> " #FUNC ": %#x", ret); \
|
||||
__VA_ARGS__; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
static AudioStreamBasicDescription audioFormatToAT(const AudioFormat &format)
|
||||
{
|
||||
// AudioQueue only supports interleave
|
||||
AudioStreamBasicDescription desc;
|
||||
desc.mSampleRate = format.sampleRate();
|
||||
desc.mFormatID = kAudioFormatLinearPCM;
|
||||
desc.mFormatFlags = kAudioFormatFlagIsPacked; // TODO: kAudioFormatFlagIsPacked?
|
||||
if (format.isFloat())
|
||||
desc.mFormatFlags |= kAudioFormatFlagIsFloat;
|
||||
else if (!format.isUnsigned())
|
||||
desc.mFormatFlags |= kAudioFormatFlagIsSignedInteger;
|
||||
desc.mFramesPerPacket = 1;// FIXME:??
|
||||
desc.mChannelsPerFrame = format.channels();
|
||||
desc.mBitsPerChannel = format.bytesPerSample()*8;
|
||||
desc.mBytesPerFrame = format.bytesPerFrame();
|
||||
desc.mBytesPerPacket = desc.mBytesPerFrame * desc.mFramesPerPacket;
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
void AudioOutputAudioToolbox::outCallback(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
|
||||
{
|
||||
Q_UNUSED(inAQ);
|
||||
AudioOutputAudioToolbox *ao = reinterpret_cast<AudioOutputAudioToolbox*>(inUserData);
|
||||
if (ao->bufferControl() & AudioOutputBackend::CountCallback) {
|
||||
ao->onCallback();
|
||||
}
|
||||
QMutexLocker locker(&ao->m_mutex);
|
||||
Q_UNUSED(locker);
|
||||
ao->m_buffer_fill.push_back(inBuffer);
|
||||
|
||||
if (ao->m_waiting) {
|
||||
ao->m_waiting = false;
|
||||
qDebug("wake up to fill buffer");
|
||||
ao->m_cond.wakeOne();
|
||||
}
|
||||
//qDebug("callback. sem: %d, fill queue: %d", ao->sem.available(), ao->m_buffer_fill.size());
|
||||
ao->tryPauseTimeline();
|
||||
}
|
||||
|
||||
AudioOutputAudioToolbox::AudioOutputAudioToolbox(QObject *parent)
|
||||
: AudioOutputBackend(AudioOutput::DeviceFeatures()
|
||||
|AudioOutput::SetVolume
|
||||
, parent)
|
||||
, m_queue(NULL)
|
||||
, m_waiting(false)
|
||||
{
|
||||
available = false;
|
||||
available = true;
|
||||
}
|
||||
|
||||
AudioOutputBackend::BufferControl AudioOutputAudioToolbox::bufferControl() const
|
||||
{
|
||||
return CountCallback;//BufferControl(Callback | PlayedCount);
|
||||
}
|
||||
|
||||
void AudioOutputAudioToolbox::onCallback()
|
||||
{
|
||||
if (bufferControl() & CountCallback)
|
||||
sem.release();
|
||||
}
|
||||
|
||||
void AudioOutputAudioToolbox::tryPauseTimeline()
|
||||
{
|
||||
/// All buffers are rendered but the AudioQueue timeline continues. If the next buffer sample time is earlier than AudioQueue timeline value, for example resume after pause, the buffer will not be rendered
|
||||
if (sem.available() == buffer_count) {
|
||||
AudioTimeStamp t;
|
||||
AudioQueueGetCurrentTime(m_queue, NULL, &t, NULL);
|
||||
qDebug("pause audio queue timeline @%.3f (sample time)/%lld (host time)", t.mSampleTime, t.mHostTime);
|
||||
AT_ENSURE(AudioQueuePause(m_queue));
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioOutputAudioToolbox::isSupported(AudioFormat::SampleFormat smpfmt) const
|
||||
{
|
||||
return !IsPlanar(smpfmt);
|
||||
}
|
||||
|
||||
bool AudioOutputAudioToolbox::open()
|
||||
{
|
||||
m_buffer.resize(buffer_count);
|
||||
m_desc = audioFormatToAT(format);
|
||||
AT_ENSURE(AudioQueueNewOutput(&m_desc, AudioOutputAudioToolbox::outCallback, this, NULL, kCFRunLoopCommonModes/*NULL*/, 0, &m_queue), false);
|
||||
for (int i = 0; i < m_buffer.size(); ++i) {
|
||||
AT_ENSURE(AudioQueueAllocateBuffer(m_queue, buffer_size, &m_buffer[i]), false);
|
||||
}
|
||||
m_buffer_fill = m_buffer;
|
||||
|
||||
sem.release(buffer_count - sem.available());
|
||||
m_waiting = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputAudioToolbox::close()
|
||||
{
|
||||
if (!m_queue) {
|
||||
qDebug("AudioQueue is not created. skip close");
|
||||
return true;
|
||||
}
|
||||
UInt32 running = 0, s = 0;
|
||||
AT_ENSURE(AudioQueueGetProperty(m_queue, kAudioQueueProperty_IsRunning, &running, &s), false);
|
||||
if (running)
|
||||
AT_ENSURE(AudioQueueStop(m_queue, true), false);
|
||||
AT_ENSURE(AudioQueueDispose(m_queue, true), false); // dispose all resouces including buffers, so we can remove AudioQueueFreeBuffer
|
||||
m_queue = NULL;
|
||||
m_buffer.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputAudioToolbox::write(const QByteArray& data)
|
||||
{
|
||||
// blocking queue.
|
||||
// if queue not full, fill buffer and enqueue buffer
|
||||
//qDebug("write. sem: %d", sem.available());
|
||||
if (bufferControl() & CountCallback)
|
||||
sem.acquire();
|
||||
|
||||
AudioQueueBufferRef buf = NULL;
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
Q_UNUSED(locker);
|
||||
// put to data queue, if has buffer to fill (was available in callback), fill the front data
|
||||
if (m_buffer_fill.isEmpty()) {
|
||||
qDebug("buffer queue to fill is empty, wait a valid buffer to fill");
|
||||
m_waiting = true;
|
||||
m_cond.wait(&m_mutex);
|
||||
}
|
||||
buf = m_buffer_fill.front();
|
||||
m_buffer_fill.pop_front();
|
||||
}
|
||||
assert(buf->mAudioDataBytesCapacity >= (UInt32)data.size() && "too many data to write to audio queue buffer");
|
||||
memcpy(buf->mAudioData, data.constData(), data.size());
|
||||
buf->mAudioDataByteSize = data.size();
|
||||
//buf->mUserData
|
||||
AT_ENSURE(AudioQueueEnqueueBuffer(m_queue, buf, 0, NULL), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputAudioToolbox::play()
|
||||
{
|
||||
OSType err = AudioQueueStart(m_queue, nullptr);
|
||||
if (err == '!pla') { //AVAudioSessionErrorCodeCannotStartPlaying
|
||||
qWarning("AudioQueueStart error: AVAudioSessionErrorCodeCannotStartPlaying. May play in background");
|
||||
close();
|
||||
open();
|
||||
return false;
|
||||
}
|
||||
if (err != noErr) {
|
||||
qWarning("AudioQueueStart error: %#x", noErr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputAudioToolbox::setVolume(qreal value)
|
||||
{
|
||||
// iOS document says the range is [0,1]. But >1.0 works on macOS. So no manually check range here
|
||||
AT_ENSURE(AudioQueueSetParameter(m_queue, kAudioQueueParam_Volume, value), false);
|
||||
return true;
|
||||
}
|
||||
} //namespace FAV
|
||||
|
||||
#endif // REMOVE_AUDIO_TOOLBOX
|
||||
117
project/fm_viewer/fav/AudioOutputBackend.cpp
Normal file
117
project/fm_viewer/fav/AudioOutputBackend.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AudioOutputBackend.h"
|
||||
#include "_fav_constants.h"
|
||||
#include "factory.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
QStringList AudioOutputBackend::defaultPriority()
|
||||
{
|
||||
static const QStringList sBackends = QStringList()
|
||||
#ifdef Q_OS_MAC
|
||||
<< QStringLiteral("AudioToolbox")
|
||||
#endif
|
||||
#if QTAV_HAVE(XAUDIO2) || (FORCE_TO_USE_XAUDIO)
|
||||
<< QStringLiteral("XAudio2")
|
||||
#endif
|
||||
#if QTAV_HAVE(PORTAUDIO)
|
||||
<< QStringLiteral("PortAudio")
|
||||
#endif
|
||||
#if QTAV_HAVE(OPENSL)
|
||||
<< QStringLiteral("OpenSL")
|
||||
#endif
|
||||
#if QTAV_HAVE(OPENAL)
|
||||
<< QStringLiteral("OpenAL")
|
||||
#endif
|
||||
#if QTAV_HAVE(PULSEAUDIO)&& !defined(Q_OS_MAC)
|
||||
<< QStringLiteral("Pulse")
|
||||
#endif
|
||||
#if QTAV_HAVE(DSOUND)
|
||||
<< QStringLiteral("DirectSound")
|
||||
#endif
|
||||
;
|
||||
return sBackends;
|
||||
}
|
||||
|
||||
AudioOutputBackend::AudioOutputBackend(AudioOutput::DeviceFeatures f, QObject *parent)
|
||||
: QObject(parent)
|
||||
, audio(0)
|
||||
, available(true)
|
||||
, buffer_size(0)
|
||||
, buffer_count(0)
|
||||
, m_features(f)
|
||||
{}
|
||||
|
||||
void AudioOutputBackend::onCallback()
|
||||
{
|
||||
if (!audio)
|
||||
return;
|
||||
audio->onCallback();
|
||||
}
|
||||
|
||||
|
||||
FACTORY_DEFINE(AudioOutputBackend)
|
||||
|
||||
void AudioOutput_RegisterAll()
|
||||
{
|
||||
static bool initialized = false;
|
||||
if (initialized)
|
||||
return;
|
||||
initialized = true;
|
||||
// check whether ids are registered automatically
|
||||
if (!AudioOutputBackendFactory::Instance().registeredIds().empty())
|
||||
return;
|
||||
extern bool RegisterAudioOutputBackendNull_Man();
|
||||
RegisterAudioOutputBackendNull_Man();
|
||||
#ifdef Q_OS_DARWIN
|
||||
extern bool RegisterAudioOutputBackendAudioToolbox_Man();
|
||||
RegisterAudioOutputBackendAudioToolbox_Man();
|
||||
#endif
|
||||
#if QTAV_HAVE(OPENSL)
|
||||
extern bool RegisterAudioOutputBackendOpenSL_Man();
|
||||
RegisterAudioOutputBackendOpenSL_Man();
|
||||
#endif //QTAV_HAVE(OPENSL)
|
||||
#if QTAV_HAVE(XAUDIO2) || (FORCE_TO_USE_XAUDIO)
|
||||
extern bool RegisterAudioOutputBackendXAudio2_Man();
|
||||
RegisterAudioOutputBackendXAudio2_Man();
|
||||
#endif
|
||||
#if QTAV_HAVE(OPENAL)
|
||||
extern bool RegisterAudioOutputBackendOpenAL_Man();
|
||||
RegisterAudioOutputBackendOpenAL_Man();
|
||||
#endif //QTAV_HAVE(OPENAL)
|
||||
#if QTAV_HAVE(PULSEAUDIO)
|
||||
extern bool RegisterAudioOutputBackendPulse_Man();
|
||||
RegisterAudioOutputBackendPulse_Man();
|
||||
#endif
|
||||
#if QTAV_HAVE(PORTAUDIO)
|
||||
extern bool RegisterAudioOutputBackendPortAudio_Man();
|
||||
RegisterAudioOutputBackendPortAudio_Man();
|
||||
#endif //QTAV_HAVE(PORTAUDIO)
|
||||
#if QTAV_HAVE(DSOUND)
|
||||
extern bool RegisterAudioOutputBackendDSound_Man();
|
||||
RegisterAudioOutputBackendDSound_Man();
|
||||
#endif
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
143
project/fm_viewer/fav/AudioOutputBackend.h
Normal file
143
project/fm_viewer/fav/AudioOutputBackend.h
Normal file
@@ -0,0 +1,143 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QAV_AUDIOOUTPUTBACKEND_H
|
||||
#define QAV_AUDIOOUTPUTBACKEND_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include "AudioFormat.h"
|
||||
#include "AudioOutput.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
#if (SELECT_AUDIO_BACKEND)
|
||||
// PORTAUDIO 에서 구현
|
||||
QList<QPair<QString,int>> globalSounddeviceList();
|
||||
//void selectDevice(int deviceID);
|
||||
#endif
|
||||
|
||||
|
||||
typedef int AudioOutputBackendId;
|
||||
class Q_AV_PRIVATE_EXPORT AudioOutputBackend : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioOutput *audio;
|
||||
bool available; // default is true. set to false when failed to create backend
|
||||
int buffer_size;
|
||||
int buffer_count;
|
||||
AudioFormat format;
|
||||
static QStringList defaultPriority();
|
||||
/*!
|
||||
* \brief AudioOutputBackend
|
||||
* Specify supported features by the backend. Use this for new backends.
|
||||
*/
|
||||
AudioOutputBackend(AudioOutput::DeviceFeatures f, QObject *parent);
|
||||
virtual ~AudioOutputBackend() {}
|
||||
virtual QString name() const = 0;
|
||||
virtual QString status() const = 0;
|
||||
|
||||
virtual bool open() = 0;
|
||||
virtual bool close() = 0;
|
||||
virtual bool write(const QByteArray& data) = 0; //MUST
|
||||
virtual bool play() = 0; //MUST
|
||||
virtual bool flush() { return false;}
|
||||
virtual bool clear() { return false;}
|
||||
virtual bool isSupported(const AudioFormat& format) const { return isSupported(format.sampleFormat()) && isSupported(format.channelLayout());}
|
||||
// FIXME: workaround. planar convertion crash now!
|
||||
virtual bool isSupported(AudioFormat::SampleFormat f) const { return !IsPlanar(f);}
|
||||
// 5, 6, 7 channels may not play
|
||||
virtual bool isSupported(AudioFormat::ChannelLayout cl) const { return int(cl) < int(AudioFormat::ChannelLayout_Unsupported);}
|
||||
/*!
|
||||
* \brief The BufferControl enum
|
||||
* Used to adapt to different audio playback backend. Usually you don't need this in application level development.
|
||||
*/
|
||||
enum BufferControl {
|
||||
User = 0, // You have to reimplement waitForNextBuffer()
|
||||
Blocking = 1,
|
||||
BytesCallback = 1 << 1,
|
||||
CountCallback = 1 << 2,
|
||||
PlayedCount = 1 << 3, //number of buffers played since last buffer dequeued
|
||||
PlayedBytes = 1 << 4,
|
||||
OffsetIndex = 1 << 5, //current playing offset
|
||||
OffsetBytes = 1 << 6, //current playing offset by bytes
|
||||
WritableBytes = 1 << 7,
|
||||
};
|
||||
virtual BufferControl bufferControl() const = 0;
|
||||
// called by callback with Callback control
|
||||
virtual void onCallback();
|
||||
virtual void acquireNextBuffer() {}
|
||||
//default return -1. means not the control
|
||||
virtual int getPlayedCount() {return -1;} //PlayedCount
|
||||
/*!
|
||||
* \brief getPlayedBytes
|
||||
* reimplement this if bufferControl() is PlayedBytes.
|
||||
* \return the bytes played since last dequeue the buffer queue
|
||||
*/
|
||||
virtual int getPlayedBytes() {return -1;} // PlayedBytes
|
||||
virtual int getOffset() {return -1;} // OffsetIndex
|
||||
virtual int getOffsetByBytes() {return -1;}// OffsetBytes
|
||||
virtual int getWritableBytes() {return -1;} //WritableBytes
|
||||
// not virtual. called in ctor
|
||||
AudioOutput::DeviceFeatures supportedFeatures() { return m_features;}
|
||||
/*!
|
||||
* \brief setVolume
|
||||
* Set volume by backend api. If backend can not set the given volume, or SetVolume feature is not set, software implemention will be used.
|
||||
* \param value >=0
|
||||
* \return true if success
|
||||
*/
|
||||
virtual bool setVolume(qreal value) { Q_UNUSED(value); return false;}
|
||||
virtual qreal getVolume() const { return 1.0;}
|
||||
virtual bool setMute(bool value = true) { Q_UNUSED(value); return false;}
|
||||
virtual bool getMute() const { return false;}
|
||||
|
||||
Q_SIGNALS:
|
||||
/*
|
||||
* \brief reportVolume
|
||||
* Volume can be changed by per-app volume control from system outside this library. Useful for synchronizing ui to system.
|
||||
* Volume control from QtAV may invoke it too. And it may be invoked even if volume is not changed.
|
||||
* If volume changed, signal volumeChanged() will be emitted and volume() will be updated.
|
||||
* Only supported by some backends, e.g. pulseaudio
|
||||
*/
|
||||
void volumeReported(qreal value);
|
||||
void muteReported(bool value);
|
||||
public:
|
||||
template<class C> static bool Register(AudioOutputBackendId id, const char* name) { return Register(id, create<C>, name);}
|
||||
static AudioOutputBackend* create(AudioOutputBackendId id);
|
||||
static AudioOutputBackend* create(const char* name);
|
||||
/*!
|
||||
* \brief next
|
||||
* \param id NULL to get the first id address
|
||||
* \return address of id or NULL if not found/end
|
||||
*/
|
||||
static AudioOutputBackendId* next(AudioOutputBackendId* id = 0);
|
||||
static const char* name(AudioOutputBackendId id);
|
||||
static AudioOutputBackendId id(const char* name);
|
||||
private:
|
||||
template<class C> static AudioOutputBackend* create() { return new C();}
|
||||
typedef AudioOutputBackend* (*AudioOutputBackendCreator)();
|
||||
static bool Register(AudioOutputBackendId id, AudioOutputBackendCreator, const char *name);
|
||||
private:
|
||||
AudioOutput::DeviceFeatures m_features;
|
||||
Q_DISABLE_COPY(AudioOutputBackend)
|
||||
};
|
||||
} //namespace FAV
|
||||
#endif //QAV_AUDIOOUTPUTBACKEND_H
|
||||
444
project/fm_viewer/fav/AudioOutputDSound.cpp
Normal file
444
project/fm_viewer/fav/AudioOutputDSound.cpp
Normal file
@@ -0,0 +1,444 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2014)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#define REMOVE_AUDIO_OUTPUT_DSOUND 0
|
||||
#if !(REMOVE_AUDIO_OUTPUT_DSOUND)
|
||||
|
||||
#include "AudioOutputBackend.h"
|
||||
#include "mkid.h"
|
||||
#include "factory.h"
|
||||
#include <QtCore/QLibrary>
|
||||
#include <QtCore/QSemaphore>
|
||||
#include <QtCore/QThread>
|
||||
#include <math.h>
|
||||
#define DIRECTSOUND_VERSION 0x0600
|
||||
#include <dsound.h>
|
||||
#include "AVCompat.h"
|
||||
#include "Logger.h"
|
||||
#define DX_LOG_COMPONENT "DSound"
|
||||
#include "DirectXHelper.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
static const char kName[] = "DirectSound";
|
||||
class AudioOutputDSound Q_DECL_FINAL: public AudioOutputBackend
|
||||
{
|
||||
public:
|
||||
AudioOutputDSound(QObject *parent = 0);
|
||||
QString name() const Q_DECL_OVERRIDE { return QString::fromLatin1(kName);}
|
||||
QString status() const Q_DECL_OVERRIDE {return "";};
|
||||
bool open() Q_DECL_OVERRIDE;
|
||||
bool close() Q_DECL_OVERRIDE;
|
||||
bool isSupported(AudioFormat::SampleFormat sampleFormat) const Q_DECL_OVERRIDE;
|
||||
BufferControl bufferControl() const Q_DECL_OVERRIDE;
|
||||
bool write(const QByteArray& data) Q_DECL_OVERRIDE;
|
||||
bool play() Q_DECL_OVERRIDE;
|
||||
int getOffsetByBytes() Q_DECL_OVERRIDE;
|
||||
|
||||
bool setVolume(qreal value) Q_DECL_OVERRIDE;
|
||||
qreal getVolume() const Q_DECL_OVERRIDE;
|
||||
void onCallback() Q_DECL_OVERRIDE;
|
||||
private:
|
||||
bool loadDll();
|
||||
bool unloadDll();
|
||||
bool init();
|
||||
bool destroy() {
|
||||
SafeRelease(¬ify);
|
||||
SafeRelease(&prim_buf);
|
||||
SafeRelease(&stream_buf);
|
||||
SafeRelease(&dsound);
|
||||
unloadDll();
|
||||
return true;
|
||||
}
|
||||
bool createDSoundBuffers();
|
||||
static DWORD WINAPI notificationThread(LPVOID lpThreadParameter);
|
||||
HINSTANCE dll;
|
||||
LPDIRECTSOUND dsound; ///direct sound object
|
||||
LPDIRECTSOUNDBUFFER prim_buf; ///primary direct sound buffer
|
||||
LPDIRECTSOUNDBUFFER stream_buf; ///secondary direct sound buffer (stream buffer)
|
||||
LPDIRECTSOUNDNOTIFY notify;
|
||||
HANDLE notify_event;
|
||||
QSemaphore sem;
|
||||
int write_offset; ///offset of the write cursor in the direct sound buffer
|
||||
QAtomicInt buffers_free;
|
||||
class PositionWatcher : public QThread {
|
||||
AudioOutputDSound *ao;
|
||||
public:
|
||||
PositionWatcher(AudioOutputDSound* dsound) : ao(dsound) {}
|
||||
void run() Q_DECL_OVERRIDE {
|
||||
DWORD dwResult = 0;
|
||||
while (ao->available) {
|
||||
dwResult = WaitForSingleObjectEx(ao->notify_event, 2000, FALSE);
|
||||
if (dwResult != WAIT_OBJECT_0) {
|
||||
//qWarning("WaitForSingleObjectEx for ao->notify_event error: %#lx", dwResult);
|
||||
continue;
|
||||
}
|
||||
ao->onCallback();
|
||||
}
|
||||
}
|
||||
};
|
||||
PositionWatcher watcher;
|
||||
};
|
||||
|
||||
typedef AudioOutputDSound AudioOutputBackendDSound;
|
||||
static const AudioOutputBackendId AudioOutputBackendId_DSound = mkid::id32base36_6<'D', 'S', 'o', 'u', 'n', 'd'>::value;
|
||||
FACTORY_REGISTER(AudioOutputBackend, DSound, kName)
|
||||
|
||||
// use the definitions from the win32 api headers when they define these
|
||||
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
|
||||
#define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092
|
||||
#define WAVE_FORMAT_EXTENSIBLE 0xFFFE
|
||||
/* GUID SubFormat IDs */
|
||||
/* We need both b/c const variables are not compile-time constants in C, giving
|
||||
* us an error if we use the const GUID in an enum */
|
||||
#undef DEFINE_GUID
|
||||
#define DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
|
||||
DEFINE_GUID(_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, WAVE_FORMAT_IEEE_FLOAT, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 );
|
||||
DEFINE_GUID(_KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF, WAVE_FORMAT_DOLBY_AC3_SPDIF, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 );
|
||||
DEFINE_GUID(_KSDATAFORMAT_SUBTYPE_PCM, WAVE_FORMAT_PCM, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
||||
DEFINE_GUID(_KSDATAFORMAT_SUBTYPE_UNKNOWN, 0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
#ifndef MS_GUID
|
||||
#define MS_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
static const GUID name = { l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}}
|
||||
#endif //MS_GUID
|
||||
#ifndef _WAVEFORMATEXTENSIBLE_
|
||||
typedef struct {
|
||||
WAVEFORMATEX Format;
|
||||
union {
|
||||
WORD wValidBitsPerSample; /* bits of precision */
|
||||
WORD wSamplesPerBlock; /* valid if wBitsPerSample==0 */
|
||||
WORD wReserved; /* If neither applies, set to zero. */
|
||||
} Samples;
|
||||
DWORD dwChannelMask; /* which channels are */
|
||||
/* present in stream */
|
||||
GUID SubFormat;
|
||||
} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;
|
||||
#endif
|
||||
|
||||
/* Microsoft speaker definitions. key/values are equal to FFmpeg's */
|
||||
#define SPEAKER_FRONT_LEFT 0x1
|
||||
#define SPEAKER_FRONT_RIGHT 0x2
|
||||
#define SPEAKER_FRONT_CENTER 0x4
|
||||
#define SPEAKER_LOW_FREQUENCY 0x8
|
||||
#define SPEAKER_BACK_LEFT 0x10
|
||||
#define SPEAKER_BACK_RIGHT 0x20
|
||||
#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40
|
||||
#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80
|
||||
#define SPEAKER_BACK_CENTER 0x100
|
||||
#define SPEAKER_SIDE_LEFT 0x200
|
||||
#define SPEAKER_SIDE_RIGHT 0x400
|
||||
#define SPEAKER_TOP_CENTER 0x800
|
||||
#define SPEAKER_TOP_FRONT_LEFT 0x1000
|
||||
#define SPEAKER_TOP_FRONT_CENTER 0x2000
|
||||
#define SPEAKER_TOP_FRONT_RIGHT 0x4000
|
||||
#define SPEAKER_TOP_BACK_LEFT 0x8000
|
||||
#define SPEAKER_TOP_BACK_CENTER 0x10000
|
||||
#define SPEAKER_TOP_BACK_RIGHT 0x20000
|
||||
#define SPEAKER_RESERVED 0x80000000
|
||||
|
||||
static int channelMaskToMS(qint64 av) {
|
||||
if (av >= (qint64)SPEAKER_RESERVED)
|
||||
return 0;
|
||||
return (int)av;
|
||||
}
|
||||
|
||||
static int channelLayoutToMS(qint64 av) {
|
||||
return channelMaskToMS(av);
|
||||
}
|
||||
|
||||
AudioOutputDSound::AudioOutputDSound(QObject *parent)
|
||||
: AudioOutputBackend(AudioOutput::DeviceFeatures()|AudioOutput::SetVolume, parent)
|
||||
, dll(NULL)
|
||||
, dsound(NULL)
|
||||
, prim_buf(NULL)
|
||||
, stream_buf(NULL)
|
||||
, notify(NULL)
|
||||
, notify_event(NULL)
|
||||
, write_offset(0)
|
||||
, watcher(this)
|
||||
{
|
||||
//setDeviceFeatures(AudioOutput::DeviceFeatures()|AudioOutput::SetVolume);
|
||||
}
|
||||
|
||||
bool AudioOutputDSound::open()
|
||||
{
|
||||
if (!init())
|
||||
goto error;
|
||||
if (!createDSoundBuffers())
|
||||
goto error;
|
||||
return true;
|
||||
error:
|
||||
unloadDll();
|
||||
SafeRelease(&dsound);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioOutputDSound::close()
|
||||
{
|
||||
available = false;
|
||||
destroy();
|
||||
CloseHandle(notify_event); // FIXME: is it ok if thread is still waiting?
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputDSound::isSupported(AudioFormat::SampleFormat sampleFormat) const
|
||||
{
|
||||
return !IsPlanar(sampleFormat);
|
||||
}
|
||||
|
||||
AudioOutputBackend::BufferControl AudioOutputDSound::bufferControl() const
|
||||
{
|
||||
// Both works. I prefer CountCallback
|
||||
return CountCallback;// OffsetBytes;
|
||||
}
|
||||
|
||||
void AudioOutputDSound::onCallback()
|
||||
{
|
||||
if (bufferControl() & CountCallback) {
|
||||
//qDebug("callback: %d", sem.available());
|
||||
if (sem.available() < buffer_count) {
|
||||
sem.release();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// if (buffers_free.deref()) {
|
||||
// return;
|
||||
//}
|
||||
// buffers_free.ref();
|
||||
}
|
||||
//DWORD status;
|
||||
//stream_buf->GetStatus(&status);
|
||||
//qDebug("status: %lu", status);
|
||||
//return;
|
||||
//if (status & DSBSTATUS_LOOPING) {
|
||||
// sound will loop even if buffer is finished
|
||||
//DX_ENSURE(stream_buf->Stop());
|
||||
// reset positions to ensure the notification positions and played buffer matches
|
||||
//DX_ENSURE(stream_buf->SetCurrentPosition(0));
|
||||
//write_offset = 0;
|
||||
//}
|
||||
}
|
||||
|
||||
bool AudioOutputDSound::write(const QByteArray &data)
|
||||
{
|
||||
//qDebug("sem %d %d", sem.available(), buffers_free.load());
|
||||
if (bufferControl() & CountCallback) {
|
||||
sem.acquire();
|
||||
} else {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) || QT_VERSION >= QT_VERSION_CHECK(5, 3, 0)
|
||||
if (buffers_free <= buffer_count)
|
||||
#else
|
||||
if (buffers_free.load() <= buffer_count)
|
||||
#endif
|
||||
buffers_free.ref();
|
||||
}
|
||||
LPVOID dst1= NULL, dst2 = NULL;
|
||||
DWORD size1 = 0, size2 = 0;
|
||||
if (write_offset >= buffer_size*buffer_count) ///!!!>=
|
||||
write_offset = 0;
|
||||
HRESULT res = stream_buf->Lock(write_offset, data.size(), &dst1, &size1, &dst2, &size2, 0); //DSBLOCK_ENTIREBUFFER
|
||||
if (res == DSERR_BUFFERLOST) {
|
||||
qDebug("buffer lost");
|
||||
DX_ENSURE(stream_buf->Restore(), false);
|
||||
DX_ENSURE(stream_buf->Lock(write_offset, data.size(), &dst1, &size1, &dst2, &size2, 0), false);
|
||||
}
|
||||
memcpy(dst1, data.constData(), size1);
|
||||
if (dst2)
|
||||
memcpy(dst2, data.constData() + size1, size2);
|
||||
write_offset += size1 + size2;
|
||||
if (write_offset >= buffer_size*buffer_count)
|
||||
write_offset = size2;
|
||||
DX_ENSURE_OK(stream_buf->Unlock(dst1, size1, dst2, size2), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputDSound::play()
|
||||
{
|
||||
DWORD status;
|
||||
stream_buf->GetStatus(&status);
|
||||
if (!(status & DSBSTATUS_PLAYING)) {
|
||||
//must be DSBPLAY_LOOPING. Sound will be very slow if set to 0. I was fucked for a long time. DAMN!
|
||||
stream_buf->Play(0, 0, DSBPLAY_LOOPING);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int AudioOutputDSound::getOffsetByBytes()
|
||||
{
|
||||
DWORD read_offset = 0;
|
||||
stream_buf->GetCurrentPosition(&read_offset /*play*/, NULL /*write*/); //what's this write_offset?
|
||||
return (int)read_offset;
|
||||
}
|
||||
|
||||
bool AudioOutputDSound::setVolume(qreal value)
|
||||
{
|
||||
// dsound supports [0, 1]
|
||||
const LONG vol = value <= 0 ? DSBVOLUME_MIN : LONG(log10(value*100.0) * 5000.0) + DSBVOLUME_MIN;
|
||||
// +DSBVOLUME_MIN == -100dB
|
||||
DX_ENSURE_OK(stream_buf->SetVolume(vol), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
qreal AudioOutputDSound::getVolume() const
|
||||
{
|
||||
LONG vol = 0;
|
||||
DX_ENSURE_OK(stream_buf->GetVolume(&vol), 1.0);
|
||||
return pow(10.0, double(vol - DSBVOLUME_MIN)/5000.0)/100.0;
|
||||
}
|
||||
|
||||
bool AudioOutputDSound::loadDll()
|
||||
{
|
||||
dll = LoadLibrary(TEXT("dsound.dll"));
|
||||
if (!dll) {
|
||||
qWarning("Can not load dsound.dll");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputDSound::unloadDll()
|
||||
{
|
||||
if (dll)
|
||||
FreeLibrary(dll);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputDSound::init()
|
||||
{
|
||||
if (!loadDll())
|
||||
return false;
|
||||
typedef HRESULT (WINAPI *DirectSoundCreateFunc)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
|
||||
//typedef HRESULT (WINAPI *DirectSoundEnumerateFunc)(LPDSENUMCALLBACKA, LPVOID);
|
||||
DirectSoundCreateFunc dsound_create = (DirectSoundCreateFunc)GetProcAddress(dll, "DirectSoundCreate");
|
||||
//DirectSoundEnumerateFunc dsound_enumerate = (DirectSoundEnumerateFunc)GetProcAddress(dll, "DirectSoundEnumerateA");
|
||||
if (!dsound_create) {
|
||||
qWarning("Failed to resolve 'DirectSoundCreate'");
|
||||
unloadDll();
|
||||
return false;
|
||||
}
|
||||
DX_ENSURE_OK(dsound_create(NULL/*dev guid*/, &dsound, NULL), false);
|
||||
/* DSSCL_EXCLUSIVE: can modify the settings of the primary buffer, only the sound of this app will be hearable when it will have the focus.
|
||||
*/
|
||||
DX_ENSURE_OK(dsound->SetCooperativeLevel(GetDesktopWindow(), DSSCL_EXCLUSIVE), false);
|
||||
qDebug("DirectSound initialized.");
|
||||
DSCAPS dscaps;
|
||||
memset(&dscaps, 0, sizeof(DSCAPS));
|
||||
dscaps.dwSize = sizeof(DSCAPS);
|
||||
DX_ENSURE_OK(dsound->GetCaps(&dscaps), false);
|
||||
if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
|
||||
qDebug("DirectSound is emulated");
|
||||
|
||||
write_offset = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DirectSound buffer of the required format.
|
||||
*
|
||||
* This function creates the buffer we'll use to play audio.
|
||||
* In DirectSound there are two kinds of buffers:
|
||||
* - the primary buffer: which is the actual buffer that the soundcard plays
|
||||
* - the secondary buffer(s): these buffers are the one actually used by
|
||||
* applications and DirectSound takes care of mixing them into the primary.
|
||||
*
|
||||
* Once you create a secondary buffer, you cannot change its format anymore so
|
||||
* you have to release the current one and create another.
|
||||
*/
|
||||
bool AudioOutputDSound::createDSoundBuffers()
|
||||
{
|
||||
WAVEFORMATEXTENSIBLE wformat;
|
||||
// TODO: Dolby Digital AC3
|
||||
ZeroMemory(&wformat, sizeof(WAVEFORMATEXTENSIBLE));
|
||||
WAVEFORMATEX wf;
|
||||
wf.cbSize = 0;
|
||||
wf.nChannels = format.channels();
|
||||
wf.nSamplesPerSec = format.sampleRate(); // FIXME: use supported values
|
||||
wf.wBitsPerSample = format.bytesPerSample() * 8;
|
||||
wf.nBlockAlign = wf.nChannels * format.bytesPerSample();
|
||||
wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
|
||||
wformat.dwChannelMask = channelLayoutToMS(format.channelLayoutFFmpeg());
|
||||
if (format.channels() > 2) {
|
||||
wf.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
||||
wf.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
wformat.SubFormat = _KSDATAFORMAT_SUBTYPE_PCM;
|
||||
wformat.Samples.wValidBitsPerSample = wf.wBitsPerSample;
|
||||
}
|
||||
if (format.isFloat()) {
|
||||
wf.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
|
||||
wformat.SubFormat = _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
||||
} else {
|
||||
wf.wFormatTag = WAVE_FORMAT_PCM;
|
||||
wformat.SubFormat = _KSDATAFORMAT_SUBTYPE_PCM;
|
||||
}
|
||||
wformat.Format = wf;
|
||||
if (true) {//format.channels() <= 2 && !format.isFloat()) { //openal use this, don't know why
|
||||
// fill in primary sound buffer descriptor
|
||||
DSBUFFERDESC dsbpridesc;
|
||||
memset(&dsbpridesc, 0, sizeof(DSBUFFERDESC));
|
||||
dsbpridesc.dwSize = sizeof(DSBUFFERDESC);
|
||||
dsbpridesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
||||
// create primary buffer and set its format
|
||||
DX_ENSURE(dsound->CreateSoundBuffer(&dsbpridesc, &prim_buf, NULL), (destroy() && false));
|
||||
DX_ENSURE(prim_buf->SetFormat((WAVEFORMATEX *)&wformat), false);
|
||||
}
|
||||
// fill in the secondary sound buffer (=stream buffer) descriptor
|
||||
DSBUFFERDESC dsbdesc;
|
||||
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
|
||||
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
|
||||
dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */
|
||||
| DSBCAPS_GLOBALFOCUS /** Allows background playing */
|
||||
| DSBCAPS_CTRLVOLUME /** volume control enabled */
|
||||
| DSBCAPS_CTRLPOSITIONNOTIFY;
|
||||
dsbdesc.dwBufferBytes = buffer_size*buffer_count;
|
||||
dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wformat;
|
||||
// Needed for 5.1 on emu101k - shit soundblaster
|
||||
if (format.channels() > 2)
|
||||
dsbdesc.dwFlags |= DSBCAPS_LOCHARDWARE;
|
||||
// now create the stream buffer (secondary buffer)
|
||||
HRESULT res = dsound->CreateSoundBuffer(&dsbdesc, &stream_buf, NULL);
|
||||
if (res != DS_OK) {
|
||||
if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE) {
|
||||
// Try without DSBCAPS_LOCHARDWARE
|
||||
dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE;
|
||||
DX_ENSURE_OK(dsound->CreateSoundBuffer(&dsbdesc, &stream_buf, NULL), (destroy() && false));
|
||||
}
|
||||
}
|
||||
qDebug( "Secondary (stream)buffer created");
|
||||
MS_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16);
|
||||
DX_ENSURE(stream_buf->QueryInterface(IID_IDirectSoundNotify, (void**)¬ify), false);
|
||||
notify_event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
QVector<DSBPOSITIONNOTIFY> notification(buffer_count);
|
||||
for (int i = 0; i < buffer_count; ++i) {
|
||||
notification[i].dwOffset = buffer_size*(i+1)-1;
|
||||
notification[i].hEventNotify = notify_event;
|
||||
}
|
||||
//notification[buffer_count].dwOffset = DSBPN_OFFSETSTOP;
|
||||
//notification[buffer_count].hEventNotify = stop_notify_event;
|
||||
DX_ENSURE(notify->SetNotificationPositions(notification.size(), notification.constData()), false);
|
||||
available = true;
|
||||
|
||||
watcher.start();
|
||||
sem.release(buffer_count - sem.available());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace FAV
|
||||
#endif //#if !(REMOVE_AUDIO_OUTPUT_DSOUND)
|
||||
52
project/fm_viewer/fav/AudioOutputNull.cpp
Normal file
52
project/fm_viewer/fav/AudioOutputNull.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AudioOutputBackend.h"
|
||||
#include "mkid.h"
|
||||
#include "factory.h"
|
||||
|
||||
namespace FAV {
|
||||
//TODO: block internally
|
||||
static const char kName[] = "null";
|
||||
class AudioOutputNull : public AudioOutputBackend
|
||||
{
|
||||
public:
|
||||
AudioOutputNull(QObject *parent = 0);
|
||||
QString name() const Q_DECL_OVERRIDE { return QLatin1String(kName);}
|
||||
QString status() const Q_DECL_OVERRIDE {return "";};
|
||||
bool open() Q_DECL_OVERRIDE { return true;}
|
||||
bool close() Q_DECL_OVERRIDE { return true;}
|
||||
// TODO: check channel layout. Null supports channels>2
|
||||
BufferControl bufferControl() const Q_DECL_OVERRIDE { return Blocking;}
|
||||
bool write(const QByteArray&) Q_DECL_OVERRIDE { return true;}
|
||||
bool play() Q_DECL_OVERRIDE { return true;}
|
||||
|
||||
};
|
||||
|
||||
typedef AudioOutputNull AudioOutputBackendNull;
|
||||
static const AudioOutputBackendId AudioOutputBackendId_Null = mkid::id32base36_4<'n', 'u', 'l', 'l'>::value;
|
||||
FACTORY_REGISTER(AudioOutputBackend, Null, kName)
|
||||
|
||||
AudioOutputNull::AudioOutputNull(QObject *parent)
|
||||
: AudioOutputBackend(AudioOutput::DeviceFeatures(), parent)
|
||||
{}
|
||||
|
||||
} //namespace FAV
|
||||
441
project/fm_viewer/fav/AudioOutputOpenAL.cpp
Normal file
441
project/fm_viewer/fav/AudioOutputOpenAL.cpp
Normal file
@@ -0,0 +1,441 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#define REMOVE_AUDIO_OPENAL 1
|
||||
#if !(REMOVE_AUDIO_OPENAL)
|
||||
|
||||
#include "AudioOutputBackend.h"
|
||||
#include "mkid.h"
|
||||
#include "factory.h"
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QWaitCondition>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
#if QTAV_HAVE(CAPI)
|
||||
#define OPENAL_CAPI_NS // CAPI_LINK_OPENAL will override it
|
||||
#include "capi/openal_api.h"
|
||||
#else
|
||||
#if defined(HEADER_OPENAL_PREFIX)
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
#else
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#endif
|
||||
#endif //QTAV_HAVE(CAPI)
|
||||
#include "Logger.h"
|
||||
|
||||
#define UNQUEUE_QUICK 0
|
||||
|
||||
namespace FAV {
|
||||
|
||||
static const char kName[] = "OpenAL";
|
||||
class AudioOutputOpenAL Q_DECL_FINAL: public AudioOutputBackend
|
||||
{
|
||||
public:
|
||||
AudioOutputOpenAL(QObject* parent = 0);
|
||||
QString name() const Q_DECL_FINAL { return QLatin1String(kName);}
|
||||
QString status() const Q_DECL_OVERRIDE {return "";};
|
||||
QString deviceName() const;
|
||||
bool open() Q_DECL_FINAL;
|
||||
bool close() Q_DECL_FINAL;
|
||||
bool isSupported(const AudioFormat& format) const Q_DECL_FINAL;
|
||||
bool isSupported(AudioFormat::SampleFormat sampleFormat) const Q_DECL_FINAL;
|
||||
bool isSupported(AudioFormat::ChannelLayout channelLayout) const Q_DECL_FINAL;
|
||||
protected:
|
||||
BufferControl bufferControl() const Q_DECL_FINAL;
|
||||
bool write(const QByteArray& data) Q_DECL_FINAL;
|
||||
bool play() Q_DECL_FINAL;
|
||||
int getPlayedCount() Q_DECL_FINAL;
|
||||
bool setVolume(qreal value) Q_DECL_FINAL;
|
||||
qreal getVolume() const Q_DECL_FINAL;
|
||||
int getQueued();
|
||||
|
||||
bool openDevice() {
|
||||
if (context)
|
||||
return true;
|
||||
const ALCchar *default_device = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
|
||||
qDebug("OpenAL opening default device: %s", default_device);
|
||||
device = alcOpenDevice(NULL); //parameter: NULL or default_device
|
||||
if (!device) {
|
||||
qWarning("OpenAL failed to open sound device: %s", alcGetString(0, alcGetError(0)));
|
||||
return false;
|
||||
}
|
||||
qDebug("AudioOutputOpenAL creating context...");
|
||||
context = alcCreateContext(device, NULL);
|
||||
alcMakeContextCurrent(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
ALCdevice *device;
|
||||
ALCcontext *context;
|
||||
ALenum format_al;
|
||||
QVector<ALuint> buffer;
|
||||
ALuint source;
|
||||
ALint state;
|
||||
QMutex mutex;
|
||||
QWaitCondition cond;
|
||||
|
||||
// used for 1 context per instance. lock when makeCurrent
|
||||
static QMutex global_mutex;
|
||||
};
|
||||
|
||||
typedef AudioOutputOpenAL AudioOutputBackendOpenAL;
|
||||
static const AudioOutputBackendId AudioOutputBackendId_OpenAL = mkid::id32base36_6<'O', 'p', 'e', 'n', 'A', 'L'>::value;
|
||||
FACTORY_REGISTER(AudioOutputBackend, OpenAL, kName)
|
||||
|
||||
#define AL_ENSURE(expr, ...) \
|
||||
do { \
|
||||
expr; \
|
||||
const ALenum err = alGetError(); \
|
||||
if (err != AL_NO_ERROR) { \
|
||||
qWarning("AudioOutputOpenAL Error>>> " #expr " (%d) : %s", err, alGetString(err)); \
|
||||
return __VA_ARGS__; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define SCOPE_LOCK_CONTEXT() \
|
||||
QMutexLocker ctx_lock(&global_mutex); \
|
||||
Q_UNUSED(ctx_lock); \
|
||||
if (context) \
|
||||
alcMakeContextCurrent(context)
|
||||
|
||||
static ALenum audioFormatToAL(const AudioFormat& fmt)
|
||||
{
|
||||
if (fmt.isPlanar())
|
||||
return 0;
|
||||
typedef union {
|
||||
const char* ext;
|
||||
ALenum fmt;
|
||||
} al_fmt_t;
|
||||
ALenum format = 0;
|
||||
// al functions need a context
|
||||
ALCcontext *ctx = alcGetCurrentContext(); //a context is required for al functions!
|
||||
const int c = fmt.channels();
|
||||
const AudioFormat::SampleFormat spfmt = fmt.sampleFormat(); //TODO: planar formats are fine too
|
||||
if (AudioFormat::SampleFormat_Unsigned8 == spfmt) {
|
||||
static const al_fmt_t u8fmt[] = {
|
||||
{(const char*)AL_FORMAT_MONO8},
|
||||
{(const char*)AL_FORMAT_STEREO8},
|
||||
{(const char*)0},
|
||||
{"AL_FORMAT_QUAD8"},
|
||||
{"AL_FORMAT_REAR8"},
|
||||
{"AL_FORMAT_51CHN8"},
|
||||
{"AL_FORMAT_61CHN8"},
|
||||
{"AL_FORMAT_71CHN8"}
|
||||
};
|
||||
if (c < 3) {
|
||||
format = u8fmt[c-1].fmt;
|
||||
} else if (c > 3 && c <= 8 && ctx) {
|
||||
if (alIsExtensionPresent("AL_EXT_MCFORMATS"))
|
||||
format = alGetEnumValue(u8fmt[c-1].ext);
|
||||
}
|
||||
} else if (AudioFormat::SampleFormat_Signed16 == spfmt) {
|
||||
static const al_fmt_t s16fmt[] = {
|
||||
{(const char*)AL_FORMAT_MONO16},
|
||||
{(const char*)AL_FORMAT_STEREO16},
|
||||
{(const char*)0},
|
||||
{"AL_FORMAT_QUAD16"},
|
||||
{"AL_FORMAT_REAR16"},
|
||||
{"AL_FORMAT_51CHN16"},
|
||||
{"AL_FORMAT_61CHN16"},
|
||||
{"AL_FORMAT_71CHN16"}
|
||||
};
|
||||
if (c < 3) {
|
||||
format = s16fmt[c-1].fmt;
|
||||
} else if (c > 3 && c <= 8 && ctx) {
|
||||
if (alIsExtensionPresent("AL_EXT_MCFORMATS"))
|
||||
format = alGetEnumValue(s16fmt[c-1].ext);
|
||||
}
|
||||
} else if (ctx) {
|
||||
if (AudioFormat::SampleFormat_Float == spfmt) {
|
||||
static const al_fmt_t f32fmt[] = {
|
||||
{"AL_FORMAT_MONO_FLOAT32"},
|
||||
{"AL_FORMAT_STEREO_FLOAT32"},
|
||||
{0},
|
||||
// AL_EXT_MCFORMATS
|
||||
{"AL_FORMAT_QUAD32"},
|
||||
{"AL_FORMAT_REAR32"},
|
||||
{"AL_FORMAT_51CHN32"},
|
||||
{"AL_FORMAT_61CHN32"},
|
||||
{"AL_FORMAT_71CHN32"}
|
||||
};
|
||||
if (c <=8 && f32fmt[c-1].ext) {
|
||||
format = alGetEnumValue(f32fmt[c-1].ext);
|
||||
}
|
||||
} else if (AudioFormat::SampleFormat_Double == spfmt) {
|
||||
if (c < 3) {
|
||||
if (alIsExtensionPresent("AL_EXT_double")) {
|
||||
static const al_fmt_t d64fmt[] = {
|
||||
{"AL_FORMAT_MONO_DOUBLE_EXT"},
|
||||
{"AL_FORMAT_STEREO_DOUBLE_EXT"}
|
||||
};
|
||||
format = alGetEnumValue(d64fmt[c-1].ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ALCenum err = alGetError();
|
||||
if (err != AL_NO_ERROR) {
|
||||
if (ctx)
|
||||
qWarning("OpenAL audioFormatToAL error: %s", alGetString(err));
|
||||
else
|
||||
qWarning("OpenAL audioFormatToAL error (null context): %#x", err);
|
||||
}
|
||||
if (format == 0) {
|
||||
qWarning("AudioOutputOpenAL Error: No OpenAL format available for audio data format %s %s."
|
||||
, qPrintable(fmt.sampleFormatName())
|
||||
, qPrintable(fmt.channelLayoutName()));
|
||||
}
|
||||
qDebug("OpenAL audio format: %#x ch:%d, sample format: %s", format, fmt.channels(), qPrintable(fmt.sampleFormatName()));
|
||||
return format;
|
||||
}
|
||||
|
||||
QMutex AudioOutputOpenAL::global_mutex;
|
||||
|
||||
AudioOutputOpenAL::AudioOutputOpenAL(QObject *parent)
|
||||
: AudioOutputBackend(AudioOutput::SetVolume, parent)
|
||||
, device(0)
|
||||
, context(0)
|
||||
, format_al(AL_FORMAT_STEREO16)
|
||||
, state(0)
|
||||
{
|
||||
#if QTAV_HAVE(CAPI)
|
||||
#ifndef CAPI_LINK_OPENAL
|
||||
if (!openal::capi::loaded()) {
|
||||
available = false;
|
||||
return;
|
||||
}
|
||||
#endif //CAPI_LINK_OPENAL
|
||||
#endif
|
||||
//setDeviceFeatures(AudioOutput::SetVolume);
|
||||
// ensure we have a context to check format support
|
||||
// TODO: AudioOutput::getDevices() => ao.setDevice() => ao.open
|
||||
QVector<QByteArray> _devices;
|
||||
const char *p = NULL;
|
||||
if (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT")) {
|
||||
// ALC_ALL_DEVICES_SPECIFIER maybe not defined
|
||||
p = alcGetString(NULL, alcGetEnumValue(NULL, "ALC_ALL_DEVICES_SPECIFIER"));
|
||||
} else {
|
||||
p = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
|
||||
}
|
||||
while (p && *p) {
|
||||
_devices.push_back(p);
|
||||
p += _devices.last().size() + 1;
|
||||
}
|
||||
qDebug() << _devices;
|
||||
available = openDevice(); //ensure isSupported(AudioFormat) works correctly
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::open()
|
||||
{
|
||||
if (!openDevice())
|
||||
return false;
|
||||
{
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
// alGetString: alsoft needs a context. apple does not
|
||||
qDebug("OpenAL %s vendor: %s; renderer: %s", alGetString(AL_VERSION), alGetString(AL_VENDOR), alGetString(AL_RENDERER));
|
||||
//alcProcessContext(ctx); //used when dealing witg multiple contexts
|
||||
ALCenum err = alcGetError(device);
|
||||
if (err != ALC_NO_ERROR) {
|
||||
qWarning("AudioOutputOpenAL Error: %s", alcGetString(device, err));
|
||||
return false;
|
||||
}
|
||||
qDebug("device: %p, context: %p", device, context);
|
||||
//init params. move to another func?
|
||||
format_al = audioFormatToAL(format);
|
||||
|
||||
buffer.resize(buffer_count);
|
||||
alGenBuffers(buffer.size(), buffer.data());
|
||||
err = alGetError();
|
||||
if (err != AL_NO_ERROR) {
|
||||
qWarning("Failed to generate OpenAL buffers: %s", alGetString(err));
|
||||
goto fail;
|
||||
}
|
||||
alGenSources(1, &source);
|
||||
err = alGetError();
|
||||
if (err != AL_NO_ERROR) {
|
||||
qWarning("Failed to generate OpenAL source: %s", alGetString(err));
|
||||
alDeleteBuffers(buffer.size(), buffer.constData());
|
||||
goto fail;
|
||||
}
|
||||
alSourcei(source, AL_LOOPING, AL_FALSE);
|
||||
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
|
||||
alSourcei(source, AL_ROLLOFF_FACTOR, 0);
|
||||
alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0);
|
||||
alSource3f(source, AL_VELOCITY, 0.0, 0.0, 0.0);
|
||||
alListener3f(AL_POSITION, 0.0, 0.0, 0.0);
|
||||
state = 0;
|
||||
qDebug("AudioOutputOpenAL open ok...");
|
||||
}
|
||||
return true;
|
||||
fail:
|
||||
alcMakeContextCurrent(NULL);
|
||||
alcDestroyContext(context);
|
||||
alcCloseDevice(device);
|
||||
context = 0;
|
||||
device = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::close()
|
||||
{
|
||||
state = 0;
|
||||
if (!context)
|
||||
return true;
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
alSourceStop(source);
|
||||
do {
|
||||
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
||||
} while (alGetError() == AL_NO_ERROR && state == AL_PLAYING);
|
||||
ALint processed = 0; //android need this!! otherwise the value may be undefined
|
||||
alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
|
||||
ALuint buf;
|
||||
while (processed-- > 0) { alSourceUnqueueBuffers(source, 1, &buf); }
|
||||
alDeleteSources(1, &source);
|
||||
alDeleteBuffers(buffer.size(), buffer.constData());
|
||||
|
||||
alcMakeContextCurrent(NULL);
|
||||
qDebug("alcDestroyContext(%p)", context);
|
||||
alcDestroyContext(context);
|
||||
ALCenum err = alcGetError(device);
|
||||
if (err != ALC_NO_ERROR) { //ALC_INVALID_CONTEXT
|
||||
qWarning("AudioOutputOpenAL Failed to destroy context: %s", alcGetString(device, err));
|
||||
return false;
|
||||
}
|
||||
context = 0;
|
||||
if (device) {
|
||||
qDebug("alcCloseDevice(%p)", device);
|
||||
alcCloseDevice(device);
|
||||
// ALC_INVALID_DEVICE now
|
||||
device = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::isSupported(const AudioFormat& format) const
|
||||
{
|
||||
//if (!context)
|
||||
// openDevice(); //not const
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
return !!audioFormatToAL(format);
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::isSupported(AudioFormat::SampleFormat sampleFormat) const
|
||||
{
|
||||
if (sampleFormat == AudioFormat::SampleFormat_Unsigned8 || sampleFormat == AudioFormat::SampleFormat_Signed16)
|
||||
return true;
|
||||
if (IsPlanar(sampleFormat))
|
||||
return false;
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
if (sampleFormat == AudioFormat::SampleFormat_Float)
|
||||
return alIsExtensionPresent("AL_EXT_float32");
|
||||
if (sampleFormat == AudioFormat::SampleFormat_Double)
|
||||
return alIsExtensionPresent("AL_EXT_double");
|
||||
// because preferredChannelLayout() is stereo while s32 only supports >3 channels, so always false
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::isSupported(AudioFormat::ChannelLayout channelLayout) const // FIXME: check
|
||||
{
|
||||
return channelLayout == AudioFormat::ChannelLayout_Mono || channelLayout == AudioFormat::ChannelLayout_Stereo;
|
||||
}
|
||||
|
||||
QString AudioOutputOpenAL::deviceName() const
|
||||
{
|
||||
if (!device)
|
||||
return QString();
|
||||
const ALCchar *name = alcGetString(device, ALC_DEVICE_SPECIFIER);
|
||||
return QString::fromUtf8(name);
|
||||
}
|
||||
|
||||
AudioOutputBackend::BufferControl AudioOutputOpenAL::bufferControl() const
|
||||
{
|
||||
return PlayedCount; //TODO: AL_BYTE_OFFSET
|
||||
}
|
||||
|
||||
// http://kcat.strangesoft.net/openal-tutorial.html
|
||||
bool AudioOutputOpenAL::write(const QByteArray& data)
|
||||
{
|
||||
if (data.isEmpty())
|
||||
return false;
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
ALuint buf = 0;
|
||||
if (state <= 0) { //state used for filling initial data
|
||||
buf = buffer[(-state)%buffer_count];
|
||||
--state;
|
||||
} else {
|
||||
AL_ENSURE(alSourceUnqueueBuffers(source, 1, &buf), false);
|
||||
}
|
||||
AL_ENSURE(alBufferData(buf, format_al, data.constData(), data.size(), format.sampleRate()), false);
|
||||
AL_ENSURE(alSourceQueueBuffers(source, 1, &buf), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::play()
|
||||
{
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
||||
if (state != AL_PLAYING) {
|
||||
qDebug("AudioOutputOpenAL: !AL_PLAYING alSourcePlay");
|
||||
alSourcePlay(source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int AudioOutputOpenAL::getPlayedCount()
|
||||
{
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
ALint processed = 0;
|
||||
alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
|
||||
return processed;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::setVolume(qreal value)
|
||||
{
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
AL_ENSURE(alListenerf(AL_GAIN, value), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
qreal AudioOutputOpenAL::getVolume() const
|
||||
{
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
ALfloat v = 1.0;
|
||||
alGetListenerf(AL_GAIN, &v);
|
||||
ALenum err = alGetError();
|
||||
if (err != AL_NO_ERROR) {
|
||||
qWarning("AudioOutputOpenAL Error>>> getVolume (%d) : %s", err, alGetString(err));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
int AudioOutputOpenAL::getQueued()
|
||||
{
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
ALint queued = 0;
|
||||
alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
|
||||
return queued;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
#endif // #if !(REMOVE_AUDIO_OPENAL)
|
||||
465
project/fm_viewer/fav/AudioOutputOpenSL.cpp
Normal file
465
project/fm_viewer/fav/AudioOutputOpenSL.cpp
Normal file
@@ -0,0 +1,465 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2014)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#define REMOVE_AUDIO_OPENSL 1
|
||||
#if !(REMOVE_AUDIO_OPENSL)
|
||||
|
||||
#include "AudioOutputBackend.h"
|
||||
#include <QtCore/QSemaphore>
|
||||
#include <QtCore/QThread>
|
||||
#include <SLES/OpenSLES.h>
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#include <SLES/OpenSLES_AndroidConfiguration.h>
|
||||
#include <sys/system_properties.h>
|
||||
#endif
|
||||
#include "mkid.h"
|
||||
#include "factory.h"
|
||||
#include "Logger.h"
|
||||
|
||||
// TODO: native sample rate, so AUDIO_OUTPUT_FLAG_FAST is enabled
|
||||
namespace FAV {
|
||||
|
||||
// OpenSL 1.1, or __ANDROID_API__ >= 21
|
||||
#ifndef SL_DATAFORMAT_PCM_EX
|
||||
#define SL_PCM_REPRESENTATION_SIGNED_INT ((SLuint32) 0x00000001)
|
||||
#define SL_PCM_REPRESENTATION_UNSIGNED_INT ((SLuint32) 0x00000002)
|
||||
#define SL_PCM_REPRESENTATION_FLOAT ((SLuint32) 0x00000003)
|
||||
#define SL_DATAFORMAT_PCM_EX ((SLuint32) 0x00000004)
|
||||
struct SLDataFormat_PCM_EX : SLDataFormat_PCM {
|
||||
SLuint32 representation;
|
||||
};
|
||||
#endif
|
||||
|
||||
static const char kName[] = "OpenSL";
|
||||
class AudioOutputOpenSL Q_DECL_FINAL: public AudioOutputBackend
|
||||
{
|
||||
public:
|
||||
AudioOutputOpenSL(QObject *parent = 0);
|
||||
~AudioOutputOpenSL();
|
||||
|
||||
int engineVersion() const { return m_sl_major*100+m_sl_minor*10+m_sl_step;}
|
||||
QString name() const Q_DECL_OVERRIDE { return QLatin1String(kName);}
|
||||
QString status() const Q_DECL_OVERRIDE {return "";};
|
||||
bool isSupported(const AudioFormat& format) const Q_DECL_OVERRIDE;
|
||||
bool isSupported(AudioFormat::SampleFormat sampleFormat) const Q_DECL_OVERRIDE;
|
||||
bool isSupported(AudioFormat::ChannelLayout channelLayout) const Q_DECL_OVERRIDE;
|
||||
bool open() Q_DECL_OVERRIDE;
|
||||
bool close() Q_DECL_OVERRIDE;
|
||||
BufferControl bufferControl() const Q_DECL_OVERRIDE;
|
||||
void onCallback() Q_DECL_OVERRIDE;
|
||||
void acquireNextBuffer() Q_DECL_OVERRIDE;
|
||||
bool write(const QByteArray& data) Q_DECL_OVERRIDE;
|
||||
bool play() Q_DECL_OVERRIDE;
|
||||
//default return -1. means not the control
|
||||
int getPlayedCount() Q_DECL_OVERRIDE;
|
||||
bool setVolume(qreal value) Q_DECL_OVERRIDE;
|
||||
qreal getVolume() const Q_DECL_OVERRIDE;
|
||||
bool setMute(bool value = true) Q_DECL_OVERRIDE;
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
static void bufferQueueCallbackAndroid(SLAndroidSimpleBufferQueueItf bufferQueue, void *context);
|
||||
#endif
|
||||
static void bufferQueueCallback(SLBufferQueueItf bufferQueue, void *context);
|
||||
static void playCallback(SLPlayItf player, void *ctx, SLuint32 event);
|
||||
private:
|
||||
SLDataFormat_PCM_EX audioFormatToSL(const AudioFormat &format);
|
||||
|
||||
SLObjectItf engineObject;
|
||||
SLEngineItf engine;
|
||||
SLEngineCapabilitiesItf m_cap;
|
||||
SLObjectItf m_outputMixObject;
|
||||
SLObjectItf m_playerObject;
|
||||
SLPlayItf m_playItf;
|
||||
SLVolumeItf m_volumeItf;
|
||||
SLBufferQueueItf m_bufferQueueItf;
|
||||
#ifdef Q_OS_ANDROID
|
||||
SLAndroidSimpleBufferQueueItf m_bufferQueueItf_android; // supports decoding, recording
|
||||
#endif
|
||||
bool m_android;
|
||||
int m_android_api_level;
|
||||
SLint16 m_sl_major, m_sl_minor, m_sl_step;
|
||||
SLint32 m_streamType;
|
||||
quint32 buffers_queued;
|
||||
QSemaphore sem;
|
||||
|
||||
// Enqueue does not copy data. We MUST keep the data until it is played out
|
||||
int queue_data_write;
|
||||
QByteArray queue_data;
|
||||
};
|
||||
|
||||
typedef AudioOutputOpenSL AudioOutputBackendOpenSL;
|
||||
static const AudioOutputBackendId AudioOutputBackendId_OpenSL = mkid::id32base36_6<'O', 'p', 'e', 'n', 'S', 'L'>::value;
|
||||
FACTORY_REGISTER(AudioOutputBackend, OpenSL, kName)
|
||||
|
||||
#define SL_ENSURE(FUNC, ...) \
|
||||
do { \
|
||||
SLresult ret = FUNC; \
|
||||
if (ret != SL_RESULT_SUCCESS) { \
|
||||
qWarning("AudioOutputOpenSL Error>>> " #FUNC " (%lu)", ret); \
|
||||
return __VA_ARGS__; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
SLDataFormat_PCM_EX AudioOutputOpenSL::audioFormatToSL(const AudioFormat &format)
|
||||
{
|
||||
SLDataFormat_PCM_EX format_pcm;
|
||||
if (format.isFloat()) {
|
||||
if (format.sampleSize() == sizeof(float))
|
||||
format_pcm.representation = SL_PCM_REPRESENTATION_FLOAT;
|
||||
} else if (format.isUnsigned()) {
|
||||
format_pcm.representation = SL_PCM_REPRESENTATION_UNSIGNED_INT;
|
||||
} else {
|
||||
format_pcm.representation = SL_PCM_REPRESENTATION_SIGNED_INT;
|
||||
}
|
||||
format_pcm.formatType = m_android_api_level >= 21 || engineVersion() >= 110 ? SL_DATAFORMAT_PCM_EX : SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = format.channels();
|
||||
format_pcm.samplesPerSec = format.sampleRate() * 1000;
|
||||
format_pcm.bitsPerSample = format.bytesPerSample()*8; //raw sample size, e.g. s24 is 24. currently we do not support such formats
|
||||
format_pcm.containerSize = format_pcm.bitsPerSample;
|
||||
// TODO: more layouts
|
||||
format_pcm.channelMask = format.channels() == 1 ? SL_SPEAKER_FRONT_CENTER : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
#ifdef SL_BYTEORDER_NATIVE
|
||||
format_pcm.endianness = SL_BYTEORDER_NATIVE;
|
||||
#else
|
||||
union { unsigned short num; char buf[sizeof(unsigned short)]; } endianness;
|
||||
endianness.num = 1;
|
||||
format_pcm.endianness = endianness.buf[0] ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN;
|
||||
#endif
|
||||
return format_pcm;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void AudioOutputOpenSL::bufferQueueCallbackAndroid(SLAndroidSimpleBufferQueueItf bufferQueue, void *context)
|
||||
{
|
||||
#if 0
|
||||
SLAndroidSimpleBufferQueueState state;
|
||||
(*bufferQueue)->GetState(bufferQueue, &state);
|
||||
qDebug(">>>>>>>>>>>>>>bufferQueueCallback state.count=%lu .playIndex=%lu", state.count, state.playIndex);
|
||||
#endif
|
||||
AudioOutputOpenSL *ao = reinterpret_cast<AudioOutputOpenSL*>(context);
|
||||
if (ao->bufferControl() & AudioOutputBackend::CountCallback) {
|
||||
ao->onCallback();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
void AudioOutputOpenSL::bufferQueueCallback(SLBufferQueueItf bufferQueue, void *context)
|
||||
{
|
||||
#if 0
|
||||
SLBufferQueueState state;
|
||||
(*bufferQueue)->GetState(bufferQueue, &state);
|
||||
qDebug(">>>>>>>>>>>>>>bufferQueueCallback state.count=%lu .playIndex=%lu", state.count, state.playIndex);
|
||||
#endif
|
||||
AudioOutputOpenSL *ao = reinterpret_cast<AudioOutputOpenSL*>(context);
|
||||
if (ao->bufferControl() & AudioOutputBackend::CountCallback) {
|
||||
ao->onCallback();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputOpenSL::playCallback(SLPlayItf player, void *ctx, SLuint32 event)
|
||||
{
|
||||
Q_UNUSED(player);
|
||||
Q_UNUSED(ctx);
|
||||
Q_UNUSED(event);
|
||||
//qDebug("---------%s event=%lu", __FUNCTION__, event);
|
||||
}
|
||||
|
||||
AudioOutputOpenSL::AudioOutputOpenSL(QObject *parent)
|
||||
: AudioOutputBackend(AudioOutput::DeviceFeatures()
|
||||
|AudioOutput::SetVolume
|
||||
|AudioOutput::SetMute, parent)
|
||||
, m_outputMixObject(0)
|
||||
, m_playerObject(0)
|
||||
, m_playItf(0)
|
||||
, m_volumeItf(0)
|
||||
, m_bufferQueueItf(0)
|
||||
, m_bufferQueueItf_android(0)
|
||||
, m_android(false)
|
||||
, m_android_api_level(0)
|
||||
, m_sl_major(0)
|
||||
, m_sl_minor(0)
|
||||
, m_sl_step(0)
|
||||
, m_streamType(-1)
|
||||
, buffers_queued(0)
|
||||
, queue_data_write(0)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
char v[PROP_VALUE_MAX+1];
|
||||
__system_property_get("ro.build.version.sdk", v);
|
||||
m_android_api_level = atoi(v);
|
||||
#endif
|
||||
available = false;
|
||||
SLEngineOption opt[] = {(SLuint32) SL_ENGINEOPTION_THREADSAFE, (SLuint32) SL_BOOLEAN_TRUE};
|
||||
SL_ENSURE(slCreateEngine(&engineObject, 1, opt, 0, NULL, NULL)); // SLEngineOption is ignored by android
|
||||
SL_ENSURE((*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE));
|
||||
SL_ENSURE((*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engine));
|
||||
available = true;
|
||||
|
||||
if ((*engineObject)->GetInterface(engineObject, SL_IID_ENGINECAPABILITIES, &m_cap) == SL_RESULT_SUCCESS) { // SL_RESULT_FEATURE_UNSUPPORTED
|
||||
if ((*m_cap)->QueryAPIVersion(m_cap, &m_sl_major, &m_sl_minor, &m_sl_step) == SL_RESULT_SUCCESS) {
|
||||
printf("OpenSL version: %d.%d.%d\n", m_sl_major, m_sl_minor, m_sl_step);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AudioOutputOpenSL::~AudioOutputOpenSL()
|
||||
{
|
||||
if (engineObject)
|
||||
(*engineObject)->Destroy(engineObject);
|
||||
}
|
||||
|
||||
bool AudioOutputOpenSL::isSupported(const AudioFormat& format) const
|
||||
{
|
||||
return isSupported(format.sampleFormat()) && isSupported(format.channelLayout());
|
||||
}
|
||||
|
||||
bool AudioOutputOpenSL::isSupported(AudioFormat::SampleFormat sampleFormat) const
|
||||
{
|
||||
if (sampleFormat == AudioFormat::SampleFormat_Unsigned8 || sampleFormat == AudioFormat::SampleFormat_Signed16) // TODO: android api 21 supports s32?
|
||||
return true;
|
||||
if (m_android_api_level < 21 || (engineVersion() > 0 && engineVersion() < 110))
|
||||
return false;
|
||||
if (!IsFloat(sampleFormat) || IsPlanar(sampleFormat))
|
||||
return false;
|
||||
return RawSampleSize(sampleFormat) == sizeof(float); // TODO: test s32 etc
|
||||
}
|
||||
|
||||
bool AudioOutputOpenSL::isSupported(AudioFormat::ChannelLayout channelLayout) const
|
||||
{
|
||||
return channelLayout == AudioFormat::ChannelLayout_Mono || channelLayout == AudioFormat::ChannelLayout_Stereo;
|
||||
}
|
||||
|
||||
AudioOutputBackend::BufferControl AudioOutputOpenSL::bufferControl() const
|
||||
{
|
||||
return CountCallback;//BufferControl(Callback | PlayedCount);
|
||||
}
|
||||
|
||||
void AudioOutputOpenSL::onCallback()
|
||||
{
|
||||
if (bufferControl() & CountCallback)
|
||||
sem.release();
|
||||
}
|
||||
|
||||
void AudioOutputOpenSL::acquireNextBuffer()
|
||||
{
|
||||
if (bufferControl() & CountCallback)
|
||||
sem.acquire();
|
||||
}
|
||||
|
||||
bool AudioOutputOpenSL::open()
|
||||
{
|
||||
queue_data.resize(buffer_size*buffer_count);
|
||||
SLDataLocator_BufferQueue bufferQueueLocator = { SL_DATALOCATOR_BUFFERQUEUE, (SLuint32)buffer_count };
|
||||
SLDataFormat_PCM_EX pcmFormat = audioFormatToSL(format);
|
||||
SLDataSource audioSrc = { &bufferQueueLocator, &pcmFormat };
|
||||
#ifdef Q_OS_ANDROID
|
||||
SLDataLocator_AndroidSimpleBufferQueue bufferQueueLocator_android = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, (SLuint32)buffer_count };
|
||||
if (m_android)
|
||||
audioSrc.pLocator = &bufferQueueLocator_android;
|
||||
#endif
|
||||
// OutputMix
|
||||
SL_ENSURE((*engine)->CreateOutputMix(engine, &m_outputMixObject, 0, NULL, NULL), false);
|
||||
SL_ENSURE((*m_outputMixObject)->Realize(m_outputMixObject, SL_BOOLEAN_FALSE), false);
|
||||
SLDataLocator_OutputMix outputMixLocator = { SL_DATALOCATOR_OUTPUTMIX, m_outputMixObject };
|
||||
SLDataSink audioSink = { &outputMixLocator, NULL };
|
||||
|
||||
const SLInterfaceID ids[] = { SL_IID_BUFFERQUEUE, SL_IID_VOLUME
|
||||
#ifdef Q_OS_ANDROID
|
||||
, SL_IID_ANDROIDCONFIGURATION
|
||||
#endif
|
||||
};
|
||||
const SLboolean req[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE
|
||||
#ifdef Q_OS_ANDROID
|
||||
, SL_BOOLEAN_TRUE
|
||||
#endif
|
||||
};
|
||||
// AudioPlayer
|
||||
SL_ENSURE((*engine)->CreateAudioPlayer(engine, &m_playerObject, &audioSrc, &audioSink, sizeof(ids)/sizeof(ids[0]), ids, req), false);
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (m_android) {
|
||||
m_streamType = SL_ANDROID_STREAM_MEDIA;
|
||||
SLAndroidConfigurationItf cfg;
|
||||
if ((*m_playerObject)->GetInterface(m_playerObject, SL_IID_ANDROIDCONFIGURATION, &cfg)) {
|
||||
(*cfg)->SetConfiguration(cfg, SL_ANDROID_KEY_STREAM_TYPE, &m_streamType, sizeof(SLint32));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
SL_ENSURE((*m_playerObject)->Realize(m_playerObject, SL_BOOLEAN_FALSE), false);
|
||||
// Buffer interface
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (m_android) {
|
||||
SL_ENSURE((*m_playerObject)->GetInterface(m_playerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &m_bufferQueueItf_android), false);
|
||||
SL_ENSURE((*m_bufferQueueItf_android)->RegisterCallback(m_bufferQueueItf_android, AudioOutputOpenSL::bufferQueueCallbackAndroid, this), false);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
SL_ENSURE((*m_playerObject)->GetInterface(m_playerObject, SL_IID_BUFFERQUEUE, &m_bufferQueueItf), false);
|
||||
SL_ENSURE((*m_bufferQueueItf)->RegisterCallback(m_bufferQueueItf, AudioOutputOpenSL::bufferQueueCallback, this), false);
|
||||
}
|
||||
// Play interface
|
||||
SL_ENSURE((*m_playerObject)->GetInterface(m_playerObject, SL_IID_PLAY, &m_playItf), false);
|
||||
// call when SL_PLAYSTATE_STOPPED
|
||||
SL_ENSURE((*m_playItf)->RegisterCallback(m_playItf, AudioOutputOpenSL::playCallback, this), false);
|
||||
SL_ENSURE((*m_playerObject)->GetInterface(m_playerObject, SL_IID_VOLUME, &m_volumeItf), false);
|
||||
#if 0
|
||||
SLuint32 mask = SL_PLAYEVENT_HEADATEND;
|
||||
// TODO: what does this do?
|
||||
SL_ENSURE((*m_playItf)->SetPositionUpdatePeriod(m_playItf, 100), false);
|
||||
SL_ENSURE((*m_playItf)->SetCallbackEventsMask(m_playItf, mask), false);
|
||||
#endif
|
||||
// Volume interface
|
||||
//SL_ENSURE((*m_playerObject)->GetInterface(m_playerObject, SL_IID_VOLUME, &m_volumeItf), false);
|
||||
|
||||
/*
|
||||
* DO NOT call sem.release(buffer_count - sem.available()) because playInitialData() will enqueue buffers and then sem.release() is called in callback.
|
||||
* Otherwise, Enqueue() the 1st(or more) real buffer may report SL_RESULT_BUFFER_INSUFFICIENT and noise will be played.
|
||||
* Can not Enqueue() internally here because buffers are managed in AudioOutput to ensure 0-copy
|
||||
*/
|
||||
// sem.release(buffer_count - sem.available());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenSL::close()
|
||||
{
|
||||
if (m_playItf)
|
||||
(*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_STOPPED);
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (m_android) {
|
||||
if (m_bufferQueueItf_android && SL_RESULT_SUCCESS != (*m_bufferQueueItf_android)->Clear(m_bufferQueueItf_android))
|
||||
qWarning("Unable to clear buffer");
|
||||
m_bufferQueueItf_android = NULL;
|
||||
}
|
||||
#endif
|
||||
if (m_bufferQueueItf && SL_RESULT_SUCCESS != (*m_bufferQueueItf)->Clear(m_bufferQueueItf))
|
||||
qWarning("Unable to clear buffer");
|
||||
|
||||
if (m_playerObject) {
|
||||
(*m_playerObject)->Destroy(m_playerObject);
|
||||
m_playerObject = NULL;
|
||||
}
|
||||
if (m_outputMixObject) {
|
||||
(*m_outputMixObject)->Destroy(m_outputMixObject);
|
||||
m_outputMixObject = NULL;
|
||||
}
|
||||
|
||||
m_playItf = NULL;
|
||||
m_volumeItf = NULL;
|
||||
m_bufferQueueItf = NULL;
|
||||
queue_data.clear();
|
||||
queue_data_write = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenSL::write(const QByteArray& data)
|
||||
{
|
||||
// assume data.size() <= buffer_size. It's true in QtAV
|
||||
const int s = qMin(queue_data.size() - queue_data_write, data.size());
|
||||
// assume data.size() <= buffer_size. It's true in QtAV
|
||||
if (s < data.size())
|
||||
queue_data_write = 0;
|
||||
memcpy((char*)queue_data.constData() + queue_data_write, data.constData(), data.size());
|
||||
//qDebug("enqueue %p, queue_data_write: %d/%d available:%d", data.constData(), queue_data_write, queue_data.size(), sem.available());
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (m_android)
|
||||
SL_ENSURE((*m_bufferQueueItf_android)->Enqueue(m_bufferQueueItf_android, queue_data.constData() + queue_data_write, data.size()), false);
|
||||
else
|
||||
#endif
|
||||
SL_ENSURE((*m_bufferQueueItf)->Enqueue(m_bufferQueueItf, queue_data.constData() + queue_data_write, data.size()), false);
|
||||
buffers_queued++;
|
||||
queue_data_write += data.size();
|
||||
if (queue_data_write == queue_data.size())
|
||||
queue_data_write = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenSL::play()
|
||||
{
|
||||
SLuint32 state = SL_PLAYSTATE_PLAYING;
|
||||
(*m_playItf)->GetPlayState(m_playItf, &state);
|
||||
if (state == SL_PLAYSTATE_PLAYING)
|
||||
return true;
|
||||
SL_ENSURE((*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PLAYING), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
int AudioOutputOpenSL::getPlayedCount()
|
||||
{
|
||||
int processed = buffers_queued;
|
||||
SLuint32 count = 0;
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (m_android) {
|
||||
SLAndroidSimpleBufferQueueState state;
|
||||
(*m_bufferQueueItf_android)->GetState(m_bufferQueueItf_android, &state);
|
||||
count = state.count;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
SLBufferQueueState state;
|
||||
(*m_bufferQueueItf)->GetState(m_bufferQueueItf, &state);
|
||||
count = state.count;
|
||||
}
|
||||
buffers_queued = count;
|
||||
processed -= count;
|
||||
return processed;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenSL::setVolume(qreal value)
|
||||
{
|
||||
if (!m_volumeItf)
|
||||
return false;
|
||||
SLmillibel v = 0;
|
||||
if (qFuzzyIsNull(value))
|
||||
v = SL_MILLIBEL_MIN;
|
||||
else if (!qFuzzyCompare(value, 1.0))
|
||||
v = 20.0*log10(value)*100.0;
|
||||
SLmillibel vmax = SL_MILLIBEL_MAX;
|
||||
SL_ENSURE((*m_volumeItf)->GetMaxVolumeLevel(m_volumeItf, &vmax), false);
|
||||
if (vmax < v) {
|
||||
qDebug("OpenSL does not support volume: %f %d/%d. sw scale will be used", value, v, vmax);
|
||||
return false;
|
||||
}
|
||||
SL_ENSURE((*m_volumeItf)->SetVolumeLevel(m_volumeItf, v), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
qreal AudioOutputOpenSL::getVolume() const
|
||||
{
|
||||
if (!m_volumeItf)
|
||||
return false;
|
||||
SLmillibel v = 0;
|
||||
SL_ENSURE((*m_volumeItf)->GetVolumeLevel(m_volumeItf, &v), 1.0);
|
||||
if (v == SL_MILLIBEL_MIN)
|
||||
return 0;
|
||||
return pow(10.0, qreal(v)/2000.0);
|
||||
}
|
||||
|
||||
bool AudioOutputOpenSL::setMute(bool value)
|
||||
{
|
||||
if (!m_volumeItf)
|
||||
return false;
|
||||
SL_ENSURE((*m_volumeItf)->SetMute(m_volumeItf, value), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
#endif // #if !(REMOVE_AUDIO_OPENSL)
|
||||
291
project/fm_viewer/fav/AudioOutputPortAudio.cpp
Normal file
291
project/fm_viewer/fav/AudioOutputPortAudio.cpp
Normal file
@@ -0,0 +1,291 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#define REMOVE_AUDIO_PORTAUDIO 0
|
||||
#if !(REMOVE_AUDIO_PORTAUDIO)
|
||||
|
||||
#include "AudioOutputBackend.h"
|
||||
#include "mkid.h"
|
||||
#include "factory.h"
|
||||
#include <portaudio.h>
|
||||
#include "Logger.h"
|
||||
#if (SELECT_AUDIO_BACKEND)
|
||||
#include "../rm_settings.h"
|
||||
#endif
|
||||
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
#include <QElapsedTimer>
|
||||
QElapsedTimer gPALoadingTimer;
|
||||
#endif
|
||||
|
||||
namespace FAV {
|
||||
|
||||
|
||||
|
||||
static const char kName[] = "PortAudio";
|
||||
class AudioOutputPortAudio Q_DECL_FINAL: public AudioOutputBackend
|
||||
{
|
||||
public:
|
||||
AudioOutputPortAudio(QObject *parent = 0);
|
||||
~AudioOutputPortAudio();
|
||||
QString name() const Q_DECL_FINAL { return QString::fromLatin1(kName);}
|
||||
QString status() const Q_DECL_OVERRIDE {return _status;};
|
||||
bool open() Q_DECL_FINAL;
|
||||
bool close() Q_DECL_FINAL;
|
||||
virtual BufferControl bufferControl() const Q_DECL_FINAL;
|
||||
virtual bool write(const QByteArray& data) Q_DECL_FINAL;
|
||||
virtual bool play() Q_DECL_FINAL { return true;}
|
||||
static bool initialized;
|
||||
private:
|
||||
PaStreamParameters *outputParameters;
|
||||
PaStream *stream;
|
||||
double outputLatency;
|
||||
QString _status;
|
||||
};
|
||||
|
||||
typedef AudioOutputPortAudio AudioOutputBackendPortAudio;
|
||||
static const AudioOutputBackendId AudioOutputBackendId_PortAudio = mkid::id32base36_5<'P', 'o', 'r', 't', 'A'>::value;
|
||||
FACTORY_REGISTER(AudioOutputBackend, PortAudio, kName)
|
||||
bool AudioOutputPortAudio::initialized = false;
|
||||
AudioOutputPortAudio::AudioOutputPortAudio(QObject *parent)
|
||||
: AudioOutputBackend(AudioOutput::NoFeature, parent)
|
||||
// , initialized(false)
|
||||
, outputParameters(new PaStreamParameters)
|
||||
, stream(0)
|
||||
{
|
||||
//qInfo() << "PORTAUDIO VERSION:" << Pa_GetVersionText() << __FUNCTION__;
|
||||
|
||||
_status = "\n";
|
||||
PaError err = paNoError;
|
||||
if ((err = Pa_Initialize()) != paNoError) {
|
||||
qWarning("Error when init portaudio: %s", Pa_GetErrorText(err));
|
||||
_status += " PORTAUDIO INIT ERROR\n";
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
|
||||
int numDevices = Pa_GetDeviceCount();
|
||||
_status += QString().sprintf("\n PORTAUDIO NUM DEVICE:%d\n",numDevices);
|
||||
//qInfo() << QString().sprintf("\n PORTAUDIO NUM DEVICE:%d\n",numDevices);
|
||||
|
||||
#if (SELECT_AUDIO_BACKEND)
|
||||
int selected = -1;
|
||||
QString selectedName = RMSettings::instance()->AudioDevice();
|
||||
//qInfo() << "selectedName:" << selectedName << __FUNCTION__;
|
||||
|
||||
#endif
|
||||
|
||||
for (int i = 0 ; i < numDevices ; ++i) {
|
||||
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
|
||||
if (deviceInfo && deviceInfo->maxOutputChannels > 0) {
|
||||
const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
|
||||
QString name = QString::fromUtf8(hostApiInfo->name) + QStringLiteral(": ") + QString::fromUtf8(deviceInfo->name); // QString::fromLocal8Bit(deviceInfo->name)
|
||||
|
||||
//_status += (QString().sprintf(" PORTAUDIO DEVICE %d(%d):",i,hostApiInfo->defaultOutputDevice) + name + "\n");
|
||||
|
||||
#if (SELECT_AUDIO_BACKEND)
|
||||
if(name.startsWith("Windows ")) {
|
||||
name = name.remove(0,QString("Windows ").length());
|
||||
}
|
||||
|
||||
if(!selectedName.isEmpty() && selectedName == name) {
|
||||
selected = i;
|
||||
}
|
||||
#endif
|
||||
|
||||
//qInfo() << (QString().sprintf(" PORTAUDIO DEVICE %d(%d):",i,hostApiInfo->defaultOutputDevice) + name) << "MAX CHANNEL:" << deviceInfo->maxOutputChannels << __FUNCTION__;
|
||||
|
||||
// if(i == 3) {
|
||||
// hostApiInfo->defaultOutputDevice = 4;
|
||||
// }
|
||||
|
||||
// for(int j=0;j<hostApiInfo->deviceCount;j++) {
|
||||
|
||||
// }
|
||||
|
||||
//qDebug("audio device %d: %s", i, name.toUtf8().constData());
|
||||
//qDebug("max in/out channels: %d/%d", deviceInfo->maxInputChannels, deviceInfo->maxOutputChannels);
|
||||
}
|
||||
}
|
||||
memset(outputParameters, 0, sizeof(PaStreamParameters));
|
||||
outputParameters->device = Pa_GetDefaultOutputDevice();
|
||||
#if (SELECT_AUDIO_BACKEND)
|
||||
if(selected >= 0) {
|
||||
outputParameters->device = selected;
|
||||
}
|
||||
#endif // SELECT_AUDIO_BACKEND
|
||||
//qInfo() << "SELECTED:" << outputParameters->device << __FUNCTION__;
|
||||
if (outputParameters->device == paNoDevice) {
|
||||
qWarning("PortAudio get device error!");
|
||||
return;
|
||||
}
|
||||
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(outputParameters->device);
|
||||
_status += (" PORTAUDIO SELECTED:" + QString::fromLocal8Bit(deviceInfo->name) + "\n");
|
||||
qDebug("DEFAULT max in/out channels: %d/%d", deviceInfo->maxInputChannels, deviceInfo->maxOutputChannels);
|
||||
qDebug("audio device: %s", QString::fromUtf8(Pa_GetDeviceInfo(outputParameters->device)->name).toUtf8().constData());
|
||||
outputParameters->hostApiSpecificStreamInfo = NULL;
|
||||
outputParameters->suggestedLatency = Pa_GetDeviceInfo(outputParameters->device)->defaultHighOutputLatency;
|
||||
}
|
||||
|
||||
AudioOutputPortAudio::~AudioOutputPortAudio()
|
||||
{
|
||||
if (outputParameters) {
|
||||
delete outputParameters;
|
||||
outputParameters = 0;
|
||||
}
|
||||
//Do NOT call this if init failed. See document
|
||||
// WAAAAAAAA!!!!!
|
||||
if (initialized) {
|
||||
Pa_Terminate();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AudioOutputBackend::BufferControl AudioOutputPortAudio::bufferControl() const
|
||||
{
|
||||
return Blocking;
|
||||
}
|
||||
|
||||
bool AudioOutputPortAudio::write(const QByteArray& data)
|
||||
{
|
||||
if (Pa_IsStreamStopped(stream))
|
||||
Pa_StartStream(stream);
|
||||
PaError err = Pa_WriteStream(stream, data.constData(), data.size()/format.channels()/format.bytesPerSample());
|
||||
if (err == paUnanticipatedHostError) {
|
||||
//qInfo() << Pa_GetErrorText(err) << __FUNCTION__;
|
||||
//qWarning("Write portaudio stream error: %s", Pa_GetErrorText(err));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//TODO: what about planar, int8, int24 etc that FFmpeg or Pa not support?
|
||||
static int toPaSampleFormat(AudioFormat::SampleFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case AudioFormat::SampleFormat_Unsigned8:
|
||||
return paUInt8;
|
||||
case AudioFormat::SampleFormat_Signed16:
|
||||
return paInt16;
|
||||
case AudioFormat::SampleFormat_Signed32:
|
||||
return paInt32;
|
||||
case AudioFormat::SampleFormat_Float:
|
||||
return paFloat32;
|
||||
default:
|
||||
return paCustomFormat;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO: call open after audio format changed?
|
||||
bool AudioOutputPortAudio::open()
|
||||
{
|
||||
outputParameters->sampleFormat = toPaSampleFormat(format.sampleFormat());
|
||||
outputParameters->channelCount = format.channels();
|
||||
//qInfo() << "PaSampleFormat:" << outputParameters->sampleFormat << " CHANNEL:" << outputParameters->channelCount << "SR:" << format.sampleRate();
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
gPALoadingTimer.start();
|
||||
#endif
|
||||
// buffer = 0
|
||||
PaError err = Pa_OpenStream(&stream, NULL, outputParameters, format.sampleRate(), 0 , paNoFlag, NULL, NULL);
|
||||
if(err == -9997) {
|
||||
err = Pa_OpenStream(&stream, NULL, outputParameters, 48000, 0, paNoFlag, NULL, NULL);
|
||||
}
|
||||
if (err != paNoError) {
|
||||
qInfo() << "Pa_OpenStream ERROR:" << err << Pa_GetErrorText(err) << __FUNCTION__;
|
||||
qWarning("Open portaudio stream error: %s", Pa_GetErrorText(err));
|
||||
return false;
|
||||
}
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
//qInfo() << "PA ELAPSED:" << gPALoadingTimer.elapsed();
|
||||
#endif
|
||||
outputLatency = Pa_GetStreamInfo(stream)->outputLatency;
|
||||
#if (FILE_LOADING_TIMELAPSE)
|
||||
//qInfo() << "PA ELAPSED:" << gPALoadingTimer.elapsed();
|
||||
#endif
|
||||
|
||||
//qInfo() << "OPEN STREAM OK SR:" << format.sampleRate() << __FUNCTION__;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputPortAudio::close()
|
||||
{
|
||||
if (!stream) {
|
||||
return true;
|
||||
}
|
||||
PaError err = Pa_StopStream(stream); //may be already stopped: paStreamIsStopped
|
||||
if (err != paNoError) {
|
||||
qWarning("Stop portaudio stream error: %s", Pa_GetErrorText(err));
|
||||
//return err == paStreamIsStopped;
|
||||
}
|
||||
err = Pa_CloseStream(stream);
|
||||
if (err != paNoError) {
|
||||
qWarning("Close portaudio stream error: %s", Pa_GetErrorText(err));
|
||||
return false;
|
||||
}
|
||||
stream = NULL;
|
||||
// DO NOT TERMINATE!!!!
|
||||
// if (initialized)
|
||||
// Pa_Terminate(); //Do NOT call this if init failed. See document
|
||||
//qInfo() << "AudioOutputPortAudio::close() done";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if (SELECT_AUDIO_BACKEND)
|
||||
QList<QPair<QString,int>> globalSounddeviceList()
|
||||
{
|
||||
QList<QPair<QString,int>> ret;
|
||||
|
||||
if(!AudioOutputPortAudio::initialized) {
|
||||
//qInfo() << "INIT PORT AUDIO" << __FUNCTION__;
|
||||
PaError err = paNoError;
|
||||
if ((err = Pa_Initialize()) != paNoError) {
|
||||
qWarning("Error when init portaudio: %s", Pa_GetErrorText(err));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
int numDevices = Pa_GetDeviceCount();
|
||||
for (int i = 0 ; i < numDevices ; ++i) {
|
||||
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
|
||||
if (deviceInfo && deviceInfo->maxOutputChannels > 0) {
|
||||
const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
|
||||
|
||||
QString name = QString::fromUtf8(hostApiInfo->name) + QStringLiteral(": ") + QString::fromUtf8(deviceInfo->name);
|
||||
|
||||
if(name.startsWith("Windows ")) {
|
||||
name = name.remove(0,QString("Windows ").length());
|
||||
}
|
||||
|
||||
ret.append(QPair<QString,int>(name,i));
|
||||
}
|
||||
}
|
||||
if(!AudioOutputPortAudio::initialized) {
|
||||
Pa_Terminate();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif // #if (SELECT_AUDIO_BACKEND)
|
||||
|
||||
} //namespace FAV
|
||||
#endif // #if !(REMOVE_AUDIO_PORTAUDIO)
|
||||
477
project/fm_viewer/fav/AudioOutputPulse.cpp
Normal file
477
project/fm_viewer/fav/AudioOutputPulse.cpp
Normal file
@@ -0,0 +1,477 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2014)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#define REMOVE_AUDIO_PULSEAUDIO 1
|
||||
#if !(REMOVE_AUDIO_PULSEAUDIO)
|
||||
|
||||
#include "AudioOutputBackend.h"
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QMetaObject>
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include "mkid.h"
|
||||
#include "factory.h"
|
||||
#include "Logger.h"
|
||||
#ifndef Q_LIKELY
|
||||
#define Q_LIKELY(x) (!!(x))
|
||||
#endif
|
||||
namespace FAV {
|
||||
|
||||
static const char kName[] = "Pulse";
|
||||
class AudioOutputPulse Q_DECL_FINAL: public AudioOutputBackend
|
||||
{
|
||||
public:
|
||||
AudioOutputPulse(QObject *parent = 0);
|
||||
|
||||
QString name() const Q_DECL_FINAL { return QString::fromLatin1(kName);}
|
||||
bool isSupported(AudioFormat::SampleFormat sampleFormat) const Q_DECL_FINAL;
|
||||
bool open() Q_DECL_FINAL;
|
||||
bool close() Q_DECL_FINAL;
|
||||
|
||||
protected:
|
||||
bool write(const QByteArray& data) Q_DECL_FINAL;
|
||||
bool play() Q_DECL_FINAL;
|
||||
BufferControl bufferControl() const Q_DECL_FINAL;
|
||||
int getWritableBytes() Q_DECL_FINAL;
|
||||
|
||||
bool setVolume(qreal value) Q_DECL_FINAL;
|
||||
qreal getVolume() const Q_DECL_FINAL;
|
||||
bool setMute(bool value = true) Q_DECL_FINAL;
|
||||
|
||||
private:
|
||||
bool init(const AudioFormat& format);
|
||||
static void contextStateCallback(pa_context *c, void *userdata);
|
||||
static void contextSubscribeCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata);
|
||||
static void stateCallback(pa_stream *s, void *userdata);
|
||||
static void latencyUpdateCallback(pa_stream *s, void *userdata);
|
||||
static void underflowCallback(pa_stream *s, void *userdata);
|
||||
static void writeCallback(pa_stream *s, size_t length, void *userdata);
|
||||
static void successCallback(pa_stream*s, int success, void *userdata);
|
||||
static void sinkInfoCallback(struct pa_context *c, const struct pa_sink_input_info *i, int is_last, void *userdata);
|
||||
|
||||
bool waitPAOperation(pa_operation *op) const {
|
||||
if (!op) {
|
||||
return false;
|
||||
}
|
||||
pa_operation_state_t state = pa_operation_get_state(op);
|
||||
while (state == PA_OPERATION_RUNNING) {
|
||||
pa_threaded_mainloop_wait(loop);
|
||||
state = pa_operation_get_state(op);
|
||||
}
|
||||
pa_operation_unref(op);
|
||||
return state == PA_OPERATION_DONE;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop *loop;
|
||||
pa_context *ctx;
|
||||
pa_stream *stream;
|
||||
pa_sink_input_info info;
|
||||
size_t writable_size; //has the same effect as pa_stream_writable_size
|
||||
};
|
||||
|
||||
typedef AudioOutputPulse AudioOutputBackendPulse;
|
||||
static const AudioOutputBackendId AudioOutputBackendId_Pulse = mkid::id32base36_5<'P', 'u', 'l', 's', 'e'>::value;
|
||||
FACTORY_REGISTER(AudioOutputBackend, Pulse, kName)
|
||||
|
||||
#define PA_ENSURE_TRUE(expr, ...) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
qWarning("PulseAudio error @%d " #expr ": %s", __LINE__, pa_strerror(pa_context_errno(ctx))); \
|
||||
return __VA_ARGS__; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
static const struct format_entry {
|
||||
AudioFormat::SampleFormat spformat;
|
||||
pa_sample_format_t pa;
|
||||
} format_map[] = {
|
||||
{AudioFormat::SampleFormat_Signed16, PA_SAMPLE_S16NE},
|
||||
{AudioFormat::SampleFormat_Signed32, PA_SAMPLE_S32NE},
|
||||
{AudioFormat::SampleFormat_Float, PA_SAMPLE_FLOAT32NE},
|
||||
{AudioFormat::SampleFormat_Unsigned8, PA_SAMPLE_U8},
|
||||
{AudioFormat::SampleFormat_Unknown, PA_SAMPLE_INVALID}
|
||||
};
|
||||
|
||||
AudioFormat::SampleFormat sampleFormatFromPulse(pa_sample_format pa)
|
||||
{
|
||||
for (int i = 0; format_map[i].spformat != AudioFormat::SampleFormat_Unknown; ++i) {
|
||||
if (format_map[i].pa == pa)
|
||||
return format_map[i].spformat;
|
||||
}
|
||||
return AudioFormat::SampleFormat_Unknown;
|
||||
}
|
||||
|
||||
static pa_sample_format sampleFormatToPulse(AudioFormat::SampleFormat format)
|
||||
{
|
||||
for (int i = 0; format_map[i].spformat != AudioFormat::SampleFormat_Unknown; ++i) {
|
||||
if (format_map[i].spformat == format)
|
||||
return format_map[i].pa;
|
||||
}
|
||||
return PA_SAMPLE_INVALID;
|
||||
}
|
||||
|
||||
class ScopedPALocker {
|
||||
pa_threaded_mainloop *ml;
|
||||
public:
|
||||
ScopedPALocker(pa_threaded_mainloop *loop) : ml(loop) {
|
||||
pa_threaded_mainloop_lock(ml);
|
||||
}
|
||||
~ScopedPALocker() {
|
||||
pa_threaded_mainloop_unlock(ml);
|
||||
}
|
||||
};
|
||||
|
||||
void AudioOutputPulse::contextStateCallback(pa_context *c, void *userdata)
|
||||
{
|
||||
AudioOutputPulse *p = reinterpret_cast<AudioOutputPulse*>(userdata);
|
||||
switch (pa_context_get_state(c)) {
|
||||
case PA_CONTEXT_FAILED:
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
case PA_CONTEXT_READY:
|
||||
pa_threaded_mainloop_signal(p->loop, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void sink_input_info_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata)
|
||||
{
|
||||
Q_UNUSED(c);
|
||||
if (eol)
|
||||
return;
|
||||
AudioOutputPulse *ao = reinterpret_cast<AudioOutputPulse*>(userdata);
|
||||
QMetaObject::invokeMethod(ao, "volumeReported", Q_ARG(qreal, (qreal)pa_cvolume_avg(&i->volume)/qreal(PA_VOLUME_NORM)));
|
||||
QMetaObject::invokeMethod(ao, "muteReported", Q_ARG(bool, i->mute));
|
||||
}
|
||||
|
||||
static void sink_input_event(pa_context* c, pa_subscription_event_type_t t, uint32_t idx, AudioOutputPulse* ao)
|
||||
{
|
||||
switch (t) {
|
||||
case PA_SUBSCRIPTION_EVENT_REMOVE:
|
||||
qWarning("PulseAudio sink killed");
|
||||
break;
|
||||
default:
|
||||
pa_operation *op = pa_context_get_sink_input_info(c, idx, sink_input_info_cb, ao);
|
||||
if (Q_LIKELY(!!op))
|
||||
pa_operation_unref(op);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputPulse::contextSubscribeCallback(pa_context *c, pa_subscription_event_type_t type, uint32_t idx, void *userdata)
|
||||
{
|
||||
AudioOutputPulse *p = reinterpret_cast<AudioOutputPulse*>(userdata);
|
||||
unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
|
||||
pa_subscription_event_type_t t = pa_subscription_event_type_t(type & PA_SUBSCRIPTION_EVENT_TYPE_MASK);
|
||||
switch (facility) {
|
||||
case PA_SUBSCRIPTION_EVENT_SINK:
|
||||
break;
|
||||
case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
|
||||
if (p->stream && idx == pa_stream_get_index(p->stream))
|
||||
sink_input_event(c, t, idx, p);
|
||||
break;
|
||||
case PA_SUBSCRIPTION_EVENT_CARD:
|
||||
qDebug("PA_SUBSCRIPTION_EVENT_CARD");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputPulse::stateCallback(pa_stream *s, void *userdata)
|
||||
{
|
||||
AudioOutputPulse *p = reinterpret_cast<AudioOutputPulse*>(userdata);
|
||||
switch (pa_stream_get_state(s)) {
|
||||
case PA_STREAM_FAILED:
|
||||
qWarning("PA_STREAM_FAILED");
|
||||
pa_threaded_mainloop_signal(p->loop, 0);
|
||||
break;
|
||||
case PA_STREAM_READY:
|
||||
case PA_STREAM_TERMINATED:
|
||||
pa_threaded_mainloop_signal(p->loop, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputPulse::latencyUpdateCallback(pa_stream *s, void *userdata)
|
||||
{
|
||||
Q_UNUSED(s);
|
||||
AudioOutputPulse *p = reinterpret_cast<AudioOutputPulse*>(userdata);
|
||||
pa_threaded_mainloop_signal(p->loop, 0);
|
||||
}
|
||||
|
||||
void AudioOutputPulse::writeCallback(pa_stream *s, size_t length, void *userdata)
|
||||
{
|
||||
Q_UNUSED(s);
|
||||
// length: writable bytes. callback is called pirioddically
|
||||
AudioOutputPulse *p = reinterpret_cast<AudioOutputPulse*>(userdata);
|
||||
//qDebug("write callback: %d + %d", p->writable_size, length);
|
||||
p->writable_size = length;
|
||||
p->onCallback();
|
||||
}
|
||||
|
||||
void AudioOutputPulse::successCallback(pa_stream *s, int success, void *userdata)
|
||||
{
|
||||
Q_UNUSED(success); //?
|
||||
AudioOutputPulse *p = reinterpret_cast<AudioOutputPulse*>(userdata);
|
||||
pa_threaded_mainloop_signal(p->loop, 0);
|
||||
}
|
||||
|
||||
void AudioOutputPulse::sinkInfoCallback(pa_context *c, const pa_sink_input_info *i, int is_last, void *userdata)
|
||||
{
|
||||
Q_UNUSED(c);
|
||||
AudioOutputPulse *p = reinterpret_cast<AudioOutputPulse*>(userdata);
|
||||
if (is_last < 0) {
|
||||
qWarning("Failed to get sink input info");
|
||||
return;
|
||||
}
|
||||
if (!i)
|
||||
return;
|
||||
p->info = *i;
|
||||
pa_threaded_mainloop_signal(p->loop, 0);
|
||||
}
|
||||
|
||||
bool AudioOutputPulse::init(const AudioFormat &format)
|
||||
{
|
||||
writable_size = 0;
|
||||
loop = pa_threaded_mainloop_new();
|
||||
if (pa_threaded_mainloop_start(loop) < 0) {
|
||||
qWarning("PulseAudio failed to start mainloop");
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedPALocker lock(loop);
|
||||
Q_UNUSED(lock);
|
||||
pa_mainloop_api *api = pa_threaded_mainloop_get_api(loop);
|
||||
ctx = pa_context_new(api, qApp->applicationName().append(QLatin1String(" @%1 (QtAV)")).arg((quintptr)this).toUtf8().constData());
|
||||
if (!ctx) {
|
||||
qWarning("PulseAudio failed to allocate a context");
|
||||
return false;
|
||||
}
|
||||
qDebug() << "PulseAudio %1, protocol: %2, server protocol: %3".arg(QString::fromLatin1(pa_get_library_version())).arg(pa_context_get_protocol_version(ctx)).arg(pa_context_get_server_protocol_version(ctx));
|
||||
// TODO: host property
|
||||
pa_context_connect(ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
|
||||
pa_context_set_state_callback(ctx, AudioOutputPulse::contextStateCallback, this);
|
||||
while (true) {
|
||||
const pa_context_state_t st = pa_context_get_state(ctx);
|
||||
if (st == PA_CONTEXT_READY)
|
||||
break;
|
||||
if (!PA_CONTEXT_IS_GOOD(st)) {
|
||||
qWarning("PulseAudio context init failed");
|
||||
return false;
|
||||
}
|
||||
pa_threaded_mainloop_wait(loop);
|
||||
}
|
||||
|
||||
pa_context_set_subscribe_callback(ctx, AudioOutputPulse::contextSubscribeCallback, this);
|
||||
pa_context_subscribe(ctx, pa_subscription_mask_t(
|
||||
PA_SUBSCRIPTION_MASK_CARD |
|
||||
PA_SUBSCRIPTION_MASK_SINK |
|
||||
PA_SUBSCRIPTION_MASK_SINK_INPUT),
|
||||
NULL, NULL);
|
||||
//pa_sample_spec
|
||||
// setup format
|
||||
pa_format_info *fi = pa_format_info_new();
|
||||
fi->encoding = PA_ENCODING_PCM;
|
||||
pa_format_info_set_sample_format(fi, sampleFormatToPulse(format.sampleFormat()));
|
||||
pa_format_info_set_channels(fi, format.channels());
|
||||
pa_format_info_set_rate(fi, format.sampleRate());
|
||||
// pa_format_info_set_channel_map(fi, NULL); // TODO
|
||||
if (!pa_format_info_valid(fi)) {
|
||||
qWarning("PulseAudio: invalid format");
|
||||
return false;
|
||||
}
|
||||
pa_proplist *pl = pa_proplist_new();
|
||||
if (pl) {
|
||||
pa_proplist_sets(pl, PA_PROP_MEDIA_ROLE, "video");
|
||||
pa_proplist_sets(pl, PA_PROP_MEDIA_ICON_NAME, qApp->applicationName().append(QLatin1String(" (QtAV)")).toUtf8().constData());
|
||||
}
|
||||
stream = pa_stream_new_extended(ctx, "audio stream", &fi, 1, pl);
|
||||
if (!stream) {
|
||||
pa_format_info_free(fi);
|
||||
pa_proplist_free(pl);
|
||||
qWarning("PulseAudio: failed to create a stream");
|
||||
return false;
|
||||
}
|
||||
pa_format_info_free(fi);
|
||||
pa_proplist_free(pl);
|
||||
pa_stream_set_write_callback(stream, AudioOutputPulse::writeCallback, this);
|
||||
pa_stream_set_state_callback(stream, AudioOutputPulse::stateCallback, this);
|
||||
pa_stream_set_latency_update_callback(stream, AudioOutputPulse::latencyUpdateCallback, this);
|
||||
|
||||
pa_buffer_attr ba;
|
||||
ba.maxlength = PA_STREAM_ADJUST_LATENCY;//-1;//buffer_size*buffer_count; // max buffer size on the server
|
||||
ba.tlength = PA_STREAM_ADJUST_LATENCY;//(uint32_t)-1; // ?
|
||||
ba.prebuf = 1;//(uint32_t)-1; // play as soon as possible
|
||||
ba.minreq = (uint32_t)-1;
|
||||
//ba.fragsize = (uint32_t)-1; //latency
|
||||
// PA_STREAM_NOT_MONOTONIC?
|
||||
pa_stream_flags_t flags = pa_stream_flags_t(PA_STREAM_NOT_MONOTONIC|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE);
|
||||
if (pa_stream_connect_playback(stream, NULL /*sink*/, &ba, flags, NULL, NULL) < 0) {
|
||||
qWarning("PulseAudio failed: pa_stream_connect_playback");
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const pa_stream_state_t st = pa_stream_get_state(stream);
|
||||
if (st == PA_STREAM_READY)
|
||||
break;
|
||||
if (!PA_STREAM_IS_GOOD(st)) {
|
||||
qWarning("PulseAudio stream init failed");
|
||||
return false;
|
||||
}
|
||||
pa_threaded_mainloop_wait(loop);
|
||||
}
|
||||
if (pa_stream_is_suspended(stream)) {
|
||||
qWarning("PulseAudio stream is suspende");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioOutputPulse::AudioOutputPulse(QObject *parent)
|
||||
: AudioOutputBackend(AudioOutput::DeviceFeatures()
|
||||
|AudioOutput::SetVolume
|
||||
|AudioOutput::SetMute
|
||||
|AudioOutput::SetSampleRate, parent)
|
||||
, loop(0)
|
||||
, ctx(0)
|
||||
, stream(0)
|
||||
, writable_size(0)
|
||||
{
|
||||
//setDeviceFeatures(DeviceFeatures()|SetVolume|SetMute);
|
||||
}
|
||||
|
||||
bool AudioOutputPulse::isSupported(AudioFormat::SampleFormat spformat) const
|
||||
{
|
||||
for (int i = 0; format_map[i].spformat != AudioFormat::SampleFormat_Unknown; ++i) {
|
||||
if (format_map[i].spformat == spformat)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioOutputPulse::open()
|
||||
{
|
||||
if (!init(format)) {
|
||||
if (ctx)
|
||||
qWarning("%s", pa_strerror(pa_context_errno(ctx)));
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputPulse::close()
|
||||
{
|
||||
if (stream) {
|
||||
ScopedPALocker palock(loop);
|
||||
Q_UNUSED(palock);
|
||||
PA_ENSURE_TRUE(waitPAOperation(pa_stream_drain(stream, AudioOutputPulse::successCallback, this)), false);
|
||||
}
|
||||
if (loop) {
|
||||
pa_threaded_mainloop_stop(loop);
|
||||
}
|
||||
if (stream) {
|
||||
pa_stream_disconnect(stream);
|
||||
pa_stream_unref(stream);
|
||||
stream = NULL;
|
||||
}
|
||||
if (ctx) {
|
||||
pa_context_disconnect(ctx);
|
||||
pa_context_unref(ctx);
|
||||
ctx = NULL;
|
||||
}
|
||||
if (loop) {
|
||||
pa_threaded_mainloop_free(loop);
|
||||
loop = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioOutputBackend::BufferControl AudioOutputPulse::bufferControl() const
|
||||
{
|
||||
return BytesCallback;
|
||||
}
|
||||
|
||||
int AudioOutputPulse::getWritableBytes()
|
||||
{
|
||||
//return writable_size;
|
||||
if (!loop || !stream) {
|
||||
qWarning("pulseaudio is not open");
|
||||
return 0;
|
||||
}
|
||||
ScopedPALocker palock(loop);
|
||||
Q_UNUSED(palock);
|
||||
return pa_stream_writable_size(stream);
|
||||
}
|
||||
|
||||
bool AudioOutputPulse::write(const QByteArray &data)
|
||||
{
|
||||
ScopedPALocker palock(loop);
|
||||
Q_UNUSED(palock);
|
||||
PA_ENSURE_TRUE(pa_stream_write(stream, data.constData(), data.size(), NULL, 0LL, PA_SEEK_RELATIVE) >= 0, false);
|
||||
writable_size -= data.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputPulse::play()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputPulse::setVolume(qreal value)
|
||||
{
|
||||
ScopedPALocker palock(loop);
|
||||
Q_UNUSED(palock);
|
||||
uint32_t stream_idx = pa_stream_get_index(stream);
|
||||
struct pa_cvolume vol; // TODO: per-channel volume
|
||||
pa_cvolume_reset(&vol, format.channels());
|
||||
pa_cvolume_set(&vol, format.channels(), pa_volume_t(value*qreal(PA_VOLUME_NORM)));
|
||||
pa_operation *o = 0;
|
||||
PA_ENSURE_TRUE((o = pa_context_set_sink_input_volume(ctx, stream_idx, &vol, NULL, NULL)) != NULL, false);
|
||||
pa_operation_unref(o);
|
||||
return true;
|
||||
}
|
||||
|
||||
qreal AudioOutputPulse::getVolume() const
|
||||
{
|
||||
ScopedPALocker palock(loop);
|
||||
Q_UNUSED(palock);
|
||||
uint32_t stream_idx = pa_stream_get_index(stream);
|
||||
PA_ENSURE_TRUE(waitPAOperation(pa_context_get_sink_input_info(ctx, stream_idx, AudioOutputPulse::sinkInfoCallback, (void*)this)), 0.0);
|
||||
return (qreal)pa_cvolume_avg(&info.volume)/qreal(PA_VOLUME_NORM);
|
||||
}
|
||||
|
||||
bool AudioOutputPulse::setMute(bool value)
|
||||
{
|
||||
ScopedPALocker palock(loop);
|
||||
Q_UNUSED(palock);
|
||||
uint32_t stream_idx = pa_stream_get_index(stream);
|
||||
pa_operation *o = 0;
|
||||
PA_ENSURE_TRUE((o = pa_context_set_sink_input_mute(ctx, stream_idx, value, NULL, NULL)) != NULL, false);
|
||||
pa_operation_unref(o);
|
||||
return true;
|
||||
}
|
||||
} //namespace FAV
|
||||
#endif // #if !(REMOVE_AUDIO_PULSEAUDIO)
|
||||
382
project/fm_viewer/fav/AudioOutputXAudio2.cpp
Normal file
382
project/fm_viewer/fav/AudioOutputXAudio2.cpp
Normal file
@@ -0,0 +1,382 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AudioOutputBackend.h"
|
||||
#include "mkid.h"
|
||||
#include "factory.h"
|
||||
#include <QtCore/QLibrary>
|
||||
#include <QtCore/QSemaphore>
|
||||
#include "AVCompat.h"
|
||||
#include "Logger.h"
|
||||
#define DX_LOG_COMPONENT "XAudio2"
|
||||
#include "DirectXHelper.h"
|
||||
#include "xaudio2_compat.h"
|
||||
|
||||
// ref: DirectXTK, SDL, wine
|
||||
namespace FAV {
|
||||
|
||||
|
||||
static const char kName[] = "XAudio2";
|
||||
|
||||
#if(FE_LOG_VERSION)
|
||||
static int SOUND_BUFFER_LOG_COUNT = 0;
|
||||
#endif
|
||||
|
||||
class AudioOutputXAudio2 Q_DECL_FINAL: public AudioOutputBackend, public IXAudio2VoiceCallback
|
||||
{
|
||||
public:
|
||||
AudioOutputXAudio2(QObject *parent = 0);
|
||||
~AudioOutputXAudio2();
|
||||
QString name() const Q_DECL_OVERRIDE { return QString::fromLatin1(kName);}
|
||||
QString status() const Q_DECL_OVERRIDE { return _status;}; // LOG
|
||||
bool open() Q_DECL_OVERRIDE;
|
||||
bool close() Q_DECL_OVERRIDE;
|
||||
// TODO: check channel layout. xaudio2 supports channels>2
|
||||
bool isSupported(const AudioFormat& format) const Q_DECL_OVERRIDE;
|
||||
bool isSupported(AudioFormat::SampleFormat sampleFormat) const Q_DECL_OVERRIDE;
|
||||
bool isSupported(AudioFormat::ChannelLayout channelLayout) const Q_DECL_OVERRIDE;
|
||||
BufferControl bufferControl() const Q_DECL_OVERRIDE;
|
||||
void onCallback() Q_DECL_OVERRIDE;
|
||||
bool write(const QByteArray& data) Q_DECL_OVERRIDE;
|
||||
bool play() Q_DECL_OVERRIDE;
|
||||
|
||||
bool setVolume(qreal value) Q_DECL_OVERRIDE;
|
||||
qreal getVolume() const Q_DECL_OVERRIDE;
|
||||
public:
|
||||
STDMETHOD_(void, OnVoiceProcessingPassStart)(THIS_ UINT32 bytesRequired) Q_DECL_OVERRIDE {Q_UNUSED(bytesRequired);}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassEnd)(THIS) Q_DECL_OVERRIDE {}
|
||||
STDMETHOD_(void, OnStreamEnd)(THIS) Q_DECL_OVERRIDE {}
|
||||
STDMETHOD_(void, OnBufferStart)(THIS_ void* bufferContext) Q_DECL_OVERRIDE { Q_UNUSED(bufferContext);}
|
||||
STDMETHOD_(void, OnBufferEnd)(THIS_ void* bufferContext) Q_DECL_OVERRIDE {
|
||||
AudioOutputXAudio2 *ao = reinterpret_cast<AudioOutputXAudio2*>(bufferContext);
|
||||
if (ao->bufferControl() & AudioOutputBackend::CountCallback) {
|
||||
ao->onCallback();
|
||||
}
|
||||
}
|
||||
STDMETHOD_(void, OnLoopEnd)(THIS_ void* bufferContext) Q_DECL_OVERRIDE { Q_UNUSED(bufferContext);}
|
||||
STDMETHOD_(void, OnVoiceError)(THIS_ void* bufferContext, HRESULT error) Q_DECL_OVERRIDE {
|
||||
Q_UNUSED(bufferContext);
|
||||
qWarning() << __FUNCTION__ << ": (" << error << ") " << qt_error_string(error);
|
||||
}
|
||||
|
||||
private:
|
||||
QString _status;
|
||||
bool xaudio2_winsdk;
|
||||
bool uninit_com;
|
||||
// TODO: com ptr
|
||||
IXAudio2SourceVoice* source_voice;
|
||||
union {
|
||||
struct {
|
||||
DXSDK::IXAudio2* xaudio;
|
||||
DXSDK::IXAudio2MasteringVoice* master;
|
||||
} dxsdk;
|
||||
struct {
|
||||
WinSDK::IXAudio2* xaudio;
|
||||
WinSDK::IXAudio2MasteringVoice* master;
|
||||
} winsdk;
|
||||
};
|
||||
QSemaphore sem;
|
||||
int queue_data_write;
|
||||
QByteArray queue_data;
|
||||
|
||||
QLibrary dll;
|
||||
};
|
||||
|
||||
typedef AudioOutputXAudio2 AudioOutputBackendXAudio2;
|
||||
static const AudioOutputBackendId AudioOutputBackendId_XAudio2 = mkid::id32base36_6<'X', 'A', 'u', 'd', 'i', 'o'>::value;
|
||||
FACTORY_REGISTER(AudioOutputBackend, XAudio2, kName)
|
||||
|
||||
AudioOutputXAudio2::AudioOutputXAudio2(QObject *parent)
|
||||
: AudioOutputBackend(AudioOutput::DeviceFeatures()|AudioOutput::SetVolume, parent)
|
||||
, xaudio2_winsdk(true)
|
||||
, uninit_com(false)
|
||||
, source_voice(NULL)
|
||||
, queue_data_write(0)
|
||||
{
|
||||
_status = "";
|
||||
|
||||
memset(&dxsdk, 0, sizeof(dxsdk));
|
||||
available = false;
|
||||
//setDeviceFeatures(AudioOutput::DeviceFeatures()|AudioOutput::SetVolume);
|
||||
#ifdef Q_OS_WINRT
|
||||
qDebug("XAudio2 for WinRT");
|
||||
// winrt can only load package dlls
|
||||
DX_ENSURE(XAudio2Create(&winsdk.xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR));
|
||||
#else
|
||||
// https://github.com/wang-bin/QtAV/issues/518
|
||||
// already initialized in qtcore for main thread. If RPC_E_CHANGED_MODE no ref is added, CoUninitialize can lead to crash
|
||||
uninit_com = ::CoInitializeEx(NULL, COINIT_MULTITHREADED) != RPC_E_CHANGED_MODE;
|
||||
// load dll. <win8: XAudio2_7.DLL, <win10: XAudio2_8.DLL, win10: XAudio2_9.DLL. also defined by XAUDIO2_DLL_A in xaudio2.h
|
||||
int ver = 9;
|
||||
for (; ver >= 7; ver--) {
|
||||
dll.setFileName(QStringLiteral("XAudio2_%1").arg(ver));
|
||||
qDebug() << "AUDIO:" << dll.fileName();
|
||||
if (!dll.load()) {
|
||||
qWarning() << dll.errorString();
|
||||
continue;
|
||||
}
|
||||
_status += QString().sprintf("\n XAUDIO VER:2.%d\n",ver);
|
||||
|
||||
|
||||
#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
|
||||
// defined as an inline function
|
||||
qDebug("Build with XAudio2 from DXSDK");
|
||||
#else
|
||||
qDebug("Build with XAudio2 from Win8 or later SDK");
|
||||
#endif
|
||||
bool ready = false;
|
||||
if (!ready && ver >= 8) {
|
||||
xaudio2_winsdk = true;
|
||||
|
||||
_status += " XAUDIO MODE:WINSDK\n";
|
||||
qDebug("Try symbol 'XAudio2Create' from WinSDK dll");
|
||||
typedef HRESULT (__stdcall *XAudio2Create_t)(WinSDK::IXAudio2** ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor);
|
||||
XAudio2Create_t XAudio2Create = (XAudio2Create_t)dll.resolve("XAudio2Create");
|
||||
if (XAudio2Create) {
|
||||
ready = SUCCEEDED(XAudio2Create(&winsdk.xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
if (!ready && ver < 8) {
|
||||
xaudio2_winsdk = false;
|
||||
#ifdef _XBOX // xbox < win8 is inline XAudio2Create
|
||||
qDebug("Try symbol 'XAudio2Create' from DXSDK dll (XBOX)");
|
||||
typedef HRESULT (__stdcall *XAudio2Create_t)(DXSDK::IXAudio2** ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor);
|
||||
XAudio2Create_t XAudio2Create = (XAudio2Create_t)dll.resolve("XAudio2Create");
|
||||
if (XAudio2Create)
|
||||
ready = SUCCEEDED(XAudio2Create(&dxsdk.xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR));
|
||||
#else
|
||||
_status += " XAUDIO MODE:DXSDK\n";
|
||||
// try xaudio2 from dxsdk without symbol
|
||||
qDebug("Try inline function 'XAudio2Create' from DXSDK");
|
||||
ready = SUCCEEDED(DXSDK::XAudio2Create(&dxsdk.xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR));
|
||||
#endif
|
||||
|
||||
}
|
||||
if (ready)
|
||||
{
|
||||
//qInfo() << "(991) XAUDIO:" << QString().sprintf("XAUDIO VER:2.%d SDK:%d CREATED:%d",ver,xaudio2_winsdk,ready);
|
||||
//qInfo() << "XAUDIO:" << dll.fileName();
|
||||
break;
|
||||
}
|
||||
dll.unload();
|
||||
}
|
||||
#endif //Q_OS_WINRT
|
||||
qDebug("xaudio2: %p", winsdk.xaudio);
|
||||
available = !!(winsdk.xaudio);
|
||||
|
||||
// qInfo() << "(991) XAUDIO:" << QString().sprintf("XAUDIO STATUS:%d",available) << __FUNCTION__;
|
||||
_status += QString().sprintf(" XAUDIO STATUS:%d\n",available);
|
||||
}
|
||||
|
||||
AudioOutputXAudio2::~AudioOutputXAudio2()
|
||||
{
|
||||
qDebug();
|
||||
if (xaudio2_winsdk)
|
||||
SafeRelease(&winsdk.xaudio);
|
||||
else
|
||||
SafeRelease(&dxsdk.xaudio);
|
||||
#ifndef Q_OS_WINRT
|
||||
//again, for COM. not for winrt
|
||||
if (uninit_com)
|
||||
CoUninitialize();
|
||||
#endif //Q_OS_WINRT
|
||||
}
|
||||
|
||||
bool AudioOutputXAudio2::open()
|
||||
{
|
||||
if (!available)
|
||||
return false;
|
||||
#if (_WIN32_WINNT < _WIN32_WINNT_WIN8) // TODO: also check runtime version before call
|
||||
// XAUDIO2_DEVICE_DETAILS details;
|
||||
#endif
|
||||
|
||||
|
||||
WAVEFORMATEX wf;
|
||||
wf.cbSize = 0; //sdl: sizeof(wf)
|
||||
wf.nChannels = format.channels();
|
||||
|
||||
|
||||
wf.nSamplesPerSec = format.sampleRate(); // FIXME: use supported values
|
||||
wf.wFormatTag = format.isFloat() ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
|
||||
wf.wBitsPerSample = format.bytesPerSample() * 8;
|
||||
wf.nBlockAlign = wf.nChannels * format.bytesPerSample();
|
||||
wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
|
||||
|
||||
//qInfo() << "(991) XAUDIO " + QString().sprintf("SUPPORTED:%d CHANNELS:%d SAMPLES:%d",isSupported(format),wf.nChannels,wf.nSamplesPerSec);
|
||||
|
||||
// dwChannelMask
|
||||
// TODO: channels >2, see dsound
|
||||
const UINT32 flags = 0; //XAUDIO2_VOICE_NOSRC | XAUDIO2_VOICE_NOPITCH;
|
||||
// TODO: sdl freq 1.0
|
||||
if (xaudio2_winsdk) {
|
||||
// 보통 여기로 진행
|
||||
// TODO: device Id property
|
||||
// TODO: parameters now default.
|
||||
// UINT32 Flags = 0,
|
||||
// LPCWSTR DeviceId = 0,
|
||||
DX_ENSURE_OK(winsdk.xaudio->CreateMasteringVoice(&winsdk.master, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE), false);
|
||||
DX_ENSURE_OK(winsdk.xaudio->CreateSourceVoice(&source_voice, &wf, flags, XAUDIO2_DEFAULT_FREQ_RATIO, this, NULL, NULL), false);
|
||||
DX_ENSURE_OK(winsdk.xaudio->StartEngine(), false);
|
||||
|
||||
//qInfo() << "XAUDIO OPENED OK." << __FUNCTION__ << __LINE__;
|
||||
|
||||
//qInfo() << "WINSDK" << __FUNCTION__;
|
||||
|
||||
} else {
|
||||
DX_ENSURE_OK(dxsdk.xaudio->CreateMasteringVoice(&dxsdk.master, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE), false);
|
||||
DX_ENSURE_OK(dxsdk.xaudio->CreateSourceVoice(&source_voice, &wf, flags, XAUDIO2_DEFAULT_FREQ_RATIO, this, NULL, NULL), false);
|
||||
DX_ENSURE_OK(dxsdk.xaudio->StartEngine(), false);
|
||||
|
||||
//qInfo() << "WINSDK ELSE" << __FUNCTION__;
|
||||
}
|
||||
// Start 무조건 0
|
||||
DX_ENSURE_OK(source_voice->Start(0, XAUDIO2_COMMIT_NOW), false);
|
||||
qDebug("source_voice:%p", source_voice);
|
||||
|
||||
queue_data.resize(buffer_size*buffer_count);
|
||||
//qInfo() << "(991) XAUDIO CREATE BUFFER " + QString().sprintf(" SIZE:%d COUNT:%d",buffer_size,buffer_count);
|
||||
|
||||
|
||||
sem.release(buffer_count - sem.available());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputXAudio2::close()
|
||||
{
|
||||
//qInfo() << "(991) XAUDIO CLOSED";
|
||||
qDebug("source_voice: %p, master: %p", source_voice, winsdk.master);
|
||||
if (source_voice) {
|
||||
source_voice->Stop(0, XAUDIO2_COMMIT_NOW);
|
||||
source_voice->FlushSourceBuffers();
|
||||
source_voice->DestroyVoice();
|
||||
source_voice = NULL;
|
||||
}
|
||||
if (xaudio2_winsdk) {
|
||||
if (winsdk.master) {
|
||||
winsdk.master->DestroyVoice();
|
||||
winsdk.master = NULL;
|
||||
}
|
||||
if (winsdk.xaudio)
|
||||
winsdk.xaudio->StopEngine();
|
||||
} else {
|
||||
if (dxsdk.master) {
|
||||
dxsdk.master->DestroyVoice();
|
||||
dxsdk.master = NULL;
|
||||
}
|
||||
if (dxsdk.xaudio)
|
||||
dxsdk.xaudio->StopEngine();
|
||||
}
|
||||
|
||||
queue_data.clear();
|
||||
queue_data_write = 0;
|
||||
|
||||
//qInfo() << "XAUDIO CLOSED OK." << __FUNCTION__ << __LINE__;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputXAudio2::isSupported(const AudioFormat& format) const
|
||||
{
|
||||
return isSupported(format.sampleFormat()) && isSupported(format.channelLayout());
|
||||
}
|
||||
|
||||
bool AudioOutputXAudio2::isSupported(AudioFormat::SampleFormat sampleFormat) const
|
||||
{
|
||||
return !IsPlanar(sampleFormat) && RawSampleSize(sampleFormat) < sizeof(double); // TODO: what about s64?
|
||||
}
|
||||
|
||||
// FIXME:
|
||||
bool AudioOutputXAudio2::isSupported(AudioFormat::ChannelLayout channelLayout) const
|
||||
{
|
||||
return channelLayout == AudioFormat::ChannelLayout_Mono || channelLayout == AudioFormat::ChannelLayout_Stereo;
|
||||
}
|
||||
|
||||
AudioOutputBackend::BufferControl AudioOutputXAudio2::bufferControl() const
|
||||
{
|
||||
return CountCallback;
|
||||
}
|
||||
|
||||
void AudioOutputXAudio2::onCallback()
|
||||
{
|
||||
if (bufferControl() & CountCallback)
|
||||
sem.release();
|
||||
}
|
||||
|
||||
bool AudioOutputXAudio2::write(const QByteArray &data)
|
||||
{
|
||||
#if(FE_LOG_VERSION)
|
||||
//if(SOUND_BUFFER_LOG_COUNT % 50 == 0) {
|
||||
//qInfo() << "(991) XAUDIO WRITE START";
|
||||
//}
|
||||
#endif
|
||||
|
||||
|
||||
//qDebug("sem: %d, write: %d/%d", sem.available(), queue_data_write, queue_data.size());
|
||||
if (bufferControl() & CountCallback)
|
||||
sem.acquire();
|
||||
const int s = qMin(queue_data.size() - queue_data_write, data.size());
|
||||
// assume data.size() <= buffer_size. It's true in QtAV
|
||||
if (s < data.size())
|
||||
queue_data_write = 0;
|
||||
memcpy((char*)queue_data.constData() + queue_data_write, data.constData(), data.size());
|
||||
XAUDIO2_BUFFER xb; //IMPORTANT! wrong value(playbegin/length, loopbegin/length) will result in commit sourcebuffer fail
|
||||
memset(&xb, 0, sizeof(XAUDIO2_BUFFER));
|
||||
xb.AudioBytes = data.size();
|
||||
//xb.Flags = XAUDIO2_END_OF_STREAM;
|
||||
xb.pContext = this;
|
||||
xb.pAudioData = (const BYTE*)(queue_data.constData() + queue_data_write);
|
||||
queue_data_write += data.size();
|
||||
if (queue_data_write == queue_data.size())
|
||||
queue_data_write = 0;
|
||||
DX_ENSURE_OK(source_voice->SubmitSourceBuffer(&xb, NULL), false);
|
||||
// TODO: XAUDIO2_E_DEVICE_INVALIDATED
|
||||
|
||||
#if(FE_LOG_VERSION)
|
||||
//if(SOUND_BUFFER_LOG_COUNT % 50 == 0) {
|
||||
// qInfo() << "(991) XAUDIO WRITE" << QString().sprintf("%d",queue_data_write);
|
||||
//}
|
||||
SOUND_BUFFER_LOG_COUNT += 1;
|
||||
#endif
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputXAudio2::play()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputXAudio2::setVolume(qreal value)
|
||||
{
|
||||
// master or source?
|
||||
DX_ENSURE_OK(source_voice->SetVolume(value), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
qreal AudioOutputXAudio2::getVolume() const
|
||||
{
|
||||
FLOAT value;
|
||||
source_voice->GetVolume(&value);
|
||||
return value;
|
||||
}
|
||||
} // namespace FAV
|
||||
199
project/fm_viewer/fav/AudioResampler.cpp
Normal file
199
project/fm_viewer/fav/AudioResampler.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AudioResampler.h"
|
||||
#include "AudioFormat.h"
|
||||
#include "AudioResampler_p.h"
|
||||
#include "factory.h"
|
||||
#include "mkid.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
FACTORY_DEFINE(AudioResampler)
|
||||
|
||||
AudioResamplerId AudioResamplerId_FF = mkid::id32base36_6<'F', 'F', 'm', 'p', 'e', 'g'>::value;
|
||||
AudioResamplerId AudioResamplerId_Libav = mkid::id32base36_5<'L', 'i', 'b', 'a', 'v'>::value;
|
||||
|
||||
extern bool RegisterAudioResamplerFF_Man();
|
||||
extern bool RegisterAudioResamplerLibav_Man();
|
||||
void AudioResampler::registerAll()
|
||||
{
|
||||
static bool done = false;
|
||||
if (done)
|
||||
return;
|
||||
done = true;
|
||||
#if QTAV_HAVE(SWRESAMPLE)
|
||||
RegisterAudioResamplerFF_Man();
|
||||
#endif
|
||||
#if QTAV_HAVE(AVRESAMPLE)
|
||||
RegisterAudioResamplerLibav_Man();
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioResampler::AudioResampler(AudioResamplerPrivate& d):DPTR_INIT(&d)
|
||||
{
|
||||
}
|
||||
|
||||
AudioResampler::~AudioResampler()
|
||||
{
|
||||
}
|
||||
|
||||
QByteArray AudioResampler::outData() const
|
||||
{
|
||||
return d_func().data_out;
|
||||
}
|
||||
|
||||
bool AudioResampler::prepare()
|
||||
{
|
||||
if (!inAudioFormat().isValid()) {
|
||||
qWarning("src audio parameters 'channel layout(or channels), sample rate and sample format must be set before initialize resampler");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioResampler::convert(const quint8 **data)
|
||||
{
|
||||
Q_UNUSED(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioResampler::setSpeed(qreal speed)
|
||||
{
|
||||
DPTR_D(AudioResampler);
|
||||
if (d.speed == speed)
|
||||
return;
|
||||
d.speed = speed;
|
||||
prepare();
|
||||
}
|
||||
|
||||
qreal AudioResampler::speed() const
|
||||
{
|
||||
return d_func().speed;
|
||||
}
|
||||
|
||||
|
||||
void AudioResampler::setInAudioFormat(const AudioFormat& format)
|
||||
{
|
||||
DPTR_D(AudioResampler);
|
||||
if (d.in_format == format)
|
||||
return;
|
||||
d.in_format = format;
|
||||
prepare();
|
||||
}
|
||||
|
||||
AudioFormat& AudioResampler::inAudioFormat()
|
||||
{
|
||||
return d_func().in_format;
|
||||
}
|
||||
|
||||
const AudioFormat& AudioResampler::inAudioFormat() const
|
||||
{
|
||||
return d_func().in_format;
|
||||
}
|
||||
|
||||
void AudioResampler::setOutAudioFormat(const AudioFormat& format)
|
||||
{
|
||||
DPTR_D(AudioResampler);
|
||||
if (d.out_format == format)
|
||||
return;
|
||||
d.out_format = format;
|
||||
prepare();
|
||||
}
|
||||
|
||||
AudioFormat& AudioResampler::outAudioFormat()
|
||||
{
|
||||
return d_func().out_format;
|
||||
}
|
||||
|
||||
const AudioFormat& AudioResampler::outAudioFormat() const
|
||||
{
|
||||
return d_func().out_format;
|
||||
}
|
||||
|
||||
void AudioResampler::setInSampesPerChannel(int samples)
|
||||
{
|
||||
d_func().in_samples_per_channel = samples;
|
||||
}
|
||||
|
||||
int AudioResampler::outSamplesPerChannel() const
|
||||
{
|
||||
return d_func().out_samples_per_channel;
|
||||
}
|
||||
|
||||
//channel count can be computed by av_get_channel_layout_nb_channels(chl)
|
||||
void AudioResampler::setInSampleRate(int isr)
|
||||
{
|
||||
AudioFormat af(d_func().in_format);
|
||||
af.setSampleRate(isr);
|
||||
setInAudioFormat(af);
|
||||
}
|
||||
|
||||
void AudioResampler::setOutSampleRate(int osr)
|
||||
{
|
||||
AudioFormat af(d_func().out_format);
|
||||
af.setSampleRate(osr);
|
||||
setOutAudioFormat(af);
|
||||
}
|
||||
|
||||
void AudioResampler::setInSampleFormat(int isf)
|
||||
{
|
||||
AudioFormat af(d_func().in_format);
|
||||
af.setSampleFormatFFmpeg(isf);
|
||||
setInAudioFormat(af);
|
||||
}
|
||||
|
||||
void AudioResampler::setOutSampleFormat(int osf)
|
||||
{
|
||||
AudioFormat af(d_func().out_format);
|
||||
af.setSampleFormatFFmpeg(osf);
|
||||
setOutAudioFormat(af);
|
||||
}
|
||||
|
||||
void AudioResampler::setInChannelLayout(qint64 icl)
|
||||
{
|
||||
AudioFormat af(d_func().in_format);
|
||||
af.setChannelLayoutFFmpeg(icl);
|
||||
setInAudioFormat(af);
|
||||
}
|
||||
|
||||
void AudioResampler::setOutChannelLayout(qint64 ocl)
|
||||
{
|
||||
AudioFormat af(d_func().out_format);
|
||||
af.setChannelLayoutFFmpeg(ocl);
|
||||
setOutAudioFormat(af);
|
||||
}
|
||||
|
||||
void AudioResampler::setInChannels(int channels)
|
||||
{
|
||||
AudioFormat af(d_func().in_format);
|
||||
af.setChannels(channels);
|
||||
setInAudioFormat(af);
|
||||
}
|
||||
|
||||
void AudioResampler::setOutChannels(int channels)
|
||||
{
|
||||
AudioFormat af(d_func().out_format);
|
||||
af.setChannels(channels);
|
||||
setOutAudioFormat(af);
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
111
project/fm_viewer/fav/AudioResampler.h
Normal file
111
project/fm_viewer/fav/AudioResampler.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AUDIORESAMPLER_H
|
||||
#define QTAV_AUDIORESAMPLER_H
|
||||
|
||||
#include "_fav_constants.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
typedef int AudioResamplerId;
|
||||
class AudioFormat;
|
||||
class AudioResamplerPrivate;
|
||||
class Q_AV_EXPORT AudioResampler //export is required for users who want add their own subclass outside QtAV
|
||||
{
|
||||
DPTR_DECLARE_PRIVATE(AudioResampler)
|
||||
public:
|
||||
virtual ~AudioResampler();
|
||||
// if QtAV is static linked (ios for example), components may be not automatically registered. Add registerAll() to workaround
|
||||
static void registerAll();
|
||||
template<class C>
|
||||
static bool Register(AudioResamplerId id, const char* name) { return Register(id, create<C>, name);}
|
||||
static AudioResampler* create(AudioResamplerId id);
|
||||
/*!
|
||||
* \brief create
|
||||
* Create resampler from name
|
||||
* \param name can be "FFmpeg", "Libav"
|
||||
*/
|
||||
static AudioResampler* create(const char* name);
|
||||
/*!
|
||||
* \brief next
|
||||
* \param id NULL to get the first id address
|
||||
* \return address of id or NULL if not found/end
|
||||
*/
|
||||
static AudioResamplerId* next(AudioResamplerId* id = 0);
|
||||
static const char* name(AudioResamplerId id);
|
||||
static AudioResamplerId id(const char* name);
|
||||
|
||||
QByteArray outData() const;
|
||||
/* check whether the parameters are supported. If not, you should use ff*/
|
||||
/*!
|
||||
* \brief prepare
|
||||
* Check whether the parameters are supported and setup the resampler
|
||||
* setIn/OutXXX will call prepare() if format is changed
|
||||
*/
|
||||
virtual bool prepare();
|
||||
virtual bool convert(const quint8** data);
|
||||
//speed: >0, default is 1
|
||||
void setSpeed(qreal speed); //out_sample_rate = out_sample_rate/speed
|
||||
qreal speed() const;
|
||||
|
||||
void setInAudioFormat(const AudioFormat& format);
|
||||
AudioFormat& inAudioFormat();
|
||||
const AudioFormat& inAudioFormat() const;
|
||||
|
||||
void setOutAudioFormat(const AudioFormat& format);
|
||||
AudioFormat& outAudioFormat();
|
||||
const AudioFormat& outAudioFormat() const;
|
||||
|
||||
//decoded frame's samples/channel
|
||||
void setInSampesPerChannel(int samples);
|
||||
// > 0 valid after resample done
|
||||
int outSamplesPerChannel() const;
|
||||
//channel count can be computed by av_get_channel_layout_nb_channels(chl)
|
||||
void setInSampleRate(int isr);
|
||||
void setOutSampleRate(int osr); //default is in
|
||||
//TODO: enum
|
||||
void setInSampleFormat(int isf); //FFmpeg sample format
|
||||
void setOutSampleFormat(int osf); //FFmpeg sample format. set by user. default is in
|
||||
//TODO: enum. layout will be set to the default layout of the channels if not defined
|
||||
void setInChannelLayout(qint64 icl);
|
||||
void setOutChannelLayout(qint64 ocl); //default is in
|
||||
void setInChannels(int channels);
|
||||
void setOutChannels(int channels);
|
||||
//Are getter functions required?
|
||||
private:
|
||||
AudioResampler();
|
||||
template<class C>
|
||||
static AudioResampler* create() {
|
||||
return new C();
|
||||
}
|
||||
typedef AudioResampler* (*AudioResamplerCreator)();
|
||||
static bool Register(AudioResamplerId id, AudioResamplerCreator, const char *name);
|
||||
|
||||
protected:
|
||||
AudioResampler(AudioResamplerPrivate& d);
|
||||
DPTR_DECLARE(AudioResampler)
|
||||
};
|
||||
|
||||
extern Q_AV_EXPORT AudioResamplerId AudioResamplerId_FF;
|
||||
extern Q_AV_EXPORT AudioResamplerId AudioResamplerId_Libav;
|
||||
} //namespace FAV
|
||||
#endif // QTAV_AUDIORESAMPLER_H
|
||||
23
project/fm_viewer/fav/AudioResamplerFF.cpp
Normal file
23
project/fm_viewer/fav/AudioResamplerFF.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2015 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#define BUILD_SWR
|
||||
#include "AudioResamplerTemplate.cpp"
|
||||
25
project/fm_viewer/fav/AudioResamplerLibav.cpp
Normal file
25
project/fm_viewer/fav/AudioResamplerLibav.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
#define QTAV_HAVE_SWR_AVR_MAP 1
|
||||
#define BUILD_AVR
|
||||
#include "AudioResamplerTemplate.cpp"
|
||||
286
project/fm_viewer/fav/AudioResamplerTemplate.cpp
Normal file
286
project/fm_viewer/fav/AudioResamplerTemplate.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
#define REMOVE_AUDIO_RESAMPLER 0
|
||||
|
||||
#if !(REMOVE_AUDIO_RESAMPLER)
|
||||
|
||||
#if defined(BUILD_AVR) || defined(BUILD_SWR) // no this macro is fine too for qmake
|
||||
|
||||
#include "AudioResampler.h"
|
||||
#include "AudioResampler_p.h"
|
||||
#include "AVCompat.h"
|
||||
#include "factory.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
#if QTAV_HAVE(SWR_AVR_MAP)
|
||||
#define AudioResamplerFF AudioResamplerLibav
|
||||
#define AudioResamplerFFPrivate AudioResamplerLibavPrivate
|
||||
#define AudioResamplerId_FF AudioResamplerId_Libav
|
||||
#define RegisterAudioResamplerFF_Man RegisterAudioResamplerLibav_Man
|
||||
#define FF Libav
|
||||
static const char kName[] = "Libav";
|
||||
#else
|
||||
static const char kName[] = "FFmpeg";
|
||||
#endif
|
||||
|
||||
class AudioResamplerFFPrivate;
|
||||
class AudioResamplerFF : public AudioResampler
|
||||
{
|
||||
DPTR_DECLARE_PRIVATE(AudioResampler)
|
||||
public:
|
||||
AudioResamplerFF();
|
||||
virtual bool convert(const quint8** data);
|
||||
virtual bool prepare();
|
||||
};
|
||||
extern AudioResamplerId AudioResamplerId_FF;
|
||||
FACTORY_REGISTER(AudioResampler, FF, kName)
|
||||
|
||||
class AudioResamplerFFPrivate : public AudioResamplerPrivate
|
||||
{
|
||||
public:
|
||||
AudioResamplerFFPrivate(): context(0) {}
|
||||
~AudioResamplerFFPrivate() {
|
||||
if (context) {
|
||||
swr_free(&context);
|
||||
context = 0;
|
||||
}
|
||||
}
|
||||
SwrContext *context;
|
||||
// defined in swr<1
|
||||
#ifndef SWR_CH_MAX
|
||||
#define SWR_CH_MAX 64
|
||||
#endif
|
||||
int channel_map[SWR_CH_MAX];
|
||||
};
|
||||
|
||||
AudioResamplerFF::AudioResamplerFF():
|
||||
AudioResampler(*new AudioResamplerFFPrivate())
|
||||
{
|
||||
}
|
||||
|
||||
bool AudioResamplerFF::convert(const quint8 **data)
|
||||
{
|
||||
DPTR_D(AudioResamplerFF);
|
||||
/*
|
||||
* swr_get_delay: Especially when downsampling by a large value, the output sample rate may be a poor choice to represent
|
||||
* the delay, similarly upsampling and the input sample rate.
|
||||
*/
|
||||
qreal osr = d.out_format.sampleRate();
|
||||
if (!qFuzzyCompare(d.speed, 1.0))
|
||||
osr /= d.speed;
|
||||
d.out_samples_per_channel = av_rescale_rnd(
|
||||
#if HAVE_SWR_GET_DELAY
|
||||
swr_get_delay(d.context, qMax(d.in_format.sampleRate(), d.out_format.sampleRate())) +
|
||||
#else
|
||||
128 + //TODO: QtAV_Compat
|
||||
#endif //HAVE_SWR_GET_DELAY
|
||||
d.in_samples_per_channel //TODO: wanted_samples(ffplay mplayer2)
|
||||
, osr, d.in_format.sampleRate(), AV_ROUND_UP);
|
||||
//TODO: why crash for swr 0.5?
|
||||
//int out_size = av_samples_get_buffer_size(NULL/*out linesize*/, d.out_channels, d.out_samples_per_channel, (AVSampleFormat)d.out_sample_format, 0/*alignment default*/);
|
||||
int size_per_sample_with_channels = d.out_format.channels()*d.out_format.bytesPerSample();
|
||||
int out_size = d.out_samples_per_channel*size_per_sample_with_channels;
|
||||
if (out_size > d.data_out.size())
|
||||
d.data_out.resize(out_size);
|
||||
uint8_t *out[] = {(uint8_t*)d.data_out.data()}; // detach if implicitly shared by others
|
||||
//number of input/output samples available in one channel
|
||||
int converted_samplers_per_channel = swr_convert(d.context, out, d.out_samples_per_channel, data, d.in_samples_per_channel);
|
||||
d.out_samples_per_channel = converted_samplers_per_channel;
|
||||
if (converted_samplers_per_channel < 0) {
|
||||
qWarning("[AudioResamplerFF] %s", av_err2str(converted_samplers_per_channel));
|
||||
return false;
|
||||
}
|
||||
//TODO: converted_samplers_per_channel==out_samples_per_channel means out_size is too small, see mplayer2
|
||||
//converted_samplers_per_channel*d.out_channels*av_get_bytes_per_sample(d.out_sample_format)
|
||||
//av_samples_get_buffer_size(0, d.out_channels, converted_samplers_per_channel, d.out_sample_format, 0)
|
||||
//if (converted_samplers_per_channel != out_size)
|
||||
d.data_out.resize(converted_samplers_per_channel*size_per_sample_with_channels);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
*TODO: broken sample rate(AAC), see mplayer
|
||||
*/
|
||||
bool AudioResamplerFF::prepare()
|
||||
{
|
||||
DPTR_D(AudioResamplerFF);
|
||||
if (!d.in_format.isValid()) {
|
||||
qWarning("src audio parameters 'channel layout(or channels), sample rate and sample format must be set before initialize resampler");
|
||||
return false;
|
||||
}
|
||||
//TODO: also in do this statistics
|
||||
if (!d.in_format.channels()) {
|
||||
if (!d.in_format.channelLayoutFFmpeg()) { //FIXME: already return
|
||||
d.in_format.setChannels(2);
|
||||
d.in_format.setChannelLayoutFFmpeg(av_get_default_channel_layout(d.in_format.channels())); //from mplayer2
|
||||
qWarning("both channels and channel layout are not available, assume channels=%d, channel layout=%lld", d.in_format.channels(), d.in_format.channelLayoutFFmpeg());
|
||||
} else {
|
||||
d.in_format.setChannels(av_get_channel_layout_nb_channels(d.in_format.channelLayoutFFmpeg()));
|
||||
}
|
||||
}
|
||||
if (!d.in_format.channels())
|
||||
d.in_format.setChannels(2); //TODO: why av_get_channel_layout_nb_channels() may return 0?
|
||||
if (!d.in_format.channelLayoutFFmpeg()) {
|
||||
qWarning("channel layout not available, use default layout");
|
||||
d.in_format.setChannelLayoutFFmpeg(av_get_default_channel_layout(d.in_format.channels()));
|
||||
}
|
||||
if (!d.out_format.channels()) {
|
||||
if (d.out_format.channelLayoutFFmpeg()) {
|
||||
d.out_format.setChannels(av_get_channel_layout_nb_channels(d.out_format.channelLayoutFFmpeg()));
|
||||
} else {
|
||||
d.out_format.setChannels(d.in_format.channels());
|
||||
d.out_format.setChannelLayoutFFmpeg(d.in_format.channelLayoutFFmpeg());
|
||||
}
|
||||
}
|
||||
if (d.out_format.channelLayout() == AudioFormat::ChannelLayout_Unsupported) {
|
||||
d.out_format.setChannels(d.in_format.channels());
|
||||
d.out_format.setChannelLayoutFFmpeg(d.in_format.channelLayoutFFmpeg());
|
||||
}
|
||||
//now we have out channels
|
||||
if (!d.out_format.channelLayoutFFmpeg())
|
||||
d.out_format.setChannelLayoutFFmpeg(av_get_default_channel_layout(d.out_format.channels()));
|
||||
if (!d.out_format.sampleRate())
|
||||
d.out_format.setSampleRate(inAudioFormat().sampleRate());
|
||||
if (d.speed <= 0)
|
||||
d.speed = 1.0;
|
||||
//DO NOT set sample rate here, we should keep the original and multiply 1/speed when needed
|
||||
//if (d.speed != 1.0)
|
||||
// d.out_format.setSampleRate(int(qreal(d.out_format.sampleFormat())/d.speed));
|
||||
qDebug("swr speed=%.2f", d.speed);
|
||||
|
||||
//d.in_planes = av_sample_fmt_is_planar((enum AVSampleFormat)d.in_sample_format) ? d.in_channels : 1;
|
||||
//d.out_planes = av_sample_fmt_is_planar((enum AVSampleFormat)d.out_sample_format) ? d.out_channels : 1;
|
||||
if (d.context)
|
||||
swr_free(&d.context); //TODO: if no free(of cause free is required), why channel mapping and layout not work if change from left to stereo?
|
||||
//If use swr_alloc() need to set the parameters (av_opt_set_xxx() manually or with swr_alloc_set_opts()) before calling swr_init()
|
||||
d.context = swr_alloc_set_opts(d.context
|
||||
, d.out_format.channelLayoutFFmpeg()
|
||||
, (enum AVSampleFormat)outAudioFormat().sampleFormatFFmpeg()
|
||||
, qreal(outAudioFormat().sampleRate())/d.speed
|
||||
, d.in_format.channelLayoutFFmpeg()
|
||||
, (enum AVSampleFormat)inAudioFormat().sampleFormatFFmpeg()
|
||||
, inAudioFormat().sampleRate()
|
||||
, 0 /*log_offset*/, 0 /*log_ctx*/);
|
||||
/*
|
||||
av_opt_set_int(d.context, "in_channel_layout", d.in_channel_layout, 0);
|
||||
av_opt_set_int(d.context, "in_sample_rate", d.in_format.sampleRate(), 0);
|
||||
av_opt_set_sample_fmt(d.context, "in_sample_fmt", (enum AVSampleFormat)in_format.sampleFormatFFmpeg(), 0);
|
||||
av_opt_set_int(d.context, "out_channel_layout", d.out_channel_layout, 0);
|
||||
av_opt_set_int(d.context, "out_sample_rate", d.out_format.sampleRate(), 0);
|
||||
av_opt_set_sample_fmt(d.context, "out_sample_fmt", (enum AVSampleFormat)out_format.sampleFormatFFmpeg(), 0);
|
||||
*/
|
||||
qDebug("out: {cl: %lld, fmt: %s, freq: %d}"
|
||||
, d.out_format.channelLayoutFFmpeg()
|
||||
, qPrintable(d.out_format.sampleFormatName())
|
||||
, d.out_format.sampleRate());
|
||||
qDebug("in {cl: %lld, fmt: %s, freq: %d}"
|
||||
, d.in_format.channelLayoutFFmpeg()
|
||||
, qPrintable(d.in_format.sampleFormatName())
|
||||
, d.in_format.sampleRate());
|
||||
|
||||
if (!d.context) {
|
||||
qWarning("Allocat swr context failed!");
|
||||
return false;
|
||||
}
|
||||
//avresample 0.0.2(FFmpeg 0.11)~1.0.1(FFmpeg 1.1) has no channel mapping. but has remix matrix, so does swresample
|
||||
//TODO: why crash if use channel mapping for L or R?
|
||||
#if QTAV_HAVE(SWR_AVR_MAP) //LIBAVRESAMPLE_VERSION_INT < AV_VERSION_INT(1, 1, 0)
|
||||
bool remix = false;
|
||||
int in_c = d.in_format.channels();
|
||||
int out_c = d.out_format.channels();
|
||||
/*
|
||||
* matrix[i + stride * o] is the weight of input channel i in output channel o.
|
||||
*/
|
||||
double *matrix = 0;
|
||||
if (d.out_format.channelLayout() == AudioFormat::ChannelLayout_Left) {
|
||||
remix = true;
|
||||
matrix = (double*)calloc(in_c*out_c, sizeof(double));
|
||||
for (int o = 0; o < out_c; ++o) {
|
||||
matrix[0 + in_c * o] = 1;
|
||||
}
|
||||
}
|
||||
if (d.out_format.channelLayout() == AudioFormat::ChannelLayout_Right) {
|
||||
remix = true;
|
||||
matrix = (double*)calloc(in_c*out_c, sizeof(double));
|
||||
for (int o = 0; o < out_c; ++o) {
|
||||
matrix[1 + in_c * o] = 1;
|
||||
}
|
||||
}
|
||||
if (!remix && in_c < out_c) {
|
||||
remix = true;
|
||||
//double matrix[in_c*out_c]; //C99, VLA
|
||||
matrix = (double*)calloc(in_c*out_c, sizeof(double));
|
||||
for (int i = 0, o = 0; o < out_c; ++o) {
|
||||
matrix[i + in_c * o] = 1;
|
||||
i = (i + i)%in_c;
|
||||
}
|
||||
}
|
||||
if (remix && matrix) {
|
||||
avresample_set_matrix(d.context, matrix, in_c);
|
||||
free(matrix);
|
||||
}
|
||||
#else
|
||||
bool use_channel_map = false;
|
||||
if (d.out_format.channelLayout() == AudioFormat::ChannelLayout_Left) {
|
||||
use_channel_map = true;
|
||||
memset(d.channel_map, 0, sizeof(d.channel_map));
|
||||
for (int i = 0; i < d.out_format.channels(); ++i) {
|
||||
d.channel_map[i] = 0;
|
||||
}
|
||||
}
|
||||
if (d.out_format.channelLayout() == AudioFormat::ChannelLayout_Right) {
|
||||
use_channel_map = true;
|
||||
memset(d.channel_map, 0, sizeof(d.channel_map));
|
||||
for (int i = 0; i < d.out_format.channels(); ++i) {
|
||||
d.channel_map[i] = 1;
|
||||
}
|
||||
}
|
||||
if (!use_channel_map && d.in_format.channels() < d.out_format.channels()) {
|
||||
use_channel_map = true;
|
||||
memset(d.channel_map, 0, sizeof(d.channel_map));
|
||||
for (int i = 0; i < d.out_format.channels(); ++i) {
|
||||
d.channel_map[i] = i % d.in_format.channels();
|
||||
}
|
||||
}
|
||||
if (use_channel_map) {
|
||||
av_opt_set_int(d.context, "icl", d.out_format.channelLayoutFFmpeg(), 0);
|
||||
//TODO: why crash if layout is mono and set uch(i.e. always the next line)
|
||||
av_opt_set_int(d.context, "uch", d.out_format.channels(), 0);
|
||||
swr_set_channel_mapping(d.context, d.channel_map);
|
||||
}
|
||||
#endif //QTAV_HAVE(SWR_AVR_MAP)
|
||||
int ret = swr_init(d.context);
|
||||
if (ret < 0) {
|
||||
qWarning("swr_init failed: %s", av_err2str(ret));
|
||||
swr_free(&d.context);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
#endif //defined(BUILD_AVR) || defined(BUILD_SWR)
|
||||
|
||||
#endif // #if !(REMOVE_AUDIO_RESAMPLER)
|
||||
52
project/fm_viewer/fav/AudioResampler_p.h
Normal file
52
project/fm_viewer/fav/AudioResampler_p.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_AUDIORESAMPLER_P_H
|
||||
#define QTAV_AUDIORESAMPLER_P_H
|
||||
|
||||
#include "AudioFormat.h"
|
||||
#include "AVCompat.h"
|
||||
#include <QtCore/QByteArray>
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class AudioResampler;
|
||||
class Q_AV_PRIVATE_EXPORT AudioResamplerPrivate : public DPtrPrivate<AudioResampler>
|
||||
{
|
||||
public:
|
||||
AudioResamplerPrivate():
|
||||
in_samples_per_channel(0)
|
||||
, out_samples_per_channel(0)
|
||||
, speed(1.0)
|
||||
{
|
||||
in_format.setSampleFormat(AudioFormat::SampleFormat_Unknown);
|
||||
out_format.setSampleFormat(AudioFormat::SampleFormat_Float);
|
||||
}
|
||||
|
||||
int in_samples_per_channel, out_samples_per_channel;
|
||||
qreal speed;
|
||||
AudioFormat in_format, out_format;
|
||||
QByteArray data_out;
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
#endif // QTAV_AUDIORESAMPLER_P_H
|
||||
620
project/fm_viewer/fav/AudioThread.cpp
Normal file
620
project/fm_viewer/fav/AudioThread.cpp
Normal file
@@ -0,0 +1,620 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "AudioThread.h"
|
||||
#include "AVThread_p.h"
|
||||
#include "AudioDecoder.h"
|
||||
#include "Packet.h"
|
||||
#include "AudioFormat.h"
|
||||
#include "AudioOutput.h"
|
||||
#include "AudioResampler.h"
|
||||
#include "AVClock.h"
|
||||
#include "Filter.h"
|
||||
#include "OutputSet.h"
|
||||
#include "AVCompat.h"
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDateTime>
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class AudioThreadPrivate : public AVThreadPrivate
|
||||
{
|
||||
public:
|
||||
void init() {
|
||||
resample = false;
|
||||
last_pts = 0;
|
||||
}
|
||||
|
||||
bool resample;
|
||||
qreal last_pts; //used when audio output is not available, to calculate the aproximate sleeping time
|
||||
};
|
||||
|
||||
AudioThread::AudioThread(QObject *parent)
|
||||
:AVThread(*new AudioThreadPrivate(), parent)
|
||||
{
|
||||
stopPosition = -1;
|
||||
|
||||
}
|
||||
|
||||
void AudioThread::applyFilters(AudioFrame &frame)
|
||||
{
|
||||
DPTR_D(AudioThread);
|
||||
//QMutexLocker locker(&d.mutex);
|
||||
//Q_UNUSED(locker);
|
||||
if (!d.filters.isEmpty()) {
|
||||
//sort filters by format. vo->defaultFormat() is the last
|
||||
foreach (Filter *filter, d.filters) {
|
||||
AudioFilter *af = static_cast<AudioFilter*>(filter);
|
||||
if (!af->isEnabled())
|
||||
continue;
|
||||
af->apply(d.statistics, &frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
*TODO:
|
||||
* if output is null or dummy, the use duration to wait
|
||||
*/
|
||||
|
||||
#define LOG_AUDIO_THREAD qInfo // qInfo
|
||||
//#define CHECK_AUDIO_BREAK 1 // 오디오 종료,끊기는 지점 확인
|
||||
|
||||
void AudioThread::run()
|
||||
{
|
||||
DPTR_D(AudioThread);
|
||||
//No decoder or output. No audio output is ok, just display picture
|
||||
if (!d.dec || !d.dec->isAvailable() || !d.outputSet)
|
||||
return;
|
||||
resetState();
|
||||
Q_ASSERT(d.clock != 0);
|
||||
d.init();
|
||||
Packet pkt;
|
||||
qint64 fake_duration = 0LL;
|
||||
qint64 fake_pts = 0LL;
|
||||
int sync_id = 0;
|
||||
|
||||
#if (FORCE_BREAK_EOF)
|
||||
forceEnd = false;
|
||||
aboutToEnd = false;
|
||||
#endif
|
||||
|
||||
while (!d.stop)
|
||||
{
|
||||
|
||||
#if (USE_DURATION_IN_THREAD)
|
||||
usleep(1); // 32x speed 에서 처리되지 않음
|
||||
if(durationSec > 0 && d.clock->value() > durationSec)
|
||||
{
|
||||
clock()->updateValue(durationSec); //external clock?
|
||||
msleep(100);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
processNextTask();
|
||||
|
||||
#if (FORCE_BREAK_EOF)
|
||||
if(forceEnd)
|
||||
{
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO BREAK(forceEnd) LN:" << __LINE__;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
const bool is_external_clock = d.clock->clockType() == AVClock::ExternalClock;
|
||||
if (d.render_pts0 < 0) { // no pause when seeking
|
||||
if (tryPause()) { //DO NOT continue, or stepForward() will fail
|
||||
if (d.stop)
|
||||
{
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO BREAK(d.stop) LN:" << __LINE__;
|
||||
#endif
|
||||
break; //the queue is empty and may block. should setBlocking(false) wake up cond empty?
|
||||
}
|
||||
} else {
|
||||
if (isPaused())
|
||||
{
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO CONTINUE(isPaused()) LN:" << __LINE__;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PAUSE 에도 발생
|
||||
if (d.seek_requested)
|
||||
{
|
||||
d.seek_requested = false;
|
||||
// LOG_AUDIO_THREAD("request seek audio thread");
|
||||
pkt = Packet(); // last decode failed and pkt is valid, reset pkt to force take the next packet if seek is requested
|
||||
msleep(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// d.render_pts0 < 0 means seek finished here
|
||||
if (d.clock->syncId() > 0)
|
||||
{
|
||||
//qInfo("audio thread wait to sync end for sync id: %d", d.clock->syncId());
|
||||
#if (FORCE_BREAK_EOF)
|
||||
if (d.render_pts0 < 0 && sync_id > 0 && aboutToEnd == false) {
|
||||
#else
|
||||
if (d.render_pts0 < 0 && sync_id > 0) {
|
||||
#endif
|
||||
msleep(10);
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO CONTINUE(d.clock->syncId() > 0) LN:" << __LINE__;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
sync_id = 0;
|
||||
}
|
||||
}
|
||||
if (!pkt.isValid())
|
||||
{
|
||||
// PACKET INVALID 원인 확인
|
||||
// qInfo() << "AUDIO PACKET INVALID LN:" << __LINE__ << "isCorrupt:" << pkt.isCorrupt << " empty:" << pkt.data.isEmpty() << " pts:" << pkt.pts << " duration:" << pkt.duration;
|
||||
|
||||
// can't seek back if eof packet is read
|
||||
//qDebug("eof pkt: %d valid: %d, aqueue size: %d, abuffer: %d %.3f %d, fake_duration: %lld", pkt.isEOF(), pkt.isValid(), d.packets.size(), d.packets.bufferValue(), d.packets.bufferMax(), d.packets.isFull(), fake_duration);
|
||||
// If seek requested but last decode failed
|
||||
if (!pkt.isEOF() && (fake_duration <= 0 || !d.packets.isEmpty()))
|
||||
{
|
||||
pkt = d.packets.take(); //wait to dequeue
|
||||
//qInfo() << "AUDIO PTS:" << pkt.dts << " CLOCK:" << d.clock->value();
|
||||
}
|
||||
#if (FORCE_BREAK_EOF)
|
||||
if (pkt.isEOF() || forceEnd)
|
||||
#else
|
||||
if (pkt.isEOF())
|
||||
#endif
|
||||
{
|
||||
fake_duration = 0; //avoid endless wait
|
||||
qDebug("audio thread gets an eof packet. pkt.pts: %.3f, d.render_pts0:%.3f", pkt.pts, d.render_pts0);
|
||||
}
|
||||
if (!pkt.isValid())
|
||||
{
|
||||
// check seek first
|
||||
if (pkt.pts >= 0)
|
||||
{
|
||||
qDebug("Invalid packet! flush audio codec context!!!!!!!! audio queue size=%d", d.packets.size());
|
||||
QMutexLocker locker(&d.mutex);
|
||||
Q_UNUSED(locker);
|
||||
if (d.dec) //maybe set to null in setDecoder()
|
||||
{
|
||||
d.dec->flush();
|
||||
}
|
||||
|
||||
// render_pts0 지정
|
||||
d.render_pts0 = pkt.pts;
|
||||
sync_id = pkt.position;
|
||||
// LOG_AUDIO_THREAD("audio seek: %.3f, id: %d", d.render_pts0, sync_id);
|
||||
pkt = Packet(); //mark invalid to take next
|
||||
if (fake_duration > 0)
|
||||
{
|
||||
//qInfo("fake_duration update on seek: %ul + %ul - %.3f", fake_duration, fake_pts, d.render_pts0);
|
||||
fake_duration = fake_duration + fake_pts - d.render_pts0*1000.0;
|
||||
fake_pts = d.render_pts0*1000.0;
|
||||
}
|
||||
|
||||
// External 일 경우 의미 없음
|
||||
// LOG_AUDIO_THREAD() << "d.clock->updateValue(d.render_pts0)";
|
||||
d.clock->updateValue(d.render_pts0);
|
||||
d.clock->updateDelay(0);
|
||||
continue;
|
||||
}
|
||||
if (pkt.duration > 0)
|
||||
{
|
||||
fake_duration = pkt.duration * 1000.0;
|
||||
fake_pts = d.last_pts*1000.0;
|
||||
pkt = Packet(); //mark invalid to avoid run here in the next loop
|
||||
//qDebug("get fake apkt: %.3f+%ul", pkt.pts, fake_duration);
|
||||
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO CONTINUE(pkt.duration > 0) LN:" << __LINE__ << " IS EOF:" << pkt.isEOF() << "forceEnd:" << forceEnd;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (fake_duration > 0)
|
||||
{
|
||||
static const ulong kSleepMs = 20;
|
||||
const ulong ms = qMin<qint64>(fake_duration, kSleepMs);
|
||||
fake_duration -= ms;
|
||||
fake_pts += ms;
|
||||
//LOG_AUDIO_THREAD("fake_wait: %ul, fake_duration: %lld, delay: %.3f", ms, fake_duration, d.clock->delay());
|
||||
d.clock->updateDelay(d.clock->delay() + qreal(ms)/1000.0);
|
||||
msleep(ms);
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO CONTINUE(fake_duration > 0) LN:" << __LINE__ << " clock:" << d.clock->value() << " IS EOF:" << pkt.isEOF() << "forceEnd:" << forceEnd;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!pkt.isValid() && !pkt.isEOF()) // decode it will cause crash
|
||||
{
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO CONTINUE(isNotValid+NotEOF) LN:" << __LINE__;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
qreal dts = pkt.dts; //FIXME: pts and dts
|
||||
// no key frame for audio. so if pts reaches, try decode and skip render if got frame pts does not reach
|
||||
bool skip_render = pkt.pts >= 0 && pkt.pts < d.render_pts0; // if audio stream is too short, seeking will fail and d.render_pts0 keeps >0
|
||||
// audio has no key frame, skip rendering equals to skip decoding
|
||||
// SEEK
|
||||
if (skip_render) {
|
||||
|
||||
d.clock->updateValue(pkt.pts);
|
||||
|
||||
// audio may be too fast than video if skip without sleep
|
||||
// a frame is about 20ms. sleep time must be << frame time
|
||||
qreal a_v = dts - d.clock->videoTime();
|
||||
|
||||
// qInfo("skip audio decode at %f/%f v=%f a-v=%fms", dts, d.render_pts0, d.clock->videoTime(), a_v*1000.0);
|
||||
if (a_v > 0)
|
||||
{
|
||||
// LOG_AUDIO_THREAD() << "a_v:" << a_v;
|
||||
msleep(qMin((ulong)20, ulong(a_v*1000.0)));
|
||||
d.clock->updateValue(a_v);
|
||||
} else {
|
||||
// audio maybe too late compared with video packet before seeking backword. so just ignore
|
||||
// 64 로 처리하면 영상 딜레이가 발생함..!!!!!!
|
||||
#if (MODEL_BBVIEWER)
|
||||
msleep(0); //original
|
||||
#else
|
||||
msleep(0); //wait video seek done if audio done early
|
||||
#endif
|
||||
}
|
||||
pkt = Packet(); //mark invalid to take next
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO CONTINUE(skip_render) LN:" << __LINE__;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
// const bool is_external_clock = d.clock->clockType() == AVClock::ExternalClock;
|
||||
if (is_external_clock && !pkt.isEOF()) {
|
||||
d.delay = dts - d.clock->value();
|
||||
|
||||
// qInfo() << "dts:" << dts << " d.delay:" << d.delay;
|
||||
|
||||
// after seeking forward, a packet may be the old, v packet may be
|
||||
// the new packet, then the d.delay is very large, omit it.
|
||||
// TODO: 1. how to choose the value
|
||||
// 2. use last delay when seeking
|
||||
// 오차가 작을경우
|
||||
if (qAbs(d.delay) < 2.0) {
|
||||
|
||||
//qInfo() << "d.delay+:" << d.delay;
|
||||
// 느릴 경우 (뒤로 이동)
|
||||
if (d.delay < -kSyncThreshold) { //Speed up. drop frame? resample?
|
||||
|
||||
//qInfo() << "d.delay < -kSyncThreshold";
|
||||
//msleep(1); // 추가 (노이즈 방지)
|
||||
//qInfo("audio is late compared with external clock. skip decoding. %.3f-%.3f=%.3f", dts, d.clock->value(), d.delay);
|
||||
pkt = Packet(); //mark invalid to take next
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO CONTINUE(qAbs(d.delay) < 2.0) LN:" << __LINE__;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
// 빠를 경우 (전방이동)
|
||||
if (d.delay > 0)
|
||||
{
|
||||
// 아래버그 처리하니 DELAY 는 해결됨 -> 노이즈가 생김...
|
||||
//qInfo() << "!!>>>>W" << d.delay << dts << __FUNCTION__ << __LINE__;
|
||||
waitAndCheck(d.delay, dts); //-> 버그로 보임
|
||||
//waitAndCheck(ulong(d.delay * 100.0), dts);
|
||||
//waitAndCheck(ulong(d.delay * 1000.0), dts);
|
||||
}
|
||||
}
|
||||
else { //when to drop off?
|
||||
|
||||
// 오차가 클 경우 (SEEK)
|
||||
|
||||
if (d.delay > 0) // 전방이동
|
||||
{
|
||||
msleep(64);
|
||||
|
||||
//qInfo() << "DELAY > 0 BIG" << d.delay;
|
||||
}
|
||||
else // 후방이동
|
||||
{
|
||||
// DELAY 가 계속 커지면서 (-) 문제가 발생함
|
||||
#if (MODEL_BBVIEWER)
|
||||
//qInfo() << "backward d.delay-:" << d.delay;
|
||||
msleep(32);
|
||||
#else
|
||||
|
||||
// 128 일 경우 전방 이동시 부드럽게 시작?? 하지 않는다.
|
||||
msleep(64); // 추가 (노이즈 방지) -> SLEEP 하지 않으면 VideoThread 처리가 늦어짐
|
||||
#endif
|
||||
//audio packet not cleaned up?
|
||||
qDebug("audio is too late compared with external clock. skip decoding. %.3f-%.3f=%.3f", dts, d.clock->value(), d.delay);
|
||||
pkt = Packet(); //mark invalid to take next
|
||||
//qInfo() << "DELAY < 0 BIG" << d.delay;
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO CONTINUE(qAbs(d.delay) > 2.0) LN:" << __LINE__;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* lock here to ensure decoder and ao can complete current work before they are changed
|
||||
* current packet maybe not supported by new decoder
|
||||
*/
|
||||
// TODO: smaller scope
|
||||
QMutexLocker locker(&d.mutex);
|
||||
Q_UNUSED(locker);
|
||||
AudioDecoder *dec = static_cast<AudioDecoder*>(d.dec);
|
||||
if (!dec) {
|
||||
pkt = Packet(); //mark invalid to take next
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO CONTINUE(!dec) LN:" << __LINE__;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
AudioOutput *ao = 0;
|
||||
// first() is not null even if list empty
|
||||
if (!d.outputSet->outputs().isEmpty())
|
||||
{
|
||||
ao = static_cast<AudioOutput*>(d.outputSet->outputs().first());
|
||||
}
|
||||
|
||||
//DO NOT decode and convert if ao is not available or mute!
|
||||
bool has_ao = ao && ao->isAvailable();
|
||||
|
||||
|
||||
//if (!has_ao) {//do not decode?
|
||||
// TODO: move resampler to AudioFrame, like VideoFrame does
|
||||
if (has_ao && dec->resampler()) {
|
||||
if (dec->resampler()->speed() != ao->speed()
|
||||
|| dec->resampler()->outAudioFormat() != ao->audioFormat()) {
|
||||
//resample later to ensure thread safe. TODO: test
|
||||
if (d.resample) {
|
||||
qDebug() << "ao.format " << ao->audioFormat();
|
||||
qDebug() << "swr.format " << dec->resampler()->outAudioFormat();
|
||||
qDebug("decoder set speed: %.2f", ao->speed());
|
||||
dec->resampler()->setOutAudioFormat(ao->audioFormat());
|
||||
dec->resampler()->setSpeed(ao->speed());
|
||||
dec->resampler()->prepare();
|
||||
d.resample = false;
|
||||
} else {
|
||||
d.resample = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (dec->resampler() && dec->resampler()->speed() != d.clock->speed()) {
|
||||
if (d.resample) {
|
||||
qDebug("decoder set speed: %.2f", d.clock->speed());
|
||||
dec->resampler()->setSpeed(d.clock->speed());
|
||||
dec->resampler()->prepare();
|
||||
d.resample = false;
|
||||
} else {
|
||||
d.resample = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (d.stop) {
|
||||
qDebug("audio thread stop before decode()");
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO BREAK(d.stop) LN:" << __LINE__;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
//qDebug("apkt: %.3f, %lld %p", pkt.pts, pkt.asAVPacket()->pts, pkt.asAVPacket()->data);
|
||||
if (!dec->decode(pkt)) {
|
||||
qWarning("Decode audio failed. undecoded: %d", dec->undecodedSize());
|
||||
if (pkt.isEOF()) {
|
||||
qDebug("audio decode eof done");
|
||||
Q_EMIT eofDecoded();
|
||||
if (d.render_pts0 >= 0) {
|
||||
qDebug("audio seek done at eof pts: %.3f. id: %d", pkt.pts, sync_id);
|
||||
d.render_pts0 = -1;
|
||||
d.clock->syncEndOnce(sync_id);
|
||||
#if (PLAY_SYNC_FIX2)
|
||||
Q_EMIT seekFinished(qint64(pkt.pts*1000.0),qint64(d.render_pts0*1000.0)); //TODO: pts
|
||||
#else
|
||||
Q_EMIT seekFinished(qint64(pkt.pts*1000.0)); //TODO: pts
|
||||
#endif
|
||||
}
|
||||
if (!pkt.position)
|
||||
{
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO BREAK(!pkt.position) LN:" << __LINE__;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
qreal dt = dts - d.last_pts;
|
||||
if (dt > 0.5 || dt < 0) {
|
||||
dt = 0;
|
||||
}
|
||||
if (!qFuzzyIsNull(dt)) {
|
||||
msleep((unsigned long)(dt*1000.0));
|
||||
}
|
||||
pkt = Packet();
|
||||
d.last_pts = d.clock->value(); //not pkt.pts! the delay is updated!
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO CONTINUE(!dec->decode(pkt)) LN:" << __LINE__;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
// reduce here to ensure to decode the rest data in the next loop
|
||||
if (!pkt.isEOF()) {
|
||||
pkt.skip(pkt.data.size() - dec->undecodedSize());
|
||||
}
|
||||
#if (USE_AUDIO_FRAME)
|
||||
AudioFrame frame(dec->frame());
|
||||
if (!frame)
|
||||
{
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO CONTINUE(!frame) LN:" << __LINE__;
|
||||
#endif // CHECK_AUDIO_BREAK
|
||||
continue; //pkt data is updated after decode, no reset here
|
||||
}
|
||||
if (frame.pts() <= 0)
|
||||
{
|
||||
frame.setPTS(pkt.pts); // pkt.pts is wrong. >= real timestamp
|
||||
}
|
||||
if (d.render_pts0 >= 0.0) { // seeking
|
||||
d.clock->updateValue(frame.pts());
|
||||
|
||||
if (frame.pts() < d.render_pts0) {
|
||||
qDebug("skip audio rendering: %f-%f", frame.pts(), d.render_pts0);
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO CONTINUE(frame.timestamp() < d.render_pts0) LN:" << __LINE__;
|
||||
#endif // CHECK_AUDIO_BREAK
|
||||
continue; //pkt data is updated after decode, no reset here
|
||||
}
|
||||
qDebug("audio seek finished @%.3f. id: %d", frame.pts(), sync_id);
|
||||
#if (PLAY_SYNC_FIX2)
|
||||
qreal requested = d.render_pts0;
|
||||
#endif // PLAY_SYNC_FIX2
|
||||
d.render_pts0 = -1.0;
|
||||
d.clock->syncEndOnce(sync_id);
|
||||
#if (PLAY_SYNC_FIX2)
|
||||
Q_EMIT seekFinished(qint64(frame.pts()*1000.0),qint64(requested*1000.0));
|
||||
#else // PLAY_SYNC_FIX2
|
||||
Q_EMIT seekFinished(qint64(frame.pts()*1000.0));
|
||||
#endif // PLAY_SYNC_FIX2
|
||||
if (has_ao) {
|
||||
ao->clear();
|
||||
}
|
||||
}
|
||||
if (has_ao) {
|
||||
applyFilters(frame);
|
||||
frame.setAudioResampler(dec->resampler()); //!!!
|
||||
// FIXME: resample ONCE is required for audio frames from ffmpeg
|
||||
//if (ao->audioFormat() != frame.format()) {
|
||||
frame = frame.to(ao->audioFormat());
|
||||
//}
|
||||
}
|
||||
|
||||
#if (FIXED_FPS_DURATION)
|
||||
bool isStopPosition = (stopPosition > 0 && (qint64)(frame.pts() * 1000000) >= stopPosition);
|
||||
// 후방 깨진영역 플레이 하지 않는다??
|
||||
if(isStopPosition == true && playerID == 0)
|
||||
{
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO CONTINUE(isStopPosition) LN:" << __LINE__;
|
||||
#endif // CHECK_AUDIO_BREAK
|
||||
msleep(1);
|
||||
continue;
|
||||
}
|
||||
#endif // FIXED_FPS_DURATION
|
||||
|
||||
QByteArray decoded(frame.data());
|
||||
#else // AUDIO FRAME
|
||||
QByteArray decoded(dec->data());
|
||||
#endif // AUDIO FRAME
|
||||
int decodedSize = decoded.size();
|
||||
int decodedPos = 0;
|
||||
qreal delay = 0;
|
||||
const qreal byte_rate = frame.format().bytesPerSecond();
|
||||
qreal pts = frame.pts();
|
||||
//qDebug("frame samples: %d @%.3f+%lld", frame.samplesPerChannel()*frame.channelCount(), frame.timestamp(), frame.duration()/1000LL);
|
||||
while (decodedSize > 0) {
|
||||
if (d.stop)
|
||||
{
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO BREAK(d.stop) LN:" << __LINE__;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
const int chunk = qMin(decodedSize, has_ao ? ao->bufferSize() : 512*frame.format().bytesPerFrame());//int(max_len*byte_rate));
|
||||
//AudioFormat.bytesForDuration
|
||||
const qreal chunk_delay = (qreal)chunk/(qreal)byte_rate;
|
||||
|
||||
if (has_ao && ao->isOpen()) {
|
||||
QByteArray decodedChunk = QByteArray::fromRawData(decoded.constData() + decodedPos, chunk);
|
||||
|
||||
//LOG_AUDIO_THREAD() << "A:" << ao->timestamp();
|
||||
//ao->play(decodedChunk, pts);
|
||||
// qInfo() << playerID << "pts" << pts << "duration" << duration << __FUNCTION__;
|
||||
|
||||
// 사운드가 재생이 안될 경우 duration 확인
|
||||
if(pts < duration) {
|
||||
ao->play(decodedChunk, pts);
|
||||
// qInfo() << "SOUND PLAY:" << ao->play(decodedChunk, pts);
|
||||
}
|
||||
|
||||
// PAUSE + PLAY 시 timestamp 가 0.1초 정도 (전,후방) 이동함
|
||||
// 7.794 -> 0000 -> 7.691
|
||||
//qInfo("ao.timestamp: %.3f, pts: %.3f, pktpts: %.3f", ao->timestamp(), pts, pkt.pts);
|
||||
|
||||
//if (!is_external_clock && ao->timestamp() > 0)
|
||||
if (!is_external_clock && ao->timestamp() > 0)
|
||||
{ //TODO: clear ao buffer
|
||||
// const qreal da = qAbs(pts - ao->timestamp());
|
||||
// if (da > 1.0) { // what if frame duration is long?
|
||||
// }
|
||||
// TODO: check seek_requested(atomic bool)
|
||||
d.clock->updateValue(ao->timestamp());
|
||||
}
|
||||
}
|
||||
else // 발생하지 않는다.
|
||||
{
|
||||
d.clock->updateDelay(delay += chunk_delay);
|
||||
// why need this even if we add delay? and usleep sounds weird
|
||||
// the advantage is if no audio device, the play speed is ok too
|
||||
// So is portaudio blocking the thread when playing?
|
||||
// TODO: avoid acummulative error. External clock?
|
||||
msleep((unsigned long)(chunk_delay * 1000.0));
|
||||
|
||||
// 누적에러 처리???
|
||||
// LOG_AUDIO_THREAD() << "chunk_delay:" << chunk_delay;
|
||||
}
|
||||
decodedPos += chunk;
|
||||
decodedSize -= chunk;
|
||||
pts += chunk_delay;
|
||||
pkt.pts += chunk_delay; // packet not fully decoded, use new pts in the next decoding
|
||||
pkt.dts += chunk_delay;
|
||||
}
|
||||
if (has_ao)
|
||||
{
|
||||
emit frameDelivered();
|
||||
}
|
||||
d.last_pts = d.clock->value(); //not pkt.pts! the delay is updated!
|
||||
//LOG_AUDIO_THREAD() << "d.last_pts:" << d.last_pts;
|
||||
}
|
||||
d.packets.clear();
|
||||
|
||||
#if (CHECK_AUDIO_BREAK)
|
||||
qInfo() << "AUDIO END LN:" << __LINE__;
|
||||
#endif
|
||||
|
||||
|
||||
qDebug("Audio thread stops running...");
|
||||
//qInfo() << "AUDIO STOP!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
|
||||
|
||||
//FIXED_SLEEP; //qInfo() << "EXIT THREAD 0 AUDIO" << __FUNCTION__ << __LINE__;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
47
project/fm_viewer/fav/AudioThread.h
Normal file
47
project/fm_viewer/fav/AudioThread.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QAV_AUDIOTHREAD_H
|
||||
#define QAV_AUDIOTHREAD_H
|
||||
|
||||
#include "AVThread.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class AudioDecoder;
|
||||
class AudioFrame;
|
||||
class AudioThreadPrivate;
|
||||
class AudioThread : public AVThread
|
||||
{
|
||||
Q_OBJECT
|
||||
DPTR_DECLARE_PRIVATE(AudioThread)
|
||||
public:
|
||||
explicit AudioThread(QObject *parent = 0);
|
||||
#if (FIXED_FPS_DURATION)
|
||||
qint64 stopPosition;
|
||||
#endif
|
||||
protected:
|
||||
void applyFilters(AudioFrame& frame);
|
||||
virtual void run();
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QAV_AUDIOTHREAD_H
|
||||
343
project/fm_viewer/fav/BlockingQueue.h
Normal file
343
project/fm_viewer/fav/BlockingQueue.h
Normal file
@@ -0,0 +1,343 @@
|
||||
/******************************************************************************
|
||||
QtAV: Media play library based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
#ifndef QTAV_BLOCKINGQUEUE_H
|
||||
#define QTAV_BLOCKINGQUEUE_H
|
||||
|
||||
#include <QtCore/QReadWriteLock>
|
||||
#include <QtCore/QScopedPointer>
|
||||
#include <QtCore/QWaitCondition>
|
||||
|
||||
//TODO: block full and empty condition separately
|
||||
QT_BEGIN_NAMESPACE
|
||||
template<typename T> class QQueue;
|
||||
QT_END_NAMESPACE
|
||||
namespace FAV {
|
||||
|
||||
template <typename T, template <typename> class Container = QQueue>
|
||||
class BlockingQueue
|
||||
{
|
||||
friend class VideoThread;
|
||||
public:
|
||||
BlockingQueue();
|
||||
virtual ~BlockingQueue() {}
|
||||
|
||||
void setCapacity(int max); //enqueue is allowed if less than capacity
|
||||
/*!
|
||||
* \brief setThreshold
|
||||
* do nothing if min >= capacity()
|
||||
* \param min
|
||||
*/
|
||||
void setThreshold(int min); //wake up and enqueue
|
||||
|
||||
void put(const T& t);
|
||||
T take();
|
||||
void setBlocking(bool block); //will wake if false. called when no more data can enqueue
|
||||
void blockEmpty(bool block);
|
||||
void blockFull(bool block);
|
||||
//TODO:setMinBlock,MaxBlock
|
||||
inline void clear();
|
||||
// TODO: checkEmpty, Enough, Full?
|
||||
inline bool isEmpty() const;
|
||||
inline bool isEnough() const; //size > thres
|
||||
inline bool isFull() const; //size >= cap
|
||||
inline int size() const;
|
||||
inline int threshold() const;
|
||||
inline int capacity() const;
|
||||
|
||||
class StateChangeCallback
|
||||
{
|
||||
public:
|
||||
virtual ~StateChangeCallback(){}
|
||||
virtual void call() = 0;
|
||||
};
|
||||
void setEmptyCallback(StateChangeCallback* call);
|
||||
void setThresholdCallback(StateChangeCallback* call);
|
||||
void setFullCallback(StateChangeCallback* call);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* \brief checkFull
|
||||
* Check whether the queue is full. Default implemention is compare queue size to capacity.
|
||||
* Full is now a more generic notion. You can implement it as checking queued bytes etc.
|
||||
* \return true if queue is full
|
||||
*/
|
||||
virtual bool checkFull() const;
|
||||
virtual bool checkEmpty() const;
|
||||
virtual bool checkEnough() const;
|
||||
|
||||
virtual void onPut(const T&) {}
|
||||
virtual void onTake(const T&) {}
|
||||
|
||||
bool block_empty, block_full;
|
||||
int cap, thres;
|
||||
Container<T> queue;
|
||||
private:
|
||||
mutable QReadWriteLock lock; //locker in const func
|
||||
QReadWriteLock block_change_lock;
|
||||
QWaitCondition cond_full, cond_empty;
|
||||
//upto_threshold_callback, downto_threshold_callback
|
||||
QScopedPointer<StateChangeCallback> empty_callback, threshold_callback, full_callback;
|
||||
};
|
||||
|
||||
/* cap - thres = 24, about 1s
|
||||
* if fps is large, then larger capacity and threshold is preferred
|
||||
*/
|
||||
template <typename T, template <typename> class Container>
|
||||
BlockingQueue<T, Container>::BlockingQueue()
|
||||
:block_empty(true),block_full(true),cap(48),thres(32)
|
||||
, empty_callback(0)
|
||||
, threshold_callback(0)
|
||||
, full_callback(0)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
void BlockingQueue<T, Container>::setCapacity(int max)
|
||||
{
|
||||
//qDebug("queue capacity==>>%d", max);
|
||||
QWriteLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
cap = max;
|
||||
if (thres > cap)
|
||||
thres = cap;
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
void BlockingQueue<T, Container>::setThreshold(int min)
|
||||
{
|
||||
//qDebug("queue threshold==>>%d", min);
|
||||
QWriteLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
if (min > cap)
|
||||
return;
|
||||
thres = min;
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
void BlockingQueue<T, Container>::put(const T& t)
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
if (checkFull()) {
|
||||
//qDebug("queue full"); //too frequent
|
||||
if (full_callback) {
|
||||
full_callback->call();
|
||||
}
|
||||
if (block_full)
|
||||
{
|
||||
if(cond_full.wait(&lock,1000) == false)
|
||||
{
|
||||
return;
|
||||
//qInfo() << __FUNCTION__ << __LINE__ << "ERRRRRROOOO!";
|
||||
}
|
||||
}
|
||||
}
|
||||
queue.enqueue(t);
|
||||
onPut(t); // emit bufferProgressChanged here if buffering
|
||||
if (checkEnough()) {
|
||||
cond_empty.wakeOne(); //emit buffering finished here
|
||||
//qDebug("queue is enough: %d/%d~%d", queue.size(), thres, cap);
|
||||
} else {
|
||||
//qDebug("buffering: %d/%d~%d", queue.size(), thres, cap);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
T BlockingQueue<T, Container>::take()
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
//TODO:always block?
|
||||
if (checkEmpty())
|
||||
{
|
||||
//qDebug("queue empty!!");
|
||||
if (empty_callback)
|
||||
{
|
||||
empty_callback->call();
|
||||
}
|
||||
if (block_empty)
|
||||
{
|
||||
// 여기서 S/W 정지함
|
||||
//block when empty only
|
||||
//Q_ASSERT
|
||||
if(cond_empty.wait(&lock,1000) == false) // 00
|
||||
{
|
||||
return T();
|
||||
// qInfo() << __FUNCTION__ << __LINE__ << "ERRRRRROOOO!";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (checkEmpty())
|
||||
{
|
||||
//qWarning("Queue is still empty");
|
||||
if (empty_callback)
|
||||
{
|
||||
empty_callback->call();
|
||||
}
|
||||
return T();
|
||||
}
|
||||
T t(queue.dequeue());
|
||||
cond_full.wakeOne();
|
||||
onTake(t); // emit start buffering here if empty
|
||||
return t;
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
void BlockingQueue<T, Container>::setBlocking(bool block)
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
block_empty = block_full = block;
|
||||
if (!block) {
|
||||
cond_empty.wakeAll(); //empty still wait. setBlock=>setCapacity(-1)
|
||||
cond_full.wakeAll();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
void BlockingQueue<T, Container>::blockEmpty(bool block)
|
||||
{
|
||||
if (!block) {
|
||||
cond_empty.wakeAll();
|
||||
}
|
||||
QWriteLocker locker(&block_change_lock);
|
||||
Q_UNUSED(locker);
|
||||
block_empty = block;
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
void BlockingQueue<T, Container>::blockFull(bool block)
|
||||
{
|
||||
if (!block) {
|
||||
cond_full.wakeAll();
|
||||
}
|
||||
//DO NOT use the same lock that put() get() use. it may be already locked
|
||||
//this function usualy called in demux thread, so no lock is ok
|
||||
QWriteLocker locker(&block_change_lock);
|
||||
Q_UNUSED(locker);
|
||||
block_full = block;
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
void BlockingQueue<T, Container>::clear()
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
//cond_empty.wakeAll();
|
||||
cond_full.wakeAll();
|
||||
queue.clear();
|
||||
//TODO: assert not empty
|
||||
onTake(T());
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
bool BlockingQueue<T, Container>::isEmpty() const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
return queue.isEmpty();
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
bool BlockingQueue<T, Container>::isEnough() const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
return queue.size() >= thres;
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
bool BlockingQueue<T, Container>::isFull() const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
return queue.size() >= cap;
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
int BlockingQueue<T, Container>::size() const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
return queue.size();
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
int BlockingQueue<T, Container>::threshold() const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
return thres;
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
int BlockingQueue<T, Container>::capacity() const
|
||||
{
|
||||
QReadLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
return cap;
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
void BlockingQueue<T, Container>::setEmptyCallback(StateChangeCallback *call)
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
empty_callback.reset(call);
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
void BlockingQueue<T, Container>::setThresholdCallback(StateChangeCallback *call)
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
threshold_callback.reset(call);
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
void BlockingQueue<T, Container>::setFullCallback(StateChangeCallback *call)
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
Q_UNUSED(locker);
|
||||
full_callback.reset(call);
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
bool BlockingQueue<T, Container>::checkFull() const
|
||||
{
|
||||
return queue.size() >= cap;
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
bool BlockingQueue<T, Container>::checkEmpty() const
|
||||
{
|
||||
return queue.isEmpty();
|
||||
}
|
||||
|
||||
template <typename T, template <typename> class Container>
|
||||
bool BlockingQueue<T, Container>::checkEnough() const
|
||||
{
|
||||
return queue.size() >= thres && !checkEmpty();
|
||||
}
|
||||
} //namespace FAV
|
||||
#endif // QTAV_BLOCKINGQUEUE_H
|
||||
418
project/fm_viewer/fav/ColorTransform.cpp
Normal file
418
project/fm_viewer/fav/ColorTransform.cpp
Normal file
@@ -0,0 +1,418 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2014)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "ColorTransform.h"
|
||||
#include <QtCore/qmath.h>
|
||||
|
||||
namespace FAV {
|
||||
|
||||
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
|
||||
static const QMatrix4x4 kXYZ2sRGB(3.2404542f, -1.5371385f, -0.4985314f, 0.0f,
|
||||
-0.9692660f, 1.8760108f, 0.0415560f, 0.0f,
|
||||
0.0556434f, -0.2040259f, 1.0572252f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f);
|
||||
// http://www.cs.utah.edu/~halzahaw/CS7650/Project2/project2_index.html no gamma correction
|
||||
static const QMatrix4x4 kXYZ_RGB(2.5623f, -1.1661f, -0.3962f, 0.0f,
|
||||
-1.0215f, 1.9778f, 0.0437f, 0.0f,
|
||||
0.0752f, -0.2562f, 1.1810f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
static const QMatrix4x4 kGBR2RGB = QMatrix4x4(0, 0, 1, 0,
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 0, 1);
|
||||
|
||||
static const QMatrix4x4 yuv2rgb_bt601 =
|
||||
QMatrix4x4(
|
||||
1.0f, 0.000f, 1.402f, 0.0f,
|
||||
1.0f, -0.344f, -0.714f, 0.0f,
|
||||
1.0f, 1.772f, 0.000f, 0.0f,
|
||||
0.0f, 0.000f, 0.000f, 1.0f)
|
||||
*
|
||||
QMatrix4x4(
|
||||
1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 1.0f, 0.0f, -0.5f,
|
||||
0.0f, 0.0f, 1.0f, -0.5f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f)
|
||||
;
|
||||
static const QMatrix4x4 yuv2rgb_bt709 =
|
||||
QMatrix4x4(
|
||||
1.0f, 0.000f, 1.5701f, 0.0f,
|
||||
1.0f, -0.187f, -0.4664f, 0.0f,
|
||||
1.0f, 1.8556f, 0.000f, 0.0f,
|
||||
0.0f, 0.000f, 0.000f, 1.0f)
|
||||
*
|
||||
QMatrix4x4(
|
||||
1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 1.0f, 0.0f, -0.5f,
|
||||
0.0f, 0.0f, 1.0f, -0.5f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f)
|
||||
;
|
||||
|
||||
const QMatrix4x4& ColorTransform::YUV2RGB(ColorSpace cs)
|
||||
{
|
||||
switch (cs) {
|
||||
case ColorSpace_BT601:
|
||||
return yuv2rgb_bt601;
|
||||
case ColorSpace_BT709:
|
||||
return yuv2rgb_bt709;
|
||||
default:
|
||||
return yuv2rgb_bt601;
|
||||
}
|
||||
return yuv2rgb_bt601;
|
||||
}
|
||||
|
||||
// For yuv->rgb, assume yuv is full range before convertion, rgb is full range after convertion. so if input yuv is limited range, transform to full range first. If display rgb is limited range, transform to limited range at last.
|
||||
// *ColorRangeYUV(...)
|
||||
static QMatrix4x4 ColorRangeYUV(ColorRange from, ColorRange to)
|
||||
{
|
||||
if (from == to)
|
||||
return QMatrix4x4();
|
||||
static const qreal Y2 = 235, Y1 = 16, C2 = 240, C1 = 16;
|
||||
static const qreal s = 255; // TODO: can be others change to 100 = green
|
||||
if (from == ColorRange_Limited) { //TODO: Unknown. But what if realy want unknown?
|
||||
// qDebug("input yuv limited range");
|
||||
// [Y1, Y2] => [0, s]
|
||||
QMatrix4x4 m;
|
||||
m.scale(s/(Y2 - Y1), s/(C2 - C1), s/(C2 - C1));
|
||||
m.translate(-Y1/s, -C1/s, -C1/s);
|
||||
return m;
|
||||
}
|
||||
if (from == ColorRange_Full) {
|
||||
// [0, s] => [Y1, Y2]
|
||||
QMatrix4x4 m;
|
||||
m.translate(Y1/s, C1/s, C1/s);
|
||||
m.scale((Y2 - Y1)/s, (C2 - C1)/s, (C2 - C1)/s);
|
||||
return m;
|
||||
}
|
||||
// ColorRange_Unknown
|
||||
return QMatrix4x4();
|
||||
}
|
||||
|
||||
// ColorRangeRGB(...)*
|
||||
static QMatrix4x4 ColorRangeRGB(ColorRange from, ColorRange to)
|
||||
{
|
||||
if (from == to)
|
||||
return QMatrix4x4();
|
||||
static const qreal R2 = 235, R1 = 16;
|
||||
static const qreal s = 255;
|
||||
if (to == ColorRange_Limited) {
|
||||
qDebug("output rgb limited range");
|
||||
QMatrix4x4 m;
|
||||
m.translate(R1/s, R1/s, R1/s);
|
||||
m.scale((R2 - R1)/s, (R2 - R1)/s, (R2 - R1)/s);
|
||||
return m;
|
||||
}
|
||||
if (to == ColorRange_Full) { // TODO: Unknown
|
||||
QMatrix4x4 m;
|
||||
m.scale(s/(R2 - R1), s/(R2 - R1), s/(R2 - R1));
|
||||
m.translate(-s/R1, -s/R1, -s/R1);
|
||||
return m;
|
||||
}
|
||||
return QMatrix4x4();
|
||||
}
|
||||
|
||||
class ColorTransform::Private : public QSharedData
|
||||
{
|
||||
public:
|
||||
Private()
|
||||
: recompute(true)
|
||||
, cs_in(ColorSpace_RGB)
|
||||
, cs_out(ColorSpace_RGB)
|
||||
, range_in(ColorRange_Limited)
|
||||
, range_out(ColorRange_Full)
|
||||
, hue(0)
|
||||
, saturation(0)
|
||||
, contrast(0)
|
||||
, brightness(0)
|
||||
, bpc_scale(1.0)
|
||||
, a_bpc_scale(false)
|
||||
{}
|
||||
Private(const Private& other)
|
||||
: QSharedData(other)
|
||||
, recompute(true)
|
||||
, cs_in(ColorSpace_RGB)
|
||||
, cs_out(ColorSpace_RGB)
|
||||
, range_in(ColorRange_Limited)
|
||||
, range_out(ColorRange_Full)
|
||||
, hue(0)
|
||||
, saturation(0)
|
||||
, contrast(0)
|
||||
, brightness(0)
|
||||
, bpc_scale(1.0)
|
||||
, a_bpc_scale(false)
|
||||
{}
|
||||
~Private() {}
|
||||
|
||||
void reset() {
|
||||
recompute = true;
|
||||
//cs_in = cs_out = ColorSpace_RGB; ///
|
||||
//range_in = range_out = ColorRange_Unknown;
|
||||
hue = 0;
|
||||
saturation = 0;
|
||||
contrast = 0;
|
||||
brightness = 0;
|
||||
bpc_scale = 1.0;
|
||||
a_bpc_scale = false;
|
||||
M.setToIdentity();
|
||||
}
|
||||
// TODO: optimize for other color spaces
|
||||
void compute() const {
|
||||
recompute = false;
|
||||
//http://docs.rainmeter.net/tips/colormatrix-guide
|
||||
//http://www.graficaobscura.com/matrix/index.html
|
||||
//http://beesbuzz.biz/code/hsv_color_transforms.php
|
||||
// ??
|
||||
const float b = brightness;
|
||||
// brightness R,G,B
|
||||
QMatrix4x4 B(1, 0, 0, b,
|
||||
0, 1, 0, b,
|
||||
0, 0, 1, b,
|
||||
0, 0, 0, 1);
|
||||
// Contrast (offset) R,G,B
|
||||
const float c = contrast+1.0;
|
||||
const float t = (1.0 - c) / 2.0;
|
||||
QMatrix4x4 C(c, 0, 0, t,
|
||||
0, c, 0, t,
|
||||
0, 0, c, t,
|
||||
0, 0, 0, 1);
|
||||
// Saturation
|
||||
const float wr = 0.3086f;
|
||||
const float wg = 0.6094f;
|
||||
const float wb = 0.0820f;
|
||||
float s = saturation + 1.0f;
|
||||
QMatrix4x4 S(
|
||||
(1.0f - s)*wr + s, (1.0f - s)*wg , (1.0f - s)*wb , 0.0f,
|
||||
(1.0f - s)*wr , (1.0f - s)*wg + s, (1.0f - s)*wb , 0.0f,
|
||||
(1.0f - s)*wr , (1.0f - s)*wg , (1.0f - s)*wb + s, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f
|
||||
);
|
||||
// Hue
|
||||
const float n = 1.0f / sqrtf(3.0f); // normalized hue rotation axis: sqrt(3)*(1 1 1)
|
||||
const float h = hue*M_PI; // hue rotation angle
|
||||
const float hc = cosf(h);
|
||||
const float hs = sinf(h);
|
||||
QMatrix4x4 H( // conversion of angle/axis representation to matrix representation
|
||||
n*n*(1.0f - hc) + hc , n*n*(1.0f - hc) - n*hs, n*n*(1.0f - hc) + n*hs, 0.0f,
|
||||
n*n*(1.0f - hc) + n*hs, n*n*(1.0f - hc) + hc , n*n*(1.0f - hc) - n*hs, 0.0f,
|
||||
n*n*(1.0f - hc) - n*hs, n*n*(1.0f - hc) + n*hs, n*n*(1.0f - hc) + hc , 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f
|
||||
);
|
||||
|
||||
// B*C*S*H*rgb_range_mat(*yuv2rgb*yuv_range_mat)*bpc_scale
|
||||
M = B*C*S*H;
|
||||
// M *= rgb_range_translate*rgb_range_scale
|
||||
// TODO: transform to output color space other than RGB
|
||||
switch (cs_out) {
|
||||
case ColorSpace_XYZ:
|
||||
M = kXYZ2sRGB.inverted() * M;
|
||||
break;
|
||||
case ColorSpace_RGB:
|
||||
M *= ColorRangeRGB(ColorRange_Full, range_out); //
|
||||
break;
|
||||
case ColorSpace_GBR:
|
||||
M *= ColorRangeRGB(ColorRange_Full, range_out);
|
||||
M = kGBR2RGB.inverted() * M;
|
||||
break;
|
||||
default:
|
||||
M = YUV2RGB(cs_out).inverted() * M;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (cs_in) {
|
||||
case ColorSpace_XYZ:
|
||||
M *= kXYZ2sRGB;
|
||||
break;
|
||||
case ColorSpace_RGB:
|
||||
break;
|
||||
case ColorSpace_GBR:
|
||||
M *= kGBR2RGB;
|
||||
break;
|
||||
default:
|
||||
M *= YUV2RGB(cs_in)*ColorRangeYUV(range_in, ColorRange_Full);
|
||||
break;
|
||||
}
|
||||
if (bpc_scale != 1.0 && cs_in != ColorSpace_XYZ) { // why no range correction for xyz?
|
||||
//qDebug("bpc scale: %f", bpc_scale);
|
||||
M *= QMatrix4x4(bpc_scale, 0, 0, 0,
|
||||
0, bpc_scale, 0, 0,
|
||||
0, 0, bpc_scale, 0,
|
||||
0, 0, 0, a_bpc_scale ? bpc_scale : 1); // scale alpha channel too
|
||||
}
|
||||
//qDebug() << "color mat: " << M;
|
||||
}
|
||||
|
||||
mutable bool recompute;
|
||||
ColorSpace cs_in, cs_out;
|
||||
ColorRange range_in, range_out;
|
||||
qreal hue, saturation, contrast, brightness;
|
||||
qreal bpc_scale;
|
||||
bool a_bpc_scale;
|
||||
mutable QMatrix4x4 M; // count the transformations between spaces
|
||||
};
|
||||
|
||||
ColorTransform::ColorTransform()
|
||||
: d(new Private())
|
||||
{
|
||||
}
|
||||
|
||||
ColorTransform::~ColorTransform()
|
||||
{
|
||||
}
|
||||
|
||||
ColorSpace ColorTransform::inputColorSpace() const
|
||||
{
|
||||
return d->cs_in;
|
||||
}
|
||||
|
||||
void ColorTransform::setInputColorSpace(ColorSpace cs)
|
||||
{
|
||||
if (d->cs_in == cs)
|
||||
return;
|
||||
d->cs_in = cs;
|
||||
d->recompute = true; //TODO: only recompute color space transform
|
||||
}
|
||||
|
||||
ColorSpace ColorTransform::outputColorSpace() const
|
||||
{
|
||||
return d->cs_out;
|
||||
}
|
||||
|
||||
void ColorTransform::setOutputColorSpace(ColorSpace cs)
|
||||
{
|
||||
if (d->cs_out == cs)
|
||||
return;
|
||||
d->cs_out = cs;
|
||||
d->recompute = true; //TODO: only recompute color space transform
|
||||
}
|
||||
|
||||
ColorRange ColorTransform::inputColorRange() const
|
||||
{
|
||||
return d->range_in;
|
||||
}
|
||||
|
||||
void ColorTransform::setInputColorRange(ColorRange value)
|
||||
{
|
||||
if (d->range_in == value)
|
||||
return;
|
||||
d->range_in = value;
|
||||
d->recompute = true;
|
||||
}
|
||||
|
||||
ColorRange ColorTransform::outputColorRange() const
|
||||
{
|
||||
return d->range_out;
|
||||
}
|
||||
|
||||
void ColorTransform::setOutputColorRange(ColorRange value)
|
||||
{
|
||||
if (d->range_out == value)
|
||||
return;
|
||||
d->range_out = value;
|
||||
d->recompute = true;
|
||||
}
|
||||
|
||||
QMatrix4x4 ColorTransform::matrix() const
|
||||
{
|
||||
if (d->recompute)
|
||||
d->compute();
|
||||
return d->M;
|
||||
}
|
||||
|
||||
const QMatrix4x4& ColorTransform::matrixRef() const
|
||||
{
|
||||
if (d->recompute)
|
||||
d->compute();
|
||||
return d->M;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief reset
|
||||
* set to identity
|
||||
*/
|
||||
void ColorTransform::reset()
|
||||
{
|
||||
d->reset();
|
||||
}
|
||||
|
||||
void ColorTransform::setBrightness(qreal brightness)
|
||||
{
|
||||
if (d->brightness == brightness)
|
||||
return;
|
||||
d->brightness = brightness;
|
||||
d->recompute = true;
|
||||
}
|
||||
|
||||
qreal ColorTransform::brightness() const
|
||||
{
|
||||
return d->brightness;
|
||||
}
|
||||
|
||||
// -1~1
|
||||
void ColorTransform::setHue(qreal hue)
|
||||
{
|
||||
if (d->hue == hue)
|
||||
return;
|
||||
d->hue = hue;
|
||||
d->recompute = true;
|
||||
}
|
||||
|
||||
qreal ColorTransform::hue() const
|
||||
{
|
||||
return d->hue;
|
||||
}
|
||||
|
||||
void ColorTransform::setContrast(qreal contrast)
|
||||
{
|
||||
if (d->contrast == contrast)
|
||||
return;
|
||||
d->contrast = contrast;
|
||||
d->recompute = true;
|
||||
}
|
||||
|
||||
qreal ColorTransform::contrast() const
|
||||
{
|
||||
return d->contrast;
|
||||
}
|
||||
|
||||
void ColorTransform::setSaturation(qreal saturation)
|
||||
{
|
||||
if (d->saturation == saturation)
|
||||
return;
|
||||
d->saturation = saturation;
|
||||
d->recompute = true;
|
||||
}
|
||||
|
||||
qreal ColorTransform::saturation() const
|
||||
{
|
||||
return d->saturation;
|
||||
}
|
||||
|
||||
void ColorTransform::setChannelDepthScale(qreal value, bool scaleAlpha)
|
||||
{
|
||||
if (d->bpc_scale == value && d->a_bpc_scale == scaleAlpha)
|
||||
return;
|
||||
qDebug("ColorTransform bpc_scale %f=>%f, scale alpha: %d=>%d", d->bpc_scale, value, d->a_bpc_scale, scaleAlpha);
|
||||
d->bpc_scale = value;
|
||||
d->a_bpc_scale = scaleAlpha;
|
||||
d->recompute = true;
|
||||
}
|
||||
} //namespace FAV
|
||||
106
project/fm_viewer/fav/ColorTransform.h
Normal file
106
project/fm_viewer/fav/ColorTransform.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2014)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_COLORTRANSFORM_H
|
||||
#define QTAV_COLORTRANSFORM_H
|
||||
|
||||
#include <QtCore/QSharedDataPointer>
|
||||
#include <QtGui/QMatrix4x4>
|
||||
#include "_fav_constants.h"
|
||||
// TODO: kernel QGenericMatrix<M,N>
|
||||
//http://www.graficaobscura.com/matrix/index.html
|
||||
|
||||
namespace FAV {
|
||||
/*!
|
||||
* \brief The ColorTransform class
|
||||
* A convenience class to get color transformation matrix.
|
||||
* Implicitly shared.
|
||||
*/
|
||||
class ColorTransform
|
||||
{
|
||||
public:
|
||||
//http://msdn.microsoft.com/en-us/library/dd206750.aspx
|
||||
// cs: BT601 or BT709
|
||||
static const QMatrix4x4& YUV2RGB(ColorSpace cs);
|
||||
|
||||
ColorTransform();
|
||||
~ColorTransform(); //required by QSharedDataPointer if Private is forward declared
|
||||
/*!
|
||||
* \brief inputColorSpace
|
||||
* if inputColorSpace is different from outputColorSpace, then the result matrix(), matrixRef() and
|
||||
* matrixData() will count the transformation between in/out color space.
|
||||
* default in/output color space is rgb
|
||||
* \param cs
|
||||
*/
|
||||
ColorSpace inputColorSpace() const;
|
||||
void setInputColorSpace(ColorSpace cs);
|
||||
ColorSpace outputColorSpace() const;
|
||||
void setOutputColorSpace(ColorSpace cs);
|
||||
/// Currently assume input is yuv, output is rgb
|
||||
void setInputColorRange(ColorRange value);
|
||||
ColorRange inputColorRange() const;
|
||||
void setOutputColorRange(ColorRange value);
|
||||
ColorRange outputColorRange() const;
|
||||
/*!
|
||||
* \brief matrix
|
||||
* \return result matrix to transform from inputColorSpace to outputColorSpace with given brightness,
|
||||
* contrast, saturation and hue
|
||||
*/
|
||||
QMatrix4x4 matrix() const;
|
||||
const QMatrix4x4& matrixRef() const;
|
||||
|
||||
/*!
|
||||
* Get the matrix in column-major order. Used by OpenGL
|
||||
*/
|
||||
template<typename T> void matrixData(T* M) const {
|
||||
const QMatrix4x4 &m = matrixRef();
|
||||
M[0] = m(0,0), M[4] = m(0,1), M[8] = m(0,2), M[12] = m(0,3),
|
||||
M[1] = m(1,0), M[5] = m(1,1), M[9] = m(1,2), M[13] = m(1,3),
|
||||
M[2] = m(2,0), M[6] = m(2,1), M[10] = m(2,2), M[14] = m(2,3),
|
||||
M[3] = m(3,0), M[7] = m(3,1), M[11] = m(3,2), M[15] = m(3,3);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief reset
|
||||
* only set in-space transform to identity. other parameters such as in/out color space does not change
|
||||
*/
|
||||
void reset();
|
||||
// -1~1
|
||||
void setBrightness(qreal brightness);
|
||||
qreal brightness() const;
|
||||
// -1~1
|
||||
void setHue(qreal hue);
|
||||
qreal hue() const;
|
||||
// -1~1
|
||||
void setContrast(qreal contrast);
|
||||
qreal contrast() const;
|
||||
// -1~1
|
||||
void setSaturation(qreal saturation);
|
||||
qreal saturation() const;
|
||||
|
||||
void setChannelDepthScale(qreal value, bool scaleAlpha = false);
|
||||
|
||||
private:
|
||||
class Private;
|
||||
QSharedDataPointer<ColorTransform::Private> d;
|
||||
};
|
||||
} //namespace FAV
|
||||
#endif // QTAV_COLORTRANSFORM_H
|
||||
61
project/fm_viewer/fav/ConvolutionShader.h
Normal file
61
project/fm_viewer/fav/ConvolutionShader.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2016)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
#ifndef QTAV_CONVOLUTIONSHADER_H
|
||||
#define QTAV_CONVOLUTIONSHADER_H
|
||||
#include "VideoShader.h"
|
||||
#if !(REMOVE_VIDEO_SHADER)
|
||||
namespace FAV {
|
||||
class ConvolutionShaderPrivate;
|
||||
/*!
|
||||
* \brief The ConvolutionShader class
|
||||
* Uniform u_Kernel is used
|
||||
*/
|
||||
class Q_AV_EXPORT ConvolutionShader : public VideoShader
|
||||
{
|
||||
DPTR_DECLARE_PRIVATE(ConvolutionShader)
|
||||
public:
|
||||
ConvolutionShader();
|
||||
/*!
|
||||
* \brief kernelRadius
|
||||
* Default is 1, i.e. 3x3 kernel
|
||||
* kernelSize is (2*kernelRadius()+1)^2
|
||||
* \return
|
||||
*/
|
||||
int kernelRadius() const;
|
||||
/// TODO: update shader program if radius is changed. mark dirty program
|
||||
void setKernelRadius(int value);
|
||||
int kernelSize() const;
|
||||
protected:
|
||||
virtual const float* kernel() const = 0;
|
||||
const QByteArray& kernelUniformHeader() const; //can be used in your userFragmentShaderHeader();
|
||||
const QByteArray& kernelSample() const; //can be in your userSample();
|
||||
void setKernelUniformValue(); // can be used in your setUserUniformValues()
|
||||
private:
|
||||
/// default implementions
|
||||
const char* userShaderHeader(QOpenGLShader::ShaderType t) const Q_DECL_OVERRIDE;
|
||||
const char* userSample() const Q_DECL_OVERRIDE;
|
||||
bool setUserUniformValues() Q_DECL_OVERRIDE;
|
||||
protected:
|
||||
ConvolutionShader(ConvolutionShaderPrivate &d);
|
||||
};
|
||||
} //namespace FAV
|
||||
#endif // QTAV_CONVOLUTIONSHADER_H
|
||||
#endif // #if !(REMOVE_VIDEO_SHADER)
|
||||
241
project/fm_viewer/fav/CopyFrame_SSE2.cpp
Normal file
241
project/fm_viewer/fav/CopyFrame_SSE2.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
#if defined(__SSE__) || defined(_M_IX86) || defined(_M_X64) // gcc, clang defines __SSE__, vc does not
|
||||
#ifndef INC_FROM_NAMESPACE
|
||||
#include <stdint.h> //intptr_t
|
||||
#include <string.h>
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
|
||||
// from https://software.intel.com/en-us/articles/copying-accelerated-video-decode-frame-buffers
|
||||
// modified by wang-bin to support unaligned src/dest and sse2
|
||||
/*
|
||||
* 1. Fill a 4K byte cached (WB) memory buffer from the USWC video frame
|
||||
* 2. Copy the 4K byte cache contents to the destination WB frame
|
||||
* 3. Repeat steps 1 and 2 until the whole frame buffer has been copied.
|
||||
*
|
||||
* _mm_store_si128 and _mm_load_si128 intrinsics will compile to the MOVDQA instruction, _mm_stream_load_si128 and _mm_stream_si128 intrinsics compile to the MOVNTDQA and MOVNTDQ instructions
|
||||
*
|
||||
* using the same pitch (which is assumed to be a multiple of 64 bytes), and expecting 64 byte alignment of every row of the source, cached 4K buffer and destination buffers.
|
||||
* The MOVNTDQA streaming load instruction and the MOVNTDQ streaming store instruction require at least 16 byte alignment in their memory addresses.
|
||||
*/
|
||||
//
|
||||
// COPIES VIDEO FRAMES FROM USWC MEMORY TO WB SYSTEM MEMORY VIA CACHED BUFFER
|
||||
// ASSUMES PITCH IS A MULTIPLE OF 64B CACHE LINE SIZE, WIDTH MAY NOT BE
|
||||
#ifndef STREAM_LOAD_SI128
|
||||
#define STREAM_LOAD_SI128(x) _mm_load_si128(x)
|
||||
#endif //STREAM_LOAD_SI128
|
||||
|
||||
#define CACHED_BUFFER_SIZE 4096
|
||||
#define UINT unsigned int
|
||||
|
||||
// copy plane
|
||||
//QT_FUNCTION_TARGET("sse2")
|
||||
void CopyFrame_SSE2(void *pSrc, void *pDest, void *pCacheBlock, UINT width, UINT height, UINT pitch)
|
||||
{
|
||||
//assert(((intptr_t)pCacheBlock & 0x0f) == 0 && (dst_pitch & 0x0f) == 0);
|
||||
__m128i x0, x1, x2, x3;
|
||||
__m128i *pCache;
|
||||
UINT x, y, yLoad, yStore;
|
||||
|
||||
UINT rowsPerBlock = CACHED_BUFFER_SIZE / pitch;
|
||||
const UINT width64 = (width + 63) & ~0x03f;
|
||||
const UINT extraPitch = (pitch - width64) / 16;
|
||||
|
||||
__m128i *pLoad = (__m128i*)pSrc;
|
||||
__m128i *pStore = (__m128i*)pDest;
|
||||
|
||||
const bool src_unaligned = !!((intptr_t)pSrc & 0x0f);
|
||||
const bool dst_unaligned = !!((intptr_t)pDest & 0x0f);
|
||||
//if (src_unaligned || dst_unaligned)
|
||||
// qDebug("===========unaligned: src %d, dst: %d, extraPitch: %d", src_unaligned, dst_unaligned, extraPitch);
|
||||
// COPY THROUGH 4KB CACHED BUFFER
|
||||
for (y = 0; y < height; y += rowsPerBlock) {
|
||||
// ROWS LEFT TO COPY AT END
|
||||
if (y + rowsPerBlock > height)
|
||||
rowsPerBlock = height - y;
|
||||
|
||||
pCache = (__m128i *)pCacheBlock;
|
||||
|
||||
_mm_mfence();
|
||||
|
||||
// LOAD ROWS OF PITCH WIDTH INTO CACHED BLOCK
|
||||
for (yLoad = 0; yLoad < rowsPerBlock; yLoad++) {
|
||||
// COPY A ROW, CACHE LINE AT A TIME
|
||||
for (x = 0; x < pitch; x +=64) {
|
||||
// movntdqa
|
||||
x0 = STREAM_LOAD_SI128(pLoad + 0);
|
||||
x1 = STREAM_LOAD_SI128(pLoad + 1);
|
||||
x2 = STREAM_LOAD_SI128(pLoad + 2);
|
||||
x3 = STREAM_LOAD_SI128(pLoad + 3);
|
||||
if (src_unaligned) {
|
||||
// movdqu
|
||||
_mm_storeu_si128(pCache +0, x0);
|
||||
_mm_storeu_si128(pCache +1, x1);
|
||||
_mm_storeu_si128(pCache +2, x2);
|
||||
_mm_storeu_si128(pCache +3, x3);
|
||||
} else {
|
||||
// movdqa
|
||||
_mm_store_si128(pCache +0, x0);
|
||||
_mm_store_si128(pCache +1, x1);
|
||||
_mm_store_si128(pCache +2, x2);
|
||||
_mm_store_si128(pCache +3, x3);
|
||||
}
|
||||
pCache += 4;
|
||||
pLoad += 4;
|
||||
}
|
||||
}
|
||||
|
||||
_mm_mfence();
|
||||
|
||||
pCache = (__m128i *)pCacheBlock;
|
||||
// STORE ROWS OF FRAME WIDTH FROM CACHED BLOCK
|
||||
for (yStore = 0; yStore < rowsPerBlock; yStore++) {
|
||||
// copy a row, cache line at a time
|
||||
for (x = 0; x < width64; x += 64) {
|
||||
// movdqa
|
||||
x0 = _mm_load_si128(pCache);
|
||||
x1 = _mm_load_si128(pCache + 1);
|
||||
x2 = _mm_load_si128(pCache + 2);
|
||||
x3 = _mm_load_si128(pCache + 3);
|
||||
|
||||
if (dst_unaligned) {
|
||||
// movdqu
|
||||
_mm_storeu_si128(pStore, x0);
|
||||
_mm_storeu_si128(pStore + 1, x1);
|
||||
_mm_storeu_si128(pStore + 2, x2);
|
||||
_mm_storeu_si128(pStore + 3, x3);
|
||||
} else {
|
||||
// movntdq
|
||||
_mm_stream_si128(pStore, x0);
|
||||
_mm_stream_si128(pStore + 1, x1);
|
||||
_mm_stream_si128(pStore + 2, x2);
|
||||
_mm_stream_si128(pStore + 3, x3);
|
||||
}
|
||||
pCache += 4;
|
||||
pStore += 4;
|
||||
}
|
||||
pCache += extraPitch;
|
||||
pStore += extraPitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Taken from the QuickSync decoder by Eric Gur
|
||||
// a memcpy style function that copied data very fast from a GPU tiled memory (write back)
|
||||
// Performance tip: page offset (12 lsb) of both addresses should be different
|
||||
// optimally use a 2K offset between them.
|
||||
void *memcpy_sse2(void* dst, const void* src, size_t size)
|
||||
{
|
||||
static const size_t kRegsInLoop = sizeof(size_t) * 2; // 8 or 16
|
||||
|
||||
if (!dst || !src)
|
||||
return NULL;
|
||||
|
||||
// If memory is not aligned, use memcpy
|
||||
// TODO: only check dst aligned
|
||||
const bool isAligned = !(((size_t)(src) | (size_t)(dst)) & 0x0F);
|
||||
if (!isAligned)
|
||||
return memcpy(dst, src, size);
|
||||
|
||||
__m128i xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7;
|
||||
#ifdef __x86_64__
|
||||
__m128i xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15;
|
||||
#endif
|
||||
|
||||
size_t reminder = size & (kRegsInLoop * sizeof(xmm0) - 1); // Copy 128 or 256 bytes every loop
|
||||
size_t end = 0;
|
||||
|
||||
__m128i* pTrg = (__m128i*)dst;
|
||||
__m128i* pTrgEnd = pTrg + ((size - reminder) >> 4);
|
||||
__m128i* pSrc = (__m128i*)src;
|
||||
|
||||
// Make sure source is synced - doesn't hurt if not needed.
|
||||
_mm_sfence();
|
||||
|
||||
while (pTrg < pTrgEnd) {
|
||||
// _mm_stream_load_si128 emits the Streaming SIMD Extensions 4 (SSE4.1) instruction MOVNTDQA
|
||||
// Fastest method for copying GPU RAM. Available since Penryn (45nm Core 2 Duo/Quad)
|
||||
xmm0 = STREAM_LOAD_SI128(pSrc);
|
||||
xmm1 = STREAM_LOAD_SI128(pSrc + 1);
|
||||
xmm2 = STREAM_LOAD_SI128(pSrc + 2);
|
||||
xmm3 = STREAM_LOAD_SI128(pSrc + 3);
|
||||
xmm4 = STREAM_LOAD_SI128(pSrc + 4);
|
||||
xmm5 = STREAM_LOAD_SI128(pSrc + 5);
|
||||
xmm6 = STREAM_LOAD_SI128(pSrc + 6);
|
||||
xmm7 = STREAM_LOAD_SI128(pSrc + 7);
|
||||
#ifdef __x86_64__ // Use all 16 xmm registers
|
||||
xmm8 = STREAM_LOAD_SI128(pSrc + 8);
|
||||
xmm9 = STREAM_LOAD_SI128(pSrc + 9);
|
||||
xmm10 = STREAM_LOAD_SI128(pSrc + 10);
|
||||
xmm11 = STREAM_LOAD_SI128(pSrc + 11);
|
||||
xmm12 = STREAM_LOAD_SI128(pSrc + 12);
|
||||
xmm13 = STREAM_LOAD_SI128(pSrc + 13);
|
||||
xmm14 = STREAM_LOAD_SI128(pSrc + 14);
|
||||
xmm15 = STREAM_LOAD_SI128(pSrc + 15);
|
||||
#endif
|
||||
pSrc += kRegsInLoop;
|
||||
// _mm_store_si128 emit the SSE2 intruction MOVDQA (aligned store)
|
||||
// TODO: why not _mm_stream_si128? it works
|
||||
_mm_store_si128(pTrg , xmm0);
|
||||
_mm_store_si128(pTrg + 1, xmm1);
|
||||
_mm_store_si128(pTrg + 2, xmm2);
|
||||
_mm_store_si128(pTrg + 3, xmm3);
|
||||
_mm_store_si128(pTrg + 4, xmm4);
|
||||
_mm_store_si128(pTrg + 5, xmm5);
|
||||
_mm_store_si128(pTrg + 6, xmm6);
|
||||
_mm_store_si128(pTrg + 7, xmm7);
|
||||
#ifdef __x86_64__ // Use all 16 xmm registers
|
||||
_mm_store_si128(pTrg + 8, xmm8);
|
||||
_mm_store_si128(pTrg + 9, xmm9);
|
||||
_mm_store_si128(pTrg + 10, xmm10);
|
||||
_mm_store_si128(pTrg + 11, xmm11);
|
||||
_mm_store_si128(pTrg + 12, xmm12);
|
||||
_mm_store_si128(pTrg + 13, xmm13);
|
||||
_mm_store_si128(pTrg + 14, xmm14);
|
||||
_mm_store_si128(pTrg + 15, xmm15);
|
||||
#endif
|
||||
pTrg += kRegsInLoop;
|
||||
}
|
||||
|
||||
// Copy in 16 byte steps
|
||||
if (reminder >= 16) {
|
||||
size = reminder;
|
||||
reminder = size & 15;
|
||||
end = size >> 4;
|
||||
for (size_t i = 0; i < end; ++i)
|
||||
pTrg[i] = STREAM_LOAD_SI128(pSrc + i);
|
||||
}
|
||||
|
||||
// Copy last bytes - shouldn't happen as strides are modulu 16
|
||||
if (reminder) {
|
||||
__m128i temp = STREAM_LOAD_SI128(pSrc + end);
|
||||
|
||||
char* ps = (char*)(&temp);
|
||||
char* pt = (char*)(pTrg + end);
|
||||
|
||||
for (size_t i = 0; i < reminder; ++i)
|
||||
pt[i] = ps[i];
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
#endif
|
||||
42
project/fm_viewer/fav/CopyFrame_SSE4.cpp
Normal file
42
project/fm_viewer/fav/CopyFrame_SSE4.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
#if defined(__SSE__) || defined(_M_IX86) || defined(_M_X64) // gcc, clang defines __SSE__, vc does not
|
||||
// for mingw gcc
|
||||
#include <smmintrin.h> //stream load
|
||||
#include <stdint.h> //intptr_t
|
||||
#include <string.h>
|
||||
#define STREAM_LOAD_SI128(x) _mm_stream_load_si128(x)
|
||||
namespace sse4 { //avoid name conflict
|
||||
#define INC_FROM_NAMESPACE
|
||||
#include "CopyFrame_SSE2.cpp"
|
||||
} //namespace sse4
|
||||
|
||||
//QT_FUNCTION_TARGET("sse4.1")
|
||||
void CopyFrame_SSE4(void *pSrc, void *pDest, void *pCacheBlock, UINT width, UINT height, UINT pitch)
|
||||
{
|
||||
sse4::CopyFrame_SSE2(pSrc, pDest, pCacheBlock, width, height, pitch);
|
||||
}
|
||||
|
||||
void *memcpy_sse4(void* dst, const void* src, size_t size)
|
||||
{
|
||||
return sse4::memcpy_sse2(dst, src, size);
|
||||
}
|
||||
#endif
|
||||
136
project/fm_viewer/fav/DirectXHelper.cpp
Normal file
136
project/fm_viewer/fav/DirectXHelper.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "DirectXHelper.h"
|
||||
#include "../Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
namespace DXHelper {
|
||||
|
||||
const char* vendorName(unsigned id)
|
||||
{
|
||||
static const struct {
|
||||
unsigned id;
|
||||
char name[32];
|
||||
} vendors [] = {
|
||||
{ 0x1002, "ATI" },
|
||||
{ 0x10DE, "NVIDIA" },
|
||||
{ 0x1106, "VIA" },
|
||||
{ 0x8086, "Intel" },
|
||||
{ 0x5333, "S3 Graphics" },
|
||||
{ 0x4D4F4351, "Qualcomm" },
|
||||
{ 0, "" }
|
||||
};
|
||||
const char *vendor = "Unknown";
|
||||
for (int i = 0; vendors[i].id != 0; i++) {
|
||||
if (vendors[i].id == id) {
|
||||
vendor = vendors[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return vendor;
|
||||
}
|
||||
|
||||
#ifndef Q_OS_WINRT
|
||||
static void InitParameters(D3DPRESENT_PARAMETERS* d3dpp)
|
||||
{
|
||||
ZeroMemory(d3dpp, sizeof(*d3dpp));
|
||||
// use mozilla's parameters
|
||||
d3dpp->Flags = D3DPRESENTFLAG_VIDEO;
|
||||
d3dpp->Windowed = TRUE;
|
||||
d3dpp->hDeviceWindow = ::GetShellWindow(); //NULL;
|
||||
d3dpp->SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
//d3dpp->MultiSampleType = D3DMULTISAMPLE_NONE;
|
||||
//d3dpp->PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
|
||||
d3dpp->BackBufferCount = 1; //0; /* FIXME what to put here */
|
||||
d3dpp->BackBufferFormat = D3DFMT_UNKNOWN; //D3DFMT_X8R8G8B8; /* FIXME what to put here */
|
||||
d3dpp->BackBufferWidth = 1; //0;
|
||||
d3dpp->BackBufferHeight = 1; //0;
|
||||
//d3dpp->EnableAutoDepthStencil = FALSE;
|
||||
}
|
||||
|
||||
IDirect3DDevice9* CreateDevice9Ex(HINSTANCE dll, IDirect3D9Ex** d3d9ex, D3DADAPTER_IDENTIFIER9 *d3dai)
|
||||
{
|
||||
qDebug("creating d3d9 device ex... dll: %p", dll);
|
||||
//http://msdn.microsoft.com/en-us/library/windows/desktop/bb219676(v=vs.85).aspx
|
||||
typedef HRESULT (WINAPI *Create9ExFunc)(UINT SDKVersion, IDirect3D9Ex **ppD3D); //IDirect3D9Ex: void is ok
|
||||
Create9ExFunc Create9Ex = (Create9ExFunc)GetProcAddress(dll, "Direct3DCreate9Ex");
|
||||
if (!Create9Ex) {
|
||||
qWarning("Symbol not found: Direct3DCreate9Ex");
|
||||
return NULL;
|
||||
}
|
||||
DX_ENSURE(Create9Ex(D3D_SDK_VERSION, d3d9ex), NULL); //TODO: will D3D_SDK_VERSION be override by other headers?
|
||||
if (d3dai)
|
||||
DX_WARN((*d3d9ex)->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, d3dai));
|
||||
|
||||
D3DPRESENT_PARAMETERS d3dpp;
|
||||
InitParameters(&d3dpp);
|
||||
// D3DCREATE_MULTITHREADED is required by gl interop. https://www.opengl.org/registry/specs/NV/DX_interop.txt
|
||||
// D3DCREATE_SOFTWARE_VERTEXPROCESSING in other dxva decoders. D3DCREATE_HARDWARE_VERTEXPROCESSING in cuda samples
|
||||
DWORD flags = D3DCREATE_FPU_PRESERVE | D3DCREATE_MULTITHREADED | D3DCREATE_MIXED_VERTEXPROCESSING;
|
||||
IDirect3DDevice9Ex *d3d9dev = NULL;
|
||||
// mpv:
|
||||
/* Direct3D needs a HWND to create a device, even without using ::Present
|
||||
this HWND is used to alert Direct3D when there's a change of focus window.
|
||||
For now, use GetDesktopWindow, as it looks harmless */
|
||||
DX_ENSURE((*d3d9ex)->CreateDeviceEx(D3DADAPTER_DEFAULT,
|
||||
D3DDEVTYPE_HAL, GetShellWindow(),// GetDesktopWindow(), //GetShellWindow()?
|
||||
flags,
|
||||
&d3dpp,
|
||||
NULL,
|
||||
(IDirect3DDevice9Ex**)(&d3d9dev))
|
||||
, NULL);
|
||||
qDebug("IDirect3DDevice9Ex created");
|
||||
return d3d9dev;
|
||||
}
|
||||
|
||||
IDirect3DDevice9* CreateDevice9(HINSTANCE dll, IDirect3D9** d3d9, D3DADAPTER_IDENTIFIER9 *d3dai)
|
||||
{
|
||||
qDebug("creating d3d9 device...");
|
||||
typedef IDirect3D9* (WINAPI *Create9Func)(UINT SDKVersion);
|
||||
Create9Func Create9 = (Create9Func)GetProcAddress(dll, "Direct3DCreate9");
|
||||
if (!Create9) {
|
||||
qWarning("Symbol not found: Direct3DCreate9");
|
||||
return NULL;
|
||||
}
|
||||
*d3d9 = Create9(D3D_SDK_VERSION);
|
||||
if (!(*d3d9)) {
|
||||
qWarning("Direct3DCreate9 failed");
|
||||
return NULL;
|
||||
}
|
||||
if (d3dai)
|
||||
DX_WARN((*d3d9)->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, d3dai));
|
||||
|
||||
D3DPRESENT_PARAMETERS d3dpp;
|
||||
InitParameters(&d3dpp);
|
||||
DWORD flags = D3DCREATE_FPU_PRESERVE | D3DCREATE_MULTITHREADED | D3DCREATE_MIXED_VERTEXPROCESSING;
|
||||
IDirect3DDevice9 *d3d9dev = NULL;
|
||||
DX_ENSURE(((*d3d9)->CreateDevice(D3DADAPTER_DEFAULT,
|
||||
D3DDEVTYPE_HAL, GetShellWindow(),// GetDesktopWindow(), //GetShellWindow()?
|
||||
flags,
|
||||
&d3dpp, &d3d9dev))
|
||||
, NULL);
|
||||
qDebug("IDirect3DDevice9 created");
|
||||
return d3d9dev;
|
||||
}
|
||||
#endif //Q_OS_WINRT
|
||||
} // namespace DXHelper
|
||||
} //namespace FAV
|
||||
65
project/fm_viewer/fav/DirectXHelper.h
Normal file
65
project/fm_viewer/fav/DirectXHelper.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_DIRECTXHELPER_H
|
||||
#define QTAV_DIRECTXHELPER_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#ifndef Q_OS_WINRT
|
||||
#include <d3d9.h>
|
||||
#endif
|
||||
|
||||
namespace FAV {
|
||||
|
||||
#ifndef DX_LOG_COMPONENT
|
||||
#define DX_LOG_COMPONENT "DirectX"
|
||||
#endif //DX_LOG_COMPONENT
|
||||
#define DX_ENSURE(f, ...) DX_CHECK(f, return __VA_ARGS__;)
|
||||
#define DX_WARN(f) DX_CHECK(f)
|
||||
#define DX_ENSURE_OK(f, ...) DX_CHECK(f, return __VA_ARGS__;)
|
||||
#define DX_CHECK(f, ...) \
|
||||
do { \
|
||||
HRESULT hr = f; \
|
||||
if (FAILED(hr)) { \
|
||||
qWarning() << QString::fromLatin1(DX_LOG_COMPONENT " error@%1. " #f ": (0x%2) %3").arg(__LINE__).arg(hr, 0, 16).arg(qt_error_string(hr)); \
|
||||
__VA_ARGS__ \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
template <class T> void SafeRelease(T **ppT) {
|
||||
if (*ppT) {
|
||||
(*ppT)->Release();
|
||||
*ppT = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
namespace DXHelper {
|
||||
const char* vendorName(unsigned id);
|
||||
|
||||
#ifndef Q_OS_WINRT
|
||||
IDirect3DDevice9* CreateDevice9Ex(HINSTANCE dll, IDirect3D9Ex **d3d9ex, D3DADAPTER_IDENTIFIER9* d3dai = NULL);
|
||||
IDirect3DDevice9* CreateDevice9(HINSTANCE dll, IDirect3D9 **d3d9, D3DADAPTER_IDENTIFIER9* d3dai = NULL);
|
||||
#endif //Q_OS_WINRT
|
||||
} //namespace DXHelper
|
||||
|
||||
} //namespace FAV
|
||||
#endif //QTAV_DIRECTXHELPER_H
|
||||
158
project/fm_viewer/fav/EncodeFilter.h
Normal file
158
project/fm_viewer/fav/EncodeFilter.h
Normal file
@@ -0,0 +1,158 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2015)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_ENCODEFILTER_H
|
||||
#define QTAV_ENCODEFILTER_H
|
||||
|
||||
#define REMOVE_ENCODE_FILTER 1
|
||||
#if !(REMOVE_ENCODE_FILTER)
|
||||
|
||||
#include "Filter.h"
|
||||
#include "Packet.h"
|
||||
#include "AudioFrame.h"
|
||||
#include "VideoFrame.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class AudioEncoder;
|
||||
class AudioEncodeFilterPrivate;
|
||||
class Q_AV_EXPORT AudioEncodeFilter : public AudioFilter
|
||||
{
|
||||
Q_OBJECT
|
||||
DPTR_DECLARE_PRIVATE(AudioEncodeFilter)
|
||||
public:
|
||||
AudioEncodeFilter(QObject *parent = 0);
|
||||
/*!
|
||||
* \brief setAsync
|
||||
* Enable async encoding. Default is disabled.
|
||||
*/
|
||||
void setAsync(bool value = true);
|
||||
bool isAsync() const;
|
||||
/*!
|
||||
* \brief createEncoder
|
||||
* Destroy old encoder and create a new one. Filter has the ownership.
|
||||
* Encoder will open when encoding first valid frame, and set width/height as frame's.
|
||||
* \param name registered encoder name, for example "FFmpeg"
|
||||
* \return null if failed
|
||||
*/
|
||||
AudioEncoder* createEncoder(const QString& name = QLatin1String("FFmpeg"));
|
||||
/*!
|
||||
* \brief encoder
|
||||
* Use this to set encoder properties and options
|
||||
* \return Encoder instance or null if createEncoder failed
|
||||
*/
|
||||
AudioEncoder* encoder() const;
|
||||
// TODO: async property
|
||||
|
||||
/*!
|
||||
* \brief startTime
|
||||
* start to encode after startTime()
|
||||
*/
|
||||
qint64 startTime() const;
|
||||
void setStartTime(qint64 value);
|
||||
public Q_SLOTS:
|
||||
/*!
|
||||
* \brief finish
|
||||
* Tell the encoder no more frames to encode. Signal finished() will be emitted when all frames are encoded
|
||||
*/
|
||||
void finish();
|
||||
Q_SIGNALS:
|
||||
void finished();
|
||||
/*!
|
||||
* \brief readyToEncode
|
||||
* Emitted when encoder is open. All parameters are set and muxer can set codec properties now.
|
||||
* close the encoder() to reset and reopen.
|
||||
*/
|
||||
void readyToEncode();
|
||||
void frameEncoded(const FAV::Packet& packet);
|
||||
void startTimeChanged(qint64 value);
|
||||
// internal use only
|
||||
void requestToEncode(const AudioFrame& frame);
|
||||
protected Q_SLOTS:
|
||||
void encode(const FAV::AudioFrame& frame = AudioFrame());
|
||||
protected:
|
||||
virtual void process(Statistics* statistics, AudioFrame* frame = 0) Q_DECL_OVERRIDE;
|
||||
};
|
||||
|
||||
class VideoEncoder;
|
||||
class VideoEncodeFilterPrivate;
|
||||
class Q_AV_EXPORT VideoEncodeFilter : public VideoFilter
|
||||
{
|
||||
Q_OBJECT
|
||||
DPTR_DECLARE_PRIVATE(VideoEncodeFilter)
|
||||
public:
|
||||
VideoEncodeFilter(QObject* parent = 0);
|
||||
/*!
|
||||
* \brief setAsync
|
||||
* Enable async encoding. Default is disabled.
|
||||
*/
|
||||
void setAsync(bool value = true);
|
||||
bool isAsync() const;
|
||||
bool isSupported(VideoFilterContext::Type t) const Q_DECL_OVERRIDE { return t == VideoFilterContext::None;}
|
||||
/*!
|
||||
* \brief createEncoder
|
||||
* Destroy old encoder and create a new one. Filter has the ownership.
|
||||
* Encoder will open when encoding first valid frame, and set width/height as frame's.
|
||||
* \param name registered encoder name, for example "FFmpeg"
|
||||
* \return null if failed
|
||||
*/
|
||||
VideoEncoder* createEncoder(const QString& name = QLatin1String("FFmpeg"));
|
||||
/*!
|
||||
* \brief encoder
|
||||
* Use this to set encoder properties and options
|
||||
* \return Encoder instance or null if createEncoder failed
|
||||
*/
|
||||
VideoEncoder* encoder() const;
|
||||
// TODO: async property
|
||||
|
||||
/*!
|
||||
* \brief startTime
|
||||
* start to encode after startTime()
|
||||
*/
|
||||
qint64 startTime() const;
|
||||
void setStartTime(qint64 value);
|
||||
public Q_SLOTS:
|
||||
/*!
|
||||
* \brief finish
|
||||
* Tell the encoder no more frames to encode. Signal finished() will be emitted when all frames are encoded
|
||||
*/
|
||||
void finish();
|
||||
Q_SIGNALS:
|
||||
void finished();
|
||||
/*!
|
||||
* \brief readyToEncode
|
||||
* Emitted when encoder is open. All parameters are set and muxer can set codec properties now.
|
||||
* close the encoder() to reset and reopen.
|
||||
*/
|
||||
void readyToEncode();
|
||||
void frameEncoded(const FAV::Packet& packet);
|
||||
void startTimeChanged(qint64 value);
|
||||
// internal use only
|
||||
void requestToEncode(const FAV::VideoFrame& frame);
|
||||
protected Q_SLOTS:
|
||||
void encode(const FAV::VideoFrame& frame = VideoFrame());
|
||||
protected:
|
||||
virtual void process(Statistics* statistics, VideoFrame* frame = 0) Q_DECL_OVERRIDE;
|
||||
};
|
||||
} //namespace FAV
|
||||
#endif // QTAV_ENCODEFILTER_H
|
||||
#endif // #if !(REMOVE_ENCODE_FILTER)
|
||||
|
||||
158
project/fm_viewer/fav/FactoryDefine.h
Normal file
158
project/fm_viewer/fav/FactoryDefine.h
Normal file
@@ -0,0 +1,158 @@
|
||||
/******************************************************************************
|
||||
Some macros to create a factory and register functions
|
||||
Copyright (C) 2012-2015 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef FACTORYDEFINE_H
|
||||
#define FACTORYDEFINE_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/*!
|
||||
* A: Suppose we need a factory for class MyClass. We can query a derived class
|
||||
* of MyClass from factory by an id type MyClassId.
|
||||
* To create the factory, just 2 steps
|
||||
* 1. In MyClass.h:
|
||||
* #include "FactoryDefine.h"
|
||||
* FACTORY_DECLARE(MyClass)
|
||||
* 2. In MyClass.cpp:
|
||||
* #include "factory.h"
|
||||
* FACTORY_DEFINE(MyClass)
|
||||
*
|
||||
* To create and register a new subclass MyClassSubA with it's id
|
||||
* 0. In MyClassTypes.h (Usually just include this is enough to use the factory. And MyClassXXX.{h,cpp} can NOT include this file),
|
||||
* MyClassSubA's ID:
|
||||
* extern Q_AV_EXPORT MyClassId MyClassId_SubA;
|
||||
* In MyClassTypes.cpp, define the id value:
|
||||
* MyClassId MyClassId_SubA = some_value;
|
||||
* We define the id in MyClassTypes.cpp because MyClassSubA may not be compiled(e.g. platform dependent features), but the id must be defined.
|
||||
* 1. create a source file MyClassSubA.cpp and implement the required members
|
||||
* 2. In MyClassSubA.cpp, add the following lines
|
||||
* #include "prepost.h" //for PRE_FUNC_ADD()
|
||||
* //we don't want to depend on MyClassTypes.h, so extern
|
||||
* extern MyClassId MyClassId_SubA;
|
||||
* FACTORY_REGISTER_ID_AUTO(MyClass, SubA, "SubA's name")
|
||||
* void RegisterMyClassSubA_Man()
|
||||
* {
|
||||
* FACTORY_REGISTER_ID_MAN(MyClass, SubA, "SubA's name")
|
||||
* }
|
||||
*
|
||||
* 3. In MyClass.cpp, add register function into MyClass_RegisterAll();
|
||||
* extern void RegisterMyClassSubA_Man();
|
||||
* void MyClass_RegisterAll()
|
||||
* {
|
||||
* RegisterMyClassSubA_Man(); //add this line
|
||||
* }
|
||||
*
|
||||
*******************************************************************************************************
|
||||
* B: If MyClass and it's factory already exist in the library and you want to add a new subclass without
|
||||
* changing the library,
|
||||
* just create MyClassSubA.cpp with the content:
|
||||
*
|
||||
* #include "MyClass.h"
|
||||
* #include "prepost.h" //for PRE_FUNC_ADD()
|
||||
* MyClassId MyClassId_SubA = some_value; //it can be used somewhere else as "extern"
|
||||
* FACTORY_REGISTER_ID_AUTO(MyClass, SubA, "SubA's name")
|
||||
* void RegisterMyClassSubA_Man() //call it when you need as "extern"
|
||||
* {
|
||||
* FACTORY_REGISTER_ID_MAN(MyClass, SubA, "SubA's name")
|
||||
* }
|
||||
*
|
||||
* class MyClassSubA : public MyClass
|
||||
* {
|
||||
* ...
|
||||
* };
|
||||
*/
|
||||
|
||||
/*
|
||||
* This should be in header
|
||||
*/
|
||||
#define FACTORY_REGISTER(BASE, _ID, NAME) FACTORY_REGISTER_ID_TYPE(BASE, BASE##Id_##_ID, BASE##_ID, NAME)
|
||||
|
||||
#define FACTORY_REGISTER_ID_TYPE(BASE, ID, TYPE, NAME) \
|
||||
FACTORY_REGISTER_ID_TYPE_AUTO(BASE, ID, TYPE, NAME) \
|
||||
void Register##TYPE##_Man() { \
|
||||
FACTORY_REGISTER_ID_TYPE_MAN(BASE, ID, TYPE, NAME); \
|
||||
}
|
||||
|
||||
#define FACTORY_REGISTER_ID_AUTO(BASE, _ID, NAME) \
|
||||
FACTORY_REGISTER_ID_TYPE_AUTO(BASE, BASE##Id_##_ID, BASE##_ID, NAME)
|
||||
|
||||
#define FACTORY_REGISTER_ID_MAN(BASE, _ID, NAME) \
|
||||
FACTORY_REGISTER_ID_TYPE_MAN(BASE, BASE##Id_##_ID, BASE##_ID, NAME)
|
||||
|
||||
#define FACTORY_REGISTER_ID_TYPE_MAN(BASE, ID, TYPE, NAME) \
|
||||
BASE##Factory::register_<TYPE>(ID); \
|
||||
BASE##Factory::registerIdName(ID, NAME);
|
||||
|
||||
/*
|
||||
* FIXME: __init_##TYPE (only if static) and xxx_Man() has the same content, and are both defined, construtor functions will not be called for gcc5.
|
||||
* maybe also happens for ios
|
||||
* Remove xxx_Man() is also a workaround
|
||||
*/
|
||||
#define FACTORY_REGISTER_ID_TYPE_AUTO(BASE, ID, TYPE, NAME) \
|
||||
static int __init_##TYPE() { \
|
||||
FACTORY_REGISTER_ID_TYPE_MAN(BASE, ID, TYPE, NAME) \
|
||||
return 0; \
|
||||
} \
|
||||
PRE_FUNC_ADD(__init_##TYPE)
|
||||
|
||||
/*
|
||||
* This should be in header
|
||||
*/
|
||||
#define FACTORY_DECLARE(T) FACTORY_DECLARE_ID(T, T##Id)
|
||||
#define FACTORY_DECLARE_ID(T, ID) \
|
||||
class Q_AV_EXPORT T##Factory \
|
||||
{ \
|
||||
public: \
|
||||
typedef T* (*T##Creator)(); \
|
||||
static T* create(const ID& id); \
|
||||
template<class C> \
|
||||
static bool register_(const ID& id) { return registerCreator(id, create<C>); } \
|
||||
static bool registerCreator(const ID&, const T##Creator&); \
|
||||
static bool registerIdName(const ID& id, const std::string& name); \
|
||||
static bool unregisterCreator(const ID& id); \
|
||||
static ID id(const std::string& name, bool caseSensitive = true); \
|
||||
static std::string name(const ID &id); \
|
||||
static std::vector<ID> registeredIds(); \
|
||||
static std::vector<std::string> registeredNames(); \
|
||||
static size_t count(); \
|
||||
static T* getRandom(); \
|
||||
private: \
|
||||
template<class C> static T* create() { return new C(); } \
|
||||
};
|
||||
|
||||
/*
|
||||
* This should be in cpp
|
||||
*/
|
||||
#define FACTORY_DEFINE(T) FACTORY_DEFINE_ID(T, T##Id)
|
||||
#define FACTORY_DEFINE_ID(T, ID) \
|
||||
class T##FactoryBridge : public Factory<ID, T, T##FactoryBridge> {}; \
|
||||
T* T##Factory::create(const ID& id) { return T##FactoryBridge::Instance().create(id); } \
|
||||
bool T##Factory::registerCreator(const ID& id, const T##Creator& callback) { return T##FactoryBridge::Instance().registerCreator(id, callback); } \
|
||||
bool T##Factory::registerIdName(const ID& id, const std::string& name) { return T##FactoryBridge::Instance().registerIdName(id, name); } \
|
||||
bool T##Factory::unregisterCreator(const ID& id) { return T##FactoryBridge::Instance().unregisterCreator(id); } \
|
||||
ID T##Factory::id(const std::string& name, bool caseSensitive) { return T##FactoryBridge::Instance().id(name, caseSensitive); } \
|
||||
std::string T##Factory::name(const ID &id) { return T##FactoryBridge::Instance().name(id); } \
|
||||
std::vector<ID> T##Factory::registeredIds() { return T##FactoryBridge::Instance().registeredIds(); } \
|
||||
std::vector<std::string> T##Factory::registeredNames() { return T##FactoryBridge::Instance().registeredNames(); } \
|
||||
size_t T##Factory::count() { return T##FactoryBridge::Instance().count(); } \
|
||||
T* T##Factory::getRandom() { fflush(0);return T##FactoryBridge::Instance().getRandom(); }
|
||||
|
||||
#endif // FACTORYDEFINE_H
|
||||
185
project/fm_viewer/fav/Filter.cpp
Normal file
185
project/fm_viewer/fav/Filter.cpp
Normal file
@@ -0,0 +1,185 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "Filter.h"
|
||||
#include "Filter_p.h"
|
||||
#include "Statistics.h"
|
||||
#include "AVOutput.h"
|
||||
#include "AVPlayer.h"
|
||||
#include "FilterManager.h"
|
||||
#include "Logger.h"
|
||||
|
||||
/*
|
||||
* 1. parent == target (QObject):
|
||||
* in ~target(), remove the filter but not delete it (parent not null now).
|
||||
* in ~QObject, filter is deleted as a child
|
||||
*-----------------------------------------------
|
||||
* 2. parent != target.parent:
|
||||
* if delete filter first, filter must notify FilterManager (uninstall in dtor here) to uninstall to avoid target to access it (in ~target())
|
||||
* if delete target first, target remove the filter but not delete it (parent not null now).
|
||||
*/
|
||||
namespace FAV {
|
||||
|
||||
Filter::Filter(FilterPrivate &d, QObject *parent)
|
||||
: QObject(parent)
|
||||
, DPTR_INIT(&d)
|
||||
{
|
||||
if (parent)
|
||||
setOwnedByTarget(false);
|
||||
}
|
||||
|
||||
Filter::~Filter()
|
||||
{
|
||||
uninstall();
|
||||
}
|
||||
|
||||
void Filter::setEnabled(bool enabled)
|
||||
{
|
||||
DPTR_D(Filter);
|
||||
if (d.enabled == enabled)
|
||||
return;
|
||||
d.enabled = enabled;
|
||||
Q_EMIT enabledChanged(enabled);
|
||||
}
|
||||
|
||||
bool Filter::isEnabled() const
|
||||
{
|
||||
DPTR_D(const Filter);
|
||||
return d.enabled;
|
||||
}
|
||||
|
||||
void Filter::setOwnedByTarget(bool value)
|
||||
{
|
||||
d_func().owned_by_target = value;
|
||||
}
|
||||
|
||||
bool Filter::isOwnedByTarget() const
|
||||
{
|
||||
return d_func().owned_by_target;
|
||||
}
|
||||
|
||||
bool Filter::uninstall()
|
||||
{
|
||||
return FilterManager::instance().uninstallFilter(this); // TODO: target
|
||||
}
|
||||
|
||||
AudioFilter::AudioFilter(QObject *parent)
|
||||
: Filter(*new AudioFilterPrivate(), parent)
|
||||
{}
|
||||
|
||||
AudioFilter::AudioFilter(AudioFilterPrivate& d, QObject *parent)
|
||||
: Filter(d, parent)
|
||||
{}
|
||||
|
||||
/*TODO: move to AVPlayer.cpp to reduce dependency?*/
|
||||
bool AudioFilter::installTo(AVPlayer *player)
|
||||
{
|
||||
return player->installFilter(this);
|
||||
}
|
||||
|
||||
void AudioFilter::apply(Statistics *statistics, AudioFrame *frame)
|
||||
{
|
||||
process(statistics, frame);
|
||||
}
|
||||
|
||||
VideoFilter::VideoFilter(QObject *parent)
|
||||
: Filter(*new VideoFilterPrivate(), parent)
|
||||
{}
|
||||
|
||||
VideoFilter::VideoFilter(VideoFilterPrivate &d, QObject *parent)
|
||||
: Filter(d, parent)
|
||||
{}
|
||||
|
||||
VideoFilterContext *VideoFilter::context()
|
||||
{
|
||||
DPTR_D(VideoFilter);
|
||||
if (!d.context) {
|
||||
//fake. only to store some parameters at the beginnig. it will be destroyed and set to a new instance if context type mismatch in prepareContext, with old parameters
|
||||
d.context = VideoFilterContext::create(VideoFilterContext::QtPainter);
|
||||
}
|
||||
return d.context;
|
||||
}
|
||||
|
||||
bool VideoFilter::isSupported(VideoFilterContext::Type ct) const
|
||||
{
|
||||
// TODO: return false
|
||||
return VideoFilterContext::None == ct;
|
||||
}
|
||||
|
||||
bool VideoFilter::installTo(AVPlayer *player)
|
||||
{
|
||||
return player->installFilter(this);
|
||||
}
|
||||
|
||||
/*TODO: move to AVOutput.cpp to reduce dependency?*/
|
||||
/*
|
||||
* filter.installTo(target,...) calls target.installFilter(filter)
|
||||
* If filter is already registered in FilterManager, then return false
|
||||
* Otherwise, call FilterManager.register(filter) and target.filters.push_back(filter), return true
|
||||
* NOTE: the installed filter will be deleted by the target if filter is owned by target AND it's parent (QObject) is null.
|
||||
*/
|
||||
bool VideoFilter::installTo(AVOutput *output)
|
||||
{
|
||||
return output->installFilter(this);
|
||||
}
|
||||
|
||||
bool VideoFilter::prepareContext(VideoFilterContext *&ctx, Statistics *statistics, VideoFrame *frame)
|
||||
{
|
||||
DPTR_D(VideoFilter);
|
||||
if (!ctx || !isSupported(ctx->type())) {
|
||||
//qDebug("no context: %p, or context type %d is not supported", ctx, ctx? ctx->type() : 0);
|
||||
return isSupported(VideoFilterContext::None);
|
||||
}
|
||||
if (!d.context || d.context->type() != ctx->type()) {
|
||||
VideoFilterContext* c = VideoFilterContext::create(ctx->type());//each filter has it's own context instance, but share the common parameters
|
||||
if (d.context) {
|
||||
c->pen = d.context->pen;
|
||||
c->brush = d.context->brush;
|
||||
c->clip_path = d.context->clip_path;
|
||||
c->rect = d.context->rect;
|
||||
c->transform = d.context->transform;
|
||||
c->font = d.context->font;
|
||||
c->opacity = d.context->opacity;
|
||||
c->paint_device = d.context->paint_device;
|
||||
}
|
||||
if (d.context) {
|
||||
delete d.context;
|
||||
}
|
||||
d.context = c;
|
||||
}
|
||||
d.context->video_width = statistics->video_only.width;
|
||||
d.context->video_height = statistics->video_only.height;
|
||||
ctx->video_width = statistics->video_only.width;
|
||||
ctx->video_height = statistics->video_only.height;
|
||||
|
||||
// share common data
|
||||
d.context->shareFrom(ctx);
|
||||
d.context->initializeOnFrame(frame);
|
||||
ctx->shareFrom(d.context);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VideoFilter::apply(Statistics *statistics, VideoFrame *frame)
|
||||
{
|
||||
process(statistics, frame);
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
119
project/fm_viewer/fav/Filter.h
Normal file
119
project/fm_viewer/fav/Filter.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_FILTER_H
|
||||
#define QTAV_FILTER_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include "_fav_constants.h"
|
||||
#include "FilterContext.h"
|
||||
|
||||
namespace FAV {
|
||||
class AudioFormat;
|
||||
class AVOutput;
|
||||
class AVPlayer;
|
||||
class FilterPrivate;
|
||||
class Statistics;
|
||||
class Frame;
|
||||
class Q_AV_EXPORT Filter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
DPTR_DECLARE_PRIVATE(Filter)
|
||||
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
|
||||
public:
|
||||
virtual ~Filter();
|
||||
bool isEnabled() const;
|
||||
/*!
|
||||
* \brief setOwnedByTarget
|
||||
* If a filter is owned by target, it's not safe to access the filter after it's installed to a target.
|
||||
* QtAV will delete the installed filter internally if filter is owned by target AND it's parent (QObject) is null.
|
||||
*/
|
||||
void setOwnedByTarget(bool value = true);
|
||||
// default is false
|
||||
bool isOwnedByTarget() const;
|
||||
// setInput/Output: no need to call installTo
|
||||
// bool setInput(Filter*);
|
||||
// bool setOutput(Filter*);
|
||||
/*!
|
||||
* \brief installTo
|
||||
* Install filter to player can process every frame before rendering.
|
||||
* Equals to player->installFilter(this)
|
||||
*/
|
||||
virtual bool installTo(AVPlayer *player) = 0;
|
||||
// called in destructor automatically
|
||||
bool uninstall();
|
||||
public Q_SLOTS:
|
||||
void setEnabled(bool enabled = true);
|
||||
Q_SIGNALS:
|
||||
void enabledChanged(bool);
|
||||
protected:
|
||||
/*
|
||||
* If the filter is in AVThread, it's safe to operate on ref.
|
||||
*/
|
||||
Filter(FilterPrivate& d, QObject *parent = 0);
|
||||
|
||||
DPTR_DECLARE(Filter)
|
||||
};
|
||||
|
||||
class VideoFilterPrivate;
|
||||
class Q_AV_EXPORT VideoFilter : public Filter
|
||||
{
|
||||
Q_OBJECT
|
||||
DPTR_DECLARE_PRIVATE(VideoFilter)
|
||||
public:
|
||||
VideoFilter(QObject* parent = 0);
|
||||
|
||||
VideoFilterContext* context();
|
||||
virtual bool isSupported(VideoFilterContext::Type ct) const;
|
||||
bool installTo(AVPlayer *player);
|
||||
/*!
|
||||
* \brief installTo
|
||||
* The process() function is in rendering thread. Used by
|
||||
* 1. GPU filters
|
||||
* 2. QPainter rendering on widget based renderers. Changing the frame has no effect
|
||||
* \return false if already installed
|
||||
*/
|
||||
bool installTo(AVOutput *output); //only for video. move to video filter installToRenderer
|
||||
void apply(Statistics* statistics, VideoFrame *frame = 0);
|
||||
|
||||
bool prepareContext(VideoFilterContext*& ctx, Statistics* statistics = 0, VideoFrame* frame = 0); //internal use
|
||||
protected:
|
||||
VideoFilter(VideoFilterPrivate& d, QObject *parent = 0);
|
||||
virtual void process(Statistics* statistics, VideoFrame* frame = 0) = 0;
|
||||
};
|
||||
|
||||
class AudioFrame;
|
||||
class AudioFilterPrivate;
|
||||
class Q_AV_EXPORT AudioFilter : public Filter
|
||||
{
|
||||
Q_OBJECT
|
||||
DPTR_DECLARE_PRIVATE(AudioFilter)
|
||||
public:
|
||||
AudioFilter(QObject* parent = 0);
|
||||
bool installTo(AVPlayer *player);
|
||||
void apply(Statistics* statistics, AudioFrame *frame = 0);
|
||||
protected:
|
||||
AudioFilter(AudioFilterPrivate& d, QObject *parent = 0);
|
||||
virtual void process(Statistics* statistics, AudioFrame* frame = 0) = 0;
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QTAV_FILTER_H
|
||||
285
project/fm_viewer/fav/FilterContext.cpp
Normal file
285
project/fm_viewer/fav/FilterContext.cpp
Normal file
@@ -0,0 +1,285 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "FilterContext.h"
|
||||
#include <QtGui/QFontMetrics>
|
||||
#include <QtGui/QImage>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QTextDocument>
|
||||
#include "VideoFrame.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
VideoFilterContext *VideoFilterContext::create(Type t)
|
||||
{
|
||||
VideoFilterContext *ctx = 0;
|
||||
switch (t) {
|
||||
case QtPainter:
|
||||
ctx = new QPainterFilterContext();
|
||||
break;
|
||||
#if QTAV_HAVE(X11)
|
||||
case X11:
|
||||
ctx = new X11FilterContext();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void VideoFilterContext::initializeOnFrame(VideoFrame *frame)
|
||||
{
|
||||
Q_UNUSED(frame);
|
||||
}
|
||||
|
||||
VideoFilterContext::VideoFilterContext():
|
||||
painter(0)
|
||||
, opacity(1)
|
||||
, paint_device(0)
|
||||
, video_width(0)
|
||||
, video_height(0)
|
||||
, own_painter(false)
|
||||
, own_paint_device(false)
|
||||
{
|
||||
font.setBold(true);
|
||||
font.setPixelSize(26);
|
||||
pen.setColor(Qt::white);
|
||||
rect = QRect(32, 32, 0, 0); //TODO: why painting will above the visible area if the draw at (0, 0)? ascent
|
||||
}
|
||||
|
||||
VideoFilterContext::~VideoFilterContext()
|
||||
{
|
||||
if (painter) {
|
||||
// painter is shared, so may be end() multiple times.
|
||||
// TODO: use shared ptr
|
||||
//if (painter->isActive())
|
||||
// painter->end();
|
||||
if (own_painter) {
|
||||
qDebug("VideoFilterContext %p delete painter %p", this, painter);
|
||||
delete painter;
|
||||
painter = 0;
|
||||
}
|
||||
}
|
||||
if (paint_device) {
|
||||
qDebug("VideoFilterContext %p delete paint device in %p", this, paint_device);
|
||||
if (own_paint_device)
|
||||
delete paint_device; //delete recursively for widget
|
||||
paint_device = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoFilterContext::drawImage(const QPointF &pos, const QImage &image, const QRectF& source, Qt::ImageConversionFlags flags)
|
||||
{
|
||||
Q_UNUSED(pos);
|
||||
Q_UNUSED(image);
|
||||
Q_UNUSED(source);
|
||||
Q_UNUSED(flags);
|
||||
}
|
||||
|
||||
void VideoFilterContext::drawImage(const QRectF &target, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags)
|
||||
{
|
||||
Q_UNUSED(target);
|
||||
Q_UNUSED(image);
|
||||
Q_UNUSED(source);
|
||||
Q_UNUSED(flags);
|
||||
}
|
||||
|
||||
void VideoFilterContext::drawPlainText(const QPointF &pos, const QString &text)
|
||||
{
|
||||
Q_UNUSED(pos);
|
||||
Q_UNUSED(text);
|
||||
}
|
||||
|
||||
void VideoFilterContext::drawPlainText(const QRectF &rect, int flags, const QString &text)
|
||||
{
|
||||
Q_UNUSED(rect);
|
||||
Q_UNUSED(flags);
|
||||
Q_UNUSED(text);
|
||||
}
|
||||
|
||||
void VideoFilterContext::drawRichText(const QRectF &rect, const QString &text, bool wordWrap)
|
||||
{
|
||||
Q_UNUSED(rect);
|
||||
Q_UNUSED(text);
|
||||
Q_UNUSED(wordWrap);
|
||||
}
|
||||
|
||||
void VideoFilterContext::shareFrom(VideoFilterContext *vctx)
|
||||
{
|
||||
if (!vctx) {
|
||||
qWarning("shared filter context is null!");
|
||||
return;
|
||||
}
|
||||
painter = vctx->painter;
|
||||
paint_device = vctx->paint_device;
|
||||
own_painter = false;
|
||||
own_paint_device = false;
|
||||
video_width = vctx->video_width;
|
||||
video_height = vctx->video_height;
|
||||
}
|
||||
|
||||
QPainterFilterContext::QPainterFilterContext() : VideoFilterContext()
|
||||
, doc(0)
|
||||
, cvt(0)
|
||||
{}
|
||||
|
||||
QPainterFilterContext::~QPainterFilterContext()
|
||||
{
|
||||
if (doc) {
|
||||
delete doc;
|
||||
doc = 0;
|
||||
}
|
||||
if (cvt) {
|
||||
delete cvt;
|
||||
cvt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use drawPixmap?
|
||||
void QPainterFilterContext::drawImage(const QPointF &pos, const QImage &image, const QRectF& source, Qt::ImageConversionFlags flags)
|
||||
{
|
||||
if (!prepare())
|
||||
return;
|
||||
if (source.isNull())
|
||||
painter->drawImage(pos, image, QRectF(0, 0, image.width(), image.height()), flags);
|
||||
else
|
||||
painter->drawImage(pos, image, source, flags);
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
void QPainterFilterContext::drawImage(const QRectF &target, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags)
|
||||
{
|
||||
if (!prepare())
|
||||
return;
|
||||
if (source.isNull())
|
||||
painter->drawImage(target, image, QRectF(0, 0, image.width(), image.height()), flags);
|
||||
else
|
||||
painter->drawImage(target, image, source, flags);
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
void QPainterFilterContext::drawPlainText(const QPointF &pos, const QString &text)
|
||||
{
|
||||
if (!prepare())
|
||||
return;
|
||||
QFontMetrics fm(font);
|
||||
painter->drawText(pos + QPoint(0, fm.ascent()), text);
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
void QPainterFilterContext::drawPlainText(const QRectF &rect, int flags, const QString &text)
|
||||
{
|
||||
if (!prepare())
|
||||
return;
|
||||
if (rect.isNull())
|
||||
painter->drawText(rect.topLeft(), text);
|
||||
else
|
||||
painter->drawText(rect, flags, text);
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
void QPainterFilterContext::drawRichText(const QRectF &rect, const QString &text, bool wordWrap)
|
||||
{
|
||||
Q_UNUSED(rect);
|
||||
Q_UNUSED(text);
|
||||
if (!prepare())
|
||||
return;
|
||||
if (!doc)
|
||||
doc = new QTextDocument();
|
||||
doc->setHtml(text);
|
||||
painter->translate(rect.topLeft()); //drawContent() can not set target rect, so move here
|
||||
if (wordWrap)
|
||||
doc->setTextWidth(rect.width());
|
||||
doc->drawContents(painter);
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
bool QPainterFilterContext::isReady() const
|
||||
{
|
||||
return !!painter && painter->isActive();
|
||||
}
|
||||
|
||||
bool QPainterFilterContext::prepare()
|
||||
{
|
||||
if (!isReady())
|
||||
return false;
|
||||
painter->save(); //is it necessary?
|
||||
painter->setBrush(brush);
|
||||
painter->setPen(pen);
|
||||
painter->setFont(font);
|
||||
painter->setOpacity(opacity);
|
||||
if (!clip_path.isEmpty()) {
|
||||
painter->setClipPath(clip_path);
|
||||
}
|
||||
//transform at last! because clip_path is relative to paint device coord
|
||||
painter->setTransform(transform);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QPainterFilterContext::initializeOnFrame(VideoFrame *vframe)
|
||||
{
|
||||
if (!vframe) {
|
||||
if (!painter) {
|
||||
painter = new QPainter(); //warning: more than 1 painter on 1 device
|
||||
}
|
||||
if (!paint_device) {
|
||||
paint_device = painter->device();
|
||||
}
|
||||
if (!paint_device && !painter->isActive()) {
|
||||
qWarning("No paint device and painter is not active. No painting!");
|
||||
return;
|
||||
}
|
||||
if (!painter->isActive())
|
||||
painter->begin(paint_device);
|
||||
return;
|
||||
}
|
||||
VideoFormat format = vframe->format();
|
||||
if (!format.isValid()) {
|
||||
qWarning("Not a valid format");
|
||||
return;
|
||||
}
|
||||
if (format.imageFormat() == QImage::Format_Invalid) {
|
||||
format.setPixelFormat(VideoFormat::Format_RGB32);
|
||||
if (!cvt) {
|
||||
cvt = new VideoFrameConverter();
|
||||
}
|
||||
*vframe = cvt->convert(*vframe, format);
|
||||
}
|
||||
if (paint_device) {
|
||||
if (painter && painter->isActive()) {
|
||||
painter->end(); //destroy a paint device that is being painted is not allowed!
|
||||
}
|
||||
delete paint_device;
|
||||
paint_device = 0;
|
||||
}
|
||||
Q_ASSERT(video_width > 0 && video_height > 0);
|
||||
// direct draw on frame data, so use VideoFrame::constBits()
|
||||
paint_device = new QImage((uchar*)vframe->constBits(0), video_width, video_height, vframe->bytesPerLine(0), format.imageFormat());
|
||||
if (!painter)
|
||||
painter = new QPainter();
|
||||
own_painter = true;
|
||||
own_paint_device = true; //TODO: what about renderer is not a widget?
|
||||
painter->begin((QImage*)paint_device);
|
||||
}
|
||||
} //namespace FAV
|
||||
175
project/fm_viewer/fav/FilterContext.h
Normal file
175
project/fm_viewer/fav/FilterContext.h
Normal file
@@ -0,0 +1,175 @@
|
||||
/******************************************************************************
|
||||
QtAV: Media play library based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_FILTERCONTEXT_H
|
||||
#define QTAV_FILTERCONTEXT_H
|
||||
|
||||
#include "_fav_constants.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QRect>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QPainterPath>
|
||||
/*
|
||||
* QPainterFilterContext, D2DFilterContext, ...
|
||||
*/
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QPainter;
|
||||
class QPaintDevice;
|
||||
class QTextDocument;
|
||||
QT_END_NAMESPACE
|
||||
namespace FAV {
|
||||
|
||||
class VideoFrame;
|
||||
class Q_AV_EXPORT VideoFilterContext
|
||||
{
|
||||
public:
|
||||
enum Type { ////audio and video...
|
||||
QtPainter,
|
||||
OpenGL,
|
||||
Direct2D, //Not implemeted
|
||||
GdiPlus, //Not implemented
|
||||
X11,
|
||||
None //user defined filters, no context can be used
|
||||
};
|
||||
static VideoFilterContext* create(Type t);
|
||||
VideoFilterContext();
|
||||
virtual ~VideoFilterContext();
|
||||
virtual Type type() const = 0;
|
||||
|
||||
// map to Qt types
|
||||
//drawSurface?
|
||||
virtual void drawImage(const QPointF& pos, const QImage& image, const QRectF& source = QRectF(), Qt::ImageConversionFlags flags = Qt::AutoColor);
|
||||
// if target is null, draw image at target.topLeft(). if source is null, draw the whole image
|
||||
virtual void drawImage(const QRectF& target, const QImage& image, const QRectF& source = QRectF(), Qt::ImageConversionFlags flags = Qt::AutoColor);
|
||||
virtual void drawPlainText(const QPointF& pos, const QString& text);
|
||||
// if rect is null, draw single line text at rect.topLeft(), ignoring flags
|
||||
virtual void drawPlainText(const QRectF& rect, int flags, const QString& text);
|
||||
virtual void drawRichText(const QRectF& rect, const QString& text, bool wordWrap = true);
|
||||
/*
|
||||
* TODO: x, y, width, height: |?|>=1 is in pixel unit, otherwise is ratio of video context rect
|
||||
* filter.x(a).y(b).width(c).height(d)
|
||||
*/
|
||||
QRectF rect;
|
||||
// Fallback to QPainter if no other paint engine implemented
|
||||
QPainter *painter; //TODO: shared_ptr?
|
||||
qreal opacity;
|
||||
QTransform transform;
|
||||
QPainterPath clip_path;
|
||||
QFont font;
|
||||
QPen pen;
|
||||
QBrush brush;
|
||||
/*
|
||||
* for the filters apply on decoded data, paint_device must be initialized once the data changes
|
||||
* can we allocate memory on stack?
|
||||
*/
|
||||
QPaintDevice *paint_device;
|
||||
int video_width, video_height; //original size
|
||||
|
||||
protected:
|
||||
bool own_painter;
|
||||
bool own_paint_device;
|
||||
protected:
|
||||
virtual bool isReady() const = 0;
|
||||
// save the state then setup new parameters
|
||||
virtual bool prepare() = 0;
|
||||
|
||||
virtual void initializeOnFrame(VideoFrame *frame); //private?
|
||||
// share qpainter etc.
|
||||
virtual void shareFrom(VideoFilterContext *vctx);
|
||||
friend class VideoFilter;
|
||||
};
|
||||
|
||||
class VideoFrameConverter;
|
||||
//TODO: font, pen, brush etc?
|
||||
class Q_AV_EXPORT QPainterFilterContext Q_DECL_FINAL: public VideoFilterContext
|
||||
{
|
||||
public:
|
||||
QPainterFilterContext();
|
||||
virtual ~QPainterFilterContext();
|
||||
Type type() const Q_DECL_OVERRIDE { return VideoFilterContext::QtPainter;}
|
||||
// empty source rect equals to the whole source rect
|
||||
void drawImage(const QPointF& pos, const QImage& image, const QRectF& source = QRectF(), Qt::ImageConversionFlags flags = Qt::AutoColor) Q_DECL_OVERRIDE;
|
||||
void drawImage(const QRectF& target, const QImage& image, const QRectF& source = QRectF(), Qt::ImageConversionFlags flags = Qt::AutoColor) Q_DECL_OVERRIDE;
|
||||
void drawPlainText(const QPointF& pos, const QString& text) Q_DECL_OVERRIDE;
|
||||
// if rect is null, draw single line text at rect.topLeft(), ignoring flags
|
||||
void drawPlainText(const QRectF& rect, int flags, const QString& text) Q_DECL_OVERRIDE;
|
||||
void drawRichText(const QRectF& rect, const QString& text, bool wordWrap = true) Q_DECL_OVERRIDE;
|
||||
|
||||
protected:
|
||||
bool isReady() const Q_DECL_OVERRIDE;
|
||||
bool prepare() Q_DECL_OVERRIDE;
|
||||
void initializeOnFrame(VideoFrame *vframe) Q_DECL_OVERRIDE;
|
||||
|
||||
QTextDocument *doc;
|
||||
VideoFrameConverter *cvt;
|
||||
};
|
||||
|
||||
#if QTAV_HAVE(X11)
|
||||
class Q_AV_EXPORT X11FilterContext Q_DECL_FINAL: public VideoFilterContext
|
||||
{
|
||||
public:
|
||||
typedef struct _XDisplay Display;
|
||||
typedef struct _XGC *GC;
|
||||
typedef quintptr Drawable;
|
||||
typedef quintptr Pixmap;
|
||||
struct XImage;
|
||||
|
||||
X11FilterContext();
|
||||
virtual ~X11FilterContext();
|
||||
Type type() const Q_DECL_OVERRIDE { return VideoFilterContext::X11;}
|
||||
void resetX11(Display* dpy = 0, GC g = 0, Drawable d = 0);
|
||||
// empty source rect equals to the whole source rect
|
||||
void drawImage(const QPointF& pos, const QImage& image, const QRectF& source = QRectF(), Qt::ImageConversionFlags flags = Qt::AutoColor) Q_DECL_OVERRIDE;
|
||||
void drawImage(const QRectF& target, const QImage& image, const QRectF& source = QRectF(), Qt::ImageConversionFlags flags = Qt::AutoColor) Q_DECL_OVERRIDE;
|
||||
void drawPlainText(const QPointF& pos, const QString& text) Q_DECL_OVERRIDE;
|
||||
// if rect is null, draw single line text at rect.topLeft(), ignoring flags
|
||||
void drawPlainText(const QRectF& rect, int flags, const QString& text) Q_DECL_OVERRIDE;
|
||||
void drawRichText(const QRectF& rect, const QString& text, bool wordWrap = true) Q_DECL_OVERRIDE;
|
||||
protected:
|
||||
bool isReady() const Q_DECL_OVERRIDE;
|
||||
bool prepare() Q_DECL_OVERRIDE;
|
||||
void initializeOnFrame(VideoFrame *vframe) Q_DECL_OVERRIDE;
|
||||
void shareFrom(VideoFilterContext *vctx) Q_DECL_OVERRIDE;
|
||||
// null image: use the old x11 image/pixmap
|
||||
void renderTextImageX11(QImage* img, const QPointF &pos);
|
||||
void destroyX11Resources();
|
||||
|
||||
QTextDocument *doc;
|
||||
VideoFrameConverter *cvt;
|
||||
|
||||
Display* display;
|
||||
GC gc;
|
||||
Drawable drawable;
|
||||
XImage *text_img;
|
||||
XImage *mask_img;
|
||||
Pixmap mask_pix;
|
||||
QImage text_q;
|
||||
QImage mask_q;
|
||||
|
||||
bool plain;
|
||||
QString text;
|
||||
QImage test_img; //for computing bounding rect
|
||||
};
|
||||
#endif //QTAV_HAVE(X11)
|
||||
} //namespace FAV
|
||||
|
||||
#endif // QTAV_FILTERCONTEXT_H
|
||||
196
project/fm_viewer/fav/FilterManager.cpp
Normal file
196
project/fm_viewer/fav/FilterManager.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "FilterManager.h"
|
||||
#include <QtCore/QMap>
|
||||
#include "AVPlayer.h"
|
||||
#include "Filter.h"
|
||||
#include "AVOutput.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class FilterManagerPrivate : public DPtrPrivate<FilterManager>
|
||||
{
|
||||
public:
|
||||
FilterManagerPrivate()
|
||||
{}
|
||||
~FilterManagerPrivate() {
|
||||
}
|
||||
|
||||
QList<Filter*> pending_release_filters;
|
||||
QMap<AVOutput*, QList<Filter*> > filter_out_map;
|
||||
QMap<AVPlayer*, QList<Filter*> > afilter_player_map;
|
||||
QMap<AVPlayer*, QList<Filter*> > vfilter_player_map;
|
||||
};
|
||||
|
||||
FilterManager::FilterManager()
|
||||
{
|
||||
}
|
||||
|
||||
FilterManager::~FilterManager()
|
||||
{
|
||||
}
|
||||
|
||||
FilterManager& FilterManager::instance()
|
||||
{
|
||||
static FilterManager sMgr;
|
||||
return sMgr;
|
||||
}
|
||||
|
||||
bool FilterManager::insert(Filter *filter, QList<Filter *> &filters, int pos)
|
||||
{
|
||||
int p = pos;
|
||||
if (p < 0)
|
||||
p += filters.size();
|
||||
if (p < 0)
|
||||
p = 0;
|
||||
if (p > filters.size())
|
||||
p = filters.size();
|
||||
const int index = filters.indexOf(filter);
|
||||
// already installed at desired position
|
||||
if (p == index)
|
||||
return false;
|
||||
if (p >= 0)
|
||||
filters.removeAt(p);
|
||||
filters.insert(p, filter);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FilterManager::registerFilter(Filter *filter, AVOutput *output, int pos)
|
||||
{
|
||||
DPTR_D(FilterManager);
|
||||
d.pending_release_filters.removeAll(filter); //erase?
|
||||
QList<Filter*>& fs = d.filter_out_map[output];
|
||||
return insert(filter, fs, pos);
|
||||
}
|
||||
|
||||
QList<Filter*> FilterManager::outputFilters(AVOutput *output) const
|
||||
{
|
||||
DPTR_D(const FilterManager);
|
||||
return d.filter_out_map.value(output);
|
||||
}
|
||||
|
||||
bool FilterManager::registerAudioFilter(Filter *filter, AVPlayer *player, int pos)
|
||||
{
|
||||
DPTR_D(FilterManager);
|
||||
d.pending_release_filters.removeAll(filter); //erase?
|
||||
QList<Filter*>& fs = d.afilter_player_map[player];
|
||||
return insert(filter, fs, pos);
|
||||
}
|
||||
|
||||
QList<Filter*> FilterManager::audioFilters(AVPlayer *player) const
|
||||
{
|
||||
DPTR_D(const FilterManager);
|
||||
return d.afilter_player_map.value(player);
|
||||
}
|
||||
|
||||
bool FilterManager::registerVideoFilter(Filter *filter, AVPlayer *player, int pos)
|
||||
{
|
||||
DPTR_D(FilterManager);
|
||||
d.pending_release_filters.removeAll(filter); //erase?
|
||||
QList<Filter*>& fs = d.vfilter_player_map[player];
|
||||
return insert(filter, fs, pos);
|
||||
}
|
||||
|
||||
QList<Filter *> FilterManager::videoFilters(AVPlayer *player) const
|
||||
{
|
||||
DPTR_D(const FilterManager);
|
||||
return d.vfilter_player_map.value(player);
|
||||
}
|
||||
|
||||
// called by AVOutput/AVPlayer.uninstall imediatly
|
||||
bool FilterManager::unregisterAudioFilter(Filter *filter, AVPlayer *player)
|
||||
{
|
||||
DPTR_D(FilterManager);
|
||||
QList<Filter*>& fs = d.afilter_player_map[player];
|
||||
if (fs.removeAll(filter) > 0) {
|
||||
if (fs.isEmpty())
|
||||
d.afilter_player_map.remove(player);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FilterManager::unregisterVideoFilter(Filter *filter, AVPlayer *player)
|
||||
{
|
||||
DPTR_D(FilterManager);
|
||||
QList<Filter*>& fs = d.vfilter_player_map[player];
|
||||
if (fs.removeAll(filter) > 0) {
|
||||
if (fs.isEmpty())
|
||||
d.vfilter_player_map.remove(player);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FilterManager::unregisterFilter(Filter *filter, AVOutput *output)
|
||||
{
|
||||
DPTR_D(FilterManager);
|
||||
QList<Filter*>& fs = d.filter_out_map[output];
|
||||
return fs.removeAll(filter) > 0;
|
||||
}
|
||||
|
||||
bool FilterManager::uninstallFilter(Filter *filter)
|
||||
{
|
||||
DPTR_D(FilterManager);
|
||||
QMap<AVPlayer*, QList<Filter*> >::iterator it = d.vfilter_player_map.begin();
|
||||
while (it != d.vfilter_player_map.end()) {
|
||||
if (uninstallVideoFilter(filter, it.key()))
|
||||
return true;
|
||||
++it;
|
||||
}
|
||||
it = d.afilter_player_map.begin();
|
||||
while (it != d.afilter_player_map.end()) {
|
||||
if (uninstallAudioFilter(filter, it.key()))
|
||||
return true;
|
||||
++it;
|
||||
}
|
||||
QMap<AVOutput*, QList<Filter*> >::iterator it2 = d.filter_out_map.begin();
|
||||
while (it2 != d.filter_out_map.end()) {
|
||||
if (uninstallFilter(filter, it2.key()))
|
||||
return true;
|
||||
++it2;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FilterManager::uninstallAudioFilter(Filter *filter, AVPlayer *player)
|
||||
{
|
||||
if (unregisterAudioFilter(filter, player))
|
||||
return player->uninstallFilter(reinterpret_cast<AudioFilter*>(filter));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FilterManager::uninstallVideoFilter(Filter *filter, AVPlayer *player)
|
||||
{
|
||||
if (unregisterVideoFilter(filter, player))
|
||||
return player->uninstallFilter(reinterpret_cast<VideoFilter*>(filter));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FilterManager::uninstallFilter(Filter *filter, AVOutput *output)
|
||||
{
|
||||
if (unregisterFilter(filter, output))
|
||||
return output->uninstallFilter(filter);
|
||||
return false;
|
||||
}
|
||||
} //namespace FAV
|
||||
70
project/fm_viewer/fav/FilterManager.h
Normal file
70
project/fm_viewer/fav/FilterManager.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_FILTERMANAGER_H
|
||||
#define QTAV_FILTERMANAGER_H
|
||||
|
||||
#include <QtCore/QList>
|
||||
#include "Filter.h" // signal qobj parameter
|
||||
#include "_fav_constants.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class AVOutput;
|
||||
class AVPlayer;
|
||||
class FilterManagerPrivate;
|
||||
class FilterManager
|
||||
{
|
||||
DPTR_DECLARE_PRIVATE(FilterManager)
|
||||
Q_DISABLE_COPY(FilterManager)
|
||||
public:
|
||||
static FilterManager& instance();
|
||||
/*!
|
||||
* \brief registerFilter
|
||||
* record the filter in manager
|
||||
* target.installXXXFilter/filter.installTo(target) must call registerXXXFilter
|
||||
*/
|
||||
bool registerFilter(Filter *filter, AVOutput *output, int pos = 0x7FFFFFFF);
|
||||
QList<Filter*> outputFilters(AVOutput* output) const;
|
||||
bool registerAudioFilter(Filter *filter, AVPlayer *player, int pos = 0x7FFFFFFF);
|
||||
QList<Filter *> audioFilters(AVPlayer* player) const;
|
||||
bool registerVideoFilter(Filter *filter, AVPlayer *player, int pos = 0x7FFFFFFF);
|
||||
QList<Filter*> videoFilters(AVPlayer* player) const;
|
||||
bool unregisterAudioFilter(Filter *filter, AVPlayer *player);
|
||||
bool unregisterVideoFilter(Filter *filter, AVPlayer *player);
|
||||
bool unregisterFilter(Filter *filter, AVOutput *output);
|
||||
// unregister and call target.uninstall
|
||||
bool uninstallFilter(Filter *filter); //called by filter.uninstall
|
||||
bool uninstallAudioFilter(Filter *filter, AVPlayer* player);
|
||||
bool uninstallVideoFilter(Filter *filter, AVPlayer* player);
|
||||
bool uninstallFilter(Filter *filter, AVOutput* output);
|
||||
private:
|
||||
// return bool is for AVPlayer.installAudio/VideoFilter compatibility
|
||||
bool insert(Filter* filter, QList<Filter*>& filters, int pos);
|
||||
FilterManager();
|
||||
~FilterManager();
|
||||
|
||||
DPTR_DECLARE(FilterManager)
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
#endif // QTAV_FILTERMANAGER_H
|
||||
60
project/fm_viewer/fav/Filter_p.h
Normal file
60
project/fm_viewer/fav/Filter_p.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_FILTER_P_H
|
||||
#define QTAV_FILTER_P_H
|
||||
|
||||
#include "_fav_constants.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class Filter;
|
||||
class VideoFilterContext;
|
||||
class Statistics;
|
||||
class Q_AV_PRIVATE_EXPORT FilterPrivate : public DPtrPrivate<Filter>
|
||||
{
|
||||
public:
|
||||
FilterPrivate():
|
||||
enabled(true)
|
||||
, owned_by_target(false)
|
||||
{}
|
||||
virtual ~FilterPrivate() {}
|
||||
|
||||
bool enabled;
|
||||
bool owned_by_target;
|
||||
};
|
||||
|
||||
class Q_AV_PRIVATE_EXPORT VideoFilterPrivate : public FilterPrivate
|
||||
{
|
||||
public:
|
||||
VideoFilterPrivate() :
|
||||
context(0)
|
||||
{}
|
||||
VideoFilterContext *context; //used only when is necessary
|
||||
};
|
||||
|
||||
class Q_AV_PRIVATE_EXPORT AudioFilterPrivate : public FilterPrivate
|
||||
{
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
#endif // QTAV_FILTER_P_H
|
||||
227
project/fm_viewer/fav/Frame.cpp
Normal file
227
project/fm_viewer/fav/Frame.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "Frame.h"
|
||||
#include "Frame_p.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
Frame::Frame(const Frame &other)
|
||||
:d_ptr(other.d_ptr)
|
||||
#if (PLAY_BREAK_EOF_FRAME)
|
||||
,_isEOF(false)
|
||||
#endif // PLAY_BREAK_EOF_FRAME
|
||||
{
|
||||
}
|
||||
|
||||
Frame::Frame(FramePrivate *d):
|
||||
d_ptr(d)
|
||||
#if (PLAY_BREAK_EOF_FRAME)
|
||||
,_isEOF(false)
|
||||
#endif // PLAY_BREAK_EOF_FRAME
|
||||
{
|
||||
}
|
||||
|
||||
Frame::~Frame()
|
||||
{
|
||||
}
|
||||
|
||||
Frame &Frame::operator =(const Frame &other)
|
||||
{
|
||||
d_ptr = other.d_ptr;
|
||||
#if (PLAY_BREAK_EOF_FRAME)
|
||||
_isEOF = other._isEOF;
|
||||
#endif // PLAY_BREAK_EOF_FRAME
|
||||
return *this;
|
||||
}
|
||||
|
||||
int Frame::bytesPerLine(int plane) const
|
||||
{
|
||||
if (plane < 0 || plane >= planeCount()) {
|
||||
qWarning("Invalid plane! Valid range is [0, %d)", planeCount());
|
||||
return 0;
|
||||
}
|
||||
return d_func()->line_sizes[plane];
|
||||
}
|
||||
|
||||
QByteArray Frame::frameData() const
|
||||
{
|
||||
return d_func()->data;
|
||||
}
|
||||
|
||||
QByteArray Frame::data(int plane) const
|
||||
{
|
||||
if (plane < 0 || plane >= planeCount()) {
|
||||
qWarning("Invalid plane! Valid range is [0, %d)", planeCount());
|
||||
return QByteArray();
|
||||
}
|
||||
return QByteArray((char*)d_func()->planes[plane], bytesPerLine(plane));
|
||||
}
|
||||
|
||||
uchar* Frame::bits(int plane)
|
||||
{
|
||||
if (plane < 0 || plane >= planeCount()) {
|
||||
qWarning("Invalid plane! Valid range is [0, %d]", planeCount());
|
||||
return 0;
|
||||
}
|
||||
return d_func()->planes[plane];
|
||||
}
|
||||
|
||||
const uchar* Frame::constBits(int plane) const
|
||||
{
|
||||
if (plane < 0 || plane >= planeCount()) {
|
||||
qWarning("Invalid plane! Valid range is [0, %d]", planeCount());
|
||||
return 0;
|
||||
}
|
||||
return d_func()->planes[plane];
|
||||
}
|
||||
|
||||
void Frame::setBits(uchar *b, int plane)
|
||||
{
|
||||
if (plane < 0 || plane >= planeCount()) {
|
||||
qWarning("Invalid plane! Valid range is [0, %d]", planeCount());
|
||||
return;
|
||||
}
|
||||
Q_D(Frame);
|
||||
d->planes[plane] = b;
|
||||
}
|
||||
|
||||
void Frame::setBits(const QVector<uchar *> &b)
|
||||
{
|
||||
Q_D(Frame);
|
||||
const int nb_planes = planeCount();
|
||||
d->planes = b;
|
||||
if (d->planes.size() > nb_planes) {
|
||||
d->planes.reserve(nb_planes);
|
||||
d->planes.resize(nb_planes);
|
||||
}
|
||||
}
|
||||
|
||||
void Frame::setBits(quint8 *slice[])
|
||||
{
|
||||
for (int i = 0; i < planeCount(); ++i ) {
|
||||
setBits(slice[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
void Frame::setBytesPerLine(int lineSize, int plane)
|
||||
{
|
||||
if (plane < 0 || plane >= planeCount()) {
|
||||
qWarning("Invalid plane! Valid range is [0, %d)", planeCount());
|
||||
return;
|
||||
}
|
||||
Q_D(Frame);
|
||||
d->line_sizes[plane] = lineSize;
|
||||
}
|
||||
|
||||
void Frame::setBytesPerLine(const QVector<int> &lineSize)
|
||||
{
|
||||
Q_D(Frame);
|
||||
const int nb_planes = planeCount();
|
||||
d->line_sizes = lineSize;
|
||||
if (d->line_sizes.size() > nb_planes) {
|
||||
d->line_sizes.reserve(nb_planes);
|
||||
d->line_sizes.resize(nb_planes);
|
||||
}
|
||||
}
|
||||
|
||||
void Frame::setBytesPerLine(int stride[])
|
||||
{
|
||||
for (int i = 0; i < planeCount(); ++i ) {
|
||||
setBytesPerLine(stride[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
int Frame::planeCount() const
|
||||
{
|
||||
Q_D(const Frame);
|
||||
return d->planes.size();
|
||||
}
|
||||
|
||||
int Frame::channelCount() const
|
||||
{
|
||||
return planeCount();
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns any extra metadata associated with this frame.
|
||||
*/
|
||||
QVariantMap Frame::availableMetaData() const
|
||||
{
|
||||
Q_D(const Frame);
|
||||
return d->metadata;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns any metadata for this frame for the given \a key.
|
||||
|
||||
This might include frame specific information from
|
||||
a camera, or subtitles from a decoded video stream.
|
||||
|
||||
See the documentation for the relevant video frame
|
||||
producer for further information about available metadata.
|
||||
*/
|
||||
QVariant Frame::metaData(const QString &key) const
|
||||
{
|
||||
Q_D(const Frame);
|
||||
return d->metadata.value(key);
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the metadata for the given \a key to \a value.
|
||||
|
||||
If \a value is a null variant, any metadata for this key will be removed->
|
||||
|
||||
The producer of the video frame might use this to associate
|
||||
certain data with this frame, or for an intermediate processor
|
||||
to add information for a consumer of this frame.
|
||||
*/
|
||||
void Frame::setMetaData(const QString &key, const QVariant &value)
|
||||
{
|
||||
Q_D(Frame);
|
||||
if (!value.isNull())
|
||||
d->metadata.insert(key, value);
|
||||
else
|
||||
d->metadata.remove(key);
|
||||
}
|
||||
|
||||
qreal Frame::pts() const
|
||||
{
|
||||
return d_func()->pts;
|
||||
}
|
||||
|
||||
void Frame::setPTS(qreal ts)
|
||||
{
|
||||
d_func()->pts = ts;
|
||||
}
|
||||
|
||||
qreal Frame::duration() const
|
||||
{
|
||||
return d_func()->duration;
|
||||
}
|
||||
|
||||
void Frame::setDuration(qreal duration)
|
||||
{
|
||||
d_func()->duration = duration;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
111
project/fm_viewer/fav/Frame.h
Normal file
111
project/fm_viewer/fav/Frame.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_FRAME_H
|
||||
#define QTAV_FRAME_H
|
||||
|
||||
#include "_fav_constants.h"
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QSharedData>
|
||||
|
||||
// TODO: fromAVFrame() asAVFrame()?
|
||||
namespace FAV {
|
||||
|
||||
class FramePrivate;
|
||||
class Q_AV_EXPORT Frame
|
||||
{
|
||||
Q_DECLARE_PRIVATE(Frame)
|
||||
public:
|
||||
Frame(const Frame& other);
|
||||
virtual ~Frame() = 0;
|
||||
Frame& operator =(const Frame &other);
|
||||
|
||||
#if (PLAY_BREAK_EOF_FRAME)
|
||||
bool _isEOF;
|
||||
bool isEOF() const;
|
||||
void setEOF(); // 최종 프레임으로 지정
|
||||
#endif // PLAY_BREAK_EOF_FRAME
|
||||
|
||||
/*!
|
||||
* \brief planeCount
|
||||
* a decoded frame can be packed and planar. packed format has only 1 plane, while planar
|
||||
* format has more than 1 plane. For audio, the number plane equals channel count. For
|
||||
* video, rgb is 1 plane, yuv420p is 3 plane, p means planar
|
||||
* \param plane default is the first plane
|
||||
* \return
|
||||
*/
|
||||
int planeCount() const;
|
||||
/*!
|
||||
* \brief channelCount
|
||||
* for audio, channel count equals plane count
|
||||
* for video, channels >= planes
|
||||
* \return
|
||||
*/
|
||||
virtual int channelCount() const;
|
||||
/*!
|
||||
* \brief bytesPerLine
|
||||
* For video, it's size of each picture line. For audio, it's the whole size of plane
|
||||
* \param plane
|
||||
* \return line size of plane
|
||||
*/
|
||||
int bytesPerLine(int plane = 0) const;
|
||||
// the whole frame data. may be empty unless clone() or allocate is called
|
||||
QByteArray frameData() const;
|
||||
// deep copy 1 plane data
|
||||
QByteArray data(int plane = 0) const;
|
||||
uchar* bits(int plane = 0);
|
||||
const uchar *bits(int plane = 0) const { return constBits(plane);}
|
||||
const uchar* constBits(int plane = 0) const;
|
||||
/*!
|
||||
* \brief setBits
|
||||
* does nothing if plane is invalid. if given array size is greater than planeCount(), only planeCount() elements is used
|
||||
* \param b slice
|
||||
* \param plane color/audio channel
|
||||
*/
|
||||
// TODO: const?
|
||||
void setBits(uchar *b, int plane = 0);
|
||||
void setBits(const QVector<uchar*>& b);
|
||||
void setBits(quint8 *slice[]);
|
||||
/*!
|
||||
* \brief setBytesPerLine
|
||||
* does nothing if plane is invalid. if given array size is greater than planeCount(), only planeCount() elements is used
|
||||
*/
|
||||
void setBytesPerLine(int lineSize, int plane = 0);
|
||||
void setBytesPerLine(const QVector<int>& lineSize);
|
||||
void setBytesPerLine(int stride[]);
|
||||
|
||||
QVariantMap availableMetaData() const;
|
||||
QVariant metaData(const QString& key) const;
|
||||
void setMetaData(const QString &key, const QVariant &value);
|
||||
void setPTS(qreal ts);
|
||||
qreal pts() const;
|
||||
void setDuration(qreal duration);
|
||||
qreal duration() const;
|
||||
inline void swap(Frame &other) { qSwap(d_ptr, other.d_ptr); }
|
||||
|
||||
protected:
|
||||
Frame(FramePrivate *d);
|
||||
QExplicitlySharedDataPointer<FramePrivate> d_ptr;
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
#endif // QTAV_FRAME_H
|
||||
389
project/fm_viewer/fav/FrameReader.cpp
Normal file
389
project/fm_viewer/fav/FrameReader.cpp
Normal file
@@ -0,0 +1,389 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2016)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
#include "FrameReader.h"
|
||||
#include <QtCore/QQueue>
|
||||
#include <QtCore/QThread>
|
||||
#include "AVDemuxer.h"
|
||||
#include "VideoDecoder.h"
|
||||
#include "BlockingQueue.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
typedef FAV::BlockingQueue<VideoFrame> VideoFrameQueue;
|
||||
const int kQueueMin = 2;
|
||||
static QVariantHash dec_opt_framedrop;
|
||||
static QVariantHash dec_opt_normal;
|
||||
|
||||
class FrameReader::Private {
|
||||
public:
|
||||
Private() : nb_seek(0) {
|
||||
QVariantHash opt;
|
||||
opt[QString::fromLatin1("skip_frame")] = 8; // 8 for "avcodec", "NoRef" for "FFmpeg". see AVDiscard
|
||||
opt[QString::fromLatin1("skip_loop_filter")] = 8; //skip all?
|
||||
//skip_dict is slower
|
||||
dec_opt_framedrop[QString::fromLatin1("avcodec")] = opt;
|
||||
opt[QString::fromLatin1("skip_frame")] = 0; // 0 for "avcodec", "Default" for "FFmpeg". see AVDiscard
|
||||
opt[QString::fromLatin1("skip_loop_filter")] = 0;
|
||||
dec_opt_normal[QString::fromLatin1("avcodec")] = opt; // avcodec need correct string or value in libavcodec
|
||||
|
||||
//decs = QStringList() << "VideoToolbox" << "FFmpeg";
|
||||
vframes.setCapacity(4);
|
||||
vframes.setThreshold(kQueueMin); //
|
||||
}
|
||||
~Private() {
|
||||
if (read_thread.isRunning()) {
|
||||
read_thread.quit();
|
||||
read_thread.wait();
|
||||
}
|
||||
}
|
||||
|
||||
bool tryLoad();
|
||||
qint64 seekInternal(qint64 pos);
|
||||
QString url;
|
||||
QStringList vdecs;
|
||||
AVDemuxer demuxer;
|
||||
QScopedPointer<VideoDecoder> decoder;
|
||||
VideoFrameQueue vframes;
|
||||
QThread read_thread;
|
||||
int nb_seek;
|
||||
};
|
||||
|
||||
bool FrameReader::Private::tryLoad()
|
||||
{
|
||||
const bool loaded = demuxer.fileName() == url && demuxer.isLoaded();
|
||||
if (loaded && decoder)
|
||||
return true;
|
||||
if (decoder) { // new source
|
||||
decoder->close();
|
||||
decoder.reset(0);
|
||||
}
|
||||
if (!loaded || demuxer.atEnd()) {
|
||||
demuxer.unload();
|
||||
demuxer.setMedia(url);
|
||||
if (!demuxer.load()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const bool has_video = demuxer.videoStreams().size() > 0;
|
||||
if (!has_video) {
|
||||
demuxer.unload();
|
||||
return false;
|
||||
}
|
||||
if (vdecs.isEmpty()) {
|
||||
VideoDecoder *vd = VideoDecoder::create("FFmpeg");
|
||||
if (vd) {
|
||||
decoder.reset(vd);
|
||||
decoder->setCodecContext(demuxer.videoCodecContext());
|
||||
if (!decoder->open())
|
||||
decoder.reset(0);
|
||||
}
|
||||
} else {
|
||||
foreach (const QString& c, vdecs) {
|
||||
VideoDecoder *vd = VideoDecoder::create(c.toLatin1().constData());
|
||||
if (!vd)
|
||||
continue;
|
||||
decoder.reset(vd);
|
||||
decoder->setCodecContext(demuxer.videoCodecContext());
|
||||
decoder->setProperty("copyMode", "OptimizedCopy");
|
||||
if (!decoder->open()) {
|
||||
decoder.reset(0);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
nb_seek = 0;
|
||||
qDebug("decoder: %p", decoder.data());
|
||||
vframes.setThreshold(kQueueMin);
|
||||
return !!decoder;
|
||||
}
|
||||
|
||||
// code is from QtAV VideoFrameExtractor.cpp
|
||||
qint64 FrameReader::Private::seekInternal(qint64 value)
|
||||
{
|
||||
if (!tryLoad()) {
|
||||
qDebug("load error");
|
||||
return -1;
|
||||
}
|
||||
VideoFrame frame;
|
||||
int range = 100;
|
||||
demuxer.seek(value);
|
||||
const int vstream = demuxer.videoStream();
|
||||
Packet pkt;
|
||||
qint64 pts0 = -1;
|
||||
bool warn_bad_seek = true;
|
||||
bool warn_out_of_range = true;
|
||||
while (!demuxer.atEnd()) {
|
||||
if (!demuxer.readFrame())
|
||||
continue;
|
||||
if (demuxer.stream() != vstream)
|
||||
continue;
|
||||
pkt = demuxer.packet();
|
||||
if (pts0 < 0LL)
|
||||
pts0 = (qint64)(pkt.pts*1000.0);
|
||||
if ((qint64)(pkt.pts*1000.0) - value > (qint64)range) {
|
||||
if (warn_out_of_range)
|
||||
qDebug("read packet out of range");
|
||||
warn_out_of_range = false;
|
||||
// No return because decoder needs more packets before the desired frame is decoded
|
||||
//return false;
|
||||
}
|
||||
//qDebug("video packet: %f", pkt.pts);
|
||||
// TODO: always key frame?
|
||||
if (pkt.hasKeyFrame)
|
||||
break;
|
||||
if (warn_bad_seek)
|
||||
qWarning("Not seek to key frame!!!");
|
||||
warn_bad_seek = false;
|
||||
}
|
||||
// enlarge range if seek to key-frame failed
|
||||
const qint64 key_pts = (qint64)(pkt.pts*1000.0);
|
||||
const bool enlarge_range = pts0 >= 0LL && key_pts - pts0 > 0LL;
|
||||
if (enlarge_range) {
|
||||
range = qMax<qint64>(key_pts - value, range);
|
||||
qDebug() << "enlarge range ==>>>> " << range;
|
||||
}
|
||||
if (!pkt.isValid()) {
|
||||
qWarning("FrameReader failed to get a packet at %lld", value);
|
||||
return -1;
|
||||
}
|
||||
decoder->flush(); //must flush otherwise old frames will be decoded at the beginning
|
||||
decoder->setOptions(dec_opt_normal);
|
||||
// must decode key frame
|
||||
int k = 0;
|
||||
while (k < 2 && !frame.isValid()) {
|
||||
//qWarning("invalid key frame!!!!! undecoded: %d", decoder->undecodedSize());
|
||||
if (decoder->decode(pkt)) {
|
||||
frame = decoder->frame();
|
||||
}
|
||||
++k;
|
||||
}
|
||||
// if seek backward correctly to key frame, diff0 = t - value <= 0
|
||||
// but sometimes seek to no-key frame(and range is enlarged), diff0 >= 0
|
||||
// decode key frame
|
||||
const int diff0 = qint64(frame.pts()*1000.0) - value;
|
||||
if (qAbs(diff0) <= range) { //TODO: flag forward: result pts must >= value
|
||||
if (frame.isValid()) {
|
||||
qDebug() << "VideoFrameExtractor: key frame found @" << frame.pts() <<" diff=" << diff0 << ". format: " << frame.format();
|
||||
return qint64(frame.pts()*1000.0);
|
||||
}
|
||||
}
|
||||
QVariantHash* dec_opt = &dec_opt_normal; // 0: default, 1: framedrop
|
||||
// decode at the given position
|
||||
while (!demuxer.atEnd()) {
|
||||
if (!demuxer.readFrame())
|
||||
continue;
|
||||
if (demuxer.stream() != vstream)
|
||||
continue;
|
||||
pkt = demuxer.packet();
|
||||
const qreal t = pkt.pts;
|
||||
//qDebug("video packet: %f, delta=%lld", t, value - qint64(t*1000.0));
|
||||
if (!pkt.isValid()) {
|
||||
qWarning("invalid packet. no decode");
|
||||
continue;
|
||||
}
|
||||
if (pkt.hasKeyFrame) {
|
||||
// FIXME:
|
||||
//qCritical("Internal error. Can not be a key frame!!!!");
|
||||
//return false; //??
|
||||
}
|
||||
qint64 diff = qint64(t*1000.0) - value;
|
||||
QVariantHash *dec_opt_old = dec_opt;
|
||||
if (nb_seek == 0 || diff >= 0)
|
||||
dec_opt = &dec_opt_normal;
|
||||
else
|
||||
dec_opt = &dec_opt_framedrop;
|
||||
if (dec_opt != dec_opt_old)
|
||||
decoder->setOptions(*dec_opt);
|
||||
// invalid packet?
|
||||
if (!decoder->decode(pkt)) {
|
||||
qWarning("!!!!!!!!!decode failed!!!!");
|
||||
frame = VideoFrame();
|
||||
return -1;
|
||||
}
|
||||
// store the last decoded frame because next frame may be out of range
|
||||
const VideoFrame f = decoder->frame();
|
||||
if (!f.isValid()) {
|
||||
//qDebug("VideoFrameExtractor: invalid frame!!!");
|
||||
continue;
|
||||
}
|
||||
frame = f;
|
||||
const qreal pts = frame.pts();// timestamp();
|
||||
const qint64 pts_ms = pts*1000.0;
|
||||
if (pts_ms < value)
|
||||
continue; //
|
||||
diff = pts_ms - value;
|
||||
if (qAbs(diff) <= (qint64)range) {
|
||||
qDebug("got frame at %fs, diff=%lld", pts, diff);
|
||||
break;
|
||||
}
|
||||
// if decoder was not flushed, we may get old frame which is acceptable
|
||||
if (diff > range && t > pts) {
|
||||
qWarning("out pts out of range. diff=%lld, range=%d", diff, range);
|
||||
frame = VideoFrame();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
++nb_seek;
|
||||
return qint64(frame.pts()*1000.0); // timestamp()
|
||||
}
|
||||
|
||||
FrameReader::FrameReader(QObject *parent)
|
||||
: QObject(parent)
|
||||
, d(new Private())
|
||||
{
|
||||
qDebug() << "--------------------" << Q_FUNC_INFO;
|
||||
moveToThread(&d->read_thread);
|
||||
connect(this, SIGNAL(readMoreRequested()), SLOT(readMoreInternal()));
|
||||
connect(this, SIGNAL(readEnd()), &d->read_thread, SLOT(quit()));
|
||||
connect(this, SIGNAL(seekRequested(qint64)), SLOT(seekInternal(qint64)));
|
||||
}
|
||||
|
||||
FrameReader::~FrameReader()
|
||||
{
|
||||
}
|
||||
|
||||
void FrameReader::setMedia(const QString &url)
|
||||
{
|
||||
if (url == d->url)
|
||||
return;
|
||||
d->url = url;
|
||||
}
|
||||
|
||||
QString FrameReader::mediaUrl() const
|
||||
{
|
||||
return d->url;
|
||||
}
|
||||
|
||||
void FrameReader::setVideoDecoders(const QStringList &names)
|
||||
{
|
||||
if (names == d->vdecs)
|
||||
return;
|
||||
d->vdecs = names;
|
||||
}
|
||||
|
||||
QStringList FrameReader::videoDecoders() const
|
||||
{
|
||||
return d->vdecs;
|
||||
}
|
||||
|
||||
VideoFrame FrameReader::getVideoFrame()
|
||||
{
|
||||
return d->vframes.take();
|
||||
}
|
||||
|
||||
bool FrameReader::hasVideoFrame() const
|
||||
{
|
||||
return !d->vframes.isEmpty();
|
||||
}
|
||||
|
||||
bool FrameReader::hasEnoughVideoFrames() const
|
||||
{
|
||||
return d->vframes.isEnough();
|
||||
}
|
||||
|
||||
bool FrameReader::readMore()
|
||||
{
|
||||
if (d->demuxer.isLoaded() && d->demuxer.atEnd()) {
|
||||
if (!d->read_thread.isRunning())
|
||||
return false;
|
||||
qDebug("wait for read thread quit");
|
||||
d->read_thread.quit();
|
||||
d->read_thread.wait(); // sync
|
||||
return false;
|
||||
}
|
||||
if (!d->read_thread.isRunning())
|
||||
d->read_thread.start();
|
||||
Q_EMIT readMoreRequested();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FrameReader::seek(qint64 pos)
|
||||
{
|
||||
if (!d->read_thread.isRunning())
|
||||
d->read_thread.start();
|
||||
Q_EMIT seekRequested(pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FrameReader::readMoreInternal()
|
||||
{
|
||||
if (!d->tryLoad()) {
|
||||
qDebug("load error");
|
||||
return;
|
||||
}
|
||||
//TODO: decode eof packets
|
||||
if (d->demuxer.atEnd())
|
||||
return;
|
||||
const int vstream = d->demuxer.videoStream();
|
||||
Packet pkt;
|
||||
while (!d->demuxer.atEnd()) {
|
||||
if (!d->demuxer.readFrame()) {
|
||||
// qDebug("demuxer read error");
|
||||
continue;
|
||||
}
|
||||
if (d->demuxer.stream() != vstream) {
|
||||
// qDebug("not video stream");
|
||||
continue;
|
||||
}
|
||||
pkt = d->demuxer.packet();
|
||||
if (d->decoder->decode(pkt)) {
|
||||
const VideoFrame frame(d->decoder->frame());
|
||||
if (!frame) {
|
||||
qDebug("no frame got, continue to decoder");
|
||||
continue;
|
||||
}
|
||||
d->vframes.put(frame);
|
||||
Q_EMIT frameRead(frame);
|
||||
//qDebug("frame got @%.3f, queue enough: %d", frame.timestamp(), vframes.isEnough());
|
||||
if (d->vframes.isEnough())
|
||||
break;
|
||||
} else {
|
||||
qDebug("dec error, continue to decoder");
|
||||
}
|
||||
}
|
||||
if (d->demuxer.atEnd()) {
|
||||
d->vframes.setThreshold(1);
|
||||
d->vframes.blockFull(false);
|
||||
while (d->decoder->decode(Packet::createEOF())) {
|
||||
qDebug("decoded buffered packets");
|
||||
const VideoFrame frame(d->decoder->frame());
|
||||
d->vframes.put(frame);
|
||||
Q_EMIT frameRead(frame);
|
||||
qDebug("put decoded buffered packets @%.3f", frame.pts()); // timestamp()
|
||||
}
|
||||
d->vframes.put(VideoFrame()); //make sure take() will not be blocked
|
||||
d->vframes.blockFull(true);
|
||||
qDebug("eof");
|
||||
Q_EMIT readEnd();
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameReader::seekInternal(qint64 value)
|
||||
{
|
||||
qint64 t = !d->seekInternal(value);
|
||||
if (t < 0)
|
||||
return false;
|
||||
// now we get the final frame
|
||||
Q_EMIT seekFinished(t);
|
||||
return true;
|
||||
}
|
||||
} //namespace FAV
|
||||
84
project/fm_viewer/fav/FrameReader.h
Normal file
84
project/fm_viewer/fav/FrameReader.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2016)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
#ifndef QTAV_FRAMEREADER_H
|
||||
#define QTAV_FRAMEREADER_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include "VideoFrame.h"
|
||||
|
||||
namespace FAV {
|
||||
/*!
|
||||
* \brief The FrameReader class
|
||||
* while (reader->readMore()) {
|
||||
* while (reader->hasVideoFrame()) { //or hasEnoughVideoFrames()
|
||||
* reader->getVideoFrame();
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
* or (faster)
|
||||
* while (reader->readMore()) {
|
||||
* reader->getVideoFrame(); //we can ensure 1 frame is available, but may block here
|
||||
* }
|
||||
* while (r.hasVideoFrame()) { //get buffered frames
|
||||
* reader->getVideoFrame();
|
||||
* }
|
||||
* TODO: multiple tracks
|
||||
*/
|
||||
class Q_AV_EXPORT FrameReader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
// TODO: load and get info
|
||||
// TODO: asnyc option
|
||||
explicit FrameReader(QObject *parent = 0);
|
||||
~FrameReader();
|
||||
void setMedia(const QString& url);
|
||||
QString mediaUrl() const;
|
||||
void setVideoDecoders(const QStringList& names);
|
||||
QStringList videoDecoders() const;
|
||||
VideoFrame getVideoFrame();
|
||||
bool hasVideoFrame() const;
|
||||
bool hasEnoughVideoFrames() const;
|
||||
// return false if eof
|
||||
bool readMore();
|
||||
// TODO: tryLoad on seek even at eof
|
||||
// TODO: compress seek requests
|
||||
bool seek(qint64 pos);
|
||||
|
||||
Q_SIGNALS:
|
||||
void frameRead(const FAV::VideoFrame& frame);
|
||||
void readEnd();
|
||||
void seekFinished(qint64 pos);
|
||||
|
||||
// internal
|
||||
void readMoreRequested();
|
||||
void seekRequested(qint64);
|
||||
|
||||
private Q_SLOTS:
|
||||
void readMoreInternal();
|
||||
bool seekInternal(qint64 value);
|
||||
|
||||
private:
|
||||
class Private;
|
||||
QScopedPointer<Private> d;
|
||||
};
|
||||
} //namespace FAV
|
||||
#endif // QTAV_FRAMEREADER_H
|
||||
53
project/fm_viewer/fav/Frame_p.h
Normal file
53
project/fm_viewer/fav/Frame_p.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_FRAME_P_H
|
||||
#define QTAV_FRAME_P_H
|
||||
|
||||
#include "_fav_constants.h"
|
||||
#include <QtCore/QVector>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QSharedData>
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class Frame;
|
||||
class FramePrivate : public QSharedData
|
||||
{
|
||||
Q_DISABLE_COPY(FramePrivate)
|
||||
public:
|
||||
FramePrivate()
|
||||
: pts(0) // 2024/02/07 timestamp 에서 PTS 로 변경
|
||||
, duration(0) // SEEK 처리시 timestamp + duration 처리하지 않으면 끝까지 SEEK 불가
|
||||
{}
|
||||
virtual ~FramePrivate() {}
|
||||
|
||||
QVector<uchar*> planes; //slice
|
||||
QVector<int> line_sizes; //stride
|
||||
QVariantMap metadata;
|
||||
QByteArray data;
|
||||
qreal pts;
|
||||
qreal duration;
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
#endif // QTAV_Frame_P_H
|
||||
144
project/fm_viewer/fav/GLSLFilter.cpp
Normal file
144
project/fm_viewer/fav/GLSLFilter.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2016)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "GLSLFilter.h"
|
||||
#include "Filter_p.h"
|
||||
#include "VideoFrame.h"
|
||||
#include "opengl/OpenGLHelper.h"
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
|
||||
#include <QtGui/QOpenGLFramebufferObject>
|
||||
#else
|
||||
#include <QtOpenGL/QGLFramebufferObject>
|
||||
#endif
|
||||
#include "SurfaceInterop.h"
|
||||
#include "OpenGLVideo.h"
|
||||
|
||||
namespace FAV {
|
||||
class GLSLFilterPrivate : public VideoFilterPrivate
|
||||
{
|
||||
public:
|
||||
GLSLFilterPrivate() : VideoFilterPrivate()
|
||||
, fbo(0)
|
||||
{}
|
||||
|
||||
QOpenGLFramebufferObject *fbo;
|
||||
QSize size;
|
||||
OpenGLVideo glv;
|
||||
};
|
||||
|
||||
GLSLFilter::GLSLFilter(QObject *parent)
|
||||
: VideoFilter(*new GLSLFilterPrivate(), parent)
|
||||
{}
|
||||
|
||||
GLSLFilter::GLSLFilter(GLSLFilterPrivate &d, QObject *parent)
|
||||
: VideoFilter(d, parent)
|
||||
{}
|
||||
|
||||
OpenGLVideo* GLSLFilter::opengl() const
|
||||
{
|
||||
return const_cast<OpenGLVideo*>(&d_func().glv);
|
||||
}
|
||||
|
||||
QOpenGLFramebufferObject* GLSLFilter::fbo() const
|
||||
{
|
||||
return d_func().fbo;
|
||||
}
|
||||
|
||||
QSize GLSLFilter::outputSize() const
|
||||
{
|
||||
return d_func().size;
|
||||
}
|
||||
|
||||
void GLSLFilter::setOutputSize(const QSize &value)
|
||||
{
|
||||
DPTR_D(GLSLFilter);
|
||||
if (d.size == value)
|
||||
return;
|
||||
d.size = value;
|
||||
Q_EMIT outputSizeChanged(value);
|
||||
}
|
||||
|
||||
void GLSLFilter::setOutputSize(int width, int height)
|
||||
{
|
||||
setOutputSize(QSize(width, height));
|
||||
}
|
||||
|
||||
void GLSLFilter::process(Statistics *statistics, VideoFrame *frame)
|
||||
{
|
||||
Q_UNUSED(statistics);
|
||||
if (!QOpenGLContext::currentContext()) {
|
||||
qWarning() << "No current gl context for glsl filter: " << this;
|
||||
return;
|
||||
}
|
||||
DPTR_D(GLSLFilter);
|
||||
if (!frame || !*frame)
|
||||
return;
|
||||
GLint currentFbo = 0;
|
||||
DYGL(glGetIntegerv(GL_FRAMEBUFFER_BINDING, ¤tFbo));
|
||||
// now use the frame size
|
||||
if (d.fbo) {
|
||||
if (d.size.isEmpty()) {
|
||||
if (d.fbo->size() != frame->size()) {
|
||||
delete d.fbo;
|
||||
d.fbo = 0;
|
||||
}
|
||||
} else {
|
||||
if (d.fbo->size() != d.size) {
|
||||
delete d.fbo;
|
||||
d.fbo = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!d.fbo) {
|
||||
d.fbo = new QOpenGLFramebufferObject(outputSize().isEmpty() ? frame->size() : outputSize(), GL_TEXTURE_2D); //TODO: prefer 16bit rgb
|
||||
QOpenGLContext *ctx = const_cast<QOpenGLContext*>(QOpenGLContext::currentContext()); //qt4 returns const
|
||||
d.glv.setOpenGLContext(ctx);
|
||||
d.glv.setProjectionMatrixToRect(QRectF(0, 0, d.fbo->width(), d.fbo->height()));
|
||||
qDebug("new fbo texture: %d %dx%d", d.fbo->texture(), d.fbo->width(), d.fbo->height());
|
||||
}
|
||||
d.fbo->bind();
|
||||
DYGL(glViewport(0, 0, d.fbo->width(), d.fbo->height()));
|
||||
d.glv.setCurrentFrame(*frame);
|
||||
QMatrix4x4 mat; // flip vertical
|
||||
mat.scale(1, -1);
|
||||
d.glv.render(QRectF(), QRectF(), mat);
|
||||
gl().BindFramebuffer(GL_FRAMEBUFFER, (GLuint)currentFbo);
|
||||
VideoFormat fmt(VideoFormat::Format_RGB32);
|
||||
VideoFrame f(d.fbo->width(), d.fbo->height(), fmt); //
|
||||
f.setBytesPerLine(d.fbo->width()*fmt.bytesPerPixel(), 0);
|
||||
// set interop;
|
||||
class GLTextureInterop : public VideoSurfaceInterop
|
||||
{
|
||||
GLuint tex;
|
||||
public:
|
||||
GLTextureInterop(GLuint id) : tex(id) {}
|
||||
void* map(SurfaceType, const VideoFormat &, void *handle, int plane) {
|
||||
Q_UNUSED(plane);
|
||||
GLuint* t = reinterpret_cast<GLuint*>(handle);
|
||||
*t = tex;
|
||||
return t;
|
||||
}
|
||||
};
|
||||
GLTextureInterop *interop = new GLTextureInterop(d.fbo->texture());
|
||||
f.setMetaData(QStringLiteral("surface_interop"), QVariant::fromValue(VideoSurfaceInteropPtr((interop))));
|
||||
*frame = f;
|
||||
}
|
||||
} //namespace FAV
|
||||
78
project/fm_viewer/fav/GLSLFilter.h
Normal file
78
project/fm_viewer/fav/GLSLFilter.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2016)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_GLSLFILTER_H
|
||||
#define QTAV_GLSLFILTER_H
|
||||
|
||||
#include "_fav_constants.h"
|
||||
#include "Filter.h"
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
#undef QOpenGLFramebufferObject
|
||||
#define QOpenGLFramebufferObject QGLFramebufferObject
|
||||
#endif
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QOpenGLFramebufferObject;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace FAV {
|
||||
class OpenGLVideo;
|
||||
class GLSLFilterPrivate;
|
||||
class Q_AV_EXPORT GLSLFilter : public VideoFilter
|
||||
{
|
||||
Q_OBJECT
|
||||
DPTR_DECLARE_PRIVATE(GLSLFilter)
|
||||
Q_PROPERTY(QSize outputSize READ outputSize WRITE setOutputSize NOTIFY outputSizeChanged)
|
||||
public:
|
||||
GLSLFilter(QObject* parent = 0);
|
||||
bool isSupported(VideoFilterContext::Type ct) const Q_DECL_OVERRIDE {
|
||||
return ct == VideoFilterContext::OpenGL;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief opengl
|
||||
* Currently you can only use it to set custom shader OpenGLVideo.setUserShader()
|
||||
*/
|
||||
OpenGLVideo* opengl() const;
|
||||
QOpenGLFramebufferObject* fbo() const;
|
||||
/*!
|
||||
* \brief outputSize
|
||||
* Output frame size. FBO uses the same size to render. An empty size means using the input frame size
|
||||
* \return
|
||||
*/
|
||||
QSize outputSize() const;
|
||||
void setOutputSize(const QSize& value);
|
||||
void setOutputSize(int width, int height);
|
||||
Q_SIGNALS:
|
||||
void outputSizeChanged(const QSize& size);
|
||||
protected:
|
||||
GLSLFilter(GLSLFilterPrivate& d, QObject *parent = 0);
|
||||
/*!
|
||||
* \brief process
|
||||
* Draw video frame into fbo and apply the user shader from opengl()->userShader();
|
||||
* \param frame input frame can be a frame holding host memory data, or any other GPU frame can interop with OpenGL texture (including frames from HW decoders in QtAV).
|
||||
* Output frame holds an RGB texture, which can be processed in the next GPU filter, or rendered by OpenGL renderers.
|
||||
* When process() is done, FBO before before process() is bounded.
|
||||
*/
|
||||
void process(Statistics* statistics, VideoFrame* frame = 0) Q_DECL_OVERRIDE;
|
||||
};
|
||||
} //namespace FAV
|
||||
#endif // QTAV_GLSLFILTER_H
|
||||
146
project/fm_viewer/fav/GPUMemCopy.cpp
Normal file
146
project/fm_viewer/fav/GPUMemCopy.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "GPUMemCopy.h"
|
||||
|
||||
#if !(REMOVE_GPU_MEMCOPY)
|
||||
|
||||
#include "_fav_constants.h"
|
||||
#include <string.h> //memcpy
|
||||
#include <algorithm>
|
||||
extern "C" {
|
||||
#include <libavutil/cpu.h>
|
||||
}
|
||||
|
||||
#ifndef Q_PROCESSOR_X86 // qt4
|
||||
#if defined(__SSE__) || defined(_M_IX86) || defined(_M_X64)
|
||||
#define Q_PROCESSOR_X86
|
||||
#endif
|
||||
#endif
|
||||
// read qsimd_p.h
|
||||
#define UINT unsigned int
|
||||
void CopyFrame_SSE2(void *pSrc, void *pDest, void *pCacheBlock, UINT width, UINT height, UINT pitch);
|
||||
void CopyFrame_SSE4(void *pSrc, void *pDest, void *pCacheBlock, UINT width, UINT height, UINT pitch);
|
||||
|
||||
void *memcpy_sse2(void* dst, const void* src, size_t size);
|
||||
void *memcpy_sse4(void* dst, const void* src, size_t size);
|
||||
|
||||
namespace FAV {
|
||||
|
||||
bool detect_sse4() {
|
||||
static bool is_sse4 = !!(av_get_cpu_flags() & AV_CPU_FLAG_SSE4);
|
||||
return is_sse4;
|
||||
}
|
||||
bool detect_sse2() {
|
||||
static bool is_sse2 = !!(av_get_cpu_flags() & AV_CPU_FLAG_SSE2);
|
||||
return is_sse2;
|
||||
}
|
||||
|
||||
bool GPUMemCopy::isAvailable()
|
||||
{
|
||||
#if QTAV_HAVE(SSE4_1) && defined(Q_PROCESSOR_X86)
|
||||
if (detect_sse4())
|
||||
return true;
|
||||
#endif
|
||||
#if QTAV_HAVE(SSE2) && defined(Q_PROCESSOR_X86)
|
||||
if (detect_sse2())
|
||||
return true;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
GPUMemCopy::GPUMemCopy()
|
||||
: mInitialized(false)
|
||||
{
|
||||
#if QTAV_HAVE(SSE2) && defined(Q_PROCESSOR_X86)
|
||||
mCache.buffer = 0;
|
||||
mCache.size = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
GPUMemCopy::~GPUMemCopy()
|
||||
{
|
||||
cleanCache();
|
||||
}
|
||||
|
||||
bool GPUMemCopy::isReady() const
|
||||
{
|
||||
return mInitialized && GPUMemCopy::isAvailable();
|
||||
}
|
||||
|
||||
#define CACHED_BUFFER_SIZE 4096
|
||||
bool GPUMemCopy::initCache(unsigned width)
|
||||
{
|
||||
mInitialized = false;
|
||||
#if QTAV_HAVE(SSE2) && defined(Q_PROCESSOR_X86)
|
||||
mCache.size = std::max<size_t>((width + 0x0f) & ~ 0x0f, CACHED_BUFFER_SIZE);
|
||||
mCache.buffer = (unsigned char*)qMallocAligned(mCache.size, 16);
|
||||
mInitialized = !!mCache.buffer;
|
||||
return mInitialized;
|
||||
#else
|
||||
Q_UNUSED(width);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void GPUMemCopy::cleanCache()
|
||||
{
|
||||
mInitialized = false;
|
||||
#if QTAV_HAVE(SSE2) && defined(Q_PROCESSOR_X86)
|
||||
if (mCache.buffer) {
|
||||
qFreeAligned(mCache.buffer);
|
||||
}
|
||||
mCache.buffer = 0;
|
||||
mCache.size = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void GPUMemCopy::copyFrame(void *pSrc, void *pDest, unsigned width, unsigned height, unsigned pitch)
|
||||
{
|
||||
#if QTAV_HAVE(SSE4_1) && defined(Q_PROCESSOR_X86)
|
||||
if (detect_sse4())
|
||||
CopyFrame_SSE4(pSrc, pDest, mCache.buffer, width, height, pitch);
|
||||
#elif QTAV_HAVE(SSE2) && defined(Q_PROCESSOR_X86)
|
||||
if (detect_sse2())
|
||||
CopyFrame_SSE2(pSrc, pDest, mCache.buffer, width, height, pitch);
|
||||
#else
|
||||
Q_UNUSED(pSrc);
|
||||
Q_UNUSED(pDest);
|
||||
Q_UNUSED(width);
|
||||
Q_UNUSED(height);
|
||||
Q_UNUSED(pitch);
|
||||
#endif
|
||||
}
|
||||
|
||||
void* gpu_memcpy(void *dst, const void *src, size_t size)
|
||||
{
|
||||
#if QTAV_HAVE(SSE4_1) && defined(Q_PROCESSOR_X86)
|
||||
if (detect_sse4())
|
||||
return memcpy_sse4(dst, src, size);
|
||||
#elif QTAV_HAVE(SSE2) && defined(Q_PROCESSOR_X86)
|
||||
if (detect_sse2())
|
||||
return memcpy_sse2(dst, src, size);
|
||||
#endif
|
||||
return memcpy(dst, src, size);
|
||||
}
|
||||
} //namespace FAV
|
||||
#endif // #if !(REMOVE_GPU_MEMCOPY)
|
||||
64
project/fm_viewer/fav/GPUMemCopy.h
Normal file
64
project/fm_viewer/fav/GPUMemCopy.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef GPUMemCopy_H
|
||||
#define GPUMemCopy_H
|
||||
|
||||
#ifdef RM_USE_HW_DECODER
|
||||
#define REMOVE_GPU_MEMCOPY 0
|
||||
#else
|
||||
#define REMOVE_GPU_MEMCOPY 1
|
||||
#endif
|
||||
|
||||
|
||||
#if !(REMOVE_GPU_MEMCOPY)
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class GPUMemCopy
|
||||
{
|
||||
public:
|
||||
static bool isAvailable();
|
||||
GPUMemCopy();
|
||||
~GPUMemCopy();
|
||||
// available and initialized
|
||||
bool isReady() const;
|
||||
bool initCache(unsigned int width);
|
||||
void cleanCache();
|
||||
void copyFrame(void *pSrc, void *pDest, unsigned int width, unsigned int height, unsigned int pitch);
|
||||
//memcpy
|
||||
private:
|
||||
bool mInitialized;
|
||||
typedef struct {
|
||||
unsigned char* buffer;
|
||||
size_t size;
|
||||
} cache_t;
|
||||
cache_t mCache;
|
||||
};
|
||||
|
||||
void* gpu_memcpy(void* dst, const void* src, size_t size);
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
#endif // GPUMemCopy_H
|
||||
#endif // REMOVE_GPU_MEMCOPY
|
||||
179
project/fm_viewer/fav/Geometry.h
Normal file
179
project/fm_viewer/fav/Geometry.h
Normal file
@@ -0,0 +1,179 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2016)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
#ifndef QTAV_GEOMETRY_H
|
||||
#define QTAV_GEOMETRY_H
|
||||
#include <QtCore/QRectF>
|
||||
#include <QtCore/QVector>
|
||||
#include "_fav_constants.h"
|
||||
|
||||
// TODO: generate vertex/fragment shader code from Geometry.attributes()
|
||||
namespace FAV {
|
||||
|
||||
// 구체의 스케일(*)
|
||||
#define SPHERE_SCALE 20.0f
|
||||
#define DEG_PI 3.14159265359f
|
||||
#define DEG2RAD(d) (d * DEG_PI / 180.0)
|
||||
|
||||
enum DataType { //equals to GL_BYTE etc.
|
||||
TypeS8 = 0x1400, //S8
|
||||
TypeU8 = 0x1401, //U8
|
||||
TypeS16 = 0x1402, //S16
|
||||
TypeU16 = 0x1403, //U16
|
||||
TypeS32 = 0x1404, //S32
|
||||
TypeU32 = 0x1405, //U32
|
||||
TypeF32 = 0x1406 //F32
|
||||
};
|
||||
|
||||
class Q_AV_EXPORT Attribute {
|
||||
bool m_normalize;
|
||||
DataType m_type;
|
||||
int m_tupleSize, m_offset;
|
||||
QByteArray m_name;
|
||||
public:
|
||||
Attribute(DataType type = TypeF32, int tupleSize = 0, int offset = 0, bool normalize = false);
|
||||
Attribute(const QByteArray& name, DataType type = TypeF32, int tupleSize = 0, int offset = 0, bool normalize = false);
|
||||
QByteArray name() const {return m_name;}
|
||||
DataType type() const {return m_type;}
|
||||
int tupleSize() const {return m_tupleSize;}
|
||||
int offset() const {return m_offset;}
|
||||
bool normalize() const {return m_normalize;}
|
||||
bool operator==(const Attribute& other) const {
|
||||
return tupleSize() == other.tupleSize()
|
||||
&& offset() == other.offset()
|
||||
&& type() == other.type()
|
||||
&& normalize() == other.normalize();
|
||||
}
|
||||
};
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
Q_AV_EXPORT QDebug operator<<(QDebug debug, const Attribute &a);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief The Geometry class
|
||||
* \code
|
||||
* foreach (const Attribute& a, g->attributes()) {
|
||||
* program()->setAttributeBuffer(a.name(), a.type(), a.offset(), a.tupleSize(), g->stride());
|
||||
* }
|
||||
* \endcode
|
||||
*/
|
||||
class Q_AV_EXPORT Geometry {
|
||||
public:
|
||||
/// Strip or Triangles is preferred by ANGLE. The values are equal to opengl
|
||||
enum Primitive {
|
||||
Triangles = 0x0004,
|
||||
TriangleStrip = 0x0005, //default
|
||||
TriangleFan = 0x0006, // Not recommended
|
||||
};
|
||||
Geometry(int vertexCount = 0, int indexCount = 0, DataType indexType = TypeU16);
|
||||
virtual ~Geometry() {}
|
||||
Primitive primitive() const {return m_primitive;}
|
||||
void setPrimitive(Primitive value) { m_primitive = value;}
|
||||
int vertexCount() const {return m_vcount;}
|
||||
void setVertexCount(int value) {m_vcount = value;} // TODO: remove, or allocate data here
|
||||
// TODO: setStride and no virtual
|
||||
virtual int stride() const = 0;
|
||||
// TODO: add/set/remove attributes()
|
||||
virtual const QVector<Attribute>& attributes() const = 0;
|
||||
void* vertexData() { return m_vdata.data();}
|
||||
const void* vertexData() const { return m_vdata.constData();}
|
||||
const void* constVertexData() const { return m_vdata.constData();}
|
||||
void dumpVertexData();
|
||||
void* indexData() { return m_icount > 0 ? m_idata.data() : NULL;}
|
||||
const void* indexData() const { return m_icount > 0 ? m_idata.constData() : NULL;}
|
||||
const void* constIndexData() const { return m_icount > 0 ? m_idata.constData() : NULL;}
|
||||
int indexCount() const { return m_icount;}
|
||||
int indexDataSize() const;
|
||||
// GL_UNSIGNED_BYTE/SHORT/INT
|
||||
DataType indexType() const {return m_itype;}
|
||||
void setIndexType(DataType value) { m_itype = value;}
|
||||
void setIndexValue(int index, int value);
|
||||
void setIndexValue(int index, int v1, int v2, int v3); // a triangle
|
||||
void dumpIndexData();
|
||||
/*!
|
||||
* \brief allocate
|
||||
* Call allocate() when all parameters are set. vertexData() may change
|
||||
*/
|
||||
void allocate(int nbVertex, int nbIndex = 0);
|
||||
/*!
|
||||
* \brief compare
|
||||
* Compare each attribute and stride that used in glVertexAttribPointer
|
||||
* \return true if equal
|
||||
*/
|
||||
bool compare(const Geometry *other) const;
|
||||
protected:
|
||||
Primitive m_primitive;
|
||||
DataType m_itype;
|
||||
int m_vcount;
|
||||
int m_icount;
|
||||
QByteArray m_vdata;
|
||||
QByteArray m_idata;
|
||||
};
|
||||
class Fisheye;
|
||||
|
||||
class Q_AV_EXPORT TexturedGeometry : public Geometry {
|
||||
friend Fisheye;
|
||||
public:
|
||||
TexturedGeometry();
|
||||
/*!
|
||||
* \brief setTextureCount
|
||||
* sometimes we needs more than 1 texture coordinates, for example we have to set rectangle texture
|
||||
* coordinates for each plane.
|
||||
*/
|
||||
void setTextureCount(int value);
|
||||
int textureCount() const;
|
||||
|
||||
void setRect(const QRectF& r, const QRectF& tr, int texIndex = 0);
|
||||
void setGeometryRect(const QRectF& r);
|
||||
void setTextureRect(const QRectF& tr, int texIndex = 0);
|
||||
int stride() const Q_DECL_OVERRIDE { return 2*sizeof(float)*(textureCount()+1); }
|
||||
const QVector<Attribute>& attributes() const Q_DECL_OVERRIDE;
|
||||
virtual void create();
|
||||
protected:
|
||||
void setPoint(int index, const QPointF& p, const QPointF& tp, int texIndex = 0);
|
||||
void setGeometryPoint(int index, const QPointF& p);
|
||||
void setTexturePoint(int index, const QPointF& tp, int texIndex = 0);
|
||||
protected:
|
||||
int nb_tex;
|
||||
QRectF geo_rect;
|
||||
QVector<QRectF> texRect;
|
||||
QVector<Attribute> a;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class Q_AV_EXPORT Sphere : public TexturedGeometry {
|
||||
public:
|
||||
Sphere();
|
||||
void setResolution(int w, int h); // >= 2x2
|
||||
void setRadius(float value);
|
||||
float radius() const;
|
||||
void create() Q_DECL_OVERRIDE;
|
||||
int stride() const Q_DECL_OVERRIDE { return 3*sizeof(float)+2*sizeof(float)*textureCount(); }
|
||||
protected:
|
||||
using Geometry::setPrimitive;
|
||||
private:
|
||||
int ru, rv;
|
||||
float r;
|
||||
};
|
||||
|
||||
|
||||
} //namespace FAV
|
||||
#endif //QTAV_GEOMETRY_H
|
||||
96
project/fm_viewer/fav/GeometryRenderer.h
Normal file
96
project/fm_viewer/fav/GeometryRenderer.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2017 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2016)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
#ifndef QTAV_GEOMETRYRENDERER_H
|
||||
#define QTAV_GEOMETRYRENDERER_H
|
||||
|
||||
#define REMOVE_GEOMETRY_RENDERER 0
|
||||
#if !(REMOVE_GEOMETRY_RENDERER)
|
||||
|
||||
#include "Geometry.h"
|
||||
#define QT_VAO (QT_VERSION >= QT_VERSION_CHECK(5, 1, 0))
|
||||
//#define QT_VAO 0
|
||||
#if QT_VAO
|
||||
#include <QtGui/QOpenGLVertexArrayObject>
|
||||
#endif //QT_VAO
|
||||
# if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
|
||||
#include <QtGui/QOpenGLBuffer>
|
||||
#else
|
||||
#include <QtOpenGL/QGLBuffer>
|
||||
typedef QGLBuffer QOpenGLBuffer;
|
||||
#endif
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QOpenGLShaderProgram;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#define DEBUG_QT_VAO 0
|
||||
|
||||
namespace FAV {
|
||||
/*!
|
||||
* \brief The GeometryRenderer class
|
||||
* TODO: support multiple geometry with same primitive (glPrimitiveRestartIndex, glDrawArraysInstanced, glDrawArraysInstanced, glMultiDrawArrays...)
|
||||
*/
|
||||
class Q_AV_EXPORT GeometryRenderer
|
||||
{
|
||||
public:
|
||||
// rendering features. Use all possible features as the default.
|
||||
static const int kVBO = 0x01;
|
||||
static const int kIBO = 0x02;
|
||||
static const int kVAO = 0x04;
|
||||
static const int kMapBuffer = 1<<16;
|
||||
// TODO: VAB, VBUM etc.
|
||||
GeometryRenderer();
|
||||
virtual ~GeometryRenderer() {}
|
||||
// call updateBuffer internally in bindBuffer if feature is changed
|
||||
void setFeature(int f, bool on);
|
||||
void setFeatures(int value);
|
||||
int features() const;
|
||||
int actualFeatures() const;
|
||||
bool testFeatures(int value) const;
|
||||
/*!
|
||||
* \brief updateGeometry
|
||||
* Update geometry buffer. Rebind VBO, IBO to VAO if geometry attributes is changed.
|
||||
* Assume attributes are bound in the order 0, 1, 2,....
|
||||
* Make sure the old geometry is not destroyed before this call because it will be used to compare with the new \l geo
|
||||
* \param geo null: release vao/vbo
|
||||
*/
|
||||
void updateGeometry(Geometry* geo = NULL);
|
||||
virtual void render();
|
||||
protected:
|
||||
void bindBuffers();
|
||||
void unbindBuffers();
|
||||
private:
|
||||
Geometry *g;
|
||||
int features_;
|
||||
int vbo_size, ibo_size; // QOpenGLBuffer.size() may get error 0x501 (vertex buffer)
|
||||
QOpenGLBuffer vbo; //VertexBuffer
|
||||
#if QT_VAO
|
||||
QOpenGLVertexArrayObject vao;
|
||||
#endif //QT_VAO
|
||||
QOpenGLBuffer ibo; // index buffer
|
||||
|
||||
// geometry characteristic
|
||||
int stride;
|
||||
QVector<Attribute> attrib;
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif //QTAV_GEOMETRYRENDERER_H
|
||||
#endif // #if !(REMOVE_GEOMETRY_RENDERER)
|
||||
239
project/fm_viewer/fav/ImageConverter.cpp
Normal file
239
project/fm_viewer/fav/ImageConverter.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
/******************************************************************************
|
||||
ImageConverter: Base class for image resizing & color model convertion
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
#include "ImageConverter_p.h"
|
||||
#include "AVCompat.h"
|
||||
#include "factory.h"
|
||||
#include "ImageConverter.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
FACTORY_DEFINE(ImageConverter)
|
||||
|
||||
ImageConverter::ImageConverter()
|
||||
{
|
||||
}
|
||||
|
||||
ImageConverter::ImageConverter(ImageConverterPrivate& d)
|
||||
: DPTR_INIT(&d)
|
||||
{
|
||||
}
|
||||
|
||||
ImageConverter::~ImageConverter()
|
||||
{
|
||||
}
|
||||
|
||||
QByteArray ImageConverter::outData() const
|
||||
{
|
||||
return d_func().data_out;
|
||||
}
|
||||
|
||||
bool ImageConverter::check() const
|
||||
{
|
||||
DPTR_D(const ImageConverter);
|
||||
return d.w_in > 0 && d.w_out > 0 && d.h_in > 0 && d.h_out > 0
|
||||
&& d.fmt_in != QTAV_PIX_FMT_C(NONE) && d.fmt_out != QTAV_PIX_FMT_C(NONE);
|
||||
}
|
||||
|
||||
void ImageConverter::setInSize(int width, int height)
|
||||
{
|
||||
DPTR_D(ImageConverter);
|
||||
if (d.w_in == width && d.h_in == height)
|
||||
return;
|
||||
d.w_in = width;
|
||||
d.h_in = height;
|
||||
}
|
||||
|
||||
// TODO: default is in size
|
||||
void ImageConverter::setOutSize(int width, int height)
|
||||
{
|
||||
DPTR_D(ImageConverter);
|
||||
if (d.w_out == width && d.h_out == height)
|
||||
return;
|
||||
d.w_out = width;
|
||||
d.h_out = height;
|
||||
d.update_data = true;
|
||||
prepareData();
|
||||
d.update_data = false;
|
||||
}
|
||||
|
||||
void ImageConverter::setInFormat(const VideoFormat& format)
|
||||
{
|
||||
d_func().fmt_in = (AVPixelFormat)format.pixelFormatFFmpeg();
|
||||
}
|
||||
|
||||
void ImageConverter::setInFormat(VideoFormat::PixelFormat format)
|
||||
{
|
||||
d_func().fmt_in = (AVPixelFormat)VideoFormat::pixelFormatToFFmpeg(format);
|
||||
}
|
||||
|
||||
void ImageConverter::setInFormat(int format)
|
||||
{
|
||||
d_func().fmt_in = (AVPixelFormat)format;
|
||||
}
|
||||
|
||||
void ImageConverter::setOutFormat(const VideoFormat& format)
|
||||
{
|
||||
setOutFormat(format.pixelFormatFFmpeg());
|
||||
}
|
||||
|
||||
void ImageConverter::setOutFormat(VideoFormat::PixelFormat format)
|
||||
{
|
||||
setOutFormat(VideoFormat::pixelFormatToFFmpeg(format));
|
||||
}
|
||||
|
||||
void ImageConverter::setOutFormat(int format)
|
||||
{
|
||||
DPTR_D(ImageConverter);
|
||||
if (d.fmt_out == format)
|
||||
return;
|
||||
d.fmt_out = (AVPixelFormat)format;
|
||||
d.update_data = true;
|
||||
prepareData();
|
||||
d.update_data = false;
|
||||
}
|
||||
|
||||
void ImageConverter::setInRange(ColorRange range)
|
||||
{
|
||||
DPTR_D(ImageConverter);
|
||||
if (d.range_in == range)
|
||||
return;
|
||||
d.range_in = range;
|
||||
// TODO: do not call setupColorspaceDetails(). use a wrapper convert() func and call there
|
||||
d.setupColorspaceDetails();
|
||||
}
|
||||
|
||||
ColorRange ImageConverter::inRange() const
|
||||
{
|
||||
return d_func().range_in;
|
||||
}
|
||||
|
||||
void ImageConverter::setOutRange(ColorRange range)
|
||||
{
|
||||
DPTR_D(ImageConverter);
|
||||
if (d.range_out == range)
|
||||
return;
|
||||
d.range_out = range;
|
||||
d.setupColorspaceDetails();
|
||||
}
|
||||
|
||||
ColorRange ImageConverter::outRange() const
|
||||
{
|
||||
return d_func().range_out;
|
||||
}
|
||||
|
||||
void ImageConverter::setBrightness(int value)
|
||||
{
|
||||
DPTR_D(ImageConverter);
|
||||
if (d.brightness == value)
|
||||
return;
|
||||
d.brightness = value;
|
||||
d.setupColorspaceDetails();
|
||||
}
|
||||
|
||||
int ImageConverter::brightness() const
|
||||
{
|
||||
return d_func().brightness;
|
||||
}
|
||||
|
||||
void ImageConverter::setContrast(int value)
|
||||
{
|
||||
DPTR_D(ImageConverter);
|
||||
if (d.contrast == value)
|
||||
return;
|
||||
d.contrast = value;
|
||||
d.setupColorspaceDetails();
|
||||
}
|
||||
|
||||
int ImageConverter::contrast() const
|
||||
{
|
||||
return d_func().contrast;
|
||||
}
|
||||
|
||||
void ImageConverter::setSaturation(int value)
|
||||
{
|
||||
DPTR_D(ImageConverter);
|
||||
if (d.saturation == value)
|
||||
return;
|
||||
d.saturation = value;
|
||||
d.setupColorspaceDetails();
|
||||
}
|
||||
|
||||
int ImageConverter::saturation() const
|
||||
{
|
||||
return d_func().saturation;
|
||||
}
|
||||
|
||||
QVector<quint8*> ImageConverter::outPlanes() const
|
||||
{
|
||||
return d_func().bits;
|
||||
}
|
||||
|
||||
QVector<int> ImageConverter::outLineSizes() const
|
||||
{
|
||||
return d_func().pitchs;
|
||||
}
|
||||
|
||||
bool ImageConverter::convert(const quint8 * const src[], const int srcStride[])
|
||||
{
|
||||
DPTR_D(ImageConverter);
|
||||
if (d.update_data && !prepareData()) {
|
||||
qWarning("prepair output data error");
|
||||
return false;
|
||||
} else {
|
||||
d.update_data = false;
|
||||
}
|
||||
return convert(src, srcStride, (uint8_t**)d.bits.constData(), d.pitchs.constData());
|
||||
}
|
||||
|
||||
bool ImageConverter::prepareData()
|
||||
{
|
||||
DPTR_D(ImageConverter);
|
||||
if (d.fmt_out == QTAV_PIX_FMT_C(NONE) || d.w_out <=0 || d.h_out <= 0)
|
||||
return false;
|
||||
AV_ENSURE(av_image_check_size(d.w_out, d.h_out, 0, NULL), false);
|
||||
const int nb_planes = qMax(av_pix_fmt_count_planes(d.fmt_out), 0);
|
||||
d.bits.resize(nb_planes);
|
||||
d.pitchs.resize(nb_planes);
|
||||
// alignment is 16. sws in ffmpeg is 16, libav10 is 8
|
||||
const int kAlign = 16;
|
||||
AV_ENSURE(av_image_fill_linesizes((int*)d.pitchs.constData(), d.fmt_out, kAlign > 7 ? FFALIGN(d.w_out, 8) : d.w_out), false);
|
||||
for (int i = 0; i < d.pitchs.size(); ++i)
|
||||
d.pitchs[i] = FFALIGN(d.pitchs[i], kAlign);
|
||||
int s = av_image_fill_pointers((uint8_t**)d.bits.constData(), d.fmt_out, d.h_out, NULL, d.pitchs.constData());
|
||||
if (s < 0)
|
||||
return false;
|
||||
d.data_out.resize(s + kAlign-1);
|
||||
const int offset = (kAlign - ((uintptr_t)d.data_out.constData() & (kAlign-1))) & (kAlign-1);
|
||||
AV_ENSURE(av_image_fill_pointers((uint8_t**)d.bits.constData(), d.fmt_out, d.h_out, (uint8_t*)d.data_out.constData()+offset, d.pitchs.constData()), false);
|
||||
// TODO: special formats
|
||||
//if (desc->flags & AV_PIX_FMT_FLAG_PAL || desc->flags & AV_PIX_FMT_FLAG_PSEUDOPAL)
|
||||
// avpriv_set_systematic_pal2((uint32_t*)pointers[1], pix_fmt);
|
||||
for (int i = 0; i < d.pitchs.size(); ++i) {
|
||||
Q_ASSERT(d.pitchs[i]%kAlign == 0);
|
||||
Q_ASSERT(qptrdiff(d.bits[i])%kAlign == 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
118
project/fm_viewer/fav/ImageConverter.h
Normal file
118
project/fm_viewer/fav/ImageConverter.h
Normal file
@@ -0,0 +1,118 @@
|
||||
/******************************************************************************
|
||||
ImageConverter: Base class for image resizing & color model convertion
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
#ifndef QTAV_IMAGECONVERTER_H
|
||||
#define QTAV_IMAGECONVERTER_H
|
||||
|
||||
#include "_fav_constants.h"
|
||||
#include "VideoFormat.h"
|
||||
#include <QtCore/QVector>
|
||||
|
||||
namespace FAV {
|
||||
|
||||
typedef int ImageConverterId;
|
||||
class ImageConverterPrivate;
|
||||
class ImageConverter //export is not needed
|
||||
{
|
||||
DPTR_DECLARE_PRIVATE(ImageConverter)
|
||||
public:
|
||||
ImageConverter();
|
||||
virtual ~ImageConverter();
|
||||
|
||||
QByteArray outData() const;
|
||||
// return false if i/o format not supported, or size is not valid.
|
||||
// TODO: use isSupported(i/o format);
|
||||
virtual bool check() const;
|
||||
void setInSize(int width, int height);
|
||||
void setOutSize(int width, int height);
|
||||
void setInFormat(const VideoFormat& format);
|
||||
void setInFormat(VideoFormat::PixelFormat format);
|
||||
void setInFormat(int format);
|
||||
void setOutFormat(const VideoFormat& format);
|
||||
void setOutFormat(VideoFormat::PixelFormat format);
|
||||
void setOutFormat(int formate);
|
||||
// default is full range.
|
||||
void setInRange(ColorRange range);
|
||||
ColorRange inRange() const;
|
||||
// default is full range
|
||||
void setOutRange(ColorRange range);
|
||||
ColorRange outRange() const;
|
||||
/*!
|
||||
* brightness, contrast, saturation: -100~100
|
||||
* If value changes, setup sws
|
||||
*/
|
||||
void setBrightness(int value);
|
||||
int brightness() const;
|
||||
void setContrast(int value);
|
||||
int contrast() const;
|
||||
void setSaturation(int value);
|
||||
int saturation() const;
|
||||
QVector<quint8*> outPlanes() const;
|
||||
QVector<int> outLineSizes() const;
|
||||
virtual bool convert(const quint8 *const src[], const int srcStride[]);
|
||||
virtual bool convert(const quint8 *const src[], const int srcStride[], quint8 *const dst[], const int dstStride[]) = 0;
|
||||
public:
|
||||
template<class C> static bool Register(ImageConverterId id, const char* name) { return Register(id, create<C>, name);}
|
||||
static ImageConverter* create(ImageConverterId id);
|
||||
static ImageConverter* create(const char* name);
|
||||
/*!
|
||||
* \brief next
|
||||
* \param id NULL to get the first id address
|
||||
* \return address of id or NULL if not found/end
|
||||
*/
|
||||
static ImageConverterId* next(ImageConverterId* id = 0);
|
||||
static const char* name(ImageConverterId id);
|
||||
static ImageConverterId id(const char* name);
|
||||
private:
|
||||
template<class C> static ImageConverter* create() { return new C();}
|
||||
typedef ImageConverter* (*ImageConverterCreator)();
|
||||
static bool Register(ImageConverterId id, ImageConverterCreator, const char *name);
|
||||
protected:
|
||||
ImageConverter(ImageConverterPrivate& d);
|
||||
//Allocate memory for out data. Called in setOutFormat()
|
||||
virtual bool prepareData(); //Allocate memory for out data
|
||||
DPTR_DECLARE(ImageConverter)
|
||||
};
|
||||
|
||||
class ImageConverterFFPrivate;
|
||||
/*!
|
||||
* \brief The ImageConverterFF class
|
||||
* based on libswscale
|
||||
*/
|
||||
class ImageConverterFF Q_DECL_FINAL: public ImageConverter //Q_AV_EXPORT is not needed
|
||||
{
|
||||
DPTR_DECLARE_PRIVATE(ImageConverterFF)
|
||||
public:
|
||||
ImageConverterFF();
|
||||
bool check() const Q_DECL_OVERRIDE;
|
||||
// FIXME: why match to the pure virtual one if not declare here?
|
||||
bool convert(const quint8 *const src[], const int srcStride[]) Q_DECL_OVERRIDE { return ImageConverter::convert(src, srcStride);}
|
||||
bool convert(const quint8 *const src[], const int srcStride[], quint8 *const dst[], const int dstStride[]) Q_DECL_OVERRIDE;
|
||||
};
|
||||
typedef ImageConverterFF ImageConverterSWS;
|
||||
|
||||
//ImageConverter* c = ImageConverter::create(ImageConverterId_FF);
|
||||
extern ImageConverterId ImageConverterId_FF;
|
||||
extern ImageConverterId ImageConverterId_IPP;
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QTAV_IMAGECONVERTER_H
|
||||
133
project/fm_viewer/fav/ImageConverterFF.cpp
Normal file
133
project/fm_viewer/fav/ImageConverterFF.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
/******************************************************************************
|
||||
ImageConverterFF: Image resizing & color model convertion using FFmpeg swscale
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "ImageConverter.h"
|
||||
#include "ImageConverter_p.h"
|
||||
#include "AVCompat.h"
|
||||
#include "mkid.h"
|
||||
#include "factory.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
ImageConverterId ImageConverterId_FF = mkid::id32base36_6<'F', 'F', 'm', 'p', 'e', 'g'>::value;
|
||||
FACTORY_REGISTER(ImageConverter, FF, "FFmpeg")
|
||||
|
||||
class ImageConverterFFPrivate Q_DECL_FINAL: public ImageConverterPrivate
|
||||
{
|
||||
public:
|
||||
ImageConverterFFPrivate()
|
||||
: sws_ctx(0)
|
||||
, update_eq(true)
|
||||
{}
|
||||
~ImageConverterFFPrivate() {
|
||||
if (sws_ctx) {
|
||||
sws_freeContext(sws_ctx);
|
||||
sws_ctx = 0;
|
||||
}
|
||||
}
|
||||
virtual bool setupColorspaceDetails(bool force = true) Q_DECL_FINAL;
|
||||
|
||||
SwsContext *sws_ctx;
|
||||
bool update_eq;
|
||||
};
|
||||
|
||||
ImageConverterFF::ImageConverterFF()
|
||||
:ImageConverter(*new ImageConverterFFPrivate())
|
||||
{
|
||||
}
|
||||
|
||||
bool ImageConverterFF::check() const
|
||||
{
|
||||
if (!ImageConverter::check())
|
||||
return false;
|
||||
DPTR_D(const ImageConverterFF);
|
||||
if (sws_isSupportedInput((AVPixelFormat)d.fmt_in) <= 0) {
|
||||
qWarning("Input pixel format not supported (%s)", av_get_pix_fmt_name((AVPixelFormat)d.fmt_in));
|
||||
return false;
|
||||
}
|
||||
if (sws_isSupportedOutput((AVPixelFormat)d.fmt_out) <= 0) {
|
||||
qWarning("Output pixel format not supported (%s)", av_get_pix_fmt_name((AVPixelFormat)d.fmt_out));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImageConverterFF::convert(const quint8 *const src[], const int srcStride[], quint8 *const dst[], const int dstStride[])
|
||||
{
|
||||
DPTR_D(ImageConverterFF);
|
||||
//Check out dimension. equals to in dimension if not setted. TODO: move to another common func
|
||||
if (d.w_out == 0 || d.h_out == 0) {
|
||||
if (d.w_in == 0 || d.h_in == 0)
|
||||
return false;
|
||||
setOutSize(d.w_in, d.h_in);
|
||||
}
|
||||
//TODO: move those code to prepare()
|
||||
d.sws_ctx = sws_getCachedContext(d.sws_ctx
|
||||
, d.w_in, d.h_in, (AVPixelFormat)d.fmt_in
|
||||
, d.w_out, d.h_out, (AVPixelFormat)d.fmt_out
|
||||
, (d.w_in == d.w_out && d.h_in == d.h_out) ? SWS_POINT : SWS_FAST_BILINEAR //SWS_BICUBIC
|
||||
, NULL, NULL, NULL
|
||||
);
|
||||
//int64_t flags = SWS_CPU_CAPS_SSE2 | SWS_CPU_CAPS_MMX | SWS_CPU_CAPS_MMX2;
|
||||
//av_opt_set_int(d.sws_ctx, "sws_flags", flags, 0);
|
||||
if (!d.sws_ctx)
|
||||
return false;
|
||||
d.setupColorspaceDetails(false);
|
||||
int result_h = sws_scale(d.sws_ctx, src, srcStride, 0, d.h_in, dst, dstStride);
|
||||
if (result_h != d.h_out) {
|
||||
qDebug("convert failed: %d, %d", result_h, d.h_out);
|
||||
return false;
|
||||
}
|
||||
Q_UNUSED(result_h);
|
||||
for (int i = 0; i < d.pitchs.size(); ++i) {
|
||||
d.bits[i] = dst[i];
|
||||
d.pitchs[i] = dstStride[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImageConverterFFPrivate::setupColorspaceDetails(bool force)
|
||||
{
|
||||
if (!sws_ctx) {
|
||||
update_eq = true;
|
||||
return false;
|
||||
}
|
||||
if (force)
|
||||
update_eq = true;
|
||||
if (!update_eq) {
|
||||
return true;
|
||||
}
|
||||
const int srcRange = range_in == ColorRange_Limited ? 0 : 1;
|
||||
int dstRange = range_out == ColorRange_Limited ? 0 : 1;
|
||||
// TODO: color space
|
||||
bool supported = sws_setColorspaceDetails(sws_ctx, sws_getCoefficients(SWS_CS_DEFAULT)
|
||||
, srcRange, sws_getCoefficients(SWS_CS_DEFAULT)
|
||||
, dstRange
|
||||
, ((brightness << 16) + 50)/100
|
||||
, (((contrast + 100) << 16) + 50)/100
|
||||
, (((saturation + 100) << 16) + 50)/100
|
||||
) >= 0;
|
||||
//sws_init_context(d.sws_ctx, NULL, NULL);
|
||||
update_eq = false;
|
||||
return supported;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
109
project/fm_viewer/fav/ImageConverterIPP.cpp
Normal file
109
project/fm_viewer/fav/ImageConverterIPP.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
/******************************************************************************
|
||||
ImageConverterIPP: Image resizing & color model convertion using Intel IPP
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#define REMOVE_IMAGE_CONVERTER_IPP 1
|
||||
#if !(REMOVE_IMAGE_CONVERTER_IPP)
|
||||
|
||||
#include "ImageConverter.h"
|
||||
#include "ImageConverter_p.h"
|
||||
#include "AVCompat.h"
|
||||
#include "factory.h"
|
||||
#include "mkid.h"
|
||||
#if QTAV_HAVE(IPP)
|
||||
#include <ipp.h>
|
||||
#endif
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class ImageConverterIPPPrivate;
|
||||
class ImageConverterIPP : public ImageConverter //Q_AV_EXPORT is not needed
|
||||
{
|
||||
DPTR_DECLARE_PRIVATE(ImageConverterIPP)
|
||||
public:
|
||||
ImageConverterIPP();
|
||||
virtual bool convert(const quint8 *const srcSlice[], const int srcStride[]);
|
||||
protected:
|
||||
virtual bool prepareData(); //Allocate memory for out data
|
||||
};
|
||||
|
||||
ImageConverterId ImageConverterId_IPP = mkid::id32base36_3<'I', 'P', 'P'>::value;
|
||||
FACTORY_REGISTER(ImageConverter, IPP, "IPP")
|
||||
|
||||
class ImageConverterIPPPrivate : public ImageConverterPrivate
|
||||
{
|
||||
public:
|
||||
ImageConverterIPPPrivate():need_scale(true) {}
|
||||
bool need_scale;
|
||||
QByteArray orig_ori_rgb;
|
||||
};
|
||||
|
||||
ImageConverterIPP::ImageConverterIPP()
|
||||
:ImageConverter(*new ImageConverterIPPPrivate())
|
||||
{
|
||||
}
|
||||
|
||||
bool ImageConverterIPP::convert(const quint8 *const srcSlice[], const int srcStride[])
|
||||
{
|
||||
DPTR_D(ImageConverterIPP);
|
||||
//color convertion, no scale
|
||||
#if QTAV_HAVE(IPP)
|
||||
struct {
|
||||
const quint8 *data[3];
|
||||
int linesize[3];
|
||||
} yuv = {
|
||||
{ srcSlice[0], srcSlice[2], srcSlice[1] },
|
||||
{ srcStride[0], srcStride[2], srcStride[1] }
|
||||
};
|
||||
//ippiSwapChannels
|
||||
ippiYUV420ToRGB_8u_P3AC4R(const_cast<const quint8 **>(yuv.data), const_cast<int*>(yuv.linesize), (Ipp8u*)(d.orig_ori_rgb.data())
|
||||
, 4*sizeof(quint8)*d.w_in, (IppiSize){d.w_in, d.h_in});
|
||||
d.data_out = d.orig_ori_rgb;
|
||||
return true;
|
||||
if (d.need_scale) {
|
||||
qDebug("rs");
|
||||
ippiResize_8u_AC4R((const Ipp8u*)d.orig_ori_rgb.data(), (IppiSize){d.w_in, d.h_in}, 4*sizeof(quint8)*d.w_in, (IppiRect){0, 0, d.w_in, d.h_in}
|
||||
, (Ipp8u*)d.data_out.data(), 4*sizeof(quint8)*d.w_in, (IppiSize){d.w_out, d.h_out}
|
||||
, (double)d.w_out/(double)d.w_in, (double)d.h_out/(double)d.h_in, IPPI_INTER_CUBIC);
|
||||
} else {
|
||||
d.data_out = d.orig_ori_rgb;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
//TODO: call it when out format is setted. and avoid too much calls
|
||||
bool ImageConverterIPP::prepareData()
|
||||
{
|
||||
DPTR_D(ImageConverterIPP);
|
||||
//for color convertion
|
||||
if (d.w_in > 0 && d.h_in > 0) {
|
||||
qDebug("in size=%d x %d", d.w_in, d.h_in);
|
||||
int bytes = avpicture_get_size((AVPixelFormat)d.fmt_out, d.w_in, d.h_in);
|
||||
//if(d.orig_ori_rgb.size() < bytes) {
|
||||
d.orig_ori_rgb.resize(bytes);
|
||||
//}
|
||||
}
|
||||
return ImageConverter::prepareData();
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
#endif // #if !(REMOVE_IMAGE_CONVERTER_IPP)
|
||||
66
project/fm_viewer/fav/ImageConverter_p.h
Normal file
66
project/fm_viewer/fav/ImageConverter_p.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
#ifndef QTAV_IMAGECONVERTER_P_H
|
||||
#define QTAV_IMAGECONVERTER_P_H
|
||||
|
||||
#include "AVCompat.h"
|
||||
#include <QtCore/QVector>
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class ImageConverter;
|
||||
class ImageConverterPrivate : public DPtrPrivate<ImageConverter>
|
||||
{
|
||||
public:
|
||||
ImageConverterPrivate()
|
||||
: w_in(0),h_in(0)
|
||||
, w_out(0),h_out(0)
|
||||
, fmt_in(QTAV_PIX_FMT_C(YUV420P))
|
||||
, fmt_out(QTAV_PIX_FMT_C(RGB32))
|
||||
, range_in(ColorRange_Unknown)
|
||||
, range_out(ColorRange_Unknown)
|
||||
, brightness(0)
|
||||
, contrast(0)
|
||||
, saturation(0)
|
||||
, update_data(true)
|
||||
{
|
||||
bits.reserve(8);
|
||||
pitchs.reserve(8);
|
||||
}
|
||||
virtual bool setupColorspaceDetails(bool force = true) {
|
||||
Q_UNUSED(force);
|
||||
return true;
|
||||
}
|
||||
|
||||
int w_in, h_in, w_out, h_out;
|
||||
AVPixelFormat fmt_in, fmt_out;
|
||||
ColorRange range_in, range_out;
|
||||
int brightness, contrast, saturation;
|
||||
bool update_data;
|
||||
QByteArray data_out;
|
||||
QVector<quint8*> bits;
|
||||
QVector<int> pitchs;
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QTAV_IMAGECONVERTER_P_H
|
||||
560
project/fm_viewer/fav/LibAVFilter.cpp
Normal file
560
project/fm_viewer/fav/LibAVFilter.cpp
Normal file
@@ -0,0 +1,560 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2014)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "LibAVFilter.h"
|
||||
#if !(REMOVE_LIBAV_FILTER)
|
||||
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include "Filter_p.h"
|
||||
#include "Statistics.h"
|
||||
#include "AudioFrame.h"
|
||||
#include "VideoFrame.h"
|
||||
#include "AVCompat.h"
|
||||
#include "internal.h"
|
||||
#include "Logger.h"
|
||||
|
||||
/*
|
||||
* libav10.x, ffmpeg2.x: av_buffersink_read deprecated
|
||||
* libav9.x: only av_buffersink_read can be used
|
||||
* ffmpeg<2.0: av_buffersink_get_buffer_ref and av_buffersink_read
|
||||
*/
|
||||
// TODO: enabled = false if no libavfilter
|
||||
// TODO: filter_complex
|
||||
// NO COPY in push/pull
|
||||
#define QTAV_HAVE_av_buffersink_get_frame (LIBAV_MODULE_CHECK(LIBAVFILTER, 4, 2, 0) || FFMPEG_MODULE_CHECK(LIBAVFILTER, 3, 79, 100)) //3.79.101: ff2.0.4
|
||||
|
||||
namespace FAV {
|
||||
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
// local types can not be used as template parameters
|
||||
class AVFrameHolder {
|
||||
public:
|
||||
AVFrameHolder() {
|
||||
m_frame = av_frame_alloc();
|
||||
#if !QTAV_HAVE_av_buffersink_get_frame
|
||||
picref = 0;
|
||||
#endif
|
||||
}
|
||||
~AVFrameHolder() {
|
||||
av_frame_free(&m_frame);
|
||||
#if !QTAV_HAVE_av_buffersink_get_frame
|
||||
avfilter_unref_bufferp(&picref);
|
||||
#endif
|
||||
}
|
||||
AVFrame* frame() { return m_frame;}
|
||||
#if !QTAV_HAVE_av_buffersink_get_frame
|
||||
AVFilterBufferRef** bufferRef() { return &picref;}
|
||||
// copy properties and data ptrs(no deep copy).
|
||||
void copyBufferToFrame() { avfilter_copy_buf_props(m_frame, picref);}
|
||||
#endif
|
||||
private:
|
||||
AVFrame *m_frame;
|
||||
#if !QTAV_HAVE_av_buffersink_get_frame
|
||||
AVFilterBufferRef *picref;
|
||||
#endif
|
||||
};
|
||||
typedef QSharedPointer<AVFrameHolder> AVFrameHolderRef;
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
|
||||
|
||||
class LibAVFilter::Private
|
||||
{
|
||||
public:
|
||||
Private()
|
||||
: avframe(0)
|
||||
, status(LibAVFilter::NotConfigured)
|
||||
{
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
filter_graph = 0;
|
||||
in_filter_ctx = 0;
|
||||
out_filter_ctx = 0;
|
||||
avfilter_register_all();
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
}
|
||||
~Private() {
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
avfilter_graph_free(&filter_graph);
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
if (avframe) {
|
||||
av_frame_free(&avframe);
|
||||
avframe = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool setOptions(const QString& opt) {
|
||||
if (options == opt)
|
||||
return false;
|
||||
options = opt;
|
||||
status = LibAVFilter::NotConfigured;
|
||||
return true;
|
||||
}
|
||||
bool pushAudioFrame(Frame *frame, bool changed, const QString& args);
|
||||
bool pushVideoFrame(Frame *frame, bool changed, const QString& args);
|
||||
|
||||
bool setup(const QString& args, bool video) {
|
||||
if (avframe) {
|
||||
av_frame_free(&avframe);
|
||||
avframe = 0;
|
||||
}
|
||||
status = LibAVFilter::ConfigureFailed;
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
avfilter_graph_free(&filter_graph);
|
||||
filter_graph = avfilter_graph_alloc();
|
||||
//QString sws_flags_str;
|
||||
// pixel_aspect==sar, pixel_aspect is more compatible
|
||||
QString buffersrc_args = args;
|
||||
qDebug("buffersrc_args=%s", buffersrc_args.toUtf8().constData());
|
||||
const AVFilter *buffersrc = avfilter_get_by_name(video ? "buffer" : "abuffer");
|
||||
Q_ASSERT(buffersrc);
|
||||
AV_ENSURE_OK(avfilter_graph_create_filter(&in_filter_ctx,
|
||||
buffersrc,
|
||||
"in", buffersrc_args.toUtf8().constData(), NULL,
|
||||
filter_graph)
|
||||
, false);
|
||||
/* buffer video sink: to terminate the filter chain. */
|
||||
const AVFilter *buffersink = avfilter_get_by_name(video ? "buffersink" : "abuffersink");
|
||||
Q_ASSERT(buffersink);
|
||||
AV_ENSURE_OK(avfilter_graph_create_filter(&out_filter_ctx, buffersink, "out",
|
||||
NULL, NULL, filter_graph)
|
||||
, false);
|
||||
/* Endpoints for the filter graph. */
|
||||
AVFilterInOut *outputs = avfilter_inout_alloc();
|
||||
AVFilterInOut *inputs = avfilter_inout_alloc();
|
||||
outputs->name = av_strdup("in");
|
||||
outputs->filter_ctx = in_filter_ctx;
|
||||
outputs->pad_idx = 0;
|
||||
outputs->next = NULL;
|
||||
|
||||
inputs->name = av_strdup("out");
|
||||
inputs->filter_ctx = out_filter_ctx;
|
||||
inputs->pad_idx = 0;
|
||||
inputs->next = NULL;
|
||||
|
||||
struct delete_helper {
|
||||
AVFilterInOut **x;
|
||||
delete_helper(AVFilterInOut **io) : x(io) {}
|
||||
~delete_helper() {
|
||||
// libav always free it in avfilter_graph_parse. so we does nothing
|
||||
#if QTAV_USE_FFMPEG(LIBAVFILTER)
|
||||
avfilter_inout_free(x);
|
||||
#endif
|
||||
}
|
||||
} scoped_in(&inputs), scoped_out(&outputs);
|
||||
//avfilter_graph_parse, avfilter_graph_parse2?
|
||||
AV_ENSURE_OK(avfilter_graph_parse_ptr(filter_graph, options.toUtf8().constData(), &inputs, &outputs, NULL), false);
|
||||
AV_ENSURE_OK(avfilter_graph_config(filter_graph, NULL), false);
|
||||
avframe = av_frame_alloc();
|
||||
status = LibAVFilter::ConfigureOk;
|
||||
#if DBG_GRAPH
|
||||
//not available in libav9
|
||||
const char* g = avfilter_graph_dump(filter_graph, NULL);
|
||||
if (g)
|
||||
qDebug().nospace() << "filter graph:\n" << g; // use << to not print special chars in qt5.5
|
||||
av_freep(&g);
|
||||
#endif //DBG_GRAPH
|
||||
return true;
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
return false;
|
||||
}
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
AVFilterGraph *filter_graph;
|
||||
AVFilterContext *in_filter_ctx;
|
||||
AVFilterContext *out_filter_ctx;
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
AVFrame *avframe;
|
||||
QString options;
|
||||
LibAVFilter::Status status;
|
||||
};
|
||||
|
||||
QStringList LibAVFilter::videoFilters()
|
||||
{
|
||||
static const QStringList list(LibAVFilter::registeredFilters(AVMEDIA_TYPE_VIDEO));
|
||||
return list;
|
||||
}
|
||||
|
||||
QStringList LibAVFilter::audioFilters()
|
||||
{
|
||||
static const QStringList list(LibAVFilter::registeredFilters(AVMEDIA_TYPE_AUDIO));
|
||||
return list;
|
||||
}
|
||||
|
||||
QString LibAVFilter::filterDescription(const QString &filterName)
|
||||
{
|
||||
QString s;
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
avfilter_register_all();
|
||||
const AVFilter *f = avfilter_get_by_name(filterName.toUtf8().constData());
|
||||
if (!f)
|
||||
return s;
|
||||
if (f->description)
|
||||
s.append(QString::fromUtf8(f->description));
|
||||
#if AV_MODULE_CHECK(LIBAVFILTER, 3, 7, 0, 8, 100)
|
||||
return s.append(QLatin1String("\n")).append("Options:")
|
||||
.append(Internal::optionsToString((void*)&f->priv_class));
|
||||
#endif
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
Q_UNUSED(filterName);
|
||||
return s;
|
||||
}
|
||||
|
||||
LibAVFilter::LibAVFilter()
|
||||
: priv(new Private())
|
||||
{
|
||||
}
|
||||
|
||||
LibAVFilter::~LibAVFilter()
|
||||
{
|
||||
delete priv;
|
||||
}
|
||||
|
||||
void LibAVFilter::setOptions(const QString &options)
|
||||
{
|
||||
if (!priv->setOptions(options))
|
||||
return;
|
||||
Q_EMIT optionsChanged();
|
||||
}
|
||||
|
||||
QString LibAVFilter::options() const
|
||||
{
|
||||
return priv->options;
|
||||
}
|
||||
|
||||
LibAVFilter::Status LibAVFilter::status() const
|
||||
{
|
||||
return priv->status;
|
||||
}
|
||||
|
||||
bool LibAVFilter::pushVideoFrame(Frame *frame, bool changed)
|
||||
{
|
||||
return priv->pushVideoFrame(frame, changed, sourceArguments());
|
||||
}
|
||||
|
||||
bool LibAVFilter::pushAudioFrame(Frame *frame, bool changed)
|
||||
{
|
||||
return priv->pushAudioFrame(frame, changed, sourceArguments());
|
||||
}
|
||||
|
||||
void* LibAVFilter::pullFrameHolder()
|
||||
{
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
AVFrameHolder *holder = NULL;
|
||||
holder = new AVFrameHolder();
|
||||
#if QTAV_HAVE_av_buffersink_get_frame
|
||||
int ret = av_buffersink_get_frame(priv->out_filter_ctx, holder->frame());
|
||||
#else
|
||||
int ret = av_buffersink_read(priv->out_filter_ctx, holder->bufferRef());
|
||||
#endif //QTAV_HAVE_av_buffersink_get_frame
|
||||
if (ret < 0) {
|
||||
qWarning("av_buffersink_get_frame error: %s", av_err2str(ret));
|
||||
delete holder;
|
||||
return 0;
|
||||
}
|
||||
#if !QTAV_HAVE_av_buffersink_get_frame
|
||||
holder->copyBufferToFrame();
|
||||
#endif
|
||||
return holder;
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
return 0;
|
||||
}
|
||||
|
||||
QStringList LibAVFilter::registeredFilters(int type)
|
||||
{
|
||||
QStringList filters;
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
avfilter_register_all();
|
||||
const AVFilter* f = NULL;
|
||||
AVFilterPad* fp = NULL; // no const in avfilter_pad_get_name() for ffmpeg<=1.2 libav<=9
|
||||
#if AV_MODULE_CHECK(LIBAVFILTER, 3, 8, 0, 53, 100)
|
||||
while ((f = avfilter_next(f))) {
|
||||
#else
|
||||
AVFilter** ff = NULL;
|
||||
while ((ff = av_filter_next(ff)) && *ff) {
|
||||
f = (*ff);
|
||||
#endif
|
||||
fp = (AVFilterPad*)f->inputs;
|
||||
// only check the 1st pad
|
||||
if (!fp || !avfilter_pad_get_name(fp, 0) || avfilter_pad_get_type(fp, 0) != (AVMediaType)type)
|
||||
continue;
|
||||
fp = (AVFilterPad*)f->outputs;
|
||||
// only check the 1st pad
|
||||
if (!fp || !avfilter_pad_get_name(fp, 0) || avfilter_pad_get_type(fp, 0) != (AVMediaType)type)
|
||||
continue;
|
||||
filters.append(QLatin1String(f->name));
|
||||
}
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
return filters;
|
||||
}
|
||||
|
||||
class LibAVFilterVideoPrivate : public VideoFilterPrivate
|
||||
{
|
||||
public:
|
||||
LibAVFilterVideoPrivate()
|
||||
: VideoFilterPrivate()
|
||||
, pixfmt(QTAV_PIX_FMT_C(NONE))
|
||||
, width(0)
|
||||
, height(0)
|
||||
{}
|
||||
AVPixelFormat pixfmt;
|
||||
int width, height;
|
||||
};
|
||||
|
||||
LibAVFilterVideo::LibAVFilterVideo(QObject *parent)
|
||||
: VideoFilter(*new LibAVFilterVideoPrivate(), parent)
|
||||
, LibAVFilter()
|
||||
{
|
||||
}
|
||||
|
||||
QStringList LibAVFilterVideo::filters() const
|
||||
{
|
||||
return LibAVFilter::videoFilters();
|
||||
}
|
||||
VideoFrame LibAVFilterVideo::originalFrame()
|
||||
{
|
||||
const AVFrame *f = priv->avframe;
|
||||
if(f == NULL) {
|
||||
return VideoFrame();
|
||||
}
|
||||
VideoFrame vf(f->width, f->height, VideoFormat(f->format));
|
||||
vf.setBits((quint8**)f->data);
|
||||
vf.setBytesPerLine((int*)f->linesize);
|
||||
//vf.set_pkt_dts(priv->pkt_dts);
|
||||
return vf;
|
||||
}
|
||||
void LibAVFilterVideo::pushFrame(VideoFrame* frame)
|
||||
{
|
||||
//qInfo() << "PUSH FRAME" << frame << __FUNCTION__;
|
||||
process(NULL,frame);
|
||||
}
|
||||
void LibAVFilterVideo::process(Statistics *statistics, VideoFrame *frame)
|
||||
{
|
||||
Q_UNUSED(statistics);
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
if (status() == ConfigureFailed)
|
||||
return;
|
||||
DPTR_D(LibAVFilterVideo);
|
||||
//Status old = status();
|
||||
bool changed = false;
|
||||
if (d.width != frame->width() || d.height != frame->height() || d.pixfmt != frame->pixelFormatFFmpeg()) {
|
||||
changed = true;
|
||||
d.width = frame->width();
|
||||
d.height = frame->height();
|
||||
d.pixfmt = (AVPixelFormat)frame->pixelFormatFFmpeg();
|
||||
}
|
||||
bool ok = pushVideoFrame(frame, changed);
|
||||
//if (old != status())
|
||||
// emit statusChanged();
|
||||
if (!ok)
|
||||
return;
|
||||
|
||||
AVFrameHolderRef ref((AVFrameHolder*)pullFrameHolder());
|
||||
if (!ref)
|
||||
return;
|
||||
const AVFrame *f = ref->frame();
|
||||
VideoFrame vf(f->width, f->height, VideoFormat(f->format));
|
||||
vf.setBits((quint8**)f->data);
|
||||
vf.setBytesPerLine((int*)f->linesize);
|
||||
vf.setMetaData(QStringLiteral("avframe_hoder_ref"), QVariant::fromValue(ref));
|
||||
vf.setPTS(ref->frame()->pts/1000000.0); //pkt_pts?
|
||||
vf.setDuration(ref->frame()->pkt_duration/1000.0);
|
||||
//vf.setMetaData(frame->availableMetaData());
|
||||
*frame = vf;
|
||||
#else
|
||||
Q_UNUSED(frame);
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
}
|
||||
|
||||
QString LibAVFilterVideo::sourceArguments() const
|
||||
{
|
||||
DPTR_D(const LibAVFilterVideo);
|
||||
#if QTAV_USE_LIBAV(LIBAVFILTER)
|
||||
return QStringLiteral("%1:%2:%3:%4:%5:%6:%7")
|
||||
#else
|
||||
return QStringLiteral("video_size=%1x%2:pix_fmt=%3:time_base=%4/%5:pixel_aspect=%6/%7")
|
||||
#endif
|
||||
.arg(d.width).arg(d.height).arg(d.pixfmt)
|
||||
.arg(1).arg(AV_TIME_BASE) //time base 1/1?
|
||||
.arg(1).arg(1) //sar
|
||||
;
|
||||
}
|
||||
|
||||
class LibAVFilterAudioPrivate : public AudioFilterPrivate
|
||||
{
|
||||
public:
|
||||
LibAVFilterAudioPrivate()
|
||||
: AudioFilterPrivate()
|
||||
, sample_rate(0)
|
||||
, sample_fmt(AV_SAMPLE_FMT_NONE)
|
||||
, channel_layout(0)
|
||||
{}
|
||||
int sample_rate;
|
||||
AVSampleFormat sample_fmt;
|
||||
qint64 channel_layout;
|
||||
};
|
||||
|
||||
LibAVFilterAudio::LibAVFilterAudio(QObject *parent)
|
||||
: AudioFilter(*new LibAVFilterAudioPrivate(), parent)
|
||||
, LibAVFilter()
|
||||
{}
|
||||
|
||||
QStringList LibAVFilterAudio::filters() const
|
||||
{
|
||||
return LibAVFilter::audioFilters();
|
||||
}
|
||||
|
||||
QString LibAVFilterAudio::sourceArguments() const
|
||||
{
|
||||
DPTR_D(const LibAVFilterAudio);
|
||||
return QStringLiteral("time_base=%1/%2:sample_rate=%3:sample_fmt=%4:channel_layout=0x%5")
|
||||
.arg(1)
|
||||
.arg(AV_TIME_BASE)
|
||||
.arg(d.sample_rate)
|
||||
//ffmpeg new: AV_OPT_TYPE_SAMPLE_FMT
|
||||
//libav, ffmpeg old: AV_OPT_TYPE_STRING
|
||||
.arg(QLatin1String(av_get_sample_fmt_name(d.sample_fmt)))
|
||||
.arg(d.channel_layout, 0, 16) //AV_OPT_TYPE_STRING
|
||||
;
|
||||
}
|
||||
|
||||
void LibAVFilterAudio::process(Statistics *statistics, AudioFrame *frame)
|
||||
{
|
||||
Q_UNUSED(statistics);
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
if (status() == ConfigureFailed)
|
||||
return;
|
||||
DPTR_D(LibAVFilterAudio);
|
||||
//Status old = status();
|
||||
bool changed = false;
|
||||
const AudioFormat afmt(frame->format());
|
||||
if (d.sample_rate != afmt.sampleRate() || d.sample_fmt != afmt.sampleFormatFFmpeg() || d.channel_layout != afmt.channelLayoutFFmpeg()) {
|
||||
changed = true;
|
||||
d.sample_rate = afmt.sampleRate();
|
||||
d.sample_fmt = (AVSampleFormat)afmt.sampleFormatFFmpeg();
|
||||
d.channel_layout = afmt.channelLayoutFFmpeg();
|
||||
}
|
||||
bool ok = pushAudioFrame(frame, changed);
|
||||
//if (old != status())
|
||||
// emit statusChanged();
|
||||
if (!ok)
|
||||
return;
|
||||
|
||||
AVFrameHolderRef ref((AVFrameHolder*)pullFrameHolder());
|
||||
if (!ref)
|
||||
return;
|
||||
const AVFrame *f = ref->frame();
|
||||
AudioFormat fmt;
|
||||
fmt.setSampleFormatFFmpeg(f->format);
|
||||
fmt.setChannelLayoutFFmpeg(f->channel_layout);
|
||||
fmt.setSampleRate(f->sample_rate);
|
||||
if (!fmt.isValid()) {// need more data to decode to get a frame
|
||||
return;
|
||||
}
|
||||
AudioFrame af(fmt);
|
||||
//af.setBits((quint8**)f->extended_data);
|
||||
//af.setBytesPerLine((int*)f->linesize);
|
||||
af.setBits(f->extended_data); // TODO: ref
|
||||
af.setBytesPerLine(f->linesize[0], 0); // for correct alignment
|
||||
af.setSamplesPerChannel(f->nb_samples);
|
||||
af.setMetaData(QStringLiteral("avframe_hoder_ref"), QVariant::fromValue(ref));
|
||||
af.setPTS(ref->frame()->pts/1000000.0); //pkt_pts?
|
||||
af.setDuration(((qreal)ref->frame()->pkt_duration)/1000000.0);
|
||||
//af.setMetaData(frame->availableMetaData());
|
||||
*frame = af;
|
||||
#else
|
||||
Q_UNUSED(frame);
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
}
|
||||
|
||||
bool LibAVFilter::Private::pushVideoFrame(Frame *frame, bool changed, const QString &args)
|
||||
{
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
VideoFrame *vf = static_cast<VideoFrame*>(frame);
|
||||
if (status == LibAVFilter::NotConfigured || !avframe || changed) {
|
||||
if (!setup(args, true)) {
|
||||
qWarning("setup video filter graph error");
|
||||
//enabled = false; // skip this filter and avoid crash
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!vf->constBits(0)) {
|
||||
*vf = vf->to(vf->format());
|
||||
}
|
||||
avframe->pts = frame->pts() * 1000000.0; // time_base is 1/1000000
|
||||
avframe->width = vf->width();
|
||||
avframe->height = vf->height();
|
||||
avframe->format = (AVPixelFormat)vf->pixelFormatFFmpeg();
|
||||
for (int i = 0; i < vf->planeCount(); ++i) {
|
||||
avframe->data[i] = (uint8_t*)vf->constBits(i);
|
||||
avframe->linesize[i] = vf->bytesPerLine(i);
|
||||
}
|
||||
//TODO: side data for vf_codecview etc
|
||||
//int ret = av_buffersrc_add_frame_flags(in_filter_ctx, avframe, AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
/*
|
||||
* av_buffersrc_write_frame equals to av_buffersrc_add_frame_flags with AV_BUFFERSRC_FLAG_KEEP_REF.
|
||||
* av_buffersrc_write_frame is more compatible, while av_buffersrc_add_frame_flags only exists in ffmpeg >=2.0
|
||||
* add a ref if frame is ref counted
|
||||
* TODO: libav < 10.0 will copy the frame, prefer to use av_buffersrc_buffer
|
||||
*/
|
||||
AV_ENSURE_OK(av_buffersrc_write_frame(in_filter_ctx, avframe), false);
|
||||
return true;
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
Q_UNUSED(frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool LibAVFilter::Private::pushAudioFrame(Frame *frame, bool changed, const QString &args)
|
||||
{
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
if (status == LibAVFilter::NotConfigured || !avframe || changed) {
|
||||
if (!setup(args, false)) {
|
||||
qWarning("setup audio filter graph error");
|
||||
//enabled = false; // skip this filter and avoid crash
|
||||
return false;
|
||||
}
|
||||
}
|
||||
AudioFrame *af = static_cast<AudioFrame*>(frame);
|
||||
const AudioFormat afmt(af->format());
|
||||
avframe->pts = frame->pts() * 1000000.0; // time_base is 1/1000000
|
||||
avframe->sample_rate = afmt.sampleRate();
|
||||
avframe->channel_layout = afmt.channelLayoutFFmpeg();
|
||||
#if QTAV_USE_FFMPEG(LIBAVCODEC) || QTAV_USE_FFMPEG(LIBAVUTIL) //AVFrame was in avcodec
|
||||
avframe->channels = afmt.channels(); //MUST set because av_buffersrc_write_frame will compare channels and layout
|
||||
#endif
|
||||
avframe->format = (AVSampleFormat)afmt.sampleFormatFFmpeg();
|
||||
avframe->nb_samples = af->samplesPerChannel();
|
||||
for (int i = 0; i < af->planeCount(); ++i) {
|
||||
//avframe->data[i] = (uint8_t*)af->constBits(i);
|
||||
avframe->extended_data[i] = (uint8_t*)af->constBits(i);
|
||||
avframe->linesize[i] = af->bytesPerLine(i);
|
||||
}
|
||||
AV_ENSURE_OK(av_buffersrc_write_frame(in_filter_ctx, avframe), false);
|
||||
return true;
|
||||
#endif //QTAV_HAVE(AVFILTER)
|
||||
Q_UNUSED(frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
#if QTAV_HAVE(AVFILTER)
|
||||
Q_DECLARE_METATYPE(FAV::AVFrameHolderRef)
|
||||
#endif
|
||||
#endif // #if !(REMOVE_LIBAV_FILTER)
|
||||
110
project/fm_viewer/fav/LibAVFilter.h
Normal file
110
project/fm_viewer/fav/LibAVFilter.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2014)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_LIBAVFILTER_H
|
||||
#define QTAV_LIBAVFILTER_H
|
||||
|
||||
#include "Filter.h"
|
||||
|
||||
#define REMOVE_LIBAV_FILTER 0
|
||||
#if !(REMOVE_LIBAV_FILTER)
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class Q_AV_EXPORT LibAVFilter
|
||||
{
|
||||
public:
|
||||
static QString filterDescription(const QString& filterName);
|
||||
static QStringList videoFilters();
|
||||
static QStringList audioFilters();
|
||||
/*!
|
||||
* \brief The Status enum
|
||||
* Status of filter graph.
|
||||
* If setOptions() is called, the status is NotConfigured, and will to configure when processing
|
||||
* a new frame.
|
||||
* Filter graph will be reconfigured if options, incoming video frame format or size changed
|
||||
*/
|
||||
enum Status {
|
||||
NotConfigured,
|
||||
ConfigureFailed,
|
||||
ConfigureOk
|
||||
};
|
||||
|
||||
LibAVFilter();
|
||||
virtual ~LibAVFilter();
|
||||
/*!
|
||||
* \brief setOptions
|
||||
* Set new option. Filter graph will be setup if receives a frame if options changed.
|
||||
* \param options option string for libavfilter. libav and ffmpeg are different
|
||||
*/
|
||||
void setOptions(const QString& options);
|
||||
QString options() const;
|
||||
|
||||
Status status() const;
|
||||
protected:
|
||||
virtual QString sourceArguments() const = 0;
|
||||
bool pushVideoFrame(Frame* frame, bool changed);
|
||||
bool pushAudioFrame(Frame* frame, bool changed);
|
||||
void* pullFrameHolder();
|
||||
static QStringList registeredFilters(int type); // filters whose input/output type matches
|
||||
virtual void optionsChanged() {}
|
||||
class Private;
|
||||
Private *priv;
|
||||
};
|
||||
|
||||
class Q_AV_EXPORT LibAVFilterVideo : public VideoFilter, public LibAVFilter
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString options READ options WRITE setOptions NOTIFY optionsChanged)
|
||||
Q_PROPERTY(QStringList filters READ filters)
|
||||
public:
|
||||
LibAVFilterVideo(QObject *parent = 0);
|
||||
bool isSupported(VideoFilterContext::Type t) const Q_DECL_OVERRIDE { return t == VideoFilterContext::None;}
|
||||
QStringList filters() const; //the same as LibAVFilter::videoFilters
|
||||
VideoFrame originalFrame();
|
||||
void pushFrame(VideoFrame* frame);
|
||||
|
||||
Q_SIGNALS:
|
||||
void optionsChanged() Q_DECL_OVERRIDE;
|
||||
protected:
|
||||
void process(Statistics *statistics, VideoFrame *frame) Q_DECL_OVERRIDE;
|
||||
QString sourceArguments() const Q_DECL_OVERRIDE;
|
||||
};
|
||||
|
||||
class Q_AV_EXPORT LibAVFilterAudio : public AudioFilter, public LibAVFilter
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString options READ options WRITE setOptions NOTIFY optionsChanged)
|
||||
Q_PROPERTY(QStringList filters READ filters)
|
||||
public:
|
||||
LibAVFilterAudio(QObject *parent = 0);
|
||||
QStringList filters() const; //the same as LibAVFilter::audioFilters
|
||||
Q_SIGNALS:
|
||||
void optionsChanged() Q_DECL_OVERRIDE;
|
||||
protected:
|
||||
void process(Statistics *statistics, AudioFrame *frame) Q_DECL_OVERRIDE;
|
||||
QString sourceArguments() const Q_DECL_OVERRIDE;
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
#endif // QTAV_LIBAVFILTER_H
|
||||
#endif // #if !(REMOVE_LIBAV_FILTER)
|
||||
308
project/fm_viewer/fav/Logger.cpp
Normal file
308
project/fm_viewer/fav/Logger.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
/*!
|
||||
* DO NOT appear qDebug, qWanring etc in Logger.cpp! They are undefined and redefined to QtAV:Internal::Logger.xxx
|
||||
*/
|
||||
// we need LogLevel so must include QtAV_Global.h
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QMutex>
|
||||
#include "_fav_constants.h"
|
||||
#include "Logger.h"
|
||||
|
||||
#ifndef QTAV_NO_LOG_LEVEL
|
||||
|
||||
void ffmpeg_version_print();
|
||||
namespace FAV {
|
||||
namespace Internal {
|
||||
static QString gQtAVLogTag = QString();
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
typedef Logger::Context QMessageLogger;
|
||||
#endif
|
||||
|
||||
static void log_helper(QtMsgType msgType, const QMessageLogger *qlog, const char* msg, va_list ap) {
|
||||
static QMutex m;
|
||||
QMutexLocker lock(&m);
|
||||
Q_UNUSED(lock);
|
||||
static int repeat = 0;
|
||||
static QString last_msg;
|
||||
static QtMsgType last_type = QtDebugMsg;
|
||||
QString qmsg(gQtAVLogTag);
|
||||
QString formated;
|
||||
if (msg) {
|
||||
formated = QString().vsprintf(msg, ap);
|
||||
}
|
||||
// repeate check
|
||||
if (last_type == msgType && last_msg == formated) {
|
||||
repeat++;
|
||||
return;
|
||||
}
|
||||
if (repeat > 0) {
|
||||
// print repeat message and current message
|
||||
qmsg = QStringLiteral("%1(repeat %2)%3\n%4%5")
|
||||
.arg(qmsg).arg(repeat).arg(last_msg).arg(qmsg).arg(formated);
|
||||
} else {
|
||||
qmsg += formated;
|
||||
}
|
||||
repeat = 0;
|
||||
last_type = msgType;
|
||||
last_msg = formated;
|
||||
|
||||
// qt_message_output is a public api
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
qt_message_output(msgType, qmsg.toUtf8().constData());
|
||||
return;
|
||||
#else
|
||||
if (!qlog) {
|
||||
QMessageLogContext ctx;
|
||||
qt_message_output(msgType, ctx, qmsg);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
if (msgType == QtWarningMsg)
|
||||
qlog->warning() << qmsg;
|
||||
else if (msgType == QtCriticalMsg)
|
||||
qlog->critical() << qmsg;
|
||||
else if (msgType == QtFatalMsg)
|
||||
qlog->fatal("%s", qmsg.toUtf8().constData());
|
||||
else
|
||||
qlog->debug() << qmsg;
|
||||
#else
|
||||
if (msgType == QtFatalMsg)
|
||||
qlog->fatal("%s", qmsg.toUtf8().constData());
|
||||
#endif //
|
||||
}
|
||||
|
||||
// macro does not support A::##X
|
||||
|
||||
void Logger::debug(const char *msg, ...) const
|
||||
{
|
||||
#if (OFF_OTHER_DEBUG)
|
||||
Q_UNUSED(msg)
|
||||
#else
|
||||
QtAVDebug d; // initialize something. e.g. environment check
|
||||
Q_UNUSED(d);
|
||||
const int v = (int)logLevel();
|
||||
if (v <= (int)LogOff)
|
||||
return;
|
||||
if (v > (int)LogDebug && v < (int)LogAll)
|
||||
return;
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
// can not use ctx.debug() <<... because QT_NO_DEBUG_STREAM maybe defined
|
||||
log_helper(QtDebugMsg, &ctx, msg, ap);
|
||||
va_end(ap);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Logger::warning(const char *msg, ...) const
|
||||
{
|
||||
QtAVDebug d; // initialize something. e.g. environment check
|
||||
Q_UNUSED(d);
|
||||
const int v = (int)logLevel();
|
||||
if (v <= (int)LogOff)
|
||||
return;
|
||||
if (v > (int)LogWarning && v < (int)LogAll)
|
||||
return;
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
log_helper(QtWarningMsg, &ctx, msg, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void Logger::critical(const char *msg, ...) const
|
||||
{
|
||||
QtAVDebug d; // initialize something. e.g. environment check
|
||||
Q_UNUSED(d);
|
||||
const int v = (int)logLevel();
|
||||
if (v <= (int)LogOff)
|
||||
return;
|
||||
if (v > (int)LogCritical && v < (int)LogAll)
|
||||
return;
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
log_helper(QtCriticalMsg, &ctx, msg, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void Logger::fatal(const char *msg, ...) const Q_DECL_NOTHROW
|
||||
{
|
||||
QtAVDebug d; // initialize something. e.g. environment check
|
||||
Q_UNUSED(d);
|
||||
const int v = (int)logLevel();
|
||||
/*
|
||||
if (v <= (int)LogOff)
|
||||
abort();
|
||||
if (v > (int)LogFatal && v < (int)LogAll)
|
||||
abort();
|
||||
*/
|
||||
if (v > (int)LogOff) {
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
log_helper(QtFatalMsg, &ctx, msg, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
// internal used by Logger::fatal(const char*,...) with log level checked, so always do things here
|
||||
void Logger::Context::fatal(const char *msg, ...) const Q_DECL_NOTHROW
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
QString qmsg;
|
||||
if (msg)
|
||||
qmsg += QString().vsprintf(msg, ap);
|
||||
qt_message_output(QtFatalMsg, qmsg.toUtf8().constData());
|
||||
va_end(ap);
|
||||
abort();
|
||||
}
|
||||
#endif //QT_NO_DEBUG_STREAM
|
||||
#endif //QT_VERSION
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
// will print message in ~QDebug()
|
||||
// can not use QDebug on stack. It must lives in QtAVDebug
|
||||
QtAVDebug Logger::debug() const
|
||||
{
|
||||
QtAVDebug d(QtDebugMsg); //// initialize something. e.g. environment check
|
||||
const int v = (int)logLevel();
|
||||
if (v <= (int)LogOff)
|
||||
return d;
|
||||
if (v <= (int)LogDebug || v >= (int)LogAll)
|
||||
d.setQDebug(new QDebug(ctx.debug()));
|
||||
return d; //ref > 0
|
||||
}
|
||||
|
||||
QtAVDebug Logger::warning() const
|
||||
{
|
||||
QtAVDebug d(QtWarningMsg);
|
||||
const int v = (int)logLevel();
|
||||
if (v <= (int)LogOff)
|
||||
return d;
|
||||
if (v <= (int)LogWarning || v >= (int)LogAll)
|
||||
d.setQDebug(new QDebug(ctx.warning()));
|
||||
return d;
|
||||
}
|
||||
|
||||
QtAVDebug Logger::critical() const
|
||||
{
|
||||
QtAVDebug d(QtCriticalMsg);
|
||||
const int v = (int)logLevel();
|
||||
if (v <= (int)LogOff)
|
||||
return d;
|
||||
if (v <= (int)LogCritical || v >= (int)LogAll)
|
||||
d.setQDebug(new QDebug(ctx.critical()));
|
||||
return d;
|
||||
}
|
||||
// no QMessageLogger::fatal()
|
||||
#endif //QT_NO_DEBUG_STREAM
|
||||
|
||||
bool isLogLevelSet();
|
||||
void print_library_info();
|
||||
|
||||
QtAVDebug::QtAVDebug(QtMsgType t, QDebug *d)
|
||||
: type(t)
|
||||
, dbg(0)
|
||||
{
|
||||
if (d)
|
||||
setQDebug(d); // call *dbg << gQtAVLogTag
|
||||
static bool sFirstRun = true;
|
||||
if (!sFirstRun)
|
||||
return;
|
||||
sFirstRun = false;
|
||||
printf("%s\n", aboutQtAV_PlainText().toUtf8().constData());
|
||||
// check environment var and call other functions at first Qt logging call
|
||||
// always override setLogLevel()
|
||||
QByteArray env = qgetenv("QTAV_LOG_LEVEL");
|
||||
if (env.isEmpty())
|
||||
env = qgetenv("QTAV_LOG");
|
||||
if (!env.isEmpty()) {
|
||||
bool ok = false;
|
||||
const int level = env.toInt(&ok);
|
||||
if (ok) {
|
||||
if (level < (int)LogOff)
|
||||
setLogLevel(LogOff);
|
||||
else if (level > (int)LogAll)
|
||||
setLogLevel(LogAll);
|
||||
else
|
||||
setLogLevel((LogLevel)level);
|
||||
} else {
|
||||
env = env.toLower();
|
||||
if (env.endsWith("off"))
|
||||
setLogLevel(LogOff);
|
||||
else if (env.endsWith("debug"))
|
||||
setLogLevel(LogDebug);
|
||||
else if (env.endsWith("warning"))
|
||||
setLogLevel(LogWarning);
|
||||
else if (env.endsWith("critical"))
|
||||
setLogLevel(LogCritical);
|
||||
else if (env.endsWith("fatal"))
|
||||
setLogLevel(LogFatal);
|
||||
else if (env.endsWith("all") || env.endsWith("default"))
|
||||
setLogLevel(LogAll);
|
||||
}
|
||||
}
|
||||
env = qgetenv("QTAV_LOG_TAG");
|
||||
if (!env.isEmpty()) {
|
||||
gQtAVLogTag = QString::fromUtf8(env);
|
||||
}
|
||||
|
||||
if ((int)logLevel() > (int)LogOff) {
|
||||
print_library_info();
|
||||
}
|
||||
}
|
||||
|
||||
QtAVDebug::~QtAVDebug()
|
||||
{
|
||||
}
|
||||
|
||||
void QtAVDebug::setQDebug(QDebug *d)
|
||||
{
|
||||
dbg = QSharedPointer<QDebug>(d);
|
||||
if (dbg && !gQtAVLogTag.isEmpty()) {
|
||||
*dbg << gQtAVLogTag;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
QtAVDebug debug(const char *msg, ...)
|
||||
{
|
||||
if ((int)logLevel() > (int)Debug && logLevel() != All) {
|
||||
return QtAVDebug();
|
||||
}
|
||||
va_list ap;
|
||||
va_start(ap, msg); // use variable arg list
|
||||
QMessageLogContext ctx;
|
||||
log_helper(QtDebugMsg, &ctx, msg, ap);
|
||||
va_end(ap);
|
||||
return QtAVDebug();
|
||||
}
|
||||
#endif
|
||||
|
||||
} //namespace Internal
|
||||
} // namespace FAV
|
||||
|
||||
#endif //QTAV_NO_LOG_LEVEL
|
||||
231
project/fm_viewer/fav/Logger.h
Normal file
231
project/fm_viewer/fav/Logger.h
Normal file
@@ -0,0 +1,231 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2013)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
/*
|
||||
* Qt Logging Hack
|
||||
* in qmacdefines_mac.h, if qDebug is defined, then if will be defined as QT_NO_QDEBUG_MACRO, don't know why
|
||||
* So you have to include this only as the last #include in cpp file!
|
||||
* DO NOT use qDebug in public header!
|
||||
*/
|
||||
|
||||
#ifndef QTAV_LOGGER_H
|
||||
#define QTAV_LOGGER_H
|
||||
|
||||
/*!
|
||||
Environment var
|
||||
QTAV_LOG_TAG: prefix the value to log message
|
||||
QTAV_LOG_LEVEL: set log level, can be "off", "debug", "warning", "critical", "fatal", "all"
|
||||
*/
|
||||
|
||||
#include <QtDebug> //always include
|
||||
|
||||
#ifndef QTAV_NO_LOG_LEVEL
|
||||
#include "_fav_constants.h"
|
||||
#include <QSharedPointer>
|
||||
#ifndef Q_DECL_CONSTEXPR
|
||||
#define Q_DECL_CONSTEXPR
|
||||
#endif //Q_DECL_CONSTEXPR
|
||||
#ifndef Q_DECL_NOTHROW
|
||||
#define Q_DECL_NOTHROW
|
||||
#endif //Q_DECL_NOTHROW
|
||||
#ifndef Q_ATTRIBUTE_FORMAT_PRINTF
|
||||
#define Q_ATTRIBUTE_FORMAT_PRINTF(...)
|
||||
#endif //Q_ATTRIBUTE_FORMAT_PRINTF
|
||||
#ifndef Q_NORETURN
|
||||
#define Q_NORETURN
|
||||
#endif
|
||||
|
||||
#ifndef Q_FUNC_INFO
|
||||
#define Q_FUNC_INFO __FUNCTION__
|
||||
#endif
|
||||
|
||||
namespace FAV {
|
||||
namespace Internal {
|
||||
|
||||
// internal use when building QtAV library
|
||||
class QtAVDebug {
|
||||
public:
|
||||
/*!
|
||||
* \brief QtAVDebug
|
||||
* QDebug can be copied from QMessageLogger or others. take the ownership of d
|
||||
* \param d nothing will be logged and t is ignored if null
|
||||
*/
|
||||
QtAVDebug(QtMsgType t = QtDebugMsg, QDebug *d = 0);
|
||||
~QtAVDebug();
|
||||
void setQDebug(QDebug* d);
|
||||
// QDebug api
|
||||
inline QtAVDebug &space() {
|
||||
if (dbg)
|
||||
dbg->space();
|
||||
return *this;
|
||||
}
|
||||
inline QtAVDebug &nospace() {
|
||||
if (dbg)
|
||||
dbg->nospace();
|
||||
return *this;
|
||||
}
|
||||
inline QtAVDebug &maybeSpace() {
|
||||
if (dbg)
|
||||
dbg->maybeSpace();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> QtAVDebug &operator<<(T t) {
|
||||
if (!dbg)
|
||||
return *this;
|
||||
const int l = (int)logLevel();
|
||||
if (l <= (int)LogOff)
|
||||
return *this;
|
||||
if (l >= (int)LogAll) {
|
||||
*dbg << t;
|
||||
return *this;
|
||||
}
|
||||
if (l == (int)LogDebug) {
|
||||
*dbg << t;
|
||||
return *this;
|
||||
}
|
||||
if (l == (int)LogWarning) {
|
||||
if ((int)type >= (int)QtWarningMsg)
|
||||
*dbg << t;
|
||||
return *this;
|
||||
}
|
||||
if (l == (int)LogCritical) {
|
||||
if ((int)type >= (int)QtCriticalMsg)
|
||||
*dbg << t;
|
||||
return *this;
|
||||
}
|
||||
if (l == (int)LogFatal) {
|
||||
if ((int)type >= (int)QtFatalMsg)
|
||||
*dbg << t;
|
||||
return *this;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
private:
|
||||
QtMsgType type;
|
||||
// use ptr. otherwise ~QDebug() will print message.
|
||||
QSharedPointer<QDebug> dbg;
|
||||
};
|
||||
class Logger
|
||||
{
|
||||
Q_DISABLE_COPY(Logger)
|
||||
public:Q_DECL_CONSTEXPR Logger(const char *file = "unknown", int line = 0, const char *function = "unknown", const char *category = "default")
|
||||
: ctx(file, line, function, category) {}
|
||||
void debug(const char *msg, ...) const Q_ATTRIBUTE_FORMAT_PRINTF(2, 3);
|
||||
void noDebug(const char *, ...) const Q_ATTRIBUTE_FORMAT_PRINTF(2, 3)
|
||||
{}
|
||||
void warning(const char *msg, ...) const Q_ATTRIBUTE_FORMAT_PRINTF(2, 3);
|
||||
void critical(const char *msg, ...) const Q_ATTRIBUTE_FORMAT_PRINTF(2, 3);
|
||||
|
||||
//TODO: QLoggingCategory
|
||||
#ifndef Q_CC_MSVC
|
||||
Q_NORETURN
|
||||
#endif
|
||||
void fatal(const char *msg, ...) const Q_DECL_NOTHROW Q_ATTRIBUTE_FORMAT_PRINTF(2, 3);
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
//TODO: QLoggingCategory
|
||||
QtAVDebug debug() const;
|
||||
QtAVDebug warning() const;
|
||||
QtAVDebug critical() const;
|
||||
//QtAVDebug fatal() const;
|
||||
QNoDebug noDebug() const Q_DECL_NOTHROW;
|
||||
#endif // QT_NO_DEBUG_STREAM
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
public: //public can typedef outside
|
||||
class Context {
|
||||
Q_DISABLE_COPY(Context)
|
||||
public:
|
||||
Q_DECL_CONSTEXPR Context(const char *fileName, int lineNumber, const char *functionName, const char *categoryName)
|
||||
: version(1), line(lineNumber), file(fileName), function(functionName), category(categoryName) {}
|
||||
#ifndef Q_CC_MSVC
|
||||
Q_NORETURN
|
||||
#endif
|
||||
void fatal(const char *msg, ...) const Q_DECL_NOTHROW Q_ATTRIBUTE_FORMAT_PRINTF(2, 3);
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
inline QDebug debug() const { return QDebug(QtDebugMsg);}
|
||||
inline QDebug warning() const { return QDebug(QtWarningMsg);}
|
||||
inline QDebug critical() const { return QDebug(QtCriticalMsg);}
|
||||
inline QDebug fatal() const { return QDebug(QtFatalMsg);}
|
||||
#endif //QT_NO_DEBUG_STREAM
|
||||
int version;
|
||||
int line;
|
||||
const char *file;
|
||||
const char *function;
|
||||
const char *category;
|
||||
};
|
||||
private:
|
||||
Context ctx;
|
||||
#else
|
||||
private:
|
||||
QMessageLogger ctx;
|
||||
#endif
|
||||
};
|
||||
//simple way
|
||||
#if 0
|
||||
#undef qDebug
|
||||
#define qDebug(fmt, ...) FAV::Logger::debug(#fmt, ##__VA_ARGS__)
|
||||
#undef qDebug
|
||||
#if !defined(QT_NO_DEBUG_STREAM)
|
||||
QtAVDebug debug(const char *msg, ...); /* print debug message */
|
||||
#else // QT_NO_DEBUG_STREAM
|
||||
#undef qDebug
|
||||
QNoDebug debug(const char *msg, ...); /* print debug message */
|
||||
#define qDebug QT_NO_QDEBUG_MACRO
|
||||
#endif //QT_NO_DEBUG_STREAM
|
||||
#endif //0
|
||||
|
||||
// complex way like Qt5. can use Qt5's logging context features
|
||||
// including qDebug() << ...
|
||||
//qDebug() Log(__FILE__, __LINE__, Q_FUNC_INFO).debug
|
||||
// DO NOT appear qDebug, qWanring etc in Logger.cpp! They are undefined and redefined to QtAV:Internal::Logger.xxx
|
||||
#undef qDebug //was defined as QMessageLogger...
|
||||
#undef qWarning
|
||||
#undef qCritical
|
||||
#undef qFatal
|
||||
// debug and warning output can be disabled at build time in Qt
|
||||
// from qdebug.h
|
||||
|
||||
#ifdef QT_NO_DEBUG_OUTPUT
|
||||
#define QT_NO_WARNING_OUTPUT ////FIXME. qWarning() => Logger.warning() not declared
|
||||
#undef qDebug
|
||||
inline QNoDebug qDebug() { return QNoDebug(); }
|
||||
#define qDebug QT_NO_QDEBUG_MACRO
|
||||
#else
|
||||
inline QtAVDebug qDebug() { return QtAVDebug(QtDebugMsg); }
|
||||
#define qDebug FAV::Internal::Logger(__FILE__, __LINE__, Q_FUNC_INFO).debug
|
||||
#endif //QT_NO_DEBUG_OUTPUT
|
||||
|
||||
#ifdef QT_NO_WARNING_OUTPUT
|
||||
#undef qWarning
|
||||
inline QNoDebug qWarning() { return QNoDebug(); }
|
||||
#define qWarning QT_NO_QWARNING_MACRO
|
||||
#else
|
||||
inline QtAVDebug qWarning() { return QtAVDebug(QtWarningMsg); }
|
||||
#define qWarning FAV::Internal::Logger(__FILE__, __LINE__, Q_FUNC_INFO).warning
|
||||
#endif //QT_NO_WARNING_OUTPUT
|
||||
#define qCritical FAV::Internal::Logger(__FILE__, __LINE__, Q_FUNC_INFO).critical
|
||||
#define qFatal FAV::Internal::Logger(__FILE__, __LINE__, Q_FUNC_INFO).fatal
|
||||
} // namespace Internal
|
||||
} // namespace FAV
|
||||
|
||||
#endif //QTAV_NO_LOG_LEVEL
|
||||
#endif // QTAV_LOGGER_H
|
||||
216
project/fm_viewer/fav/MediaIO.cpp
Normal file
216
project/fm_viewer/fav/MediaIO.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
Initial QAVIOContext.cpp code is from Stefan Ladage <sladage@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2014)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#include "MediaIO.h"
|
||||
#include "MediaIO_p.h"
|
||||
#include "factory.h"
|
||||
#include <QtCore/QStringList>
|
||||
#include "Logger.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
FACTORY_DEFINE(MediaIO)
|
||||
|
||||
extern bool RegisterMediaIOQIODevice_Man();
|
||||
extern bool RegisterMediaIOQFile_Man();
|
||||
extern bool RegisterMediaIOWinRT_Man();
|
||||
void MediaIO::registerAll()
|
||||
{
|
||||
static bool done = false;
|
||||
if (done)
|
||||
return;
|
||||
done = true;
|
||||
RegisterMediaIOQIODevice_Man();
|
||||
RegisterMediaIOQFile_Man();
|
||||
#ifdef Q_OS_WINRT
|
||||
RegisterMediaIOWinRT_Man();
|
||||
#endif
|
||||
}
|
||||
|
||||
QStringList MediaIO::builtInNames()
|
||||
{
|
||||
static QStringList names;
|
||||
if (!names.isEmpty())
|
||||
return names;
|
||||
std::vector<const char*> ns(MediaIOFactory::Instance().registeredNames());
|
||||
foreach (const char* n, ns) {
|
||||
names.append(QLatin1String(n));
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
// TODO: plugin
|
||||
|
||||
// TODO: plugin use metadata(Qt plugin system) to avoid loading
|
||||
MediaIO* MediaIO::createForProtocol(const QString &protocol)
|
||||
{
|
||||
std::vector<MediaIOId> ids(MediaIOFactory::Instance().registeredIds());
|
||||
foreach (MediaIOId id, ids) {
|
||||
MediaIO *in = MediaIO::create(id);
|
||||
if (in->protocols().contains(protocol))
|
||||
return in;
|
||||
delete in;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
MediaIO* MediaIO::createForUrl(const QString &url)
|
||||
{
|
||||
const int p = url.indexOf(QLatin1String(":"));
|
||||
if (p < 0)
|
||||
return 0;
|
||||
MediaIO *io = MediaIO::createForProtocol(url.left(p));
|
||||
if (!io)
|
||||
return 0;
|
||||
io->setUrl(url);
|
||||
return io;
|
||||
}
|
||||
|
||||
static int av_read(void *opaque, unsigned char *buf, int buf_size)
|
||||
{
|
||||
MediaIO* io = static_cast<MediaIO*>(opaque);
|
||||
return io->read((char*)buf, buf_size);
|
||||
}
|
||||
|
||||
static int av_write(void *opaque, unsigned char *buf, int buf_size)
|
||||
{
|
||||
MediaIO* io = static_cast<MediaIO*>(opaque);
|
||||
return io->write((const char*)buf, buf_size);
|
||||
}
|
||||
|
||||
static int64_t av_seek(void *opaque, int64_t offset, int whence)
|
||||
{
|
||||
if (whence == SEEK_SET && offset < 0)
|
||||
return -1;
|
||||
MediaIO* io = static_cast<MediaIO*>(opaque);
|
||||
if (!io->isSeekable()) {
|
||||
qWarning("Can not seek. MediaIO[%s] is not a seekable IO", MediaIO::staticMetaObject.className());
|
||||
return -1;
|
||||
}
|
||||
if (whence == AVSEEK_SIZE) {
|
||||
// return the filesize without seeking anywhere. Supporting this is optional.
|
||||
return io->size() > 0 ? io->size() : 0;
|
||||
}
|
||||
if (!io->seek(offset, whence))
|
||||
return -1;
|
||||
return io->position();
|
||||
}
|
||||
|
||||
MediaIO::MediaIO(QObject *parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
MediaIO::MediaIO(MediaIOPrivate &d, QObject *parent)
|
||||
: QObject(parent)
|
||||
, DPTR_INIT(&d)
|
||||
{}
|
||||
|
||||
MediaIO::~MediaIO()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
void MediaIO::setUrl(const QString &url)
|
||||
{
|
||||
DPTR_D(MediaIO);
|
||||
if (d.url == url)
|
||||
return;
|
||||
// TODO: check protocol
|
||||
d.url = url;
|
||||
onUrlChanged();
|
||||
}
|
||||
|
||||
QString MediaIO::url() const
|
||||
{
|
||||
return d_func().url;
|
||||
}
|
||||
|
||||
void MediaIO::onUrlChanged()
|
||||
{}
|
||||
|
||||
bool MediaIO::setAccessMode(AccessMode value)
|
||||
{
|
||||
DPTR_D(MediaIO);
|
||||
if (d.mode == value)
|
||||
return true;
|
||||
if (value == Write && !isWritable()) {
|
||||
qWarning("Can not set Write access mode to this MediaIO");
|
||||
return false;
|
||||
}
|
||||
d.mode = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
MediaIO::AccessMode MediaIO::accessMode() const
|
||||
{
|
||||
return d_func().mode;
|
||||
}
|
||||
|
||||
const QStringList& MediaIO::protocols() const
|
||||
{
|
||||
static QStringList no_protocols;
|
||||
return no_protocols;
|
||||
}
|
||||
|
||||
#define IODATA_BUFFER_SIZE 32768 // TODO: user configurable
|
||||
static const int kBufferSizeDefault = 32768;
|
||||
|
||||
void MediaIO::setBufferSize(int value)
|
||||
{
|
||||
DPTR_D(MediaIO);
|
||||
if (d.buffer_size == value)
|
||||
return;
|
||||
d.buffer_size = value;
|
||||
}
|
||||
|
||||
int MediaIO::bufferSize() const
|
||||
{
|
||||
return d_func().buffer_size;
|
||||
}
|
||||
|
||||
void* MediaIO::avioContext()
|
||||
{
|
||||
DPTR_D(MediaIO);
|
||||
if (d.ctx)
|
||||
return d.ctx;
|
||||
// buffer will be released in av_probe_input_buffer2=>ffio_rewind_with_probe_data. always is? may be another context
|
||||
unsigned char* buf = (unsigned char*)av_malloc(IODATA_BUFFER_SIZE);
|
||||
// open for write if 1. SET 0 if open for read otherwise data ptr in av_read(data, ...) does not change
|
||||
const int write_flag = (accessMode() == Write) && isWritable();
|
||||
d.ctx = avio_alloc_context(buf, bufferSize() > 0 ? bufferSize() : kBufferSizeDefault, write_flag, this, &av_read, write_flag ? &av_write : NULL, &av_seek);
|
||||
// if seekable==false, containers that estimate duration from pts(or bit rate) will not seek to the last frame when computing duration
|
||||
// but it's still seekable if call seek outside(e.g. from demuxer)
|
||||
// TODO: isVariableSize: size = -real_size
|
||||
d.ctx->seekable = isSeekable() && !isVariableSize() ? AVIO_SEEKABLE_NORMAL : 0;
|
||||
return d.ctx;
|
||||
}
|
||||
|
||||
void MediaIO::release()
|
||||
{
|
||||
DPTR_D(MediaIO);
|
||||
if (!d.ctx)
|
||||
return;
|
||||
// avio_close is called by avformat_close_input. here we only allocate but no open
|
||||
av_freep(&d.ctx->buffer);
|
||||
av_freep(&d.ctx);
|
||||
}
|
||||
} //namespace FAV
|
||||
181
project/fm_viewer/fav/MediaIO.h
Normal file
181
project/fm_viewer/fav/MediaIO.h
Normal file
@@ -0,0 +1,181 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2014)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
#ifndef QTAV_MediaIO_H
|
||||
#define QTAV_MediaIO_H
|
||||
|
||||
#include <stdio.h> //SEEK_SET
|
||||
#include "_fav_constants.h"
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
namespace FAV {
|
||||
/*!
|
||||
* \brief MediaIO
|
||||
* Built-in io (use MediaIO::create(name), example: MediaIO *qio = MediaIO::create("QIODevice"))
|
||||
* "QIODevice":
|
||||
* properties:
|
||||
* device - read/write. parameter: QIODevice*. example: io->setDevice(mydev)
|
||||
* "QFile"
|
||||
* properties:
|
||||
* device - read only. example: io->device()
|
||||
* protocols: "", "qrc"
|
||||
*/
|
||||
typedef int MediaIOId;
|
||||
class MediaIOPrivate;
|
||||
class Q_AV_EXPORT MediaIO : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
DPTR_DECLARE_PRIVATE(MediaIO)
|
||||
Q_DISABLE_COPY(MediaIO)
|
||||
Q_ENUMS(AccessMode)
|
||||
public:
|
||||
enum AccessMode {
|
||||
Read, // default
|
||||
Write
|
||||
};
|
||||
|
||||
/// Registered MediaIO::name(): "QIODevice", "QFile"
|
||||
static QStringList builtInNames();
|
||||
/*!
|
||||
* \brief createForProtocol
|
||||
* If an MediaIO subclass SomeInput.protocols() contains the protocol, return it's instance.
|
||||
* "QFile" input has protocols "qrc"(and empty "" means "qrc")
|
||||
* \return Null if none of registered MediaIO supports the protocol
|
||||
*/
|
||||
static MediaIO* createForProtocol(const QString& protocol);
|
||||
/*!
|
||||
* \brief createForUrl
|
||||
* Create a MediaIO and setUrl(url) if protocol of url is supported.
|
||||
* Example: MediaIO *qrc = MediaIO::createForUrl("qrc:/icon/test.mkv");
|
||||
* \return MediaIO instance with url set. Null if protocol is not supported.
|
||||
*/
|
||||
static MediaIO* createForUrl(const QString& url);
|
||||
|
||||
virtual ~MediaIO();
|
||||
virtual QString name() const = 0;
|
||||
/*!
|
||||
* \brief setUrl
|
||||
* onUrlChange() will be called if url is different. onUrlChange() will close the old url and open the new url if it's not empty
|
||||
* \param url
|
||||
*/
|
||||
void setUrl(const QString& url = QString());
|
||||
QString url() const;
|
||||
/*!
|
||||
* \brief setAccessMode
|
||||
* A MediaIO instance can be 1 mode, Read (default) or Write. If !isWritable(), then set to Write will fail and mode does not change
|
||||
* Call it before any function!
|
||||
* \return false if set failed
|
||||
*/
|
||||
bool setAccessMode(AccessMode value);
|
||||
AccessMode accessMode() const;
|
||||
|
||||
/// supported protocols. default is empty
|
||||
virtual const QStringList& protocols() const;
|
||||
virtual bool isSeekable() const = 0;
|
||||
virtual bool isWritable() const { return false;}
|
||||
/*!
|
||||
* \brief read
|
||||
* read at most maxSize bytes to data, and return the bytes were actually read
|
||||
*/
|
||||
virtual qint64 read(char *data, qint64 maxSize) = 0;
|
||||
/*!
|
||||
* \brief write
|
||||
* write at most maxSize bytes from data, and return the bytes were actually written
|
||||
*/
|
||||
virtual qint64 write(const char* data, qint64 maxSize) {
|
||||
Q_UNUSED(data);
|
||||
Q_UNUSED(maxSize);
|
||||
return 0;
|
||||
}
|
||||
/*!
|
||||
* \brief seek
|
||||
* \param from SEEK_SET, SEEK_CUR and SEEK_END from stdio.h
|
||||
* \return true if success
|
||||
*/
|
||||
virtual bool seek(qint64 offset, int from = SEEK_SET) = 0;
|
||||
/*!
|
||||
* \brief position
|
||||
* MUST implement this. Used in seek
|
||||
* TODO: implement internally by default
|
||||
*/
|
||||
virtual qint64 position() const = 0;
|
||||
/*!
|
||||
* \brief size
|
||||
* \return <=0 if not support
|
||||
*/
|
||||
virtual qint64 size() const = 0;
|
||||
/*!
|
||||
* \brief isVariableSize
|
||||
* Experiment: A hack for size() changes during playback.
|
||||
* If true, containers that estimate duration from pts(or bit rate) will get an invalid duration. Thus no eof get
|
||||
* when the size of playback start reaches. So playback will not stop.
|
||||
* Demuxer seeking should work for this case.
|
||||
*/
|
||||
virtual bool isVariableSize() const { return false;}
|
||||
/*!
|
||||
* \brief setBufferSize
|
||||
* \param value <0: use default value
|
||||
*/
|
||||
void setBufferSize(int value = -1);
|
||||
int bufferSize() const;
|
||||
// The followings are for internal use. used by AVDemuxer, AVMuxer
|
||||
//struct AVIOContext; //anonymous struct in FFmpeg1.0.x
|
||||
void* avioContext(); //const?
|
||||
void release(); //TODO: how to remove it?
|
||||
public:
|
||||
static void registerAll();
|
||||
template<class C> static bool Register(MediaIOId id, const char* name) { return Register(id, create<C>, name);}
|
||||
static MediaIO* create(MediaIOId id);
|
||||
static MediaIO* create(const char* name);
|
||||
/*!
|
||||
* \brief next
|
||||
* \param id NULL to get the first id address
|
||||
* \return address of id or NULL if not found/end
|
||||
*/
|
||||
static MediaIOId* next(MediaIOId* id = 0);
|
||||
static const char* name(MediaIOId id);
|
||||
static MediaIOId id(const char* name);
|
||||
private:
|
||||
template<class C> static MediaIO* create() { return new C();}
|
||||
typedef MediaIO* (*MediaIOCreator)();
|
||||
static bool Register(MediaIOId id, MediaIOCreator, const char *name);
|
||||
protected:
|
||||
MediaIO(MediaIOPrivate& d, QObject* parent = 0);
|
||||
/*!
|
||||
* \brief onUrlChanged
|
||||
* Here you can close old url, parse new url() and open it
|
||||
*/
|
||||
virtual void onUrlChanged();
|
||||
DPTR_DECLARE(MediaIO)
|
||||
//private: // must add QT+=av-private if default ctor is private
|
||||
// base class, not direct create. only final class has public ctor is enough
|
||||
// FIXME: it's required by Q_DECLARE_METATYPE (also copy ctor)
|
||||
MediaIO(QObject* parent = 0);
|
||||
};
|
||||
Q_DECL_DEPRECATED typedef MediaIO AVInput; // for source compatibility
|
||||
} //namespace FAV
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
#include <QtCore/QMetaType>
|
||||
Q_DECLARE_METATYPE(FAV::MediaIO*)
|
||||
Q_DECLARE_METATYPE(QIODevice*)
|
||||
#endif
|
||||
#endif // QTAV_MediaIO_H
|
||||
48
project/fm_viewer/fav/MediaIO_p.h
Normal file
48
project/fm_viewer/fav/MediaIO_p.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/******************************************************************************
|
||||
QtAV: Multimedia framework based on Qt and FFmpeg
|
||||
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
|
||||
|
||||
* This file is part of QtAV (from 2014)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef QTAV_MediaIO_P_H
|
||||
#define QTAV_MediaIO_P_H
|
||||
|
||||
#include "_fav_constants.h"
|
||||
#include "AVCompat.h"
|
||||
#include <QtCore/QString>
|
||||
#include "MediaIO.h"
|
||||
|
||||
namespace FAV {
|
||||
|
||||
class MediaIO;
|
||||
class Q_AV_PRIVATE_EXPORT MediaIOPrivate : public DPtrPrivate<MediaIO>
|
||||
{
|
||||
public:
|
||||
MediaIOPrivate()
|
||||
: ctx(NULL)
|
||||
, buffer_size(-1)
|
||||
, mode(MediaIO::Read)
|
||||
{}
|
||||
AVIOContext *ctx;
|
||||
int buffer_size;
|
||||
MediaIO::AccessMode mode;
|
||||
QString url;
|
||||
};
|
||||
|
||||
} //namespace FAV
|
||||
#endif // QTAV_MediaIO_P_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user