first commit
This commit is contained in:
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)
|
||||
Reference in New Issue
Block a user