/****************************************************************************** QtAV: Multimedia framework based on Qt and FFmpeg Copyright (C) 2012-2016 Wang Bin * 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 aqueue, vqueue; // TODO: 1 queue if packet.mediaType is enabled AVMuxer muxer; QString format; QVector 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(sender()); d->filters.remove(d->filters.indexOf(f)); if (d->filters.isEmpty()) stopInternal(); } } //namespace FAV #endif // #if !(REMOVE_AV_ENCODER)