2023-04-18 14:14:46 +08:00
|
|
|
#ifndef FFPLAYER_H
|
|
|
|
#define FFPLAYER_H
|
|
|
|
|
|
|
|
#include <condition_variable>
|
|
|
|
#include <thread>
|
|
|
|
#include <QImage>
|
|
|
|
#if(QT_VERSION_MAJOR > 5)
|
|
|
|
#include <QAudioSink>
|
|
|
|
#else
|
|
|
|
#include <QAudioOutput>
|
|
|
|
typedef QAudioOutput QAudioSink;
|
|
|
|
typedef QAudioDeviceInfo QAudioDevice;
|
|
|
|
#endif
|
|
|
|
extern "C"{
|
|
|
|
#include <libavformat/avformat.h>
|
|
|
|
#include <libavcodec/avcodec.h>
|
|
|
|
#include <libswresample/swresample.h>
|
|
|
|
#include <libswscale/swscale.h>
|
|
|
|
}
|
|
|
|
|
|
|
|
template<class T>
|
|
|
|
class Queue {
|
|
|
|
public:
|
|
|
|
virtual ~Queue() {
|
|
|
|
delAll();
|
|
|
|
}
|
|
|
|
void add(T *node) {
|
2024-02-21 18:08:50 +08:00
|
|
|
if(node==0) {
|
2023-04-18 14:14:46 +08:00
|
|
|
throw std::invalid_argument("node is null");
|
|
|
|
return;
|
|
|
|
}
|
2024-02-21 18:08:50 +08:00
|
|
|
if(last) last->next = node;
|
2023-04-18 14:14:46 +08:00
|
|
|
else first = node;
|
|
|
|
last = node;
|
|
|
|
size++;
|
|
|
|
}
|
|
|
|
void remove() {
|
2024-02-21 18:08:50 +08:00
|
|
|
if(first==0) return;
|
|
|
|
if(first==last) first = last = 0;
|
2023-04-18 14:14:46 +08:00
|
|
|
else first = first->next;
|
|
|
|
size--;
|
|
|
|
}
|
|
|
|
T *poll() {
|
|
|
|
auto node = first;
|
|
|
|
remove();
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
T *delGetNext() {
|
|
|
|
delete poll();
|
|
|
|
return first;
|
|
|
|
}
|
|
|
|
T *removeGetNext() {
|
|
|
|
remove();
|
|
|
|
return first;
|
|
|
|
}
|
|
|
|
void delAll() {
|
2024-02-21 18:08:50 +08:00
|
|
|
while(first) delete poll();
|
2023-04-18 14:14:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
T *volatile first{0};
|
|
|
|
T *volatile last{0};
|
|
|
|
volatile int size{0};
|
|
|
|
};
|
|
|
|
|
|
|
|
template<class T>
|
|
|
|
class ConQueue : public Queue<T> {
|
|
|
|
public:
|
|
|
|
virtual ~ConQueue() {
|
|
|
|
std::lock_guard<std::mutex> lock(mtx);
|
|
|
|
this->delAll();
|
|
|
|
}
|
|
|
|
void lockAddNtf(T *node) {
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(mtx);
|
|
|
|
this->add(node);
|
|
|
|
}
|
|
|
|
cv.notify_one();
|
|
|
|
}
|
|
|
|
void lockRemove() {
|
|
|
|
std::lock_guard<std::mutex> lock(mtx);
|
|
|
|
this->remove();
|
|
|
|
}
|
|
|
|
T *lockPoll() {
|
|
|
|
std::lock_guard<std::mutex> lock(mtx);
|
|
|
|
return this->poll();
|
|
|
|
}
|
|
|
|
T *lockGet() {
|
|
|
|
std::lock_guard<std::mutex> lock(mtx);
|
|
|
|
return this->first;
|
|
|
|
}
|
|
|
|
T *lockDelGetNext() {
|
|
|
|
std::lock_guard<std::mutex> lock(mtx);
|
|
|
|
return this->delGetNext();
|
|
|
|
}
|
|
|
|
T *lockRemoveGetNext() {
|
|
|
|
std::lock_guard<std::mutex> lock(mtx);
|
|
|
|
return this->removeGetNext();
|
|
|
|
}
|
|
|
|
void lockDelAll() {
|
|
|
|
std::lock_guard<std::mutex> lock(mtx);
|
|
|
|
this->delAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
T *take(int ms = 0) {
|
|
|
|
std::unique_lock<std::mutex> lock(mtx);
|
|
|
|
if(ms==0) while(this->first==nullptr) cv.wait(lock);
|
|
|
|
else if(this->first==nullptr) cv.wait_for(lock, std::chrono::milliseconds(ms));
|
|
|
|
return this->poll();
|
|
|
|
}
|
|
|
|
std::mutex mtx;
|
|
|
|
std::condition_variable cv;
|
|
|
|
};
|
|
|
|
struct Act {
|
|
|
|
qint64 arg;
|
|
|
|
Act *next;
|
|
|
|
char act;
|
|
|
|
};
|
|
|
|
struct Packet {
|
|
|
|
AVPacket *pkt;
|
|
|
|
Packet *next;
|
|
|
|
uchar act;
|
|
|
|
~Packet() {av_packet_free(&pkt);}
|
|
|
|
};
|
|
|
|
struct Img {
|
|
|
|
QImage img;
|
|
|
|
qint64 st;
|
|
|
|
qint64 et;
|
|
|
|
uint loop;
|
|
|
|
Img *next;
|
|
|
|
};
|
|
|
|
struct Pcm {
|
|
|
|
uint8_t *data;
|
|
|
|
int size;
|
|
|
|
qint64 st;
|
|
|
|
qint64 et;
|
|
|
|
uint loop;
|
|
|
|
Pcm *next;
|
|
|
|
~Pcm() {if(data!=nullptr) delete [] data;}
|
|
|
|
};
|
|
|
|
class Type {
|
|
|
|
public:
|
|
|
|
inline bool isNull(){return idx <= -1;}
|
|
|
|
int idx{-1};
|
|
|
|
int time_num{0}, time_den{0};
|
|
|
|
qint64 start{0};
|
|
|
|
AVCodecContext *ctx{0};
|
|
|
|
std::thread *thd{0};
|
|
|
|
std::atomic<bool> stop{false};
|
|
|
|
AVFrame *frm{av_frame_alloc()};
|
|
|
|
ConQueue<Packet> pkts;
|
|
|
|
uint deLoop{0};
|
|
|
|
~Type() {
|
|
|
|
av_frame_free(&frm);
|
|
|
|
avcodec_free_context(&ctx);
|
|
|
|
}
|
|
|
|
};
|
2024-08-13 19:47:06 +08:00
|
|
|
class FFPlayer;
|
2023-04-18 14:14:46 +08:00
|
|
|
class AVFmt {
|
|
|
|
friend class FFPlayer;
|
|
|
|
public:
|
|
|
|
static const char Run{0}, Pause{1}, Quit{2};
|
|
|
|
AVFmt(QByteArray url);
|
|
|
|
~AVFmt();
|
|
|
|
void mainRead();
|
|
|
|
void mainViDecode();
|
|
|
|
void mainAuDecode();
|
|
|
|
bool isLoop{true};
|
|
|
|
protected:
|
|
|
|
void checkAndUpd(FFPlayer *, qint64);
|
|
|
|
void playAudio(Pcm *, FFPlayer *, qint64, qint64 lmt = INT64_MAX);
|
|
|
|
void whiteAudio(Pcm *, FFPlayer *, bool, qint64 cur, qint64 lmt = INT64_MAX);
|
|
|
|
QString errStr(int err) {
|
|
|
|
if(av_strerror(err, errbuf, AV_ERROR_MAX_STRING_SIZE)==0) return QString::fromUtf8(errbuf);
|
|
|
|
return QString::number(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString err;
|
|
|
|
char errbuf[AV_ERROR_MAX_STRING_SIZE]{};
|
|
|
|
|
|
|
|
AVFormatContext *fmt_ctx{0};
|
|
|
|
std::thread *read_thd{0};
|
|
|
|
qint64 start{0};
|
|
|
|
Type vi, au;
|
|
|
|
qint64 frmDur{0};
|
|
|
|
qint64 viLmt{0};
|
|
|
|
|
|
|
|
SwsContext *sws_ctx{0};
|
|
|
|
int dstStride[4]{0};
|
|
|
|
SwrContext *swr_ctx{0};
|
|
|
|
AVChannelLayout out_ch_layout
|
|
|
|
#if(QT_VERSION_MAJOR > 5)
|
|
|
|
AV_CHANNEL_LAYOUT_STEREO
|
|
|
|
#endif
|
|
|
|
;
|
|
|
|
int sample_rate{0}, pcm_sample_size{0};
|
|
|
|
QAudioSink *sink{0};
|
|
|
|
QIODevice *sinkWriter{0};
|
|
|
|
int buf_size{0};
|
|
|
|
|
|
|
|
ConQueue<Act> acts;
|
|
|
|
AVPacket *packet{av_packet_alloc()};
|
|
|
|
ConQueue<Img> vi_frms;
|
|
|
|
ConQueue<Pcm> au_frms;
|
|
|
|
qint64 start_epoch{0};
|
|
|
|
uint playLoop{0};
|
|
|
|
};
|
|
|
|
|
|
|
|
class FFPlayer : public QObject {
|
|
|
|
friend class AVFmt;
|
|
|
|
Q_OBJECT
|
|
|
|
public:
|
|
|
|
static const char Playing{0}, Paused{1}, Closed{2};
|
|
|
|
|
|
|
|
explicit FFPlayer();
|
|
|
|
~FFPlayer();
|
|
|
|
|
|
|
|
inline void open(QByteArray url) {
|
|
|
|
status = Paused;
|
|
|
|
emit emOpen(url);
|
|
|
|
}
|
|
|
|
inline void close() {emit emClose();}
|
|
|
|
inline void play() {emit emPlay();}
|
|
|
|
inline void pause() {emit emPause();}
|
|
|
|
inline void stop() {emit emStop();}
|
|
|
|
inline void setVol(qreal vol) {emit emVol(vol);}
|
|
|
|
inline void quit() {emit emQuit();}
|
|
|
|
|
|
|
|
std::atomic<char> status{Closed};
|
|
|
|
QImage img;
|
|
|
|
qint64 dur{0};
|
|
|
|
int viSize{0}, auSize{0};
|
|
|
|
qint64 viCurTime{0}, auCurTime{0};
|
|
|
|
public slots:
|
|
|
|
void updFrame();
|
|
|
|
signals:
|
|
|
|
void emOpen(QByteArray url);
|
|
|
|
void emClose();
|
|
|
|
void emPlay();
|
|
|
|
void emPause();
|
|
|
|
void emStop();
|
|
|
|
void emVol(qreal vol);
|
|
|
|
void emUpd(QImage);
|
|
|
|
void emError(QString err);
|
|
|
|
void emQuit();
|
|
|
|
protected slots:
|
|
|
|
void onOpen(QByteArray url);
|
|
|
|
void onClose();
|
|
|
|
void onPlay();
|
|
|
|
void onPause();
|
|
|
|
void onStop();
|
|
|
|
void onVol(qreal vol);
|
|
|
|
void onQuit();
|
|
|
|
protected:
|
|
|
|
void timerEvent(QTimerEvent *event) override;
|
|
|
|
void startAudioDaemon() {
|
|
|
|
if(audioDaemonTimerId==0 && ! ctx->au.isNull()) audioDaemonTimerId = startTimer(20);
|
|
|
|
}
|
|
|
|
QThread *thread;
|
|
|
|
AVFmt *ctx{0};
|
|
|
|
qint64 last_epoch{0};
|
|
|
|
int audioDaemonTimerId{0};
|
|
|
|
bool hasImg{false};
|
|
|
|
qreal mVol{1.0};
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif // FFPLAYER_H
|