Krita Source Code Documentation
Loading...
Searching...
No Matches
KisAndroidMediaEncoderRunnable Class Reference

#include <KisAndroidMediaEncoderRunnable.h>

+ Inheritance diagram for KisAndroidMediaEncoderRunnable:

Classes

class  Audio
 
class  Context
 
struct  EncoderImage
 
class  Format
 

Static Public Member Functions

static KisAndroidMediaEncoderRunnablecreate (const KisMediaEncoderWrapperSettings &settings, QObject *parent=nullptr)
 
static void getSupportedFormats (QVector< KisMediaEncoderFormat * > &outSupportedFormats)
 

Protected Member Functions

EncodeResult encode (QString &outErrorMessage) override
 
 KisAndroidMediaEncoderRunnable (const KisMediaEncoderWrapperSettings &settings, QObject *parent)
 
- Protected Member Functions inherited from KisMediaEncoderRunnable
bool isCancelled () const
 
 KisMediaEncoderRunnable (const KisMediaEncoderWrapperSettings &settings, QObject *parent)
 
bool nextFrame (Frame &outFrame)
 
void rewindFrames ()
 
void setOutputFrameNoMultiplier (double outputFrameNoMultiplier)
 
const KisMediaEncoderWrapperSettings settings ()
 

Private Member Functions

int drain (Context &ctx, long long initialTimeout, bool audio)
 
int drainAudio (Context &ctx, long long initialTimeout)
 
int drainVideo (Context &ctx, long long initialTimeout)
 
EncodeResult prepare (Context &ctx, bool audio)
 
EncodeResult prepareAudio (Context &ctx)
 
EncodeResult prepareVideo (Context &ctx)
 

Static Private Member Functions

static void checkFormatSupport (Context &ctx, int formatId, QVector< KisMediaEncoderFormat * > &outSupportedFormats)
 
static bool readAudioInputBuffer (Context &ctx, void *&outBuffer, int &outSize)
 
static bool readEncoderImage (Context &ctx, EncoderImage &outImage)
 
static bool readPlaneBuffer (Context &ctx, int index, uint8_t *&outBuffer)
 
static bool readPlanePixelStride (Context &ctx, int index, int &outPixelStride)
 
static bool readPlaneRowStride (Context &ctx, int index, int &outRowStride)
 

Static Private Attributes

static constexpr int DRAIN_CANCELLED = -3
 
static constexpr int DRAIN_END_OF_STREAM = -1
 
static constexpr int DRAIN_ERROR = -2
 
static constexpr int FORMAT_MP4_AV1 = 2
 
static constexpr int FORMAT_MP4_H264 = 0
 
static constexpr int FORMAT_WEBM_VP8 = 1
 
static constexpr int STATUS_END_OF_STREAM = 2
 
static constexpr int STATUS_ERROR_DRAIN_AUDIO_MUXER_ADD_TRACK = 704
 
static constexpr int STATUS_ERROR_DRAIN_VIDEO_MUXER_ADD_TRACK = 404
 
static constexpr int STATUS_ERROR_START_AUDIO_ENCODER = 108
 
static constexpr int STATUS_ERROR_START_AUDIO_FORMAT = 107
 
static constexpr int STATUS_ERROR_START_VIDEO_ENCODER = 104
 
static constexpr int STATUS_NEEDS_COPY = 3
 
static constexpr int STATUS_OK = 0
 
static constexpr int STATUS_TIMEOUT = 1
 

Additional Inherited Members

- Public Slots inherited from KisMediaEncoderRunnable
void slotHandleCancelRequested ()
 
- Signals inherited from KisMediaEncoderRunnable
void sigCancelled ()
 
void sigCompleted ()
 
void sigFailed (const QString &errorMessage)
 
void sigProgressUpdated (int frameNo)
 
void sigStarted ()
 
- Public Member Functions inherited from KisMediaEncoderRunnable
void run () override
 
- Protected Types inherited from KisMediaEncoderRunnable
enum class  EncodeResult { Completed , Cancelled , Failed }
 

Detailed Description

Definition at line 13 of file KisAndroidMediaEncoderRunnable.h.

Constructor & Destructor Documentation

◆ KisAndroidMediaEncoderRunnable()

KisAndroidMediaEncoderRunnable::KisAndroidMediaEncoderRunnable ( const KisMediaEncoderWrapperSettings & settings,
QObject * parent )
protected

Definition at line 640 of file KisAndroidMediaEncoderRunnable.cpp.

643{
644}
KisMediaEncoderRunnable(const KisMediaEncoderWrapperSettings &settings, QObject *parent)
const KisMediaEncoderWrapperSettings settings()

Member Function Documentation

◆ checkFormatSupport()

void KisAndroidMediaEncoderRunnable::checkFormatSupport ( Context & ctx,
int formatId,
QVector< KisMediaEncoderFormat * > & outSupportedFormats )
staticprivate

Definition at line 1198 of file KisAndroidMediaEncoderRunnable.cpp.

1201{
1202 QVector<Format::Encoder> videoEncoders;
1203 QVector<Format::Encoder> audioEncoders;
1204
1205 const QPair<QVector<Format::Encoder> *, const char *> ps[] = {
1206 {&videoEncoders, "getSupportsForVideoFormat"},
1207 {&audioEncoders, "getSupportsForAudioFormat"},
1208 };
1209
1210 for (const QPair<QVector<Format::Encoder> *, const char *> &p : ps) {
1211 QJniObject supports = QJniObject::callStaticObjectMethod("org/krita/android/VideoEncoder",
1212 p.second,
1213 "(I)Ljava/util/List;",
1214 jint(formatId));
1215 if (ctx.checkObject(QStringLiteral("supports"), supports)) {
1216 return;
1217 }
1218
1219 jint count = supports.callMethod<jint>("size", "()I");
1220 if (ctx.checkException(QStringLiteral("size"))) {
1221 return;
1222 }
1223
1224 for (jint i = 0; i < count; ++i) {
1225 QJniObject entry = supports.callObjectMethod("get", "(I)Ljava/lang/Object;", i);
1226 if (ctx.checkObject(QStringLiteral("entry"), entry)) {
1227 continue;
1228 }
1229
1230 QJniObject name = entry.getObjectField("name", "Ljava/lang/String;");
1231 if (ctx.checkObject(QStringLiteral("name"), name)) {
1232 continue;
1233 }
1234
1235 QString nameString = name.toString();
1236 if (ctx.checkException(QStringLiteral("nameString")) || nameString.isEmpty()) {
1237 continue;
1238 }
1239
1240 bool hardware = entry.getField<jboolean>("hardware");
1241 if (ctx.checkException(QStringLiteral("hardware"))) {
1242 continue;
1243 }
1244
1245 p.first->append({nameString, hardware});
1246 }
1247 }
1248
1249 if (!videoEncoders.isEmpty()) {
1250 outSupportedFormats.append(new Format(formatId, videoEncoders, audioEncoders));
1251 }
1252}
const Params2D p
const char * name(StandardAction id)

References KisAndroidMediaEncoderRunnable::Context::checkException(), KisAndroidMediaEncoderRunnable::Context::checkObject(), and p.

◆ create()

KisAndroidMediaEncoderRunnable * KisAndroidMediaEncoderRunnable::create ( const KisMediaEncoderWrapperSettings & settings,
QObject * parent = nullptr )
static

◆ drain()

int KisAndroidMediaEncoderRunnable::drain ( Context & ctx,
long long initialTimeout,
bool audio )
private

Definition at line 1085 of file KisAndroidMediaEncoderRunnable.cpp.

1086{
1087 const char *methodName = audio ? "drainAudio" : "drainVideo";
1088 QString title = audio ? QStringLiteral("drainAudio") : QStringLiteral("drainVideo");
1089
1090 int count = 0;
1091 long long timeout = initialTimeout;
1092 while (true) {
1093 if (isCancelled()) {
1094 return DRAIN_CANCELLED;
1095 }
1096
1097 int drainResult = int(ctx.encoder().callMethod<jint>(methodName, "(J)I", jlong(timeout)));
1098
1099 if (ctx.checkResult(title, drainResult)) {
1100 return DRAIN_ERROR;
1101
1102 } else if (drainResult == STATUS_TIMEOUT) {
1103 break;
1104
1105 } else if (drainResult == STATUS_END_OF_STREAM) {
1106 return DRAIN_END_OF_STREAM;
1107
1108 } else {
1110 ++count;
1111 }
1112 }
1113 return count;
1114}
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130

References KisAndroidMediaEncoderRunnable::Context::checkResult(), DRAIN_CANCELLED, DRAIN_END_OF_STREAM, DRAIN_ERROR, KisAndroidMediaEncoderRunnable::Context::encoder(), KisMediaEncoderRunnable::isCancelled(), KIS_SAFE_ASSERT_RECOVER_NOOP, STATUS_END_OF_STREAM, STATUS_OK, and STATUS_TIMEOUT.

◆ drainAudio()

int KisAndroidMediaEncoderRunnable::drainAudio ( Context & ctx,
long long initialTimeout )
private

Definition at line 1080 of file KisAndroidMediaEncoderRunnable.cpp.

1081{
1082 return drain(ctx, initialTimeout, true);
1083}
int drain(Context &ctx, long long initialTimeout, bool audio)

References drain().

◆ drainVideo()

int KisAndroidMediaEncoderRunnable::drainVideo ( Context & ctx,
long long initialTimeout )
private

Definition at line 1075 of file KisAndroidMediaEncoderRunnable.cpp.

1076{
1077 return drain(ctx, initialTimeout, false);
1078}

References drain().

◆ encode()

KisMediaEncoderRunnable::EncodeResult KisAndroidMediaEncoderRunnable::encode ( QString & outErrorMessage)
overrideprotectedvirtual

Implements KisMediaEncoderRunnable.

Definition at line 646 of file KisAndroidMediaEncoderRunnable.cpp.

647{
648 Format *format = static_cast<Format *>(settings().format);
649
650 QTemporaryFile tempFile;
651 QString tempFilePath;
652 if (tempFile.open()) {
653 tempFilePath = tempFile.fileName();
654 tempFile.close();
655 } else {
656 warnFile << "Failed to open temporary file:" << tempFile.errorString();
657 // Keep going, we might not actually need a temporary file.
658 }
659
660 Context ctx(&outErrorMessage);
661 int outputWidth = settings().outputSize.width();
662 int outputHeight = settings().outputSize.height();
663
664 // Set up audio if we were given some.
665 std::unique_ptr<Audio> audio;
666 if (!settings().audioFile.isEmpty()) {
667 // We always use these fixed parameters: 16 bit PCM, 2 channels and a
668 // sample rate according to the format. The Android media encoder makes
669 // it appear like you can create audio tracks with other parameters, but
670 // if it doesn't like the parameters it just gives you something else,
671 // which obviously just results in nonsense. We'll go with known good
672 // parameters instead, they'll also play back consistently everywhere.
673 audio = std::make_unique<Audio>(mlt_audio_s16, format->audioSampleRate(), 2);
674 if (!audio->open(ctx, settings())) {
676 }
677
678 switch (audio->pullFrame(ctx)) {
680 break;
682 // There's no frames, which can happen if our animation range start
683 // is beyond the end of the file. Just export without audio then.
684 audio.reset();
685 break;
686 default:
688 }
689 }
690
691 // Set up the encoder.
692 {
693 QJniObject outputPath = QJniObject::fromString(settings().outputFile);
694 if (ctx.checkObject(QStringLiteral("outputPath"), outputPath)) {
696 }
697
698 QJniObject tempPath = QJniObject::fromString(tempFilePath);
699 if (ctx.checkObject(QStringLiteral("tempPath"), tempPath)) {
701 }
702
703 QJniObject videoEncoderName =
704 QJniObject::fromString(format->getVideoEncoderPreference(settings().formatPreferences));
705 ctx.checkException(QStringLiteral("videoEncoderName"));
706
707 jint audioSampleRate;
708 jint audioChannelCount;
709 QJniObject audioEncoderName;
710 QJniObject audioFormatName;
711 if (audio) {
712 audioSampleRate = jint(audio->sampleRate());
713 audioChannelCount = jint(audio->channelCount());
714 audioEncoderName = QJniObject::fromString(format->getAudioEncoderPreference(settings().formatPreferences));
715 ctx.checkException(QStringLiteral("audioEncoderName"));
716 audioFormatName = QJniObject::fromString(audio->formatName());
717 ctx.checkException(QStringLiteral("audioFormatName"));
718 } else {
719 audioSampleRate = jint(0);
720 audioChannelCount = jint(0);
721 }
722
723 ctx.setEncoder(QJniObject(
724 "org/krita/android/VideoEncoder",
725 "(IIIFLjava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;III)V",
726 jint(format->formatId()),
727 jint(outputWidth),
728 jint(outputHeight),
729 jfloat(settings().outputFps),
730 outputPath.object<jstring>(),
731 tempPath.object<jstring>(),
732 videoEncoderName.object<jstring>(),
733 jint(format->getVideoBitratePreference(settings().formatPreferences)),
734 audio ? audioEncoderName.object<jstring>() : nullptr,
735 audio ? audioFormatName.object<jstring>() : nullptr,
736 jint(audioSampleRate),
737 jint(audioChannelCount),
738 jint(format->getAudioBitratePreference(settings().formatPreferences))));
739 if (ctx.checkObject(QStringLiteral("encoder"), ctx.encoder())) {
741 }
742 }
743
744 if (isCancelled()) {
746 }
747
748 // Start the encoding.
749 {
750 QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative",
751 "activity",
752 "()Landroid/app/Activity;");
753 if (ctx.checkObject(QStringLiteral("activity"), activity)) {
755 }
756
757 int startResult =
758 int(ctx.encoder().callMethod<jint>("start", "(Landroid/content/Context;)I", activity.object<jobject>()));
759 if (ctx.checkResult(QStringLiteral("start"), startResult)) {
761 }
762 }
763
764 // Encode the frames.
765 Frame frame;
766 int swsFlags = ctx.getSwsFlags(settings().scaleFilter);
767 while (nextFrame(frame)) {
768 if (isCancelled()) {
770 }
771
772 // Grab the next frame from disk.
773 QImage inputImage;
774 if (!frame.readImage(inputImage)) {
775 // If we don't have audio, we can just skip the frame and hope it's
776 // okay. In timelapses it often is, you're not gonna notice a single
777 // frame missing in the video. However, if we have an animation with
778 // audio, skipping a frame would make a mess and be hard to resync,
779 // so in that case we just give up and bail out.
780 if (audio) {
781 ctx.setInternalErrorMessage(QStringLiteral("failed to read frame %1").arg(frame.path()));
783 } else {
784 continue;
785 }
786 }
787
788 AVPixelFormat inputPixelFormat;
789 if (!ctx.convertFrame(inputImage, inputPixelFormat)) {
790 // Dito to the above, try to skip frames we don't understand, don't
791 // bother when there's audio involved.
792 if (audio) {
793 ctx.setInternalErrorMessage(QStringLiteral("failed to convert frame %1").arg(frame.path()));
795 } else {
796 continue;
797 }
798 }
799
800 int instances = frame.instances();
801 for (int i = 0; i < instances; ++i) {
802 // Grab a buffer from the encoder.
803 EncodeResult prepareVideoResult = prepareVideo(ctx);
804 if (prepareVideoResult != EncodeResult::Completed) {
805 return prepareVideoResult;
806 }
807
808 // Retrieve the buffer layout.
809 EncoderImage encoderImage;
810 if (!readEncoderImage(ctx, encoderImage)) {
812 }
813
814 // Map the buffer layout to a libswscale-befitting arrangement. The
815 // layout may either have the YUV components in separate buffers or it
816 // may have the U and V components combined into a single buffer, where
817 // either U or V can come first. Which one we get depends on hardware.
818 uint8_t *dstBuffers[4] = {nullptr, nullptr, nullptr, nullptr};
819 int dstLinesizes[4] = {0, 0, 0, 0};
820 AVPixelFormat outputPixelFormat;
821 if (encoderImage.pixelStrideU == 1 && encoderImage.pixelStrideV == 1) {
822 // Separate Y, U and V buffers.
823 outputPixelFormat = AV_PIX_FMT_YUV420P;
824 dstBuffers[0] = encoderImage.bufferY;
825 dstBuffers[1] = encoderImage.bufferU;
826 dstBuffers[2] = encoderImage.bufferV;
827 dstLinesizes[0] = encoderImage.rowStrideY;
828 dstLinesizes[1] = encoderImage.rowStrideU;
829 dstLinesizes[2] = encoderImage.rowStrideV;
830
831 } else if (encoderImage.pixelStrideU == 2 && encoderImage.pixelStrideV == 2
832 && encoderImage.bufferU + 1 == encoderImage.bufferV) {
833 // One Y buffer and one combined UV buffer, U comes first.
834 outputPixelFormat = AV_PIX_FMT_NV12;
835 dstBuffers[0] = encoderImage.bufferY;
836 dstBuffers[1] = encoderImage.bufferU;
837 dstLinesizes[0] = encoderImage.rowStrideY;
838 dstLinesizes[1] = encoderImage.rowStrideU;
839
840 } else if (encoderImage.pixelStrideU == 2 && encoderImage.pixelStrideV == 2
841 && encoderImage.bufferV + 1 == encoderImage.bufferU) {
842 // One Y buffer and one combined UV buffer, V comes first.
843 outputPixelFormat = AV_PIX_FMT_NV21;
844 dstBuffers[0] = encoderImage.bufferY;
845 dstBuffers[1] = encoderImage.bufferV;
846 dstLinesizes[0] = encoderImage.rowStrideY;
847 dstLinesizes[1] = encoderImage.rowStrideV;
848
849 } else {
850 ctx.setInternalErrorMessage(QStringLiteral("unknown buffer format u%1/%2 v%3/%4")
851 .arg(quintptr(encoderImage.bufferU), 0, 16)
852 .arg(encoderImage.pixelStrideU)
853 .arg(quintptr(encoderImage.bufferV), 0, 16)
854 .arg(encoderImage.pixelStrideV));
856 }
857
858 SwsContext *swsContext = ctx.getSwsContextFor(inputImage.width(),
859 inputImage.height(),
860 inputPixelFormat,
861 outputWidth,
862 outputHeight,
863 outputPixelFormat,
864 swsFlags);
865 if (!swsContext) {
866 ctx.setInternalErrorMessage(QStringLiteral("sws_getCachedContext"));
868 }
869
870 if (instances == 1) {
871 // Just a single frame, scale it into the native buffer.
872 const uint8_t *srcBuffers[] = {inputImage.bits(), nullptr, nullptr, nullptr};
873 const int srcLinesizes[] = {inputImage.bytesPerLine(), 0, 0, 0};
874 sws_scale(swsContext, srcBuffers, srcLinesizes, 0, inputImage.height(), dstBuffers, dstLinesizes);
875
876 } else {
877 // Repeated frame, scale it into an intermediate buffer, then
878 // copy it over to the native one for each instance.
879 if (i == 0 || ctx.imageFormat() != outputPixelFormat) {
880 if (ctx.imageFormat() != outputPixelFormat) {
881 if (!ctx.allocateImage(outputWidth, outputHeight, outputPixelFormat)) {
882 warnFile << "Encoder changed pixel format from" << int(ctx.imageFormat()) << "to"
883 << int(outputPixelFormat);
885 }
886 }
887
888 const uint8_t *srcBuffers[] = {inputImage.bits(), nullptr, nullptr, nullptr};
889 const int srcLinesizes[] = {inputImage.bytesPerLine(), 0, 0, 0};
890 sws_scale(swsContext,
891 srcBuffers,
892 srcLinesizes,
893 0,
894 inputImage.height(),
895 ctx.imageBuffers(),
896 ctx.imageLinesizes());
897 }
898
899 av_image_copy2(dstBuffers,
900 dstLinesizes,
901 ctx.imageBuffers(),
902 ctx.imageLinesizes(),
903 outputPixelFormat,
904 outputWidth,
905 outputHeight);
906 }
907
908 int commitResult = int(ctx.encoder().callMethod<jint>("commitVideo", "()I"));
909 if (ctx.checkResult(QStringLiteral("commitVideo"), commitResult)) {
911 }
912
913 if (audio && !audio->isFinished()) {
914 int pullFrameResult = audio->pullFrame(ctx);
915 if (pullFrameResult == Audio::PULL_FRAME_OK) {
916 do {
917 EncodeResult prepareAudioResult = prepareAudio(ctx);
918 if (prepareAudioResult != EncodeResult::Completed) {
919 return prepareAudioResult;
920 }
921
922 if (!audio->pushSamples(ctx)) {
924 }
925 } while (audio->isSampleDataRemaining());
926 audio->finishFrame();
927
928 } else if (pullFrameResult == Audio::PULL_FRAME_END_OF_STREAM) {
929 EncodeResult prepareAudioResult = prepareAudio(ctx);
930 if (prepareAudioResult != EncodeResult::Completed) {
931 return prepareAudioResult;
932 }
933
934 if (!audio->finish(ctx)) {
936 }
937
938 } else {
940 }
941 }
942 }
943 }
944
945 if (isCancelled()) {
947 }
948
949 // Finish the encoder streams.
950 {
951 // Need a buffer from the encoder to tell it that it's done.
952 EncodeResult prepareVideoResult = prepareVideo(ctx);
953 if (prepareVideoResult != EncodeResult::Completed) {
954 return prepareVideoResult;
955 }
956
957 // Hand empty buffer back with the end of stream flag set.
958 int finishVideoResult = int(ctx.encoder().callMethod<jint>("finishVideo", "()I"));
959 if (ctx.checkResult(QStringLiteral("finishVideo"), finishVideoResult)) {
961 }
962
963 // Same procedure with audio, but it might already have finished.
964 if (audio && !audio->isFinished()) {
965 EncodeResult prepareAudioResult = prepareAudio(ctx);
966 if (prepareAudioResult != EncodeResult::Completed) {
967 return prepareAudioResult;
968 }
969
970 if (!audio->finish(ctx)) {
972 }
973 }
974 }
975
976 // Drain all remaining frames and samples out of the encoders.
977 bool videoStreamEnded = false;
978 bool audioStreamEnded = !audio;
979 do {
980 if (!videoStreamEnded) {
981 int drainVideoResult = drainVideo(ctx, 1000000LL);
982 if (drainVideoResult == DRAIN_END_OF_STREAM) {
983 videoStreamEnded = true;
984 } else if (drainVideoResult == DRAIN_ERROR) {
986 } else if (drainVideoResult == DRAIN_CANCELLED) {
988 } else {
989 KIS_SAFE_ASSERT_RECOVER_NOOP(drainVideoResult >= 0);
990 }
991 }
992 if (!audioStreamEnded) {
993 int drainAudioResult = drainAudio(ctx, 1000000LL);
994 if (drainAudioResult == DRAIN_END_OF_STREAM) {
995 audioStreamEnded = true;
996 } else if (drainAudioResult == DRAIN_ERROR) {
998 } else if (drainAudioResult == DRAIN_CANCELLED) {
1000 } else {
1001 KIS_SAFE_ASSERT_RECOVER_NOOP(drainAudioResult >= 0);
1002 }
1003 }
1004 } while (!videoStreamEnded || !audioStreamEnded);
1005
1006 // Close the encoder, copy the temporary file to the output file if needed.
1007 {
1008 int closeResult = int(ctx.encoder().callMethod<jint>("close", "()I"));
1009 if (ctx.checkResult(QStringLiteral("close"), closeResult)) {
1010 return EncodeResult::Failed;
1011 }
1012
1013 if (isCancelled()) {
1015 }
1016
1017 if (closeResult == STATUS_NEEDS_COPY) {
1018 QString copyErrorMessage;
1019 if (!KisAndroidUtils::copyFile(tempFilePath, settings().outputFile, &copyErrorMessage)) {
1020 ctx.setInternalErrorMessage(copyErrorMessage);
1021 return EncodeResult::Failed;
1022 }
1023 }
1024 }
1025
1027}
static bool readEncoderImage(Context &ctx, EncoderImage &outImage)
int drainAudio(Context &ctx, long long initialTimeout)
int drainVideo(Context &ctx, long long initialTimeout)
bool nextFrame(Frame &outFrame)
#define warnFile
Definition kis_debug.h:99
bool copyFile(const QString &inputPath, const QString &outputPath, QString *outErrorMessage)

References KisAndroidMediaEncoderRunnable::Context::allocateImage(), KisAndroidMediaEncoderRunnable::Format::audioSampleRate(), KisAndroidMediaEncoderRunnable::EncoderImage::bufferU, KisAndroidMediaEncoderRunnable::EncoderImage::bufferV, KisAndroidMediaEncoderRunnable::EncoderImage::bufferY, KisMediaEncoderRunnable::Cancelled, KisAndroidMediaEncoderRunnable::Context::checkException(), KisAndroidMediaEncoderRunnable::Context::checkObject(), KisAndroidMediaEncoderRunnable::Context::checkResult(), KisMediaEncoderRunnable::Completed, KisLibavEncoderContext::convertFrame(), KisAndroidUtils::copyFile(), DRAIN_CANCELLED, DRAIN_END_OF_STREAM, DRAIN_ERROR, drainAudio(), drainVideo(), KisAndroidMediaEncoderRunnable::Context::encoder(), KisMediaEncoderRunnable::Failed, KisMediaEncoderWrapperSettings::format, KisAndroidMediaEncoderRunnable::Format::formatId(), KisAndroidMediaEncoderRunnable::Format::getAudioBitratePreference(), KisAndroidMediaEncoderRunnable::Format::getAudioEncoderPreference(), KisLibavEncoderContext::getSwsContextFor(), KisLibavEncoderContext::getSwsFlags(), KisAndroidMediaEncoderRunnable::Format::getVideoBitratePreference(), KisAndroidMediaEncoderRunnable::Format::getVideoEncoderPreference(), KisAndroidMediaEncoderRunnable::Context::imageBuffers(), KisAndroidMediaEncoderRunnable::Context::imageFormat(), KisAndroidMediaEncoderRunnable::Context::imageLinesizes(), KisMediaEncoderRunnable::Frame::instances(), KisMediaEncoderRunnable::isCancelled(), KIS_SAFE_ASSERT_RECOVER_NOOP, KisMediaEncoderRunnable::nextFrame(), KisMediaEncoderWrapperSettings::outputSize, KisMediaEncoderRunnable::Frame::path(), KisAndroidMediaEncoderRunnable::EncoderImage::pixelStrideU, KisAndroidMediaEncoderRunnable::EncoderImage::pixelStrideV, prepareAudio(), prepareVideo(), KisAndroidMediaEncoderRunnable::Audio::PULL_FRAME_END_OF_STREAM, KisAndroidMediaEncoderRunnable::Audio::PULL_FRAME_OK, readEncoderImage(), KisMediaEncoderRunnable::Frame::readImage(), KisAndroidMediaEncoderRunnable::EncoderImage::rowStrideU, KisAndroidMediaEncoderRunnable::EncoderImage::rowStrideV, KisAndroidMediaEncoderRunnable::EncoderImage::rowStrideY, KisAndroidMediaEncoderRunnable::Context::setEncoder(), KisLibavEncoderContext::setInternalErrorMessage(), KisMediaEncoderRunnable::settings(), STATUS_NEEDS_COPY, and warnFile.

◆ getSupportedFormats()

void KisAndroidMediaEncoderRunnable::getSupportedFormats ( QVector< KisMediaEncoderFormat * > & outSupportedFormats)
static

Definition at line 631 of file KisAndroidMediaEncoderRunnable.cpp.

632{
633 Context ctx;
634 int formatIds[] = {FORMAT_MP4_H264, FORMAT_WEBM_VP8, FORMAT_MP4_AV1};
635 for (int formatId : formatIds) {
636 checkFormatSupport(ctx, formatId, outSupportedFormats);
637 }
638}
static void checkFormatSupport(Context &ctx, int formatId, QVector< KisMediaEncoderFormat * > &outSupportedFormats)

References checkFormatSupport(), FORMAT_MP4_AV1, FORMAT_MP4_H264, and FORMAT_WEBM_VP8.

◆ prepare()

KisMediaEncoderRunnable::EncodeResult KisAndroidMediaEncoderRunnable::prepare ( Context & ctx,
bool audio )
private

Definition at line 1039 of file KisAndroidMediaEncoderRunnable.cpp.

1040{
1041 const char *methodName = audio ? "prepareAudio" : "prepareVideo";
1042 QString title = audio ? QStringLiteral("prepareAudio") : QStringLiteral("prepareVideo");
1043
1044 while (true) {
1045 if (isCancelled()) {
1047 }
1048
1049 int prepareResult = int(ctx.encoder().callMethod<jint>(methodName, "(J)I", jlong(100000LL)));
1050 if (ctx.checkResult(title, prepareResult)) {
1051 return EncodeResult::Failed;
1052
1053 } else if (prepareResult == STATUS_TIMEOUT) {
1054 int drainResult = drain(ctx, 0LL, audio);
1055 if (drainResult == DRAIN_END_OF_STREAM) {
1056 ctx.setInternalErrorMessage(QStringLiteral("unexpected end of %1 stream")
1057 .arg(audio ? QStringLiteral("audio") : QStringLiteral("video")));
1058 return EncodeResult::Failed;
1059 } else if (drainResult == DRAIN_ERROR) {
1060 return EncodeResult::Failed;
1061 } else if (drainResult == DRAIN_CANCELLED) {
1063 } else {
1064 KIS_SAFE_ASSERT_RECOVER_NOOP(drainResult >= 0);
1065 }
1066
1067 } else {
1068 KIS_SAFE_ASSERT_RECOVER_NOOP(prepareResult == STATUS_OK);
1069 break;
1070 }
1071 }
1073}

References KisMediaEncoderRunnable::Cancelled, KisAndroidMediaEncoderRunnable::Context::checkResult(), KisMediaEncoderRunnable::Completed, drain(), DRAIN_CANCELLED, DRAIN_END_OF_STREAM, DRAIN_ERROR, KisAndroidMediaEncoderRunnable::Context::encoder(), KisMediaEncoderRunnable::Failed, KisMediaEncoderRunnable::isCancelled(), KIS_SAFE_ASSERT_RECOVER_NOOP, KisLibavEncoderContext::setInternalErrorMessage(), STATUS_OK, and STATUS_TIMEOUT.

◆ prepareAudio()

KisMediaEncoderRunnable::EncodeResult KisAndroidMediaEncoderRunnable::prepareAudio ( Context & ctx)
private

Definition at line 1034 of file KisAndroidMediaEncoderRunnable.cpp.

1035{
1036 return prepare(ctx, true);
1037}
EncodeResult prepare(Context &ctx, bool audio)

References prepare().

◆ prepareVideo()

KisMediaEncoderRunnable::EncodeResult KisAndroidMediaEncoderRunnable::prepareVideo ( Context & ctx)
private

Definition at line 1029 of file KisAndroidMediaEncoderRunnable.cpp.

1030{
1031 return prepare(ctx, false);
1032}

References prepare().

◆ readAudioInputBuffer()

bool KisAndroidMediaEncoderRunnable::readAudioInputBuffer ( Context & ctx,
void *& outBuffer,
int & outSize )
staticprivate

Definition at line 1170 of file KisAndroidMediaEncoderRunnable.cpp.

1171{
1172 QJniObject audioBuffer = ctx.encoder().callObjectMethod("getInputAudioBuffer", "()Ljava/nio/ByteBuffer;", jint());
1173 if (ctx.checkObject(QStringLiteral("audioBuffer"), audioBuffer)) {
1174 return false;
1175 }
1176
1177 void *buffer = ctx.env()->GetDirectBufferAddress(audioBuffer.object<jobject>());
1178 if (!buffer) {
1179 ctx.setInternalErrorMessage(QStringLiteral("null audio buffer"));
1180 return false;
1181 }
1182
1183 jint size = audioBuffer.callMethod<jint>("remaining", "()I");
1184 if (ctx.checkException(QStringLiteral("remaining"))) {
1185 return false;
1186 }
1187
1188 if (size <= 0) {
1189 ctx.setInternalErrorMessage(QStringLiteral("audio buffer with size %1").arg(size));
1190 return false;
1191 }
1192
1193 outBuffer = buffer;
1194 outSize = size;
1195 return true;
1196}
int size(const Forest< T > &forest)
Definition KisForest.h:1232

References KisAndroidMediaEncoderRunnable::Context::checkException(), KisAndroidMediaEncoderRunnable::Context::checkObject(), KisAndroidMediaEncoderRunnable::Context::encoder(), KisAndroidMediaEncoderRunnable::Context::env(), and KisLibavEncoderContext::setInternalErrorMessage().

◆ readEncoderImage()

bool KisAndroidMediaEncoderRunnable::readEncoderImage ( Context & ctx,
EncoderImage & outImage )
staticprivate

Definition at line 1116 of file KisAndroidMediaEncoderRunnable.cpp.

1117{
1118 return readPlaneBuffer(ctx, 0, outImage.bufferY) && readPlaneBuffer(ctx, 1, outImage.bufferU)
1119 && readPlaneBuffer(ctx, 2, outImage.bufferV) && readPlaneRowStride(ctx, 0, outImage.rowStrideY)
1120 && readPlaneRowStride(ctx, 1, outImage.rowStrideU) && readPlaneRowStride(ctx, 2, outImage.rowStrideV)
1121 && readPlanePixelStride(ctx, 1, outImage.pixelStrideU) && readPlanePixelStride(ctx, 2, outImage.pixelStrideV);
1122}
static bool readPlaneBuffer(Context &ctx, int index, uint8_t *&outBuffer)
static bool readPlaneRowStride(Context &ctx, int index, int &outRowStride)
static bool readPlanePixelStride(Context &ctx, int index, int &outPixelStride)

References KisAndroidMediaEncoderRunnable::EncoderImage::bufferU, KisAndroidMediaEncoderRunnable::EncoderImage::bufferV, KisAndroidMediaEncoderRunnable::EncoderImage::bufferY, KisAndroidMediaEncoderRunnable::EncoderImage::pixelStrideU, KisAndroidMediaEncoderRunnable::EncoderImage::pixelStrideV, readPlaneBuffer(), readPlanePixelStride(), readPlaneRowStride(), KisAndroidMediaEncoderRunnable::EncoderImage::rowStrideU, KisAndroidMediaEncoderRunnable::EncoderImage::rowStrideV, and KisAndroidMediaEncoderRunnable::EncoderImage::rowStrideY.

◆ readPlaneBuffer()

bool KisAndroidMediaEncoderRunnable::readPlaneBuffer ( Context & ctx,
int index,
uint8_t *& outBuffer )
staticprivate

Definition at line 1124 of file KisAndroidMediaEncoderRunnable.cpp.

1125{
1126 QJniObject plane =
1127 ctx.encoder().callObjectMethod("getInputImagePlaneBuffer", "(I)Ljava/nio/ByteBuffer;", jint(index));
1128 if (ctx.checkObject(QStringLiteral("plane"), plane)) {
1129 return false;
1130 }
1131
1132 uint8_t *buffer = static_cast<uint8_t *>(ctx.env()->GetDirectBufferAddress(plane.object<jobject>()));
1133 if (!buffer) {
1134 ctx.setInternalErrorMessage(QStringLiteral("null plane buffer %1").arg(index));
1135 return false;
1136 }
1137
1138 outBuffer = buffer;
1139 return true;
1140}

References KisAndroidMediaEncoderRunnable::Context::checkObject(), KisAndroidMediaEncoderRunnable::Context::encoder(), KisAndroidMediaEncoderRunnable::Context::env(), and KisLibavEncoderContext::setInternalErrorMessage().

◆ readPlanePixelStride()

bool KisAndroidMediaEncoderRunnable::readPlanePixelStride ( Context & ctx,
int index,
int & outPixelStride )
staticprivate

Definition at line 1156 of file KisAndroidMediaEncoderRunnable.cpp.

1157{
1158 jint pixelStride = ctx.encoder().callMethod<jint>("getInputImagePlanePixelStride", "(I)I", jint(index));
1159 if (ctx.checkException(QStringLiteral("pixelStride"))) {
1160 return false;
1161 } else if (pixelStride <= 0) {
1162 ctx.setInternalErrorMessage(QStringLiteral("invalid pixel stride %1: %2").arg(index).arg(pixelStride));
1163 return false;
1164 }
1165
1166 outPixelStride = int(pixelStride);
1167 return true;
1168}

References KisAndroidMediaEncoderRunnable::Context::checkException(), KisAndroidMediaEncoderRunnable::Context::encoder(), and KisLibavEncoderContext::setInternalErrorMessage().

◆ readPlaneRowStride()

bool KisAndroidMediaEncoderRunnable::readPlaneRowStride ( Context & ctx,
int index,
int & outRowStride )
staticprivate

Definition at line 1142 of file KisAndroidMediaEncoderRunnable.cpp.

1143{
1144 jint rowStride = ctx.encoder().callMethod<jint>("getInputImagePlaneRowStride", "(I)I", jint(index));
1145 if (ctx.checkException(QStringLiteral("rowStride"))) {
1146 return false;
1147 } else if (rowStride <= 0) {
1148 ctx.setInternalErrorMessage(QStringLiteral("invalid row stride %1: %2").arg(index).arg(rowStride));
1149 return false;
1150 }
1151
1152 outRowStride = int(rowStride);
1153 return true;
1154}

References KisAndroidMediaEncoderRunnable::Context::checkException(), KisAndroidMediaEncoderRunnable::Context::encoder(), and KisLibavEncoderContext::setInternalErrorMessage().

Member Data Documentation

◆ DRAIN_CANCELLED

constexpr int KisAndroidMediaEncoderRunnable::DRAIN_CANCELLED = -3
staticconstexprprivate

Definition at line 29 of file KisAndroidMediaEncoderRunnable.h.

◆ DRAIN_END_OF_STREAM

constexpr int KisAndroidMediaEncoderRunnable::DRAIN_END_OF_STREAM = -1
staticconstexprprivate

Definition at line 27 of file KisAndroidMediaEncoderRunnable.h.

◆ DRAIN_ERROR

constexpr int KisAndroidMediaEncoderRunnable::DRAIN_ERROR = -2
staticconstexprprivate

Definition at line 28 of file KisAndroidMediaEncoderRunnable.h.

◆ FORMAT_MP4_AV1

constexpr int KisAndroidMediaEncoderRunnable::FORMAT_MP4_AV1 = 2
staticconstexprprivate

Definition at line 33 of file KisAndroidMediaEncoderRunnable.h.

◆ FORMAT_MP4_H264

constexpr int KisAndroidMediaEncoderRunnable::FORMAT_MP4_H264 = 0
staticconstexprprivate

Definition at line 31 of file KisAndroidMediaEncoderRunnable.h.

◆ FORMAT_WEBM_VP8

constexpr int KisAndroidMediaEncoderRunnable::FORMAT_WEBM_VP8 = 1
staticconstexprprivate

Definition at line 32 of file KisAndroidMediaEncoderRunnable.h.

◆ STATUS_END_OF_STREAM

constexpr int KisAndroidMediaEncoderRunnable::STATUS_END_OF_STREAM = 2
staticconstexprprivate

Definition at line 37 of file KisAndroidMediaEncoderRunnable.h.

◆ STATUS_ERROR_DRAIN_AUDIO_MUXER_ADD_TRACK

constexpr int KisAndroidMediaEncoderRunnable::STATUS_ERROR_DRAIN_AUDIO_MUXER_ADD_TRACK = 704
staticconstexprprivate

Definition at line 43 of file KisAndroidMediaEncoderRunnable.h.

◆ STATUS_ERROR_DRAIN_VIDEO_MUXER_ADD_TRACK

constexpr int KisAndroidMediaEncoderRunnable::STATUS_ERROR_DRAIN_VIDEO_MUXER_ADD_TRACK = 404
staticconstexprprivate

Definition at line 42 of file KisAndroidMediaEncoderRunnable.h.

◆ STATUS_ERROR_START_AUDIO_ENCODER

constexpr int KisAndroidMediaEncoderRunnable::STATUS_ERROR_START_AUDIO_ENCODER = 108
staticconstexprprivate

Definition at line 41 of file KisAndroidMediaEncoderRunnable.h.

◆ STATUS_ERROR_START_AUDIO_FORMAT

constexpr int KisAndroidMediaEncoderRunnable::STATUS_ERROR_START_AUDIO_FORMAT = 107
staticconstexprprivate

Definition at line 40 of file KisAndroidMediaEncoderRunnable.h.

◆ STATUS_ERROR_START_VIDEO_ENCODER

constexpr int KisAndroidMediaEncoderRunnable::STATUS_ERROR_START_VIDEO_ENCODER = 104
staticconstexprprivate

Definition at line 39 of file KisAndroidMediaEncoderRunnable.h.

◆ STATUS_NEEDS_COPY

constexpr int KisAndroidMediaEncoderRunnable::STATUS_NEEDS_COPY = 3
staticconstexprprivate

Definition at line 38 of file KisAndroidMediaEncoderRunnable.h.

◆ STATUS_OK

constexpr int KisAndroidMediaEncoderRunnable::STATUS_OK = 0
staticconstexprprivate

Definition at line 35 of file KisAndroidMediaEncoderRunnable.h.

◆ STATUS_TIMEOUT

constexpr int KisAndroidMediaEncoderRunnable::STATUS_TIMEOUT = 1
staticconstexprprivate

Definition at line 36 of file KisAndroidMediaEncoderRunnable.h.


The documentation for this class was generated from the following files: