На самом деле я работаю с OpenGL и хотел бы поместить все свои текстуры в MP4, чтобы сжать их.
Тогда мне нужно получить его из MP4 на моем Android
Мне нужно как-то декодировать MP4 и получать кадр за кадром по запросу.
Я нашел это MediaCodec
https://developer.android.com/reference/android/media/MediaCodec
а это MediaMetadataRetriever
https://developer.android.com/reference/android/media/MediaMetadataRetriever
Но я не видел подхода, как запрашивать кадр за кадром...
Если есть кто-то, кто работал с MP4, подскажите, пожалуйста, куда идти.
P.S. Я работаю с нативным способом (JNI), так что неважно, как это сделать. Java или нативный, но мне нужно найти способ.
РЕДАКТИРОВАТЬ1
Я делаю какой-то фильм (всего одну 3d-модель), поэтому я меняю свою геометрию, а также текстуры каждые 32 миллисекунды. Итак, мне кажется разумным использовать mp4 для tex, потому что каждый новый кадр (32 миллисекунды) очень похож на предыдущий...
Сейчас использую 400 рамок на одну модель. Для геометрии я использую .mtr, а для tex я использую .pkm (потому что он оптимизирован для Android), поэтому у меня есть около 350 файлов .mtr (потому что некоторые файлы включают субиндекс) и 400 файлов .pkm...
Именно по этой причине я собираюсь использовать mp4 для tex. Потому что один mp4 намного меньше, чем 400 .pkm
РЕДАКТИРОВАТЬ2
Пожалуйста, взгляните на Edit1
На самом деле все, что мне нужно знать, есть ли API Android, который может читать MP4 по кадрам? Может быть, какой-то getNextFrame() метод?
Что-то вроде этого
MP4Player player = new MP4Player(PATH_TO_MY_MP4_FILE);
void readMP4(){
Bitmap b;
while(player.hasNext()){
b = player.getNextFrame();
///.... my code here ...///
}
}
РЕДАКТИРОВАТЬ3
Я сделал такую реализацию на Java
public static void read(@NonNull final Context iC, @NonNull final String iPath)
{
long time;
int fileCount = 0;
//Create a new Media Player
MediaPlayer mp = MediaPlayer.create(iC, Uri.parse(iPath));
time = mp.getDuration() * 1000;
Log.e("TAG", String.format("TIME :: %s", time));
MediaMetadataRetriever mRetriever = new MediaMetadataRetriever();
mRetriever.setDataSource(iPath);
long a = System.nanoTime();
//frame rate 10.03/sec, 1/10.03 = in microseconds 99700
for (int i = 99700 ; i <= time ; i = i + 99700)
{
Bitmap b = mRetriever.getFrameAtTime(i, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
if (b == null)
{
Log.e("TAG", String.format("BITMAP STATE :: %s", "null"));
}
else
{
fileCount++;
}
long curTime = System.nanoTime();
Log.e("TAG", String.format("EXECUTION TIME :: %s", curTime - a));
a = curTime;
}
Log.e("TAG", String.format("COUNT :: %s", fileCount));
}
а тут время выполнения
E/TAG: EXECUTION TIME :: 267982039
E/TAG: EXECUTION TIME :: 222928769
E/TAG: EXECUTION TIME :: 289899461
E/TAG: EXECUTION TIME :: 138265423
E/TAG: EXECUTION TIME :: 127312577
E/TAG: EXECUTION TIME :: 251179654
E/TAG: EXECUTION TIME :: 133996500
E/TAG: EXECUTION TIME :: 289730345
E/TAG: EXECUTION TIME :: 132158270
E/TAG: EXECUTION TIME :: 270951461
E/TAG: EXECUTION TIME :: 116520808
E/TAG: EXECUTION TIME :: 209071269
E/TAG: EXECUTION TIME :: 149697230
E/TAG: EXECUTION TIME :: 138347269
На этот раз в наносекундах == +/- 200 миллисекунд... Это очень медленно... Мне нужно около 30 миллисекунд на кадр.
Итак, я думаю, что этот метод выполняется на процессоре, поэтому вопрос, есть ли метод, который выполняется на графическом процессоре?
РЕДАКТИРОВАТЬ4
Я узнал, что есть MediaCodec класс
https://developer.android.com/reference/android/media/MediaCodec
также я нашел аналогичный вопрос здесь MediaCodec получить все кадры из видео
Я понял, что есть способ читать по байтам, а не по кадрам...
Итак, еще вопрос - есть ли способ читать mp4 видео по кадрам?
Пожалуйста, проверьте: Сообщение в блоге разработчиков Android об игровых текстурах, Поддержка сжатия текстур OpenGL и Сжатие изображений для разработчиков Android — Google I/O 2016 для более нормальной разработки Android
@NicolBolas да, я вижу, но я использую ту же модель. Я отредактировал свой вопрос, пожалуйста, посмотрите
Я понимаю, почему может показаться, что хранить все текстуры в одном файле просто, но это действительно очень плохая идея.
MP4 — это видеокодек, оптимизированный для списка кадров, которые имеют высокий уровень сходства с соседними кадрами, т. е. с движением. Он также оптимизирован для распаковки в последовательном порядке, поэтому использование подхода «произвольного доступа» будет очень неэффективным.
Чтобы дать немного больше деталей, видеокодеки хранят ключевые кадры (один в секунду, но скорость меняется) и дельта-кадры в остальное время. Ключевые кадры сжимаются независимо, как отдельные изображения, но дельта-кадры сохраняются как отличие от одного или нескольких других кадров. Алгоритм предполагает, что эта разница будет минимальной после того, как будет выполнена компенсация движения.
Поэтому, если вы хотите получить доступ к одному дельта-кадру, ваш код должен будет распаковать соседний ключевой кадр и все дельта-кадры, которые соединяют его с нужным вам кадром, это будет намного медленнее, чем просто использование однокадрового JPEG.
Короче говоря, используйте JPEG или PNG для сжатия текстур и добавляйте их все в один файл архива, чтобы он оставался аккуратным.
Не используйте JPEG или PNG. Используйте фактический формат сжатой текстуры, подходящий для графического процессора, например ETC2 или ASTC.
@solidpixel Понятно, но я использую ту же модель. Я отредактировал свой вопрос. Пожалуйста, взгляните
Привет! Не могли бы вы взглянуть на этот вопрос stackoverflow.com/questions/56935547/…
Решение будет выглядеть примерно как ИзвлечьMpegFramesTest, в котором MediaCodec используется для генерации «внешних» текстур из видеокадров. В тестовом коде кадры рендерятся во внеэкранный буфер, а затем сохраняются в формате PNG. Вы бы просто визуализировали их напрямую.
С этим есть несколько проблем:
getFrameAtTime() имеет менее чем желаемую производительность по причинам, указанным выше. Вы вряд ли получите лучшие результаты, написав его самостоятельно, хотя вы можете сэкономить немного времени, пропустив шаг, на котором создается объект Bitmap. Кроме того, вы передали OPTION_CLOSEST_SYNC, но это даст желаемые результаты только в том случае, если все ваши кадры являются кадрами синхронизации (опять же, неуклюжая база данных изображений JPEG). Вам нужно использовать OPTION_CLOSEST.Если вы просто пытаетесь воспроизвести фильм на текстуре (или ваша проблема может быть сведена к этому), в Графика есть несколько примеров. Один из них, который может быть уместным, — это TextureFromCamera, который визуализирует видеопоток камеры в прямоугольнике GLES, который можно масштабировать и вращать. Вы можете заменить вход камеры кодом воспроизведения MP4 из одной из других демонстраций. Это будет хорошо работать, если вы играете только вперед, но если вы хотите пропустить или вернуться назад, у вас возникнут проблемы.
Проблема, которую вы описываете, очень похожа на ту, с которой имеют дело разработчики 2D-игр. Делать то, что они делают, вероятно, лучший подход.
Итак, насколько я вижу, нет возможности получить кадры по индексам? Какой-то decoder.getFrameByIndex(index)? Как я вижу, мне предстоит проделать все эти манипуляции decoder post image to texture surface then openGL draw it then I need to read pixels glReadPixels() and then create Bitmap and just now I can use it... Итак, вопрос - нет ли возможности получить кадр таким образом decoder.setMP4Path(path) decoder.getFrameByIndex(index)? Извините, но я свеж в этом...
MediaMetadataRetriever имеет вызов getImageAtIndex(). Однако он должен выполнять те же шаги, что и все остальные: он должен указать MediaCodec искать ближайший предыдущий ключевой кадр, декодировать дельта-кадры, пока не будет достигнут желаемый, а затем передать данные YUV в принадлежащее приложению буфер. Преимущество SurfaceTexture заключается в том, что GLES может использовать данные декодированного кадра, не перемещаясь по памяти приложения. Обратите внимание, что вы можете более эффективно захватывать несколько последовательных кадров с помощью getFramesAtIndex(), но я предполагаю, что в целом это не поможет из-за требований произвольного доступа.
Я думаю, что я понял концепцию. Еще один вопрос, я использую этот образец cpp gist.github.com/alekseytimoshchenko/…, вопрос в том, как декодер узнает, что нужно перейти к следующему кадру? Его трогает эта строчка AMediaExtractor_advance(d->ex); ?
В вашем образце вся работа выполняется в doCodecWork(). Вы не управляете MediaCodec по одному кадру за раз. Вы передаете ему кучу буферов с входными данными и кучу буферов, которые будут содержать выходные данные, и говорите «вперед». Он уведомляет вас, когда буферы ввода очищаются, а буферы вывода заполняются. MediaCodec работает с модулями NAL H.264, а не с .mp4 (который может мультиплексировать несколько аудио- и видеопотоков); MediaExtractor используется для извлечения нужного элементарного видеопотока из оболочки .mp4. MediaExtractor readSampleData()/advance() делает эту работу.
Посмотрите на этот образец github.com/kueblert/AndroidMediaCodec/blob/master/…, я так понимаю, это именно то, что мне нужно, этот парень получает выходной буфер здесь AMediaCodec_getOutputBuffer(mCodec, status, &bufsize), а затем у меня есть буфер, но я не хочу использовать OpenCV, поэтому я нашел этот способ github.com/latelee/yuv2rgb/blob/мастер/yuv2rgb.c для преобразования YUV в RGB... вот и все, я могу использовать RGB-буфер кадра. Очень интересно, что вы об этом думаете?
а также, как узнать, какой у меня выходной формат? Я это знаю YUV, но по этому методу github.com/latelee/yuv2rgb/blob/мастер/yuv2rgb.c есть несколько YUV форматов , YUV422, YUV420....
Эффективный способ обработки кадров состоит в том, чтобы видеодекодер передавал их графическому процессору в виде внешней текстуры. Копирование их в пространство приложения, преобразование их в RGB в программном обеспечении и загрузка их в графический процессор добавят ненужные накладные расходы. Декодеры MediaCodec могут выбирать свой собственный формат YUV, обычно NV12 или I420, но на некоторых устройствах (особенно с чипсетами Qualcomm) это может быть собственный формат. Вы должны получить объект формата мультимедиа и запросить ключ AMEDIAFORMAT_KEY_COLOR_FORMAT, а затем использовать конвертер YUV для определенного формата. (ваш пример kueblert кажется специфичным для устройства.)
Здравствуйте, fadden, не могли бы вы взглянуть на мой вопрос stackoverflow.com/questions/57182857/… заранее спасибо
@fadden Использование текстуры GL работает для большинства видео, но иногда я просто получаю случайные цвета, как указано в этом вопросе stackoverflow.com/questions/57504443/…, пожалуйста, помогите взглянуть
Да, есть способ извлечь отдельные кадры из видео mp4.
В принципе, вы, кажется, ищете альтернативный способ загрузки текстур, вместо обычного способа GLUtils.texImage2D (который заполняет текстуру из Bitmap).
Во-первых, вы должны учитывать то, что советуют другие, и ожидать визуальных артефактов от сжатия. Но предполагая, что ваши текстуры формируют связанные текстуры (например, взрыв), получение их из видеопотока имеет смысл. Для несвязанных изображений вы получите лучшие результаты, используя JPG или PNG. И обратите внимание, что видео mp4 не имеет альфа-канала, часто используемого в текстурах.
Для задачи вы не можете использовать MediaMetadataRetriever, это не даст вам необходимой точности для извлечения всех кадров.
Вам придется работать с классами Медиакодек и MediaExtractor. Подробная документация Android для MediaCodec.
На самом деле вам нужно реализовать своего рода настраиваемый видеоплеер и добавить одну ключевую функцию: шаг кадра.
Близким к этому является Android Проигрыватель медиа, который является полноценным проигрывателем, но 1) не имеет покадрового шага и 2) имеет довольно закрытый исходный код, поскольку он реализован множеством собственных библиотек C++, которые невозможно расширить и трудно изучить.
Я советую это с опытом создания покадрового видеоплеера, и я сделал это, приняв MediaPlayer-Extended, который написан на простой java (без собственного кода), поэтому вы можете включить это в свой проект и добавить функцию, которая вам нужна . Он работает с Android MediaCodec и MediaExtractor.
Где-то в классе MediaPlayer вы бы добавили функцию для frameStep и добавили еще один сигнал + функцию в PlaybackThread для декодирования только одного следующего кадра (в режиме паузы). Однако реализация этого будет зависеть от вас. Результатом будет то, что вы позволите декодеру получить и обработать один кадр, использовать кадр, а затем повторить со следующим кадром. Я так делал, поэтому знаю, что этот подход работает.
Другая половина задачи – получение результата. Видеоплеер (с MediaCodec) выводит кадры в файл Surface. Ваша задача будет получить пиксели.
Я знаю, как читать растровое изображение RGB с такой поверхности: вам нужно создать OpenGL Pbuffer EGLSurface, позволить MediaCodec выполнить рендеринг в эту поверхность (Android SurfaceTexture), а затем прочитать пиксели с этой поверхности. Это еще одна нетривиальная задача, вам нужно создать шейдер для рендеринга текстуры EOS (поверхности) и использовать GLES20.glReadPixels для получения пикселей RGB в ByteBuffer. Затем вы загрузите эти растровые изображения RGB в свои текстуры.
Однако, поскольку вы хотите загрузить текстуры, вы можете найти оптимизированный способ рендеринга видеокадра непосредственно в ваши текстуры и избежать перемещения пикселей.
Надеюсь, это поможет, и удачи в реализации.
Посмотрите на этот пример github.com/kueblert/AndroidMediaCodec/blob/master/…, как я понимаю, это именно то, что мне нужно, этот парень получает выходной буфер здесь AMediaCodec_getOutputBuffer(mCodec, status, &bufsize), а затем у меня есть буфер, но Я не хочу использовать OpenCV, поэтому я нашел этот способ github.com/latelee/yuv2rgb/blob/master/yuv2rgb.c для преобразования YUV в RGB... вот и все, я могу использовать буфер кадра RGB . Очень интересно, что вы об этом думаете?
Да, вы в правильном направлении. Обратите внимание, что вы находитесь в C++/родном мире, а я в основном в Java/Kotlin. Так что у меня нет опыта использования медиа из C++. Однако вот что нужно изучить: developer.android.com/ndk/guides/stable_apis содержит собственные API-интерфейсы, вас может заинтересовать libmediandk (это то, что использует парень в вашей ссылке выше), а также jni-версия SurfaceTexture, API-интерфейсы аппаратного буфера и т. д. Важным шагом также является YUV2RGB, для которого у вас есть подсказки, это простое преобразование, но вам нужно получить доступ к аппаратному буферу из C++, и, возможно, вам нужно понять формат YUV.
На самом деле я хочу опубликовать свою реализацию на текущее время.
Вот h файл
#include <jni.h>
#include <memory>
#include <opencv2/opencv.hpp>
#include "looper.h"
#include "media/NdkMediaCodec.h"
#include "media/NdkMediaExtractor.h"
#ifndef NATIVE_CODEC_NATIVECODECC_H
#define NATIVE_CODEC_NATIVECODECC_H
//Originally took from here https://github.com/googlesamples/android-
ndk/tree/master/native-codec
//Convert took from here
https://github.com/kueblert/AndroidMediaCodec/blob/master/nativecodecvideo.cpp
class NativeCodec
{
public:
NativeCodec() = default;
~NativeCodec() = default;
void DecodeDone();
void Pause();
void Resume();
bool createStreamingMediaPlayer(const std::string &filename);
void setPlayingStreamingMediaPlayer(bool isPlaying);
void shutdown();
void rewindStreamingMediaPlayer();
int getFrameWidth() const
{
return m_frameWidth;
}
int getFrameHeight() const
{
return m_frameHeight;
}
void getNextFrame(std::vector<unsigned char> &imageData);
private:
struct Workerdata
{
AMediaExtractor *ex;
AMediaCodec *codec;
bool sawInputEOS;
bool sawOutputEOS;
bool isPlaying;
bool renderonce;
};
void Seek();
ssize_t m_bufidx = -1;
int m_frameWidth = -1;
int m_frameHeight = -1;
cv::Size m_frameSize;
Workerdata m_data = {nullptr, nullptr, false, false, false, false};
};
#endif //NATIVE_CODEC_NATIVECODECC_H
Здесь cc файл
#include "native_codec.h"
#include <cassert>
#include "native_codec.h"
#include <jni.h>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cerrno>
#include <climits>
#include "util.h"
#include <android/log.h>
#include <string>
#include <chrono>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include <android/log.h>
#include <string>
#include <chrono>
// for native window JNI
#include <android/native_window_jni.h>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
using namespace std;
using namespace std::chrono;
bool NativeCodec::createStreamingMediaPlayer(const std::string &filename)
{
AMediaExtractor *ex = AMediaExtractor_new();
media_status_t err = AMediaExtractor_setDataSource(ex, filename.c_str());;
if (err != AMEDIA_OK)
{
return false;
}
size_t numtracks = AMediaExtractor_getTrackCount(ex);
AMediaCodec *codec = nullptr;
for (int i = 0; i < numtracks; i++)
{
AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
int format_color;
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, &format_color);
bool ok = AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &m_frameWidth);
ok = ok && AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT,
&m_frameHeight);
if (ok)
{
m_frameSize = cv::Size(m_frameWidth, m_frameHeight);
} else
{
//Asking format for frame width / height failed.
}
const char *mime;
if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime))
{
return false;
} else if (!strncmp(mime, "video/", 6))
{
// Omitting most error handling for clarity.
// Production code should check for errors.
AMediaExtractor_selectTrack(ex, i);
codec = AMediaCodec_createDecoderByType(mime);
AMediaCodec_configure(codec, format, nullptr, nullptr, 0);
m_data.ex = ex;
m_data.codec = codec;
m_data.sawInputEOS = false;
m_data.sawOutputEOS = false;
m_data.isPlaying = false;
m_data.renderonce = true;
AMediaCodec_start(codec);
}
AMediaFormat_delete(format);
}
return true;
}
void NativeCodec::getNextFrame(std::vector<unsigned char> &imageData)
{
if (!m_data.sawInputEOS)
{
m_bufidx = AMediaCodec_dequeueInputBuffer(m_data.codec, 2000);
if (m_bufidx >= 0)
{
size_t bufsize;
auto buf = AMediaCodec_getInputBuffer(m_data.codec, m_bufidx, &bufsize);
auto sampleSize = AMediaExtractor_readSampleData(m_data.ex, buf, bufsize);
if (sampleSize < 0)
{
sampleSize = 0;
m_data.sawInputEOS = true;
}
auto presentationTimeUs = AMediaExtractor_getSampleTime(m_data.ex);
AMediaCodec_queueInputBuffer(m_data.codec, m_bufidx, 0, sampleSize,
presentationTimeUs,
m_data.sawInputEOS ?
AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
AMediaExtractor_advance(m_data.ex);
}
}
if (!m_data.sawOutputEOS)
{
AMediaCodecBufferInfo info;
auto status = AMediaCodec_dequeueOutputBuffer(m_data.codec, &info, 0);
if (status >= 0)
{
if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)
{
__android_log_print(ANDROID_LOG_ERROR,
"AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM", "AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM :: %s",
//
"output EOS");
m_data.sawOutputEOS = true;
}
if (info.size > 0)
{
// size_t bufsize;
uint8_t *buf = AMediaCodec_getOutputBuffer(m_data.codec,
static_cast<size_t>(status), /*bufsize*/nullptr);
cv::Mat YUVframe(cv::Size(m_frameSize.width, static_cast<int>
(m_frameSize.height * 1.5)), CV_8UC1, buf);
cv::Mat colImg(m_frameSize, CV_8UC3);
cv::cvtColor(YUVframe, colImg, CV_YUV420sp2BGR, 3);
auto dataSize = colImg.rows * colImg.cols * colImg.channels();
imageData.assign(colImg.data, colImg.data + dataSize);
}
AMediaCodec_releaseOutputBuffer(m_data.codec, static_cast<size_t>(status),
info.size != 0);
if (m_data.renderonce)
{
m_data.renderonce = false;
return;
}
} else if (status < 0)
{
getNextFrame(imageData);
} else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED)
{
__android_log_print(ANDROID_LOG_ERROR,
"AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED", "AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED :: %s", //
"output buffers changed");
} else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED)
{
auto format = AMediaCodec_getOutputFormat(m_data.codec);
__android_log_print(ANDROID_LOG_ERROR,
"AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED", "AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED :: %s",
//
AMediaFormat_toString(format));
AMediaFormat_delete(format);
} else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER)
{
__android_log_print(ANDROID_LOG_ERROR, "AMEDIACODEC_INFO_TRY_AGAIN_LATER",
"AMEDIACODEC_INFO_TRY_AGAIN_LATER :: %s", //
"no output buffer right now");
} else
{
__android_log_print(ANDROID_LOG_ERROR, "UNEXPECTED INFO CODE", "UNEXPECTED
INFO CODE :: %zd", //
status);
}
}
}
void NativeCodec::DecodeDone()
{
if (m_data.codec != nullptr)
{
AMediaCodec_stop(m_data.codec);
AMediaCodec_delete(m_data.codec);
AMediaExtractor_delete(m_data.ex);
m_data.sawInputEOS = true;
m_data.sawOutputEOS = true;
}
}
void NativeCodec::Seek()
{
AMediaExtractor_seekTo(m_data.ex, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
AMediaCodec_flush(m_data.codec);
m_data.sawInputEOS = false;
m_data.sawOutputEOS = false;
if (!m_data.isPlaying)
{
m_data.renderonce = true;
}
}
void NativeCodec::Pause()
{
if (m_data.isPlaying)
{
// flush all outstanding codecbuffer messages with a no-op message
m_data.isPlaying = false;
}
}
void NativeCodec::Resume()
{
if (!m_data.isPlaying)
{
m_data.isPlaying = true;
}
}
void NativeCodec::setPlayingStreamingMediaPlayer(bool isPlaying)
{
if (isPlaying)
{
Resume();
} else
{
Pause();
}
}
void NativeCodec::shutdown()
{
m_bufidx = -1;
DecodeDone();
}
void NativeCodec::rewindStreamingMediaPlayer()
{
Seek();
}
Итак, согласно этой реализации для преобразования формата (в моем случае из YUV в BGR) нужно настроить OpenCV, для понимания как это сделать проверьте эти два источника
https://thewikihow.com/video_jN9Bv5LHXMk
https://thewikihow.com/video_0fdIiOqCz3o
А также для пробы оставляю здесь свой CMakeLists.txt файл
#For add OpenCV take a look at this video
#https://thewikihow.com/video_jN9Bv5LHXMk
#https://thewikihow.com/video_0fdIiOqCz3o
#Look at the video than compare with this file and make the same
set(pathToProject
C:/Users/tetavi/Downloads/Buffer/OneMoreArNew/arcore-android-
sdk/samples/hello_ar_c)
set(pathToOpenCv C:/OpenCV-android-sdk)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE VERBOSE MAKEFILE on)
set(CMAKE CXX FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
include_directories(${pathToOpenCv}/sdk/native/jni/include)
# Import the ARCore library.
add_library(arcore SHARED IMPORTED)
set_target_properties(arcore PROPERTIES IMPORTED_LOCATION
${ARCORE_LIBPATH}/${ANDROID_ABI}/libarcore_sdk_c.so
INTERFACE_INCLUDE_DIRECTORIES ${ARCORE_INCLUDE}
)
# Import the glm header file from the NDK.
add_library(glm INTERFACE)
set_target_properties(glm PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES
${ANDROID_NDK}/sources/third_party/vulkan/src/libs/glm
)
# This is the main app library.
add_library(hello_ar_native SHARED
src/main/cpp/background_renderer.cc
src/main/cpp/hello_ar_application.cc
src/main/cpp/jni_interface.cc
src/main/cpp/video_render.cc
src/main/cpp/geometry_loader.cc
src/main/cpp/plane_renderer.cc
src/main/cpp/native_codec.cc
src/main/cpp/point_cloud_renderer.cc
src/main/cpp/frame_manager.cc
src/main/cpp/safe_queue.cc
src/main/cpp/stb_image.h
src/main/cpp/util.cc)
add_library(lib_opencv SHARED IMPORTED)
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION
${pathToProject}/app/src/main/jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libopencv_java3.so)
target_include_directories(hello_ar_native PRIVATE
src/main/cpp)
target_link_libraries(hello_ar_native $\{log-lib} lib_opencv
android
log
GLESv2
glm
mediandk
arcore)
Применение:
Вам нужно создать потоковый медиаплеер с помощью этого метода
NaviteCodec::createStreamingMediaPlayer(pathToYourMP4file);
а затем просто используйте
NativeCodec::getNextFrame(imageData);
Не стесняйтесь спрашивать
"Я хотел бы поместить все свои текстуры в MP4, чтобы сжать их." Это приведет к невероятно плохим результатам. Сжатие MPEG основано на подобии кадра к кадру; серия кадров, между которыми мало или совсем нет сходства, приведет к ужасным визуальным результатам.