В этом уроке:

- работаем с SoundPool

 

На прошлом уроке мы рассмотрели MediaPlayer, который подходит для проигрывания продолжительных треков. SoundPool же подходит для случая, когда вам необходимо многократное воспроизведение небольших файлов. Самый простой пример – игры. Когда есть короткий звук какого-либо действия (прыжка, выстрела и т.п.) и этот звук достаточно часто необходимо воспроизводить. SoundPool один раз загружает этот звук в память и оттуда его воспроизводит, что, конечно, неплохо в плане производительности.

Класс SoundPool несложен. Позволяет загрузить звук в память, проиграть его, ставить паузу, регулировать громкость (левый и правый каналы), менять скорость воспроизведения, ограничивать кол-во одновременно воспроизводимых звуков, использовать приоритеты.

Напишем простое приложение, в котором рассмотрим все эти возможности.

 

Создадим проект:

Project name: P1271_SoundPool
Build Target: Android 2.3.3
Application name: SoundPool
Package name: ru.startandroid.develop.p1271soundpool
Create Activity: MainActivity

 

Добавим строки в strings.xml:

<string name="play">Play</string>

 

layout-файл main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	tools:context=".MainActivity">
	<Button
		android:id="@+id/btnPlay"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_alignParentLeft="true"
		android:layout_alignParentTop="true"
		android:onClick="onClick"
		android:text="@string/play">
	</Button>
</RelativeLayout>

Только кнопка для запуска воспроизведения.

 

Для тестов нам понадобится пара звуков. Я буду использовать explosion.ogg (взрыв) и shot.ogg (выстрел). Поместим explosion.ogg в папку assets, а shot.ogg в папку res/raw.

 

MainActivity.java:

package ru.startandroid.develop.p1271soundpool;

import java.io.IOException;


import android.app.Activity;
import android.media.AudioManager;
import android.media.SoundPool;
import android.media.SoundPool.OnLoadCompleteListener;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class MainActivity extends Activity implements OnLoadCompleteListener {
  
  final String LOG_TAG = "myLogs";
  final int MAX_STREAMS = 5;
  
  SoundPool sp;
  int soundIdShot;
  int soundIdExplosion;

  
  int streamIDShot;
  int streamIDExplosion;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    sp = new SoundPool(MAX_STREAMS, AudioManager.STREAM_MUSIC, 0);
    sp.setOnLoadCompleteListener(this);

    soundIdShot = sp.load(this, R.raw.shot, 1);
    Log.d(LOG_TAG, "soundIdShot = " + soundIdShot);
    
    try {
      soundIdExplosion = sp.load(getAssets().openFd("explosion.ogg"), 1);
    } catch (IOException e) {
      e.printStackTrace();
    }
    Log.d(LOG_TAG, "soundIdExplosion = " + soundIdExplosion);
    
  }
  
  public void onClick(View view) {
    sp.play(soundIdShot, 1, 1, 0, 0, 1);
    sp.play(soundIdExplosion, 1, 1, 0, 0, 1);
  }

  @Override
  public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
    Log.d(LOG_TAG, "onLoadComplete, sampleId = " + sampleId + ", status = " + status);
  }
  
}

Смотрим onCreate.

Сначала создаем SoundPool. На вход ему передаем:
- максимальное кол-во одновременно воспроизводимых файлов
- аудио-поток, который будет использоваться
- некий параметр качества, который пока что игнорируется системой. Рекомендуется передавать туда 0

Методом setOnLoadCompleteListener мы устанавливаем слушателя загрузки. Загрузка аудио-файлов происходит асинхронно, и по ее окончании срабатывает метод onLoadComplete этого слушателя.

Далее загружаем файлы. Для этого используются различные варианты метода load. Чтобы загрузить файл из raw, необходимо указать Context, ID raw-файла и приоритет. Приоритет пока что также игнорируется системой, рекомендуется передавать туда 1.

Чтобы загрузить файл из assets используем другую реализацию метода load, которая на вход требует AssetFileDescriptor и приоритет. AssetFileDescriptor можно получить, используя метод openFd класса AssetManager, указав имя файла. Приоритет снова никакой роли не играет, передаем 1.

У метода load есть несколько реализаций, я специально показал две разных для наглядности. В хелпе вы можете найти ту, которая вам более подходит.

Метод load возвращает нам ID загруженного файла. Используя этот ID, мы будем проигрывать файл.

 

В методе onClick запускаем воспроизведение файлов. Для этого используется метод play. На вход он требует ряд параметров:
- ID файла. Тот самый, который мы получили от метода load.
- громкость левого канала (от 0.0 до 1.0)
- громкость правого канала (от 0.0 до 1.0)
- приоритет. Этот приоритет уже не декоративный, а вполне себе используемый. Далее увидим, где он нужен.
- количество повторов. Т.е. файл будет воспроизведен один раз точно + то количество раз, которое вы здесь укажете
- скорость воспроизведения. Можно задать от половины нормальной скорости до двойной (0.5 - 2).

Метод play возвращает ID потока, используемого для проигрывания файла. Этот ID можно использовать для дальнейшего изменения настроек в процессе проигрывания файла, а также для паузы.

 

Метод onLoadComplete слушателя OnLoadCompleteListener выполняется, когда SoundPool загружает файл. На вход вы получаете сам SoundPool, ID файла (тот же, что и load возвращал) и статус (0, если успешно)

 

Все сохраним и запустим приложение.

В логах видим:

08:38:12.726: D/myLogs(333): soundIdShot = 1
08:38:12.726: D/myLogs(333): soundIdExplosion = 2
08:38:12.855: D/myLogs(333): onLoadComplete, sampleId = 1, status = 0
08:38:12.976: D/myLogs(333): onLoadComplete, sampleId = 2, status = 0

Видно, что загрузка звуков заняла определенное время. Жмем Play и слышим, как приложение играет оба звука одновременно.

 

Параметры метода play

Давайте поменяем громкость. Перепишем onClick:

  public void onClick(View view) {
    sp.play(soundIdShot, 0.1f, 1, 0, 0, 1);
    sp.play(soundIdExplosion, 1, 0.1f, 0, 0, 1);
  }

Теперь один звук более слышен слева, а другой справа.

 

Поменяем кол-во повторов:

  public void onClick(View view) {
    sp.play(soundIdShot, 1, 1, 0, 5, 1);
    sp.play(soundIdExplosion, 1, 1, 0, 2, 1);
  }

Теперь звук выстрела воспроизведен 6 раз (1 + 5 повторов), а звук взрыва – 3 раза (1 + 2 повтора).

 

Поменяем скорость:

  public void onClick(View view) {
    sp.play(soundIdShot, 1, 1, 0, 0, 0.5f);
    sp.play(soundIdExplosion, 1, 1, 0, 0, 2);
  }

Звук выстрела стал в два раза медленнее, а звук взрыва – в два раза быстрее.

 

Приоритет

Теперь давайте разберемся с приоритетом и кол-вом одновременно проигрываемых звуков. Хелп гласит:

"Priority runs low to high, i.e. higher numbers are higher priority. Priority is used when a call to play() would cause the number of active streams to exceed the value established by the maxStreams parameter when the SoundPool was created. In this case, the stream allocator will stop the lowest priority stream. If there are multiple streams with the same low priority, it will choose the oldest stream to stop. In the case where the priority of the new stream is lower than all the active streams, the new sound will not play and the play() function will return a streamID of zero."

Т.е. приоритет используется, когда кол-во звуков, которое необходимо воспроизводить в данный момент, превышает максимально допустимое кол-во (мы его указывали при создании SoundPool, константа MAX_STREAMS). В этом случае звуки с низким приоритетом будут остановлены. Если есть несколько звуков с одинаково низким приоритетом, то останавливаются более старые. Если максимальное количество звуков уже достигнуто, и вы пытаетесь воспроизвести звук с низким приоритетом, то он не будет проигран, а метод play вернет 0 вместо ID потока.

Давайте проведем тесты и понаблюдаем все это в действии.

Звука у нас всего два. Поэтому, чтобы добиться конфликта, давайте для нашего SoundPool поставим кол-во одновременно воспроизводимых звуков равное одному:

final int MAX_STREAMS = 1;

 

onClick перепишем так:

  public void onClick(View view) {
    streamIDShot = sp.play(soundIdShot, 1, 1, 0, 10, 1);
    Log.d(LOG_TAG, "streamIDShot = " + streamIDShot);
    try {
      TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    streamIDExplosion = sp.play(soundIdExplosion, 1, 1, 0, 0, 1);
    Log.d(LOG_TAG, "streamIDExplosion = " + streamIDExplosion);
  }

Я, конечно, понимаю, что ставить паузу в основном потоке это #непофэншуюивообщенекомильфо, но в данном уроке я считаю это допустимым, чтобы не городить лишний код. Так что не обращайте на это внимание )

Мы начнем проигрывать звук выстрела 11 раз, а спустя пару секунд запустим звук взрыва. Выстрелы к тому моменту еще не успеют отзвучать, поэтому поглядим, как поведет себя система в данной ситуации. Напоминаю, что у нас сейчас установлен лимит на кол-во звуков = 1.

Заодно будем выводить в лог ID потоков, которые возвращает нам метод play.

 

Все сохраняем, запускаем, жмем Play. Я слышу 4 выстрела, потом взрыв. Т.е. при попытке воспроизвести взрыв система видит, что надо одновременно играть два звука: уже звучащий - выстрел и новый - взрыв. А лимит позволяет в один момент времени играть только один звук. Приоритеты у звуков одинаковые. Следовательно, система останавливает более старый звук, в нашем случае это выстрелы.

В логах видим

09:24:35.035: D/myLogs(754): streamIDShot = 1
09:24:37.092: D/myLogs(754): streamIDExplosion = 2

Разница в две секунды. ID потоков получены по обоим звукам.

 

Попробуем выставить для выстрелов приоритет более высокий, чем у взрыва.

  public void onClick(View view) {
    streamIDShot = sp.play(soundIdShot, 1, 1, 1, 10, 1);
    Log.d(LOG_TAG, "streamIDShot = " + streamIDShot);
    try {
      TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    streamIDExplosion = sp.play(soundIdExplosion, 1, 1, 0, 0, 1);
    Log.d(LOG_TAG, "streamIDExplosion = " + streamIDExplosion);
  }

Нарисуем у выстрела приоритет = 1, а у взрыва остался 0.

 

Сохраняем, запускаем, жмем Play.

Прога воспроизводит все выстрелы. Звук взрыва вообще не воспроизводится. Как и в прошлый раз у системы был выбор, но в этот раз она отменила взрыв и продолжила играть выстрелы, т.к. приоритеты выстрелов выше.

Смотрим логи:

09:28:32.906: D/myLogs(788): streamIDShot = 1
09:28:34.934: D/myLogs(788): streamIDExplosion = 0

Видим, что для взрыва метод play вернул 0 вместо ID потока. Это значит, что звук не воспроизводился.

 

Пауза

Давайте посмотрим на возможности паузы. Мы можем приостановить какие-то определенные звуки, а можем все сразу.

Увеличим снова лимит звуков:

final int MAX_STREAMS = 5;

 

Перепишем onClick:

  public void onClick(View view) {
    streamIDShot = sp.play(soundIdShot, 1, 1, 0, 9, 1);
    streamIDExplosion = sp.play(soundIdExplosion, 1, 1, 0, 5, 1);

    try {
      TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    sp.pause(streamIDShot);

    try {
      TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    sp.resume(streamIDShot);
  }

Мы запускаем проигрывание 10 выстрелов и 6 взрывов. Через 2 секунды приостанавливаем выстрелы, а еще через 3 секунды возобновляем. Для этого используются методы pause и resume, на вход которым даем ID потока из play.

 

Сохраняем, запускаем, жмем Play. Слышим выстрелы и взрывы. Затем выстрелы стихают, остаются только взрывы. Затем выстрелы снова возобновляются и все стихает. Counter-terrorist win )

 

Попробуем приостановить все звуки.

  public void onClick(View view) {
    streamIDShot = sp.play(soundIdShot, 1, 1, 0, 9, 1);
    streamIDExplosion = sp.play(soundIdExplosion, 1, 1, 0, 4, 1);

    try {
      TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    sp.autoPause();

    try {
      TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    sp.autoResume();
  }

Для этого используем методы autoPause и autoResume. При проигрывании у нас будет пауза в три секунды, потом все возобновится.

 

Меняем уже играющий звук

Пока звук воспроизводится, вы можете повлиять на его характеристики, которые были заданы в методе play:

громкость  - setVolume
приоритет – setPriority
повторы - setLoop
скорость - setRate

На вход методам идет ID потока (который вернул play) и необходимое вам значение параметра.

 

Например, поработаем с громкостью:

  public void onClick(View view) {
    streamIDShot = sp.play(soundIdShot, 1, 0, 0, 9, 1);
    streamIDExplosion = sp.play(soundIdExplosion, 0, 1, 0, 4, 1);

    try {
      TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    sp.setVolume(streamIDShot, 0, 1);
    sp.setVolume(streamIDExplosion, 1, 0);
  }

 Выстрелы начнут играть в одном канале, взрывы  - в другом. А через две секунды они поменяются каналами.

 

Выгрузка

Осталось упомянуть про еще два метода.

unload – выгрузит указанный звук из памяти. На вход требует ID звука, который возвращал метод load.

release – освобождает все ресурсы SoundPool, более он не сможет быть используемым и ссылку на него необходимо об-null-ить.

 

 

На следующем уроке:

- используем Audio Focus


Присоединяйтесь к нам в Telegram:

- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование



Похожие статьи


Последние статьи



Language

Система Orphus

Социальные сети

 

Telegram канал



Android чат в Telegram



Группа ВКонтакте



Страница в Facebook

Поддержка проекта

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal

Яндекс.Метрика