【问题标题】:How to get a list of key frames (sync frames) time stamps for a video file in Android?如何在 Android 中获取视频文件的关键帧(同步帧)时间戳列表?
【发布时间】:2021-11-08 23:34:14
【问题描述】:

是否有任何 API 可以为我提供 Android 中给定视频文件的关键帧的时间戳列表?

【问题讨论】:

    标签: android video


    【解决方案1】:

    对于一个优雅的解决方案,您似乎需要使用 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

    【讨论】:

    • 嗨 Stefan,感谢您的努力,但我已经尝试过这个解决方案,它的运行速度非常慢,而且结果很奇怪。我将进一步调查(例如 ffmpeg / ffprobe)并保持此线程更新。
    • 这就是我所期望的。加快速度的一个想法是尝试time += lastdistance;,如果找到关键帧,则使用set lastdistance = lastFrameTime - time;,检查lastFrameTime + lastdistance - 1 是否仍然是最后一帧。如果是,请继续...否则使用“慢搜索”查找下一帧
    • 将ffmpeg交叉编译到android上。使用带有 -show_frames 选项的 ffprobe 命令来获取 I 图片列表。这应该适用于所有类型的视频,并且速度非常快。先在电脑上看看。 [如果你安装了 ffmpeg,你也应该有 ffprobe]。您将获得所需的所有信息。如果您想知道的只是参考帧与非参考帧,则 -show_packets 速度更快,并且可以证明是足够的。
    【解决方案2】:

    我相信正确答案是http://code.google.com/p/mp4parser/

    isoparser API 可以读取和写入 MP4 文件结构。它是一个处理所谓的盒子的低级工具,但它也可以处理像轨道和电影这样的结构。

    不错的 API 和比 ffmpeg 庞然大物快得多的方法

    【讨论】:

    • 是的,但是 isoparser 不包含解码器/编码器来从盒子的 mdat 部分创建位图
    • @Nativ 那不是问题。
    • 哈哈,我在 2013 年问过这个问题
    【解决方案3】:

    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);
    }
    

    【讨论】:

    • 感谢您的回复,我一回到这个项目就会检查一下
    【解决方案4】:

    @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 应设置数据源并选择视频轨道。

    【讨论】:

      【解决方案5】:

      使用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 ;
      

      【讨论】:

        猜你喜欢
        • 2018-05-24
        • 1970-01-01
        • 2020-06-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-27
        • 2019-12-06
        • 2018-07-07
        相关资源
        最近更新 更多