2011-05-31 90 views
9

我与xuggle工作,因为一个星期,我写了一个方法,通过视频获得 框架,但如果视频是长这个方法需要太多的时间最佳方法:爪哇 - Xuggle - 获得一个框架

public static void getFrameBySec(IContainer container, int videoStreamId, IStreamCoder videoCoder, IVideoResampler resampler, double sec) 
{ 
    BufferedImage javaImage = new BufferedImage(videoCoder.getWidth(), videoCoder.getHeight(), BufferedImage.TYPE_3BYTE_BGR); 
    IConverter converter = ConverterFactory.createConverter(javaImage, IPixelFormat.Type.BGR24); 
    IPacket packet = IPacket.make(); 
    while(container.readNextPacket(packet) >= 0) 
    { 
     if (packet.getStreamIndex() == videoStreamId) 
     { 
      IVideoPicture picture = IVideoPicture.make(videoCoder.getPixelType(), videoCoder.getWidth(), videoCoder.getHeight()); 
      int offset = 0; 
      while(offset < packet.getSize()) 
      { 
       int bytesDecoded = videoCoder.decodeVideo(picture, packet, offset); 
       if (bytesDecoded < 0) 
        throw new RuntimeException("got error decoding video"); 
       offset += bytesDecoded; 
       if (picture.isComplete()) 
       { 
        IVideoPicture newPic = picture; 
        if (resampler != null) 
        { 
         newPic = IVideoPicture.make(resampler.getOutputPixelFormat(), picture.getWidth(), picture.getHeight()); 

         if (resampler.resample(newPic, picture) < 0) 
          throw new RuntimeException("could not resample video from"); 
        } 
        if (newPic.getPixelType() != IPixelFormat.Type.BGR24) 
          throw new RuntimeException("could not decode video as RGB 32 bit data in"); 

        javaImage = converter.toImage(newPic); 
        try 
        { 
         double seconds = ((double)picture.getPts())/Global.DEFAULT_PTS_PER_SECOND; 
         if (seconds >= sec && seconds <= (sec +(Global.DEFAULT_PTS_PER_SECOND))) 
         { 

          File file = new File(Config.MULTIMEDIA_PATH, "frame_" + sec + ".png"); 
          ImageIO.write(javaImage, "png", file); 
          System.out.printf("at elapsed time of %6.3f seconds wrote: %s \n", seconds, file); 
          return; 
         } 
        } 
        catch (Exception e) 
        { 
         e.printStackTrace(); 
        } 
       } 
      } 
     } 
     else 
     { 
      // This packet isn't part of our video stream, so we just 
      // silently drop it. 
     } 
    } 
    converter.delete(); 
} 

你知道更好的方法吗?

回答

2

那么从阅读你的代码,我看到了一些可以优化的东西。

您首先通读整个文件一次,创建一个byteoffsets和seconds的索引。然后,函数可以从给定的秒数查找byteoffset,并且可以在该偏移量处解码视频,并执行其余的代码。

另一种选择是使用你的方法,每次读取整个文件,但不是调用所有重新采样器,newPic和Java图像转换器代码,检查秒是否匹配。如果他们这样做,然后将图像转换成新的图片来显示。

所以

if(picture.isComplete()){ 

try { 

    double seconds = ((double)picture.getPts())/Global.DEFAULT_PTS_PER_SECOND; 
    if (seconds >= sec && seconds <= (sec +(Global.DEFAULT_PTS_PER_SECOND))) 
    { 
    Resample image 
    Convert Image 
    Do File stuff 
    } 
2

使用seekKeyFrame选项。您可以使用此功能来查看视频文件中的任何时间(时间以毫秒为单位)。

double timeBase = 0; 
int videoStreamId = -1; 

private void seekToMs(IContainer container, long timeMs) { 
    if(videoStreamId == -1) { 
     for(int i = 0; i < container.getNumStreams(); i++) { 
      IStream stream = container.getStream(i); 
      IStreamCoder coder = stream.getStreamCoder(); 
      if (coder.getCodecType() == ICodec.Type.CODEC_TYPE_VIDEO) { 
       videoStreamId = i; 
       timeBase = stream.getTimeBase().getDouble(); 
       break; 
      } 
     } 
    } 

    long seekTo = (long) (timeMs/1000.0/timeBase); 
    container.seekKeyFrame(videoStreamId, seekTo, IContainer.SEEK_FLAG_BACKWARDS); 
} 

从那里你可以使用你的经典while(container.readNextPacket(packet) >= 0)获取图像文件的方法。

注意:它不会寻求确切的时间,但近似,所以你仍然需要通过数据包(但当然比以前少得多)。

+0

什么是“timeMs”或“seekTO”相对于这种情况?我不太明白..我该如何跳过目前的分数? [IVideoPicture.getPts()] – KarlP 2015-06-09 17:08:48