12#include <QTemporaryFile>
14#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
15#include <QJniEnvironment>
18#include <QAndroidJniEnvironment>
19#include <QAndroidJniObject>
20using QJniEnvironment = QAndroidJniEnvironment;
21using QJniObject = QAndroidJniObject;
24#include <klocalizedstring.h>
29#include <libavutil/imgutils.h>
30#include <libavutil/pixfmt.h>
31#include <libswscale/swscale.h>
81 QString
key()
const override
123 QVariantMap preferences;
126 preferences.insert(QStringLiteral(
"encoder"), pw->
encoder());
127 preferences.insert(QStringLiteral(
"bitrate"), pw->
bitrate());
133 QString
encoder = preferences.value(QStringLiteral(
"encoder")).toString();
143 int bitrate = preferences.value(QStringLiteral(
"bitrate")).toInt();
162 return QStringLiteral(
"android:mp4:h264");
164 return QStringLiteral(
"android:webm:vp8");
166 return QStringLiteral(
"android:mp4:av1");
175 return QStringLiteral(
"MP4/H.264");
177 return QStringLiteral(
"WEBM/VP8");
179 return QStringLiteral(
"MP4/AV1");
189 return QStringLiteral(
"mp4");
191 return QStringLiteral(
"webm");
216 explicit Context(QString *outErrorMessage =
nullptr)
248 m_encoder.callMethod<
void>(
"cancel",
"()V");
250 if (
m_env->ExceptionCheck()) {
251 warnFile <<
"JNI exception occurred cancelling encoder";
252 m_env->ExceptionDescribe();
253 m_env->ExceptionClear();
262 }
else if (!obj.isValid()) {
275 warnFile <<
"Start encoder error" << result;
277 *
m_outErrorMessage = i18n(
"Unsupported video parameters, try lowering the video FPS or size");
281 warnFile <<
"Muxer track error" << result;
296 if (
m_env->ExceptionCheck()) {
298 m_env->ExceptionDescribe();
299 m_env->ExceptionClear();
308 AVPixelFormat inputFormat,
311 AVPixelFormat outputFormat,
342 bool allocateImage(
int outputWidth,
int outputHeight, AVPixelFormat outputFormat)
374 return result >= 100;
400 for (
int formatId : formatIds) {
415 QTemporaryFile tempFile;
416 QString tempFilePath;
417 if (tempFile.open()) {
418 tempFilePath = tempFile.fileName();
421 warnFile <<
"Failed to open temporary file:" << tempFile.errorString();
431 QJniObject outputPath = QJniObject::fromString(
settings().outputFile);
432 if (ctx.
checkObject(QStringLiteral(
"outputPath"), outputPath)) {
436 QJniObject tempPath = QJniObject::fromString(tempFilePath);
437 if (ctx.
checkObject(QStringLiteral(
"tempPath"), tempPath)) {
444 ctx.
setEncoder(QJniObject(
"org/krita/android/VideoEncoder",
445 "(IIIFLjava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V",
450 outputPath.object<jstring>(),
451 tempPath.object<jstring>(),
452 encoderName.object<jstring>(),
465 QJniObject activity = QJniObject::callStaticObjectMethod(
"org/qtproject/qt5/android/QtNative",
467 "()Landroid/app/Activity;");
468 if (ctx.
checkObject(QStringLiteral(
"activity"), activity)) {
473 int(ctx.
encoder().callMethod<jint>(
"start",
"(Landroid/content/Context;)I", activity.object<jobject>()));
474 if (ctx.
checkResult(QStringLiteral(
"start"), startResult)) {
488 AVPixelFormat inputPixelFormat;
490 switch (inputImage.format()) {
491 case QImage::Format_RGB32:
492 inputPixelFormat = AV_PIX_FMT_BGR0;
494 case QImage::Format_ARGB32:
495 inputPixelFormat = AV_PIX_FMT_BGRA;
500 inputPixelFormat = AV_PIX_FMT_BGRA;
501 inputImage = inputImage.convertToFormat(QImage::Format_ARGB32);
502 if (inputImage.isNull()) {
503 warnFile <<
"Frame conversion from" << inputImage.format() <<
"failed";
513 for (
int i = 0; i < instances; ++i) {
517 return prepareResult;
530 uint8_t *dstBuffers[4] = {
nullptr,
nullptr,
nullptr,
nullptr};
531 int dstLinesizes[4] = {0, 0, 0, 0};
532 AVPixelFormat outputPixelFormat;
535 outputPixelFormat = AV_PIX_FMT_YUV420P;
536 dstBuffers[0] = encoderImage.
bufferY;
537 dstBuffers[1] = encoderImage.
bufferU;
538 dstBuffers[2] = encoderImage.
bufferV;
546 outputPixelFormat = AV_PIX_FMT_NV12;
547 dstBuffers[0] = encoderImage.
bufferY;
548 dstBuffers[1] = encoderImage.
bufferU;
555 outputPixelFormat = AV_PIX_FMT_NV21;
556 dstBuffers[0] = encoderImage.
bufferY;
557 dstBuffers[1] = encoderImage.
bufferV;
563 .arg(quintptr(encoderImage.
bufferU), 0, 16)
565 .arg(quintptr(encoderImage.
bufferV), 0, 16)
582 if (instances == 1) {
584 const uint8_t *srcBuffers[] = {inputImage.bits(),
nullptr,
nullptr,
nullptr};
585 const int srcLinesizes[] = {inputImage.bytesPerLine(), 0, 0, 0};
586 sws_scale(swsContext, srcBuffers, srcLinesizes, 0, inputImage.height(), dstBuffers, dstLinesizes);
591 if (i == 0 || ctx.
imageFormat() != outputPixelFormat) {
593 if (!ctx.
allocateImage(outputWidth, outputHeight, outputPixelFormat)) {
595 << int(outputPixelFormat);
600 const uint8_t *srcBuffers[] = {inputImage.bits(),
nullptr,
nullptr,
nullptr};
601 const int srcLinesizes[] = {inputImage.bytesPerLine(), 0, 0, 0};
602 sws_scale(swsContext,
611 av_image_copy2(dstBuffers,
620 int commitResult = int(ctx.
encoder().callMethod<jint>(
"commit",
"()I"));
621 if (ctx.
checkResult(QStringLiteral(
"commit"), commitResult)) {
636 return prepareResult;
639 int finishResult = int(ctx.
encoder().callMethod<jint>(
"finish",
"()I"));
640 if (ctx.
checkResult(QStringLiteral(
"finish"), finishResult)) {
647 int drainResult =
drain(ctx, 1000000LL);
661 int closeResult = int(ctx.
encoder().callMethod<jint>(
"close",
"()I"));
662 if (ctx.
checkResult(QStringLiteral(
"close"), closeResult)) {
687 int prepareResult = int(ctx.
encoder().callMethod<jint>(
"prepare",
"(J)I", jlong(100000LL)));
688 if (ctx.
checkResult(QStringLiteral(
"prepare"), prepareResult)) {
692 int drainResult =
drain(ctx, 0LL);
715 long long timeout = initialTimeout;
721 int drainResult = int(ctx.
encoder().callMethod<jint>(
"drain",
"(J)I", jlong(timeout)));
723 if (ctx.
checkResult(QStringLiteral(
"drain"), drainResult)) {
741 const QString &tempPath,
742 const QString &outputPath)
744 QFile tempFile(tempPath);
745 if (!tempFile.open(QIODevice::ReadOnly)) {
747 QStringLiteral(
"failed to open temp file '%1': %2").arg(tempPath).arg(tempFile.errorString()));
751 QFile outputFile(outputPath);
752 if (!outputFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
754 QStringLiteral(
"failed to open output file '%1': %2").arg(outputPath).arg(outputFile.errorString()));
759 buffer.resize(BUFSIZ);
761 qint64 read = tempFile.read(buffer.data(), BUFSIZ);
764 QStringLiteral(
"failed to read from temp file '%1': %2").arg(tempPath).arg(tempFile.errorString()));
766 }
else if (read > 0) {
767 qint64 written = outputFile.write(buffer, read);
772 .arg(outputFile.errorString()));
774 }
else if (written != read) {
776 QStringLiteral(
"tried to write %1 byte(s) to output file '%2', but only wrote %3")
783 if (outputFile.flush()) {
788 .arg(outputFile.errorString()));
806 ctx.
encoder().callObjectMethod(
"getInputImagePlaneBuffer",
"(I)Ljava/nio/ByteBuffer;", jint(index));
807 if (ctx.
checkObject(QStringLiteral(
"plane"), plane)) {
811 uint8_t *buffer =
static_cast<uint8_t *
>(ctx.
env()->GetDirectBufferAddress(plane.object<jobject>()));
823 jint rowStride = ctx.
encoder().callMethod<jint>(
"getInputImagePlaneRowStride",
"(I)I", jint(index));
826 }
else if (rowStride <= 0) {
831 outRowStride = int(rowStride);
837 jint pixelStride = ctx.
encoder().callMethod<jint>(
"getInputImagePlanePixelStride",
"(I)I", jint(index));
840 }
else if (pixelStride <= 0) {
845 outPixelStride = int(pixelStride);
853 QJniObject supports = QJniObject::callStaticObjectMethod(
"org/krita/android/VideoEncoder",
854 "getSupportsForFormat",
855 "(I)Ljava/util/List;",
857 if (ctx.
checkObject(QStringLiteral(
"supports"), supports)) {
861 jint count = supports.callMethod<jint>(
"size",
"()I");
867 for (jint i = 0; i < count; ++i) {
868 QJniObject entry = supports.callObjectMethod(
"get",
"(I)Ljava/lang/Object;", i);
869 if (ctx.
checkObject(QStringLiteral(
"entry"), entry)) {
873 QJniObject name = entry.getObjectField(
"name",
"Ljava/lang/String;");
874 if (ctx.
checkObject(QStringLiteral(
"name"), name)) {
878 QString nameString = name.toString();
879 if (ctx.
checkException(QStringLiteral(
"nameString")) || nameString.isEmpty()) {
883 bool hardware = entry.getField<jboolean>(
"hardware");
888 encoders.append({nameString, hardware});
891 if (!encoders.isEmpty()) {
892 outSupportedFormats.append(
new Format(formatId, encoders));
899 QFormLayout *form =
new QFormLayout(
this);
923 for (
int i = 0; i < count; ++i) {
SwsContext * m_swsContext
bool checkException(const QString &title)
void setInternalErrorMessage(const QString &detail)
bool checkObject(const QString &title, QJniObject &obj)
Context(QString *outErrorMessage=nullptr)
SwsContext * getSwsContextFor(int inputWidth, int inputHeight, AVPixelFormat inputFormat, int outputWidth, int outputHeight, AVPixelFormat outputFormat, int flags)
static bool isErrorResult(int result)
bool checkResult(const QString &title, int result)
void setEncoder(const QJniObject &encoder)
uint8_t ** imageBuffers()
QString * m_outErrorMessage
bool allocateImage(int outputWidth, int outputHeight, AVPixelFormat outputFormat)
AVPixelFormat m_imageFormat
uint8_t * m_imageBuffers[4]
Encoder * encoder(Imf::OutputFile &file, const ExrPaintLayerSaveInfo &info, int width)
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)