После прочтения статьи на Хабре «Делаем ИК-пульт ДУ для фотоаппарата», захотелось поделиться опытом разработки ИК-пульта ДУ для фотоаппаратов в виде приложения под Android (от идеи до публикации).
Идея
Купив зеркальный фотоаппарат, через некоторое время осознаешь потребность в ручной настройке, штативе и бесконтактном спуске затвора, который осуществляется с помощью дополнительного оборудования, предлагаемого фирмой-производителем. Но, осознав, что в твоем Android-телефоне есть ИК, которым можно удаленно спустить затвор, идешь в Google Play. Там поиск по «саmera remote» сразу выдает массу приложений, среди которых есть подходящие.
Скачиваешь, пробуешь — да, работают, но… удобство использования и интерфейс оставляют желать лучшего. Кроме перечисленного, в моей модели фотоаппарата сильно не хватает функции подсчета экспопары для съемки ночью. Поэтому решил реализовать собственное приложение, чтобы понять, что такое Android, и с чем его едят.
Тестирование ИК на телефоне
Реализация приложения, которое выполняет бесконтактный спуск затвора, заняла не больше одного часа. За это время было создано Activity с одной кнопкой и найден способ отправки сигналов на камеру, который выглядит примерно так:
ConsumerIrManager consumerIrManager = (ConsumerIrManager) this.getSystemService(Context.CONSUMER_IR_SERVICE);
int frequency = 38400;
int[] pattern = new int[]{ 1, 105, 5, 1, 75, 1095, 20, 60, 20, 140, 15, 2500, 80, 1 };
consumerIrManager.transmit(frequency, pattern);
Что только что произошло? Была создана «программа», выполняющая функцию устройства, которое мне хотела продать фирма Nikon за 30$.
ТЗ
Я думал "Что может быть проще, чем реализация одной кнопки?", но не тут-то было.
Анализ конкурентов привел к длинному списку их возможностей и недостатков. Приведу только основные.
Возможности:
- можно установить задержку перед первым снимком, длительность выдержки камеры в blub режиме, задержку между последующими снимками, количество снимков
- HDR режим (Bracketing) — съёмка нескольких кадров с разным значением выдержки
- срабатывание по звуку
- поддержка большого числа камер и устройств
Недостатки:
- оставляющий желать лучшего дизайн и простота использования, или хороший дизайн, но мало возможностей
- работа в главном потоке
Требования к приложению:
- сделать бесплатное и без рекламы приложение, которое не стыдно показать другим
- включить все возможности конкурентов
- реализовать калькуляторы экспопары и глубины резкости
- добавить отображение времени заката и рассвета (золотого часа)
- обеспечить работу в фоновом процессе
- подключить звуковые уведомления
- разработать наиболее быстрый и интуитивный дизайн для основных возможностей
- не забыть о «вау-эффекте»
Некоторые задачи выходят за рамки пульта ДУ, но мне хотелось попробовать как можно больше возможностей Android.
Реализация или «Шагая по подводным минам»
Работа с графикой
При разработке я придерживаюсь философии: «Больше векторной, меньше растровой графики». Поэтому на весь проект потребовалось «две» растеризированных картинки — иконка приложения и иконка уведомления (не считая изображений для Google Play). Все остальное — бесплатные стоковые шрифты и шрифты-иконки. Благодаря этому, интерфейс выглядит одинаково качественно на большинстве устройств, независимо от разрешения экрана.
В целях достижения максимального быстродействия приложения, я обычно отказываюсь от анимации. Но ее отсутствие делало работу приложение неочевидной для конечного пользователя. Поэтому, и для достижения «вау-эффекта» анимация присутствует. В связи с тем, что взаимодействие с приложением происходит вокруг одной кнопки, она была максимально стилизована, и процесс съемки отображен вокруг нее.
После работы с WPF и Silverlight, мне показалось, что Android содержит очень скудный набор графических примитивов для построения независимых векторных объектов (объекты, которые сами знают как перерисовывать себя — без необходимости рисовать их на канве вручную). Поэтому пришлось сделать несколько итераций реализации кольцевого ProgressBar-а.
Странность: Если на некоторых устройствах вызывать у объекта ClipDrawable функцию setRotation() с углами отличными от 0, 90, 180,…, в котором есть другие графические объекты, они становятся полностью видимыми вне зависимости от ClipDrawable.setLevel().
Работа с уведомлением
Хотелось создать уведомление, и в нем менять состояние прогресса съемки. Но вместо этого приходится постоянно пересоздавать его, из-за ограничений Android. Скорость, с которой меняется время, оставшееся до следующего срабатывания, заставляет делать это часто, что привело к невозможности использовать иконку с большим разрешением.
Работа с аппаратным обеспечением
Разрабатывать такого типа приложение в стандартном эмуляторе сложно и долго, в более шустрых аналогах — просто сложно. Наличие
После релиза, захотелось доработать другие, более загадочные способы «нажатия на кнопку», для простых
Сценарий демонстрации: зафиксировать камеру вдалеке и положить «выключенный» телефон перед собой, после чего, от «магического» жеста рукой срабатывает камера. Для большего эффекта можно включить вспышку. Проверено, смотрится с удивлением и восклицанием «Вау!» (последнее зависит от эмоциональности конкретного человека).
Первое, что пришло в голову — использовать, продемонстрированные впервые Air Gestures в Samsung S4. Поиск SDK привёл к разочарованию: Devices with Android 4.3 Jelly Bean (API level 18) or higher support Gesture except Galaxy S4 due to a hardware issue. Но, я вспомнил про акселерометр и, позднее, про освещенность.
Вначале реализовал срабатывание при переходе устройства с вертикального положения в горизонтальное. Все работало отлично, пока не отключил телефон от компьютера. При выключении экрана акселерометр «засыпает», т.е. сигналы о изменении положения устройства в пространстве перестают генерироваться. Устранить этот эффект ни работой в фоновой службе, ни настройками энергосбережения не удалось.
Реализовал срабатывание на резкое изменение освещенности. Этот сенсор, не «засыпает» при выключенном дисплее, но количество генерируемых сигналов заметно уменьшается. Именно благодаря наличию этого сенсора в устройстве, написанный ранее сценарий работает.
Была идея подключить камеры для распознавания, например, подмигивания. Но, найти вменяемую бесплатную библиотеку для этого не смог.
Процесс в целом
В большинстве случаев, платформа Android ведет себя вполне ожидаемо. Не возникло проблем при определении местоположения, получении данных с сервера прогноза погоды, реализации жизненного цикла приложения, использовании фоновой службы и работе с базой данных.
IDE
В связи с работой, пришлось сменить Visual Studio на Eclipse, что периодически бесит и по сей день. Но, для разработки приложения установил Android Studio с надеждой, что «Studio» в названии, что-то да значит. С первых созданных методов и переменных понял, что это то, чего я так ждал: проверка правописания кода, умный autocomplete, огромное количество подсказок и многое другое. Спасибо разработчикам за качественный продукт!
Публикация
Создал аккаунт, заплатил взнос, прочитал рекомендации, настроил релиз-версию, создал подписанное приложение, подготовил описание на нескольких языках (EN, RU, UA, NO), сделал фотографии приложения и выложил как бета-версию.
Неожиданностью стало обязательное изображение для Google Play размером 1024х500, хотя в статьях писали, что это «не помешает».
При компиляции релиз-версии использовался ProGuard, который нарушил работу определенной части кода, основанного на названиях классов, которые он успешно обфусцировал. Проблема была обнаружена не сразу, поскольку при запуске релиз-версии, параметр «debuggable» равен «false». Ошибки выскакивали, обрабатывались и всё работало дальше.
В связи с тем, что приложение для микрорынка, я не планировал писать статью на Хабр, если бы не проблемы с ИК. Вначале писал на форумы для фотографов, но результат — лишь бан. В основном помог youtube. После чего появились первые реальные пользователи, а с ними и проблемы.
Детально про мучения работу с ИК-портом
Возвращаясь к коду, для отправки импульсов:
ConsumerIrManager consumerIrManager = (ConsumerIrManager) this.getSystemService(Context.CONSUMER_IR_SERVICE);
int frequency = 38400;
int[] pattern = new int[]{ 1, 105, 5, 1, 75, 1095, 20, 60, 20, 140, 15, 2500, 80, 1 };
consumerIrManager.transmit(frequency, pattern);
Все было хорошо, пока не оказалось, что поддержка ИК, появилась в Android только начиная с API версии 19. Что было до этого? Было выпущено достаточно много устройств с ИК, для управления которыми, производители либо создавали свои библиотеки (HTC OpenSense IR API и LG QRemote IR SDK) или публиковали пример кода для работы с их ИК (Samsung). Что произошло после? Вышли новые версии, и способ передачи сигналов стандартизированный в API 19 поменялся.
Удалось заставить работать приложение с ИК портами на Samsung-ах с версиями Android KitKat и Lollipop. После многих попыток удаленного тестирования с HTC One M8, понял, что самому реализовать поддержку других устройств будет очень сложно. Поэтому, я создал проект на GitHub-е, который может скачать любой желающий: AndroidInfraRed.
Немножко скучной теории
Для дистанционного управления устройством с помощью ИК, вам необходимы: передатчик и последовательность сигналов, которую необходимо отправить. В моем случае, устройство — Nikon D7100, передатчик — Samsung S4.
Последовательность задается двумя параметрами: частотой (Hertz) и паттерном, который представляет собой некую временную последовательность наличий/отсутствий сигнала заданной частоты.
Пример (для Samsung устройств с версией Android >= 4.4.3)
- Frequency: 40000
- Pattern: 125, 100, 150, 75, ...
Пример (для Samsung устройств с версией Android <= 4.4.2)
- Frequency: 40000
- Pattern: 5, 4, 6, 3, ...
Подробно о проекте
Проект AndroidInfraRed, созданный в Android Studio, включает в себя:
- Библиотеку для работы с ИК
- MainActivity с одной кнопкой для тестирования
Библиотека состоит из:
- Модуля логирования, для простой возможности отобразить сообщения либо на консоли, либо в EditText, либо отключить лог вообще
- Модуля паттернов, который позволяет адаптировать последовательности сигналов под конкретный тип устройства
- Модуля определения передатчика, который анализируя устройство, возвращает способ, с помощью которого могут быть переданы сигналы
- Модуля передатчиков, в котором реализуются способы отправки сигналов
- Класса InfraRed, объединяющего работу модулей
Как это работает
Описание строк кода из MainActivity.java
1. Выбираем один из трёх способов логирования
// Log messages print to EditText
EditText console = (EditText) this.findViewById(R.id.console);
log = new LogToEditText(console, TAG);
log.log("Hello, world!");
// Log messages print with Log.d(), Log.w(), Log.e()
// LogToConsole log = new LogToConsole(TAG);
// Turn off log
// LogToAir log = new LogToAir(TAG);
По умолчанию выбран вывод текста на экран устройства
2. Создаем объект класса InfraRed
infraRed = new InfraRed(this, log);
Для этого необходимо передать Context и Logger
3. Инициализируем передатчик
TransmitterType transmitterType = infraRed.detect();
infraRed.createTransmitter(transmitterType);
Функция «detect()» возвращает одно из значений перечисления TransmitterType. Возможные варианты: Actual, HTC, LG и Undefined. В случае Actual, для передачи сигналов будет использоваться класс ConsumerIrManager предоставляемый Android. Если HTC или LG — будут использованы соответствующие SDK, если Undefined то — «пустая» реализация.
4. Инициализируем последовательности сигналов
PatternConverter patternConverter = new PatternConverter(log);
List<TransmitInfo> patterns = new ArrayList<>();
// Nikon D7100 v.1
patterns.add(patternConverter.createTransmitInfo(38400, 1, 105, 5, 1, 75, 1095, 20, 60, 20, 140, 15, 2500, 80, 1));
// Nikon D7100 v.2
patterns.add(patternConverter.createTransmitInfo(38400, 77, 1069, 16, 61, 16, 137, 16, 2427, 77, 1069, 16, 61, 16, 137, 16));
С помощью класса PatternConverter, инициализируем сигналы для устройств, которыми хотим управлять.
Важно! Функция «createTransmitInfo(int frequency, int… pulseCountPattern)», принимает сигнал в виде последовательности циклов (как в Android <= 4.4.2), а не микросекунд (как в Android >= 4.4.3). Что бы перейти от микросекунд к циклам, необходимо каждый элемент паттерна умножить на частоту и поделить на миллион (количество микросекунд в секунде). Это обратная операция, к той, которая конвертирует заданные последовательности под Android >= 4.4.3.
5. Передаем последовательность сигналов на устройство
TransmitInfo transmitInfo = patterns[random.nextInt(patterns.length)];
infraRed.transmit(transmitInfo);
В конкретном примере, посылаем заданные сигналы в случайном порядке.
Комментарии
Проект AndroidInfraRed — почти готовое решение для дистанционного управления устройствами с помощью ИК. Единственное чего не хватает — протестировать и наладить работу с HTC, LG, Sony… Я очень надеюсь, что на Хабре найдутся люди со смартфонами с ИК, которые помогут данному проекту, и тем, кто в будущем захочет добавить в свое приложение работу с ИК.
Результат
В результате получилось создать приложение, которое покрывает все мои потребности для удаленного управления камерой и процесса фотографирования в целом. Жаль, что преимущества данного приложения могут оценить лишь обладатели Samsung.
Комментарии (7)
nickon
14.05.2015 20:13Можно подобное сделать через bluetooth или wifi, попутно собрав небольшую коробочку на фотоаппарате???…
не на каждом телефоне есть infrared, да и не удобно постоянно с лицевой стороны сигналы слать…OneButtonDeveloper Автор
15.05.2015 19:37Можно. Но обычно, если на камере есть bluetooth или wifi, то для них есть и приложения, от фирм изготовителей, которые позволяют полноценно управлять фотоаппаратом. Начиная от фокусировки и заканчивая LiveView.
Если получится «вручную» подключить bluetooth или wifi к фотоаппарату, то добавить в приложение передачу сигнала будет не сложно (по крайней мере для bluetooth)
pyra
18.05.2015 11:03Под линукс есть пакет lirc. Там есть комманда irrecord. Как раз игрался с расбери пи и инфракрасным пультом. Могу с поделится особенно если интересно.
tehnolog
Интересная статья!
А откуда вы узнали этот паттерн 1, 105, 5, 1, 75, 1095, 20, 60, 20, 140, 15, 2500, 80, 1?
И попутно вопрос — как можно перехватывать IR и Bluetooth-команды, чтобы самому посылать такие же команды с телефона со своего приложения.
OneButtonDeveloper Автор
Паттерн нашел в интернете. Но, был бы у меня HTC или LG — искать бы не пришлось. API HTC содержит функцию learnIRCmd(timeout), LG API — startIrLearning() и stopIrLearning(). С помощью них должен быть возможен перехват любых IR команд. Я видел приложения-пульты которые реализовали эту возможность.