В этом нам очень сильно поможет проект с открытым исходным кодом VLCJ CAPRICA.
The vlcj project provides a Java framework to allow an instance of a native VLC media player to be embedded in a Java application.
Идея у ребят простая, но гениальная (реально перцовая). Вместо мучений с библиотеками FFmpeg и прочим, надо сразу вызывать специалиста ядро нормального, функционального и профессионального медиаплеера VLC. И вызвать его прямо из JAVA приложения.
Кому интересно просим под кат
Поскольку, в этом вояже хватает подводных камней, то начнём, как водится, с очень простого и лишь затем перейдём к тривиальному.
Инсталляция пакета VLCJ
Первым делом проверьте установленную у вас версию медиапроигрывателя VLC. Свежая версия нам не нужна, там выпилено то, что требуется для udp стрима. Об этом уже говорилось в предыдущем посту. Поэтому качаем версию 2.2.6 Umbrella и заодно тщательно проверяем свой JAVA пакет. Они должны совпадать по разрядности. Если плеер использует 64-разрядную архитектуру, то и JDK обязан быть таким же. А то не взлетит.
После этого уже можно скачать сам пакет библиотек VLCJ
Обратите внимание, что нам нужен пакет vlcj-3.12.1 distribution (zip). Именно он работает с плеерами версий VLC 2.2.x. Разархивировать его можно куда угодно, главное, что не в папку самого VLC, ибо там по именам совпадают два файла. И если вы их перезапишите, кончится всё это полным провалом.
Далее, создаем проект в IDE IntelliJ IDEA (если у вас другое IDE, то ничем помочь не могу) и прописываем необходимые зависимости для интеграции библиотек VLCJ.
Делаем именно так для файлов:
jna-5.2.0
jna-platform-5.2.0
vlcj-3.12.1
Затем создаем единственный класс и пишем в нём следующую малюсенькую программку.
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import uk.co.caprica.vlcj.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.discovery.NativeDiscovery;
import uk.co.caprica.vlcj.player.MediaPlayerFactory;
import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer;
import uk.co.caprica.vlcj.player.embedded.videosurface.CanvasVideoSurface;
public class BasicPlayer {
public final JFrame frame;
public static String mrl;
public static MediaPlayerFactory mpf;
public static EmbeddedMediaPlayer MediaPlayer;
public static CanvasVideoSurface videoSurface;
public static Canvas canvas;
public static void main(final String[] args) {
new NativeDiscovery().discover();
mrl = "D:\\ttt.mp4";
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
BasicPlayer vp = new BasicPlayer();
vp.start(mrl);
}
});
}
public BasicPlayer() {
frame = new JFrame("My First Media Player");
frame.setBounds(200, 100, 540, 340);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.out.println(e);
MediaPlayer.release();
mpf.release();
System.exit(0);
}
});
JPanel contentPane = new JPanel();
contentPane.setLayout(new BorderLayout());
canvas = new Canvas();
mpf = new MediaPlayerFactory();
videoSurface = mpf.newVideoSurface(canvas);
MediaPlayer = mpf.newEmbeddedMediaPlayer();
MediaPlayer.setVideoSurface(videoSurface);
contentPane.add(canvas, BorderLayout.CENTER); // вот тут добавляем медиакомпонент в графическую панель
frame.setContentPane(contentPane);
frame.setVisible(true);
}
public void start(String mrl) {
MediaPlayer.playMedia(mrl);
}
}
Да, пока мы пытаемся проигрывать просто файл (как видно из кода). С udp лучше не начинать — не заработает. А файл проигрывается вполне, если вы, конечно, не забыли его с соответствующим именем разместить заранее там, где надо. Думаю, что даже для самого начинающего джависта не составит труда разобраться в вышеприведенном коде.
Всё новое это:
вызов для VLCJ
new NativeDiscovery().discover();
и создание самого инстанса медиаплеера
mpf = new MediaPlayerFactory();
videoSurface = mpf.newVideoSurface(canvas);
MediaPlayer = mpf.newEmbeddedMediaPlayer();
MediaPlayer.setVideoSurface(videoSurface);
А потом мы просто его добавляем в нужную графическую панель:
contentPane.add(canvas, BorderLayout.CENTER);
И всё, файл будет проигрываться именно в этом окошке.
А теперь попробуйте заменить
mrl = «D:\\ttt.mp4»;
на
mrl = «udp://@:40002»;
как мы спокойно делали в прошлом посте для стриминга видео через udp соединение.
Здесь такой номер не пройдёт. Окошко, конечно откроется, но покажет фигу, в смысле темный экран. Хотя никаких логов с ошибкой не будет. Просто не будет ничего.
Надо разобраться
Может быть не хватает кодека H264, который мы выбрали в настройках? Стоп, а как тогда только что проигрывался файл ttt.mp4? Он же не может проигрываться при такой настройке, он же — mp4.
Немедленно приходит понимание того, что библиотека VLCJ запускает только само ядро плеера. А какие там были предварительные настройки она не знает и знать не хочет. То есть, нам надо каким-то образом при запуске JAVA приложения, как-то передать VLC плееру, что мы хотим явно использовать кодек H264 или, допустим, хотим повернуть изображение или что-то ещё.
Оказывается, сделать это можно, используя класс MediaPlayerFactory. Только мы его запускали без аргументов, а можно даже с ними. На stackoverflow.com я тут же нашел простой пример, связанный с поворачиванием изображения:
String[] args = {
"--video-filter",
"rotate",
"rotate-angle",
"10"
};
mpf = new MediaPlayerFactory(args);
То есть, чего-то там передаем строчным массивом в медиафабрику и оно там сохраняется для используемого медиаресурса.
Я попробовал этот способ для проигрывания файла и как водится, ничего не заработало. Оказывается, забыли две черточки добавить и разнесли по всему интернету. Пришлось догадываться, используя похожий метод transform.
Короче говоря, должно быть:
String[] args = {
"--video-filter",
"rotate",
"--rotate-angle",
"10"
};
Теперь наш эталонный файл перекривило как надо!
Дальше будет уже совсем просто:
Для определения кодека, согласно командной строке VLC мы добавляем в строковый массив строку:
"--demux=h264"
Снова пробуем udp канал
mrl = «udp://@:40002»;
И в этот раз всё работает, обозначая победу человеческого разума. Теперь это окошко с видео или несколько таких окошек вы сможете беспрепятственно портировать в графический интерфейс вашего JAVA приложения.
Казалось бы победа?
Не совсем. Легкое недоумение у меня вызвали временные задержки или по научному, лаги. Сначала они более менее приёмлимые, но если у вас хватит терпения просмотреть видео до конца, то вы увидите, что лаг к концу первой минуты трансляции достигает аж пяти секунд. У меня терпения хватило на 10 минут съемки, но, как ни странно, задержка больше не увеличивалась, а так и осталась в тех же пределах.
Конечно, для просмотра видео с камеры такое сгодится, но для управления роботележкой едва ли. Даже луноход реагировал быстрее в два раза!
Подозрения сразу пали на процессы кэширования и они (подозрения )оказались верными.
Самым наглым оказался:
caching for network resources
Он как раз и отжирает по умолчанию практически всё, если ему вовремя не дать по рукам.
Может устроить лаг и:
caching for cameras and microphones
Поэтому во избежание многосекундных задержек рекомендуется добавить во всё тот же строковый массив через запятую следующие строчки:
"--live-caching=100",
"--network-caching=500",
Параметры там задаются в миллисекундах и поэтому каждый желающий может подобрать их под себя.
Ещё можно использовать ключ:
"--clock-jitter=time in milliseconds",
Тогда медиаплеер будет стараться оптимизировать джитер — подергивание экрана. Но там, чем больше установлено время, тем лучше оптимизируется и это понятно почему. Так что здесь остается лишь искать консенсус и видеть иногда в логах такое безобразие:
Вот хотел он, понимаешь, джиттер исправить, а ты временной промежуток слишком маленький поставил. Теперь сам виноват.
Теперь вроде бы все как надо. Задержку удалось сократить меньше, чем до одной секунды (правда, чуть-чуть меньше).
В итоге, получился совсем крохотный рабочий код
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import uk.co.caprica.vlcj.discovery.NativeDiscovery;
import uk.co.caprica.vlcj.player.MediaPlayerFactory;
import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer;
import uk.co.caprica.vlcj.player.embedded.videosurface.CanvasVideoSurface;
public class BasicVideoPlayer {
public final JFrame frame;
public static String mrl;
public static MediaPlayerFactory mpf;
public static EmbeddedMediaPlayer MediaPlayer;
public static CanvasVideoSurface videoSurface;
public static Canvas canvas;
public static void main(final String[] args) {
new NativeDiscovery().discover();
mrl = "udp://@:40002";
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
BasicVideoPlayer vp = new BasicVideoPlayer();
vp.start(mrl);
}
});
}
public BasicVideoPlayer() {
frame = new JFrame("My First Media Player");
frame.setBounds(200,100, 540, 340);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.out.println(e);
MediaPlayer.release();
mpf.release();
System.exit(0);
}
});
JPanel contentPane = new JPanel();
contentPane.setLayout(new BorderLayout());
canvas = new Canvas();
String[] args = {
"--video-filter",
"rotate",
"--rotate-angle",
"270",
"--demux=h264",
"--clock-jitter=100",
"--live-caching=100",
"--network-caching=500",
};
mpf = new MediaPlayerFactory(args);
videoSurface = mpf.newVideoSurface(canvas);
MediaPlayer = mpf.newEmbeddedMediaPlayer();
MediaPlayer.setVideoSurface(videoSurface);
contentPane.add(canvas, BorderLayout.CENTER);
frame.setContentPane(contentPane);
frame.setVisible(true);
}
public void start(String mrl) {
MediaPlayer.playMedia(mrl);
}
}
Теперь можно интегрировать видео трансляцию в мою программулину по управлению роботележкой, где раньше я передавал видео по кускам. И надо сказать код весьма упростился, а качество на порядок улучшилось. Плюс ко всему к видео потоку мы можем передать показания
акселерометров
гироскопов
уровня освещения
давления воздуха
показаний компаса
температуры
и даже влажности
При условии, конечно, что все эти сенсоры у вашего смартфона имеются.
И даже включить фару!
Вряд ли кому особо интересно, но на случай ссылки на гитхаб:
для телеги
для смартфона
Комментариев нет:
Отправить комментарий