Audio playing in android
Android smartphones offer amazing audio capabilities.
Following table shows a simplified version of the supported Android audio formats. There are many special rules and exceptions depending on device.
- Y indicates encoding or decoding is available (all SDK versions) for a codec.
- N indicates encoding is not available for a codec.
It can get confusing with all of the audio APIs and classes in Android. Let’s break it down. Following table summarizes the classes and APIs for Android audio. As you can see, many of these APIs have been around since the beginning of Android with API of level 1. Others have been added into Android more recently.
Name | Description | API | Level |
---|---|---|---|
AudioTrack | Low-level API, not meant to be real time. Used in most audio apps. Manages and plays a single audio resource for Java applications. Streaming/decoding of PCM audio buffers to the audio sink for playback by «pushing» the data to the AudioTrack object. Supports .wav playback. | 22 | Low |
AudioRecord | Manages the audio resources for Java applications to record audio from the hardware by «pulling» (reading) the data from the AudioRecord object. Can set rate, quality, encoding, channel config. | 22 | Low |
AudioManager | AudioManager provides access to volume and ringer mode control. | 1 | |
MediaPlayer | MediaPlayer class can be used to control playback of audio/video files and streams. Playback control of audio/video files and streams is managed as a state machine. | 1 | High |
MediaRecorder | High-level API used to record audio and video. The recording control is based on a simple state machine. Does not support .wav or .mp3. Generally better to use Audio Record for more flexibility. | 18 | High |
MediaStore | The media provider contains metadata for all available media on both internal and external storage devices. | 1 | |
MediaFormat | MediaFormat is useful to read encoded files and every detail that is connected to the content. The format of the media data is specified as string/value pairs. Keys common to all audio/video formats. | 16 | |
MediaCodec | MediaCodec class can be used to access low-level media codecs, such as encoder/decoder components. It is part of the Android low-level multimedia support infrastructure (normally used together with MediaExtractor, MediaSync, MediaMixer, MediaCrypto, MediaDrm, Image, Surface, and AudioTrack). | Low | |
SoundPool | SoundPool uses the MediaPlayer service to decode the audio into a raw 16-bit PCM stream and play the sound with very low latency, helping the CPU decompression effort. Multiple audio streams at once. | 8 | High |
AudioFormat | The AudioFormat class is used to access a number of audio formats and channel configuration constants that can be used in AudioTrack and AudioRecord. | 8 | |
TextToSpeech | Synthesizes speech from text for immediate playback or to create a sound file. The constructor for the TextToSpeech class, using the default TTS engine. | 4/21 | |
SpeechRecognition | This class provides access to the speech recognition service. The implementation of this API is likely to stream audio to remote servers to perform speech recognition. | 8 | |
MediaExtractor | MediaExtractor facilitates extraction of demuxed, typically encoded, media data from a data source. Reads bytes from the encoded data whether it is an online stream, embedded resources, or local files. | 16 |
The high-level APIs MediaPlayer , MediaRecorder and SoundPool are very useful and easy to use when you need to play and record audio without the need for low level controls. The low-level APIs AudioTrack and AudioRecord are excellent when you need low-level control over playing and recording audio.
As with most of the functions available on the Android platform, there is almost always more than one way to accomplish a given task. Audio is no exception. Following figure shows the various classes and APIs you can employ to accomplish common audio tasks.
No discussion on audio would be complete without talking about latency. Audio latency has been one of the most annoying issues on the entire platform.
Android developers are second-class citizens when it comes to audio latency on our mobile platform. Android audio latency has seen improvements but still lags other platforms. The most recent significant improvements include
- OpenSL supported was added on Android 2.3+.
- USB Audio is included in Android 5.0+ (API 21), but is not yet supported by most devices.
If you want to play audio in your Android apps, there are three APIs to choose from. You need to choose the approach that best matches your needs.
- MediaPlayer . Streams and decodes in real time for local or remote files. Good for long clips and applications such as background music. More CPU and resource intensive. Relatively long initialization time. MediaPlayer is a state machine!
- SoundPool . Good for short audio effects or clips. Stored uncompressed in memory, 1MB limit. Clips must be fully loaded before playing. Supports volume and speed control, looping, simultaneous sounds, priorities.
- AudioTrack . Lowest-level audio API on Android. Provides a channel you can configure. Push and pull byte data to the channel. Configure rate, samples, audio format, etc. You can decode audio in unsupported formats.
In summary, MediaPlayer is the good general-purpose class to play a file, SoundPool is well suited for short audio effects, and AudioTrack lets you get into the low-level audio configurations.
OPEN SL ES was included in Android starting at version 2.3. AudioTrack and AudioRecord APIs make use of OPEN SL ES.
MediaPlayer has a lot of functionality and yet it is pretty simple to just play simple sounds. Once you generate your audio URI from the resourceID , you just start the MediaPlayer . The code is shown below. Notice that it is a simple process to invoke the .setDataSource , .prepare , and .start methods on the MediaPlayer object.
Recall that SoundPool is ideal when you need to play short sounds.
Setup for SoundPool is a little more involved than with MediaPlayer because you have to load your sound, which can take a bit of time, so you use a listener to know when it is ready.
Once loaded, the onLoadComplete method will be triggered and you can then use the .play method to play the sound.
Notice the extra control you get on Priority, Volume, Repeat, and Frequency. This is one of the advantages for SoundPool . See the following key code:
AudioTrack is the low-level API for playing audio. The PlaySound function shown below uses AudioTrack to play the sound with soundID . Notice that AudioTrack is run on a thread.
Inside the thread, the first thing you do is set up the sample rate and buffer size parameters. Remember to match the buffer size and sample rate for your device. In the code below, the buffer size is automatically calculated using the .getMinBufferSize method, while the sample rate is specified directly as 44,100 Hz.
You then invoke the .play method on the object and then invoke the .write method on the AudioTrack object to copy the raw PCM audio data to the object.
You can see this is a much lower level approach to producing audio. But, this ability to directly read and write buffers to the audio hardware gives you a lot of power including the ability to encode and decode.
For example, you may have an .mp3 song that has a very long duration. What happens if you try to play it using SoundPool ? You will see that it is truncated after only a few seconds of playing. This is due to the SoundPool limitations with file size.
What happens if you try to play .mp3 files with AudioTrack ? You will see this leads to unhandled exceptions, as AudioTrack only works with raw PCM data.
Playing Audio with a Background Service
For playing background music in your apps, the background service is the best architecture. This architecture allows you to play long-running music tracks in the background while performing other more critical operations in the foreground.
Following app plays the track audio.mp3 in the background. The song file is stored locally in the /res/raw folder.
Once the app is launched, the song will begin to play. You can see there are some very basic controls available, including Play and Pause buttons.
There is also an undocumented feature that allows you to skip forward in the track. The skip forward can be accomplished by long-pressing the Pause button. Each time you long-press the Pause button, the track will skip forward 30 seconds. This type of function is often implemented by providing a SeekBar with seek forward and seek backward buttons to move the position within the track.
The project contains an activity MainActivity.java , and a service MusicService.java .
You need to register your service in the AndroidManifest.xml file:
Within the activity, three steps are required to interface with the service that will be running in the background.
- Bind the service to the activity.
- Start and connect to the service.
- Control the service from the activity.
The following code shows how the three steps are accomplished:
The services .resumeMusic and .pauseMusic methods are used within the activity to control the playing of the song by the service when the corresponding buttons are pressed.
Once you have the service defined in the AndroidManifest.xml, implementing the service is fairly straightforward.
Inside the service, you use MediaPlayer to play the song. It is the best choice for playing long audio files in a background service. Using MediaPlayer , you just need to create the object and specify the song you wish to play. Notice that there are a couple of extra methods you are using to set looping and volume controls.
With the service bound to the activity, you are free to control the playing of the song at any point during the lifecycle of the activity.
Following is MainActivity.
Following is a layout.
It is not always optimal to load up your app with audio samples. This is where audio synthesis comes into play. Pure Data and Csound are open source visual programming languages for sound synthesis and they run well on Android.
On Android, there are now three choices:
- Pure Data. Excellent stable library ported to Android by Google. Recommended as a general purpose synthesis engine for Android.
- Csound. Powerful synthesis engine. Higher learning curve and not quite as light a resource footprint as Pure Data, but can produce some amazing sounds. Chosen as the audio engine for the OLPC (One Laptop Per Child) initiative.
- Supercollider. An excellent choice for live coding. The Android library is not very stable at this time. It is an excellent synthesis engine, but unfortunately at this time there are just not enough Android resources available for me to recommend it.
Источник
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 года, но вполне актуален.
Источник