diff --git a/plugins/dockers/recorder/encoder.cpp b/plugins/dockers/recorder/encoder.cpp index 94a98a86f0..62a316b238 100644 --- a/plugins/dockers/recorder/encoder.cpp +++ b/plugins/dockers/recorder/encoder.cpp @@ -1,119 +1,181 @@ #include "encoder.h" #include "ivfenc.h" #include #include +#include -void Encoder::init(const char* filename, int width, int height) +void Encoder::init(const QString &filename, unsigned int width, unsigned int height) { m_width = width; m_height = height; - - qDebug() << "width " << m_width << " height " << m_height; - - if (!vpx_img_alloc(&m_raw, VPX_IMG_FMT_I420, m_width, m_height, 1)) { - qDebug() << "Failed to allocate image."; - return; - } - - vpx_codec_enc_config_default(vpx_codec_vp9_cx(), &m_cfg, 0); - - m_cfg.g_w = m_width; - m_cfg.g_h = m_height; - m_cfg.g_timebase.num = 1; - m_cfg.g_timebase.den = 4; - // m_cfg.rc_target_bitrate = 500; - - if (m_res = vpx_codec_enc_init(&m_codec, vpx_codec_vp9_cx(), &m_cfg, 0)) { - qDebug() << "Failed to initialize encoder." << m_res; - } - - if (vpx_codec_control_(&m_codec, VP9E_SET_LOSSLESS, 1)) { - qDebug() << "Failed to set lossless." << m_res; - } - m_frameCount = 0; - - m_file = fopen(filename, "wb"); - - ivf_write_file_header(m_file, m_width, m_height, 1, 4, 0x30395056, 0); + m_filename = filename; + m_shouldFinish = new std::atomic(); + start(); } static int vpx_img_plane_width(const vpx_image_t* img, int plane) { if (plane > 0 && img->x_chroma_shift > 0) return (img->d_w + 1) >> img->x_chroma_shift; else return img->d_w; } static int vpx_img_plane_height(const vpx_image_t* img, int plane) { if (plane > 0 && img->y_chroma_shift > 0) return (img->d_h + 1) >> img->y_chroma_shift; else return img->d_h; } static int encode_frame(vpx_codec_ctx_t* codec, vpx_image_t* img, int frame_index, int flags, FILE* file) { int got_pkts = 0; - vpx_codec_iter_t iter = NULL; - const vpx_codec_cx_pkt_t* pkt = NULL; + vpx_codec_iter_t iter = nullptr; + const vpx_codec_cx_pkt_t* pkt = nullptr; const vpx_codec_err_t res = vpx_codec_encode(codec, img, frame_index, 1, flags, VPX_DL_GOOD_QUALITY); if (res != VPX_CODEC_OK) { qDebug() << "Failed to encode frame"; } while ((pkt = vpx_codec_get_cx_data(codec, &iter)) != NULL) { got_pkts = 1; if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { ivf_write_frame_header(file, pkt->data.frame.pts, pkt->data.frame.sz); fwrite(pkt->data.frame.buf, pkt->data.frame.sz, 1, file); } } return got_pkts; } -void Encoder::pushFrame(uint8_t* data[3], uint32_t size) +void Encoder::run() { - qDebug() << "push frame"; + qDebug() << "width " << m_width << " height " << m_height; - int flags = 0; - if (m_keyframeInterval > 0 && m_frameCount % m_keyframeInterval == 0) { - flags |= VPX_EFLAG_FORCE_KF; + if (!vpx_img_alloc(&m_raw, VPX_IMG_FMT_I420, m_width, m_height, 1)) { + qDebug() << "Failed to allocate image."; + return; } - int plane; - - for (plane = 0; plane < 3; ++plane) { - unsigned char* buf = m_raw.planes[plane]; - const int stride = m_raw.stride[plane]; - const int w = vpx_img_plane_width(&m_raw, plane); - const int h = vpx_img_plane_height(&m_raw, plane); - qDebug() << "stride:" << stride << "w:" << w << "h:" << h; - for (int y = 0; y < h; ++y) { - std::copy(data[plane] + w * y, data[plane] + w * y + w, buf); - buf += stride; - } + vpx_codec_enc_config_default(vpx_codec_vp9_cx(), &m_cfg, 0); + + m_cfg.g_w = m_width; + m_cfg.g_h = m_height; + m_cfg.g_timebase.num = 1; + m_cfg.g_timebase.den = 4; + vpx_codec_err_t m_res; + if ((m_res = vpx_codec_enc_init(&m_codec, vpx_codec_vp9_cx(), &m_cfg, 0))) { + qDebug() << "Failed to initialize encoder." << m_res; } - encode_frame(&m_codec, &m_raw, m_frameCount, flags, m_file); - m_frameCount++; -} + if (vpx_codec_control_(&m_codec, VP9E_SET_LOSSLESS, 1)) { + qDebug() << "Failed to set lossless." << m_res; + } + m_frameCount = 0; -void Encoder::finish() -{ - qDebug() << "finishe called"; - while (encode_frame(&m_codec, NULL, -1, 0, m_file)) { + FILE *m_file = fopen(m_filename.toStdString().c_str(), "wb"); + + ivf_write_file_header(m_file, m_width, m_height, 1, 4, 0x30395056, 0); + + for(unsigned int i =0;iload() || (m_empty.available() != m_ringBufferSize)) + { + m_full.acquire(); + + if (m_ringBuffer[m_tail].m_command == RingBufferItem::Command::Finish) + { + break; + } + + libyuv::ABGRToI420(m_ringBuffer[m_tail].m_payload, m_width * 4, yuv[0], m_width, yuv[1], + m_width / 2, yuv[2], m_width / 2, m_width, m_height); + + m_tail = (m_tail + 1) % m_ringBufferSize; + m_empty.release(); + + int flags = 0; + if (m_keyframeInterval > 0 && m_frameCount % m_keyframeInterval == 0) { + flags |= VPX_EFLAG_FORCE_KF; + } + + int plane; + + for (plane = 0; plane < 3; ++plane) { + unsigned char* buf = m_raw.planes[plane]; + const int stride = m_raw.stride[plane]; + const int w = vpx_img_plane_width(&m_raw, plane); + const int h = vpx_img_plane_height(&m_raw, plane); + + if (stride == w) { + std::copy(yuv[plane], yuv[plane] + w * h, buf); + } else { + for (int y = 0; y < h; ++y) { + std::copy(yuv[plane] + w * y, yuv[plane] + w * y + w, buf); + buf += stride; + } + } + } + encode_frame(&m_codec, &m_raw, m_frameCount, flags, m_file); + + m_frameCount++; + qDebug() << "pushed frame" << m_frameCount; } - printf("\n"); + + while (encode_frame(&m_codec, nullptr, -1, 0, m_file)) { + } + + delete[] yuv[0]; + delete[] yuv[1]; + delete[] yuv[2]; + vpx_img_free(&m_raw); if (vpx_codec_destroy(&m_codec)) { qDebug() << "Failed to destroy codec."; } fseek(m_file, 0, SEEK_SET); ivf_write_file_header(m_file, m_width, m_height, 1, 4, 0x30395056, m_frameCount); m_frameCount = 0; fclose(m_file); - m_file = nullptr; - qDebug() << "finished"; } + +void Encoder::pushFrame(uint8_t* data, unsigned int m_width, unsigned int m_height, uint32_t size) +{ + m_empty.acquire(); + + m_ringBuffer[m_head].m_command = RingBufferItem::Command::Payload; + std::copy(data, data+size, m_ringBuffer[m_head].m_payload); + + m_head = (m_head + 1) % m_ringBufferSize; + m_full.release(); +} + +void Encoder::finish() +{ + m_empty.acquire(); + m_ringBuffer[m_head].m_command = RingBufferItem::Command::Finish; + *m_shouldFinish = true; + m_head = (m_head + 1) % m_ringBufferSize; + m_full.release(); + this->wait(10 * 1000); + + for(unsigned int i =0;i * * 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; version 2 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ENCODER_H #define ENCODER_H #include #include #include #include #include +#include +#include +#include +#include -class Encoder +class Encoder : QThread { - int m_width; - int m_height; + Q_OBJECT + class RingBufferItem + { + public: + enum class Command{ + Payload, + Finish + }; + + Command m_command; + uint8_t *m_payload; + }; + + unsigned int m_width; + unsigned int m_height; vpx_codec_ctx m_codec; vpx_image_t m_raw; - vpx_codec_err_t m_res; vpx_codec_enc_cfg_t m_cfg; int m_frameCount = 0; int m_keyframeInterval = 4; - FILE* m_file = nullptr; + static constexpr int m_ringBufferSize = 5; + std::array m_ringBuffer; + int m_head = 0; + int m_tail = 0; + QSemaphore m_full; + QSemaphore m_empty; + QString m_filename; + std::atomic *m_shouldFinish; public: Encoder() { } - void init(const char* filename, int width, int height); + void run() override; + + void init(const QString &filename, unsigned int width, unsigned int height); - void pushFrame(uint8_t* data[3], uint32_t size); + void pushFrame(uint8_t* data, unsigned int m_width, unsigned int m_height, uint32_t size); void finish(); }; #endif diff --git a/plugins/dockers/recorder/recorderdocker.cpp b/plugins/dockers/recorder/recorderdocker.cpp index 63e1cef6bc..7ffb55856d 100644 --- a/plugins/dockers/recorder/recorderdocker.cpp +++ b/plugins/dockers/recorder/recorderdocker.cpp @@ -1,86 +1,86 @@ /* * Copyright (c) 2009 Cyrille Berger * * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "recorderdocker.h" #include #include #include #include #include #include #include "KisViewManager.h" #include "kis_config.h" #include "kis_cursor.h" #include "kis_global.h" #include "kis_types.h" #include "recorderdocker_dock.h" #include K_PLUGIN_FACTORY_WITH_JSON(RecorderDockerPluginFactory, "krita_recorderdocker.json", registerPlugin();) class RecorderDockerDockFactory : public KoDockFactoryBase { public: RecorderDockerDockFactory() { } QString id() const override { return QString("RecorderDocker"); } virtual Qt::DockWidgetArea defaultDockWidgetArea() const { return Qt::RightDockWidgetArea; } QDockWidget* createDockWidget() override { RecorderDockerDock* dockWidget = new RecorderDockerDock(); dockWidget->setObjectName(id()); return dockWidget; } DockPosition defaultDockPosition() const override { return DockMinimized; } private: }; RecorderDockerPlugin::RecorderDockerPlugin(QObject* parent, const QVariantList&) : QObject(parent) { KoDockRegistry::instance()->add(new RecorderDockerDockFactory()); } RecorderDockerPlugin::~RecorderDockerPlugin() { - m_view = 0; + m_view = nullptr; } #include "recorderdocker.moc" diff --git a/plugins/dockers/recorder/recorderdocker_dock.cpp b/plugins/dockers/recorder/recorderdocker_dock.cpp index bc6b9a014a..e8aabf2e45 100644 --- a/plugins/dockers/recorder/recorderdocker_dock.cpp +++ b/plugins/dockers/recorder/recorderdocker_dock.cpp @@ -1,253 +1,244 @@ /* * Copyright (c) 2009 Cyrille Berger * * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "recorderdocker_dock.h" #include #include #include #include #include "encoder.h" #include "kis_canvas2.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_signal_compressor.h" #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include RecorderDockerDock::RecorderDockerDock() : QDockWidget(i18n("Recorder")) - , m_canvas(0) + , m_canvas(nullptr) , m_imageIdleWatcher(1000) , m_recordEnabled(false) , m_recordCounter(0) , m_encoder(nullptr) { QWidget* page = new QWidget(this); m_layout = new QGridLayout(page); m_recordDirectoryLabel = new QLabel(this); m_recordDirectoryLabel->setText("Directory:"); m_layout->addWidget(m_recordDirectoryLabel, 0, 0, 1, 2); m_recordDirectoryLineEdit = new QLineEdit(this); m_recordDirectoryLineEdit->setText(QDir::homePath()); m_recordDirectoryLineEdit->setReadOnly(true); m_layout->addWidget(m_recordDirectoryLineEdit, 1, 0); m_recordDirectoryPushButton = new QPushButton(this); m_recordDirectoryPushButton->setIcon(KisIconUtils::loadIcon("folder")); m_recordDirectoryPushButton->setToolTip(i18n("Record Video")); m_layout->addWidget(m_recordDirectoryPushButton, 1, 1); m_imageNameLabel = new QLabel(this); m_imageNameLabel->setText("Video Name:"); m_layout->addWidget(m_imageNameLabel, 2, 0, 1, 2); m_imageNameLineEdit = new QLineEdit(this); m_imageNameLineEdit->setText("image"); QRegExp rx("[0-9a-zA-z_]+"); QValidator* validator = new QRegExpValidator(rx, this); m_imageNameLineEdit->setValidator(validator); m_layout->addWidget(m_imageNameLineEdit, 3, 0); m_recordToggleButton = new QPushButton(this); m_recordToggleButton->setCheckable(true); m_recordToggleButton->setIcon(KisIconUtils::loadIcon("media-record")); m_recordToggleButton->setToolTip(i18n("Record Video")); m_layout->addWidget(m_recordToggleButton, 3, 1); m_logLabel = new QLabel(this); m_logLabel->setText("Recent Save:"); m_layout->addWidget(m_logLabel, 4, 0, 1, 2); m_logLineEdit = new QLineEdit(this); m_logLineEdit->setReadOnly(true); m_layout->addWidget(m_logLineEdit, 5, 0, 1, 2); m_spacer = new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding); m_layout->addItem(m_spacer, 6, 0, 1, 2); connect(m_recordDirectoryPushButton, SIGNAL(clicked()), this, SLOT(onSelectRecordFolderButtonClicked())); connect(m_recordToggleButton, SIGNAL(toggled(bool)), this, SLOT(onRecordButtonToggled(bool))); setWidget(page); } void RecorderDockerDock::setCanvas(KoCanvasBase* canvas) { if (m_canvas == canvas) return; - setEnabled(canvas != 0); + setEnabled(canvas != nullptr); if (m_canvas) { m_canvas->disconnectCanvasObserver(this); m_canvas->image()->disconnect(this); } m_canvas = dynamic_cast(canvas); if (m_canvas) { m_imageIdleWatcher.setTrackedImage(m_canvas->image()); connect(&m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, &RecorderDockerDock::generateThumbnail); connect(m_canvas->image(), SIGNAL(sigSizeChanged(QPointF, QPointF)), SLOT(startUpdateCanvasProjection())); } } void RecorderDockerDock::startUpdateCanvasProjection() { m_imageIdleWatcher.startCountdown(); } void RecorderDockerDock::unsetCanvas() { setEnabled(false); - m_canvas = 0; + m_canvas = nullptr; } void RecorderDockerDock::onRecordButtonToggled(bool enabled) { bool enabled2 = enabled; enableRecord(enabled2, m_recordDirectoryLineEdit->text() % "/" % m_imageNameLineEdit->text()); if (enabled && !enabled2) { disconnect(m_recordToggleButton, SIGNAL(toggle(bool)), this, SLOT(onRecordButtonToggled(bool))); m_recordToggleButton->setChecked(false); connect(m_recordToggleButton, SIGNAL(toggle(bool)), this, SLOT(onRecordButtonToggled(bool))); } } void RecorderDockerDock::onSelectRecordFolderButtonClicked() { QFileDialog dialog(this); dialog.setFileMode(QFileDialog::DirectoryOnly); QString folder = dialog.getExistingDirectory(this, tr("Select Output Folder"), m_recordDirectoryLineEdit->text(), QFileDialog::ShowDirsOnly); m_recordDirectoryLineEdit->setText(folder); } void RecorderDockerDock::enableRecord(bool& enabled, const QString& path) { m_recordEnabled = enabled; if (m_recordEnabled) { m_recordPath = path; QUrl fileUrl(m_recordPath); QString filename = fileUrl.fileName(); QString dirPath = fileUrl.adjusted(QUrl::RemoveFilename).path(); QDir dir(dirPath); if (!dir.exists()) { if (!dir.mkpath(dirPath)) { enabled = m_recordEnabled = false; return; } } QFileInfoList images = dir.entryInfoList({filename % "_*.vp9"}); QRegularExpression namePattern("^" % filename % "_([0-9]{7}).vp9$"); m_recordCounter = -1; Q_FOREACH (auto info, images) { QRegularExpressionMatch match = namePattern.match(info.fileName()); if (match.hasMatch()) { QString count = match.captured(1); int numCount = count.toInt(); if (m_recordCounter < numCount) { m_recordCounter = numCount; } } } if (m_canvas) { m_recordingCanvas = m_canvas; QString finalFileName = QString(m_recordPath % "_%1.vp9").arg(++m_recordCounter, 7, 10, QChar('0')); m_encoder = new Encoder(); m_encoder->init(finalFileName.toStdString().c_str(), m_canvas->image()->width(), m_canvas->image()->height()); + + size_t size = m_canvas->image()->width() * m_canvas->image()->height() * 4; + m_data = new quint8[size]; startUpdateCanvasProjection(); } else { enabled = m_recordEnabled = false; return; } } else { if (m_encoder) { m_encoder->finish(); + delete m_encoder; m_encoder = nullptr; + if (m_data) + { + delete [] m_data; + m_data = nullptr; + } } } } void RecorderDockerDock::generateThumbnail() { if (m_recordEnabled) { if (m_canvas && m_recordingCanvas == m_canvas) { disconnect(&m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, &RecorderDockerDock::generateThumbnail); if (m_encoder) { KisImageSP image = m_canvas->image(); image->barrierLock(); KisPaintDeviceSP dev = image->projection(); image->unlock(); - quint8* data = nullptr; - size_t size = image->width() * image->height() * dev->pixelSize(); - data = (quint8*)malloc(size); - dev->readBytes(data, 0, 0, image->width(), image->height()); - - uint8_t* yuv[3]; - yuv[0] = new uint8_t[image->width() * image->height()]; - yuv[1] = new uint8_t[image->width() * image->height() / 4]; - yuv[2] = new uint8_t[image->width() * image->height() / 4]; - - libyuv::ABGRToI420(data, image->width() * dev->pixelSize(), yuv[0], image->width(), yuv[1], - image->width() / 2, yuv[2], image->width() / 2, image->width(), image->height()); - - m_encoder->pushFrame(yuv, size); - delete[] yuv[0]; - delete[] yuv[1]; - delete[] yuv[2]; - - free(data); + dev->readBytes(m_data, 0, 0, image->width(), image->height()); + m_encoder->pushFrame(m_data, image->width(), image->height(), image->width()*image->height()*dev->pixelSize()); } connect(&m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, &RecorderDockerDock::generateThumbnail); } } } diff --git a/plugins/dockers/recorder/recorderdocker_dock.h b/plugins/dockers/recorder/recorderdocker_dock.h index 6969d433c9..d655a8c5e1 100644 --- a/plugins/dockers/recorder/recorderdocker_dock.h +++ b/plugins/dockers/recorder/recorderdocker_dock.h @@ -1,80 +1,82 @@ /* * Copyright (c) 2019 Shi Yan * * 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; version 2 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _RECORDER_DOCK_H_ #define _RECORDER_DOCK_H_ #include "encoder.h" #include "kis_idle_watcher.h" #include #include #include #include #include #include #include #include #include +#include class QVBoxLayout; class RecorderWidget; class RecorderDockerDock : public QDockWidget, public KoCanvasObserverBase { Q_OBJECT public: RecorderDockerDock(); QString observerName() override { return "RecorderDockerDock"; } void setCanvas(KoCanvasBase* canvas) override; void unsetCanvas() override; private: QGridLayout* m_layout; QPointer m_recordingCanvas; QString m_recordPath; QPointer m_canvas; QLabel* m_recordDirectoryLabel; QLineEdit* m_recordDirectoryLineEdit; QPushButton* m_recordDirectoryPushButton; QLabel* m_imageNameLabel; QLineEdit* m_imageNameLineEdit; QPushButton* m_recordToggleButton; QSpacerItem* m_spacer; QLabel* m_logLabel; QLineEdit* m_logLineEdit; KisIdleWatcher m_imageIdleWatcher; QMutex m_saveMutex; QMutex m_eventMutex; Encoder* m_encoder; + quint8* m_data = nullptr; bool m_recordEnabled; int m_recordCounter; void enableRecord(bool& enabled, const QString& path); private Q_SLOTS: void onRecordButtonToggled(bool enabled); void onSelectRecordFolderButtonClicked(); void startUpdateCanvasProjection(); void generateThumbnail(); }; #endif