【发布时间】:2021-11-08 23:34:14
【问题描述】:
是否有任何 API 可以为我提供 Android 中给定视频文件的关键帧的时间戳列表?
【问题讨论】:
是否有任何 API 可以为我提供 Android 中给定视频文件的关键帧的时间戳列表?
【问题讨论】:
对于一个优雅的解决方案,您似乎需要使用 ffmpeg for android 来实现这一点。其他人这样尝试过:How to get the frame from video file in android;并被建议使用ffmpeg。
我查看了参考手册,但只能找到一种方法来使用 MediaMetadataRetriever() 获取相对于给定时间位置的关键帧本身(没有时间戳),如下所述:http://developer.android.com/reference/android/media/MediaMetadataRetriever.html#extractMetadata%28int%29
如果您不想使用 ffmpeg,可以尝试以下方法:
private ArrayList<int> getKeyFrameTimestamps(String filename) {
ArrayList<int> timeStamps = new ArrayList<int>();
try {
MediaMetadataRetriever mRetriever = new MediaMetadataRetriever();
mRetriever.setDataSource(getDataSource(filename));
mRetriever.getFrameAtTime(i, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
Bitmap lastFame = mRetriever.getFrameAtTime(0, MediaMetadataRetriever.OPTION_NEXT_SYNC);
result.add(0);
for(int time=1; frame ; ++time) {
Bitmap frame = mRetriever.getFrameAtTime(time, MediaMetadataRetriever.OPTION_PREVIOUS_SYNC);
if (frame && !frame.sameAs(lastFrame)) {
result.add(time);
lastFrame = frame;
}
}
} catch (Exception e) { /* TODO: HANDLE THIS */ }
return timeStamps;
}
这当然不是很优雅,但安装 ffmpeg 可能不会有任何麻烦......它可能也很慢(我不知道)。但也许这正是您所需要的。
PS:可以在这里找到适合您问题的 ffmpeg 教程:http://dranger.com/ffmpeg/tutorial07.html
【讨论】:
time += lastdistance;,如果找到关键帧,则使用set lastdistance = lastFrameTime - time;,检查lastFrameTime + lastdistance - 1 是否仍然是最后一帧。如果是,请继续...否则使用“慢搜索”查找下一帧
我相信正确答案是http://code.google.com/p/mp4parser/
isoparser API 可以读取和写入 MP4 文件结构。它是一个处理所谓的盒子的低级工具,但它也可以处理像轨道和电影这样的结构。
不错的 API 和比 ffmpeg 庞然大物快得多的方法
【讨论】:
4.1+ - 比 mp4parser 更快,因为在原生底层完成并且代码更干净
mp4parser 您必须从 getsyncsamples + getsampledurations 计算时间戳,这也很烦人,而且它们与 ffprobe 不匹配。这会得到准确的结果
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(getVideoPath());
int trackindex = MediaExtractorUtil.selectVideoTrack(extractor);
extractor.selectTrack(trackindex);
while (extractor.getSampleTime() != -1) {
long sampleTime = extractor.getSampleTime();
// check not really necessary but JIC
if ((extractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) > 0) {
mKeyframeTimestampsMS.add(sampleTime / 1000);
}
extractor.seekTo(sampleTime + 1, MediaExtractor.SEEK_TO_NEXT_SYNC);
}
【讨论】:
以@ryan gordon's solution 为基础。 seekTo 可能会寻找同一帧,即使指定了 SEEK_TO_NEXT_SYNC。为避免这种情况,您可以在帧开始重复时终止。
例如:
private fun getSyncFrameTimestamps(source: MediaExtractor): List<Long> {
val result = mutableListOf<Long>()
var lastSampleTime = -1L
var sampleTime = source.sampleTime
while (sampleTime >= 0L && sampleTime != lastSampleTime) {
if (source.sampleFlags and MediaExtractor.SAMPLE_FLAG_SYNC != 0) {
result.add(sampleTime)
}
source.seekTo(sampleTime + 1L, MediaExtractor.SEEK_TO_NEXT_SYNC)
lastSampleTime = sampleTime
sampleTime = source.sampleTime
}
return result
}
请注意,在调用此函数之前,source 应设置数据源并选择视频轨道。
【讨论】:
使用MediaExtractor,您可以在任何所需时间获得所有关键(同步)帧时间和/或任何特定关键帧。 +一些更有用的数据。
然后通过MediaMetadataRetriever -> getFrameAtTime就可以得到frame Bitmaps。
这是使用 MediaExtractor 的示例代码:
mediaExtractor = new MediaExtractor() ;
mediaExtractor.setDataSource(mediaPath);
int numTracks = mediaExtractor.getTrackCount();
for (int i = 0; i < numTracks; ++i) {
MediaFormat format = mediaExtractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if ( mime.startsWith("video") ) {
mediaExtractor.selectTrack(i);
}
Log.i( "LOG_Extractor" , "track "+i+" "+mime );
}
currentTime = mediaPlayer.getCurrentPosition();
mediaExtractor.seekTo( (long)currentTime*1000 , MediaExtractor.SEEK_TO_PREVIOUS_SYNC );
previousKeyFrameTimeMilis = mediaExtractor.getSampleTime() /1000 ;
【讨论】: