Первый раз мне пришлось столкнуться с обработкой видео на 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; } } }


