Первый раз мне пришлось столкнуться с обработкой видео на 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.
Вот пример их демки :
http://www.youtube.com/watch?v=0EvQW94JuJ0
Единственно настораживает то, что давно не было никаких обновлений. Но текущая версия вполне стабильная.
В качестве примера, приведу примитивный пример – получить изображения из видео с 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;
}
}
}