Звуковые эффекты в приложениях Windows Phone 8

в 23:26, , рубрики: C#, c++, silverlight, sound, sound effect, windows phone, windows phone 8, windows runtime component, xaudio2, разработка под windows phone

Звуковые эффекты в приложениях Windows Phone 8 - 1 Несмотря на то, что подавляющему большинству приложений не требуется воспроизводить звуковые эффекты, иногда возникает ситуация, когда без звукового эффекта не обойтись. Тогда возникает закономерный вопрос, как воспроизвести звуковой эффект в приложении Windows Phone 8?

Обратившись к документации по Windows Phone Silverlight, можно найти следующие статьи Media for Windows Phone и Playing a sound effect. Исходя из содержания документов можно прийти к мнению, что есть только два способа воспроизведения эффектов в приложениях: использовать MediaElement или XNA. Рассмотрим каждый из этих способов более подробно.

Воспроизведение звуковых эффектов с помощью MediaElement

Самым простым и «родным» способом проиграть звуковой эффект, является использование элемента управления MediaElement. Данный элемент управления предоставляет широкие возможности для воспроизведения звукового и видео содержимого и может быть использован для наших целей.

Для использования элемента управления его необходимо добавить в визуальное дерево текущей страницы:

Для этого необходимо добавить в XAML страницы следующий код:

<MediaElement x:Name="viewMediaElement" AutoPlay="True" />

Или создать элемент управления из cedebehind страницы используя следующий код:

private MediaElement _mediaElement;

private void CreateMediaElement()
{
	_mediaElement = new MediaElement
	{
		AutoPlay = true
	};
	LayoutRoot.Children.Add(_mediaElement);
}

Из кода можно заметить, что у элемента управления установлено свойство AutoPlay, это переключает элемент управления в режим немедленного воспроизведения переданного содержимого.

Для воспроизведения звукового фала необходимо передать его адрес в свойство Source элемента управления.

private void PlaySoundClick(object sender, RoutedEventArgs e)
{
viewMediaElement.Source = new Uri("/Assets/sound.wav", UriKind.Relative);
}

Для установки значения свойства Source можно использовать связывание данных XAML, что позволит полностью исключить codebehind содержимое.

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

  • Простота реализации и использования
  • Воспроизведение содержимого файлов в любом из поддерживаемых операционной системой форматов

Недостатки:

  • Элемент управления необходимо добавлять в визуальное дерево приложения.
  • Этим способом можно воспроизводить только один звук в момент времени, в документации указано, что допускается использование только одного элемента управления.
  • Воспроизведение звуков привязано к пользовательскому интерфейсу и требует синхронизации с UI потоком.
  • Невозможно организовать бесшовное циклическое воспроизведение.

Воспроизведение звуковых эффектов с помощью XNA

Для воспроизведения звуковых эффектов, можно использовать библиотеку XNA. Несмотря на то, что данная библиотека разрабатывалась для работы в рамках инфраструктуры XNA при разработке игровых проектов, ее можно использовать в приложениях Windows Phone для воспроизведения звуков.

Сделать это достаточно просто:

private void PlaySoundClick(object sender, RoutedEventArgs e)
{
	var stream = TitleContainer.OpenStream("Assets/sound.wav");
	var soundEffect = SoundEffect.FromStream(stream);
	var instance = soundEffect.CreateInstance();

	FrameworkDispatcher.Update();

	instance.Play();
}

Обратите внимание на два интересных момента. Вызывать метод CreateInstance для звукового эффекта не обязательно, проиграть его можно просто вызвав метод Play() у экземпляра SoundEffect, однако только явно созданный экземпляр эффекта позволяет полностью управлять эффектом и производить его точную настройку. Незнание этого момента привело к появлению распространенного заблуждения WP SL программистов, что SoundEffect неуправляем после того, как запущено проигрывание.

Вызов метода FrameworkDispatcher.Update() необходимо для эмуляции игрового цикла XNA, который обязателен для корректной работы библиотеки XNA.

Достоинства воспроизведения звука с использованием XNA SoundEffect:

  • Простота использования.
  • Расширенные настройки звуковых эффектов, такие как громкость, скорость, повторение и т.п.

Недостатки:

  • Устаревшая технология, которая больше не поддерживается компанией Microsoft.
  • Воспроизведение только файлов WAV в PCM формате, который занимает большой объем памяти.
  • Необходимость дополнительных манипуляций, в виде эмуляции игрового цикла.

Использование XAudio2 для воспроизведения звуковых эффектов

К сожалению, других способов воспроизведения звуковых эффектов, для приложений WP 8 доступных из коробки не существует. Однако для native приложений доступна низкоуровневая технология воспроизведения звуковых эффектов XAudio2. Эти технология позиционируется как технология воспроизведения звуковых эффектов в играх, однако ее можно использовать для воспроизведения звуковых эффектов в приложениях Windows Phone 8.

Так как использовать XAudio2 напрямую из приложения WP невозможно. Разработаем простой компонент Windows Runtime, который предоставит приложению WP Silverlight возможность воспроизводить звуковые эффекты.

Компоненты Windows Runtime для операционной системы Windows Phone 8 разрабатываются только c использованием языка C++.

Добавим новый проект Windows Runtime Component (Windows Phone 8.0)

Звуковые эффекты в приложениях Windows Phone 8 - 2

Для корректной сборки проекта необходимо указать линковщику, что необходимо использовать библиотеку xaudio2.lib. Для этого необходимо открыть свойства проекта, на вкладке Linker -> Input и добавить библиотеку xaudio2.lib в список Additional Dependencies

Звуковые эффекты в приложениях Windows Phone 8 - 3

Определим класс создаваемого компонента:

public ref class SoundEffect sealed
{
private:
	std::shared_ptr<WaveData> _waveData;

	Microsoft::WRL::ComPtr<IXAudio2> _audioEngine;
	IXAudio2MasteringVoice* _masteringVoice;
	IXAudio2SourceVoice* _sourceVoice;

public:
	SoundEffect(const Array<byte>^ source);

	void Play();
	void Stop();
};

Конструктор класса принимает единственный параметр, массив байт с содержимым звукового фала в формате PCM или ADPCM. Для управления звуковым эффектом предназначены методы Play и Stop. При вызове метода Play эффект должен воспроизводиться с начала, при вызове Stop воспроизведение эффекта должно останавливаться.

Поля класса предназначены для хранения ссылок на компоненты XAudio2 и описания звукового файла WaveData. Компонент IXAudio2 является COM-объектом, поэтому для его хранения используется контейнер Microsoft::WRL::ComPrt обеспечивающий корректную работу механизма подсчета ссылок и освобождение объекта. Остальные указатели на объекты XAudio2 не используют ComPtr и подсчет ссылок их время жизни контролируется объектом IXAudio2.

Инициализируем XAudio2

SoundEffect::SoundEffect(const Array<byte>^ source)
{
	_waveData = std::make_shared<WaveData>(source);

	auto hr = XAudio2Create(&_audioEngine);
	if (FAILED(hr))
	{
		throw ref new Platform::Exception(hr);
	}
	
	hr = _audioEngine->CreateMasteringVoice(&_masteringVoice);
	if (FAILED(hr))
	{
		throw ref new Platform::Exception(hr);
	}

	hr = _audioEngine->CreateSourceVoice(&_sourceVoice, _waveData->Format);
	if (FAILED(hr))
	{
		throw ref new Platform::Exception(hr);
	}
	
	hr = _audioEngine->StartEngine();
	if (FAILED(hr))
	{
		throw ref new Platform::Exception(hr);
	}
}

Класс WaveData будет рассмотрен ниже, сейчас достаточно знать, что он извлекает из звукового файла в формате wav информацию о формате звуковых данных и сами звуковые данные.

Первым шагом вызвав функцию XAudio2Create получим ссылку на объект, реализующий интерфейс IXAudio2.

Следующим шагом будет создание объекта, реализующего интерфейс IXAudioMasteringVoice, этот объект отвечает за объединение в единый звуковой поток данных из исходных голосов. По сути этот объект представляет собой звуковой микшер.

Вызвав метод CreateSourceVoice создадим исходный голос, представляющий звуковой поток из файла звукового эффекта. При создании исходного голоса, необходимо указать формат аудиоданных, которые будут переданы ему для воспроизведения.

Подробнее о голосах XAudio2 читайте на XAudio2 Voices.

Последним шагом инициализации XAudio2 является вызов метода, StartEngine запускающего обработку звуковых эффектов ядром библиотеки.

Методы начала и окончания воспроизведения эффекта

void SoundEffect::Play()
{
	Stop();

	XAUDIO2_BUFFER playBuffer = { 0 };
	playBuffer.AudioBytes = _waveData->Length;
	playBuffer.pAudioData = _waveData->Data;
	playBuffer.Flags = XAUDIO2_END_OF_STREAM;

	auto hr = _sourceVoice->SubmitSourceBuffer(&playBuffer);
	if (SUCCEEDED(hr))
	{
		hr = _sourceVoice->Start(0, XAUDIO2_COMMIT_NOW);
	}
}

void SoundEffect::Stop()
{
	auto hr = _sourceVoice->Stop();
	if (SUCCEEDED(hr))
	{
		_sourceVoice->FlushSourceBuffers();
	}
}

Перед началом воспроизведения необходимо заполнить структуру XAUDIO2_BUFER данными звукового файла и установить ее при помощи метода SubmitSourceBuffer в качестве источника данных для исходного голоса.

И последним шагом запускаем голос на воспроизведение, методом Start;

Фактически устанавливать источник данных голоса можно один раз, потом только запускать проигрывание методом Play, однако я встречал ситуацию некорректно отработавшего метода Stop и как результат последующие ошибки при попытке начать воспроизведение. Установка исходного буфера при каждом воспроизведении решила эту проблему.

Подготовка и загрузка аудио файлов

Библиотека XAudio2 на WP 8 может воспроизводить звуковые данные в форматах PCM и ADPCM. Для подготовки файлов ADPCM необходимо использовать утилиту adpcmencode.exe, ее использование гарантирует совместимость полученного файла с библиотекой XAudio2.

Фактически я не смог подготовить ни одного файла в формате ADPCM используя сторонние утилиты, все они оказались не совместимы, не смотря на формальное соответствие требований.

Из звукового файла wav необходимо извлечь два обязательных компонента: формат аудиоданных и непосредственно данные звукового потока в формате PCM или ADPCM. Для получения указанных компонентов необходимо разобрать wav файл.

Wav файл представляет собой Riff контейнер, подробнее о котором можно почитать: WIKI RIFF, Resource Interchange File Format Services и ADPCM RIFF.

За извлечение данных из wav файла и их последующее хранение отвечает класс WavData.

private struct WaveData
{
private:
	const Array<byte>^ _buffer;
	
	ChunkInfo FindChunk(const Array<byte>^ buffer, const RiffType& chunkType) const;
	void Read(const Array<byte>^ buffer);

public:
	WaveData(const Array<byte>^ buffer);

	const WAVEFORMATEX* Format;
	const byte* Data;
	uint32 Length;
};

При разборе файла класс проверяет:

  1. файл содержит WAVE данные
  2. файл содержит frm (формат) часть и что ее размер не меньше размера структуры WAVEFORMATEX
  3. формат звуковых данных PCM или ADPCM
  4. файл содержит data часть

Указатели на данные полученные в пунктах 2 и 4 сохраняются для последующего использования.

Использование созданного компонента

Подключим созданный компонент к приложению WP Silverlight и используем его для воспроизведения звукового эффекта:

private async void PlaySoundClick(object sender, RoutedEventArgs e)
{
	var soundEffect = await CreateSoundEffectFromFile("ms-appx:///Assets/sound.wav");
	soundEffect.Play();
}

private async Task<XAudio2SoundEffect.SoundEffect> CreateSoundEffectFromFile(string fileUri)
{
	if (string.IsNullOrWhiteSpace(fileUri))
	{
		return null;
	}

	try
	{
		var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri(fileUri));
		var buffer = await ReadFile(file);

		return new XAudio2SoundEffect.SoundEffect(buffer);
	}
	catch (Exception ex)
	{
		return null;
	}
}

private async Task<byte[]> ReadFile(StorageFile file)
{
	using (var stream = await file.OpenReadAsync())
	using (var reader = new DataReader(stream))
	{
		var buffer = new byte[stream.Size];

		await reader.LoadAsync((uint)stream.Size);
		reader.ReadBytes(buffer);

		return buffer;
	}
}

Вместо заключения

Создав компонент использующий для проигрывания звуковых эффектов библиотеку XAudio2, мы избавились от устаревших библиотек XNA и избавились от недостатков присущих MediaElement.

Созданный компонент реализует самые простейшие функции доступные разработчику при использовании XAudio2, такие как: управление громкостью, управление скоростью воспроизведения, циклическое воспроизведение, звуковые эффекты и многое другое.

Исходные коды тестового проекта доступны на Github: https://github.com/Viacheslav01/WPSoundEffect

Автор: Viacheslav01

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js