【问题标题】:How to get frame by frame from MP4? (MediaCodec)如何从 MP4 逐帧获取? (媒体编解码器)
【发布时间】:2019-11-09 12:11:52
【问题描述】:

实际上,我正在使用 OpenGL,我想将所有纹理放入 MP4 中以压缩它们。

然后我需要从我的 Android 上的 MP4 获取它

我需要以某种方式解码 MP4 并按请求逐帧获取。

我找到了这个MediaCodec

https://developer.android.com/reference/android/media/MediaCodec

还有这个MediaMetadataRetriever

https://developer.android.com/reference/android/media/MediaMetadataRetriever

但我没有看到如何逐帧请求的方法......

如果有人用过 MP4,请告诉我去哪里。

P.S.我正在使用原生方式 (JNI),所以不管怎么做都无所谓。Java 或原生,但我需要找到方法。

EDIT1

我制作了某种电影(只有一个 3d 模型),所以我每 32 毫秒更改一次几何图形和纹理。因此,在我看来,将 mp4 用于 tex 似乎是合理的,因为每个新帧(32 毫秒)都与之前的帧非常相似......

现在我为一个模型使用 400 帧。对于几何,我使用 .mtr,而对于 tex,我使用 .pkm(因为它针对 android 进行了优化),所以我有大约 350 个 .mtr 文件(因为有些文件包括子索引)和 400 个 .pkm 文件......

这就是为什么我要使用 mp4 for tex 的原因。因为一个 mp4 比 400 .pkm 小得多

EDIT2

请看Edit1

实际上我只需要知道有没有可以按帧读取MP4 的Android API?也许某种getNextFrame() 方法?

类似的东西

MP4Player player = new MP4Player(PATH_TO_MY_MP4_FILE);

void readMP4(){
   Bitmap b;

   while(player.hasNext()){
      b = player.getNextFrame();

      ///.... my code here ...///
   }
}

EDIT3

我在 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 毫秒的帧。

所以,我认为这个方法是在 CPU 上执行的,所以请问是否有在 GPU 上执行的方法?

EDIT4

发现有MediaCodec

https://developer.android.com/reference/android/media/MediaCodec

我也在这里找到了类似的问题MediaCodec get all frames from video

我知道有一种方法可以按字节读取,但不能按帧读取...

那么,还有疑问 - 是否有办法逐帧读取 mp4 视频?

【问题讨论】:

标签: android opengl-es mp4 android-mediacodec


【解决方案1】:

我明白为什么将所有纹理放在一个文件中似乎很容易,但这是一个非常糟糕的主意。

MP4 是一种视频编解码器,它针对与相邻帧(即运动)具有高度相似性的帧列表进行了高度优化。它还被优化为按顺序解压缩,因此使用“随机访问”的方法将非常低​​效。

为了提供更多细节,视频编解码器存储关键帧(每秒一个,但速率会发生变化)和其余时间的增量帧。关键帧像单独的图像一样被独立压缩,但增量帧存储为与一个或多个其他帧的差异。该算法假定在执行了运动补偿之后,这种差异将非常小。

因此,如果您想访问单个 delta 帧,您的代码必须解压缩附近的关键帧以及将其连接到所需帧的所有 delta 帧,这将比仅使用单帧 JPEG 慢得多。

简而言之,使用 JPEG 或 PNG 压缩纹理并将它们全部添加到单个存档文件中以保持整洁。

【讨论】:

  • 不要使用 JPEG 或 PNG。使用适合 GPU 的实际压缩纹理格式,例如 ETC2 或 ASTC。
  • @solidpixel 我看到了,但我使用相同的模型。我编辑了我的问题。请看一下
  • 您好!请你看看这个问题stackoverflow.com/questions/56935547/…
【解决方案2】:

该解决方案类似于ExtractMpegFramesTest,其中使用 MediaCodec 从视频帧生成“外部”纹理。在测试代​​码中,帧被渲染到屏幕外 pbuffer,然后保存为 PNG。你可以直接渲染它们。

这有几个问题:

  1. MPEG 视频不能很好地用作随机访问数据库。 一个常见的 GOP(图片组)结构具有一个“关键帧”(本质上是 JPEG 图像),后跟 14 个增量帧,它们仅保存与前一个解码帧的差异。因此,如果您想要帧 N,您可能必须首先解码帧 N-14 到 N-1。如果您总是向前移动(在纹理上播放电影)或只存储关键帧(此时您已经发明了一个笨拙的 JPEG 图像数据库),这不是问题。
  2. 如 cmets and answers 中所述,您可能会看到一些视觉伪影。这些看起来有多糟糕取决于材料和您的压缩率。由于您正在生成帧,因此您可以通过确保在发生重大变化时第一帧始终是关键帧来减少这种情况。
  3. MediaCodec 接口的固件在开始产生输出之前可能需要几个帧,即使您从关键帧开始。在流中四处寻找有延迟成本。参见例如this post。 (有没有想过为什么 DVR 具有平滑的快进,而没有平滑的快退?)
  4. 通过 SurfaceTexture 传递的 MediaCodec 帧成为“外部”纹理。这些与普通纹理相比有一些限制 - 性能可能更差,can't use as color buffer 在 FBO 中等等。如果您只是以 30fps 的速度每帧渲染一次,这无关紧要。
  5. MediaMetadataRetriever 的getFrameAtTime() 方法由于上述原因而具有不理想的性能。通过自己编写它不太可能获得更好的结果,尽管您可以通过跳过创建 Bitmap 对象的步骤来节省一些时间。此外,您传入了OPTION_CLOSEST_SYNC,但只有在所有帧都是同步帧时才会产生您想要的结果(同样,笨拙的JPEG图像数据库)。您需要使用OPTION_CLOSEST

如果您只是想在纹理上播放电影(或者您的问题可以简化为),Grafika 有一些示例。一个可能相关的是 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-&gt;ex); 吗?
  • 在您的示例中,所有工作都在doCodecWork() 中完成。您不会一次驱动 MediaCodec 一帧。你递给它一堆带输入的缓冲区和一堆保存输出的缓冲区,然后说“开始”。当输入缓冲区被清空和输出缓冲区被填满时,它会通知您。 MediaCodec 在 H.264 NAL 单元上运行,而不是 .mp4(可以多路复用多个音频和视频流); MediaExtractor 用于从 .mp4 包装器中提取所需的视频基本流。 MediaExtractor readSampleData()/advance() 正在做这项工作。
  • 看看这个样本github.com/kueblert/AndroidMediaCodec/blob/master/…,据我了解这正是我需要的,这家伙在这里得到了输出缓冲区AMediaCodec_getOutputBuffer(mCodec, status, &amp;bufsize),然后我有缓冲区,但我不想使用OpenCV,所以我找到了github.com/latelee/yuv2rgb/blob/master/yuv2rgb.c这种方式将YUV转换为RGB...就是这样,欢迎使用帧的RGB缓冲区。很有意思,你怎么看?
【解决方案3】:

是的,有办法从 mp4 视频中提取单帧。

原则上,您似乎正在寻找加载纹理的替代方式,通常的方式是GLUtils.texImage2D(从Bitmap 填充纹理)。

首先,您应该考虑其他人的建议,并期望压缩产生的视觉伪影。但是假设您的纹理形成相关的纹理(例如爆炸),从视频流中获取这些纹理是有道理的。对于不相关的图像,您将使用 JPG 或 PNG 获得更好的结果。请注意,mp4 视频没有 alpha 通道,通常用于纹理。

对于该任务,您不能使用MediaMetadataRetriever,它不会为您提供提取所有帧所需的准确性。

您必须使用 MediaCodecMediaExtractor 类。 MediaCodec 的 Android 文档很详细。

实际上你需要实现一种自定义的视频播放器,并添加一个关键功能:帧步。

与此密切相关的是 Android 的 MediaPlayer,它是完整的播放器,但 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++/native 世界中,而我主要在 Java/Kotlin 中。所以我没有使用 C++ 媒体的经验。但是这里是要研究的内容:developer.android.com/ndk/guides/stable_apis 包含本机 API,您会对 libmediandk(上面链接中使用的那个人)以及 SurfaceTexture、Hardware Buffer API 等的 jni 版本感兴趣。重要的一步也是 YUV2RGB,你有线索,这是简单的转换,但你必须从 C++ 访问硬件缓冲区,也许你需要了解 YUV 格式。
【解决方案4】:

实际上我想发布我当前的实现。

这里是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

这里抄送文件

#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://www.youtube.com/watch?v=jN9Bv5LHXMk

https://www.youtube.com/watch?v=0fdIiOqCz3o

对于示例,我将CMakeLists.txt 文件留在这里

#For add OpenCV take a look at this video
#https://www.youtube.com/watch?v=jN9Bv5LHXMk
#https://www.youtube.com/watch?v=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);

欢迎提问

【讨论】:

    猜你喜欢
    • 2016-07-21
    • 1970-01-01
    • 2021-03-17
    • 1970-01-01
    • 1970-01-01
    • 2014-02-18
    • 2016-09-21
    • 2014-05-26
    • 1970-01-01
    相关资源
    最近更新 更多