Первый раз мне пришлось столкнуться с обработкой видео на Java лет 10-12 назад. Я еще был студентом и работал над проектом, в котором мы пытались сделать видео проигрыватель в виде апплета.
В те времена, для перекодирования видео мы пробовали использовать JMF (Java Media Framework). Нам нужно было сделать хитрую предварительную обработку видео, т.к. показ видео осуществлялся апплетом с помощью самописного алгоритма. В итоге пришлось отказаться от JMF в пользу приложения написанного на С++, т.к. JMF оказался не особо функциональным...

С тех пор прошло много времени, я участвовал в различных более масштабных и менее сумасшедших (и наоборот) Java-проектах (никак не связанных с видео), а тот проект проект был благополучно сдан и забыт. При этом какие-то знания по способам хранения и сжатия мультимедийной информации оказалось прочно засели у меня в голове (и даже некоторое время мучили ночными кошмарами)...


Говорят история развивается по спирали. В прошлом году взялся за проект, в котором опять-таки требуется работа с видео/аудио потоками. Небольшие исследования показали, что JMF с тех пор особо не прогрессировал. Нельзя сказать, что этот фреймворк стоял на месте, но прогресс шел явно медленней, чем развитие технологий работы с видео в целом.
К счастью спасение пришло от проекта
Xuggle!:

Оказалось, что это очень мощный и неплохо написанный проект для работы с мультимедийными данными. Причем позволяет осуществляет работу с "живыми" потоками в реальном-времени. По большому счету он представляет собой обёртку к ffmpeg (в xuggle используют специальную сборку ffmpeg).

Что мне особо понравилось:

  • Проект содержит высокоуровневое API которое позволяет осуществлять довольно сложные операции над различными видео и аудио данными.
  • Имеется очень неплохой Tutorial, Wiki и несколько скринкастов, в которых доходчиво объясняется как работать с этой библиотекой.
  • В Xuggle есть возможность получить стандартный BufferedImage. Над которым настоящий java программист может осуществлять практические любые действия! При этом полученный BufferedImage можно опять таки использовать дальше и с помощью средств Xuggle формировать новый видео-поток.
  • Конечно же работает под windows и под linux.

Вот пример их демки :

Единственно настораживает то, что давно не было никаких обновлений. Но текущая версия вполне стабильная.

В качестве примера, приведу примитивный пример - получить изображения из видео с 6 по 12 секунду с шагом в 500 милисекунд и сохранить их в PNG.
Здесь я модифицирую их стандартный пример из библиотеке, который выводит видео на экран. Единственное нужно - убрать синхронизацию, убрать показ видео, добавить сохранение в PNG. Понятно что в реальных приложениях программный код будет более аккуратный, здесь просто демонстрируются возможности.

Исходник:

import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import com.xuggle.xuggler.ICodec;
import com.xuggle.xuggler.IContainer;
import com.xuggle.xuggler.IPacket;
import com.xuggle.xuggler.IStream;
import com.xuggle.xuggler.IStreamCoder;
import com.xuggle.xuggler.IVideoPicture;
import com.xuggle.xuggler.Utils;
 
public class Foo {
 
	public static void main(String a[]) throws Exception {
		String filename = "C:/temp/1.avi";
		File outdir = new File("c:/temp/pictures");
		IContainer container = IContainer.make();
 
		if (container.open(filename, IContainer.Type.READ, null) < 0)
			throw new IllegalArgumentException("could not open file: "
					+ filename);
		int numStreams = container.getNumStreams();
		int videoStreamId = -1;
		IStreamCoder videoCoder = null;
 
		// нужно найти видео поток
		for (int i = 0; i < numStreams; i++) {
			IStream stream = container.getStream(i);
			IStreamCoder coder = stream.getStreamCoder();
			if (coder.getCodecType() == ICodec.Type.CODEC_TYPE_VIDEO) {
				videoStreamId = i;
				videoCoder = coder;
				break;
			}
		}
		if (videoStreamId == -1)
			// кажись не нашли
			throw new RuntimeException("could not find video stream in container: " 
                                         + filename);
 
		// пытаемся открыть кодек
		if (videoCoder.open() < 0)
			throw new RuntimeException(
					"could not open video decoder for container: " + filename);
 
		IPacket packet = IPacket.make();
		// с 3-ей по 5-ую микросекунду
		long start = 6 * 1000 * 1000;
		long end = 12 * 1000 * 1000;
		// с разницей в 100 милисекунд
		long step = 500 * 1000;
 
		END: 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 in: "
                                                                                         + filename);
					offset += bytesDecoded;
					// В общем случае, нужно будет использовать Resampler. См.
					// tutorials!
					if (picture.isComplete()) {
						IVideoPicture newPic = picture;
						// в микросекундах
						long timestamp = picture.getTimeStamp();
						if (timestamp > start) {
							// Получаем стандартный BufferedImage
							BufferedImage javaImage = Utils
									.videoPictureToImage(newPic);
							String fileName = String.format("%07d.png",
									timestamp);
							ImageIO.write(javaImage, "PNG", new File(outdir,
									fileName));
							start += step;
						}
						if (timestamp > end) {
							break END;
						}
					}
				}
			}
		}
		if (videoCoder != null) {
			videoCoder.close();
			videoCoder = null;
		}
		if (container != null) {
			container.close();
			container = null;
		}
 
	}
}

Результат: