实际上我正在使用 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. 我正在使用 native 方式 (JNI),所以如何做并不重要。Java 或 native ,但我需要找到方法。
编辑1
我制作了某种电影(只有一个 3d 模型),所以我每 32 毫秒更改一次几何形状和纹理。因此,在我看来,使用 mp4 作为 tex 是合理的,因为每个新帧(32 毫秒)都与原始帧非常相似......
现在我为一个模型使用 400 帧。对于几何我使用 .mtr,对于 tex 我使用 .pkm(因为它针对 android 进行了优化),所以我有大约 350 个 .mtr 文件(因为一些文件包含子索引)和 400 个 .pkm 文件......
这就是为什么我要为 tex 使用 mp4 的原因。因为一个 mp4 比 400 .pkm 小得多
EDIT2
请看Edit1
其实我需要知道的是Android的API可以逐帧读取MP4
吗?也许是某种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
视频?
最佳答案
解决方案看起来像 ExtractMpegFramesTest ,其中 MediaCodec 用于从视频帧生成“外部”纹理。在测试代码中,帧被渲染到屏幕外的 pbuffer,然后保存为 PNG。您只需直接渲染它们即可。
这有几个问题:
- MPEG 视频并不是为作为随机存取数据库而设计的。 常见的 GOP(图片组)结构有一个“关键帧”(本质上是 JPEG 图像),后面跟着 14 个增量帧,它们只保留与前一个解码帧的差异。因此,如果您想要 N 帧,则可能必须先解码 N-14 到 N-1 帧。如果您总是向前移动(在纹理上播放电影)或您只存储关键帧(此时您已经发明了一个笨拙的 JPEG 图像数据库),这不是问题。
- 如评论和回答中所述,您可能会得到一些视觉伪像。这些看起来有多糟糕取决于 Material 和压缩率。由于您正在生成帧,因此您可以通过确保在发生重大变化时第一帧始终是关键帧来减少这种情况。
- 与 MediaCodec 接口(interface)的固件在开始生成输出之前可能需要几个帧,即使您从关键帧开始也是如此。在流中四处寻找有延迟成本。参见例如this post . (有没有想过为什么 DVR 有流畅的快进,但不流畅的快退?)
- 通过 SurfaceTexture 的 MediaCodec 帧成为“外部”纹理。这些与普通纹理相比有一些限制——性能可能更差,can't use as color buffer在 FBO 等中。如果您只是以 30fps 的速度每帧渲染一次,这应该无关紧要。 由于上述原因,
- MediaMetadataRetriever 的
getFrameAtTime()
方法性能不理想。虽然您可以通过跳过创建 Bitmap 对象的步骤来节省一些时间,但您不太可能通过自己编写它来获得更好的结果。此外,您传递了OPTION_CLOSEST_SYNC
,但如果您的所有帧都是同步帧(同样是笨拙的 JPEG 图像数据库),那只会产生您想要的结果。您需要使用OPTION_CLOSEST
。
如果您只是想在纹理上播放电影(或者您的问题可以简化为那个),Grafika有一些例子。一个可能相关的是 TextureFromCamera,它在可以缩放和旋转的 GLES 矩形上呈现相机视频流。您可以使用来自其他演示之一的 MP4 播放代码替换相机输入。如果您只向前播放,这会很好用,但如果您想跳过或向后播放,就会遇到麻烦。
您描述的问题听起来与 2D 游戏开发人员处理的问题非常相似。做他们做的事可能是最好的方法。
关于android - 如何从MP4中逐帧获取? (媒体编解码器),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56791589/