From dde3d63d2e080fc8e8471c494f8070b6337bdb0f Mon Sep 17 00:00:00 2001 From: ArsenArsen Date: Sun, 11 Jun 2017 23:56:06 +0200 Subject: [PATCH] I'd be lying if I said this works but it's close!! --- KShare.pro | 9 ++- formats.cpp | 7 ++ formats.hpp | 2 +- main.cpp | 6 ++ recording/encoders/webmencoder.cpp | 114 +++++++++++++++++++++++++++++ recording/encoders/webmencoder.hpp | 39 ++++++++++ 6 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 recording/encoders/webmencoder.cpp create mode 100644 recording/encoders/webmencoder.hpp diff --git a/KShare.pro b/KShare.pro index 708d05b..99bc4a6 100644 --- a/KShare.pro +++ b/KShare.pro @@ -52,7 +52,8 @@ SOURCES += main.cpp\ recording/recordingpreview.cpp \ recording/recordingcontroller.cpp \ recording/recordingformats.cpp \ - formats.cpp + formats.cpp \ + recording/encoders/webmencoder.cpp HEADERS += mainwindow.hpp \ cropeditor/cropeditor.hpp \ @@ -86,7 +87,11 @@ HEADERS += mainwindow.hpp \ recording/recordingpreview.hpp \ recording/recordingcontroller.hpp \ recording/recordingformats.hpp \ - formats.hpp + formats.hpp \ + avencode/mp4codecwrapper.hpp \ + recording/encoders/webmencoder.hpp + +LIBS += -lavcodec -lavformat -lavutil -lswscale -lavutil mac { SOURCES += $$PWD/platformspecifics/mac/macbackend.cpp diff --git a/formats.cpp b/formats.cpp index d03ea55..c5ace05 100644 --- a/formats.cpp +++ b/formats.cpp @@ -40,6 +40,9 @@ QString formats::recordingFormatName(formats::Recording format) { case Recording::GIF: return "GIF"; break; + case Recording::WebM: + return "WEBM"; + break; default: return QString(); break; @@ -48,6 +51,7 @@ QString formats::recordingFormatName(formats::Recording format) { formats::Recording formats::recordingFormatFromName(QString format) { if (format.toLower() == "gif") return Recording::GIF; + if (format.toLower() == "webm") return Recording::WebM; return Recording::None; } @@ -56,6 +60,9 @@ QString formats::recordingFormatMIME(formats::Recording format) { case Recording::GIF: return "image/gif"; break; + case Recording::WebM: + return "video/webm"; + break; default: return QString(); break; diff --git a/formats.hpp b/formats.hpp index fa22c52..5c86550 100644 --- a/formats.hpp +++ b/formats.hpp @@ -9,7 +9,7 @@ QString normalFormatName(Normal format); Normal normalFormatFromName(QString format); QString normalFormatMIME(Normal format); -enum class Recording { GIF, None }; +enum class Recording { GIF, WebM, None }; QString recordingFormatName(Recording format); Recording recordingFormatFromName(QString format); QString recordingFormatMIME(Recording format); diff --git a/main.cpp b/main.cpp index 5c12ede..d3e26d6 100644 --- a/main.cpp +++ b/main.cpp @@ -6,6 +6,10 @@ #include #include #include +extern "C" { +#include +#include +} #include #include @@ -34,6 +38,8 @@ void handler(QtMsgType type, const QMessageLogContext &, const QString &msg) { } int main(int argc, char *argv[]) { + avcodec_register_all(); + av_register_all(); qInstallMessageHandler(handler); QApplication a(argc, argv); a.setQuitOnLastWindowClosed(false); diff --git a/recording/encoders/webmencoder.cpp b/recording/encoders/webmencoder.cpp new file mode 100644 index 0000000..295b23b --- /dev/null +++ b/recording/encoders/webmencoder.cpp @@ -0,0 +1,114 @@ +#include "webmencoder.hpp" + +#include +extern "C" { +#include +#include +} + +inline void throwAVErr(int ret) { + char err[AV_ERROR_MAX_STRING_SIZE]; + av_make_error_string(err, AV_ERROR_MAX_STRING_SIZE, ret); + std::string newString(err); + throw std::runtime_error(newString); +} + +WebMEncoder::WebMEncoder(QSize res) { + codec = avcodec_find_encoder(CODEC); + if (!codec) throw std::runtime_error("Codec not found"); + c = avcodec_alloc_context3(codec); + if (!c) throw std::runtime_error("Unable to allocate video context"); + c->bit_rate = 400000; + + c->width = res.width() % 2 ? res.width() - 1 : res.width(); + c->height = res.height() % 2 ? res.height() - 1 : res.height(); + size = QSize(c->width, c->height); + int fps = settings::settings().value("recording/framerate", 30).toInt(); + c->time_base = { 1, fps }; + c->framerate = { fps, 1 }; + c->gop_size = 10; + c->max_b_frames = 1; + c->pix_fmt = AV_PIX_FMT_YUV420P; + + int ret = avcodec_open2(c, codec, NULL); + if (ret < 0) throwAVErr(ret); + + frame = av_frame_alloc(); + if (!frame) { + fprintf(stderr, "Could not allocate video frame\n"); + exit(1); + } + frame->format = c->pix_fmt; + frame->width = c->width; + frame->height = c->height; + ret = av_image_alloc(frame->data, frame->linesize, c->width, c->height, c->pix_fmt, 32); + if (!ret) throwAVErr(ret); + + success = true; +} + +void WebMEncoder::setFrameRGB(uint8_t *rgb) { + int lineSize[1] = { 3 * c->width }; + sws_context = sws_getCachedContext(sws_context, c->width, c->height, AV_PIX_FMT_RGB24, c->width, c->height, + AV_PIX_FMT_YUV420P, 0, 0, 0, 0); + sws_scale(sws_context, (const uint8_t *const *)&rgb, lineSize, 0, c->height, frame->data, frame->linesize); +} + +WebMEncoder::~WebMEncoder() { + end(); +} + +bool WebMEncoder::addFrame(QImage frm) { + if (!success) return false; + if (frm.size() != size) frm = frm.copy(QRect(QPoint(0, 0), size)); + if (frm.format() != QImage::Format_RGB888) frm = frm.convertToFormat(QImage::Format_RGB888); + uint8_t *frameData = (uint8_t *)frm.bits(); + setFrameRGB(frameData); + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + int ret = avcodec_send_frame(c, frame); + if (ret < 0) return false; + while (ret >= 0) { + ret = avcodec_receive_packet(c, &pkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + return true; + else if (ret < 0) { + return false; + } + video.append((const char *)pkt.data, pkt.size); + av_packet_unref(&pkt); + } + return true; +} + +bool WebMEncoder::isRunning() { + return success; +} + +QByteArray WebMEncoder::end() { + int ret; + uint8_t endcode[] = { 0, 0, 1, 0xb7 }; + if (!success) { + goto cleanup; + } + ret = avcodec_send_frame(c, frame); + while (ret >= 0) { + ret = avcodec_receive_packet(c, &pkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + break; + else if (ret < 0) + break; + video.append((const char *)pkt.data, pkt.size); + av_packet_unref(&pkt); + } + video.append((const char *)endcode, sizeof(endcode)); +cleanup: + if (c) { + avcodec_close(c); + avcodec_free_context(&c); + } + if (frame) av_frame_free(&frame); + av_packet_unref(&pkt); + return video; +} diff --git a/recording/encoders/webmencoder.hpp b/recording/encoders/webmencoder.hpp new file mode 100644 index 0000000..3e410b4 --- /dev/null +++ b/recording/encoders/webmencoder.hpp @@ -0,0 +1,39 @@ +#ifndef WEBMENCODER_HPP +#define WEBMENCODER_HPP + +#include +#include +#include +extern "C" { +#include +#include +#include +} + +class WebMEncoder { +public: + static constexpr AVCodecID CODEC = AV_CODEC_ID_VP8; + static constexpr formats::Recording FORMAT = formats::Recording::WebM; + + WebMEncoder(QSize res); + ~WebMEncoder(); + bool addFrame(QImage frm); + bool isRunning(); + QByteArray end(); + +private: + AVCodec *codec = NULL; + AVCodecContext *c = NULL; + AVFrame *frame = NULL; + AVPacket pkt; + + bool success = false; + + QByteArray video; + QSize size; + struct SwsContext *sws_context = NULL; + + void setFrameRGB(uint8_t *rgb); +}; + +#endif // WEBMENCODER_HPP