Skip to content

Instantly share code, notes, and snippets.

@milannankov
Last active August 29, 2015 14:13
Show Gist options
  • Save milannankov/e7c5130c469f02334868 to your computer and use it in GitHub Desktop.
Save milannankov/e7c5130c469f02334868 to your computer and use it in GitHub Desktop.
Universal Audio Component Blog Snippets - http://www.newventuresoftware.com
struct AudioData
{
byte * bytes;
unsigned int numberOfBytes;
WAVEFORMATEX* waveFormat;
// loop information
unsigned int loopStart;
unsigned int loopLength;
};
private async Task<IBuffer> GetBuffer(string sampleName)
{
IBuffer buffer = null;
if (!this.buffers.ContainsKey(sampleName))
{
var path = String.Format("ms-appx:///Assets/{0}.wav", sampleName);
var audioFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(path));
buffer = await FileIO.ReadBufferAsync(audioFile);
this.buffers[sampleName] = buffer;
}
return this.buffers[sampleName];
}
AudioData RiffReader::Read(IBuffer^ buffer)
{
auto formatChunk = FindChunk(buffer, fourccFMT);
auto dataChunk = FindChunk(buffer, fourccDATA);
auto waveFormat = reinterpret_cast<WAVEFORMATEX*>(formatChunk.data);
AudioData data;
data.bytes = dataChunk.data;
data.numberOfBytes = dataChunk.size;
data.waveFormat = waveFormat;
// read loop data
this->SetLoopData(buffer, data);
return data;
}
// new method that reads the 'smpl' chunk
void RiffReader::SetLoopData(IBuffer^ buffer, AudioData& data)
{
auto sampleChunk = FindChunk(buffer, fourccSAMPLE);
data.loopLength = 0;
data.loopStart = 0;
if (sampleChunk.id != fourccSAMPLE)
{
return;
}
if (sampleChunk.size < sizeof(RIFFMIDISample))
{
return;
}
auto midiSample = reinterpret_cast<const RIFFMIDISample*>(sampleChunk.data);
auto loops = reinterpret_cast<const MIDILoop*>(sampleChunk.data + sizeof(RIFFMIDISample));
for (unsigned int i = 0; i < midiSample->loopCount; i++)
{
if (loops[i].type == MIDILoop::LOOP_TYPE_FORWARD)
{
// Return 'forward' loop
data.loopStart = loops[i].start;
data.loopLength = loops[i].end + loops[i].start + 1;
}
}
}
XAUDIO2_BUFFER UniversalAudioPlayer::CreateAudioBuffer(AudioData data)
{
XAUDIO2_BUFFER buffer = { 0 };
buffer.AudioBytes = data.numberOfBytes;
buffer.pAudioData = data.bytes;
buffer.Flags = XAUDIO2_END_OF_STREAM;
buffer.LoopCount = XAUDIO2_LOOP_INFINITE;
// set the loop information
if (data.loopLength > 0)
{
buffer.LoopLength = data.loopLength;
buffer.LoopBegin = data.loopStart;
}
return buffer;
}
// some code has been omitted for brevity
Array<byte>^ AudioDecoder::Decode(IRandomAccessStream^ audioStream)
{
ComPtr<IMFByteStream> byteStream = nullptr;
ComPtr<IMFSourceReader> reader = nullptr;
// Microsoft Media Foundation byte stream that wraps an IRandomAccessStream
HRESULT hr = MFCreateMFByteStreamOnStreamEx((IUnknown*)audioStream, &byteStream);
// Get IMFSourceReader
hr = MFCreateSourceReaderFromByteStream(byteStream.Get(), NULL, &reader);
auto data = DecodeAsWav(reader);
return data;
}
Array<byte>^ AudioDecoder::DecodeAsWav(ComPtr<IMFSourceReader> reader)
{
ComPtr<IMFMediaType> audioType = ConfigureAudioStream(reader);
// decode the audio data as WAVE
Vector<byte>^ decodedData = GetDecodedAudioData(reader);
// construct WAVE using the decompressed audio data
Array<byte>^ wav = GetWav(decodedData, audioType);
return wav;
}
Vector<byte>^ AudioDecoder::GetDecodedAudioData(ComPtr<IMFSourceReader> reader)
{
Vector<byte>^ dataBytes = ref new Vector<byte>();
DWORD cbBuffer = 0;
DWORD streamIndex = (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM;
ComPtr<IMFSample> sample = nullptr;
ComPtr<IMFMediaBuffer> buffer = nullptr;
BYTE *pAudioData = NULL;
while (true)
{
DWORD dwFlags = 0;
HRESULT hr = reader->ReadSample(streamIndex, 0, 0, &dwFlags, NULL, &sample);
if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM)
{
// End of input file
break;
}
if (sample == nullptr)
{
continue;
}
hr = sample->ConvertToContiguousBuffer(&buffer);
hr = buffer->Lock(&pAudioData, NULL, &cbBuffer);
WriteDataToBuffer(dataBytes, pAudioData, cbBuffer);
hr = buffer->Unlock();
pAudioData = NULL;
}
return dataBytes;
}
// some code has been omitted for brevity
Array<byte>^ AudioDecoder::GetWav(Vector<byte>^ audioBytes, ComPtr<IMFMediaType> audioType)
{
WAVEFORMATEX * wavFormat = NULL;
UINT32 wavFormatBytes = 0;
HRESULT hr = MFCreateWaveFormatExFromMFMediaType(audioType.Get(), &wavFormat, &wavFormatBytes);
int dataChunkBytes = 8 + audioBytes->Size;
int formatChunkBytes = 8 + wavFormatBytes;
int totalBytes = 12 + dataChunkBytes + formatChunkBytes;
auto buffer = ref new Array<byte>(totalBytes);
byte * bufferPtr = buffer->begin();
DWORD header[] = {
FCC('RIFF'), // Riff chunk
totalBytes - 8,
FCC('WAVE'),
FCC('fmt '), // 'fmt ' chunk
wavFormatBytes
};
DWORD dataHeader[] = {
FCC('data'), // data chunk
audioBytes->Size
};
int headerBytes = sizeof(header);
int dataHeaderBytes = sizeof(dataHeader);
// write header - RIFF chunk header + 'fmt ' chunk header
WriteDataToBuffer(bufferPtr, reinterpret_cast<byte*>(header), headerBytes);
bufferPtr += headerBytes;
// write 'fmt ' chunk format
WriteDataToBuffer(bufferPtr, reinterpret_cast<byte*>(wavFormat), wavFormatBytes);
bufferPtr += wavFormatBytes;
// write 'data' chunk header
WriteDataToBuffer(bufferPtr, reinterpret_cast<byte*>(dataHeader), dataHeaderBytes);
bufferPtr += dataHeaderBytes;
// write 'data' chunk data
WriteDataToBuffer(bufferPtr, audioBytes);
return buffer;
}
namespace UniversalAudioComponent
{
public ref class AudioDecoder sealed
{
public:
AudioDecoder();
Array<byte>^ Decode(IRandomAccessStream^ audioStream);
private:
ComPtr<IMFMediaType> ConfigureAudioStream(ComPtr<IMFSourceReader> reader);
Vector<byte>^ GetDecodedAudioData(ComPtr<IMFSourceReader> reader);
Array<byte>^ DecodeAsWav(ComPtr<IMFSourceReader> reader);
Array<byte>^ GetWav(Vector<byte>^ audioBytes, ComPtr<IMFMediaType> audioType);
unsigned int WriteHeader(byte * bufferPtr, ComPtr<IMFMediaType> audioType);
unsigned int WriteFormat(byte * bufferPtr, ComPtr<IMFMediaType> audioType);
};
}
public sealed partial class MainPage : Page
{
// create decoder
private AudioDecoder decoder = new AudioDecoder();
private async Task<IBuffer> GetBuffer(string sampleName)
{
if (!this.buffers.ContainsKey(sampleName))
{
var path = String.Format("ms-appx:///Assets/{0}.mp3", sampleName);
var audioFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(path));
// decode MP3
var audioFileStream = await audioFile.OpenReadAsync();
var wavBytes = this.decoder.Decode(audioFileStream);
var buffer = wavBytes.AsBuffer();
this.buffers[sampleName] = buffer;
}
return this.buffers[sampleName];
}
}
// some code has been omitted for brevity
public sealed partial class MainPage : Page
{
private UniversalAudioPlayer player = new UniversalAudioPlayer();
private Dictionary<string, IBuffer> buffers = new Dictionary<string, IBuffer>();
private async Task ToggleSample(string name, bool isPlaying)
{
// name = someFile.wav
var buffer = await this.GetBuffer(name);
var sample = new AudioSample(name, buffer);
if (isPlaying)
{
this.player.Play(sample);
}
else
{
this.player.Stop(sample);
}
}
private async Task<IBuffer> GetBuffer(string sampleName)
{
IBuffer buffer = null;
if (!this.buffers.ContainsKey(sampleName))
{
var path = String.Format("ms-appx:///Assets/{0}.wav", sampleName);
var audioFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(path));
buffer = await FileIO.ReadBufferAsync(audioFile);
this.buffers[sampleName] = buffer;
}
return this.buffers[sampleName];
}
}
//Little-Endian
const uint32 fourccDATA = 'atad'; // "data" chunk FOURCC
const uint32 fourccFMT = ' tmf'; // "fmt" chunk FOURCC
AudioData RiffReader::Read(IBuffer^ buffer)
{
auto formatChunk = FindChunk(buffer, fourccFMT);
auto dataChunk = FindChunk(buffer, fourccDATA);
auto waveFormat = reinterpret_cast<WAVEFORMATEX*>(formatChunk.data);
AudioData data;
data.bytes = dataChunk.data;
data.numberOfBytes = dataChunk.size;
data.waveFormat = waveFormat;
return data;
}
namespace UniversalAudioComponent
{
class RiffReader sealed
{
private:
ChunkInfo FindChunk(IBuffer^ buffer, uint32 fourcc);
byte* GetBufferByteAccess(IBuffer^ buffer);
public:
RiffReader();
AudioData Read(IBuffer^ buffer);
};
}
// some code has been omitted for brevity
UniversalAudioPlayer::UniversalAudioPlayer()
{
HRESULT hr = XAudio2Create(&xAudio);
hr = xAudio->CreateMasteringVoice(&masteringVoice);
xAudio->StartEngine();
}
void UniversalAudioPlayer::Play(AudioSample^ sample)
{
if (this->IsPlaying(sample))
{
return;
}
auto reader = new RiffReader();
auto data = reader->Read(sample->Buffer);
IXAudio2SourceVoice * voice = this->CreateVoice(data.waveFormat);
XAUDIO2_BUFFER buffer = this->CreateAudioBuffer(data);
HRESULT hr = voice->SubmitSourceBuffer(&buffer);
voice->Start(0);
this->runningVoices[sample->Name] = voice;
}
XAUDIO2_BUFFER UniversalAudioPlayer::CreateAudioBuffer(AudioData data)
{
XAUDIO2_BUFFER buffer = { 0 };
buffer.AudioBytes = data.numberOfBytes;
buffer.pAudioData = data.bytes;
buffer.Flags = XAUDIO2_END_OF_STREAM;
buffer.LoopCount = XAUDIO2_LOOP_INFINITE;
return buffer;
}
namespace UniversalAudioComponent
{
public ref class UniversalAudioPlayer sealed
{
private:
ComPtr<IXAudio2> xAudio;
IXAudio2MasteringVoice * masteringVoice;
std::map<String^, IXAudio2SourceVoice*> runningVoices;
bool IsPlaying(AudioSample^);
IXAudio2SourceVoice* CreateVoice(WAVEFORMATEX* wavFormat);
XAUDIO2_BUFFER CreateAudioBuffer(AudioData data);
public:
UniversalAudioPlayer();
void Play(AudioSample^ sample);
void Stop(AudioSample^ sample);
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment