📜 ⬆️ ⬇️

Sound effects in Windows Phone 8 applications

Despite the fact that the vast majority of applications do not need to play sound effects, sometimes there is a situation where you can not do without a sound effect. Then the natural question is how to reproduce the sound effect in the Windows Phone 8 application?

Referring to the Windows Phone Silverlight documentation, you can find the following Media for Windows Phone articles and the Playing a sound effect . Based on the content of the documents, you can come to the conclusion that there are only two ways to reproduce the effects in applications: use MediaElement or XNA. Consider each of these methods in more detail.

Playing Sound Effects with MediaElement


The easiest and “native” way to play a sound effect is to use the MediaElement control. This control provides ample opportunities to play audio and video content and can be used for our purposes.

To use the control, you need to add it to the visual tree of the current page:
')
To do this, add the following code to the XAML page:

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

Or create a control from the cedebehind page using the following code:

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

From the code, you can see that the control has the AutoPlay property set, this switches the control to immediate playback of the transferred content.

To play a sound file, you must pass its address to the Source property of the control.

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

To set the value of the Source property, you can use XAML data binding, which allows you to completely eliminate codebehind content.

The advantages of using this method of playing sound effects:


Disadvantages:


Playing Sound Effects with XNA


To play sound effects, you can use the XNA library. Despite the fact that this library was designed to work within the framework of the XNA infrastructure when developing game projects, it can be used in Windows Phone applications for playing sounds.

This is quite simple:

 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(); } 

Pay attention to two interesting points. It is not necessary to call the CreateInstance method for a sound effect, you can play it simply by calling the PlayEl method of the SoundEffect instance, but only an explicitly created effect instance allows you to fully control the effect and fine-tune it. Ignorance of this moment led to the common misconception of WP SL programmers that SoundEffect is out of control after playback is started.

Calling the FrameworkDispatcher.Update () method is necessary to emulate the XNA gaming cycle, which is required for the XNA library to work correctly.

Advantages of sound reproduction using XNA SoundEffect:


Disadvantages:


Using XAudio2 to play sound effects


Unfortunately, there are no other ways to play sound effects for WP 8 applications available out of the box. However, low-level XAudio2 sound effects playback technology is available for native applications. This technology is positioned as a technology for playing sound effects in games, but it can be used to play sound effects in Windows Phone 8 applications.

Since XAudio2 cannot be used directly from a WP application. We will develop a simple Windows Runtime component that will allow the WP Silverlight application to play sound effects.

The Windows Runtime components for the Windows Phone 8 operating system are developed only using C ++.

Add a new project Windows Runtime Component (Windows Phone 8.0)



For the correct assembly of the project, you must specify the linker to use the xaudio2.lib library. To do this, open the project properties in the Linker -> Input tab and add the xaudio2.lib library to the Additional Dependencies list.



Define the class of the component being created:

 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(); }; 


The class constructor takes a single parameter, an array of bytes with the contents of the sound file in PCM or ADPCM format. To control the sound effect, the Play and Stop methods are used. When the Play method is called, the effect should be played from the beginning, and when the Stop is called, the effect should stop playing.

Class fields are used to store references to XAudio2 components and a description of the WaveData sound file. The IXAudio2 component is a COM object, so the Microsoft :: WRL :: ComPrt container is used to store it, which ensures that the reference counting mechanism and the object are released correctly. The remaining pointers to XAudio2 objects do not use ComPtr and reference counting their lifetime is controlled by the object IXAudio2.

Initialize 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); } } 

The WaveData class will be discussed below, now it’s enough to know that it extracts information about the format of the audio data and the audio data from the sound file in wav format.

The first step by calling the XAudio2Create function is to get a reference to the object that implements the IXAudio2 interface.

The next step is to create an object that implements the IXAudioMasteringVoice interface, this object is responsible for combining data from the original voices into a single audio stream. In essence, this object is a sound mixer.

By calling the CreateSourceVoice method, we will create a source voice representing the sound stream from the sound effect file. When creating the original voice, you must specify the format of the audio data that will be transferred to it for playback.

Read more about XAudio2 voices on XAudio2 Voices .

The final step in XAudio2 initialization is to call the StartEngine method to start processing sound effects from the library core.

Methods for starting and ending effect playback

 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(); } } 

Before starting playback, you need to fill the XAUDIO2_BUFER structure with audio file data and set it using the SubmitSourceBuffer method as a data source for the original voice.

And the last step is to start the voice playback, the Start method;

In fact, you can set the source of voice data once, then just start playback using the Play method, however I encountered the situation with an incorrectly completed Stop method and as a result subsequent errors when trying to start playback. Setting the source buffer at each playback solved this problem.

Preparation and loading of audio files


The XAudio2 library on WP 8 can play PCM and ADPCM audio data. To prepare ADPCM files, you must use the adpcmencode.exe utility, its use ensures compatibility of the resulting file with the XAudio2 library.

In fact, I was not able to prepare a single file in the ADPCM format using third-party utilities, they were all incompatible, despite the formal compliance of the requirements.

Two essential components must be extracted from the wav sound file: the audio data format and the audio stream data directly in PCM or ADPCM format. To obtain the specified components, you must parse the wav file.

The wav file is a Riff container, details of which can be read: WIKI RIFF , Resource Interchange File Format Services and ADPCM RIFF .

The class WavData is responsible for extracting data from a wav file and their subsequent storage.

 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; }; 


When parsing a file, the class checks:

  1. file contains wave data
  2. the file contains the frm (format) part and that its size is not less than the size of the WAVEFORMATEX structure
  3. PCM or ADPCM audio data format
  4. file contains data part

Pointers to the data obtained in paragraphs 2 and 4 are stored for later use.

Using the created component


Connect the created component to the WP Silverlight application and use it to play a sound effect:

 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; } } 


Instead of conclusion


By creating a component that uses the XAudio2 library to play sound effects, we got rid of obsolete XNA libraries and got rid of the shortcomings inherent in MediaElement.

The created component implements the simplest sound effect control functions. The developer has access to all the features provided by XAudio2, such as: volume control, playback speed control, loop playback, sound effects and more.

Test project source codes are available on Github: https://github.com/Viacheslav01/WPSoundEffect

Source: https://habr.com/ru/post/252829/


All Articles