2011-05-31 10 views
9

estoy trabajando con xuggle ya una semana y me escribió un método para obtener un marco por un vídeo, pero si el vídeo es largo Este método toma demasiado tiempo:Java - Xuggle - Mejor método para obtener un marco

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

¿Conoces una mejor manera de hacerlo?

Respuesta

2

Bueno, solo por leer su código veo algunas optimizaciones que pueden hacerse.

Al leer por primera vez todo el archivo, cree un índice de byteoffsets y segundos. Luego, la función puede buscar el byteoffset a partir de los segundos indicados y usted puede decodificar el video en ese offset y hacer el resto de su código.

Otra opción es usar su método, leyendo todo el archivo cada vez, pero en lugar de llamar a todo ese remuestreador, newPic y el código del convertidor de imagen java, compruebe si los segundos coinciden primero. Si lo hacen, convierta la imagen en una nueva imagen para mostrar.

Así

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

Uso seekKeyFrame opción. Puede usar esta función para buscar en cualquier momento en el archivo de video (el tiempo está en milisegundos).

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

A partir de ahí, utiliza el método clásico while(container.readNextPacket(packet) >= 0) para obtener las imágenes en los archivos.

Aviso: No intentará precisar el tiempo, pero será aproximado, por lo que todavía tendrá que pasar por los paquetes (pero, por supuesto, mucho menos que antes).

+0

¿En qué se refiere "timeMs" o "seekTO" en este caso? No lo entiendo ... ¿Cómo puedo saltar relativo a los puntos actuales? [IVideoPicture.getPts()] – KarlP

Cuestiones relacionadas