/****************************************************************************** VideoCapture.cpp: description Copyright (C) 2012-2016 Wang Bin * 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 "VideoCapture.h" #include #include #include #include #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include #else #include #endif #include "Logger.h" #define CAPTURE_DEBUG 0 namespace FAV { Q_GLOBAL_STATIC(QThreadPool, videoCaptureThreadPool) static bool app_is_dieing = false; // TODO: cancel if qapp is quit class CaptureTask : public QRunnable { public: CaptureTask(VideoCapture* c) : cap(c) , save(true) , original_fmt(false) , quality(-1) , format(QStringLiteral("PNG")) , qfmt(QImage::Format_ARGB32) { setAutoDelete(true); } virtual void run() { #if (CAPTURE_DEBUG) qInfo() << "CaptureTask started"; #endif if (app_is_dieing) { qDebug("app is dieing. cancel capture task %p", this); #if (CAPTURE_DEBUG) qInfo("app is dieing. cancel capture task %p", this); #endif return; } QImage image(frame.toImage()); if (image.isNull()) { qWarning("Failed to convert to QImage"); #if (CAPTURE_DEBUG) qInfo() << "Failed to convert to QImage"; #endif return; } QMetaObject::invokeMethod(cap, "imageCaptured", Q_ARG(QImage, image)); if (!save) { #if (CAPTURE_DEBUG) qInfo() << "No need to save."; #endif return; } bool main_thread = QThread::currentThread() == qApp->thread(); qDebug("capture task running in thread %p [main thread=%d]", QThread::currentThreadId(), main_thread); #if (CAPTURE_DEBUG) qInfo("capture task running in thread %p [main thread=%d]", QThread::currentThreadId(), main_thread); #endif if (!QDir(dir).exists()) { if (!QDir().mkpath(dir)) { qWarning("Failed to create capture dir [%s]", qPrintable(dir)); #if (CAPTURE_DEBUG) qInfo("Failed to create capture dir [%s]", qPrintable(dir)); #endif QMetaObject::invokeMethod(cap, "failed"); return; } } name += QString::number(frame.pts(), 'f', 3); // timestamp() QString path(dir + QStringLiteral("/") + name + QStringLiteral(".")); if (original_fmt) { if (!frame.constBits(0)) { frame = frame.to(frame.format()); } path.append(frame.format().name()); qDebug("Saving capture to %s", qPrintable(path)); QFile file(path); if (!file.open(QIODevice::WriteOnly)) { qWarning("VideoCapture is failed to open file %s", qPrintable(path)); #if (CAPTURE_DEBUG) qInfo("VideoCapture is failed to open file %s", qPrintable(path)); #endif QMetaObject::invokeMethod(cap, "failed"); return; } if (file.write(frame.frameData()) <= 0) { qWarning("VideoCapture is failed to write captured frame with original format"); #if (CAPTURE_DEBUG) qInfo("VideoCapture is failed to write captured frame with original format"); #endif QMetaObject::invokeMethod(cap, "failed"); file.close(); return; } file.close(); #if (CAPTURE_DEBUG) qInfo("VideoCapture saved done"); #endif QMetaObject::invokeMethod(cap, "saved", Q_ARG(QString, path)); return; } if (image.isNull()) { #if (CAPTURE_DEBUG) qInfo("VideoCapture iamge is null"); #endif return; } path.append(format.toLower()); qDebug("Saving capture to %s", qPrintable(path)); #if (TB5000_INSPECTION_VERSION) // 이미지를 40% 영역으로 CROP int cw = (int)(image.size().width() * 0.4); int ch = (int)(image.size().height() * 0.4); int cxoffset = (image.size().width() - cw) / 2; int cyoffset = (image.size().height() - ch) / 2; QRect crop = QRect(cxoffset,cyoffset,cw,ch); image = image.copy(crop); #endif // TB5000_INSPECTION_VERSION bool ok = image.save(path, format.toLatin1().constData(), quality); if (!ok) { qWarning("Failed to save capture"); #if (CAPTURE_DEBUG) qInfo("Failed to save capture"); #endif QMetaObject::invokeMethod(cap, "failed"); } QMetaObject::invokeMethod(cap, "saved", Q_ARG(QString, path)); #if (CAPTURE_DEBUG) qInfo("VideoCapture saved done"); #endif } VideoCapture *cap; bool save; bool original_fmt; int quality; QString format, dir, name; QImage::Format qfmt; VideoFrame frame; }; VideoCapture::VideoCapture(QObject *parent) : QObject(parent) , async(true) , auto_save(true) , original_fmt(false) , qfmt(QImage::Format_ARGB32) { #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) dir = QDesktopServices::storageLocation(QDesktopServices::PicturesLocation); #else dir = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); #endif if (dir.isEmpty()) dir = qApp->applicationDirPath() + QStringLiteral("/capture"); fmt = QStringLiteral("PNG"); qual = -1; // seems no direct connection is fine too connect(qApp, SIGNAL(aboutToQuit()), SLOT(handleAppQuit()), Qt::DirectConnection); } void VideoCapture::setAsync(bool value) { if (async == value) return; async = value; Q_EMIT asyncChanged(); } bool VideoCapture::isAsync() const { return async; } void VideoCapture::setAutoSave(bool value) { if (auto_save == value) return; auto_save = value; Q_EMIT autoSaveChanged(); } bool VideoCapture::autoSave() const { return auto_save; } void VideoCapture::setOriginalFormat(bool value) { if (original_fmt == value) return; original_fmt = value; Q_EMIT originalFormatChanged(); } bool VideoCapture::isOriginalFormat() const { return original_fmt; } void VideoCapture::handleAppQuit() { app_is_dieing = true; // TODO: how to cancel? since qt5.2, we can use clear() #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) videoCaptureThreadPool()->clear(); #endif videoCaptureThreadPool()->setExpiryTimeout(0); videoCaptureThreadPool()->waitForDone(); } void VideoCapture::capture() { Q_EMIT requested(); } void VideoCapture::start() { Q_EMIT frameAvailable(frame); if (!frame.isValid() || !frame.constBits(0)) { // if frame is always cloned, then size is at least width*height qDebug("Captured frame from hardware decoder surface."); } CaptureTask *task = new CaptureTask(this); // copy properties so the task will not be affect even if VideoCapture properties changed task->save = autoSave(); task->original_fmt = original_fmt; task->quality = qual; task->dir = dir; task->name = name; task->format = fmt; task->qfmt = qfmt; task->frame = frame; //copy here and it's safe in capture thread because start() is called immediatly after setVideoFrame if (isAsync()) { videoCaptureThreadPool()->start(task); } else { task->run(); delete task; } } void VideoCapture::setSaveFormat(const QString &format) { if (format.toLower() == fmt.toLower()) return; fmt = format; Q_EMIT saveFormatChanged(); } QString VideoCapture::saveFormat() const { return fmt; } void VideoCapture::setQuality(int value) { if (qual == value) return; qual = value; Q_EMIT qualityChanged(); } int VideoCapture::quality() const { return qual; } void VideoCapture::setCaptureName(const QString &value) { if (name == value) return; name = value; Q_EMIT captureNameChanged(); } QString VideoCapture::captureName() const { return name; } void VideoCapture::setCaptureDir(const QString &value) { if (dir == value) return; dir = value; Q_EMIT captureDirChanged(); } QString VideoCapture::captureDir() const { return dir; } /* * If the frame is not created for direct rendering, then the frame data is already deep copied, so detach is enough. * TODO: map frame from texture etc. */ void VideoCapture::setVideoFrame(const VideoFrame &frame) { // parameter in ready(FAV::VideoFrame) ensure we can access the frame without lock /* * clone here may block VideoThread. But if not clone here, the frame may be * modified outside and is not safe. */ this->frame = frame.clone(); // TODO: no clone, use detach() } } //namespace FAV