- Телефон на Android в качестве беспроводной звуковой карты ноутбука
- Настройка SoundWire Server в Windows
- Настройка SoundWire на Android
- Уменьшение задержки звука
- Audio buffer size
- Audio compression (платная версия)
- Android native audio
- Latency steering amount (платная версия)
- Android и звук: как делать правильно
Телефон на Android в качестве беспроводной звуковой карты ноутбука
Однажды у меня возникла необходимость подключить большие колонки к ноутбуку, но так, чтобы не быть привязанным к ним кабелем. Протестировав несколько приложений для трансляции звука по Wi-Fi, я сделал вывод, что самым гибким из такого рода программ является SoundWire, позволяющее добиться задержки звука не более 100 миллисекунд (при условиях, что телефон довольно мощный, с версией Android 4.2+ и поддерживает режим native audio). А это означает, что уже можно смотреть фильмы без заметного отставания звука.
Примечание: конечно же, в первую очередь, все зависит от мощности и аппаратных возможностей Вашего Android-телефона. Протестировав данное приложение на разных устройствах я сделал вывод, что на старом Android-телефоне (Процессор: 1 ядро, 600 МГц; Память: 256 Мб; Режим аудио: standard_audio) не удается сделать задержку звука меньше 100 миллисекунд и при просмотре фильма чувствуется явное отставание звука.
Приложение обладает достаточно большим набором настроек под любой тип Wi-Fi сети (в плане скорости соединения). В Google Play Маркете есть платная и бесплатная версии. В платной версии доступна возможность установки размера буфера в миллисекундах (в бесплатной только в килобайтах), функция сжатия звука и функция стабилизации размера буфера, позволяющие максимально снизить нагрузку на беспроводную сеть и добиться минимальной задержки звука.
Настройка SoundWire Server в Windows
Скачайте программу SoundWire Server, соответствующую вашей версии Windows и установите её на компьютер.
Убедитесь, что она нормально работает, запустив какую-нибудь музыку в плеере или браузере. В окне программы, в поле «Level», должен отображаться индикатор звука. Если он подымается до уровня красного цвета (искажение звука), отрегулируйте ползунком «Audio Output» уровень звука так, чтобы цвет был только зеленым.
В зависимости от версии Windows ползунок регулятора громкости будет регулировать или уровень звука динамиков ноутбука, или трансляции по Wi-Fi. У меня Windows 8.1, и в моем случае реализован первый вариант, поэтому, чтобы звук от самого ноутбука не мешал, я его просто отключил.
Совет: на время тестирования приложения с разными настройками, звук компьютера лучше оставить включенным, тогда Вы будете слышать реальную задержку между воспроизведением звука на компьютере и по сети, через телефон на Android.
Настройка SoundWire на Android
Установите с GooglePlay Маркета приложение SoundWire.
Убедитесь, что Ваш телефон на Android и ноутбук находятся в одной сети Wi-Fi. Откройте приложение SoundWire и нажмите на кнопку с изображением спирали.
После небольшого ожидания спираль должна изменить цвет на золотистый, и Вы должны услышать звук на вашем Android-телефоне. Если этого не произойдет, попробуйте вручную прописать в приложении на Android IP-адрес, который показывает программа SoundWire Server в Windows и снова нажмите на кнопку со спиралью.
Если и в этот раз Вы ничего не услышите, тогда откройте в Windows через кнопку «Пуск» приложение «Командная строка», наберите команду «ipconfig» и нажмите клавишу «Enter». Введите в поле адреса в приложении SoundWire на Android IP-адрес, указанный в строке «IPv4-адрес» командной строки и снова нажмите на кнопку со спиралью.
Уменьшение задержки звука
Для уменьшения задержки звука в приложении SoundWire предусмотрен целый набор инструментов.
- Настройка буферизации (Audio buffer size);
- Сжатие звукового потока, только демо на несколько минут (Audio compression);
- Включение альтернативного звукового тракта (Android native audio).
- Настройка буферизации в миллисекундах (Audio buffer size);
- Сжатие звукового потока (Audio compression);
- Включение альтернативного звукового тракта (Android native audio);
- Уменьшение «плавания» размера буфера (Latency steering amount).
А теперь более подробно о каждой опции.
Audio buffer size
Первое, что можно сделать, чтобы уменьшить задержку звука, это уменьшить размер буфера входящего звукового потока. Для этого нажмите кнопку меню, затем выберите опцию «Settings», в открывшихся настройках нажмите на пункт «Audio buffer size» и выберите желаемый размер буфера.
Чем он меньше, тем меньше будет задержка, но очень маленький размер буфера может привести к эффекту дискретного «роботизированного звука». Размер буфера в бесплатной версии выставляется в килобайтах, в платной версии в миллисекундах. Кроме того, в платной версии реальная задержка звука в миллисекундах отображается на главной странице приложения.
Audio compression (платная версия)
Как показывает опыт, очень важная функция, так как позволяет не только уменьшить задержку, но и экономно использовать канал связи, чтобы, например, видео не подвисало во время просмотра онлайн. Приложение умеет сжимать транслируемый звуковой поток с помощью кодека Opus. В бесплатной версии пробный период использования этой опции составляет 10 минут. Чтобы включить сжатие звукового потока, поставьте в настройках галочку рядом с опцией «Audio compression», нажмите на опцию «Compression bitrate» и выберите битрейт сжимаемого аудиопотока. Чем он меньше, тем меньше трафика будет тратиться на передачу звука и, как следствие, уменьшится задержка и обрывы при передаче звука, но пострадает качество, поэтому экспериментируйте.
Как видно из рисунка выше, в моем случае при битрейте 64 кБит/с (как мне кажется, самом оптимальном) скоростьWi-Fi соединения, затрачиваемая на передачу звука, уменьшилась от
18 кБайт/с, то есть примерно в 10 раз!
Android native audio
Внимание: Эта опция поддерживается не всеми устройствами!
При включении опции «Android native audio» выбирается альтернативный внутренний звуковой тракт (OpenSL ES native audio), который может работать лучше и позволяет получить более низкие времена задержки звука на некоторых устройствах, поддерживающих «Android native audio». Опция «Android native audio» имеет три переключателя:
- Auto – используется native audio с малыми размерами буфера (32 кБ / 190 мс или меньше), а также standard audio с более крупными размерами буфера. Рекомендуется для устройств, которые поддерживают низкую задержку звука (Android 4.2+).
- Standard audio – рекомендуется для устройств, которые не поддерживают низкую задержку звука. Стандартный звуковой тракт является более надежным, на большинстве устройств Android.
- Android native audio – выберите, если альтернативный внутренний звуковой тракт работает лучше на устройстве даже при больших размерах буфера, например, если при использовании Auto или Standard audio есть проблемы.
На некоторых современных телефонах для корректной работы функции «Android native audio» необходимо транслировать звук с частотой дискретизации не 44.1 кГц, а 48 кГц. Требуемая частота дискретизации будет отображаться при нажатии на опцию «Android native audio». Если нужно, перенастройте SoundWire Server и Windows на использование частоты дискретизации 48 кГц (см. документацию).
Latency steering amount (платная версия)
Опция «Latency steering amount» позволяет контролировать, насколько агрессивно SoundWire будет пытаться достичь установленной задержки звука (примерно размер буфера, деленный на 2). Опция имеет три режима: Normal (нормальный), Tight (сжатый), Very Tight (сильно сжатый).
Обратите внимание, что фактическая задержка звука будет выше, чем отображаемая на панели, так как многие другие факторы способствуют задержке, такие как внутренний аудиотракт телефона на Android и буферизация на стороне сервера. Поэтому для оценки реальной задержки используйте Ваши уши, а не отображаемые цифры на экране телефона.
Источник
Android и звук: как делать правильно
В статье рассматривается архитектура и API для создания приложений, воспроизводящих музыку. Мы напишем простое приложение, которое будет проигрывать небольшой заранее заданный плейлист, но «по-взрослому» — с использованием официально рекомендуемых практик. Мы применим MediaSession и MediaController для организации единой точки доступа к медиаплееру, и MediaBrowserService для поддержки Android Auto. А также оговорим ряд шагов, которые обязательны, если мы не хотим вызвать ненависти пользователя.
В первом приближении задача выглядит просто: в activity создаем MediaPlayer, при нажатии кнопки Play начинаем воспроизведение, а Stop — останавливаем. Все прекрасно работает ровно до тех пор, пока пользователь не выйдет из activity. Очевидным решением будет перенос MediaPlayer в сервис. Однако теперь у нас встают вопросы организации доступа к плееру из UI. Нам придется реализовать binded-сервис, придумать для него API, который позволил бы управлять плеером и получать от него события. Но это только половина дела: никто, кроме нас, не знает API сервиса, соответственно, наша activity будет единственным средством управления. Пользователю придется зайти в приложение и нажать Pause, если он хочет позвонить. В идеале нам нужен унифицированный способ сообщить Android, что наше приложение является плеером, им можно управлять и что в настоящий момент мы играем такой-то трек из такого-то альбома. Чтобы система со своей стороны подсобила нам с UI. В Lollipop (API 21) был представлен такой механизм в виде классов MediaSession и MediaController. Немногим позже в support library появились их близнецы MediaSessionCompat и MediaControllerCompat.
Следует сразу отметить, что MediaSession не имеет отношения к воспроизведению звука, он только об управлении плеером и его метаданными.
MediaSession
Итак, мы создаем экземпляр MediaSession в сервисе, заполняем его сведениями о нашем плеере, его состоянии и отдаем MediaSession.Callback, в котором определены методы onPlay, onPause, onStop, onSkipToNext и прочие. В эти методы мы помещаем код управления MediaPlayer (в примере воспользуемся ExoPlayer). Наша цель, чтобы события и от аппаратных кнопок, и из окна блокировки, и с часов под Android Wear вызывали эти методы.
Полностью рабочий код доступен на GitHub (ветка master). В статьи приводятся только переработанные выдержки из него.
Для доступа извне к MediaSession требуется токен. Для этого научим сервис его отдавать
и пропишем в манифест
MediaController
Теперь реализуем activity с кнопками управления. Создаем экземпляр MediaController и передаем в конструктор полученный из сервиса токен.
MediaController предоставляет как методы управления плеером play, pause, stop, так и коллбэки onPlaybackStateChanged(PlaybackState state) и onMetadataChanged(MediaMetadata metadata). К одному MediaSession могут подключиться несколько MediaController, таким образом можно легко обеспечить консистентность состояний кнопок во всех окнах.
Наша activity работает, но ведь идея исходно была, чтобы из окна блокировки тоже можно было управлять. И тут мы приходим к важному моменту: в API 21 полностью переделали окно блокировки, теперь там отображаются уведомления и кнопки управления плеером надо делать через уведомления. К этому мы вернемся позже, давайте пока рассмотрим старое окно блокировки.
Как только мы вызываем mediaSession.setActive(true), система магическим образом присоединяется без всяких токенов к MediaSession и показывает кнопки управления на фоне картинки из метаданных.
Однако в силу исторических причин события о нажатии кнопок приходят не напрямую в MediaSession, а в виде бродкастов. Соответственно, нам надо еще подписаться на эти бродкасты и перебросить их в MediaSession.
MediaButtonReceiver
Для этого разработчики Android любезно предлагают нам воспользоваться готовым ресивером MediaButtonReceiver.
Добавим его в манифест
MediaButtonReceiver при получении события ищет в приложении сервис, который также принимает «android.intent.action.MEDIA_BUTTON» и перенаправляет его туда. Поэтому добавим аналогичный интент-фильтр в сервис
Если подходящий сервис не найден или их несколько, будет выброшен IllegalStateException.
Теперь в сервис добавим
Метод handleIntent анализирует коды кнопок из intent и вызывает соответствующие коллбэки в mediaSession. Получилось немного плясок с бубном, но зато почти без написания кода.
На системах с API >= 21 система не использует бродкасты для отправки событий нажатия на кнопки и вместо этого напрямую обращается в MediaSession. Однако, если наш MediaSession неактивен (setActive(false)), его пробудят бродкастом. И для того, чтобы этот механизм работал, надо сообщить MediaSession, в какой ресивер отправлять бродкасты.
Добавим в onCreate сервиса
На системах с API Так это выглядит
Android 4.4
MIUI 8 (базируется на Android 6, то есть теоретически окно блокировки не должно отображать наш трек, но здесь уже сказывается кастомизация MIUI).
Уведомления
Однако, как ранее упоминалось, начиная с API 21 окно блокировки научилось отображать уведомления. И по этому радостному поводу, вышеописанный механизм был выпилен. Так что теперь давайте еще формировать уведомления. Это не только требование современных систем, но и просто удобно, поскольку пользователю не придется выключать и включать экран, чтобы просто нажать паузу. Заодно применим это уведомление для перевода сервиса в foreground-режим.
Нам не придется рисовать кастомное уведомление, поскольку Android предоставляет специальный стиль для плееров — Notification.MediaStyle.
Добавим в сервис два метода
И добавим вызов refreshNotificationAndForegroundStatus(int playbackState) во все коллбэки MediaSession.
Android 4.4
Android 7.1.1
Android Wear
Started service
В принципе у нас уже все работает, но есть засада: наша activity запускает сервис через binding. Соответственно, после того, как activity отцепится от сервиса, он будет уничтожен и музыка остановится. Поэтому нам надо в onPlay добавить
Никакой обработки в onStartCommand не надо, наша цель не дать системе убить сервис после onUnbind.
А в onStop добавить
В случае, если к сервису привязаны клиенты, stopSelf ничего не делает, только взводит флаг, что после onUnbind сервис можно уничтожить. Так что это вполне безопасно.
ACTION_AUDIO_BECOMING_NOISY
Продолжаем полировать сервис. Допустим пользователь слушает музыку в наушниках и выдергивает их. Если эту ситуацию специально не обработать, звук переключится на динамик телефона и его услышат все окружающие. Было бы хорошо в этом случае встать на паузу.
Для этого в Android есть специальный бродкаст AudioManager.ACTION_AUDIO_BECOMING_NOISY.
Добавим в onPlay
В onPause и onStop
И по факту события встаем на паузу
Android Auto
Начиная с API 21 появилась возможность интегрировать телефон с экраном в автомобиле. Для этого необходимо поставить приложение Android Auto и подключить телефон к совместимому автомобилю. На экран автомобиля будет выведены крупные контролы для управления навигацией, сообщениями и музыкой. Давайте предложим Android Auto наше приложение в качестве поставщика музыки.
Если у вас под рукой нет совместимого автомобиля, что, согласитесь, иногда бывает, можно просто запустить приложение и экран самого телефона будет работать в качестве автомобильного.
Исходный код выложен на GitHub (ветка MediaBrowserService).
Прежде всего надо указать в манифесте, что наше приложение совместимо с Android Auto.
Добавим в манифест
Здесь automotive_app_desc — это ссылка на файл automotive_app_desc.xml, который надо создать в папке xml
Преобразуем наш сервис в MediaBrowserService. Его задача, помимо всего ранее сделанного, отдавать токен в Android Auto и предоставлять плейлисты.
Поправим декларацию сервиса в манифесте
Во-первых, теперь наш сервис экспортируется, поскольку к нему будут подсоединяться снаружи.
И, во-вторых, добавлен интент-фильтр android.media.browse.MediaBrowserService.
Меняем родительский класс на MediaBrowserServiceCompat.
Поскольку теперь сервис должен отдавать разные IBinder в зависимости от интента, поправим onBind
Имплементируем два абстрактных метода, возвращающие плейлисты
И, наконец, имплементируем новый коллбэк MediaSession
Здесь mediaId — это тот, который мы отдали в setMediaId в onLoadChildren.
Плейлист
Трек
UPDATE от 27.10.2017: Пример на GitHub переведен на targetSdkVersion=26. Из релевантных теме статьи изменений необходимо отметить следующее:
- android.support.v7.app.NotificationCompat.MediaStyle теперь deprecated. Вместо него следует использовать android.support.v4.media.app.NotificationCompat.MediaStyle. Соответственно, больше нет необходимости использовать android.support.v7.app.NotificationCompat, теперь можно использовать android.support.v4.app.NotificationCompat
- Метод AudioManager.requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) теперь тоже deprecated. Вместо него надо использовать AudioManager.requestAudioFocus(AudioFocusRequest focusRequest). AudioFocusRequest — новый класс, добавленный с API 26, поэтому не забывайте проверять на API level.
Создание AudioFocusRequest выглядит следующим образом
Теперь запрашиваем фокус
Разумеется, все вышеописанные изменения вносить необязательно, старые методы работать не перестали.
Вот мы и добрались до конца. В целом тема эта довольно запутанная. Плюс отличия реализаций на разных API level и у разных производителей. Очень надеюсь, что я ничего не упустил. Но если у вас есть, что исправить и добавить, с удовольствием внесу изменения в статью.
Еще очень рекомендую к просмотру доклад Ian Lake. Доклад от 2015 года, но вполне актуален.
Источник