Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Sounds Of Android - AndroidMakers 2018

The Sounds Of Android - AndroidMakers 2018

Sound on Android is a topic that is rarely covered, which is why I wanted to shed some lights in my experience with the sound management APIs on Android.

So in this talk, we'll touch on how sounds actually work programmatically, and we'll talk about how to play a sound on Android in the most simple way. We will also cover the principle of sound focus, what it is, how and when to use it. We'll see what we can do with MIDI as well. And finally, we'll go deeper in the rabbit hole and get introduced to the lower levels of sounds processing with the help of OpenSL ES and the SuperPowered library.

Yannick Lemin

April 23, 2018
Tweet

More Decks by Yannick Lemin

Other Decks in Technology

Transcript

  1. Noise generator: Yannick Lemin • Android developer freelance • GDG

    Brussels Organizer • @android_leaks • Noise Maker • @theyann
  2. d

  3. Step 4.1 - MediaPlayer class MainActivity : AppCompatActivity() { private

    var player: MediaPlayer? = null override fun onCreate(savedInstanceState: Bundle?) { ... findViewById<View>(R.id.button_play).setOnClickListener { play() } } override fun onStop() { super.onStop() player?.release() } private fun play() { player?.release() player = MediaPlayer.create(this, R.raw.best_fart_ever).apply { setOnCompletionListener { release() } start() } } }
  4. MediaPlayer • Very simple to use • Exists since API1

    • Handles resources, over the network streaming, even with DRM PROS
  5. MediaPlayer • Very simple to use • Exists since API1

    • Handles resources, over the network streaming, even with DRM PROS • Not extensible • Dependent on OS for bug fixes and new features CONS
  6. class MainActivity : AppCompatActivity() { private lateinit var player: ExoPlayer

    override fun onCreate(savedInstanceState: Bundle?) { ... player = ExoPlayerFactory.newSimpleInstance(this, DefaultTrackSelector()) } ... private fun play() { stop() with (player) { val source = ExtractorMediaSource.Factory( DefaultDataSourceFactory(this@MainActivity, "fartheaven") ).createMediaSource(Uri.parse("asset:///best_fart_ever.m4a")) prepare(source) playWhenReady = true } } } Step 4.2 - ExoPlayer
  7. ExoPlayer • Much more flexible and extensible • Better suited

    for complex use cases • External library • Handles caching, adaptive playback, composition, etc PROS
  8. ExoPlayer • Much more flexible and extensible • Better suited

    for complex use cases • External library • Handles caching, adaptive playback, composition, etc PROS • More complex to apprehend • MinSdk 16 (or 19 for encryption support) • Big library CONS
  9. private fun initSoundPool() { soundPool = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)

    { SoundPool(1, AudioManager.STREAM_MUSIC, 0) } else { val attributes = AudioAttributesCompat.Builder() .setUsage(AudioAttributesCompat.USAGE_MEDIA) .setContentType(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION) .build() SoundPool.Builder() .setAudioAttributes(attributes.unwrap() as AudioAttributes) .setMaxStreams(1) .build() } soundPool.setOnLoadCompleteListener({ _, _, status -> loadComplete = status == 0 if (loadComplete) { play() } }) soundId = soundPool.load(this, R.raw.best_fart_ever, 1) } Step 4.3 - SoundPool
  10. private fun initSoundPool() { soundPool = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)

    { SoundPool(1, AudioManager.STREAM_MUSIC, 0) } else { val attributes = AudioAttributesCompat.Builder() .setUsage(AudioAttributesCompat.USAGE_MEDIA) .setContentType(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION) .build() SoundPool.Builder() .setAudioAttributes(attributes.unwrap() as AudioAttributes) .setMaxStreams(1) .build() } soundPool.setOnLoadCompleteListener({ _, _, status -> loadComplete = status == 0 if (loadComplete) { play() } }) soundId = soundPool.load(this, R.raw.best_fart_ever, 1) } Step 4.3 - SoundPool
  11. private fun initSoundPool() { soundPool = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)

    { SoundPool(1, AudioManager.STREAM_MUSIC, 0) } else { val attributes = AudioAttributesCompat.Builder() .setUsage(AudioAttributesCompat.USAGE_MEDIA) .setContentType(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION) .build() SoundPool.Builder() .setAudioAttributes(attributes.unwrap() as AudioAttributes) .setMaxStreams(1) .build() } soundPool.setOnLoadCompleteListener({ _, _, status -> loadComplete = status == 0 if (loadComplete) { play() } }) soundId = soundPool.load(this, R.raw.best_fart_ever, 1) } Step 4.3 - SoundPool
  12. private fun initSoundPool() { soundPool = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)

    { SoundPool(1, AudioManager.STREAM_MUSIC, 0) } else { val attributes = AudioAttributesCompat.Builder() .setUsage(AudioAttributesCompat.USAGE_MEDIA) .setContentType(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION) .build() SoundPool.Builder() .setAudioAttributes(attributes.unwrap() as AudioAttributes) .setMaxStreams(1) .build() } soundPool.setOnLoadCompleteListener({ _, _, status -> loadComplete = status == 0 if (loadComplete) { play() } }) soundId = soundPool.load(this, R.raw.best_fart_ever, 1) } Step 4.3 - SoundPool
  13. private fun play() { val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager

    val actualVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) val volume = (actualVolume / maxVolume).toFloat() soundPool.play(soundId, volume, volume, priority, loopCount, speed) } Step 4.3 - SoundPool
  14. MediaRecorder fileName = "${externalCacheDir.absolutePath}/best_fart_ever.3gp" private fun record() { recorder =

    MediaRecorder().apply { setAudioSource(MediaRecorder.AudioSource.MIC) setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP) setOutputFile(fileName) setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) try { prepare() start() } catch (e: IOException) { release() } } }
  15. To play it back private fun play() { player =

    MediaPlayer().apply { try { setOnCompletionListener { [email protected]() } setDataSource(fileName) prepare() start() } catch (e: IOException) { release() } } }
  16. AudioFocus …or how to make your audio app not be

    a jackass and work well with the platform
  17. 1. You need a listener class AudioFocusListener(...) : AudioManager.OnAudioFocusChangeListener {

    override fun onAudioFocusChange(focusChange: Int) { when(focusChange) { AudioManager.AUDIOFOCUS_GAIN -> { // start playing or reset volume } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { // reduce volume } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { // pause without dropping focus } AudioManager.AUDIOFOCUS_LOSS -> { // focus was lost } } } }
  18. 1. You need a listener class AudioFocusListener(...) : AudioManager.OnAudioFocusChangeListener {

    override fun onAudioFocusChange(focusChange: Int) { when(focusChange) { AudioManager.AUDIOFOCUS_GAIN -> { // start playing or reset volume } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { // reduce volume } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { // pause without dropping focus } AudioManager.AUDIOFOCUS_LOSS -> { // focus was lost } } } }
  19. 1. You need a listener class AudioFocusListener(...) : AudioManager.OnAudioFocusChangeListener {

    override fun onAudioFocusChange(focusChange: Int) { when(focusChange) { AudioManager.AUDIOFOCUS_GAIN -> { // start playing or reset volume } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { // reduce volume } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { // pause without dropping focus } AudioManager.AUDIOFOCUS_LOSS -> { // focus was lost } } } }
  20. 1. You need a listener class AudioFocusListener(...) : AudioManager.OnAudioFocusChangeListener {

    override fun onAudioFocusChange(focusChange: Int) { when(focusChange) { AudioManager.AUDIOFOCUS_GAIN -> { // start playing or reset volume } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { // reduce volume } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { // pause without dropping focus } AudioManager.AUDIOFOCUS_LOSS -> { // focus was lost } } } }
  21. 1. You need a listener class AudioFocusListener(...) : AudioManager.OnAudioFocusChangeListener {

    override fun onAudioFocusChange(focusChange: Int) { when(focusChange) { AudioManager.AUDIOFOCUS_GAIN -> { // start playing or reset volume } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { // reduce volume } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { // pause without dropping focus } AudioManager.AUDIOFOCUS_LOSS -> { // focus was lost } } } }
  22. 2. Request the focus private fun requestAudioFocus() { audioFocusListener =

    AudioFocusListener(…) val requestResult = audioManager.requestAudioFocus(audioFocusListener, AudioManager.STREAM_MUSIC AudioManager.AUDIOFOCUS_GAIN) if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { // start playing } else { // handle error } }
  23. 2. Request the focus … API >= 26 val attributes

    = AudioAttributesCompat.Builder() .setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC) .setUsage(AudioAttributesCompat.USAGE_MEDIA) .build() // keep the request around request = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) .setAudioAttributes(attributes.unwrap as AudioAttributes) .setOnAudioFocusChangeListener(audioFocusListener) .build() audioManager.requestAudioFocus(request)
  24. Content Type and Usage AudioAttributesCompat.CONTENT_TYPE_SPEECH AudioAttributesCompat.CONTENT_TYPE_MUSIC AudioAttributesCompat.CONTENT_TYPE_MOVIE AudioAttributesCompat.CONTENT_TYPE_SONIFICATION AudioAttributesCompat.USAGE_MEDIA AudioAttributesCompat.USAGE_VOICE_COMMUNICATION

    AudioAttributesCompat.USAGE_VOICE_COMMUNICATION_SIGNALLING AudioAttributesCompat.USAGE_ALARM AudioAttributesCompat.USAGE_NOTIFICATION AudioAttributesCompat.USAGE_NOTIFICATION_RINGTONE AudioAttributesCompat.USAGE_NOTIFICATION_COMMUNICATION_REQUEST AudioAttributesCompat.USAGE_NOTIFICATION_COMMUNICATION_INSTANT AudioAttributesCompat.USAGE_NOTIFICATION_COMMUNICATION_DELAYED AudioAttributesCompat.USAGE_NOTIFICATION_EVENT AudioAttributesCompat.USAGE_ASSISTANCE_ACCESSIBILITY AudioAttributesCompat.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE AudioAttributesCompat.USAGE_ASSISTANCE_SONIFICATION AudioAttributesCompat.USAGE_GAME AudioAttributesCompat.USAGE_VIRTUAL_SOURCE AudioAttributesCompat.USAGE_ASSISTANT
  25. 3. Abandon when you’re done // API < 26 audioManager.abandonAudioFocus(audioFocusListener)

    // API >= 26 audioManager.abandonAudioFocusRequest(request)
  26. AudioTrack API < 26 AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, // most likely 44.1

    Khz AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, numberOfSamples, AudioTrack.MODE_STATIC)
  27. AudioTrack usage fun play() { val audioTrack = initAudioTrack() val

    someSoundDataInByteArray = getSoundDataSomewhere() audioTrack.write(someSoundsInByteArray, startOffset, bufferSize) }
  28. Effect Usage val reverb = PresetReverb(priority, audioSession).apply { preset =

    PresetReverb.PRESET_LARGEHALL enabled = true ... } audioTrack.apply { attachAuxEffect(reverb.id) setAuxEffectSendLevel(1.0F) ... }
  29. Good Reads • Understanding MediaSession http://bit.ly/mediasession • Building a Video

    Player app in Android http://bit.ly/ vplayerandroid • ADB Podcast Episode 85 Focus on Audio http://bit.ly/ adbaudiofocus • Styling Android’s MIDI posts http://bit.ly/midiandroid